Snap for 7889170 from be1275c1d5d7af96afdfc64d8ad5ddc0927aadad to sc-v2-release
Change-Id: I52dcbd1b776bd21bb78759bc70782f6da679682e
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index f04998d..0000000
--- 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/Android.bp b/Android.bp
index 2f8a461..2a8a902 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
-// * 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,44 +32,6 @@
],
}
-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"],
@@ -88,287 +39,92 @@
prefer_rlib: true,
crate_name: "crosvm",
srcs: ["src/main.rs"],
-
+ edition: "2018",
+ features: [
+ "audio",
+ "default",
+ "gfxstream",
+ "gpu",
+ "usb",
+ ],
+ rustlibs: [
+ "libacpi_tables",
+ "libarch",
+ "libassertions",
+ "libaudio_streams",
+ "libbase_rust",
+ "libbit_field",
+ "libcrosvm",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libhypervisor",
+ "libkernel_cmdline",
+ "libkernel_loader",
+ "liblibc",
+ "liblibcras",
+ "libminijail_rust",
+ "libnet_util",
+ "libp9",
+ "libresources",
+ "librutabaga_gfx",
+ "libserde_json",
+ "libsync_rust",
+ "libtempfile",
+ "libvhost",
+ "libvm_control",
+ "libvm_memory",
+ ],
+ proc_macros: [
+ "libenumn",
+ "libremain",
+ ],
+ arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
+ },
target: {
linux_bionic_arm64: {
relative_install_path: "aarch64-linux-bionic",
},
+ darwin: {
+ enabled: false,
+ },
linux_glibc_x86_64: {
+ relative_install_path: "x86_64-linux-gnu",
features: [
"gdb",
"gdbstub",
],
- relative_install_path: "x86_64-linux-gnu",
rustlibs: [
"libgdbstub",
+ "libgdbstub_arch",
"libthiserror",
],
},
- darwin: {
- enabled: false,
- },
},
- arch: {
- x86_64: {
- rustlibs: ["libx86_64_rust"],
- },
- arm64: {
- rustlibs: ["libaarch64"],
- },
- },
-
- edition: "2018",
- features: [
- "audio",
- "default",
- "gpu",
- "gfxstream",
- "usb"
- ],
-
- flags: [
- "-C overflow-checks=y",
- ],
ld_flags: [
"-Wl,--rpath,\\$$ORIGIN",
"-Wl,--rpath,\\$$ORIGIN/../../lib64",
],
- rustlibs: [
- "libacpi_tables",
- "libarch",
- "libassertions",
- "libaudio_streams",
- "libbase_rust",
- "libbit_field",
- "libcrosvm",
- "libdata_model",
- "libdevices",
- "libdisk",
- "libhypervisor",
- "libkernel_cmdline",
- "libkernel_loader",
- "liblibc",
- "liblibcras",
- "libminijail_rust",
- "libnet_util",
- "libp9",
- "librand_ish",
- "libresources",
- "librutabaga_gfx",
- "libserde_json",
- "libsync_rust",
- "libtempfile",
- "libvhost",
- "libvhost_user_devices",
- "libvm_control",
- "libvm_memory",
- ],
- proc_macros: [
- "libenumn",
- "libremain",
- ],
}
rust_defaults {
- name: "crosvm_defaults_test",
+ name: "crosvm_test_defaults",
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"],
- },
- arm64: {
- rustlibs: ["libaarch64"],
- },
- },
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- "libthiserror",
- ],
- },
- },
- features: [
- "default",
- ],
- rustlibs: [
- "libacpi_tables",
- "libarch",
- "libassertions",
- "libbase_rust",
- "libbit_field",
- "libdata_model",
- "libdevices",
- "libdisk",
- "libhypervisor",
- "libkernel_cmdline",
- "libkernel_loader",
- "liblibc",
- "libminijail_rust",
- "libnet_util",
- "libp9",
- "librand_ish",
- "libresources",
- "librutabaga_gfx",
- "libserde_json",
- "libsync_rust",
- "libtempfile",
- "libvhost",
- "libvm_control",
- "libvm_memory",
- ],
- proc_macros: [
- "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,
- },
-}
-
-rust_test {
- name: "crosvm_device_test_src_crosvm",
- defaults: ["crosvm_defaults_test"],
- features: [
- "audio",
- ],
- rustlibs: [
- "libaudio_streams",
- "liblibcras",
- ],
-}
-
-rust_defaults {
- name: "crosvm_defaults_crosvm",
- defaults: ["crosvm_defaults"],
- crate_name: "crosvm",
- srcs: ["src/main.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- arch: {
- x86_64: {
- rustlibs: ["libx86_64_rust"],
- },
- arm64: {
- rustlibs: ["libaarch64"],
- },
- },
- features: [
- "default",
- ],
- rustlibs: [
- "libacpi_tables",
- "libarch",
- "libassertions",
- "libaudio_streams",
- "libbase_rust",
- "libbit_field",
- "libcrosvm",
- "libdata_model",
- "libdevices",
- "libdisk",
- "libhypervisor",
- "libkernel_cmdline",
- "libkernel_loader",
- "liblibc",
- "liblibcras",
- "libminijail_rust",
- "libnet_util",
- "libp9",
- "librand_ish",
- "libresources",
- "librutabaga_gfx",
- "libserde_json",
- "libsync_rust",
- "libtempfile",
- "libvhost",
- "libvhost_user_devices",
- "libvm_control",
- "libvm_memory",
- ],
- proc_macros: [
- "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,
- },
-}
-
-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: {
- features: [
- "gfxstream",
- ],
- },
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- "libthiserror",
- ],
- },
- },
- arch: {
- x86_64: {
- rustlibs: ["libx86_64_rust"],
- },
- arm64: {
- rustlibs: ["libaarch64"],
- },
- },
features: [
"audio",
"default",
+ "gfxstream",
"gpu",
"usb",
],
@@ -390,14 +146,12 @@
"libminijail_rust",
"libnet_util",
"libp9",
- "librand_ish",
"libresources",
"librutabaga_gfx",
"libserde_json",
"libsync_rust",
"libtempfile",
"libvhost",
- "libvhost_user_devices",
"libvm_control",
"libvm_memory",
],
@@ -407,62 +161,274 @@
],
}
-// 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
-// ../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master,vhost-user-slave"
-// ../vm_tools/p9/src/lib.rs
-// ../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-// anyhow-1.0.41 "default,std"
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// 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.15 "alloc,std"
-// futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-// futures-core-0.3.15 "alloc,std"
-// futures-io-0.3.15 "std"
-// futures-sink-0.3.15 "alloc,std"
-// futures-task-0.3.15 "alloc,std"
-// futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-// getopts-0.2.21
-// getrandom-0.2.3 "std"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// log-0.4.14
-// memchr-2.4.0 "default,std"
-// memoffset-0.5.6 "default"
-// once_cell-1.8.0 "alloc,default,race,std"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// protobuf-2.24.1
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.1 "std"
-// rand_core-0.6.3 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-width-0.1.8 "default"
-// unicode-xid-0.2.2 "default"
+rust_test_host {
+ name: "crosvm_host_test_src_crosvm",
+ defaults: ["crosvm_test_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+ arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
+ },
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ "gdbstub",
+ ],
+ rustlibs: [
+ "libgdbstub",
+ "libgdbstub_arch",
+ "libthiserror",
+ ],
+ },
+ },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+ ],
+}
+
+rust_test {
+ name: "crosvm_device_test_src_crosvm",
+ defaults: ["crosvm_test_defaults"],
+ arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
+ },
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ "gdbstub",
+ ],
+ rustlibs: [
+ "libgdbstub",
+ "libgdbstub_arch",
+ "libthiserror",
+ ],
+ },
+ },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+ ],
+}
+
+rust_defaults {
+ name: "crosvm_test_defaults_crosvm",
+ defaults: ["crosvm_defaults"],
+ crate_name: "crosvm",
+ srcs: ["src/main.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
+ features: [
+ "audio",
+ "default",
+ "gfxstream",
+ "gpu",
+ "usb",
+ ],
+ rustlibs: [
+ "libacpi_tables",
+ "libarch",
+ "libassertions",
+ "libaudio_streams",
+ "libbase_rust",
+ "libbit_field",
+ "libcrosvm",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libhypervisor",
+ "libkernel_cmdline",
+ "libkernel_loader",
+ "liblibc",
+ "liblibcras",
+ "libminijail_rust",
+ "libnet_util",
+ "libp9",
+ "libresources",
+ "librutabaga_gfx",
+ "libserde_json",
+ "libsync_rust",
+ "libtempfile",
+ "libvhost",
+ "libvm_control",
+ "libvm_memory",
+ ],
+ proc_macros: [
+ "libenumn",
+ "libremain",
+ ],
+}
+
+rust_test_host {
+ name: "crosvm_host_test_src_main",
+ defaults: ["crosvm_test_defaults_crosvm"],
+ test_options: {
+ unit_test: true,
+ },
+ arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
+ },
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ "gdbstub",
+ ],
+ rustlibs: [
+ "libgdbstub",
+ "libgdbstub_arch",
+ "libthiserror",
+ ],
+ },
+ },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+ ],
+}
+
+rust_test {
+ name: "crosvm_device_test_src_main",
+ defaults: ["crosvm_test_defaults_crosvm"],
+ arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
+ },
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ "gdbstub",
+ ],
+ rustlibs: [
+ "libgdbstub",
+ "libgdbstub_arch",
+ "libthiserror",
+ ],
+ },
+ },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+ ],
+}
+
+rust_library {
+ name: "libcrosvm",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "crosvm",
+ srcs: ["src/crosvm.rs"],
+ edition: "2018",
+ features: [
+ "audio",
+ "default",
+ "gfxstream",
+ "gpu",
+ "usb",
+ ],
+ rustlibs: [
+ "libacpi_tables",
+ "libarch",
+ "libassertions",
+ "libaudio_streams",
+ "libbase_rust",
+ "libbit_field",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libhypervisor",
+ "libkernel_cmdline",
+ "libkernel_loader",
+ "liblibc",
+ "liblibcras",
+ "libminijail_rust",
+ "libnet_util",
+ "libp9",
+ "libresources",
+ "librutabaga_gfx",
+ "libserde_json",
+ "libsync_rust",
+ "libtempfile",
+ "libvhost",
+ "libvm_control",
+ "libvm_memory",
+ ],
+ proc_macros: [
+ "libenumn",
+ "libremain",
+ ],
+ arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
+ },
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ "gdbstub",
+ ],
+ rustlibs: [
+ "libgdbstub",
+ "libgdbstub_arch",
+ "libthiserror",
+ ],
+ },
+ },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+ ],
+}
+
+rust_defaults {
+ name: "crosvm_defaults",
+ edition: "2018",
+ enabled: false,
+ target: {
+ linux_glibc_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/Cargo.lock b/Cargo.lock
index fde8abf..370fab0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -48,7 +48,7 @@
"acpi_tables",
"base",
"devices",
- "gdbstub",
+ "gdbstub_arch",
"hypervisor",
"kernel_cmdline",
"libc",
@@ -86,18 +86,14 @@
name = "audio_streams"
version = "0.1.0"
dependencies = [
+ "async-trait",
+ "cros_async",
"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"
@@ -152,20 +148,18 @@
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
-name = "cloudabi"
-version = "0.0.3"
+name = "cfg-if"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-dependencies = [
- "bitflags",
-]
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
-name = "cras-sys"
-version = "0.1.0"
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
- "audio_streams",
- "data_model",
+ "cfg-if 1.0.0",
]
[[package]]
@@ -179,6 +173,7 @@
"intrusive-collections",
"io_uring",
"libc",
+ "once_cell",
"paste",
"pin-utils",
"slab",
@@ -188,13 +183,6 @@
]
[[package]]
-name = "cros_fuzz"
-version = "0.1.0"
-dependencies = [
- "rand_core 0.4.2",
-]
-
-[[package]]
name = "crosvm"
version = "0.1.0"
dependencies = [
@@ -211,6 +199,7 @@
"disk",
"enumn",
"gdbstub",
+ "gdbstub_arch",
"hypervisor",
"kernel_cmdline",
"kernel_loader",
@@ -223,7 +212,6 @@
"p9",
"protobuf",
"protos",
- "rand_ish",
"remain",
"resources",
"rutabaga_gfx",
@@ -232,31 +220,12 @@
"tempfile",
"thiserror",
"vhost",
- "vhost_user_devices",
"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 = [
@@ -314,7 +283,6 @@
"p9",
"power_monitor",
"protos",
- "rand_ish",
"remain",
"resources",
"rutabaga_gfx",
@@ -340,6 +308,7 @@
dependencies = [
"async-trait",
"base",
+ "crc32fast",
"cros_async",
"data_model",
"futures",
@@ -349,6 +318,7 @@
"remain",
"tempfile",
"thiserror",
+ "uuid",
"vm_memory",
]
@@ -368,12 +338,6 @@
]
[[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 = [
@@ -387,9 +351,9 @@
[[package]]
name = "futures"
-version = "0.3.1"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987"
+checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1"
dependencies = [
"futures-channel",
"futures-core",
@@ -401,9 +365,9 @@
[[package]]
name = "futures-channel"
-version = "0.3.1"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86"
+checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939"
dependencies = [
"futures-core",
"futures-sink",
@@ -411,33 +375,33 @@
[[package]]
name = "futures-core"
-version = "0.3.1"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866"
+checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94"
[[package]]
name = "futures-io"
-version = "0.3.1"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff"
+checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59"
[[package]]
name = "futures-sink"
-version = "0.3.1"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16"
+checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3"
[[package]]
name = "futures-task"
-version = "0.3.1"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
+checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80"
[[package]]
name = "futures-util"
-version = "0.3.1"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
+checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1"
dependencies = [
"futures-channel",
"futures-core",
@@ -445,17 +409,18 @@
"futures-sink",
"futures-task",
"memchr",
+ "pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "gdbstub"
-version = "0.4.2"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224c17cf54ffe7e084343f25c7f2881a399bea69862ecaf5bc97f0f6586ba0dc"
+checksum = "7e135587d3f6eee6fa02c4ba174270c2337424e6d852c156942c0840b3c0f5cc"
dependencies = [
- "cfg-if",
+ "cfg-if 0.1.10",
"log",
"managed",
"num-traits",
@@ -463,15 +428,36 @@
]
[[package]]
-name = "getopts"
-version = "0.2.18"
+name = "gdbstub_arch"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
+checksum = "e358b9c0e1468eae66099062e47bb502849308b987b74b5e72f1936397c33c16"
+dependencies = [
+ "gdbstub",
+ "num-traits",
+]
+
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
dependencies = [
"unicode-width",
]
[[package]]
+name = "getrandom"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "wasi",
+]
+
+[[package]]
name = "gpu_display"
version = "0.1.0"
dependencies = [
@@ -484,6 +470,15 @@
]
[[package]]
+name = "hermit-abi"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "hypervisor"
version = "0.1.0"
dependencies = [
@@ -578,20 +573,25 @@
[[package]]
name = "libc"
-version = "0.2.93"
+version = "0.2.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
+checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
+
+[[package]]
+name = "libchromeos"
+version = "0.1.0"
+dependencies = [
+ "data_model",
+ "futures",
+ "intrusive-collections",
+ "libc",
+ "log",
+ "protobuf",
+]
[[package]]
name = "libcras"
version = "0.1.0"
-dependencies = [
- "audio_streams",
- "cras-sys",
- "data_model",
- "libc",
- "sys_util",
-]
[[package]]
name = "libcrosvm_control"
@@ -631,11 +631,11 @@
[[package]]
name = "log"
-version = "0.4.5"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.0",
]
[[package]]
@@ -646,9 +646,9 @@
[[package]]
name = "memchr"
-version = "2.3.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223"
+checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "memoffset"
@@ -656,7 +656,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
dependencies = [
- "autocfg 1.0.1",
+ "autocfg",
]
[[package]]
@@ -700,15 +700,16 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
dependencies = [
- "autocfg 1.0.1",
+ "autocfg",
]
[[package]]
name = "num_cpus"
-version = "1.9.0"
+version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
+ "hermit-abi",
"libc",
]
@@ -723,7 +724,7 @@
version = "0.1.0"
dependencies = [
"libc",
- "sys_util",
+ "libchromeos",
"wire_format_derive",
]
@@ -734,10 +735,16 @@
checksum = "ba7ae1a2180ed02ddfdb5ab70c70d596a26dd642e097bb6fe78b1bde8588ed97"
[[package]]
-name = "pin-utils"
-version = "0.1.0-alpha.4"
+name = "pin-project-lite"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
+checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
@@ -766,42 +773,43 @@
[[package]]
name = "proc-macro2"
-version = "1.0.24"
+version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
dependencies = [
"unicode-xid",
]
[[package]]
name = "protobuf"
-version = "2.8.1"
+version = "2.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20"
+checksum = "db50e77ae196458ccd3dc58a31ea1a90b0698ab1b7928d89f644c25d72070267"
[[package]]
name = "protobuf-codegen"
-version = "2.8.1"
+version = "2.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a"
+checksum = "09321cef9bee9ddd36884f97b7f7cc92a586cdc74205c4b3aeba65b5fc9c6f90"
dependencies = [
"protobuf",
]
[[package]]
name = "protoc"
-version = "2.8.1"
+version = "2.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3998c4bc0af8ccbd3cc68245ee9f72663c5ae2fb78bc48ff7719aef11562edea"
+checksum = "c367feabb5f78ca3b2ec25e2c4a5f4f0826017d7fb634f52961afd1a6613d1fb"
dependencies = [
"log",
+ "which",
]
[[package]]
name = "protoc-rust"
-version = "2.8.1"
+version = "2.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234c97039c32bb58a883d0deafa57db37e59428ce536f3bdfe1c46cffec04113"
+checksum = "7bb2c1038f8014a2e42fdffec03ffc03f574a8bf66b0ac32f1b6941681eb1317"
dependencies = [
"protobuf",
"protobuf-codegen",
@@ -830,133 +838,14 @@
[[package]]
name = "quote"
-version = "1.0.2"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
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"
@@ -994,18 +883,18 @@
[[package]]
name = "serde"
-version = "1.0.121"
+version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6159e3c76cab06f6bc466244d43b35e77e9500cd685da87620addadc2a4c40b1"
+checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.121"
+version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3fcab8778dc651bc65cfab2e4eb64996f3c912b74002fb379c94517e1f27c46"
+checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [
"proc-macro2",
"quote",
@@ -1025,9 +914,9 @@
[[package]]
name = "slab"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
[[package]]
name = "smallvec"
@@ -1037,9 +926,9 @@
[[package]]
name = "syn"
-version = "1.0.58"
+version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
+checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
dependencies = [
"proc-macro2",
"quote",
@@ -1109,15 +998,15 @@
[[package]]
name = "unicode-width"
-version = "0.1.5"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
-version = "0.2.0"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "usb_sys"
@@ -1139,6 +1028,15 @@
]
[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
name = "vfio_sys"
version = "0.1.0"
dependencies = [
@@ -1162,6 +1060,7 @@
version = "0.1.0"
dependencies = [
"anyhow",
+ "arch",
"base",
"cros_async",
"data_model",
@@ -1195,7 +1094,7 @@
dependencies = [
"base",
"data_model",
- "gdbstub",
+ "gdbstub_arch",
"hypervisor",
"libc",
"resources",
@@ -1228,28 +1127,22 @@
]
[[package]]
-name = "winapi"
-version = "0.3.9"
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "which"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef"
dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
+ "libc",
+ "thiserror",
]
[[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 = [
@@ -1268,7 +1161,7 @@
"base",
"data_model",
"devices",
- "gdbstub",
+ "gdbstub_arch",
"hypervisor",
"kernel_cmdline",
"kernel_loader",
diff --git a/Cargo.toml b/Cargo.toml
index 2b5312c..d967db1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,33 +21,42 @@
panic = 'abort'
overflow-checks = 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 = [
- # Disabled to support running cargo2android while cros-fuzz is not
- # available in Android.
- # "fuzz",
+ "fuzz",
"qcow_utils",
"integration_tests",
"libcrosvm_control",
+ "vhost_user_devices",
]
exclude = [
"assertions",
"base",
+ "common",
"cros_async",
"data_model",
- "rand_ish",
"sync",
"sys_util",
"tempfile",
"vm_memory",
+ "audio_streams",
]
[features]
+audio = ["devices/audio"]
+audio_cras = ["devices/audio_cras"]
+chromeos = ["base/chromeos", "audio_cras"]
+composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
default = ["audio", "gpu", "usb"]
-chromeos = ["base/chromeos"]
default-no-sandbox = []
direct = ["devices/direct"]
-audio = ["devices/audio"]
+gdb = ["gdbstub", "gdbstub_arch", "thiserror", "arch/gdb", "vm_control/gdb", "x86_64/gdb"]
+gfxstream = ["devices/gfxstream"]
gpu = ["devices/gpu"]
plugin = ["protos/plugin", "crosvm_plugin", "kvm", "kvm_sys", "protobuf"]
power-monitor-powerd = ["arch/power-monitor-powerd"]
@@ -55,13 +64,10 @@
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]
arch = { path = "arch" }
@@ -74,7 +80,8 @@
devices = { path = "devices" }
disk = { path = "disk" }
enumn = { path = "enumn" }
-gdbstub = { version = "0.4.0", optional = true }
+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" }
@@ -85,10 +92,9 @@
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" }
serde_json = "*"
@@ -96,7 +102,6 @@
tempfile = "*"
thiserror = { version = "1.0.20", optional = true }
vhost = { path = "vhost" }
-vhost_user_devices = { path = "vhost_user_devices" }
vm_control = { path = "vm_control" }
acpi_tables = { path = "acpi_tables" }
vm_memory = { path = "vm_memory" }
@@ -112,19 +117,15 @@
[patch.crates-io]
assertions = { path = "assertions" }
-audio_streams = { path = "../adhd/audio_streams" } # ignored by ebuild
+audio_streams = { path = "audio_streams" }
base = { path = "base" }
-cras-sys = { path = "../adhd/cras/client/cras-sys" } # ignored by ebuild
-# Disabled to support running cargo2android while cros-fuzz is not
-# available in Android.
-# cros_fuzz = { path = "../../platform2/cros-fuzz" } # ignored by ebuild
+cros_fuzz = { path = "common/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
-minijail = { path = "../minijail/rust/minijail" } # ignored by ebuild
-p9 = { path = "../vm_tools/p9" } # ignored by ebuild
+libcras = { path = "libcras_stub" } # ignored by ebuild
+p9 = { path = "common/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/vmm_vhost", features = ["vhost-user-master"] } # ignored by ebuild
+wire_format_derive = { path = "common/p9/wire_format_derive" } # ignored by ebuild
+minijail = { path = "../minijail/rust/minijail" } # ignored by ebuild
+vmm_vhost = { path = "../rust/vmm_vhost", features = ["vhost-user-master", "vhost-user-slave"] } # ignored by ebuild
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
deleted file mode 100644
index 91b4973..0000000
--- 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/README.md b/README.md
index 43c119f..68a1766 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,10 @@
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.
+
[Chromium OS developer guide]: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/developer_guide.md
### Building with Docker
@@ -28,15 +32,17 @@
>**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.
+Crosvm uses submodules to manage external dependencies. Initialize them via:
```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
+git submodule update --init
+```
+
+It is recommended to enable automatic recursive operations to keep the
+submodules in sync with the main repository:
+
+```sh
+git config --global submodule.recurse true
```
A basic crosvm build links against `libcap`. On a Debian-based system,
@@ -73,8 +79,6 @@
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
diff --git a/aarch64/Android.bp b/aarch64/Android.bp
index 833d4af..415b9bf 100644
--- a/aarch64/Android.bp
+++ b/aarch64/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 target and arch to rules below.
+// 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
@@ -11,10 +11,9 @@
}
rust_defaults {
- name: "aarch64_defaults",
+ name: "aarch64_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "aarch64",
- // has rustc warnings
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
@@ -36,6 +35,14 @@
"libvm_memory",
],
proc_macros: ["libremain"],
+}
+
+rust_test_host {
+ name: "aarch64_host_test_src_lib",
+ defaults: ["aarch64_test_defaults"],
+ test_options: {
+ unit_test: true,
+ },
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
@@ -51,23 +58,27 @@
},
}
-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"],
+ defaults: ["aarch64_test_defaults"],
+ 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_library {
name: "libaarch64",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "aarch64",
srcs: ["src/lib.rs"],
@@ -103,93 +114,3 @@
},
},
}
-
-// 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
-// ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master"
-// ../../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
-// ../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.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// cfg-if-1.0.0
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.15 "alloc,std"
-// futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-// futures-core-0.3.15 "alloc,std"
-// futures-io-0.3.15 "std"
-// futures-sink-0.3.15 "alloc,std"
-// futures-task-0.3.15 "alloc,std"
-// futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-// getrandom-0.2.3 "std"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// log-0.4.14
-// memchr-2.4.0 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// protobuf-2.24.1
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.1 "std"
-// rand_core-0.6.3 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/aarch64/cargo2android.json b/aarch64/cargo2android.json
new file mode 100644
index 0000000..42ec06f
--- /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 0000000..54602b2
--- /dev/null
+++ b/aarch64/cargo2android_arch.bp
@@ -0,0 +1,13 @@
+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,
+ },
+}
\ No newline at end of file
diff --git a/aarch64/src/fdt.rs b/aarch64/src/fdt.rs
index 376bd60..b4874a1 100644
--- a/aarch64/src/fdt.rs
+++ b/aarch64/src/fdt.rs
@@ -46,6 +46,7 @@
// 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;
@@ -71,6 +72,27 @@
Ok(())
}
+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,
@@ -264,6 +286,7 @@
pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
pci_device_base: u64,
pci_device_size: u64,
+ 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
@@ -332,6 +355,9 @@
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(())
@@ -398,6 +424,7 @@
is_gicv3: bool,
use_pmu: bool,
psci_version: PsciVersion,
+ swiotlb: Option<u64>,
) -> Result<()> {
let mut fdt = FdtWriter::new(&[]);
@@ -412,6 +439,7 @@
}
create_chosen_node(&mut fdt, cmdline, initrd)?;
create_memory_node(&mut fdt, guest_mem)?;
+ 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)?;
@@ -420,7 +448,13 @@
}
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_device_base,
+ pci_device_size,
+ dma_pool_phandle,
+ )?;
create_rtc_node(&mut fdt)?;
// End giant node
fdt.end_node(root_node)?;
diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs
index b0c7e9b..4b13558 100644
--- a/aarch64/src/lib.rs
+++ b/aarch64/src/lib.rs
@@ -248,6 +248,10 @@
Ok(arch_memory_regions(components.memory_size))
}
+ fn get_phys_max_addr() -> u64 {
+ u64::max_value()
+ }
+
fn create_system_allocator(guest_mem: &GuestMemory) -> SystemAllocator {
Self::get_resource_allocator(guest_mem.memory_size())
}
@@ -274,14 +278,6 @@
let mem = vm.get_memory().clone();
- 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)?;
- }
-
let mut use_pmu = vm
.get_hypervisor()
.check_capability(&HypervisorCap::ArmPmuV3);
@@ -323,6 +319,14 @@
.map_err(Error::MapPvtimeError)?;
}
+ 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)?;
+ }
+
for (vcpu_id, vcpu) in vcpus.iter().enumerate() {
use_pmu &= vcpu.init_pmu(AARCH64_PMU_IRQ as u64 + 16).is_ok();
if has_pvtime {
@@ -435,6 +439,7 @@
irq_chip.get_vgic_version() == DeviceKind::ArmVgicV3,
use_pmu,
psci_version,
+ components.swiotlb,
)
.map_err(Error::CreateFdt)?;
@@ -451,7 +456,9 @@
pid_debug_label_map,
suspend_evt,
rt_cpus: components.rt_cpus,
+ delay_rt: components.delay_rt,
bat_control: None,
+ resume_notify_devices: Vec::new(),
})
}
diff --git a/acpi_tables/Android.bp b/acpi_tables/Android.bp
index 8b035df..8a16674 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 {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "acpi_tables_defaults",
+ name: "acpi_tables_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "acpi_tables",
srcs: ["src/lib.rs"],
@@ -26,7 +26,7 @@
rust_test_host {
name: "acpi_tables_host_test_src_lib",
- defaults: ["acpi_tables_defaults"],
+ defaults: ["acpi_tables_test_defaults"],
test_options: {
unit_test: true,
},
@@ -34,7 +34,7 @@
rust_test {
name: "acpi_tables_device_test_src_lib",
- defaults: ["acpi_tables_defaults"],
+ defaults: ["acpi_tables_test_defaults"],
}
rust_library {
@@ -49,15 +49,3 @@
"libtempfile",
],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../data_model/src/lib.rs
-// ../tempfile/src/lib.rs
-// libc-0.2.97 "default,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.2 "default"
diff --git a/all2android.sh b/all2android.sh
index 1ab3f1d..f724ef2 100755
--- a/all2android.sh
+++ b/all2android.sh
@@ -6,15 +6,21 @@
set -e
cargo2android() {
- cargo2android.py --run --device --tests --dependencies $@
- rm -r cargo.out
+ # Some crates need special options to cargo2android.py, if there's a config file then use it.
+ if [[ -f "cargo2android.json" ]]; then
+ cargo2android.py --config cargo2android.json
+ else
+ cargo2android.py --run --device --tests $@
+ fi
+ rm -f cargo.out
rm -rf target.tmp || /bin/true
}
# Run in the main crosvm directory.
cargo2android --no-subdir
-for dir in */src
+initial_dir=`pwd`
+for dir in */src common/*/src
do
base=`dirname $dir`
echo "$base"
@@ -37,5 +43,5 @@
cargo2android --global_defaults=crosvm_defaults --add_workspace
fi
- cd ..
+ cd "$initial_dir"
done
diff --git a/arch/Android.bp b/arch/Android.bp
index 01d23bb..b688c03 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
@@ -11,24 +11,13 @@
}
rust_defaults {
- name: "arch_defaults",
+ name: "arch_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "arch",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
- },
rustlibs: [
"libacpi_tables",
"libbase_rust",
@@ -44,22 +33,39 @@
"libvm_control",
"libvm_memory",
],
- shared_libs: [
- "libfdt", // added manually
- ],
}
rust_test_host {
name: "arch_host_test_src_lib",
- defaults: ["arch_defaults"],
+ defaults: ["arch_test_defaults"],
test_options: {
unit_test: true,
},
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
}
rust_test {
name: "arch_device_test_src_lib",
- defaults: ["arch_defaults"],
+ defaults: ["arch_test_defaults"],
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
}
rust_library {
@@ -69,17 +75,6 @@
crate_name: "arch",
srcs: ["src/lib.rs"],
edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
- },
rustlibs: [
"libacpi_tables",
"libbase_rust",
@@ -95,96 +90,14 @@
"libvm_control",
"libvm_memory",
],
- shared_libs: [
- "libfdt", // added manually
- ],
+ target: {
+ linux_glibc_x86_64: {
+ 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
-// ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master"
-// ../../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
-// ../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.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// cfg-if-1.0.0
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.15 "alloc,std"
-// futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-// futures-core-0.3.15 "alloc,std"
-// futures-io-0.3.15 "std"
-// futures-sink-0.3.15 "alloc,std"
-// futures-task-0.3.15 "alloc,std"
-// futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-// getrandom-0.2.3 "std"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// log-0.4.14
-// memchr-2.4.0 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// protobuf-2.24.1
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.1 "std"
-// rand_core-0.6.3 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/arch/Cargo.toml b/arch/Cargo.toml
index 095cc48..728e7fe 100644
--- a/arch/Cargo.toml
+++ b/arch/Cargo.toml
@@ -6,12 +6,12 @@
[features]
power-monitor-powerd = ["power_monitor/powerd"]
-gdb = ["gdbstub"]
+gdb = ["gdbstub_arch"]
[dependencies]
acpi_tables = { path = "../acpi_tables" }
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 = "*"
diff --git a/arch/cargo2android.json b/arch/cargo2android.json
new file mode 100644
index 0000000..2ae8879
--- /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 0000000..0787bb5
--- /dev/null
+++ b/arch/cargo2android_gdb.bp
@@ -0,0 +1,10 @@
+target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+}
\ No newline at end of file
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index e11c485..f900872 100644
--- a/arch/src/lib.rs
+++ b/arch/src/lib.rs
@@ -17,11 +17,11 @@
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,
+ Bus, BusDevice, BusError, BusResumeDevice, IrqChip, PciAddress, PciDevice, PciDeviceError,
+ PciInterruptPin, PciRoot, ProtectionType, ProxyDevice,
};
use hypervisor::{IoEventAddress, Vm};
use minijail::Minijail;
@@ -31,7 +31,7 @@
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 {
@@ -76,6 +76,7 @@
/// 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>>,
@@ -87,9 +88,9 @@
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.
@@ -113,9 +114,12 @@
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)>,
+ /// Devices to be notified before the system resumes from the S3 suspended state.
+ pub resume_notify_devices: Vec<Arc<Mutex<dyn BusResumeDevice>>>,
}
/// The device and optional jail.
@@ -146,6 +150,8 @@
/// * `guest_mem` - The memory to be used as a template for the `SystemAllocator`.
fn create_system_allocator(guest_mem: &GuestMemory) -> SystemAllocator;
+ fn get_phys_max_addr() -> u64;
+
/// Takes `VmComponents` and generates a `RunnableLinuxVm`.
///
/// # Arguments
@@ -365,6 +371,7 @@
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());
let irqfd = Event::new().map_err(DeviceRegistrationError::EventCreate)?;
let irq_resample_fd = Event::new().map_err(DeviceRegistrationError::EventCreate)?;
diff --git a/assertions/Android.bp b/assertions/Android.bp
index 5692b59..734a4d4 100644
--- a/assertions/Android.bp
+++ b/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 {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "assertions_defaults",
+ name: "assertions_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "assertions",
srcs: ["src/lib.rs"],
@@ -22,7 +22,7 @@
rust_test_host {
name: "assertions_host_test_src_lib",
- defaults: ["assertions_defaults"],
+ defaults: ["assertions_test_defaults"],
test_options: {
unit_test: true,
},
@@ -30,7 +30,7 @@
rust_test {
name: "assertions_device_test_src_lib",
- defaults: ["assertions_defaults"],
+ defaults: ["assertions_test_defaults"],
}
rust_library {
diff --git a/audio_streams/Android.bp b/audio_streams/Android.bp
new file mode 100644
index 0000000..0268069
--- /dev/null
+++ b/audio_streams/Android.bp
@@ -0,0 +1,55 @@
+// 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_defaults {
+ name: "audio_streams_test_defaults",
+ defaults: ["crosvm_defaults"],
+ crate_name: "audio_streams",
+ srcs: ["src/audio_streams.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
+ rustlibs: [
+ "libcros_async",
+ "libsync_rust",
+ "libsys_util",
+ ],
+ proc_macros: ["libasync_trait"],
+}
+
+rust_test_host {
+ name: "audio_streams_host_test_src_audio_streams",
+ defaults: ["audio_streams_test_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "audio_streams_device_test_src_audio_streams",
+ defaults: ["audio_streams_test_defaults"],
+}
+
+rust_library {
+ name: "libaudio_streams",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "audio_streams",
+ srcs: ["src/audio_streams.rs"],
+ edition: "2018",
+ rustlibs: [
+ "libcros_async",
+ "libsync_rust",
+ "libsys_util",
+ ],
+ proc_macros: ["libasync_trait"],
+}
diff --git a/audio_streams/Cargo.toml b/audio_streams/Cargo.toml
new file mode 100644
index 0000000..49160a8
--- /dev/null
+++ b/audio_streams/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "audio_streams"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[lib]
+path = "src/audio_streams.rs"
+
+[dependencies]
+async-trait = "0.1.36"
+cros_async = { path = "../cros_async" } # provided by ebuild
+sync = { path = "../sync" } # provided by ebuild
+sys_util = { path = "../sys_util" } # provided by ebuild
diff --git a/audio_streams/README.md b/audio_streams/README.md
new file mode 100644
index 0000000..d3a02e8
--- /dev/null
+++ b/audio_streams/README.md
@@ -0,0 +1,6 @@
+# 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/audio_streams/src/audio_streams.rs b/audio_streams/src/audio_streams.rs
new file mode 100644
index 0000000..b125196
--- /dev/null
+++ b/audio_streams/src/audio_streams.rs
@@ -0,0 +1,838 @@
+// 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 (())
+//! # }
+//! ```
+
+use async_trait::async_trait;
+use std::cmp::min;
+use std::error;
+use std::fmt::{self, Display};
+use std::io::{self, Read, Write};
+use std::os::unix::io::RawFd;
+use std::result::Result;
+use std::str::FromStr;
+use std::time::{Duration, Instant};
+
+use cros_async::{Executor, TimerAsync};
+
+#[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`.
+#[derive(Debug)]
+pub enum StreamEffectError {
+ InvalidEffect,
+}
+
+impl error::Error for StreamEffectError {}
+
+impl Display for StreamEffectError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ StreamEffectError::InvalidEffect => write!(f, "Must be in [EchoCancellation, aec]"),
+ }
+ }
+}
+
+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),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub enum Error {
+ Unimplemented,
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Error::Unimplemented => write!(f, "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: &Executor,
+ ) -> 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,
+ ) -> 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,
+ _ex: &Executor,
+ ) -> 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.
+ 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: &Executor,
+ ) -> 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: &Executor,
+ ) -> 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: &Executor,
+) -> 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`.
+#[derive(Debug)]
+pub enum PlaybackBufferError {
+ InvalidLength,
+}
+
+impl error::Error for PlaybackBufferError {}
+
+impl Display for PlaybackBufferError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ PlaybackBufferError::InvalidLength => write!(f, "Invalid buffer length"),
+ }
+ }
+}
+
+/// `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)
+ }
+
+ /// Writes complete frames to `buf`, and return the number of bytes written.
+ pub fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ // only write complete frames.
+ let len = buf.len() / self.frame_size * self.frame_size;
+ let written = (&mut self.buffer[self.offset..]).write(&buf[..len])?;
+ self.offset += written;
+ Ok(written)
+ }
+
+ /// 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)
+ }
+}
+
+impl<'a> Write for AudioBuffer<'a> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ // only write complete frames.
+ let len = buf.len() / self.frame_size * self.frame_size;
+ let written = (&mut self.buffer[self.offset..]).write(&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)
+ }
+}
+
+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: &Executor,
+ ) -> Result<AsyncPlaybackBuffer<'a>, BoxError> {
+ if let Some(start_time) = self.start_time {
+ let elapsed = start_time.elapsed();
+ if elapsed < self.next_frame {
+ TimerAsync::sleep(ex, 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: &Executor,
+ ) -> 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::*;
+
+ #[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 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);
+ }
+
+ let ex = Executor::new().expect("failed to create executor");
+ ex.run_until(this_test()).unwrap();
+ }
+
+ #[test]
+ fn consumption_rate_async() {
+ async fn this_test(ex: &Executor) {
+ 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 = Executor::new().expect("failed to create executor");
+ ex.run_until(this_test(&ex)).unwrap();
+ }
+}
diff --git a/audio_streams/src/capture.rs b/audio_streams/src/capture.rs
new file mode 100644
index 0000000..70d9832
--- /dev/null
+++ b/audio_streams/src/capture.rs
@@ -0,0 +1,465 @@
+// 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::{
+ error,
+ fmt::{self, Display},
+ io::{self, Read},
+ time::{Duration, Instant},
+};
+
+use super::{
+ AsyncBufferCommit, AudioBuffer, BoxError, BufferCommit, NoopBufferCommit, SampleFormat,
+};
+use cros_async::{Executor, TimerAsync};
+
+/// `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: &Executor,
+ ) -> 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: &Executor,
+ ) -> 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`.
+#[derive(Debug)]
+pub enum CaptureBufferError {
+ InvalidLength,
+}
+
+impl error::Error for CaptureBufferError {}
+
+impl Display for CaptureBufferError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ CaptureBufferError::InvalidLength => write!(f, "Invalid buffer length"),
+ }
+ }
+}
+
+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)
+ }
+}
+
+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: &Executor,
+ ) -> Result<AsyncCaptureBuffer<'a>, BoxError> {
+ if let Some(start_time) = self.start_time {
+ let elapsed = start_time.elapsed();
+ if elapsed < self.next_frame {
+ TimerAsync::sleep(ex, 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: &Executor,
+) -> 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::*;
+ use super::*;
+
+ #[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);
+ }
+
+ let ex = Executor::new().expect("failed to create executor");
+ ex.run_until(this_test()).unwrap();
+ }
+
+ #[test]
+ fn consumption_rate_async() {
+ async fn this_test(ex: &Executor) {
+ 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),
+ "next_capture_buffer didn't block long enough {}",
+ elapsed.subsec_millis()
+ );
+ Ok(())
+ };
+ async_read_capture_buffer(&mut *stream, assert_func, ex)
+ .await
+ .unwrap();
+ }
+
+ let ex = Executor::new().expect("failed to create executor");
+ ex.run_until(this_test(&ex)).unwrap();
+ }
+}
diff --git a/audio_streams/src/shm_streams.rs b/audio_streams/src/shm_streams.rs
new file mode 100644
index 0000000..b11626f
--- /dev/null
+++ b/audio_streams/src/shm_streams.rs
@@ -0,0 +1,568 @@
+// 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::error;
+use std::fmt;
+use std::os::unix::io::RawFd;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+
+use sync::{Condvar, Mutex};
+use sys_util::SharedMemory;
+
+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<()>;
+}
+
+#[derive(Debug)]
+pub enum Error {
+ TooManyFrames(usize, usize),
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Error::TooManyFrames(provided, requested) => write!(
+ f,
+ "Provided number of frames {} exceeds requested number of frames {}",
+ provided, requested
+ ),
+ }
+ }
+}
+
+/// `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>>;
+}
+
+/// `ShmStreamSource` creates streams for playback or capture of audio.
+pub trait ShmStreamSource: 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: &SharedMemory,
+ 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.
+ 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 ShmStreamSource for NullShmStreamSource {
+ fn new_stream(
+ &mut self,
+ _direction: StreamDirection,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ _effects: &[StreamEffect],
+ _client_shm: &SharedMemory,
+ _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 ShmStreamSource for MockShmStreamSource {
+ fn new_stream(
+ &mut self,
+ _direction: StreamDirection,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ _effects: &[StreamEffect],
+ _client_shm: &SharedMemory,
+ _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))
+ }
+}
+
+#[cfg(test)]
+pub mod tests {
+ use super::*;
+
+ #[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 = SharedMemory::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 = SharedMemory::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/base/Android.bp b/base/Android.bp
index e1a7f69..19e27d2 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 --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "base_defaults",
+ name: "base_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "base",
srcs: ["src/lib.rs"],
@@ -33,7 +33,7 @@
rust_test_host {
name: "base_host_test_src_lib",
- defaults: ["base_defaults"],
+ defaults: ["base_test_defaults"],
test_options: {
unit_test: true,
},
@@ -41,7 +41,7 @@
rust_test {
name: "base_device_test_src_lib",
- defaults: ["base_defaults"],
+ defaults: ["base_test_defaults"],
}
rust_library {
@@ -64,42 +64,3 @@
"libthiserror",
],
}
-
-// 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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/base/src/ioctl.rs b/base/src/ioctl.rs
index 307d5d9..d5a91fc 100644
--- a/base/src/ioctl.rs
+++ b/base/src/ioctl.rs
@@ -6,16 +6,22 @@
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 +31,8 @@
}
/// 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 +46,8 @@
}
/// 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 +57,8 @@
}
/// 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/mmap.rs b/base/src/mmap.rs
index d2bd4cb..16444fb 100644
--- a/base/src/mmap.rs
+++ b/base/src/mmap.rs
@@ -74,6 +74,7 @@
}
pub trait MemoryMappingBuilderUnix<'a> {
+ #[allow(clippy::wrong_self_convention)]
fn from_descriptor(self, descriptor: &'a dyn AsRawDescriptor) -> MemoryMappingBuilder;
}
@@ -89,6 +90,7 @@
/// 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 @@
///
/// 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
diff --git a/base/src/shm.rs b/base/src/shm.rs
index a445f3d..eb4da79 100644
--- a/base/src/shm.rs
+++ b/base/src/shm.rs
@@ -87,9 +87,9 @@
}
}
-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()) }
}
}
diff --git a/base/src/tube.rs b/base/src/tube.rs
index 21917c9..613731b 100644
--- a/base/src/tube.rs
+++ b/base/src/tube.rs
@@ -104,13 +104,6 @@
.map_err(Error::Json)
}
- /// 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 set_send_timeout(&self, timeout: Option<Duration>) -> Result<()> {
self.socket
.set_write_timeout(timeout)
@@ -157,9 +150,9 @@
}
}
-impl Into<Tube> for AsyncTube {
- fn into(self) -> Tube {
- self.inner.into_source()
+impl From<AsyncTube> for Tube {
+ fn from(at: AsyncTube) -> Tube {
+ at.inner.into_source()
}
}
diff --git a/bin/clippy b/bin/clippy
index 6d8568d..3fa2a24 100755
--- a/bin/clippy
+++ b/bin/clippy
@@ -56,7 +56,6 @@
should_implement_trait
single_char_pattern
too_many_arguments
- transmute_ptr_to_ptr
trivially_copy_pass_by_ref
type_complexity
unreadable_literal
@@ -65,6 +64,26 @@
new-ret-no-self
)
+FEATURES=(
+ default
+ direct
+ audio
+ gpu
+ plugin
+ tpm
+ usb
+ video-decoder
+ video-encoder
+ wl-dmabuf
+ x
+ virgl_renderer_next
+ composite-disk
+ virgl_renderer
+ gfxstream
+ gdb
+)
+printf -v FEATURES_LIST '%s,' "${FEATURES[@]}"
+
# Needed or else clippy won't re-run on code that has already compiled.
if [[ "${USE_CACHE}" == false ]]; then
cargo clean
@@ -76,5 +95,5 @@
RUSTFLAGS="${RUSTFLAGS:-}"
export RUSTFLAGS="$RUSTFLAGS --sysroot=$RUST_SYSROOT"
-cargo clippy --all-features --all-targets -- ${SUPPRESS[@]/#/-Aclippy::} \
- "${CLIPPY_ARGS[@]}" -D warnings
+cargo clippy --features ${FEATURES_LIST} --all-targets -- \
+ ${SUPPRESS[@]/#/-Aclippy::} "${CLIPPY_ARGS[@]}" -D warnings
diff --git a/bit_field/Android.bp b/bit_field/Android.bp
index 568f047..a5a7a7b 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 --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,7 +11,7 @@
}
rust_defaults {
- name: "bit_field_defaults",
+ name: "bit_field_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "bit_field",
srcs: ["src/lib.rs"],
@@ -23,7 +23,7 @@
rust_test_host {
name: "bit_field_host_test_src_lib",
- defaults: ["bit_field_defaults"],
+ defaults: ["bit_field_test_defaults"],
test_options: {
unit_test: true,
},
@@ -31,11 +31,11 @@
rust_test {
name: "bit_field_device_test_src_lib",
- defaults: ["bit_field_defaults"],
+ defaults: ["bit_field_test_defaults"],
}
rust_defaults {
- name: "bit_field_defaults_bit_field",
+ name: "bit_field_test_defaults_bit_field",
defaults: ["crosvm_defaults"],
crate_name: "bit_field",
test_suites: ["general-tests"],
@@ -49,7 +49,7 @@
rust_test_host {
name: "bit_field_host_test_tests_test_enum",
- defaults: ["bit_field_defaults_bit_field"],
+ defaults: ["bit_field_test_defaults_bit_field"],
srcs: ["tests/test_enum.rs"],
test_options: {
unit_test: true,
@@ -58,13 +58,13 @@
rust_test {
name: "bit_field_device_test_tests_test_enum",
- defaults: ["bit_field_defaults_bit_field"],
+ defaults: ["bit_field_test_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"],
+ defaults: ["bit_field_test_defaults_bit_field"],
srcs: ["tests/test_tuple_struct.rs"],
test_options: {
unit_test: true,
@@ -73,7 +73,7 @@
rust_test {
name: "bit_field_device_test_tests_test_tuple_struct",
- defaults: ["bit_field_defaults_bit_field"],
+ defaults: ["bit_field_test_defaults_bit_field"],
srcs: ["tests/test_tuple_struct.rs"],
}
@@ -86,9 +86,3 @@
edition: "2018",
proc_macros: ["libbit_field_derive"],
}
-
-// dependent_library ["feature_list"]
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.2 "default"
diff --git a/bit_field/bit_field_derive/Android.bp b/bit_field/bit_field_derive/Android.bp
index d357ce7..e3923f0 100644
--- a/bit_field/bit_field_derive/Android.bp
+++ b/bit_field/bit_field_derive/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually disabled tests which depend on host only crate.
+// 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,34 +10,6 @@
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "bit_field_derive_defaults",
- 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_test_host {
-// name: "bit_field_derive_host_test_bit_field_derive",
-// defaults: ["bit_field_derive_defaults"],
-// test_options: {
-// unit_test: true,
-// },
-//}
-
-//rust_test {
-// name: "bit_field_derive_device_test_bit_field_derive",
-// defaults: ["bit_field_derive_defaults"],
-//}
-
rust_proc_macro {
name: "libbit_field_derive",
defaults: ["crosvm_defaults"],
@@ -50,9 +22,3 @@
"libsyn",
],
}
-
-// dependent_library ["feature_list"]
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.2 "default"
diff --git a/bit_field/bit_field_derive/cargo2android.json b/bit_field/bit_field_derive/cargo2android.json
new file mode 100644
index 0000000..9a6eee3
--- /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/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..176988e
--- /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": "default,gfxstream",
+ "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
+}
\ No newline at end of file
diff --git a/cargo2android_defaults.bp b/cargo2android_defaults.bp
new file mode 100644
index 0000000..e046a78
--- /dev/null
+++ b/cargo2android_defaults.bp
@@ -0,0 +1,24 @@
+rust_defaults {
+ name: "crosvm_defaults",
+ edition: "2018",
+ enabled: false,
+ target: {
+ linux_glibc_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",
+ ],
+}
\ No newline at end of file
diff --git a/cargo2android_module.bp b/cargo2android_module.bp
new file mode 100644
index 0000000..c4241d0
--- /dev/null
+++ b/cargo2android_module.bp
@@ -0,0 +1,25 @@
+arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
+},
+target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ "gdbstub",
+ ],
+ rustlibs: [
+ "libgdbstub",
+ "libgdbstub_arch",
+ "libthiserror",
+ ],
+ },
+},
+ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+]
\ No newline at end of file
diff --git a/ci/README.md b/ci/README.md
index 52ac317..58ffa7e 100644
--- a/ci/README.md
+++ b/ci/README.md
@@ -10,19 +10,10 @@
### 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:
+Crosvm requires a bunch of dependencies that are checked out via submodules:
```
-$ 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
+$ git submodule update --init
```
### Installing Podman (or Docker)
diff --git a/ci/build_environment/Makefile b/ci/build_environment/Makefile
index 716daa4..619310e 100644
--- a/ci/build_environment/Makefile
+++ b/ci/build_environment/Makefile
@@ -2,12 +2,12 @@
# 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.
+# This makefile is run by the ./ci/crosvm_* containers to build native third
+# party dependencies required by crosvm.
#
# Setting TARGET_ARCH=aarch64 enables cross-compilation for aarch64.
-SRC ?= /workspace/src
+SRC ?= /workspace/src/crosvm
BUILD ?= /workspace/scratch/build
LIB ?= /workspace/scratch/lib
TARGET_ARCH ?=
@@ -36,7 +36,7 @@
tpm2:
mkdir -p $(BUILD)/tpm2
- $(MAKE) -C $(SRC)/third_party/tpm2 \
+ $(MAKE) -C $(SRC)/tpm2-sys/libtpm2 \
obj=$(BUILD)/tpm2 \
AR=$(CROSS_COMPILE)ar \
CC=$(CROSS_COMPILE)gcc \
@@ -44,13 +44,13 @@
minijail:
mkdir -p $(BUILD)/minijail
- $(MAKE) -C $(SRC)/aosp/external/minijail \
+ $(MAKE) -C $(SRC)/third_party/minijail \
OUT=$(BUILD)/minijail \
CROSS_COMPILE=$(CROSS_COMPILE)
minigbm:
mkdir -p $(BUILD)/minigbm
- $(MAKE) -C $(SRC)/platform/minigbm \
+ $(MAKE) -C $(SRC)/third_party/minigbm \
OUT=$(BUILD)/minigbm \
CROSS_COMPILE=$(CROSS_COMPILE)
@@ -60,7 +60,7 @@
$(SRC)/third_party/virglrenderer \
$(MESON_ARGS)
- CPATH=$(SRC)/platform/minigbm \
+ CPATH=$(SRC)/third_party/minigbm \
meson compile -C $(BUILD)/virglrenderer
@@ -90,7 +90,7 @@
# 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/
+ ln -sf $(SRC)/third_party/minigbm/gbm.pc $(LIB)/pkgconfig/
# virglrenderer
ln -sf $(BUILD)/virglrenderer/src/libvirglrenderer.so $(LIB)
diff --git a/ci/crosvm_aarch64_builder/Dockerfile b/ci/crosvm_aarch64_builder/Dockerfile
index 42ba628..fa6830f 100644
--- a/ci/crosvm_aarch64_builder/Dockerfile
+++ b/ci/crosvm_aarch64_builder/Dockerfile
@@ -71,7 +71,7 @@
/root/.ssh /root/.ssh
# Setup entrypoint and interactive shell
-WORKDIR /workspace/src/platform/crosvm
+WORKDIR /workspace/src/crosvm
COPY bashrc /root/.bashrc
COPY entrypoint /workspace
ENTRYPOINT ["/workspace/entrypoint"]
diff --git a/ci/crosvm_base/Dockerfile b/ci/crosvm_base/Dockerfile
index 690957d..c9d1091 100644
--- a/ci/crosvm_base/Dockerfile
+++ b/ci/crosvm_base/Dockerfile
@@ -52,8 +52,10 @@
# Install the current crosvm rust toolchain via rustup.
COPY rust-toolchain ./
-RUN curl https://sh.rustup.rs -sSf | sh -s -- \
- -y \
+RUN curl https://static.rust-lang.org/rustup/archive/1.24.3/x86_64-unknown-linux-gnu/rustup-init -sSf -o rustup-init \
+ && echo "3dc5ef50861ee18657f9db2eeb7392f9c2a6c95c90ab41e45ab4ca71476b4338 rustup-init" | sha256sum --check \
+ && chmod +x rustup-init \
+ && ./rustup-init -y \
--profile minimal \
-c rustfmt,clippy \
--default-toolchain $(cat rust-toolchain)
@@ -78,4 +80,4 @@
ENV CROSVM_CROS_BUILD=1
# All commands will be executed in the crosvm src directory.
-WORKDIR /workspace/src/platform/crosvm
+WORKDIR /workspace/src/crosvm
diff --git a/ci/crosvm_builder/Dockerfile b/ci/crosvm_builder/Dockerfile
index 8c70dd8..3b3e02b 100644
--- a/ci/crosvm_builder/Dockerfile
+++ b/ci/crosvm_builder/Dockerfile
@@ -43,7 +43,7 @@
/root/.ssh /root/.ssh
# Setup entrypoint and interactive shell
-WORKDIR /workspace/src/platform/crosvm
+WORKDIR /workspace/src/crosvm
COPY bashrc /root/.bashrc
COPY entrypoint /workspace
ENTRYPOINT ["/workspace/entrypoint"]
diff --git a/ci/crosvm_test_vm/Dockerfile b/ci/crosvm_test_vm/Dockerfile
index aa19f17..aa34173 100644
--- a/ci/crosvm_test_vm/Dockerfile
+++ b/ci/crosvm_test_vm/Dockerfile
@@ -24,7 +24,7 @@
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"
+ "http://cloud.debian.org/images/cloud/bullseye/daily/20210702-691/debian-11-generic-${VM_ARCH}-daily-20210702-691.qcow2"
# Package `cloud_init_data.yaml` to be loaded during `first_boot.expect`
COPY build/cloud_init_data.yaml ./
diff --git a/ci/image_tag b/ci/image_tag
index 5aed675..d9830c0 100644
--- a/ci/image_tag
+++ b/ci/image_tag
@@ -1 +1 @@
-r0007
+r0009
diff --git a/ci/kokoro/common.sh b/ci/kokoro/common.sh
index c1e1e8b..ac05184 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
@@ -21,42 +19,13 @@
fi
setup_source() {
- if [ -z "${KOKORO_ARTIFACTS_DIR}" ]; then
+ if [ -z "${KOKORO_ARTIFACTS_DIR}/git" ]; then
echo "This script must be run in kokoro"
exit 1
fi
- cd "${KOKORO_ARTIFACTS_DIR}"
-
- echo ""
- echo "Downloading crosvm dependencies to $(pwd)/cros..."
- mkdir cros
- cd cros
-
- # repo gets confused by pyenv, make sure we select 3.6.1 as our default
- # version.
- if command -v pyenv >/dev/null; then
- echo "Selecting Python 3.6.1"
- pyenv global 3.6.1
- 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
- fi
-
+ cd "${KOKORO_ARTIFACTS_DIR}/git/crosvm"
+ git submodule update --init
}
cleanup() {
@@ -66,12 +35,6 @@
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 +49,4 @@
# 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/simulate b/ci/kokoro/simulate
index bd192ab..2673a90 100755
--- a/ci/kokoro/simulate
+++ b/ci/kokoro/simulate
@@ -19,7 +19,8 @@
main() {
echo "Copying ${crosvm_src}/ to ${kokoro_src}"
mkdir -p "${kokoro_src}"
- rsync -arq --exclude "target" --exclude ".git" "${crosvm_src}/" "${kokoro_src}"
+ rsync -arq --exclude "target" --exclude "__pycache__" \
+ "${crosvm_src}/" "${kokoro_src}"
# Run user-provided kokoro build script.
export KOKORO_ARTIFACTS_DIR="${kokoro_root}/src"
diff --git a/ci/run_container.sh b/ci/run_container.sh
index 27ecc2a..1ed298a 100755
--- a/ci/run_container.sh
+++ b/ci/run_container.sh
@@ -11,14 +11,7 @@
# 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
@@ -43,14 +36,14 @@
version=$(cat $(dirname $0)/image_tag)
echo "Using builder: ${builder}:${version}"
-src="${cros_root}/src"
-echo "Using source directory: ${src} (Available at /workspace/src)"
+echo "Using source directory: ${crosvm_root} \
+(Available at /workspace/src/crosvm)"
docker_args=(
--rm
--device /dev/kvm
--volume /dev/log:/dev/log
- --volume "${src}":/workspace/src:rw
+ --volume "${crosvm_root}":/workspace/src/crosvm:rw
)
if [ ! -z "${CROSVM_BUILDER_SCRATCH_DIR}" ]; then
diff --git a/ci/test_runner.py b/ci/test_runner.py
index 1207207..85bdc78 100644
--- a/ci/test_runner.py
+++ b/ci/test_runner.py
@@ -620,6 +620,8 @@
print("--require-all needs to be run with --use-vm or --run-privileged")
exit(1)
+ os.environ["RUST_BACKTRACE"] = "1"
+
execute_tests(
crate_requirements,
feature_requirements,
diff --git a/ci/vm_tools/exec_binary_in_vm b/ci/vm_tools/exec_binary_in_vm
index 7b2c230..d01987b 100755
--- a/ci/vm_tools/exec_binary_in_vm
+++ b/ci/vm_tools/exec_binary_in_vm
@@ -8,6 +8,8 @@
${0%/*}/wait_for_vm_with_timeout || exit 1
+vm_tmp_dir=/var/tmp
+
if [ "$1" = "--no-sync" ]; then
shift
else
@@ -19,10 +21,10 @@
filename=$(basename $filepath)
echo "Executing $filename ${@:2}"
-scp -q $filepath vm:/tmp/$filename
-ssh vm -q -t "cd /tmp && sudo ./$filename ${@:2}"
+scp -q $filepath vm:$vm_tmp_dir/$filename
+ssh vm -q -t "cd $vm_tmp_dir && 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"
+ssh vm -q -t "rm $vm_tmp_dir/$filename"
exit $ret
diff --git a/ci/vm_tools/sync_deps b/ci/vm_tools/sync_deps
index a97b019..3697180 100755
--- a/ci/vm_tools/sync_deps
+++ b/ci/vm_tools/sync_deps
@@ -8,12 +8,13 @@
${0%/*}/wait_for_vm_with_timeout || exit 1
-crosvm_root="/workspace/src/platform/crosvm"
+crosvm_root="/workspace/src/crosvm"
rust_toolchain=$(cat ${crosvm_root}/rust-toolchain)
target_dir=$(
cargo metadata --no-deps --format-version 1 |
jq -r ".target_directory"
)
+vm_tmp_dir=/var/tmp
# List of shared objects used by crosvm that need to be synced.
shared_objects=(
@@ -34,4 +35,4 @@
)
fi
-rsync -azPLq --rsync-path="sudo rsync" ${runtime_files} vm:/tmp
+rsync -azPLq --rsync-path="sudo rsync" ${runtime_files} vm:$vm_tmp_dir
diff --git a/common/README.md b/common/README.md
new file mode 100644
index 0000000..bfb228d
--- /dev/null
+++ b/common/README.md
@@ -0,0 +1,12 @@
+# 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/common/cros-fuzz/Cargo.toml b/common/cros-fuzz/Cargo.toml
new file mode 100644
index 0000000..e91baf2
--- /dev/null
+++ b/common/cros-fuzz/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "cros_fuzz"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+include = ["Cargo.toml", "src/*.rs"]
+
+[dependencies]
+rand_core = "0.4"
+
+[workspace]
diff --git a/common/cros-fuzz/OWNERS b/common/cros-fuzz/OWNERS
new file mode 100644
index 0000000..8a33102
--- /dev/null
+++ b/common/cros-fuzz/OWNERS
@@ -0,0 +1 @@
+chirantan@chromium.org
diff --git a/common/cros-fuzz/README.md b/common/cros-fuzz/README.md
new file mode 100644
index 0000000..d988980
--- /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 0000000..9e26dfe
--- /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 0000000..da5bd6d
--- /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 0000000..e78f53c
--- /dev/null
+++ b/common/cros-fuzz/src/rand.rs
@@ -0,0 +1,214 @@
+// 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() {
+ Ok(self.fill_bytes(dest))
+ } 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 mut zero_buf = Vec::with_capacity(dest.len());
+ zero_buf.resize(dest.len(), 0);
+
+ 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/rand_ish/Android.bp b/common/p9/Android.bp
similarity index 61%
copy from rand_ish/Android.bp
copy to common/p9/Android.bp
index aa1fd97..bb6142e 100644
--- a/rand_ish/Android.bp
+++ b/common/p9/Android.bp
@@ -1,6 +1,8 @@
-// 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -11,33 +13,43 @@
}
rust_library {
- name: "librand_ish",
+ name: "libp9",
defaults: ["crosvm_defaults"],
host_supported: true,
- crate_name: "rand_ish",
+ crate_name: "p9",
srcs: ["src/lib.rs"],
edition: "2018",
+ rustlibs: [
+ "liblibc",
+ "libsys_util",
+ ],
+ proc_macros: ["libwire_format_derive"],
}
rust_defaults {
- name: "rand_ish_defaults",
+ name: "p9_test_defaults",
defaults: ["crosvm_defaults"],
- crate_name: "rand_ish",
+ crate_name: "p9",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
edition: "2018",
+ rustlibs: [
+ "liblibc",
+ "libsys_util",
+ ],
+ proc_macros: ["libwire_format_derive"],
}
rust_test_host {
- name: "rand_ish_host_test_src_lib",
- defaults: ["rand_ish_defaults"],
+ name: "p9_host_test_src_lib",
+ defaults: ["p9_test_defaults"],
test_options: {
unit_test: true,
},
}
rust_test {
- name: "rand_ish_device_test_src_lib",
- defaults: ["rand_ish_defaults"],
+ name: "p9_device_test_src_lib",
+ defaults: ["p9_test_defaults"],
}
diff --git a/common/p9/Cargo.toml b/common/p9/Cargo.toml
new file mode 100644
index 0000000..f3725dc
--- /dev/null
+++ b/common/p9/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "p9"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[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 0000000..8a33102
--- /dev/null
+++ b/common/p9/OWNERS
@@ -0,0 +1 @@
+chirantan@chromium.org
diff --git a/common/p9/README.md b/common/p9/README.md
new file mode 100644
index 0000000..b85cc15
--- /dev/null
+++ b/common/p9/README.md
@@ -0,0 +1,21 @@
+# 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 0000000..fe9af4a
--- /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 = "2018"
+
+[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 0000000..9e26dfe
--- /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 0000000..6cdeae3
--- /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 0000000..47aa070
--- /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 0000000..b00c706
--- /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 0000000..b5a03c0
--- /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 0000000..9c278ee
--- /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 0000000..fc729cd
--- /dev/null
+++ b/common/p9/src/protocol/wire_format.rs
@@ -0,0 +1,698 @@
+// 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>,
+ }
+
+ 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 0000000..cc01332
--- /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(¤t, &name).or_else(|e| {
+ if ascii_casefold {
+ if let Some(libc::ENOENT) = e.raw_os_error() {
+ return ascii_casefold_lookup(proc, ¤t, name.to_bytes());
+ }
+ }
+
+ Err(e)
+ })?;
+ mds.push(stat(¤t)?);
+ }
+
+ 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(), × 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 0000000..a273429
--- /dev/null
+++ b/common/p9/src/server/tests.rs
@@ -0,0 +1,1099 @@
+// 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`.
+ 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 0000000..1bae7f8
--- /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",
+ srcs: ["wire_format_derive.rs"],
+ edition: "2015",
+ rustlibs: [
+ "libproc_macro2",
+ "libquote",
+ "libsyn",
+ ],
+}
diff --git a/common/p9/wire_format_derive/Cargo.toml b/common/p9/wire_format_derive/Cargo.toml
new file mode 100644
index 0000000..52ab6a4
--- /dev/null
+++ b/common/p9/wire_format_derive/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "wire_format_derive"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+
+[dependencies]
+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 0000000..9a6eee3
--- /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 0000000..290ffc5
--- /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/cros_async/Android.bp b/cros_async/Android.bp
index a33b151..5c54336 100644
--- a/cros_async/Android.bp
+++ b/cros_async/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --verbose.
+// 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 {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "cros_async_defaults",
+ name: "cros_async_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "cros_async",
srcs: ["src/lib.rs"],
@@ -27,6 +27,7 @@
"libintrusive_collections",
"libio_uring",
"liblibc",
+ "libonce_cell",
"libpin_utils",
"libslab",
"libsync_rust",
@@ -42,7 +43,7 @@
rust_test_host {
name: "cros_async_host_test_src_lib",
- defaults: ["cros_async_defaults"],
+ defaults: ["cros_async_test_defaults"],
test_options: {
unit_test: true,
},
@@ -50,13 +51,12 @@
rust_test {
name: "cros_async_device_test_src_lib",
- defaults: ["cros_async_defaults"],
+ defaults: ["cros_async_test_defaults"],
}
rust_library {
name: "libcros_async",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "cros_async",
srcs: ["src/lib.rs"],
@@ -68,6 +68,7 @@
"libintrusive_collections",
"libio_uring",
"liblibc",
+ "libonce_cell",
"libpin_utils",
"libslab",
"libsync_rust",
@@ -79,46 +80,3 @@
"libpaste",
],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/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.15 "alloc,async-await,default,executor,futures-executor,std"
-// futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-// futures-core-0.3.15 "alloc,std"
-// futures-executor-0.3.15 "default,num_cpus,std,thread-pool"
-// futures-io-0.3.15 "std"
-// futures-macro-0.3.15
-// futures-sink-0.3.15 "alloc,std"
-// futures-task-0.3.15 "alloc,std"
-// futures-util-0.3.15 "alloc,async-await,async-await-macro,channel,default,futures-channel,futures-io,futures-macro,futures-sink,io,memchr,proc-macro-hack,proc-macro-nested,sink,slab,std"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memchr-2.4.0 "default,std"
-// memoffset-0.5.6 "default"
-// num_cpus-1.13.0
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro-hack-0.5.19
-// proc-macro-nested-0.1.7
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/cros_async/Cargo.toml b/cros_async/Cargo.toml
index 165a322..cd9417c 100644
--- a/cros_async/Cargo.toml
+++ b/cros_async/Cargo.toml
@@ -7,15 +7,16 @@
[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"
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
thiserror = "1.0.20"
[dependencies.futures]
diff --git a/cros_async/src/blocking.rs b/cros_async/src/blocking.rs
index ae79d2b..f6430a7 100644
--- a/cros_async/src/blocking.rs
+++ b/cros_async/src/blocking.rs
@@ -3,5 +3,7 @@
// found in the LICENSE file.
mod block_on;
+mod pool;
pub use block_on::*;
+pub use pool::*;
diff --git a/cros_async/src/blocking/pool.rs b/cros_async/src/blocking/pool.rs
new file mode 100644
index 0000000..2109fd7
--- /dev/null
+++ b/cros_async/src/blocking/pool.rs
@@ -0,0 +1,489 @@
+// 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,
+ 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.tasks.is_empty()
+ });
+ state = guard;
+
+ // 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 -= 1;
+ 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;
+ 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,
+ 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,
+ 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::replace(&mut state.tasks, VecDeque::new());
+ 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 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 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/executor.rs b/cros_async/src/executor.rs
index 21295ef..56860f8 100644
--- a/cros_async/src/executor.rs
+++ b/cros_async/src/executor.rs
@@ -50,7 +50,7 @@
/// // 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 +68,7 @@
///
/// 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.
@@ -227,6 +227,46 @@
}
}
+ /// 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 b3c2748..339f9fa 100644
--- a/cros_async/src/fd_executor.rs
+++ b/cros_async/src/fd_executor.rs
@@ -27,8 +27,8 @@
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 crate::{queue::RunnableQueue, BlockingPool};
#[derive(Debug, ThisError)]
pub enum Error {
@@ -253,6 +253,7 @@
queue: RunnableQueue,
poll_ctx: EpollContext<usize>,
ops: Mutex<Slab<OpStatus>>,
+ blocking_pool: BlockingPool,
state: AtomicI32,
notify: EventFd,
}
@@ -263,6 +264,7 @@
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,
})
@@ -330,6 +332,14 @@
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);
@@ -497,6 +507,14 @@
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);
diff --git a/cros_async/src/io_ext.rs b/cros_async/src/io_ext.rs
index 075c152..81ad987 100644
--- a/cros_async/src/io_ext.rs
+++ b/cros_async/src/io_ext.rs
@@ -52,12 +52,16 @@
#[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 +79,14 @@
/// 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>;
@@ -212,7 +216,7 @@
// 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));
}
@@ -243,7 +247,7 @@
// 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));
}
@@ -274,7 +278,7 @@
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());
@@ -297,7 +301,7 @@
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());
@@ -320,7 +324,7 @@
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 },
@@ -360,7 +364,7 @@
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 }],
)
@@ -421,7 +425,7 @@
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 d59df8b..f9604e5 100644
--- a/cros_async/src/lib.rs
+++ b/cros_async/src/lib.rs
@@ -74,7 +74,7 @@
mod uring_source;
mod waker;
-pub use blocking::block_on;
+pub use blocking::{block_on, BlockingPool};
pub use event::EventAsync;
pub use executor::Executor;
pub use fd_executor::FdExecutor;
diff --git a/cros_async/src/poll_source.rs b/cros_async/src/poll_source.rs
index 1a2d4ae..1fd7f06 100644
--- a/cros_async/src/poll_source.rs
+++ b/cros_async/src/poll_source.rs
@@ -97,18 +97,28 @@
/// 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 {
@@ -128,7 +138,7 @@
/// 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> {
@@ -140,13 +150,23 @@
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 {
@@ -202,18 +222,28 @@
/// 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 {
@@ -233,7 +263,7 @@
/// 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> {
@@ -246,13 +276,23 @@
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 {
@@ -329,7 +369,7 @@
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());
@@ -347,7 +387,7 @@
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());
diff --git a/cros_async/src/queue.rs b/cros_async/src/queue.rs
index 3192703..f95de06 100644
--- a/cros_async/src/queue.rs
+++ b/cros_async/src/queue.rs
@@ -38,6 +38,12 @@
}
}
+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/uring_executor.rs b/cros_async/src/uring_executor.rs
index 08a6448..a38e6c7 100644
--- a/cros_async/src/uring_executor.rs
+++ b/cros_async/src/uring_executor.rs
@@ -52,13 +52,14 @@
//! ensure it lives long enough.
use std::convert::TryInto;
+use std::ffi::CStr;
use std::fs::File;
use std::future::Future;
use std::io;
-use std::mem;
+use std::mem::{self, MaybeUninit};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::pin::Pin;
-use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
+use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Arc, Weak};
use std::task::Waker;
use std::task::{Context, Poll};
@@ -67,15 +68,19 @@
use async_task::Task;
use futures::task::noop_waker;
use io_uring::URingContext;
+use once_cell::sync::Lazy;
use pin_utils::pin_mut;
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 crate::{
+ mem::{BackingMemory, MemRegion},
+ BlockingPool,
+};
#[derive(Debug, ThisError)]
pub enum Error {
@@ -130,29 +135,41 @@
}
}
+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 {
@@ -277,6 +294,7 @@
ctx: URingContext,
queue: RunnableQueue,
ring: Mutex<Ring>,
+ blocking_pool: BlockingPool,
thread_id: Mutex<Option<ThreadId>>,
state: AtomicI32,
}
@@ -290,6 +308,7 @@
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),
})
@@ -351,6 +370,14 @@
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
@@ -753,6 +780,14 @@
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);
@@ -880,6 +915,10 @@
#[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 =
@@ -920,6 +959,10 @@
#[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 =
@@ -961,6 +1004,10 @@
#[test]
fn canceled_before_completion() {
+ if !use_uring() {
+ return;
+ }
+
async fn cancel_io(op: PendingOperation) {
mem::drop(op);
}
@@ -999,6 +1046,10 @@
#[test]
fn drop_before_completion() {
+ if !use_uring() {
+ return;
+ }
+
const VALUE: u64 = 0xef6c_a8df_b842_eb9c;
async fn check_op(op: PendingOperation) {
@@ -1044,6 +1095,10 @@
#[test]
fn drop_on_different_thread() {
+ if !use_uring() {
+ return;
+ }
+
let ex = URingExecutor::new().unwrap();
let ex2 = ex.clone();
diff --git a/cros_async/src/uring_source.rs b/cros_async/src/uring_source.rs
index fa1b3c2..1223577 100644
--- a/cros_async/src/uring_source.rs
+++ b/cros_async/src/uring_source.rs
@@ -18,26 +18,6 @@
/// `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,
@@ -64,12 +44,12 @@
/// 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,13 +107,13 @@
/// 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)
}
@@ -144,12 +124,12 @@
/// 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 +149,15 @@
/// 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)
}
@@ -236,12 +218,17 @@
use std::path::PathBuf;
use crate::io_ext::{ReadAsync, WriteAsync};
+ use crate::uring_executor::use_uring;
use crate::UringSource;
use super::*;
#[test]
fn read_to_mem() {
+ if !use_uring() {
+ return;
+ }
+
use crate::mem::VecIoWrapper;
use std::io::Write;
use tempfile::tempfile;
@@ -258,7 +245,7 @@
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 +262,16 @@
#[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 +284,20 @@
#[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 +310,7 @@
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 +319,10 @@
#[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,6 +332,10 @@
#[test]
fn event() {
+ if !use_uring() {
+ return;
+ }
+
use sys_util::EventFd;
async fn write_event(ev: EventFd, wait: EventFd, ex: &URingExecutor) {
@@ -367,6 +373,10 @@
#[test]
fn pend_on_pipe() {
+ if !use_uring() {
+ return;
+ }
+
use std::io::Write;
use futures::future::Either;
@@ -393,6 +403,10 @@
#[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 +414,7 @@
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 +433,7 @@
let vw = Arc::new(VecIoWrapper::from(v));
let ret = source
.read_to_mem(
- 0,
+ None,
Arc::<VecIoWrapper>::clone(&vw),
&[MemRegion {
offset: 32,
@@ -443,6 +457,10 @@
#[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 +468,7 @@
let vw = Arc::new(VecIoWrapper::from(v));
let ret = source
.read_to_mem(
- 0,
+ None,
Arc::<VecIoWrapper>::clone(&vw),
&[MemRegion {
offset: 32,
@@ -467,6 +485,10 @@
#[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());
@@ -500,6 +522,10 @@
#[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 +538,10 @@
#[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 +554,10 @@
#[test]
fn writemem() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = OpenOptions::new()
.create(true)
@@ -534,7 +568,7 @@
let v = vec![0x55u8; 64];
let vw = Arc::new(crate::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 +580,10 @@
#[test]
fn writevec() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = OpenOptions::new()
.create(true)
@@ -556,7 +594,7 @@
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 +605,10 @@
#[test]
fn writemulti() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = OpenOptions::new()
.create(true)
@@ -577,9 +619,11 @@
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/crosvm_plugin/Android.bp b/crosvm_plugin/Android.bp
index bf0e215..25e7b3e 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/cargo2android.json b/crosvm_plugin/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/crosvm_plugin/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs
index 86827a6..e0fb2f1 100644
--- a/crosvm_plugin/src/lib.rs
+++ b/crosvm_plugin/src/lib.rs
@@ -31,7 +31,7 @@
use libc::{E2BIG, EINVAL, ENOENT, ENOTCONN, EPROTO};
-use protobuf::{parse_from_bytes, Message, ProtobufEnum, RepeatedField};
+use protobuf::{Message, ProtobufEnum, RepeatedField};
use base::ScmSocket;
@@ -308,8 +308,8 @@
.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);
}
@@ -1070,7 +1070,7 @@
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)?;
diff --git a/data_model/Android.bp b/data_model/Android.bp
index ee96a8e..a4e8604 100644
--- a/data_model/Android.bp
+++ b/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 {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "data_model_defaults",
+ name: "data_model_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "data_model",
srcs: ["src/lib.rs"],
@@ -27,7 +27,7 @@
rust_test_host {
name: "data_model_host_test_src_lib",
- defaults: ["data_model_defaults"],
+ defaults: ["data_model_test_defaults"],
test_options: {
unit_test: true,
},
@@ -35,7 +35,7 @@
rust_test {
name: "data_model_device_test_src_lib",
- defaults: ["data_model_defaults"],
+ defaults: ["data_model_test_defaults"],
}
rust_library {
@@ -51,13 +51,3 @@
"libserde",
],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// libc-0.2.97 "default,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.2 "default"
diff --git a/devices/Android.bp b/devices/Android.bp
index 154bc2b..6ab3fe8 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, usb.
+// 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
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "devices_defaults",
+ name: "devices_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "devices",
// has rustc warnings
@@ -19,6 +19,12 @@
test_suites: ["general-tests"],
auto_gen_config: true,
edition: "2018",
+ features: [
+ "audio",
+ "gfxstream",
+ "gpu",
+ "usb",
+ ],
rustlibs: [
"libacpi_tables",
"libaudio_streams",
@@ -29,19 +35,18 @@
"libdisk",
"libfuse_rust",
"libfutures",
+ "libgpu_display",
"libhypervisor",
"libkvm_sys",
"liblibc",
- "liblibchromeos",
- "liblibcras",
"liblinux_input_sys",
"libminijail_rust",
"libnet_sys",
"libnet_util",
"libp9",
"libpower_monitor",
- "librand_ish",
"libresources",
+ "librutabaga_gfx",
"libserde",
"libsmallvec",
"libsync_rust",
@@ -60,31 +65,6 @@
"libenumn",
"libremain",
],
-}
-
-rust_test_host {
- name: "devices_host_test_src_lib",
- defaults: ["devices_defaults"],
- test_options: {
- unit_test: true,
- },
- 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"],
@@ -100,9 +80,19 @@
},
}
+rust_test_host {
+ name: "devices_host_test_src_lib",
+ defaults: ["devices_test_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+ shared_libs: ["libgfxstream_backend"],
+}
+
rust_test {
name: "devices_device_test_src_lib",
- defaults: ["devices_defaults"],
+ defaults: ["devices_test_defaults"],
+ shared_libs: ["libgfxstream_backend"],
}
rust_library {
@@ -115,14 +105,10 @@
edition: "2018",
features: [
"audio",
- "gpu",
- "gpu_buffer",
- "gpu_display",
- "gpu_renderer",
"gfxstream",
+ "gpu",
"usb",
],
- shared_libs: ["libgfxstream_backend",],
rustlibs: [
"libacpi_tables",
"libaudio_streams",
@@ -133,21 +119,18 @@
"libdisk",
"libfuse_rust",
"libfutures",
- "libgpu_display", // added manually
+ "libgpu_display",
"libhypervisor",
"libkvm_sys",
"liblibc",
- "liblibchromeos",
- "liblibcras",
"liblinux_input_sys",
"libminijail_rust",
"libnet_sys",
"libnet_util",
"libp9",
"libpower_monitor",
- "librand_ish",
"libresources",
- "librutabaga_gfx", // added manually
+ "librutabaga_gfx",
"libserde",
"libsmallvec",
"libsync_rust",
@@ -165,91 +148,5 @@
"libenumn",
"libremain",
],
+ 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
-// ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master"
-// ../../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
-// ../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.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// cfg-if-1.0.0
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.15 "alloc,std"
-// futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-// futures-core-0.3.15 "alloc,std"
-// futures-io-0.3.15 "std"
-// futures-sink-0.3.15 "alloc,std"
-// futures-task-0.3.15 "alloc,std"
-// futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-// getrandom-0.2.3 "std"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// log-0.4.14
-// memchr-2.4.0 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// protobuf-2.24.1
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.1 "std"
-// rand_core-0.6.3 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index f459ca8..bac657d 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -6,9 +6,10 @@
[features]
audio = []
+audio_cras = ["libcras"]
direct = []
gpu = ["gpu_display","rutabaga_gfx"]
-tpm = ["protos/trunks", "tpm2"]
+tpm = ["tpm2"]
usb = []
video-decoder = []
video-encoder = []
@@ -19,7 +20,7 @@
[dependencies]
acpi_tables = {path = "../acpi_tables" }
-audio_streams = { path = "../../adhd/audio_streams" } # ignored by ebuild
+audio_streams = { path = "../audio_streams" }
base = { path = "../base" }
bit_field = { path = "../bit_field" }
cros_async = { path = "../cros_async" }
@@ -32,16 +33,15 @@
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 = "../libvda", optional = true }
linux_input_sys = { path = "../linux_input_sys" }
minijail = { path = "../../minijail/rust/minijail" } # ignored by ebuild
net_sys = { path = "../net_sys" }
net_util = { path = "../net_util" }
-p9 = { path = "../../vm_tools/p9" }
+p9 = { path = "../common/p9" }
power_monitor = { path = "../power_monitor" }
protos = { path = "../protos", optional = true }
-rand_ish = { path = "../rand_ish" }
remain = "*"
resources = { path = "../resources" }
serde = { version = "1", features = [ "derive" ] }
diff --git a/devices/cargo2android.json b/devices/cargo2android.json
new file mode 100644
index 0000000..76c31af
--- /dev/null
+++ b/devices/cargo2android.json
@@ -0,0 +1,10 @@
+{
+ "add-module-block": "cargo2android_libs.bp",
+ "add_workspace": true,
+ "device": true,
+ "features": "audio,usb",
+ "global_defaults": "crosvm_defaults",
+ "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 0000000..16bc744
--- /dev/null
+++ b/devices/cargo2android_libs.bp
@@ -0,0 +1 @@
+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 0000000..7124ad6
--- /dev/null
+++ b/devices/patches/Android.bp.patch
@@ -0,0 +1,74 @@
+diff --git a/devices/Android.bp b/devices/Android.bp
+index 09290b81..8580691c 100644
+--- a/devices/Android.bp
++++ b/devices/Android.bp
+@@ -21,6 +21,8 @@ rust_defaults {
+ edition: "2018",
+ features: [
+ "audio",
++ "gfxstream",
++ "gpu",
+ "usb",
+ ],
+ rustlibs: [
+@@ -33,6 +35,7 @@ rust_defaults {
+ "libdisk",
+ "libfuse_rust",
+ "libfutures",
++ "libgpu_display",
+ "libhypervisor",
+ "libkvm_sys",
+ "liblibc",
+@@ -45,6 +48,7 @@ rust_defaults {
+ "libpower_monitor",
+ "librand_ish",
+ "libresources",
++ "librutabaga_gfx",
+ "libserde",
+ "libsmallvec",
+ "libsync_rust",
+@@ -63,6 +67,19 @@ rust_defaults {
+ "libenumn",
+ "libremain",
+ ],
++ target: {
++ host: {
++ shared_libs: ["libvirglrenderer"],
++ },
++ android: {
++ shared_libs: ["libdrm"],
++ static_libs: [
++ "libepoxy",
++ "libgbm",
++ "libvirglrenderer",
++ ],
++ },
++ },
+ }
+
+ rust_test_host {
+@@ -90,6 +107,8 @@ rust_library {
+ edition: "2018",
+ features: [
+ "audio",
++ "gfxstream",
++ "gpu",
+ "usb",
+ ],
+ rustlibs: [
+@@ -102,6 +121,7 @@ rust_library {
+ "libdisk",
+ "libfuse_rust",
+ "libfutures",
++ "libgpu_display",
+ "libhypervisor",
+ "libkvm_sys",
+ "liblibc",
+@@ -114,6 +134,7 @@ rust_library {
+ "libpower_monitor",
+ "librand_ish",
+ "libresources",
++ "librutabaga_gfx",
+ "libserde",
+ "libsmallvec",
+ "libsync_rust",
diff --git a/devices/src/bus.rs b/devices/src/bus.rs
index 90360cd..2984937 100644
--- a/devices/src/bus.rs
+++ b/devices/src/bus.rs
@@ -165,13 +165,9 @@
///
/// 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>>>,
access_id: usize,
}
@@ -180,7 +176,6 @@
pub fn new() -> Bus {
Bus {
devices: BTreeMap::new(),
- resume_notify_devices: Vec::new(),
access_id: 0,
}
}
@@ -307,19 +302,6 @@
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)]
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index 1ebd1b1..589f1cc 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -23,7 +23,7 @@
pub mod acpi;
pub mod bat;
mod serial;
-mod serial_device;
+pub mod serial_device;
#[cfg(feature = "usb")]
pub mod usb;
#[cfg(feature = "usb")]
diff --git a/devices/src/pci/ac97.rs b/devices/src/pci/ac97.rs
index 3b396ff..eaf6330 100644
--- a/devices/src/pci/ac97.rs
+++ b/devices/src/pci/ac97.rs
@@ -10,6 +10,7 @@
use audio_streams::shm_streams::{NullShmStreamSource, ShmStreamSource};
use base::{error, Event, RawDescriptor};
+#[cfg(feature = "audio_cras")]
use libcras::{CrasClient, CrasClientType, CrasSocketType, CrasSysError};
use resources::{Alloc, MmioType, SystemAllocator};
use vm_memory::GuestMemory;
@@ -39,6 +40,7 @@
#[derive(Debug, Clone)]
pub enum Ac97Backend {
NULL,
+ #[cfg(feature = "audio_cras")]
CRAS,
VIOS,
}
@@ -71,6 +73,7 @@
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),
@@ -85,6 +88,7 @@
pub backend: Ac97Backend,
pub capture: bool,
pub vios_server_path: Option<PathBuf>,
+ #[cfg(feature = "audio_cras")]
client_type: Option<CrasClientType>,
}
@@ -92,6 +96,7 @@
/// 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(())
@@ -145,6 +150,7 @@
/// 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",
@@ -160,12 +166,14 @@
/// 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)
diff --git a/devices/src/pci/msix.rs b/devices/src/pci/msix.rs
index 2852aef..3b065cc 100644
--- a/devices/src/pci/msix.rs
+++ b/devices/src/pci/msix.rs
@@ -100,7 +100,8 @@
let mut table_entries: Vec<MsixTableEntry> = Vec::new();
table_entries.resize_with(msix_vectors as usize, Default::default);
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);
MsixConfig {
@@ -556,6 +557,11 @@
fn id(&self) -> PciCapabilityID {
PciCapabilityID::MSIX
}
+
+ fn writable_bits(&self) -> Vec<u32> {
+ // Only msg_ctl[15:14] is writable
+ vec![0x3000_0000, 0, 0]
+ }
}
impl MsixCap {
diff --git a/devices/src/pci/pci_configuration.rs b/devices/src/pci/pci_configuration.rs
index 4cff20f..89ef933 100644
--- a/devices/src/pci/pci_configuration.rs
+++ b/devices/src/pci/pci_configuration.rs
@@ -187,6 +187,7 @@
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.
@@ -320,14 +321,20 @@
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
+ writable_bits[7] = 0xf900_0000; // IO base and limit, secondary status,
+ writable_bits[8] = 0xfff0_fff0; // Memory base and limit
+ 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,
@@ -600,6 +607,10 @@
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)
}
@@ -695,6 +706,10 @@
fn id(&self) -> PciCapabilityID {
PciCapabilityID::VendorSpecific
}
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32; 1]
+ }
}
#[test]
diff --git a/devices/src/pci/pci_device.rs b/devices/src/pci/pci_device.rs
index a316fcf..1c736ed 100644
--- a/devices/src/pci/pci_device.rs
+++ b/devices/src/pci/pci_device.rs
@@ -4,6 +4,8 @@
use std::fmt::{self, Display};
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use acpi_tables::sdt::SDT;
use base::{Event, RawDescriptor};
use hypervisor::Datamatch;
use resources::{Error as SystemAllocatorFaliure, SystemAllocator};
@@ -27,7 +29,7 @@
/// Registering an IO BAR failed.
IoRegistrationFailed(u64, pci_configuration::Error),
/// Create cras client failed.
- #[cfg(feature = "audio")]
+ #[cfg(all(feature = "audio", feature = "audio_cras"))]
CreateCrasClientFailed(libcras::Error),
/// Create VioS client failed.
#[cfg(feature = "audio")]
@@ -36,6 +38,12 @@
PciAllocationFailed,
/// PCI Address is not allocated.
PciAddressMissing,
+ /// MSIX Allocator encounters size of zero
+ MsixAllocatorSizeZero,
+ /// MSIX Allocator encounters overflow
+ MsixAllocatorOverflow { base: u64, size: u64 },
+ /// MSIX Allocator encounters out-of-space
+ MsixAllocatorOutOfSpace,
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -45,7 +53,7 @@
match self {
CapabilitiesSetup(e) => write!(f, "failed to add capability {}", e),
- #[cfg(feature = "audio")]
+ #[cfg(all(feature = "audio", feature = "audio_cras"))]
CreateCrasClientFailed(e) => write!(f, "failed to create CRAS Client: {}", e),
#[cfg(feature = "audio")]
CreateViosClientFailed(e) => write!(f, "failed to create VioS Client: {}", e),
@@ -59,6 +67,13 @@
}
PciAllocationFailed => write!(f, "failed to allocate PCI address"),
PciAddressMissing => write!(f, "PCI address is not allocated"),
+ MsixAllocatorSizeZero => write!(f, "Size of zero detected in MSIX Allocator"),
+ MsixAllocatorOverflow { base, size } => write!(
+ f,
+ "base={} + size={} overflows in MSIX Allocator",
+ base, size
+ ),
+ MsixAllocatorOutOfSpace => write!(f, "Out-of-space detected in MSIX Allocator"),
}
}
}
@@ -132,6 +147,11 @@
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)
+ }
}
impl<T: PciDevice> BusDevice for T {
@@ -240,6 +260,11 @@
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)
+ }
}
#[cfg(test)]
diff --git a/devices/src/pci/pci_root.rs b/devices/src/pci/pci_root.rs
index de451f7..242ffda 100644
--- a/devices/src/pci/pci_root.rs
+++ b/devices/src/pci/pci_root.rs
@@ -111,6 +111,11 @@
| ((Self::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.to_config_address(0) >> Self::FUNCTION_OFFSET
+ }
+
/// Returns true if the address points to PCI root host-bridge.
fn is_root(&self) -> bool {
matches!(
diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs
index 70f8cdf..cfc4925 100644
--- a/devices/src/pci/vfio_pci.rs
+++ b/devices/src/pci/vfio_pci.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.
+use std::cmp::{max, min};
+use std::collections::BTreeSet;
use std::sync::Arc;
use std::u32;
@@ -307,29 +309,37 @@
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,
}
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_size = (msix_ctl & PCI_MSIX_FLAGS_QSIZE) as u64 + 1;
let table = config.read_config_dword(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 table_size_bytes = table_size * MSIX_TABLE_ENTRIES_MODULO;
let pba = config.read_config_dword(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 pba_size_bytes = ((table_size + BITS_PER_PBA_ENTRY as u64 - 1)
+ / BITS_PER_PBA_ENTRY as u64)
+ * MSIX_PBA_ENTRIES_MODULO;
VfioMsixCap {
- config: MsixConfig::new(table_size, vm_socket_irq),
+ config: MsixConfig::new(table_size as u16, vm_socket_irq),
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,
}
}
@@ -362,10 +372,17 @@
}
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]) {
@@ -379,12 +396,17 @@
}
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]) {
@@ -397,10 +419,6 @@
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_irqfds(&self) -> Option<Vec<&Event>> {
let mut irqfds = Vec::new();
@@ -417,6 +435,61 @@
}
}
+struct VfioMsixAllocator {
+ // memory regions unoccupied by MSIX registers
+ // stores sets of (start, end) tuples, where `end` is the address of the
+ // last byte in the region
+ regions: BTreeSet<(u64, u64)>,
+}
+
+impl VfioMsixAllocator {
+ // Creates a new `VfioMsixAllocator` for managing a range of MSIX addresses.
+ // 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::MsixAllocatorSizeZero);
+ }
+ let end = base
+ .checked_add(size - 1)
+ .ok_or(PciDeviceError::MsixAllocatorOverflow { base, size })?;
+ let mut regions = BTreeSet::new();
+ regions.insert((base, end));
+ Ok(VfioMsixAllocator { regions })
+ }
+
+ // Allocates a range of addresses from the managed region with a required location.
+ // Returns OutOfSpace if requested range is not available (e.g. already allocated).
+ fn allocate_at(&mut self, start: u64, size: u64) -> Result<(), PciDeviceError> {
+ if size == 0 {
+ return Err(PciDeviceError::MsixAllocatorSizeZero);
+ }
+ let end = start
+ .checked_add(size - 1)
+ .ok_or(PciDeviceError::MsixAllocatorOutOfSpace)?;
+ match self
+ .regions
+ .iter()
+ .find(|range| range.0 <= start && range.1 >= end)
+ .cloned()
+ {
+ Some(slot) => {
+ 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(())
+ }
+ None => Err(PciDeviceError::MsixAllocatorOutOfSpace),
+ }
+ }
+}
+
enum DeviceData {
IntelGfxData { opregion_index: u32 },
}
@@ -652,18 +725,77 @@
self.enable_intx();
}
+ 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();
+ 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 VfioMsixAllocator::new(mmap_offset, mmap_size) {
+ Ok(a) => a,
+ Err(e) => {
+ error!("add_bar_mmap_msix failed: {}", 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);
+ mmaps.clear();
+ return mmaps;
+ }
+ }
+ }
+ }
+
+ 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<MemoryMapping> {
let mut mem_map: Vec<MemoryMapping> = 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;
}
@@ -715,7 +847,8 @@
// 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) } {
+ match unsafe { self.device.vfio_dma_map(guest_map_start, size, host, true) }
+ {
Ok(_) => mem_map.push(mmap),
Err(e) => {
error!(
diff --git a/devices/src/usb/host_backend/host_device.rs b/devices/src/usb/host_backend/host_device.rs
index 708f0e2..3b5cee0 100644
--- a/devices/src/usb/host_backend/host_device.rs
+++ b/devices/src/usb/host_backend/host_device.rs
@@ -273,10 +273,10 @@
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");
@@ -320,7 +320,10 @@
}
_ => {
// 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)?;
diff --git a/devices/src/usb/xhci/xhci_transfer.rs b/devices/src/usb/xhci/xhci_transfer.rs
index a008684..085f79f 100644
--- a/devices/src/usb/xhci/xhci_transfer.rs
+++ b/devices/src/usb/xhci/xhci_transfer.rs
@@ -108,6 +108,21 @@
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> {
diff --git a/devices/src/vfio.rs b/devices/src/vfio.rs
index 48f09d7..f0c68d5 100644
--- a/devices/src/vfio.rs
+++ b/devices/src/vfio.rs
@@ -3,6 +3,7 @@
// 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;
@@ -19,7 +20,6 @@
ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref, ioctl_with_val, warn,
AsRawDescriptor, Error, Event, FromRawDescriptor, RawDescriptor, SafeDescriptor,
};
-use hypervisor::{DeviceKind, Vm};
use vm_memory::GuestMemory;
use vfio_sys::*;
@@ -29,19 +29,20 @@
OpenContainer(io::Error),
OpenGroup(io::Error),
GetGroupStatus(Error),
+ BorrowVfioContainer,
GroupViable,
VfioApiVersion,
VfioType1V2,
GroupSetContainer(Error),
ContainerSetIOMMU(Error),
GroupGetDeviceFD(Error),
- CreateVfioKvmDevice(Error),
KvmSetDeviceAttr(Error),
VfioDeviceGetInfo(Error),
VfioDeviceGetRegionInfo(Error),
InvalidPath,
IommuDmaMap(Error),
IommuDmaUnmap(Error),
+ IommuGetInfo(Error),
VfioIrqEnable(Error),
VfioIrqDisable(Error),
VfioIrqUnmask(Error),
@@ -54,19 +55,20 @@
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::BorrowVfioContainer => write!(f, "failed to borrow global vfio container"),
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::IommuGetInfo(e) => write!(f, "failed to get IOMMU info from host: {}", 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),
@@ -82,7 +84,6 @@
/// 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>>,
}
@@ -104,11 +105,14 @@
Ok(VfioContainer {
container,
- kvm_vfio_dev: None,
groups: HashMap::new(),
})
}
+ fn is_group_set(&self, group_id: u32) -> bool {
+ self.groups.get(&group_id).is_some()
+ }
+
fn check_extension(&self, val: u32) -> bool {
if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU {
panic!("IOMMU type error");
@@ -128,15 +132,25 @@
unsafe { ioctl_with_val(self, VFIO_SET_IOMMU(), val.into()) }
}
- 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<(), VfioError> {
+ 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,7 +159,7 @@
Ok(())
}
- fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<(), VfioError> {
+ pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<(), VfioError> {
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
flags: 0,
@@ -163,7 +177,24 @@
Ok(())
}
- fn init(&mut self, vm: &impl Vm, guest_mem: &GuestMemory) -> Result<(), VfioError> {
+ pub fn vfio_get_iommu_page_size_mask(&self) -> Result<u64, VfioError> {
+ let mut iommu_info = vfio_iommu_type1_info {
+ argsz: mem::size_of::<vfio_iommu_type1_info>() as u32,
+ flags: 0,
+ iova_pgsizes: 0,
+ };
+
+ // 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()));
+ }
+
+ Ok(iommu_info.iova_pgsizes)
+ }
+
+ fn init(&mut self, guest_mem: &GuestMemory, iommu_enabled: bool) -> Result<(), VfioError> {
if !self.check_extension(VFIO_TYPE1v2_IOMMU) {
return Err(VfioError::VfioType1V2);
}
@@ -174,15 +205,12 @@
// 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) }
- })?;
-
- let vfio_descriptor = vm
- .create_device(DeviceKind::Vfio)
- .map_err(VfioError::CreateVfioKvmDevice)?;
- self.kvm_vfio_dev = Some(vfio_descriptor);
+ if !iommu_enabled {
+ 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, true) }
+ })?;
+ }
Ok(())
}
@@ -190,8 +218,9 @@
fn get_group(
&mut self,
id: u32,
- vm: &impl Vm,
guest_mem: &GuestMemory,
+ kvm_vfio_file: &SafeDescriptor,
+ iommu_enabled: bool,
) -> Result<Arc<VfioGroup>, VfioError> {
match self.groups.get(&id) {
Some(group) => Ok(group.clone()),
@@ -201,13 +230,9 @@
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)?;
+ self.init(guest_mem, iommu_enabled)?;
}
- let kvm_vfio_file = self
- .kvm_vfio_dev
- .as_ref()
- .expect("kvm vfio device should exist");
group.kvm_device_add_group(kvm_vfio_file)?;
self.groups.insert(id, group.clone());
@@ -272,6 +297,20 @@
Ok(VfioGroup { group: group_file })
}
+ fn get_group_id(sysfspath: &Path) -> Result<u32, 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)?;
+
+ Ok(group_id)
+ }
+
fn kvm_device_add_group(&self, kvm_vfio_file: &SafeDescriptor) -> Result<(), VfioError> {
let group_descriptor = self.as_raw_descriptor();
let group_descriptor_ptr = &group_descriptor as *const i32;
@@ -318,6 +357,86 @@
}
}
+/// 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(
+ sysfspath: &Path,
+ iommu_enabled: bool,
+ ) -> Result<Arc<Mutex<VfioContainer>>, VfioError>;
+}
+
+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()));
+}
+
+pub struct VfioCommonSetup;
+
+impl VfioCommonTrait for VfioCommonSetup {
+ fn vfio_get_container(
+ sysfspath: &Path,
+ iommu_enabled: bool,
+ ) -> Result<Arc<Mutex<VfioContainer>>, VfioError> {
+ match iommu_enabled {
+ false => {
+ // 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)
+ }
+ })
+ }
+ true => {
+ let group_id = VfioGroup::get_group_id(sysfspath)?;
+
+ // 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)
+ }
+ })
+ }
+ }
+ }
+}
+
/// Vfio Irq type used to enable/disable/mask/unmask vfio irq
pub enum VfioIrqType {
Intx,
@@ -351,23 +470,20 @@
/// 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.
+ /// kvm_vfio_file specify a valid file descriptor returned from KVM_CREATE_DEVICE
+ /// with type KVM_DEV_TYPE_VFIO
pub fn new(
sysfspath: &Path,
- vm: &impl Vm,
guest_mem: &GuestMemory,
+ kvm_vfio_file: &SafeDescriptor,
container: Arc<Mutex<VfioContainer>>,
+ iommu_enabled: bool,
) -> 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 group_id = VfioGroup::get_group_id(sysfspath)?;
+ let group =
+ container
+ .lock()
+ .get_group(group_id, guest_mem, kvm_vfio_file, iommu_enabled)?;
let name_osstr = sysfspath.file_name().ok_or(VfioError::InvalidPath)?;
let name_str = name_osstr.to_str().ok_or(VfioError::InvalidPath)?;
let name = String::from(name_str);
@@ -590,7 +706,7 @@
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 +715,7 @@
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 +747,11 @@
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;
@@ -794,8 +915,11 @@
iova: u64,
size: u64,
user_addr: u64,
+ write_en: bool,
) -> Result<(), VfioError> {
- self.container.lock().vfio_dma_map(iova, size, user_addr)
+ self.container
+ .lock()
+ .vfio_dma_map(iova, size, user_addr, write_en)
}
/// Remove (iova, user_addr) map from vfio container iommu table
diff --git a/devices/src/virtio/balloon.rs b/devices/src/virtio/balloon.rs
index e1bc984..ecd9a44 100644
--- a/devices/src/virtio/balloon.rs
+++ b/devices/src/virtio/balloon.rs
@@ -12,10 +12,10 @@
use remain::sorted;
use thiserror::Error as ThisError;
-use base::{self, error, info, warn, AsRawDescriptor, AsyncTube, Event, RawDescriptor, Tube};
+use base::{self, error, warn, AsRawDescriptor, AsyncTube, Event, RawDescriptor, Tube};
use cros_async::{select6, EventAsync, Executor};
use data_model::{DataInit, Le16, Le32, Le64};
-use vm_control::{BalloonControlCommand, BalloonControlResult, BalloonStats};
+use vm_control::{BalloonStats, BalloonTubeCommand, BalloonTubeResult};
use vm_memory::{GuestAddress, GuestMemory};
use super::{
@@ -178,7 +178,7 @@
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());
}
}
@@ -190,12 +190,34 @@
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>,
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 +225,7 @@
}
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) => {
@@ -222,23 +244,14 @@
};
}
let actual_pages = config.actual_pages.load(Ordering::Relaxed) as u64;
- let result = BalloonControlResult::Stats {
+ 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;
- }
-
- // Request a new stats_desc to the guest.
- queue.add_used(&mem, index, 0);
- interrupt.borrow_mut().signal_used_queue(queue.vector);
}
}
@@ -248,20 +261,19 @@
command_tube: &AsyncTube,
interrupt: Rc<RefCell<Interrupt>>,
config: Arc<BalloonConfig>,
- mut stats_tx: mpsc::Sender<()>,
+ mut stats_tx: mpsc::Sender<u64>,
) -> Result<()> {
loop {
match command_tube.next().await {
Ok(command) => match command {
- BalloonControlCommand::Adjust { num_bytes } => {
+ BalloonTubeCommand::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();
}
- 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);
}
}
@@ -347,8 +359,10 @@
);
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(
diff --git a/devices/src/virtio/block/asynchronous.rs b/devices/src/virtio/block/asynchronous.rs
index f4bf888..6c963d8 100644
--- a/devices/src/virtio/block/asynchronous.rs
+++ b/devices/src/virtio/block/asynchronous.rs
@@ -223,7 +223,6 @@
let mut queue = queue.borrow_mut();
queue.add_used(&mem, descriptor_index, len as u32);
queue.trigger_interrupt(&mem, interrupt);
- queue.update_int_required(&mem);
}
// There is one async task running `handle_queue` per virtio queue in use.
@@ -940,8 +939,8 @@
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
@@ -951,8 +950,8 @@
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
@@ -962,8 +961,8 @@
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/block.rs b/devices/src/virtio/block/block.rs
index f7c55e2..e14810c 100644
--- a/devices/src/virtio/block/block.rs
+++ b/devices/src/virtio/block/block.rs
@@ -789,8 +789,8 @@
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
@@ -800,8 +800,8 @@
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
@@ -811,7 +811,8 @@
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());
}
}
diff --git a/devices/src/virtio/console.rs b/devices/src/virtio/console.rs
index 9ec6c7b..80284af 100644
--- a/devices/src/virtio/console.rs
+++ b/devices/src/virtio/console.rs
@@ -3,11 +3,13 @@
// found in the LICENSE file.
use std::io::{self, Read, Write};
+use std::result;
use std::sync::mpsc::{channel, Receiver, TryRecvError};
use std::thread;
use base::{error, Event, PollToken, RawDescriptor, WaitContext};
use data_model::{DataInit, Le16, Le32};
+use thiserror::Error as ThisError;
use vm_memory::GuestMemory;
use super::{
@@ -22,18 +24,144 @@
// If VIRTIO_CONSOLE_F_MULTIPORT is implemented, more queues will be needed.
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];
+#[derive(ThisError, Debug)]
+pub enum ConsoleError {
+ /// There are no more available descriptors to receive into
+ #[error("no rx descriptors available")]
+ RxDescriptorsExhausted,
+ /// Input channel has been disconnected
+ #[error("input channel disconnected")]
+ RxDisconnected,
+}
+
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
-pub(crate) 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 {}
+/// Checks for input from `in_channel_opt` 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
+/// * `in_channel_opt` - Optional input channel to read data from
+/// * `receive_queue` - The receive virtio Queue
+pub fn handle_input<I: SignalableInterrupt>(
+ mem: &GuestMemory,
+ interrupt: &I,
+ in_channel: &Receiver<Vec<u8>>,
+ receive_queue: &mut Queue,
+) -> result::Result<(), ConsoleError> {
+ let mut exhausted_queue = false;
+
+ loop {
+ let desc = match receive_queue.peek(&mem) {
+ Some(d) => d,
+ None => {
+ exhausted_queue = true;
+ break;
+ }
+ };
+ 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);
+ break;
+ }
+ };
+
+ let mut disconnected = false;
+ while writer.available_bytes() > 0 {
+ match in_channel.try_recv() {
+ Ok(data) => {
+ writer.write_all(&data).unwrap();
+ }
+ Err(TryRecvError::Empty) => break,
+ Err(TryRecvError::Disconnected) => {
+ disconnected = true;
+ break;
+ }
+ }
+ }
+
+ let bytes_written = writer.bytes_written() as u32;
+
+ 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 disconnected {
+ return Err(ConsoleError::RxDisconnected);
+ }
+
+ if bytes_written == 0 {
+ break;
+ }
+ }
+
+ if exhausted_queue {
+ Err(ConsoleError::RxDescriptorsExhausted)
+ } else {
+ Ok(())
+ }
+}
+
+/// 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 len = match process_transmit_request(reader, output) {
+ Ok(written) => written,
+ Err(e) => {
+ error!("console: process_transmit_request failed: {}", e);
+ 0
+ }
+ };
+
+ transmit_queue.add_used(&mem, desc_index, len);
+ needs_interrupt = true;
+ }
+
+ if needs_interrupt {
+ transmit_queue.trigger_interrupt(mem, interrupt);
+ }
+}
+
struct Worker {
mem: GuestMemory,
interrupt: Interrupt,
@@ -41,170 +169,82 @@
output: Option<Box<dyn io::Write + Send>>,
}
-fn write_output(output: &mut Box<dyn io::Write>, data: &[u8]) -> io::Result<()> {
+fn write_output(output: &mut 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)
- }
+/// Starts a thread that reads rx_input and sends the input back via the returned channel.
+///
+/// # Arguments
+/// * `rx_input` - Data source that the reader thread will wait on to send data back to the channel
+/// * `in_avail_evt` - Event triggered by the thread when new input is available on the channel
+pub fn spawn_input_thread(
+ mut rx: Box<dyn io::Read + Send>,
+ in_avail_evt: &Event,
+) -> Option<Receiver<Vec<u8>>> {
+ let (send_channel, recv_channel) = channel();
- 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;
-
- 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;
- }
- };
-
- let len = match Self::process_transmit_request(reader, output) {
- Ok(written) => written,
- Err(e) => {
- error!("console: process_transmit_request failed: {}", e);
- 0
- }
- };
-
- transmit_queue.add_used(&self.mem, desc_index, len);
- needs_interrupt = true;
- }
-
- if needs_interrupt {
- self.interrupt.signal_used_queue(transmit_queue.vector);
- }
- }
-
- // 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,
- };
-
- let (send_channel, recv_channel) = channel();
-
- 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 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);
+ 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;
}
- Some(recv_channel)
- }
+ };
- // 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,
- };
-
- 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;
- }
- };
-
- let mut disconnected = false;
- while writer.available_bytes() > 0 {
- match in_channel.try_recv() {
- Ok(data) => {
- writer.write_all(&data).unwrap();
+ // 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(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(recv_channel)
+}
+/// 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)
+}
+
+impl Worker {
fn run(&mut self, mut queues: Vec<Queue>, mut queue_evts: Vec<Event>, kill_evt: Event) {
#[derive(PollToken)]
enum Token {
@@ -234,7 +274,10 @@
// 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 mut in_channel = match self.input.take() {
+ Some(input) => spawn_input_thread(input, &in_avail_evt),
+ None => None,
+ };
let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
(&transmit_evt, Token::TransmitQueueAvailable),
@@ -279,21 +322,50 @@
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 transmit_queue,
+ &mut output,
+ );
}
Token::ReceiveQueueAvailable => {
if let Err(e) = receive_evt.read() {
error!("failed reading receive queue Event: {}", e);
break 'wait;
}
- self.handle_input(&mut in_channel, &mut receive_queue);
+ if let Some(ch) = in_channel.as_ref() {
+ match handle_input(&self.mem, &self.interrupt, ch, &mut receive_queue) {
+ Ok(()) => {}
+ Err(ConsoleError::RxDisconnected) => {
+ // Set in_channel to None so that future handle_input calls exit early.
+ in_channel.take();
+ }
+ // Other console errors are no-ops, so just continue.
+ Err(_) => {
+ continue;
+ }
+ }
+ }
}
Token::InputAvailable => {
if let Err(e) = 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(ch) = in_channel.as_ref() {
+ match handle_input(&self.mem, &self.interrupt, ch, &mut receive_queue) {
+ Ok(()) => {}
+ Err(ConsoleError::RxDisconnected) => {
+ // Set in_channel to None so that future handle_input calls exit early.
+ in_channel.take();
+ }
+ // Other console errors are no-ops, so just continue.
+ Err(_) => {
+ continue;
+ }
+ }
+ }
}
Token::InterruptResample => {
self.interrupt.interrupt_resample();
diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs
index fc839b6..d2ac32d 100644
--- a/devices/src/virtio/descriptor_utils.rs
+++ b/devices/src/virtio/descriptor_utils.rs
@@ -305,6 +305,24 @@
}
}
+ /// 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
@@ -566,6 +584,24 @@
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 +722,21 @@
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
diff --git a/devices/src/virtio/fs/mod.rs b/devices/src/virtio/fs/mod.rs
index bc8c28f..4cf916a 100644
--- a/devices/src/virtio/fs/mod.rs
+++ b/devices/src/virtio/fs/mod.rs
@@ -5,12 +5,13 @@
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 resources::Alloc;
+use sync::Mutex;
use vm_control::{FsMappingRequest, VmResponse};
use vm_memory::GuestMemory;
diff --git a/devices/src/virtio/fs/worker.rs b/devices/src/virtio/fs/worker.rs
index 19fec11..e3bab81 100644
--- a/devices/src/virtio/fs/worker.rs
+++ b/devices/src/virtio/fs/worker.rs
@@ -6,10 +6,11 @@
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 fuse::filesystem::{FileSystem, ZeroCopyReader, ZeroCopyWriter};
+use sync::Mutex;
use vm_control::{FsMappingRequest, VmResponse};
use vm_memory::GuestMemory;
@@ -19,6 +20,8 @@
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 @@
}
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);
@@ -171,7 +171,7 @@
}
if needs_interrupt {
- self.irq.signal_used_queue(self.queue.vector);
+ self.queue.trigger_interrupt(&self.mem, &*self.irq);
}
Ok(())
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 7103ee8..c95d65d 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -8,7 +8,7 @@
mod virtio_gpu;
use std::cell::RefCell;
-use std::collections::{BTreeMap, VecDeque};
+use std::collections::{BTreeMap, HashMap, VecDeque};
use std::convert::TryFrom;
use std::i64;
use std::io::Read;
@@ -20,8 +20,8 @@
use std::time::Duration;
use base::{
- debug, error, warn, AsRawDescriptor, AsRawDescriptors, Event, ExternalMapping, PollToken,
- RawDescriptor, Tube, WaitContext,
+ debug, error, warn, AsRawDescriptor, Event, ExternalMapping, PollToken, RawDescriptor, Tube,
+ WaitContext,
};
use data_model::*;
@@ -118,10 +118,37 @@
pub offsets: [u32; 4],
}
+#[derive(Hash, Eq, PartialEq)]
+enum VirtioGpuRing {
+ Global,
+ ContextSpecific { ctx_id: u32, ring_idx: u32 },
+}
+
+struct FenceDescriptor {
+ ring: VirtioGpuRing,
+ fence_id: u64,
+ index: u16,
+ len: u32,
+}
+
+struct FenceState {
+ descs: Vec<FenceDescriptor>,
+ completed_fences: HashMap<VirtioGpuRing, u64>,
+}
+
+impl Default for FenceState {
+ fn default() -> Self {
+ FenceState {
+ descs: Vec::new(),
+ completed_fences: HashMap::new(),
+ }
+ }
+}
+
trait QueueReader {
fn pop(&self, mem: &GuestMemory) -> Option<DescriptorChain>;
fn add_used(&self, mem: &GuestMemory, desc_index: u16, len: u32);
- fn signal_used(&self);
+ fn signal_used(&self, mem: &GuestMemory);
}
struct LocalQueueReader {
@@ -147,8 +174,10 @@
self.queue.borrow_mut().add_used(mem, desc_index, len)
}
- fn signal_used(&self) {
- self.interrupt.signal_used_queue(self.queue.borrow().vector);
+ fn signal_used(&self, mem: &GuestMemory) {
+ self.queue
+ .borrow_mut()
+ .trigger_interrupt(mem, &*self.interrupt);
}
}
@@ -176,8 +205,8 @@
self.queue.lock().add_used(mem, desc_index, len)
}
- fn signal_used(&self) {
- self.interrupt.signal_used_queue(self.queue.lock().vector);
+ fn signal_used(&self, mem: &GuestMemory) {
+ self.queue.lock().trigger_interrupt(mem, &*self.interrupt);
}
}
@@ -227,58 +256,61 @@
)
}
+/// Create a handler that writes into the completed fence queue
+fn create_fence_handler(
+ mem: GuestMemory,
+ ctrl_queue: SharedQueueReader,
+ fence_state: Arc<Mutex<FenceState>>,
+) -> RutabagaFenceHandler {
+ 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,
+ },
+ };
+
+ 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);
+ }
+
+ if signal {
+ ctrl_queue.signal_used(&mem);
+ }
+ })
+}
+
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;
- }
-
- // Different 3D contexts
- if desc_fence.ctx_id != completed.ctx_id {
- return false;
- }
-
- // Different fence contexts with same 3D context
- if desc_fence.fence_ctx_idx != completed.fence_ctx_idx {
- return false;
- }
-
- true
-}
-
struct Frontend {
- return_ctrl_descriptors: VecDeque<ReturnDescriptor>,
+ 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,
}
}
@@ -509,9 +541,7 @@
};
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);
}
@@ -672,7 +702,7 @@
flags,
fence_id,
ctx_id,
- fence_ctx_idx: info,
+ ring_idx: info,
};
gpu_response = match self.virtio_gpu.create_fence(fence_data) {
Ok(_) => gpu_response,
@@ -692,21 +722,30 @@
}
if flags & VIRTIO_GPU_FLAG_FENCE != 0 {
- self.fence_descriptors.push(FenceDescriptor {
- desc_fence: RutabagaFenceData {
- flags,
- fence_id,
+ let ring = match flags & VIRTIO_GPU_FLAG_INFO_RING_IDX {
+ 0 => VirtioGpuRing::Global,
+ _ => VirtioGpuRing::ContextSpecific {
ctx_id,
- fence_ctx_idx: info,
+ ring_idx: info,
},
- index: desc_index,
- len,
- });
+ };
- return None;
+ // 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,
+ index: desc_index,
+ len,
+ });
+
+ return None;
+ }
}
- // No fence, respond now.
+ // No fence (or already completed fence), respond now.
}
Some(ReturnDescriptor {
index: desc_index,
@@ -717,29 +756,12 @@
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) {
+ self.virtio_gpu.fence_poll();
}
- 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
- })
+ fn has_pending_fences(&self) -> bool {
+ !self.fence_state.lock().descs.is_empty()
}
}
@@ -808,7 +830,7 @@
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() {
+ let duration = if self.state.has_pending_fences() {
Duration::from_millis(FENCE_POLL_MS)
} else {
Duration::new(i64::MAX as u64, 0)
@@ -882,11 +904,6 @@
self.state.fence_poll();
- while let Some(desc) = self.state.return_ctrl() {
- self.ctrl_queue.add_used(&self.mem, desc.index, desc.len);
- signal_used_ctrl = true;
- }
-
// Process the entire control queue before the resource bridge in case a resource is
// created or destroyed by the control queue. Processing the resource bridge first may
// lead to a race condition.
@@ -902,11 +919,11 @@
}
if signal_used_ctrl {
- self.ctrl_queue.signal_used();
+ self.ctrl_queue.signal_used(&self.mem);
}
if signal_used_cursor {
- self.cursor_queue.signal_used();
+ self.cursor_queue.signal_used(&self.mem);
}
}
}
@@ -952,7 +969,6 @@
external_blob: bool,
rutabaga_component: RutabagaComponentType,
base_features: u64,
- mem: GuestMemory,
udmabuf: bool,
}
@@ -968,7 +984,6 @@
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)
@@ -1038,7 +1053,6 @@
external_blob,
rutabaga_component: component,
base_features,
- mem,
udmabuf: gpu_parameters.udmabuf,
}
}
@@ -1105,10 +1119,6 @@
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());
}
@@ -1209,6 +1219,7 @@
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()));
if let (Some(gpu_device_tube), Some(pci_bar), Some(rutabaga_builder)) = (
self.gpu_device_tube.take(),
self.pci_bar.take(),
@@ -1218,7 +1229,11 @@
thread::Builder::new()
.name("virtio_gpu".to_string())
.spawn(move || {
- let fence_handler = RutabagaFenceClosure::new(|_completed_fence| {});
+ let fence_handler = create_fence_handler(
+ mem.clone(),
+ ctrl_queue.clone(),
+ fence_state.clone(),
+ );
let virtio_gpu = match build(
&display_backends,
@@ -1240,13 +1255,13 @@
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 95306fd..953b690 100644
--- a/devices/src/virtio/gpu/protocol.rs
+++ b/devices/src/virtio/gpu/protocol.rs
@@ -97,9 +97,6 @@
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",
@@ -147,7 +144,7 @@
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)]
diff --git a/devices/src/virtio/input/mod.rs b/devices/src/virtio/input/mod.rs
index 12ccb5d..9e247a0 100644
--- a/devices/src/virtio/input/mod.rs
+++ b/devices/src/virtio/input/mod.rs
@@ -524,7 +524,8 @@
}
}
if needs_interrupt {
- self.interrupt.signal_used_queue(self.event_queue.vector);
+ self.event_queue
+ .trigger_interrupt(&self.guest_memory, &self.interrupt);
}
}
diff --git a/devices/src/virtio/iommu.rs b/devices/src/virtio/iommu.rs
new file mode 100644
index 0000000..6df1497
--- /dev/null
+++ b/devices/src/virtio/iommu.rs
@@ -0,0 +1,842 @@
+// 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::{
+ copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader, SignalableInterrupt,
+ VirtioDevice, Writer, TYPE_IOMMU,
+};
+use crate::pci::PciAddress;
+use crate::vfio::{VfioContainer, VfioError};
+use acpi_tables::sdt::SDT;
+use base::{
+ error, warn, AsRawDescriptor, Error as SysError, Event, PollToken, RawDescriptor,
+ Result as SysResult, WaitContext,
+};
+use data_model::DataInit;
+use std::collections::BTreeMap;
+use std::fmt::{self, Display};
+use std::io::{self, Write};
+use std::mem::size_of;
+use std::sync::Arc;
+use std::{result, thread};
+use sync::Mutex;
+use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
+
+const QUEUE_SIZE: u16 = 256;
+const NUM_QUEUES: usize = 2;
+const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
+
+/// Virtio IOMMU features
+const VIRTIO_IOMMU_F_INPUT_RANGE: u32 = 0;
+const VIRTIO_IOMMU_F_MAP_UNMAP: u32 = 2;
+const VIRTIO_IOMMU_F_PROBE: u32 = 4;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuRange64 {
+ start: u64,
+ end: u64,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuRange64 {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuConfig {
+ page_size_mask: u64,
+ input_range: VirtioIommuRange64,
+ domain_range: [u32; 2],
+ probe_size: u32,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuConfig {}
+
+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 {}
+
+/// Virtio IOMMU request type
+const VIRTIO_IOMMU_T_ATTACH: u8 = 1;
+const VIRTIO_IOMMU_T_DETACH: u8 = 2;
+const VIRTIO_IOMMU_T_MAP: u8 = 3;
+const VIRTIO_IOMMU_T_UNMAP: u8 = 4;
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+const VIRTIO_IOMMU_T_PROBE: u8 = 5;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqHead {
+ type_: u8,
+ reserved: [u8; 3],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqHead {}
+
+const VIRTIO_IOMMU_S_OK: u8 = 0;
+const VIRTIO_IOMMU_S_UNSUPP: u8 = 2;
+const VIRTIO_IOMMU_S_INVAL: u8 = 4;
+const VIRTIO_IOMMU_S_RANGE: u8 = 5;
+const VIRTIO_IOMMU_S_NOENT: u8 = 6;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqTail {
+ status: u8,
+ reserved: [u8; 3],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqTail {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqAttach {
+ domain: u32,
+ endpoint: u32,
+ reserved: [u8; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqAttach {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqDetach {
+ domain: u32,
+ endpoint: u32,
+ reserved: [u8; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqDetach {}
+
+const VIRTIO_IOMMU_MAP_F_READ: u32 = 1;
+const VIRTIO_IOMMU_MAP_F_WRITE: u32 = 2;
+const VIRTIO_IOMMU_MAP_F_MASK: u32 = VIRTIO_IOMMU_MAP_F_READ | VIRTIO_IOMMU_MAP_F_WRITE;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqMap {
+ domain: u32,
+ virt_start: u64,
+ virt_end: u64,
+ phys_start: u64,
+ flags: u32,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqMap {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqUnmap {
+ domain: u32,
+ virt_start: u64,
+ virt_end: u64,
+ reserved: [u8; 4],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqUnmap {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuReqProbe {
+ endpoint: u32,
+ reserved: [u64; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuReqProbe {}
+
+// Size of struct virtio_iommu_probe_property
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+const IOMMU_PROBE_SIZE: usize = size_of::<VirtioIommuProbeResvMem>();
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuProbeResvMem {
+ type_: u16,
+ length: u16,
+ subtype: u8,
+ reserved: [u8; 3],
+ start: u64,
+ end: u64,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuProbeResvMem {}
+
+#[derive(Debug)]
+pub enum IommuError {
+ CreateWaitContext(SysError),
+ WaitError(SysError),
+ GuestMemoryRead(io::Error),
+ GuestMemoryWrite(io::Error),
+ CreateReader(DescriptorError),
+ CreateWriter(DescriptorError),
+ ReadQueueEvent(SysError),
+ UnexpectedDescriptor,
+ WriteBufferTooSmall,
+ VfioContainerError(VfioError),
+ GetHostAddress(GuestMemoryError),
+}
+
+impl Display for IommuError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::IommuError::*;
+
+ match self {
+ CreateWaitContext(e) => write!(f, "failed to create wait context: {}", e),
+ WaitError(err) => write!(f, "failed to wait for events: {}", err),
+ GuestMemoryWrite(e) => write!(f, "failed to write to guest address: {}", e),
+ GuestMemoryRead(e) => write!(f, "failed to read from guest address: {}", e),
+ CreateReader(e) => write!(f, "failed to create reader: {}", e),
+ CreateWriter(e) => write!(f, "failed to create writer: {}", e),
+ ReadQueueEvent(err) => write!(f, "failed to read from virtio queue Event: {}", err),
+ UnexpectedDescriptor => write!(f, "unexpected descriptor error"),
+ WriteBufferTooSmall => write!(f, "write buffer length too small"),
+ VfioContainerError(e) => write!(f, "failed on VFIO ioctl call: {}", e),
+ GetHostAddress(e) => write!(f, "failed getting host address: {}", e),
+ }
+ }
+}
+
+struct Worker {
+ interrupt: Interrupt,
+ mem: GuestMemory,
+ page_mask: u64,
+ // contains all pass-through endpoints that attach to the IOMMU device
+ endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+ // 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 VfioContainer
+ domain_map: BTreeMap<u32, (u32, Arc<Mutex<VfioContainer>>)>,
+}
+
+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 dettach_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 VirtioIommuReqTail,
+ ) -> result::Result<usize, IommuError> {
+ let req: VirtioIommuReqAttach = 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 = req.domain;
+ let endpoint = req.endpoint;
+ if !self.endpoints.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.dettach_endpoint(endpoint);
+ }
+
+ if let Some(vfio_container) = self.endpoints.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 VirtioIommuReqTail,
+ ) -> result::Result<usize, IommuError> {
+ let req: VirtioIommuReqMap = 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 & req.phys_start != 0
+ || self.page_mask & req.virt_start != 0
+ || self.page_mask & (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 req.flags & !VIRTIO_IOMMU_MAP_F_MASK != 0 {
+ tail.status = VIRTIO_IOMMU_S_INVAL;
+ return Ok(0);
+ }
+
+ let domain = req.domain;
+ 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 = req.flags & VIRTIO_IOMMU_MAP_F_WRITE != 0;
+
+ if let Some(vfio_container) = self.domain_map.get(&domain) {
+ let size = req.virt_end - req.virt_start + 1u64;
+ let host_addr = self
+ .mem
+ .get_host_address_range(GuestAddress(req.phys_start), size as usize)
+ .map_err(IommuError::GetHostAddress)?;
+
+ // Safe because both guest and host address are guaranteed by
+ // get_host_address_range() to be valid
+ let vfio_map_result = unsafe {
+ vfio_container.1.lock().vfio_dma_map(
+ req.virt_start,
+ size,
+ host_addr as u64,
+ write_en,
+ )
+ };
+
+ match vfio_map_result {
+ Ok(()) => (),
+ Err(e) => match sys_util::Error::last() {
+ err if err.errno() == libc::EEXIST => {
+ // 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::VfioContainerError(e)),
+ },
+ }
+ }
+
+ Ok(0)
+ }
+
+ fn process_dma_unmap_request(
+ &mut self,
+ reader: &mut Reader,
+ tail: &mut VirtioIommuReqTail,
+ ) -> result::Result<usize, IommuError> {
+ let req: VirtioIommuReqUnmap = reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+ let domain = req.domain;
+ if let Some(vfio_container) = self.domain_map.get(&domain) {
+ let size = req.virt_end - req.virt_start + 1;
+ vfio_container
+ .1
+ .lock()
+ .vfio_dma_unmap(req.virt_start, size)
+ .map_err(IommuError::VfioContainerError)?;
+ } 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 VirtioIommuReqTail,
+ ) -> result::Result<usize, IommuError> {
+ let req: VirtioIommuReqProbe = reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+ let endpoint = req.endpoint;
+
+ // 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 !self.endpoints.contains_key(&endpoint) {
+ tail.status = VIRTIO_IOMMU_S_NOENT;
+ }
+
+ let properties_size = writer.available_bytes() - size_of::<VirtioIommuReqTail>();
+
+ // 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 = VirtioIommuProbeResvMem {
+ type_: VIRTIO_IOMMU_PROBE_T_RESV_MEM,
+ length: IOMMU_PROBE_SIZE as u16 - PROBE_PROPERTY_SIZE,
+ subtype: VIRTIO_IOMMU_RESV_MEM_T_MSI,
+ start: X86_MSI_IOVA_START,
+ end: X86_MSI_IOVA_END,
+ ..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::<VirtioIommuReqTail>();
+
+ 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,
+ ) -> result::Result<usize, IommuError> {
+ 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::<VirtioIommuReqTail>() {
+ return Err(IommuError::WriteBufferTooSmall);
+ }
+
+ let req_head: VirtioIommuReqHead =
+ reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+ let mut tail = VirtioIommuReqTail {
+ 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)?,
+ 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)?
+ }
+ _ => return Err(IommuError::UnexpectedDescriptor),
+ };
+
+ writer
+ .write_all(tail.as_slice())
+ .map_err(IommuError::GuestMemoryWrite)?;
+ Ok((reply_len as usize) + size_of::<VirtioIommuReqTail>())
+ }
+
+ fn request_queue(&mut self, req_queue: &mut Queue) -> bool {
+ let mut needs_interrupt = false;
+ while let Some(avail_desc) = req_queue.pop(&self.mem) {
+ let desc_index = avail_desc.index;
+
+ let len = match self.execute_request(&avail_desc) {
+ Ok(len) => len as u32,
+ Err(e) => {
+ error!("{}", e);
+
+ // If a request type is not recognized, the device SHOULD NOT write
+ // the buffer and SHOULD set the used length to zero
+ 0
+ }
+ };
+
+ req_queue.add_used(&self.mem, desc_index, len as u32);
+ needs_interrupt = true;
+ }
+
+ needs_interrupt
+ }
+
+ fn run(
+ &mut self,
+ mut queues: Vec<Queue>,
+ mut queue_evts: Vec<Event>,
+ kill_evt: Event,
+ ) -> Result<(), IommuError> {
+ #[derive(PollToken)]
+ enum Token {
+ RequestQueue,
+ InterruptResample,
+ Kill,
+ }
+
+ let (mut req_queue, req_evt) = (queues.remove(0), queue_evts.remove(0));
+ let wait_ctx: WaitContext<Token> =
+ WaitContext::build_with(&[(&req_evt, Token::RequestQueue), (&kill_evt, Token::Kill)])
+ .map_err(IommuError::CreateWaitContext)?;
+
+ if let Some(resample_evt) = self.interrupt.get_resample_evt() {
+ wait_ctx
+ .add(resample_evt, Token::InterruptResample)
+ .map_err(IommuError::CreateWaitContext)?;
+ }
+
+ 'wait: loop {
+ let mut needs_interrupt = false;
+ let events = wait_ctx.wait().map_err(IommuError::WaitError)?;
+ for event in events.iter().filter(|e| e.is_readable) {
+ match event.token {
+ Token::RequestQueue => {
+ req_evt.read().map_err(IommuError::ReadQueueEvent)?;
+ needs_interrupt |= self.request_queue(&mut req_queue);
+ }
+ Token::InterruptResample => {
+ self.interrupt.interrupt_resample();
+ }
+ Token::Kill => break 'wait,
+ }
+ }
+ if needs_interrupt {
+ req_queue.trigger_interrupt(&self.mem, &self.interrupt);
+ }
+ }
+ Ok(())
+ }
+}
+
+/// Virtio device for IOMMU memory management.
+pub struct Iommu {
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<Worker>>,
+ config: VirtioIommuConfig,
+ avail_features: u64,
+ endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+}
+
+impl Iommu {
+ /// Create a new virtio IOMMU device.
+ pub fn new(
+ base_features: u64,
+ endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+ phys_max_addr: u64,
+ ) -> SysResult<Iommu> {
+ let mut page_size_mask = !0_u64;
+ for (_, container) in endpoints.iter() {
+ page_size_mask &= container
+ .lock()
+ .vfio_get_iommu_page_size_mask()
+ .map_err(|_e| SysError::new(libc::EIO))?;
+ }
+
+ if page_size_mask == 0 {
+ error!("failed to get IOMMU device valid page size masks");
+ return Err(SysError::new(libc::EIO));
+ }
+
+ let input_range = VirtioIommuRange64 {
+ start: 0_u64,
+ end: phys_max_addr,
+ };
+
+ let config = VirtioIommuConfig {
+ page_size_mask,
+ input_range,
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ probe_size: IOMMU_PROBE_SIZE as u32,
+ ..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,
+ })
+ }
+}
+
+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 (_, vfio) in self.endpoints.iter() {
+ rds.push(vfio.lock().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 << self.config.page_size_mask.trailing_zeros()) - 1;
+ let eps = self.endpoints.clone();
+ let worker_result = thread::Builder::new()
+ .name("virtio_iommu".to_string())
+ .spawn(move || {
+ let mut worker = Worker {
+ interrupt,
+ mem,
+ page_mask,
+ endpoints: eps,
+ endpoint_map: BTreeMap::new(),
+ domain_map: BTreeMap::new(),
+ };
+ let result = worker.run(queues, queue_evts, kill_evt);
+ 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),
+ }
+ }
+
+ #[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() + 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()
+ });
+ }
+ sdts.push(viot);
+ Some(sdts)
+ }
+}
diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs
index 0daf6ea..b9c069e 100644
--- a/devices/src/virtio/mod.rs
+++ b/devices/src/virtio/mod.rs
@@ -5,10 +5,10 @@
//! Implements virtio devices, queues, and transport mechanisms.
mod balloon;
-mod console;
mod descriptor_utils;
mod input;
mod interrupt;
+mod iommu;
mod p9;
mod pmem;
mod queue;
@@ -23,6 +23,7 @@
pub mod wl;
pub mod block;
+pub mod console;
pub mod fs;
#[cfg(feature = "gpu")]
pub mod gpu;
@@ -41,11 +42,14 @@
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"))]
@@ -58,6 +62,8 @@
use std::cmp;
use std::convert::TryFrom;
+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;
@@ -81,8 +87,10 @@
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
@@ -120,6 +128,7 @@
TYPE_VSOCK => "vsock",
TYPE_CRYPTO => "crypto",
TYPE_IOMMU => "iommu",
+ TYPE_SOUND => "snd",
TYPE_FS => "fs",
TYPE_PMEM => "pmem",
TYPE_WL => "wl",
@@ -156,7 +165,7 @@
/// 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 {
features |= 1 << VIRTIO_F_ACCESS_PLATFORM;
diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs
index 6dcad5c..a9153ac 100644
--- a/devices/src/virtio/net.rs
+++ b/devices/src/virtio/net.rs
@@ -191,7 +191,7 @@
}
if needs_interrupt {
- interrupt.signal_used_queue(rx_queue.vector);
+ rx_queue.trigger_interrupt(mem, interrupt);
}
if exhausted_queue {
@@ -233,7 +233,7 @@
tx_queue.add_used(mem, index, 0);
}
- interrupt.signal_used_queue(tx_queue.vector);
+ tx_queue.trigger_interrupt(mem, interrupt);
}
pub fn process_ctrl<I: SignalableInterrupt, T: TapT>(
@@ -306,7 +306,7 @@
ctrl_queue.add_used(&mem, index, writer.bytes_written() as u32);
}
- interrupt.signal_used_queue(ctrl_queue.vector);
+ ctrl_queue.trigger_interrupt(mem, interrupt);
Ok(())
}
diff --git a/devices/src/virtio/p9.rs b/devices/src/virtio/p9.rs
index 670645d..a4f5629 100644
--- a/devices/src/virtio/p9.rs
+++ b/devices/src/virtio/p9.rs
@@ -99,8 +99,7 @@
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 9a3ca39..1e0c55b 100644
--- a/devices/src/virtio/pmem.rs
+++ b/devices/src/virtio/pmem.rs
@@ -217,7 +217,7 @@
}
}
if needs_interrupt {
- self.interrupt.signal_used_queue(self.queue.vector);
+ self.queue.trigger_interrupt(&self.memory, &self.interrupt);
}
}
}
diff --git a/devices/src/virtio/queue.rs b/devices/src/virtio/queue.rs
index 1fd614b..06ae892 100644
--- a/devices/src/virtio/queue.rs
+++ b/devices/src/virtio/queue.rs
@@ -345,6 +345,9 @@
//
// 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>) {
+ // Ensure that all previous writes are available before this one.
+ fence(Ordering::Release);
+
let avail_event_addr = self
.used_ring
.unchecked_add(4 + 8 * u64::from(self.actual_size()));
@@ -358,6 +361,10 @@
#[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();
+
+ // Don't allow subsequent reads to be ordered before the avail_flags read.
+ fence(Ordering::Acquire);
+
avail_flags & flag == flag
}
@@ -373,6 +380,10 @@
.avail_ring
.unchecked_add(4 + 2 * u64::from(self.actual_size()));
let used_event: u16 = mem.read_obj_from_addr(used_event_addr).unwrap();
+
+ // Prevent any reads after this from being ordered before the used_event read.
+ fence(Ordering::Acquire);
+
Wrapping(used_event)
}
@@ -393,6 +404,9 @@
//
// Changes the bit specified by the mask in `flag` to `value`.
fn set_used_flag(&mut self, mem: &GuestMemory, flag: u16, value: bool) {
+ // This fence ensures all descriptor writes are visible before the flag update.
+ fence(Ordering::Release);
+
let mut used_flags: u16 = mem.read_obj_from_addr(self.used_ring).unwrap();
if value {
used_flags |= flag;
@@ -488,11 +502,6 @@
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 +511,9 @@
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 +532,7 @@
// 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)
}
}
diff --git a/devices/src/virtio/rng.rs b/devices/src/virtio/rng.rs
index 65d671e..5b9da29 100644
--- a/devices/src/virtio/rng.rs
+++ b/devices/src/virtio/rng.rs
@@ -119,7 +119,7 @@
}
}
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/constants.rs b/devices/src/virtio/snd/constants.rs
index 28a73a0..114d098 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;
-pub const CHANNEL_MAP_INFO: u32 = 0x0200;
+/* 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;
+
+/* 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_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,38 +87,6 @@
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 */
diff --git a/devices/src/virtio/snd/layout.rs b/devices/src/virtio/snd/layout.rs
index 8881af0..a217bff 100644
--- a/devices/src/virtio/snd/layout.rs
+++ b/devices/src/virtio/snd/layout.rs
@@ -41,7 +41,7 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 e6b87ac..cb65893 100644
--- a/devices/src/virtio/snd/mod.rs
+++ b/devices/src/virtio/snd/mod.rs
@@ -8,3 +8,6 @@
pub mod layout;
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 602ec4d..c993b37 100644
--- a/devices/src/virtio/snd/vios_backend/mod.rs
+++ b/devices/src/virtio/snd/vios_backend/mod.rs
@@ -9,3 +9,217 @@
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 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];
+
+#[derive(ThisError, Debug)]
+pub enum SoundError {
+ #[error("Failed to create VioS client: {0}")]
+ ClientNew(Error),
+ #[error("Failed to get event notifier from VioS client: {0}")]
+ ClientEventNotifier(Error),
+ #[error("Error creating WaitContext: {0}")]
+ WaitCtx(BaseError),
+ #[error("Error consuming queue event: {0}")]
+ QueueEvt(BaseError),
+ #[error("Error with queue descriptor: {0}")]
+ Descriptor(DescriptorError),
+ #[error("The driver sent an invalid message")]
+ BadDriverMsg,
+ #[error("Failed to read/write from/to queue: {0}")]
+ QueueIO(IoError),
+ #[error("Failed to create thread: {0}")]
+ CreateThread(IoError),
+ #[error("Attempted a {0} operation while on the wrong state: {1}, this is a bug")]
+ ImpossibleState(&'static str, &'static str),
+ #[error("Failed to create event pair: {0}")]
+ CreateEvent(BaseError),
+ #[error("Failed to send message: {0}")]
+ StreamThreadSend(SendError<StreamMsg>),
+ #[error("Failed to receive message: {0}")]
+ StreamThreadRecv(RecvError),
+ #[error("Failed to create Reader from descriptor chain: {0}")]
+ CreateReader(DescriptorError),
+ #[error("Failed to create Writer from descriptor chain: {0}")]
+ CreateWriter(DescriptorError),
+}
+
+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 87bfbee..e839594 100644
--- a/devices/src/virtio/snd/vios_backend/shm_streams.rs
+++ b/devices/src/virtio/snd/vios_backend/shm_streams.rs
@@ -15,7 +15,9 @@
use audio_streams::shm_streams::{BufferSet, ServerRequest, ShmStream, ShmStreamSource};
use audio_streams::{BoxError, SampleFormat, StreamDirection, StreamEffect};
-use base::{error, SharedMemory, SharedMemoryUnix};
+use base::{error, MemoryMapping, MemoryMappingBuilder, SharedMemory, SharedMemoryUnix};
+use data_model::VolatileMemory;
+use sync::Mutex;
use std::fs::File;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
@@ -31,16 +33,43 @@
// 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,
})
}
}
@@ -80,8 +109,19 @@
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 for VioSShmStreamSource {
@@ -98,32 +138,30 @@
client_shm: &SysSharedMemory,
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,
- };
+ self.vios_client.start_bg_thread()?;
let stream_id = self
- .vios_client
- .get_unused_stream_id(virtio_dir)
+ .get_unused_stream_id(direction)
.ok_or(Box::new(Error::NoStreamsAvailable))?;
- 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
- })
+ 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 +187,7 @@
direction: StreamDirection,
vios_client: Arc<VioSClient>,
client_shm: SharedMemory,
+ state: Arc<Mutex<StreamState>>,
}
impl VioSndShmStream {
@@ -162,6 +201,7 @@
direction: StreamDirection,
vios_client: Arc<VioSClient>,
client_shm: &SysSharedMemory,
+ state: Arc<Mutex<StreamState>>,
) -> GenericResult<Box<dyn ShmStream>> {
let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
@@ -191,6 +231,7 @@
direction,
vios_client,
client_shm: client_shm_clone,
+ state,
}))
}
}
@@ -233,20 +274,54 @@
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(())
@@ -267,5 +342,28 @@
{
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 9aec672..696efac 100644
--- a/devices/src/virtio/snd/vios_backend/shm_vios.rs
+++ b/devices/src/virtio/snd/vios_backend/shm_vios.rs
@@ -8,11 +8,11 @@
use base::{
error, net::UnixSeqpacket, AsRawDescriptor, Error as BaseError, Event, FromRawDescriptor,
IntoRawDescriptor, MemoryMapping, MemoryMappingBuilder, MmapError, PollToken, SafeDescriptor,
- ScmSocket, SharedMemory, WaitContext,
+ ScmSocket, 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::os::unix::io::{AsRawFd, FromRawFd, RawFd};
@@ -51,10 +51,10 @@
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 in unexpected state: {0:?}")]
- UnexpectedState(StreamState),
#[error("Invalid operation for stream direction: {0}")]
WrongDirection(u8),
#[error("Insuficient space for the new buffer in the queue's buffer area")]
@@ -75,6 +75,8 @@
EventCreateError(BaseError),
#[error("Failed to dup Recv event: {0}")]
EventDupError(BaseError),
+ #[error("Failed to signal event: {0}")]
+ EventWriteError(BaseError),
#[error("Failed to create Recv thread's WaitContext: {0}")]
WaitContextCreateError(BaseError),
#[error("Error waiting for events")]
@@ -99,17 +101,28 @@
/// 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<()>>>>,
}
@@ -180,351 +193,472 @@
));
}
- 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 recv_running = Arc::new(Mutex::new(true));
+ let rx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>> =
+ Arc::new(Mutex::new(HashMap::new()));
+ 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(Error::EventDupError)?;
- let rx_socket = self
- .rx
- .lock()
- .socket
+ .event_socket
.try_clone()
.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)
- }
-
- pub fn request_audio_data(
- &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_OUTPUT
{
- 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_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))
+ }
+
+ /// 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,
+ size: usize,
+ callback: Cb,
+ ) -> Result<(u32, R)> {
+ if self
+ .streams
+ .get(stream_id as usize)
+ .ok_or(Error::InvalidStreamId(stream_id))?
+ .direction
+ != VIRTIO_SND_D_INPUT
+ {
+ 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())
- };
- let recv_event = self.recv_event.lock().as_raw_descriptor();
- vec![
- control_fd,
- event_fd,
- tx_socket_fd,
- tx_shm_fd,
- rx_socket_fd,
- rx_shm_fd,
- recv_event,
- ]
+ 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(&*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_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: req_code.into(),
+ },
+ start_id: 0u32.into(),
+ count: (count as u32).into(),
+ size: (std::mem::size_of::<virtio_snd_query_info>() as u32).into(),
+ };
+ let control_socket_lock = self.control_socket.lock();
+ seq_socket_send(&*control_socket_lock, req)?;
+ let reply = control_socket_lock
+ .recv_as_vec()
+ .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(count * info_size, reply.len()),
+ ));
+ }
+ 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;
- let info_size = std::mem::size_of::<virtio_snd_pcm_info>();
- let req = virtio_snd_query_info {
- hdr: virtio_snd_hdr {
- code: STREAM_INFO.into(),
- },
- start_id: 0u32.into(),
- count: (num_streams 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
- .recv_as_vec()
- .map_err(Error::ServerIOError)?;
- if info_vec.len() != num_streams * info_size {
- return Err(Error::ProtocolError(
- ProtocolErrorKind::UnexpectedMessageSize(num_streams * info_size, info_vec.len()),
- ));
+ if num_streams == 0 {
+ return Ok(());
}
- 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(),
- );
+ 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(Error::WaitContextCreateError)?;
- while *running.lock() {
+ 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(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 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
@@ -540,10 +674,14 @@
})
}
-fn await_status(promise: Receiver<(u32, usize)>) -> Result<usize> {
- let (status, consumed_len) = promise.recv().map_err(Error::BufferStatusSenderLost)?;
+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))
}
@@ -554,7 +692,7 @@
file: File,
mmap: MemoryMapping,
size: usize,
- next: usize,
+ next: Mutex<usize>,
}
impl IoBufferQueue {
@@ -571,92 +709,45 @@
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(Error::VolatileMemoryError)?;
- let dst_slice = self
- .mmap
- .get_slice(shm_offset, size)
- .map_err(Error::VolatileMemoryError)?;
- src_slice.copy_to_volatile_slice(dst_slice);
- Ok(shm_offset)
+ fn buffer_at(&self, offset: usize, len: usize) -> Result<VolatileSlice> {
+ self.mmap
+ .get_slice(offset, len)
+ .map_err(Error::VolatileMemoryError)
}
- 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(Error::VolatileMemoryError)?;
- let src_slice = self
- .mmap
- .get_slice(src_offset, size)
- .map_err(Error::VolatileMemoryError)?;
- src_slice.copy_to_volatile_slice(dst_slice);
- Ok(())
+ fn try_clone_socket(&self) -> Result<UnixSeqpacket> {
+ self.socket
+ .try_clone()
+ .map_err(Error::UnixSeqpacketDupError)
}
-}
-/// 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,
-}
-
-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.
@@ -674,7 +765,7 @@
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(),
},
@@ -689,29 +780,12 @@
}
}
-/// 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))
+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())
@@ -739,7 +813,7 @@
Ok(())
}
-const VIOS_VERSION: u32 = 1;
+const VIOS_VERSION: u32 = 2;
#[repr(C)]
#[derive(Copy, Clone, Default)]
@@ -752,6 +826,12 @@
// 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 0000000..cf5166a
--- /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 0000000..7fe2235
--- /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 e9e07c9..57f6c51 100644
--- a/devices/src/virtio/tpm.rs
+++ b/devices/src/virtio/tpm.rs
@@ -81,23 +81,23 @@
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 mut needs_interrupt = NeedsInterrupt::No;
+ while let Some(avail_desc) = self.queue.pop(&self.mem) {
+ let index = avail_desc.index;
- let index = avail_desc.index;
+ let len = match self.device.perform_work(&self.mem, 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 @@
}
}
if needs_interrupt == NeedsInterrupt::Yes {
- self.interrupt.signal_used_queue(self.queue.vector);
+ self.queue.trigger_interrupt(&self.mem, &self.interrupt);
}
}
}
diff --git a/devices/src/virtio/vhost/user/block.rs b/devices/src/virtio/vhost/user/block.rs
index 135bff7..926d2cd 100644
--- a/devices/src/virtio/vhost/user/block.rs
+++ b/devices/src/virtio/vhost/user/block.rs
@@ -47,6 +47,7 @@
| 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;
diff --git a/devices/src/virtio/vhost/user/console.rs b/devices/src/virtio/vhost/user/console.rs
index 319f05b..df6076f 100644
--- a/devices/src/virtio/vhost/user/console.rs
+++ b/devices/src/virtio/vhost/user/console.rs
@@ -34,6 +34,7 @@
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;
diff --git a/devices/src/virtio/vhost/user/fs.rs b/devices/src/virtio/vhost/user/fs.rs
index ca4be80..02dfa01 100644
--- a/devices/src/virtio/vhost/user/fs.rs
+++ b/devices/src/virtio/vhost/user/fs.rs
@@ -53,6 +53,7 @@
let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
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 =
diff --git a/devices/src/virtio/vhost/user/mac80211_hwsim.rs b/devices/src/virtio/vhost/user/mac80211_hwsim.rs
new file mode 100644
index 0000000..f73fa43
--- /dev/null
+++ b/devices/src/virtio/vhost/user/mac80211_hwsim.rs
@@ -0,0 +1,168 @@
+// 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 cros_async::Executor;
+use thiserror::Error as ThisError;
+use vm_memory::GuestMemory;
+use vmm_vhost::vhost_user::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::{Interrupt, Queue, VirtioDevice, TYPE_MAC80211_HWSIM, VIRTIO_F_VERSION_1};
+
+use std::result::Result;
+
+#[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 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) {
+ 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/mod.rs b/devices/src/virtio/vhost/user/mod.rs
index 568ab88..03d28e7 100644
--- a/devices/src/virtio/vhost/user/mod.rs
+++ b/devices/src/virtio/vhost/user/mod.rs
@@ -6,6 +6,7 @@
mod console;
mod fs;
mod handler;
+mod mac80211_hwsim;
mod net;
mod wl;
mod worker;
@@ -14,6 +15,7 @@
pub use self::console::*;
pub use self::fs::*;
pub use self::handler::VhostUserHandler;
+pub use self::mac80211_hwsim::*;
pub use self::net::*;
pub use self::wl::*;
diff --git a/devices/src/virtio/vhost/user/net.rs b/devices/src/virtio/vhost/user/net.rs
index 90a0cf6..bff2783 100644
--- a/devices/src/virtio/vhost/user/net.rs
+++ b/devices/src/virtio/vhost/user/net.rs
@@ -46,6 +46,7 @@
| 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 =
diff --git a/devices/src/virtio/vhost/user/wl.rs b/devices/src/virtio/vhost/user/wl.rs
index cb6009e..89b5d6a 100644
--- a/devices/src/virtio/vhost/user/wl.rs
+++ b/devices/src/virtio/vhost/user/wl.rs
@@ -32,6 +32,7 @@
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 =
diff --git a/devices/src/virtio/video/command.rs b/devices/src/virtio/video/command.rs
index 3e71850..48aa564 100644
--- a/devices/src/virtio/video/command.rs
+++ b/devices/src/virtio/video/command.rs
@@ -272,9 +272,12 @@
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));
}
@@ -292,6 +295,16 @@
.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
@@ -302,7 +315,12 @@
.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));
}
diff --git a/devices/src/virtio/video/control.rs b/devices/src/virtio/video/control.rs
index e997a11..4fcaf8d 100644
--- a/devices/src/virtio/video/control.rs
+++ b/devices/src/virtio/video/control.rs
@@ -9,7 +9,7 @@
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 @@
Profile,
Level,
ForceKeyframe,
+ BitrateMode,
+ BitratePeak,
+ PrependSpsPpsToIdr,
}
#[derive(Debug, Clone)]
@@ -61,7 +64,10 @@
Bitrate(u32),
Profile(Profile),
Level(Level),
- ForceKeyframe(),
+ ForceKeyframe,
+ BitrateMode(BitrateMode),
+ BitratePeak(u32),
+ PrependSpsPpsToIdr(bool),
}
impl Response for CtrlVal {
@@ -71,6 +77,14 @@
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 @@
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/mod.rs b/devices/src/virtio/video/decoder/mod.rs
index fa744fb..cefb683 100644
--- a/devices/src/virtio/video/decoder/mod.rs
+++ b/devices/src/virtio/video/decoder/mod.rs
@@ -159,10 +159,13 @@
}
}
-struct PictureReadyEvent {
- picture_buffer_id: i32,
- bitstream_id: i32,
- visible_rect: Rect,
+enum PendingResponse {
+ PictureReady {
+ picture_buffer_id: i32,
+ bitstream_id: i32,
+ visible_rect: Rect,
+ },
+ FlushCompleted,
}
// Context is associated with one `DecoderSession`, which corresponds to one stream from the
@@ -179,7 +182,7 @@
// 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>,
}
@@ -199,57 +202,85 @@
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 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(
@@ -805,7 +836,7 @@
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,
@@ -876,7 +907,7 @@
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
@@ -971,12 +1002,13 @@
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,
+ visible_rect,
+ });
+ ctx.output_pending_responses()
}
}
DecoderEvent::NotifyEndOfBitstreamBuffer(bitstream_id) => {
@@ -998,40 +1030,9 @@
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`,
diff --git a/devices/src/virtio/video/encoder/backend/mod.rs b/devices/src/virtio/video/encoder/backend/mod.rs
index d6f25f4..868d460 100644
--- a/devices/src/virtio/video/encoder/backend/mod.rs
+++ b/devices/src/virtio/video/encoder/backend/mod.rs
@@ -9,7 +9,7 @@
use base::AsRawDescriptor;
use crate::virtio::video::error::VideoResult;
-use crate::virtio::video::format::FramePlane;
+use crate::virtio::video::format::{Bitrate, FramePlane};
use super::encoder::{
EncoderCapabilities, EncoderEvent, InputBufferId, OutputBufferId, SessionConfig,
@@ -48,7 +48,11 @@
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: u32, framerate: u32) -> VideoResult<()>;
+ 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.
diff --git a/devices/src/virtio/video/encoder/backend/vda.rs b/devices/src/virtio/video/encoder/backend/vda.rs
index 0ee0ea6..1ec02e6 100644
--- a/devices/src/virtio/video/encoder/backend/vda.rs
+++ b/devices/src/virtio/video/encoder/backend/vda.rs
@@ -12,7 +12,7 @@
use super::*;
use crate::virtio::video::format::{
- Format, FormatDesc, FormatRange, FrameFormat, FramePlane, Level, Profile,
+ Bitrate, Format, FormatDesc, FormatRange, FrameFormat, FramePlane, Level, Profile,
};
use crate::virtio::video::{
encoder::{encoder::*, EncoderDevice},
@@ -20,6 +20,23 @@
Tube,
};
+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,
+ },
+ }
+ }
+}
+
pub struct LibvdaEncoder {
instance: VeaInstance,
capabilities: EncoderCapabilities,
@@ -213,7 +230,7 @@
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 {
@@ -317,9 +334,13 @@
self.session.flush().map_err(VideoError::from)
}
- fn request_encoding_params_change(&mut self, bitrate: u32, framerate: u32) -> VideoResult<()> {
+ fn request_encoding_params_change(
+ &mut self,
+ bitrate: Bitrate,
+ framerate: u32,
+ ) -> VideoResult<()> {
self.session
- .request_encoding_params_change(bitrate, framerate)
+ .request_encoding_params_change(bitrate.into(), framerate)
.map_err(VideoError::from)
}
diff --git a/devices/src/virtio/video/encoder/encoder.rs b/devices/src/virtio/video/encoder/encoder.rs
index d9836f4..fa4544e 100644
--- a/devices/src/virtio/video/encoder/encoder.rs
+++ b/devices/src/virtio/video/encoder/encoder.rs
@@ -9,7 +9,7 @@
use crate::virtio::video::params::Params;
use crate::virtio::video::{
error::{VideoError, VideoResult},
- format::{find_closest_resolution, Format, FormatDesc, Level, PlaneFormat, Profile},
+ format::{find_closest_resolution, Bitrate, Format, FormatDesc, Level, PlaneFormat, Profile},
};
pub type InputBufferId = u32;
@@ -45,7 +45,7 @@
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,
}
diff --git a/devices/src/virtio/video/encoder/mod.rs b/devices/src/virtio/video/encoder/mod.rs
index 7b75c69..211b767 100644
--- a/devices/src/virtio/video/encoder/mod.rs
+++ b/devices/src/virtio/video/encoder/mod.rs
@@ -24,7 +24,9 @@
};
use crate::virtio::video::error::*;
use crate::virtio::video::event::{EvtType, VideoEvt};
-use crate::virtio::video::format::{Format, FramePlane, Level, PlaneFormat, Profile};
+use crate::virtio::video::format::{
+ Bitrate, BitrateMode, Format, FramePlane, Level, PlaneFormat, Profile,
+};
use crate::virtio::video::params::Params;
use crate::virtio::video::protocol;
use crate::virtio::video::response::CmdResponse;
@@ -70,7 +72,7 @@
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,
@@ -99,7 +101,12 @@
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;
@@ -917,93 +924,132 @@
.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.frame_rate != frame_rate {
+ stream.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.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);
+ }
- 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,
- )?;
+ // There should be at least a single plane.
+ if plane_formats.is_empty() {
+ return Err(VideoError::InvalidArgument);
+ }
- // 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;
+ 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,
+ )?;
+
+ 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(
- &mut stream.dst_params,
- desired_format,
- plane_formats[0].plane_size,
- )?;
+ // There should be exactly one output buffer.
+ if plane_formats.len() != 1 {
+ return Err(VideoError::InvalidArgument);
+ }
- if frame_rate > 0 {
- stream.frame_rate = frame_rate;
- }
+ self.cros_capabilities.populate_dst_params(
+ &mut stream.dst_params,
+ desired_format,
+ plane_formats[0].plane_size,
+ )?;
- // Format is always populated for encoder.
- let new_format = stream
- .dst_params
- .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 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))
}
@@ -1057,7 +1103,13 @@
.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
@@ -1076,6 +1128,9 @@
}
// 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,
@@ -1092,19 +1147,61 @@
.get_mut(&stream_id)
.ok_or(VideoError::InvalidStreamId(stream_id))?;
match ctrl_val {
+ CtrlVal::BitrateMode(bitrate_mode) => {
+ if stream.encoder_session.is_some() {
+ error!(
+ "set control called for bitrate mode but encoder session already exists."
+ );
+ return Err(VideoError::InvalidOperation);
+ }
+
+ // We only need to care if there is a change.
+ if stream.dst_bitrate.mode() != bitrate_mode {
+ 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(),
+ },
+ };
+ }
+ }
CtrlVal::Bitrate(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(bitrate, stream.frame_rate)
+ if let Err(e) = encoder_session
+ .request_encoding_params_change(new_bitrate, stream.frame_rate)
{
- error!(
- "failed to dynamically request encoding params change: {}",
- e
- );
+ error!("failed to dynamically request target bitrate change: {}", e);
return Err(VideoError::InvalidOperation);
}
}
- stream.dst_bitrate = bitrate;
+ stream.dst_bitrate = new_bitrate;
+ }
+ CtrlVal::BitratePeak(bitrate) => {
+ let mut new_bitrate = stream.dst_bitrate;
+ match &mut new_bitrate {
+ Bitrate::CBR { .. } => {
+ // Trying to set the peak bitrate while in constant mode. This is not an
+ // error, just ignored.
+ return Ok(VideoCmdResponseType::Sync(CmdResponse::SetControl));
+ }
+ Bitrate::VBR { peak, .. } => *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.frame_rate)
+ {
+ error!("failed to dynamically request peak bitrate change: {}", e);
+ return Err(VideoError::InvalidOperation);
+ }
+ }
+ stream.dst_bitrate = new_bitrate;
}
CtrlVal::Profile(profile) => {
if stream.encoder_session.is_some() {
@@ -1148,9 +1245,17 @@
}
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);
+ }
+ }
}
Ok(VideoCmdResponseType::Sync(CmdResponse::SetControl))
}
diff --git a/devices/src/virtio/video/format.rs b/devices/src/virtio/video/format.rs
index 321902b..1e99cb4 100644
--- a/devices/src/virtio/video/format.rs
+++ b/devices/src/virtio/video/format.rs
@@ -117,6 +117,38 @@
}
}
+#[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");
+
+#[derive(Debug, Copy, Clone)]
+pub enum Bitrate {
+ /// Constant bitrate.
+ CBR { target: u32 },
+ /// Variable bitrate.
+ VBR { target: u32, peak: u32 },
+}
+
+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,
@@ -126,7 +158,7 @@
}
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,
diff --git a/devices/src/virtio/video/protocol.rs b/devices/src/virtio/video/protocol.rs
index ec03f8c..843d112 100644
--- a/devices/src/virtio/video/protocol.rs
+++ b/devices/src/virtio/video/protocol.rs
@@ -93,6 +93,10 @@
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 {
@@ -363,6 +367,9 @@
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 +444,24 @@
#[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 +480,15 @@
#[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/worker.rs b/devices/src/virtio/video/worker.rs
index 38f9355..98087ac 100644
--- a/devices/src/virtio/video/worker.rs
+++ b/devices/src/virtio/video/worker.rs
@@ -75,7 +75,7 @@
self.cmd_queue
.add_used(&self.mem, desc_index, writer.bytes_written() as u32);
}
- self.interrupt.signal_used_queue(self.cmd_queue.vector);
+ self.cmd_queue.trigger_interrupt(&self.mem, &self.interrupt);
Ok(())
}
@@ -94,7 +94,8 @@
.map_err(|error| Error::WriteEventFailure { event, error })?;
self.event_queue
.add_used(&self.mem, desc_index, writer.bytes_written() as u32);
- self.interrupt.signal_used_queue(self.event_queue.vector);
+ self.event_queue
+ .trigger_interrupt(&self.mem, &self.interrupt);
Ok(())
}
diff --git a/devices/src/virtio/virtio_device.rs b/devices/src/virtio/virtio_device.rs
index c181b44..be4264b 100644
--- a/devices/src/virtio/virtio_device.rs
+++ b/devices/src/virtio/virtio_device.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.
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use acpi_tables::sdt::SDT;
use base::{Event, RawDescriptor};
use vm_memory::GuestMemory;
@@ -87,4 +89,13 @@
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)
+ }
}
diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs
index 509dffb..1eaa3c1 100644
--- a/devices/src/virtio/virtio_pci_device.rs
+++ b/devices/src/virtio/virtio_pci_device.rs
@@ -6,6 +6,8 @@
use std::sync::Arc;
use sync::Mutex;
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use acpi_tables::sdt::SDT;
use base::{warn, AsRawDescriptor, Event, RawDescriptor, Result, Tube};
use data_model::{DataInit, Le32};
use hypervisor::Datamatch;
@@ -57,6 +59,10 @@
fn id(&self) -> PciCapabilityID {
PciCapabilityID::VendorSpecific
}
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32; 4]
+ }
}
impl VirtioPciCap {
@@ -93,6 +99,10 @@
fn id(&self) -> PciCapabilityID {
PciCapabilityID::VendorSpecific
}
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32; 5]
+ }
}
impl VirtioPciNotifyCap {
@@ -138,6 +148,10 @@
fn id(&self) -> PciCapabilityID {
PciCapabilityID::VendorSpecific
}
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32; 6]
+ }
}
impl VirtioPciShmCap {
@@ -744,4 +758,9 @@
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)
+ }
}
diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs
index d7eee75..6482c2c 100644
--- a/devices/src/virtio/wl.rs
+++ b/devices/src/virtio/wl.rs
@@ -1511,7 +1511,7 @@
}
if needs_interrupt {
- interrupt.signal_used_queue(in_queue.vector);
+ in_queue.trigger_interrupt(mem, interrupt);
}
if exhausted_queue {
@@ -1560,7 +1560,7 @@
}
if needs_interrupt {
- interrupt.signal_used_queue(out_queue.vector);
+ out_queue.trigger_interrupt(mem, interrupt);
}
}
diff --git a/disk/Android.bp b/disk/Android.bp
index ff801af..d3e3b9d 100644
--- a/disk/Android.bp
+++ b/disk/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 "composite-disk" feature and proto libs.
+// 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
@@ -11,10 +11,9 @@
}
rust_defaults {
- name: "disk_defaults",
+ name: "disk_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "disk",
- // has rustc warnings
srcs: ["src/disk.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
@@ -37,7 +36,7 @@
rust_test_host {
name: "disk_host_test_src_disk",
- defaults: ["disk_defaults"],
+ defaults: ["disk_test_defaults"],
test_options: {
unit_test: true,
},
@@ -45,13 +44,12 @@
rust_test {
name: "disk_device_test_src_disk",
- defaults: ["disk_defaults"],
+ defaults: ["disk_test_defaults"],
}
rust_library {
name: "libdisk",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "disk",
srcs: ["src/disk.rs"],
@@ -61,6 +59,7 @@
],
rustlibs: [
"libbase_rust",
+ "libcrc32fast", // Added manually
"libcros_async",
"libdata_model",
"libfutures",
@@ -69,6 +68,7 @@
"libprotos", // Added manually
"libtempfile",
"libthiserror",
+ "libuuid", // Added manually
"libvm_memory",
],
proc_macros: [
@@ -76,54 +76,3 @@
"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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// getrandom-0.2.3 "std"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.1 "std"
-// rand_core-0.6.3 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/disk/Cargo.toml b/disk/Cargo.toml
index 8c49b45..da386f2 100644
--- a/disk/Cargo.toml
+++ b/disk/Cargo.toml
@@ -8,19 +8,21 @@
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 = "*"
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 }
+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 0000000..a0c7bb4
--- /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 0000000..c8f9ef7
--- /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: "2018",
++ 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/composite.rs b/disk/src/composite.rs
index efa5e1d..32d6edf 100644
--- a/disk/src/composite.rs
+++ b/disk/src/composite.rs
@@ -3,31 +3,65 @@
// found in the LICENSE file.
use std::cmp::{max, min};
+use std::collections::HashSet;
+use std::convert::TryInto;
use std::fmt::{self, Display};
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 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)]
pub enum Error {
DiskError(Box<crate::Error>),
+ DuplicatePartitionLabel(String),
+ GptError(gpt::Error),
InvalidMagicHeader,
+ InvalidPath(PathBuf),
InvalidProto(protobuf::ProtobufError),
InvalidSpecification(String),
+ NoImageFiles(PartitionInfo),
OpenFile(io::Error, String),
ReadSpecificationError(io::Error),
+ UnalignedReadWrite(PartitionInfo),
UnknownVersion(u64),
UnsupportedComponent(ImageType),
+ WriteHeader(io::Error),
+ WriteProto(protobuf::ProtobufError),
+ WriteZeroFiller(io::Error),
}
impl Display for Error {
@@ -38,17 +72,40 @@
#[sorted]
match self {
DiskError(e) => write!(f, "failed to use underlying disk: \"{}\"", e),
+ DuplicatePartitionLabel(label) => {
+ write!(f, "duplicate GPT partition label \"{}\"", label)
+ }
+ GptError(e) => write!(f, "failed to write GPT header: \"{}\"", e),
InvalidMagicHeader => write!(f, "invalid magic header for composite disk format"),
+ InvalidPath(path) => write!(f, "invalid partition path {:?}", path),
InvalidProto(e) => write!(f, "failed to parse specification proto: \"{}\"", e),
InvalidSpecification(s) => write!(f, "invalid specification: \"{}\"", s),
+ NoImageFiles(partition) => write!(f, "no image files for partition {:?}", partition),
OpenFile(e, p) => write!(f, "failed to open component file \"{}\": \"{}\"", p, e),
ReadSpecificationError(e) => write!(f, "failed to read specification: \"{}\"", e),
+ UnalignedReadWrite(partition) => write!(
+ f,
+ "Read-write partition {:?} size is not a multiple of {}.",
+ partition,
+ 1 << PARTITION_SIZE_SHIFT
+ ),
UnknownVersion(v) => write!(f, "unknown version {} in specification", v),
UnsupportedComponent(c) => write!(f, "unsupported component disk type \"{:?}\"", c),
+ WriteHeader(e) => write!(f, "failed to write composite disk header: \"{}\"", e),
+ WriteProto(e) => write!(f, "failed to write specification proto: \"{}\"", e),
+ WriteZeroFiller(e) => write!(f, "failed to write zero filler: \"{}\"", e),
}
}
}
+impl std::error::Error for Error {}
+
+impl From<gpt::Error> for Error {
+ fn from(e: gpt::Error) -> Self {
+ Self::GptError(e)
+ }
+}
+
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
@@ -86,11 +143,14 @@
}
}
+/// The version of the composite disk format supported by this implementation.
+const COMPOSITE_DISK_VERSION: u64 = 1;
+
/// 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> {
@@ -127,22 +187,21 @@
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 file = open_file(
+ Path::new(disk.get_file_path()),
+ disk.get_read_write_capability() != cdisk_spec::ReadWriteCapability::READ_WRITE,
+ // TODO(b/190435784): add support for O_DIRECT.
+ false, /*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)))?,
offset: disk.get_offset(),
@@ -339,9 +398,284 @@
}
}
+/// 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;
@@ -561,4 +895,146 @@
}
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 792075f..fd07334 100644
--- a/disk/src/disk.rs
+++ b/disk/src/disk.rs
@@ -26,6 +26,15 @@
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};
@@ -262,7 +271,13 @@
// 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(512))]
+ 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 {
@@ -271,19 +286,19 @@
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() {
@@ -420,7 +435,7 @@
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)
}
@@ -432,7 +447,7 @@
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)
}
@@ -471,7 +486,7 @@
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)?;
diff --git a/disk/src/gpt.rs b/disk/src/gpt.rs
new file mode 100644
index 0000000..39e6d59
--- /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 c7c56cc..82c6590 100644
--- a/disk/src/qcow/mod.rs
+++ b/disk/src/qcow/mod.rs
@@ -7,7 +7,7 @@
mod vec_cache;
use base::{
- error, AsRawDescriptor, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile,
+ error, open_file, AsRawDescriptor, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile,
FileReadWriteVolatile, FileSetLen, FileSync, PunchHole, RawDescriptor, SeekHole, WriteZeroesAt,
};
use data_model::{VolatileMemory, VolatileSlice};
@@ -16,9 +16,10 @@
use std::cmp::{max, min};
use std::fmt::{self, Display};
-use std::fs::{File, OpenOptions};
+use std::fs::File;
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;
@@ -445,10 +446,13 @@
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_raw_file = open_file(
+ Path::new(&path),
+ true, /*read_only*/
+ // TODO(b/190435784): Add support for O_DIRECT.
+ false, /*O_DIRECT*/
+ )
+ .map_err(|e| Error::BackingFileIo(e.into()))?;
let backing_file = create_disk_file(backing_raw_file)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
Some(backing_file)
@@ -582,10 +586,13 @@
/// 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_raw_file = open_file(
+ Path::new(backing_file_name),
+ true, /*read_only*/
+ // TODO(b/190435784): add support for O_DIRECT.
+ false, /*O_DIRECT*/
+ )
+ .map_err(|e| Error::BackingFileIo(e.into()))?;
let backing_file =
create_disk_file(backing_raw_file).map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
let size = backing_file.get_len().map_err(Error::BackingFileIo)?;
diff --git a/enumn/Android.bp b/enumn/Android.bp
index 7a7a112..4d335e8 100644
--- a/enumn/Android.bp
+++ b/enumn/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually disabled tests which depend on host only crate.
+// 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,34 +10,6 @@
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "enumn_defaults",
- 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",
- ],
-}
-
-//rust_test_host {
-// name: "enumn_host_test_src_lib",
-// defaults: ["enumn_defaults"],
-// test_options: {
-// unit_test: true,
-// },
-//}
-
-//rust_test {
-// name: "enumn_device_test_src_lib",
-// defaults: ["enumn_defaults"],
-//}
-
rust_proc_macro {
name: "libenumn",
defaults: ["crosvm_defaults"],
@@ -50,9 +22,3 @@
"libsyn",
],
}
-
-// dependent_library ["feature_list"]
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.2 "default"
diff --git a/enumn/cargo2android.json b/enumn/cargo2android.json
new file mode 100644
index 0000000..9a6eee3
--- /dev/null
+++ b/enumn/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/fuse/Android.bp b/fuse/Android.bp
index b364c07..392342f 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 {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "fuse_defaults",
+ name: "fuse_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "fuse",
srcs: ["src/lib.rs"],
@@ -30,7 +30,7 @@
rust_test_host {
name: "fuse_host_test_src_lib",
- defaults: ["fuse_defaults"],
+ defaults: ["fuse_test_defaults"],
test_options: {
unit_test: true,
},
@@ -38,7 +38,7 @@
rust_test {
name: "fuse_device_test_src_lib",
- defaults: ["fuse_defaults"],
+ defaults: ["fuse_test_defaults"],
}
rust_library {
@@ -58,45 +58,3 @@
],
proc_macros: ["libenumn"],
}
-
-// 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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/fuse/src/server.rs b/fuse/src/server.rs
index f28e948..7538c4d 100644
--- a/fuse/src/server.rs
+++ b/fuse/src/server.rs
@@ -24,11 +24,17 @@
/// 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 +47,27 @@
/// 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
diff --git a/fuse/src/worker.rs b/fuse/src/worker.rs
index 378e5ee..27dcbee 100644
--- a/fuse/src/worker.rs
+++ b/fuse/src/worker.rs
@@ -13,27 +13,31 @@
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 +47,17 @@
}
}
-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 +67,7 @@
}
}
-impl Write for DevFuseWriter<'_> {
+impl Write for DevFuseWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.write_buf.write(buf)
}
@@ -76,7 +80,9 @@
}
}
-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 +100,7 @@
}
}
-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;
@@ -145,20 +151,25 @@
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)?,
- );
-
- let mut write_buf = Cursor::new(Vec::with_capacity(max_read as usize));
- let mut wfile = dev_fuse.try_clone().map_err(Error::EndpointSetup)?;
+ let mut dev_fuse_reader = {
+ let rfile = dev_fuse.try_clone().map_err(Error::EndpointSetup)?;
+ let buf_reader = BufReader::with_capacity(
+ max_write as usize + size_of::<sys::InHeader>() + size_of::<sys::WriteIn>(),
+ rfile,
+ );
+ DevFuseReader::new(buf_reader)
+ };
+ let mut dev_fuse_writer = {
+ let wfile = dev_fuse.try_clone().map_err(Error::EndpointSetup)?;
+ let write_buf = Cursor::new(Vec::with_capacity(max_read 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();
}
}
diff --git a/gpu_display/Android.bp b/gpu_display/Android.bp
index 9704970..a12dd9e 100644
--- a/gpu_display/Android.bp
+++ b/gpu_display/Android.bp
@@ -142,46 +142,3 @@
generated_headers: ["gpu_display_server_protocol_headers"],
export_generated_headers: ["gpu_display_server_protocol_headers"],
}
-
-// 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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.95 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.72 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/gpu_display/cargo2android.json b/gpu_display/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/gpu_display/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/gpu_display/src/display_wl.c b/gpu_display/src/display_wl.c
index c1b396a..b243120 100644
--- a/gpu_display/src/display_wl.c
+++ b/gpu_display/src/display_wl.c
@@ -28,7 +28,11 @@
#include "aura-shell.h"
#include "linux-dmabuf-unstable-v1.h"
#include "viewporter.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>
diff --git a/hypervisor/Android.bp b/hypervisor/Android.bp
index 8b8bc33..9563000 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 --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "hypervisor_defaults",
+ name: "hypervisor_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "hypervisor",
// has rustc warnings
@@ -36,7 +36,7 @@
rust_test_host {
name: "hypervisor_host_test_src_lib",
- defaults: ["hypervisor_defaults"],
+ defaults: ["hypervisor_test_defaults"],
test_options: {
unit_test: true,
},
@@ -44,7 +44,7 @@
rust_test {
name: "hypervisor_device_test_src_lib",
- defaults: ["hypervisor_defaults"],
+ defaults: ["hypervisor_test_defaults"],
}
rust_library {
@@ -69,51 +69,3 @@
],
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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/hypervisor/src/kvm/x86_64.rs b/hypervisor/src/kvm/x86_64.rs
index 4a29c1b..aeedf57 100644
--- a/hypervisor/src/kvm/x86_64.rs
+++ b/hypervisor/src/kvm/x86_64.rs
@@ -3,7 +3,6 @@
// found in the LICENSE file.
use base::IoctlNr;
-use std::convert::TryInto;
use libc::E2BIG;
@@ -824,14 +823,13 @@
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
}
@@ -844,12 +842,14 @@
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
}
@@ -1356,10 +1356,7 @@
// 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
diff --git a/integration_tests/tests/boot.rs b/integration_tests/tests/boot.rs
index 877610c..4305990 100644
--- a/integration_tests/tests/boot.rs
+++ b/integration_tests/tests/boot.rs
@@ -6,7 +6,13 @@
#[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_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 42a1604..398417c 100644
--- a/integration_tests/tests/fixture.rs
+++ b/integration_tests/tests/fixture.rs
@@ -3,6 +3,7 @@
// found in the LICENSE file.
use std::ffi::CString;
+use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
@@ -11,7 +12,6 @@
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 +173,9 @@
/// 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.
@@ -228,16 +230,21 @@
}
/// 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());
@@ -256,13 +263,10 @@
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 9f21a6f..eae1ce5 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 --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "io_uring_defaults",
+ name: "io_uring_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "io_uring",
srcs: ["src/lib.rs"],
@@ -30,7 +30,7 @@
rust_test_host {
name: "io_uring_host_test_src_lib",
- defaults: ["io_uring_defaults"],
+ defaults: ["io_uring_test_defaults"],
test_options: {
unit_test: true,
},
@@ -38,7 +38,7 @@
rust_test {
name: "io_uring_device_test_src_lib",
- defaults: ["io_uring_defaults"],
+ defaults: ["io_uring_test_defaults"],
}
rust_library {
@@ -56,23 +56,3 @@
"libthiserror",
],
}
-
-// 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.97 "default,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/io_uring/src/uring.rs b/io_uring/src/uring.rs
index 724b79e..3a516a5 100644
--- a/io_uring/src/uring.rs
+++ b/io_uring/src/uring.rs
@@ -310,6 +310,7 @@
.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>(
@@ -368,6 +369,7 @@
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>(
diff --git a/kernel_cmdline/Android.bp b/kernel_cmdline/Android.bp
index 538b7a1..5250c8f 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 {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "kernel_cmdline_defaults",
+ name: "kernel_cmdline_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "kernel_cmdline",
srcs: ["src/kernel_cmdline.rs"],
@@ -25,7 +25,7 @@
rust_test_host {
name: "kernel_cmdline_host_test_src_kernel_cmdline",
- defaults: ["kernel_cmdline_defaults"],
+ defaults: ["kernel_cmdline_test_defaults"],
test_options: {
unit_test: true,
},
@@ -33,7 +33,7 @@
rust_test {
name: "kernel_cmdline_device_test_src_kernel_cmdline",
- defaults: ["kernel_cmdline_defaults"],
+ defaults: ["kernel_cmdline_test_defaults"],
}
rust_library {
@@ -47,6 +47,3 @@
"liblibc",
],
}
-
-// dependent_library ["feature_list"]
-// libc-0.2.97 "default,std"
diff --git a/kernel_loader/Android.bp b/kernel_loader/Android.bp
index 630617d..606674d 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 {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "kernel_loader_defaults",
+ name: "kernel_loader_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "kernel_loader",
srcs: ["src/lib.rs"],
@@ -28,7 +28,7 @@
rust_test_host {
name: "kernel_loader_host_test_src_lib",
- defaults: ["kernel_loader_defaults"],
+ defaults: ["kernel_loader_test_defaults"],
test_options: {
unit_test: true,
},
@@ -36,7 +36,7 @@
rust_test {
name: "kernel_loader_device_test_src_lib",
- defaults: ["kernel_loader_defaults"],
+ defaults: ["kernel_loader_test_defaults"],
}
rust_library {
@@ -53,53 +53,3 @@
"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
-// ../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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// getrandom-0.2.3 "std"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.1 "std"
-// rand_core-0.6.3 "alloc,getrandom,std"
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/kvm/Android.bp b/kvm/Android.bp
index d87344f..d0c81cc 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 --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "kvm_defaults",
+ name: "kvm_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "kvm",
srcs: ["src/lib.rs"],
@@ -30,7 +30,7 @@
rust_test_host {
name: "kvm_host_test_src_lib",
- defaults: ["kvm_defaults"],
+ defaults: ["kvm_test_defaults"],
test_options: {
unit_test: true,
},
@@ -38,11 +38,11 @@
rust_test {
name: "kvm_device_test_src_lib",
- defaults: ["kvm_defaults"],
+ defaults: ["kvm_test_defaults"],
}
rust_defaults {
- name: "kvm_defaults_kvm",
+ name: "kvm_test_defaults_kvm",
defaults: ["crosvm_defaults"],
crate_name: "kvm",
test_suites: ["general-tests"],
@@ -61,7 +61,7 @@
rust_test_host {
name: "kvm_host_test_tests_dirty_log",
- defaults: ["kvm_defaults_kvm"],
+ defaults: ["kvm_test_defaults_kvm"],
srcs: ["tests/dirty_log.rs"],
test_options: {
unit_test: true,
@@ -70,13 +70,13 @@
rust_test {
name: "kvm_device_test_tests_dirty_log",
- defaults: ["kvm_defaults_kvm"],
+ defaults: ["kvm_test_defaults_kvm"],
srcs: ["tests/dirty_log.rs"],
}
rust_test_host {
name: "kvm_host_test_tests_read_only_memory",
- defaults: ["kvm_defaults_kvm"],
+ defaults: ["kvm_test_defaults_kvm"],
srcs: ["tests/read_only_memory.rs"],
test_options: {
unit_test: true,
@@ -85,13 +85,13 @@
rust_test {
name: "kvm_device_test_tests_read_only_memory",
- defaults: ["kvm_defaults_kvm"],
+ defaults: ["kvm_test_defaults_kvm"],
srcs: ["tests/read_only_memory.rs"],
}
rust_test_host {
name: "kvm_host_test_tests_real_run_adder",
- defaults: ["kvm_defaults_kvm"],
+ defaults: ["kvm_test_defaults_kvm"],
srcs: ["tests/real_run_adder.rs"],
test_options: {
unit_test: true,
@@ -100,7 +100,7 @@
rust_test {
name: "kvm_device_test_tests_real_run_adder",
- defaults: ["kvm_defaults_kvm"],
+ defaults: ["kvm_test_defaults_kvm"],
srcs: ["tests/real_run_adder.rs"],
}
@@ -120,46 +120,3 @@
"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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/kvm_sys/Android.bp b/kvm_sys/Android.bp
index 95ebf8d..4f26e2b 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 --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -11,9 +11,10 @@
}
rust_defaults {
- name: "kvm_sys_defaults",
+ name: "kvm_sys_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "kvm_sys",
+ // has rustc warnings
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
@@ -27,7 +28,7 @@
rust_test_host {
name: "kvm_sys_host_test_src_lib",
- defaults: ["kvm_sys_defaults"],
+ defaults: ["kvm_sys_test_defaults"],
test_options: {
unit_test: true,
},
@@ -35,13 +36,14 @@
rust_test {
name: "kvm_sys_device_test_src_lib",
- defaults: ["kvm_sys_defaults"],
+ defaults: ["kvm_sys_test_defaults"],
}
rust_defaults {
- name: "kvm_sys_defaults_basic",
+ name: "kvm_sys_test_defaults_basic",
defaults: ["crosvm_defaults"],
crate_name: "basic",
+ // has rustc warnings
srcs: ["tests/basic.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
@@ -56,7 +58,7 @@
rust_test_host {
name: "kvm_sys_host_test_tests_basic",
- defaults: ["kvm_sys_defaults_basic"],
+ defaults: ["kvm_sys_test_defaults_basic"],
test_options: {
unit_test: true,
},
@@ -64,12 +66,13 @@
rust_test {
name: "kvm_sys_device_test_tests_basic",
- defaults: ["kvm_sys_defaults_basic"],
+ defaults: ["kvm_sys_test_defaults_basic"],
}
rust_library {
name: "libkvm_sys",
defaults: ["crosvm_defaults"],
+ // has rustc warnings
host_supported: true,
crate_name: "kvm_sys",
srcs: ["src/lib.rs"],
@@ -80,43 +83,3 @@
"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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/rand_ish/Android.bp b/libcras_stub/Android.bp
similarity index 64%
rename from rand_ish/Android.bp
rename to libcras_stub/Android.bp
index aa1fd97..17f9af6 100644
--- a/rand_ish/Android.bp
+++ b/libcras_stub/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,34 @@
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_library {
- name: "librand_ish",
- defaults: ["crosvm_defaults"],
- host_supported: true,
- crate_name: "rand_ish",
- srcs: ["src/lib.rs"],
- edition: "2018",
-}
-
rust_defaults {
- name: "rand_ish_defaults",
+ name: "libcras_stub_test_defaults",
defaults: ["crosvm_defaults"],
- crate_name: "rand_ish",
- srcs: ["src/lib.rs"],
+ crate_name: "libcras",
+ srcs: ["src/libcras.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"],
+ name: "libcras_stub_host_test_src_libcras",
+ defaults: ["libcras_stub_test_defaults"],
test_options: {
unit_test: true,
},
}
rust_test {
- name: "rand_ish_device_test_src_lib",
- defaults: ["rand_ish_defaults"],
+ name: "libcras_stub_device_test_src_libcras",
+ defaults: ["libcras_stub_test_defaults"],
+}
+
+rust_library {
+ name: "liblibcras",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "libcras",
+ srcs: ["src/libcras.rs"],
+ edition: "2018",
}
diff --git a/rand_ish/Cargo.toml b/libcras_stub/Cargo.toml
similarity index 63%
rename from rand_ish/Cargo.toml
rename to libcras_stub/Cargo.toml
index 63bb001..7d40cf2 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"
+
+[lib]
+path = "src/libcras.rs"
diff --git a/libcras_stub/README.md b/libcras_stub/README.md
new file mode 100644
index 0000000..bdd5e74
--- /dev/null
+++ b/libcras_stub/README.md
@@ -0,0 +1,12 @@
+# 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 0000000..8b13789
--- /dev/null
+++ b/libcras_stub/src/libcras.rs
@@ -0,0 +1 @@
+
diff --git a/libcrosvm_control/Android.bp b/libcrosvm_control/Android.bp
index dedb8f9..2307595 100644
--- a/libcrosvm_control/Android.bp
+++ b/libcrosvm_control/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 {
@@ -11,7 +11,7 @@
}
rust_defaults {
- name: "libcrosvm_control_defaults",
+ name: "libcrosvm_control_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "libcrosvm_control",
srcs: ["src/lib.rs"],
@@ -27,7 +27,7 @@
rust_test_host {
name: "libcrosvm_control_host_test_src_lib",
- defaults: ["libcrosvm_control_defaults"],
+ defaults: ["libcrosvm_control_test_defaults"],
test_options: {
unit_test: true,
},
@@ -35,7 +35,7 @@
rust_test {
name: "libcrosvm_control_device_test_src_lib",
- defaults: ["libcrosvm_control_defaults"],
+ defaults: ["libcrosvm_control_test_defaults"],
}
rust_ffi_shared {
@@ -52,55 +52,3 @@
"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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/libvda/Android.bp b/libvda/Android.bp
new file mode 100644
index 0000000..eeacbc9
--- /dev/null
+++ b/libvda/Android.bp
@@ -0,0 +1 @@
+// Manually removed, this doesn't build on Android.
diff --git a/libvda/Cargo.toml b/libvda/Cargo.toml
new file mode 100644
index 0000000..0be4e18
--- /dev/null
+++ b/libvda/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "libvda"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+links = "vda"
+
+[dependencies]
+enumn = { path = "../enumn" }
+libc = "*"
+
+[build-dependencies]
+pkg-config = "*"
diff --git a/libvda/README.md b/libvda/README.md
new file mode 100644
index 0000000..38eaa08
--- /dev/null
+++ b/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/libvda/build.rs b/libvda/build.rs
new file mode 100644
index 0000000..253bba9
--- /dev/null
+++ b/libvda/build.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.
+
+fn main() {
+ match pkg_config::probe_library("libvda") {
+ Ok(_) => (),
+ // Ignore a pkg-config failure to allow cargo-clippy to run even when libvda.pc doesn't
+ // exist.
+ Err(pkg_config::Error::Failure { command, .. })
+ if command == r#""pkg-config" "--libs" "--cflags" "libvda""# => {}
+ Err(e) => panic!("{}", e),
+ };
+
+ println!("cargo:rustc-link-lib=dylib=vda");
+}
diff --git a/libvda/cargo2android.json b/libvda/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/libvda/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/libvda/src/bindings.rs b/libvda/src/bindings.rs
new file mode 100644
index 0000000..bf67a44
--- /dev/null
+++ b/libvda/src/bindings.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.
+
+#![allow(
+ dead_code,
+ non_camel_case_types,
+ non_snake_case,
+ non_upper_case_globals
+)]
+
+/*
+automatically generated by rust-bindgen
+
+generated with the command:
+cd ${CHROMEOS_DIR}/src/platform2 && \
+bindgen arc/vm/libvda/libvda_common.h \
+ -o ../platform/crosvm/libvda/src/common/bindings.rs \
+ --whitelist-type "video_.*"
+*/
+
+pub type __int32_t = ::std::os::raw::c_int;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct video_frame_plane {
+ pub offset: i32,
+ pub stride: i32,
+}
+#[test]
+fn bindgen_test_layout_video_frame_plane() {
+ assert_eq!(
+ ::std::mem::size_of::<video_frame_plane>(),
+ 8usize,
+ concat!("Size of: ", stringify!(video_frame_plane))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<video_frame_plane>(),
+ 4usize,
+ concat!("Alignment of ", stringify!(video_frame_plane))
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<video_frame_plane>())).offset as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(video_frame_plane),
+ "::",
+ stringify!(offset)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<video_frame_plane>())).stride as *const _ as usize },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(video_frame_plane),
+ "::",
+ stringify!(stride)
+ )
+ );
+}
+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 = i32;
+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 = u32;
+pub use self::video_pixel_format as video_pixel_format_t;
diff --git a/libvda/src/decode/bindings.rs b/libvda/src/decode/bindings.rs
new file mode 100644
index 0000000..9562436
--- /dev/null
+++ b/libvda/src/decode/bindings.rs
@@ -0,0 +1,617 @@
+// 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.
+
+#![allow(
+ dead_code,
+ non_camel_case_types,
+ non_snake_case,
+ non_upper_case_globals
+)]
+
+/*
+automatically generated by rust-bindgen
+
+generated with the command:
+cd ${CHROMEOS_DIR}/src/platform2/ && \
+bindgen arc/vm/libvda/libvda_decode.h \
+ -o ../platform/crosvm/libvda/src/decode/bindings.rs \
+ --raw-line 'pub use crate::bindings::*;' \
+ --whitelist-function "initialize" \
+ --whitelist-function "deinitialize" \
+ --whitelist-function "get_vda_capabilities" \
+ --whitelist-function "init_decode_session" \
+ --whitelist-function "close_decode_session" \
+ --whitelist-function "vda_.*" \
+ --whitelist-type "vda_.*" \
+ --blacklist-type "video_.*" \
+ -- \
+ -I .
+*/
+
+pub use crate::bindings::*;
+
+pub type __int32_t = ::std::os::raw::c_int;
+pub type __uint32_t = ::std::os::raw::c_uint;
+pub const vda_impl_type_FAKE: vda_impl_type = 0;
+pub const vda_impl_type_GAVDA: vda_impl_type = 1;
+pub type vda_impl_type = u32;
+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 = u32;
+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 = u32;
+pub use self::vda_event_type as vda_event_type_t;
+#[repr(C)]
+#[derive(Debug, 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,
+}
+#[test]
+fn bindgen_test_layout_provide_picture_buffers_event_data() {
+ assert_eq!(
+ ::std::mem::size_of::<provide_picture_buffers_event_data>(),
+ 28usize,
+ concat!("Size of: ", stringify!(provide_picture_buffers_event_data))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<provide_picture_buffers_event_data>(),
+ 4usize,
+ concat!(
+ "Alignment of ",
+ stringify!(provide_picture_buffers_event_data)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).min_num_buffers
+ as *const _ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(provide_picture_buffers_event_data),
+ "::",
+ stringify!(min_num_buffers)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).width as *const _
+ as usize
+ },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(provide_picture_buffers_event_data),
+ "::",
+ stringify!(width)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).height as *const _
+ as usize
+ },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(provide_picture_buffers_event_data),
+ "::",
+ stringify!(height)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).visible_rect_left
+ as *const _ as usize
+ },
+ 12usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(provide_picture_buffers_event_data),
+ "::",
+ stringify!(visible_rect_left)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).visible_rect_top
+ as *const _ as usize
+ },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(provide_picture_buffers_event_data),
+ "::",
+ stringify!(visible_rect_top)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).visible_rect_right
+ as *const _ as usize
+ },
+ 20usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(provide_picture_buffers_event_data),
+ "::",
+ stringify!(visible_rect_right)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<provide_picture_buffers_event_data>())).visible_rect_bottom
+ as *const _ as usize
+ },
+ 24usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(provide_picture_buffers_event_data),
+ "::",
+ stringify!(visible_rect_bottom)
+ )
+ );
+}
+pub type provide_picture_buffers_event_data_t = provide_picture_buffers_event_data;
+#[repr(C)]
+#[derive(Debug, 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,
+}
+#[test]
+fn bindgen_test_layout_picture_ready_event_data() {
+ assert_eq!(
+ ::std::mem::size_of::<picture_ready_event_data>(),
+ 24usize,
+ concat!("Size of: ", stringify!(picture_ready_event_data))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<picture_ready_event_data>(),
+ 4usize,
+ concat!("Alignment of ", stringify!(picture_ready_event_data))
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<picture_ready_event_data>())).picture_buffer_id as *const _
+ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(picture_ready_event_data),
+ "::",
+ stringify!(picture_buffer_id)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<picture_ready_event_data>())).bitstream_id as *const _ as usize
+ },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(picture_ready_event_data),
+ "::",
+ stringify!(bitstream_id)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<picture_ready_event_data>())).crop_left as *const _ as usize
+ },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(picture_ready_event_data),
+ "::",
+ stringify!(crop_left)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<picture_ready_event_data>())).crop_top as *const _ as usize
+ },
+ 12usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(picture_ready_event_data),
+ "::",
+ stringify!(crop_top)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<picture_ready_event_data>())).crop_right as *const _ as usize
+ },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(picture_ready_event_data),
+ "::",
+ stringify!(crop_right)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<picture_ready_event_data>())).crop_bottom as *const _ as usize
+ },
+ 20usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(picture_ready_event_data),
+ "::",
+ stringify!(crop_bottom)
+ )
+ );
+}
+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,
+ _bindgen_union_align: [u32; 7usize],
+}
+#[test]
+fn bindgen_test_layout_vda_event_data() {
+ assert_eq!(
+ ::std::mem::size_of::<vda_event_data>(),
+ 28usize,
+ concat!("Size of: ", stringify!(vda_event_data))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vda_event_data>(),
+ 4usize,
+ concat!("Alignment of ", stringify!(vda_event_data))
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vda_event_data>())).provide_picture_buffers as *const _ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_event_data),
+ "::",
+ stringify!(provide_picture_buffers)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_event_data>())).picture_ready as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_event_data),
+ "::",
+ stringify!(picture_ready)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_event_data>())).bitstream_id as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_event_data),
+ "::",
+ stringify!(bitstream_id)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_event_data>())).result as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_event_data),
+ "::",
+ stringify!(result)
+ )
+ );
+}
+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,
+}
+#[test]
+fn bindgen_test_layout_vda_input_format() {
+ assert_eq!(
+ ::std::mem::size_of::<vda_input_format>(),
+ 20usize,
+ concat!("Size of: ", stringify!(vda_input_format))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vda_input_format>(),
+ 4usize,
+ concat!("Alignment of ", stringify!(vda_input_format))
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_input_format>())).profile as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_input_format),
+ "::",
+ stringify!(profile)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_input_format>())).min_width as *const _ as usize },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_input_format),
+ "::",
+ stringify!(min_width)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_input_format>())).min_height as *const _ as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_input_format),
+ "::",
+ stringify!(min_height)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_input_format>())).max_width as *const _ as usize },
+ 12usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_input_format),
+ "::",
+ stringify!(max_width)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_input_format>())).max_height as *const _ as usize },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_input_format),
+ "::",
+ stringify!(max_height)
+ )
+ );
+}
+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,
+}
+#[test]
+fn bindgen_test_layout_vda_event() {
+ assert_eq!(
+ ::std::mem::size_of::<vda_event>(),
+ 32usize,
+ concat!("Size of: ", stringify!(vda_event))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vda_event>(),
+ 4usize,
+ concat!("Alignment of ", stringify!(vda_event))
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_event>())).event_type as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_event),
+ "::",
+ stringify!(event_type)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_event>())).event_data as *const _ as usize },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_event),
+ "::",
+ stringify!(event_data)
+ )
+ );
+}
+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,
+}
+#[test]
+fn bindgen_test_layout_vda_capabilities() {
+ assert_eq!(
+ ::std::mem::size_of::<vda_capabilities>(),
+ 32usize,
+ concat!("Size of: ", stringify!(vda_capabilities))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vda_capabilities>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(vda_capabilities))
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vda_capabilities>())).num_input_formats as *const _ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_capabilities),
+ "::",
+ stringify!(num_input_formats)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_capabilities>())).input_formats as *const _ as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_capabilities),
+ "::",
+ stringify!(input_formats)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vda_capabilities>())).num_output_formats as *const _ as usize
+ },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_capabilities),
+ "::",
+ stringify!(num_output_formats)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_capabilities>())).output_formats as *const _ as usize },
+ 24usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_capabilities),
+ "::",
+ stringify!(output_formats)
+ )
+ );
+}
+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,
+}
+#[test]
+fn bindgen_test_layout_vda_session_info() {
+ assert_eq!(
+ ::std::mem::size_of::<vda_session_info>(),
+ 16usize,
+ concat!("Size of: ", stringify!(vda_session_info))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vda_session_info>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(vda_session_info))
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_session_info>())).ctx as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_session_info),
+ "::",
+ stringify!(ctx)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vda_session_info>())).event_pipe_fd as *const _ as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vda_session_info),
+ "::",
+ stringify!(event_pipe_fd)
+ )
+ );
+}
+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/libvda/src/decode/event.rs b/libvda/src/decode/event.rs
new file mode 100644
index 0000000..4e365b7
--- /dev/null
+++ b/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/libvda/src/decode/format.rs b/libvda/src/decode/format.rs
new file mode 100644
index 0000000..0acdc3f
--- /dev/null
+++ b/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/libvda/src/decode/mod.rs b/libvda/src/decode/mod.rs
new file mode 100644
index 0000000..9a02e5a
--- /dev/null
+++ b/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/libvda/src/decode/session.rs b/libvda/src/decode/session.rs
new file mode 100644
index 0000000..dada37b
--- /dev/null
+++ b/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/libvda/src/decode/vda_instance.rs b/libvda/src/decode/vda_instance.rs
new file mode 100644
index 0000000..23765f2
--- /dev/null
+++ b/libvda/src/decode/vda_instance.rs
@@ -0,0 +1,111 @@
+// 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
+}
+
+/// 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,
+ };
+
+ // 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/libvda/src/encode/bindings.rs b/libvda/src/encode/bindings.rs
new file mode 100644
index 0000000..8f1198f
--- /dev/null
+++ b/libvda/src/encode/bindings.rs
@@ -0,0 +1,741 @@
+// 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.
+
+/*
+automatically generated by rust-bindgen
+
+generated with the command:
+cd ${CHROMEOS_DIR}/src/platform2/ && \
+bindgen arc/vm/libvda/libvda_encode.h \
+ -o ../platform/crosvm/libvda/src/encode/bindings.rs \
+ --raw-line 'pub use crate::bindings::*;' \
+ --whitelist-function "initialize_encode" \
+ --whitelist-function "deinitialize_encode" \
+ --whitelist-function "get_vea_capabilities" \
+ --whitelist-function "init_encode_session" \
+ --whitelist-function "close_encode_session" \
+ --whitelist-function "vea_.*" \
+ --whitelist-type "vea_.*" \
+ --blacklist-type "video_.*" \
+ -- \
+ -I .
+ */
+
+#![allow(non_camel_case_types)]
+#![allow(non_upper_case_globals)]
+#![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 = u32;
+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,
+}
+#[test]
+fn bindgen_test_layout_vea_profile() {
+ assert_eq!(
+ ::std::mem::size_of::<vea_profile>(),
+ 20usize,
+ concat!("Size of: ", stringify!(vea_profile))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vea_profile>(),
+ 4usize,
+ concat!("Alignment of ", stringify!(vea_profile))
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_profile>())).profile as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_profile),
+ "::",
+ stringify!(profile)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_profile>())).max_width as *const _ as usize },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_profile),
+ "::",
+ stringify!(max_width)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_profile>())).max_height as *const _ as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_profile),
+ "::",
+ stringify!(max_height)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_profile>())).max_framerate_numerator as *const _ as usize
+ },
+ 12usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_profile),
+ "::",
+ stringify!(max_framerate_numerator)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_profile>())).max_framerate_denominator as *const _ as usize
+ },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_profile),
+ "::",
+ stringify!(max_framerate_denominator)
+ )
+ );
+}
+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,
+}
+#[test]
+fn bindgen_test_layout_vea_capabilities() {
+ assert_eq!(
+ ::std::mem::size_of::<vea_capabilities>(),
+ 32usize,
+ concat!("Size of: ", stringify!(vea_capabilities))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vea_capabilities>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(vea_capabilities))
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_capabilities>())).num_input_formats as *const _ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_capabilities),
+ "::",
+ stringify!(num_input_formats)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_capabilities>())).input_formats as *const _ as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_capabilities),
+ "::",
+ stringify!(input_formats)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_capabilities>())).num_output_formats as *const _ as usize
+ },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_capabilities),
+ "::",
+ stringify!(num_output_formats)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_capabilities>())).output_formats as *const _ as usize },
+ 24usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_capabilities),
+ "::",
+ stringify!(output_formats)
+ )
+ );
+}
+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 = u32;
+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,
+}
+#[test]
+fn bindgen_test_layout_vea_bitrate() {
+ assert_eq!(
+ ::std::mem::size_of::<vea_bitrate>(),
+ 12usize,
+ concat!("Size of: ", stringify!(vea_bitrate))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vea_bitrate>(),
+ 4usize,
+ concat!("Alignment of ", stringify!(vea_bitrate))
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_bitrate>())).mode as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_bitrate),
+ "::",
+ stringify!(mode)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_bitrate>())).target as *const _ as usize },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_bitrate),
+ "::",
+ stringify!(target)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_bitrate>())).peak as *const _ as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_bitrate),
+ "::",
+ stringify!(peak)
+ )
+ );
+}
+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,
+}
+#[test]
+fn bindgen_test_layout_vea_config() {
+ assert_eq!(
+ ::std::mem::size_of::<vea_config>(),
+ 36usize,
+ concat!("Size of: ", stringify!(vea_config))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vea_config>(),
+ 4usize,
+ concat!("Alignment of ", stringify!(vea_config))
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_config>())).input_format as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_config),
+ "::",
+ stringify!(input_format)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_config>())).input_visible_width as *const _ as usize },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_config),
+ "::",
+ stringify!(input_visible_width)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_config>())).input_visible_height as *const _ as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_config),
+ "::",
+ stringify!(input_visible_height)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_config>())).output_profile as *const _ as usize },
+ 12usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_config),
+ "::",
+ stringify!(output_profile)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_config>())).bitrate as *const _ as usize },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_config),
+ "::",
+ stringify!(bitrate)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_config>())).initial_framerate as *const _ as usize },
+ 28usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_config),
+ "::",
+ stringify!(initial_framerate)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_config>())).has_initial_framerate as *const _ as usize
+ },
+ 32usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_config),
+ "::",
+ stringify!(has_initial_framerate)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_config>())).h264_output_level as *const _ as usize },
+ 33usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_config),
+ "::",
+ stringify!(h264_output_level)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_config>())).has_h264_output_level as *const _ as usize
+ },
+ 34usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_config),
+ "::",
+ stringify!(has_h264_output_level)
+ )
+ );
+}
+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 = u32;
+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,
+}
+#[test]
+fn bindgen_test_layout_vea_session_info() {
+ assert_eq!(
+ ::std::mem::size_of::<vea_session_info>(),
+ 16usize,
+ concat!("Size of: ", stringify!(vea_session_info))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vea_session_info>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(vea_session_info))
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_session_info>())).ctx as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_session_info),
+ "::",
+ stringify!(ctx)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_session_info>())).event_pipe_fd as *const _ as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_session_info),
+ "::",
+ stringify!(event_pipe_fd)
+ )
+ );
+}
+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 = u32;
+pub use self::vea_event_type as vea_event_type_t;
+#[repr(C)]
+#[derive(Debug, 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,
+}
+#[test]
+fn bindgen_test_layout_vea_require_input_buffers_event_data() {
+ assert_eq!(
+ ::std::mem::size_of::<vea_require_input_buffers_event_data>(),
+ 16usize,
+ concat!(
+ "Size of: ",
+ stringify!(vea_require_input_buffers_event_data)
+ )
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vea_require_input_buffers_event_data>(),
+ 4usize,
+ concat!(
+ "Alignment of ",
+ stringify!(vea_require_input_buffers_event_data)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_require_input_buffers_event_data>())).input_count as *const _
+ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_require_input_buffers_event_data),
+ "::",
+ stringify!(input_count)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_require_input_buffers_event_data>())).input_frame_width
+ as *const _ as usize
+ },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_require_input_buffers_event_data),
+ "::",
+ stringify!(input_frame_width)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_require_input_buffers_event_data>())).input_frame_height
+ as *const _ as usize
+ },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_require_input_buffers_event_data),
+ "::",
+ stringify!(input_frame_height)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_require_input_buffers_event_data>())).output_buffer_size
+ as *const _ as usize
+ },
+ 12usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_require_input_buffers_event_data),
+ "::",
+ stringify!(output_buffer_size)
+ )
+ );
+}
+pub type vea_require_input_buffers_event_data_t = vea_require_input_buffers_event_data;
+#[repr(C)]
+#[derive(Debug, 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,
+}
+#[test]
+fn bindgen_test_layout_vea_processed_output_buffer_event_data() {
+ assert_eq!(
+ ::std::mem::size_of::<vea_processed_output_buffer_event_data>(),
+ 24usize,
+ concat!(
+ "Size of: ",
+ stringify!(vea_processed_output_buffer_event_data)
+ )
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vea_processed_output_buffer_event_data>(),
+ 8usize,
+ concat!(
+ "Alignment of ",
+ stringify!(vea_processed_output_buffer_event_data)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_processed_output_buffer_event_data>())).output_buffer_id
+ as *const _ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_processed_output_buffer_event_data),
+ "::",
+ stringify!(output_buffer_id)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_processed_output_buffer_event_data>())).payload_size
+ as *const _ as usize
+ },
+ 4usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_processed_output_buffer_event_data),
+ "::",
+ stringify!(payload_size)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_processed_output_buffer_event_data>())).key_frame as *const _
+ as usize
+ },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_processed_output_buffer_event_data),
+ "::",
+ stringify!(key_frame)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_processed_output_buffer_event_data>())).timestamp as *const _
+ as usize
+ },
+ 16usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_processed_output_buffer_event_data),
+ "::",
+ stringify!(timestamp)
+ )
+ );
+}
+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,
+ _bindgen_union_align: [u64; 3usize],
+}
+#[test]
+fn bindgen_test_layout_vea_event_data() {
+ assert_eq!(
+ ::std::mem::size_of::<vea_event_data>(),
+ 24usize,
+ concat!("Size of: ", stringify!(vea_event_data))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vea_event_data>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(vea_event_data))
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_event_data>())).require_input_buffers as *const _ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_event_data),
+ "::",
+ stringify!(require_input_buffers)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_event_data>())).processed_input_buffer_id as *const _
+ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_event_data),
+ "::",
+ stringify!(processed_input_buffer_id)
+ )
+ );
+ assert_eq!(
+ unsafe {
+ &(*(::std::ptr::null::<vea_event_data>())).processed_output_buffer as *const _ as usize
+ },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_event_data),
+ "::",
+ stringify!(processed_output_buffer)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_event_data>())).flush_done as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_event_data),
+ "::",
+ stringify!(flush_done)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_event_data>())).error as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_event_data),
+ "::",
+ stringify!(error)
+ )
+ );
+}
+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,
+}
+#[test]
+fn bindgen_test_layout_vea_event() {
+ assert_eq!(
+ ::std::mem::size_of::<vea_event>(),
+ 32usize,
+ concat!("Size of: ", stringify!(vea_event))
+ );
+ assert_eq!(
+ ::std::mem::align_of::<vea_event>(),
+ 8usize,
+ concat!("Alignment of ", stringify!(vea_event))
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_event>())).event_type as *const _ as usize },
+ 0usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_event),
+ "::",
+ stringify!(event_type)
+ )
+ );
+ assert_eq!(
+ unsafe { &(*(::std::ptr::null::<vea_event>())).event_data as *const _ as usize },
+ 8usize,
+ concat!(
+ "Offset of field: ",
+ stringify!(vea_event),
+ "::",
+ stringify!(event_data)
+ )
+ );
+}
+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/libvda/src/encode/event.rs b/libvda/src/encode/event.rs
new file mode 100644
index 0000000..93c8aa1
--- /dev/null
+++ b/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/libvda/src/encode/format.rs b/libvda/src/encode/format.rs
new file mode 100644
index 0000000..7b802f1
--- /dev/null
+++ b/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/libvda/src/encode/mod.rs b/libvda/src/encode/mod.rs
new file mode 100644
index 0000000..ddb9f47
--- /dev/null
+++ b/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/libvda/src/encode/session.rs b/libvda/src/encode/session.rs
new file mode 100644
index 0000000..7e5168a
--- /dev/null
+++ b/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/libvda/src/encode/vea_instance.rs b/libvda/src/encode/vea_instance.rs
new file mode 100644
index 0000000..c628e7a
--- /dev/null
+++ b/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/libvda/src/error.rs b/libvda/src/error.rs
new file mode 100644
index 0000000..791ca39
--- /dev/null
+++ b/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/libvda/src/format.rs b/libvda/src/format.rs
new file mode 100644
index 0000000..41f6a01
--- /dev/null
+++ b/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/libvda/src/lib.rs b/libvda/src/lib.rs
new file mode 100644
index 0000000..ba62434
--- /dev/null
+++ b/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/libvda/tests/decode_tests.rs b/libvda/tests/decode_tests.rs
new file mode 100644
index 0000000..e8f739e
--- /dev/null
+++ b/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/libvda/tests/encode_tests.rs b/libvda/tests/encode_tests.rs
new file mode 100644
index 0000000..687d37e
--- /dev/null
+++ b/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/linux_input_sys/Android.bp b/linux_input_sys/Android.bp
index 0dc2cc8..47fe99b 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 {
@@ -25,7 +25,7 @@
}
rust_defaults {
- name: "linux_input_sys_defaults",
+ name: "linux_input_sys_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "linux_input_sys",
srcs: ["src/lib.rs"],
@@ -41,7 +41,7 @@
rust_test_host {
name: "linux_input_sys_host_test_src_lib",
- defaults: ["linux_input_sys_defaults"],
+ defaults: ["linux_input_sys_test_defaults"],
test_options: {
unit_test: true,
},
@@ -49,45 +49,5 @@
rust_test {
name: "linux_input_sys_device_test_src_lib",
- defaults: ["linux_input_sys_defaults"],
+ defaults: ["linux_input_sys_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/net_sys/Android.bp b/net_sys/Android.bp
index 437fa68..d27975a 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 {
@@ -13,6 +13,7 @@
rust_library {
name: "libnet_sys",
defaults: ["crosvm_defaults"],
+ // has rustc warnings
host_supported: true,
crate_name: "net_sys",
srcs: ["src/lib.rs"],
@@ -23,9 +24,10 @@
}
rust_defaults {
- name: "net_sys_defaults",
+ name: "net_sys_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "net_sys",
+ // has rustc warnings
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
@@ -37,7 +39,7 @@
rust_test_host {
name: "net_sys_host_test_src_lib",
- defaults: ["net_sys_defaults"],
+ defaults: ["net_sys_test_defaults"],
test_options: {
unit_test: true,
},
@@ -45,45 +47,5 @@
rust_test {
name: "net_sys_device_test_src_lib",
- defaults: ["net_sys_defaults"],
+ defaults: ["net_sys_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/net_util/Android.bp b/net_util/Android.bp
index 23db193..0000aa9 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 --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -28,7 +28,7 @@
}
rust_defaults {
- name: "net_util_defaults",
+ name: "net_util_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "net_util",
srcs: ["src/lib.rs"],
@@ -47,7 +47,7 @@
rust_test_host {
name: "net_util_host_test_src_lib",
- defaults: ["net_util_defaults"],
+ defaults: ["net_util_test_defaults"],
test_options: {
unit_test: true,
},
@@ -55,46 +55,5 @@
rust_test {
name: "net_util_device_test_src_lib",
- defaults: ["net_util_defaults"],
+ defaults: ["net_util_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/patches/Android.bp.patch b/patches/Android.bp.patch
new file mode 100644
index 0000000..813d67e
--- /dev/null
+++ b/patches/Android.bp.patch
@@ -0,0 +1,27 @@
+diff --git a/Android.bp b/Android.bp
+index 21ebb3ac..519fb5d5 100644
+--- a/Android.bp
++++ b/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,
+ cargo_pkg_version: "0.1.0",
+@@ -92,7 +93,14 @@ rust_binary {
+ },
+ },
+ target: {
++ linux_bionic_arm64: {
++ relative_install_path: "aarch64-linux-bionic",
++ },
++ darwin: {
++ enabled: false,
++ },
+ linux_glibc_x86_64: {
++ relative_install_path: "x86_64-linux-gnu",
+ features: [
+ "gdb",
+ "gdbstub",
diff --git a/power_monitor/Android.bp b/power_monitor/Android.bp
index 354745d..d67a4d6 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 {
@@ -23,7 +23,7 @@
}
rust_defaults {
- name: "power_monitor_defaults",
+ name: "power_monitor_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "power_monitor",
srcs: ["src/lib.rs"],
@@ -37,7 +37,7 @@
rust_test_host {
name: "power_monitor_host_test_src_lib",
- defaults: ["power_monitor_defaults"],
+ defaults: ["power_monitor_test_defaults"],
test_options: {
unit_test: true,
},
@@ -45,45 +45,5 @@
rust_test {
name: "power_monitor_device_test_src_lib",
- defaults: ["power_monitor_defaults"],
+ defaults: ["power_monitor_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/power_monitor/build.rs b/power_monitor/build.rs
index b925c28..174389d 100644
--- a/power_monitor/build.rs
+++ b/power_monitor/build.rs
@@ -31,13 +31,12 @@
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/protos/Android.bp b/protos/Android.bp
index 655c084..00a570d 100644
--- a/protos/Android.bp
+++ b/protos/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually editted to 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
@@ -13,20 +13,45 @@
rust_library {
name: "libprotos",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "protos",
srcs: ["src/lib.rs"],
- features: [
- "composite-disk",
- ],
edition: "2018",
+ features: ["composite-disk"],
rustlibs: [
"libcdisk_spec_proto",
"libprotobuf",
],
}
+rust_defaults {
+ name: "protos_test_defaults",
+ defaults: ["crosvm_defaults"],
+ crate_name: "protos",
+ srcs: ["src/lib.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
+ features: ["composite-disk"],
+ rustlibs: [
+ "libcdisk_spec_proto",
+ "libprotobuf",
+ ],
+}
+
+rust_test_host {
+ name: "protos_host_test_src_lib",
+ defaults: ["protos_test_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "protos_device_test_src_lib",
+ defaults: ["protos_test_defaults"],
+}
+
rust_protobuf {
name: "libcdisk_spec_proto",
crate_name: "cdisk_spec_proto",
@@ -46,80 +71,3 @@
source_stem: "plugin",
host_supported: true,
}
-
-rust_defaults {
- name: "protos_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "protos",
- // has rustc warnings
- srcs: ["src/lib.rs"],
- features: [
- "composite-disk",
- ],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libcdisk_spec_proto",
- "libprotobuf",
- ],
-}
-
-rust_test_host {
- name: "protos_host_test_src_lib",
- defaults: ["protos_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "protos_device_test_src_lib",
- defaults: ["protos_defaults"],
-}
-
-rust_defaults {
- name: "protos_defaults_trunks",
- defaults: ["crosvm_defaults"],
- crate_name: "trunks",
- // has rustc warnings
- srcs: ["tests/trunks.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libprotobuf",
- "libprotos",
- ],
-}
-
-rust_test_host {
- name: "protos_host_test_tests_trunks",
- defaults: ["protos_defaults_trunks"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "protos_device_test_tests_trunks",
- defaults: ["protos_defaults_trunks"],
-}
-
-// dependent_library ["feature_list"]
-// cfg-if-1.0.0
-// either-1.6.1 "default,use_std"
-// getrandom-0.2.3 "std"
-// libc-0.2.97 "default,std"
-// log-0.4.14
-// ppv-lite86-0.2.10 "simd,std"
-// protobuf-2.24.1
-// protobuf-codegen-2.24.1
-// protoc-2.24.1
-// protoc-rust-2.24.1
-// rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.1 "std"
-// rand_core-0.6.3 "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 98cd4ae..d4286e6 100644
--- a/protos/Cargo.toml
+++ b/protos/Cargo.toml
@@ -7,7 +7,6 @@
[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 2e870b4..5aa3ca2 100644
--- a/protos/build.rs
+++ b/protos/build.rs
@@ -29,15 +29,7 @@
// 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 @@
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 0000000..8dac0dc
--- /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 0000000..a23e061
--- /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 0000000..977968a
--- /dev/null
+++ b/protos/patches/Android.bp.patch
@@ -0,0 +1,24 @@
+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: "2018",
++ features: ["composite-disk"],
+ rustlibs: [
++ "libcdisk_spec_proto",
+ "libprotobuf",
+ ],
+ }
+@@ -30,7 +32,9 @@ rust_defaults {
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
++ features: ["composite-disk"],
+ rustlibs: [
++ "libcdisk_spec_proto",
+ "libprotobuf",
+ ],
+ }
diff --git a/protos/src/lib.rs b/protos/src/lib.rs
index 785a5e3..5c2a751 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 39723ae..0000000
--- 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 f8d68d3..25e7b3e 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/cargo2android.json b/qcow_utils/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /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
index 3fdd269..1e28fc4 100644
--- a/qcow_utils/src/qcow_img.rs
+++ b/qcow_utils/src/qcow_img.rs
@@ -49,7 +49,7 @@
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
- Err(f) => panic!(f.to_string()),
+ Err(f) => panic!("{}", f.to_string()),
};
if matches.free.len() < 2 {
diff --git a/rand_ish/src/lib.rs b/rand_ish/src/lib.rs
deleted file mode 100644
index c7b0ff4..0000000
--- 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 c6fc78a..02a85b8 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 {
@@ -25,7 +25,7 @@
}
rust_defaults {
- name: "resources_defaults",
+ name: "resources_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "resources",
srcs: ["src/lib.rs"],
@@ -41,7 +41,7 @@
rust_test_host {
name: "resources_host_test_src_lib",
- defaults: ["resources_defaults"],
+ defaults: ["resources_test_defaults"],
test_options: {
unit_test: true,
},
@@ -49,45 +49,5 @@
rust_test {
name: "resources_device_test_src_lib",
- defaults: ["resources_defaults"],
+ defaults: ["resources_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/run_tests b/run_tests
index 95c92b8..78c8744 100755
--- a/run_tests
+++ b/run_tests
@@ -20,6 +20,8 @@
"base": [],
"bit_field": [],
"bit_field_derive": [],
+ "common/cros-fuzz": [Requirements.SEPARATE_WORKSPACE],
+ "common/p9": [Requirements.SEPARATE_WORKSPACE, Requirements.X86_64],
"cros_async": [Requirements.DISABLED],
"crosvm": [Requirements.DO_NOT_RUN],
"crosvm_plugin": [Requirements.X86_64],
@@ -52,7 +54,6 @@
"power_monitor": [],
"protos": [],
"qcow_utils": [],
- "rand_ish": [],
"resources": [],
"rutabaga_gfx": [Requirements.CROS_BUILD, Requirements.PRIVILEGED],
"sync": [],
@@ -73,11 +74,11 @@
# Just like for crates, lists requirements for each cargo feature flag.
FEATURE_REQUIREMENTS: Dict[str, List[Requirements]] = {
- "chromeos": [],
+ "chromeos": [Requirements.DISABLED],
"audio": [],
"gpu": [Requirements.CROS_BUILD],
"plugin": [Requirements.PRIVILEGED, Requirements.X86_64],
- "power-monitor-powerd": [],
+ "power-monitor-powerd": [Requirements.DISABLED],
"tpm": [Requirements.CROS_BUILD],
"video-decoder": [Requirements.DISABLED],
"video-encoder": [Requirements.DISABLED],
diff --git a/rutabaga_gfx/Android.bp b/rutabaga_gfx/Android.bp
index d0762d0..adf4d62 100644
--- a/rutabaga_gfx/Android.bp
+++ b/rutabaga_gfx/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, shared_libs, and target to rules below.
+// 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
@@ -28,8 +28,6 @@
"liblibc",
"libsync_rust",
],
-
- // added manually
shared_libs: ["libgfxstream_backend"],
target: {
host: {
@@ -37,8 +35,8 @@
},
android: {
shared_libs: [
- "libdrm",
- ],
+ "libdrm",
+ ],
static_libs: [
"libepoxy",
"libgbm",
@@ -49,10 +47,9 @@
}
rust_defaults {
- name: "rutabaga_gfx_defaults",
+ name: "rutabaga_gfx_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "rutabaga_gfx",
- // has rustc warnings
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
@@ -68,8 +65,14 @@
"liblibc",
"libsync_rust",
],
+}
- // added manually
+rust_test_host {
+ name: "rutabaga_gfx_host_test_src_lib",
+ defaults: ["rutabaga_gfx_test_defaults"],
+ test_options: {
+ unit_test: true,
+ },
shared_libs: ["libgfxstream_backend"],
target: {
host: {
@@ -77,8 +80,8 @@
},
android: {
shared_libs: [
- "libdrm",
- ],
+ "libdrm",
+ ],
static_libs: [
"libepoxy",
"libgbm",
@@ -88,55 +91,23 @@
},
}
-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"],
+ defaults: ["rutabaga_gfx_test_defaults"],
+ shared_libs: ["libgfxstream_backend"],
+ target: {
+ host: {
+ shared_libs: ["libvirglrenderer"],
+ },
+ android: {
+ shared_libs: [
+ "libdrm",
+ ],
+ static_libs: [
+ "libepoxy",
+ "libgbm",
+ "libvirglrenderer",
+ ],
+ },
+ },
}
-
-// 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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/rutabaga_gfx/cargo2android.json b/rutabaga_gfx/cargo2android.json
new file mode 100644
index 0000000..09d4626
--- /dev/null
+++ b/rutabaga_gfx/cargo2android.json
@@ -0,0 +1,9 @@
+{
+ "add-module-block": "cargo2android_target.bp",
+ "add_workspace": true,
+ "device": true,
+ "features": "gfxstream,virgl_renderer,virgl_renderer_next",
+ "global_defaults": "crosvm_defaults",
+ "run": true,
+ "tests": true
+}
\ No newline at end of file
diff --git a/rutabaga_gfx/cargo2android_target.bp b/rutabaga_gfx/cargo2android_target.bp
new file mode 100644
index 0000000..bffc28a
--- /dev/null
+++ b/rutabaga_gfx/cargo2android_target.bp
@@ -0,0 +1,16 @@
+shared_libs: ["libgfxstream_backend"],
+target: {
+ host: {
+ shared_libs: ["libvirglrenderer"],
+ },
+ android: {
+ shared_libs: [
+ "libdrm",
+ ],
+ static_libs: [
+ "libepoxy",
+ "libgbm",
+ "libvirglrenderer",
+ ],
+ },
+}
\ No newline at end of file
diff --git a/rutabaga_gfx/src/cross_domain/cross_domain.rs b/rutabaga_gfx/src/cross_domain/cross_domain.rs
index 4426f51..37cfea4 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::{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 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(RutabagaFenceData),
+ 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,16 +110,369 @@
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>,
- fence_handler: RutabagaFenceHandler,
- 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(())
+ }
+ _ => {
+ // Expect to receive the write end of a Wayland pipe only.
+ *descriptor_type = match FileFlags::from_file(descriptor) {
+ Ok(FileFlags::Write) => CROSS_DOMAIN_ID_TYPE_WRITE_PIPE,
+ _ => return Err(RutabagaError::SpecViolation),
+ };
+ 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::SpecViolation)
+ .and_then(|conn| Ok(conn.send_with_fds(opaque_data, descriptors)?))
+ }
+
+ fn receive_msg(
+ &self,
+ opaque_data: &mut Vec<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(&mut 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::SpecViolation)
+ }
+ }
+}
+
+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: RutabagaFenceData,
+ resample_evt: &Event,
+ receive_buf: &mut Vec<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, mut identifier_type), mut identifier_size), mut file) in
+ iter
+ {
+ // Safe since the descriptors from receive_msg(..) are owned by us and valid.
+ descriptor_analysis(
+ &mut file,
+ &mut identifier_type,
+ &mut 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::SpecViolation),
+ };
+ }
+
+ 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::SpecViolation)?;
+
+ 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::SpecViolation),
+ }
+
+ 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::SpecViolation)?;
+
+ match item {
+ CrossDomainItem::WaylandReadPipe(file) => self
+ .wait_ctx
+ .add(file, CrossDomainToken::WaylandReadPipe(read_pipe_id))?,
+ _ => return Err(RutabagaError::SpecViolation),
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
}
impl CrossDomain {
@@ -61,11 +491,17 @@
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)?;
@@ -75,7 +511,47 @@
.ok_or(RutabagaError::SpecViolation)?
.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(())
@@ -92,7 +568,6 @@
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 {
@@ -100,9 +575,8 @@
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,
};
@@ -112,26 +586,156 @@
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::SpecViolation)
+ }
+ }
- 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;
- // 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);
+ let num_identifiers = cmd_send.num_identifiers.try_into()?;
+
+ if num_identifiers > CROSS_DOMAIN_MAX_IDENTIFIERS {
+ return Err(RutabagaError::SpecViolation);
+ }
+
+ 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::SpecViolation);
+ }
+ } else if *identifier_type == CROSS_DOMAIN_ID_TYPE_READ_PIPE {
+ // In practice, just 1 write or read pipe per send is observed. If we encounter
+ // more, this can be changed later.
+ if write_pipe_opt.is_some() {
+ return Err(RutabagaError::SpecViolation);
+ }
+
+ 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::SpecViolation);
+ }
+
+ // 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::SpecViolation);
+ }
+ }
+
+ 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::SpecViolation);
+ }
+
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::SpecViolation)?;
+
+ 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::SpecViolation),
+ }
+ }
+}
+
+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 {
@@ -141,45 +745,88 @@
resource_create_blob: ResourceCreateBlob,
handle: Option<RutabagaHandle>,
) -> RutabagaResult<RutabagaResource> {
- let reqs = self
- .requirements_blobs
- .get(&resource_create_blob.blob_id)
- .ok_or(RutabagaError::SpecViolation)?;
+ let item_id = resource_create_blob.blob_id as u32;
- if reqs.size != resource_create_blob.size {
- return Err(RutabagaError::SpecViolation);
+ // 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::SpecViolation)?;
+
+ match item {
+ CrossDomainItem::ImageRequirements(reqs) => {
+ if reqs.size != resource_create_blob.size {
+ return Err(RutabagaError::SpecViolation);
+ }
+
+ // 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,
+ })
+ }
+ _ => Err(RutabagaError::SpecViolation),
+ }
+ } else {
+ let item = self
+ .item_state
+ .lock()
+ .table
+ .remove(&item_id)
+ .ok_or(RutabagaError::SpecViolation)?;
+
+ match item {
+ CrossDomainItem::WaylandKeymap(descriptor) => {
+ let hnd = RutabagaHandle {
+ os_handle: descriptor,
+ handle_type: RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD,
+ };
+
+ 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::SpecViolation),
+ }
}
-
- // 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<()> {
@@ -202,6 +849,27 @@
self.get_image_requirements(&cmd_get_reqs)?;
}
+ 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::Unsupported),
}
@@ -213,7 +881,7 @@
fn attach(&mut self, resource: &mut RutabagaResource) {
if resource.blob_mem == RUTABAGA_BLOB_MEM_GUEST {
- self.context_resources.insert(
+ self.context_resources.lock().insert(
resource.resource_id,
CrossDomainResource {
handle: None,
@@ -221,7 +889,7 @@
},
);
} else if let Some(ref handle) = resource.handle {
- self.context_resources.insert(
+ self.context_resources.lock().insert(
resource.resource_id,
CrossDomainResource {
handle: Some(handle.clone()),
@@ -232,21 +900,21 @@
}
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.fence_handler.call(fence_data);
- self.last_fence_data = Some(fence_data);
- 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,
+ match fence_data.ring_idx {
+ CROSS_DOMAIN_QUERY_RING => self.fence_handler.call(fence_data),
+ CROSS_DOMAIN_CHANNEL_RING => {
+ if let Some(state) = &self.state {
+ state.add_job(CrossDomainJob::HandleFence(fence_data));
+ }
+ }
+ _ => return Err(RutabagaError::SpecViolation),
}
+
+ Ok(())
}
}
@@ -271,7 +939,7 @@
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()
}
@@ -285,13 +953,13 @@
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,
+ state: None,
+ context_resources: Arc::new(Mutex::new(Default::default())),
+ item_state: Arc::new(Mutex::new(Default::default())),
fence_handler,
- blob_id: 0,
+ 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 25dd6d3..d3f6865 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 @@
/// 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 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 @@
#[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 @@
}
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/renderer_utils.rs b/rutabaga_gfx/src/renderer_utils.rs
index f116d89..f8cb7c2 100644
--- a/rutabaga_gfx/src/renderer_utils.rs
+++ b/rutabaga_gfx/src/renderer_utils.rs
@@ -64,7 +64,7 @@
flags: RUTABAGA_FLAG_FENCE,
fence_id: latest_fence as u64,
ctx_id: 0,
- fence_ctx_idx: 0,
+ ring_idx: 0,
});
}
}
diff --git a/rutabaga_gfx/src/rutabaga_core.rs b/rutabaga_gfx/src/rutabaga_core.rs
index 6ac5055..8913d28 100644
--- a/rutabaga_gfx/src/rutabaga_core.rs
+++ b/rutabaga_gfx/src/rutabaga_core.rs
@@ -181,7 +181,7 @@
/// 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
+ /// Implementations must create a fence on specified `ring_idx` in `fence_data`. This
/// allows for multiple syncrhonizations timelines per RutabagaContext.
fn context_create_fence(&mut self, _fence_data: RutabagaFenceData) -> RutabagaResult<()> {
Err(RutabagaError::Unsupported)
@@ -276,10 +276,10 @@
}
/// 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
+ /// 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 {
+ if fence_data.flags & RUTABAGA_FLAG_INFO_RING_IDX != 0 {
let ctx = self
.contexts
.get_mut(&fence_data.ctx_id)
@@ -303,7 +303,7 @@
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.
+ // `ctx_id` or `ring_idx`. This path exists for OpenGL legacy reasons and 2D mode.
let component = self
.components
.get_mut(&self.default_component)
@@ -315,7 +315,7 @@
flags: RUTABAGA_FLAG_FENCE,
fence_id: global_fence_id as u64,
ctx_id: 0,
- fence_ctx_idx: 0,
+ ring_idx: 0,
});
for ctx in self.contexts.values_mut() {
diff --git a/rutabaga_gfx/src/rutabaga_utils.rs b/rutabaga_gfx/src/rutabaga_utils.rs
index d5dea79..a204f04 100644
--- a/rutabaga_gfx/src/rutabaga_utils.rs
+++ b/rutabaga_gfx/src/rutabaga_utils.rs
@@ -25,12 +25,16 @@
/// 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.
@@ -89,7 +93,7 @@
/// Rutabaga flags for creating fences (fence ctx idx info not upstreamed).
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
#[derive(Copy, Clone)]
@@ -97,7 +101,7 @@
pub flags: u32,
pub fence_id: u64,
pub ctx_id: u32,
- pub fence_ctx_idx: u32,
+ pub ring_idx: u32,
}
/// Mapped memory caching flags (see virtio_gpu spec)
diff --git a/seccomp/aarch64/common_device.policy b/seccomp/aarch64/common_device.policy
index 349afe9..771a988 100644
--- a/seccomp/aarch64/common_device.policy
+++ b/seccomp/aarch64/common_device.policy
@@ -37,6 +37,7 @@
rt_sigprocmask: 1
rt_sigreturn: 1
sched_getaffinity: 1
+sched_yield: 1
sendmsg: 1
sendto: 1
set_robust_list: 1
@@ -44,3 +45,4 @@
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 60797f9..74d71b2 100644
--- a/seccomp/aarch64/cras_audio_device.policy
+++ b/seccomp/aarch64/cras_audio_device.policy
@@ -12,3 +12,6 @@
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/gpu_device.policy b/seccomp/aarch64/gpu_device.policy
index 4ceac5c..d5541ed 100644
--- a/seccomp/aarch64/gpu_device.policy
+++ b/seccomp/aarch64/gpu_device.policy
@@ -42,6 +42,7 @@
sigaltstack: 1
write: 1
writev: 1
+uname: 1
# Required for perfetto tracing
getsockopt: 1
@@ -58,6 +59,7 @@
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
@@ -77,7 +79,6 @@
clock_gettime: 1
# Rules specific to Mesa.
-uname: 1
sched_setscheduler: 1
sched_setaffinity: 1
kcmp: 1
diff --git a/seccomp/aarch64/xhci.policy b/seccomp/aarch64/xhci.policy
index 684ae0d..b949ec2 100644
--- a/seccomp/aarch64/xhci.policy
+++ b/seccomp/aarch64/xhci.policy
@@ -17,7 +17,6 @@
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/common_device.policy b/seccomp/arm/common_device.policy
index 165bfda..f0adee9 100644
--- a/seccomp/arm/common_device.policy
+++ b/seccomp/arm/common_device.policy
@@ -46,6 +46,7 @@
rt_sigprocmask: 1
rt_sigreturn: 1
sched_getaffinity: 1
+sched_yield: 1
sendmsg: 1
sendto: 1
set_robust_list: 1
@@ -53,3 +54,4 @@
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 20bf60e..21ebdb6 100644
--- a/seccomp/arm/cras_audio_device.policy
+++ b/seccomp/arm/cras_audio_device.policy
@@ -12,3 +12,8 @@
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/gpu_device.policy b/seccomp/arm/gpu_device.policy
index ec5a5b4..88c9fcc 100644
--- a/seccomp/arm/gpu_device.policy
+++ b/seccomp/arm/gpu_device.policy
@@ -42,12 +42,14 @@
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
@@ -64,6 +66,8 @@
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
@@ -89,7 +93,6 @@
clock_gettime64: 1
# Rules specific to Mesa.
-uname: 1
sched_setscheduler: 1
sched_setaffinity: 1
kcmp: 1
diff --git a/seccomp/arm/video_device.policy b/seccomp/arm/video_device.policy
index 2d82956..8169957 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
@@ -20,7 +22,6 @@
ioctl: arg1 & 0x6400
memfd_create: 1
openat: 1
-sched_yield: 1
send: 1
setpriority: 1
socket: arg0 == AF_UNIX
diff --git a/seccomp/arm/xhci.policy b/seccomp/arm/xhci.policy
index ca1a73d..322a296 100644
--- a/seccomp/arm/xhci.policy
+++ b/seccomp/arm/xhci.policy
@@ -19,7 +19,6 @@
socket: arg0 == AF_NETLINK
stat: 1
statx: 1
-uname: 1
# The following ioctls are:
# 0x4004550d == USBDEVFS_REAPURBNDELAY32
# 0x550b == USBDEVFS_DISCARDURB
diff --git a/seccomp/x86_64/common_device.policy b/seccomp/x86_64/common_device.policy
index 49d4520..8c72d0d 100644
--- a/seccomp/x86_64/common_device.policy
+++ b/seccomp/x86_64/common_device.policy
@@ -40,6 +40,7 @@
rt_sigprocmask: 1
rt_sigreturn: 1
sched_getaffinity: 1
+sched_yield: 1
sendmsg: 1
sendto: 1
set_robust_list: 1
@@ -47,3 +48,4 @@
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 bbaffb0..416712b 100644
--- a/seccomp/x86_64/cras_audio_device.policy
+++ b/seccomp/x86_64/cras_audio_device.policy
@@ -13,3 +13,6 @@
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/gpu_device.policy b/seccomp/x86_64/gpu_device.policy
index dcfc081..d28f828 100644
--- a/seccomp/x86_64/gpu_device.policy
+++ b/seccomp/x86_64/gpu_device.policy
@@ -39,12 +39,14 @@
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
@@ -75,6 +77,7 @@
stat: 1
statx: 1
sysinfo: 1
+fstatfs: 1
# Required for perfetto tracing
# fcntl: arg1 == F_SETFD || arg1 == F_GETFL || arg1 == F_SETFL (merged above)
@@ -90,7 +93,6 @@
unlink: 1
# Rules specific to AMD gpus.
-uname: 1
sched_setscheduler: 1
sched_setaffinity: 1
kcmp: 1
@@ -100,6 +102,3 @@
getgid: 1
getegid: 1
getcwd: 1
-
-# Rules for virglrenderer
-sched_yield: 1
diff --git a/seccomp/x86_64/iommu_device.policy b/seccomp/x86_64/iommu_device.policy
new file mode 100644
index 0000000..28a2002
--- /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/video_device.policy b/seccomp/x86_64/video_device.policy
index 1f81f73..650e029 100644
--- a/seccomp/x86_64/video_device.policy
+++ b/seccomp/x86_64/video_device.policy
@@ -20,11 +20,11 @@
ioctl: arg1 & 0x6400
memfd_create: 1
openat: 1
-sched_yield: 1
setpriority: 1
socket: arg0 == AF_UNIX
stat: 1
fstat: 1
+fstatfs: 1
# Rules needed for minigbm on AMD devices.
getrandom: 1
@@ -35,7 +35,6 @@
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/xhci.policy b/seccomp/x86_64/xhci.policy
index 9ef3766..50b41d1 100644
--- a/seccomp/x86_64/xhci.policy
+++ b/seccomp/x86_64/xhci.policy
@@ -18,7 +18,6 @@
openat: 1
socket: arg0 == AF_NETLINK
stat: 1
-uname: 1
# The following ioctls are:
# 0x4008550d == USBDEVFS_REAPURBNDELAY
# 0x41045508 == USBDEVFS_GETDRIVER
diff --git a/setup_cros_cargo.sh b/setup_cros_cargo.sh
new file mode 100755
index 0000000..aa5a65a
--- /dev/null
+++ b/setup_cros_cargo.sh
@@ -0,0 +1,14 @@
+#!/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.
+#
+# TODO(b/194323235): Add documentation for ChromeOS developer workflows.
+
+sed -i 's|path = "libcras_stub"|path = "../../third_party/adhd/cras/client/libcras"|g' \
+ Cargo.toml
+
+echo "Modified Cargo.toml with new paths. Please do not commit those."
diff --git a/src/crosvm.rs b/src/crosvm.rs
index 30a0d43..a52fca3 100644
--- a/src/crosvm.rs
+++ b/src/crosvm.rs
@@ -6,6 +6,7 @@
//! configs.
pub mod argument;
+pub mod error;
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub mod gdb;
#[path = "linux.rs"]
@@ -54,6 +55,7 @@
pub path: PathBuf,
pub read_only: bool,
pub sparse: bool,
+ pub o_direct: bool,
pub block_size: u32,
pub id: Option<[u8; DISK_ID_LEN]>,
}
@@ -206,8 +208,10 @@
pub vcpu_affinity: Option<VcpuAffinity>,
pub cpu_clusters: Vec<Vec<usize>>,
pub cpu_capacity: BTreeMap<usize, u32>, // CPU index -> capacity
+ 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>,
@@ -229,7 +233,6 @@
pub tap_fd: Vec<RawFd>,
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,
@@ -242,6 +245,8 @@
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: Vec<TouchDeviceOption>,
@@ -252,7 +257,7 @@
pub virtio_switches: Vec<PathBuf>,
pub virtio_input_evdevs: Vec<PathBuf>,
pub split_irqchip: bool,
- pub vfio: Vec<PathBuf>,
+ pub vfio: BTreeMap<PathBuf, bool>,
pub video_dec: bool,
pub video_enc: bool,
pub acpi_tables: Vec<PathBuf>,
@@ -264,6 +269,7 @@
pub vhost_user_blk: Vec<VhostUserOption>,
pub vhost_user_console: Vec<VhostUserOption>,
pub vhost_user_fs: Vec<VhostUserFsOption>,
+ pub vhost_user_mac80211_hwsim: Option<VhostUserOption>,
pub vhost_user_net: Vec<VhostUserOption>,
pub vhost_user_wl: Vec<VhostUserWlOption>,
#[cfg(feature = "direct")]
@@ -287,8 +293,10 @@
vcpu_affinity: None,
cpu_clusters: Vec::new(),
cpu_capacity: BTreeMap::new(),
+ delay_rt: false,
no_smt: false,
memory: None,
+ swiotlb: None,
hugepages: false,
memory_file: None,
executable_path: None,
@@ -313,7 +321,6 @@
gpu_parameters: None,
software_tpm: false,
wayland_socket_paths: BTreeMap::new(),
- wayland_dmabuf: false,
x_display: None,
display_window_keyboard: false,
display_window_mouse: false,
@@ -323,6 +330,8 @@
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: Vec::new(),
@@ -333,7 +342,7 @@
virtio_switches: Vec::new(),
virtio_input_evdevs: Vec::new(),
split_irqchip: false,
- vfio: Vec::new(),
+ vfio: BTreeMap::new(),
video_dec: false,
video_enc: false,
acpi_tables: Vec::new(),
@@ -345,6 +354,7 @@
vhost_user_blk: Vec::new(),
vhost_user_console: Vec::new(),
vhost_user_fs: Vec::new(),
+ vhost_user_mac80211_hwsim: None,
vhost_user_net: Vec::new(),
vhost_user_wl: Vec::new(),
#[cfg(feature = "direct")]
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..f304d90
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,314 @@
+// 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 arch::{self, LinuxArch};
+use base::TubeError;
+use devices::virtio;
+use devices::virtio::vhost::user::Error as VhostUserError;
+use std::error::Error as StdError;
+use std::fmt::{self, Display};
+use std::io;
+use std::num::ParseIntError;
+use std::path::PathBuf;
+#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+use std::sync::mpsc;
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use 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},
+};
+
+use net_util::Error as NetError;
+use remain::sorted;
+#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+use vm_control::VcpuDebugStatusMessage;
+
+#[sorted]
+#[derive(Debug)]
+pub enum Error {
+ AddGpuDeviceMemory(base::Error),
+ AddIrqChipVcpu(base::Error),
+ AddPmemDeviceMemory(base::Error),
+ AllocateGpuDeviceAddress,
+ AllocatePmemDeviceAddress(resources::Error),
+ 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),
+ ConnectTube(io::Error),
+ #[cfg(feature = "audio")]
+ CreateAc97(devices::PciDeviceError),
+ CreateConsole(arch::serial::Error),
+ CreateControlServer(io::Error),
+ CreateDiskError(disk::Error),
+ CreateEvent(base::Error),
+ CreateGrallocError(rutabaga_gfx::RutabagaError),
+ CreateGuestMemory(vm_memory::GuestMemoryError),
+ CreateIrqChip(base::Error),
+ CreateKvm(base::Error),
+ CreateSignalFd(base::SignalFdError),
+ CreateSocket(io::Error),
+ CreateTapDevice(NetError),
+ CreateTimer(base::Error),
+ CreateTpmStorage(PathBuf, io::Error),
+ CreateTube(TubeError),
+ #[cfg(feature = "usb")]
+ CreateUsbProvider(devices::usb::host_backend::error::Error),
+ CreateVcpu(base::Error),
+ CreateVfioDevice(devices::vfio::VfioError),
+ CreateVfioKvmDevice(base::Error),
+ CreateVirtioIommu(base::Error),
+ 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),
+ GenerateAcpi,
+ GetMaxOpenFiles(io::Error),
+ GetSignalMask(base::signal::Error),
+ GuestMemoryLayout(<Arch as LinuxArch>::Error),
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ HandleDebugCommand(<Arch as LinuxArch>::Error),
+ InputDeviceNew(virtio::InputError),
+ InputEventsOpen(io::Error),
+ 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(feature = "audio")]
+ SoundDeviceNew(virtio::SoundError),
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ SpawnGdbServer(io::Error),
+ SpawnVcpu(io::Error),
+ SwiotlbTooLarge,
+ Timer(base::Error),
+ ValidateRawDescriptor(base::Error),
+ VhostNetDeviceNew(virtio::vhost::Error),
+ VhostUserBlockDeviceNew(VhostUserError),
+ VhostUserConsoleDeviceNew(VhostUserError),
+ VhostUserFsDeviceNew(VhostUserError),
+ VhostUserMac80211HwsimNew(VhostUserError),
+ VhostUserNetDeviceNew(VhostUserError),
+ VhostUserNetWithNetArgs,
+ VhostUserWlDeviceNew(VhostUserError),
+ 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)
+ }
+ 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),
+ ConnectTube(e) => write!(f, "failed to connect to tube: {}", 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),
+ CreateGuestMemory(e) => write!(f, "failed to create guest memory: {}", e),
+ CreateIrqChip(e) => write!(f, "failed to create IRQ chip: {}", 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),
+ #[cfg(feature = "usb")]
+ 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),
+ CreateVfioKvmDevice(e) => write!(f, "failed to create KVM vfio device: {}", e),
+ CreateVirtioIommu(e) => write!(f, "Failed to create IOMMU 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),
+ GenerateAcpi => write!(f, "failed to generate ACPI table"),
+ 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),
+ 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),
+ 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(feature = "audio")]
+ SoundDeviceNew(e) => write!(f, "failed to create sound device: {}", 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),
+ SwiotlbTooLarge => write!(f, "requested swiotlb size too large"),
+ 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)
+ }
+ VhostUserConsoleDeviceNew(e) => {
+ write!(f, "failed to set up vhost-user console device: {}", e)
+ }
+ VhostUserFsDeviceNew(e) => write!(f, "failed to set up vhost-user fs device: {}", e),
+ VhostUserMac80211HwsimNew(e) => {
+ write!(f, "failed to set up vhost-user mac80211_hwsim 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"
+ ),
+ VhostUserWlDeviceNew(e) => {
+ write!(f, "failed to set up vhost-user wl device: {}", e)
+ }
+ 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 {}
+
+pub type Result<T> = std::result::Result<T, Error>;
diff --git a/src/gdb.rs b/src/gdb.rs
index 0801241..42e094d 100644
--- a/src/gdb.rs
+++ b/src/gdb.rs
@@ -14,15 +14,15 @@
};
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};
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 +141,7 @@
}
// 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 +150,7 @@
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,6 +173,7 @@
"Failed to resume the target"
})?;
+ let mut check_gdb_interrupt = check_gdb_interrupt.no_async();
// Polling
loop {
// TODO(keiichiw): handle error?
@@ -194,7 +195,7 @@
}
}
- 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"
@@ -294,7 +295,11 @@
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 +327,7 @@
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);
@@ -340,3 +346,5 @@
}
}
}
+
+impl Breakpoints for GdbStub {}
diff --git a/src/linux.rs b/src/linux.rs
index 44ae93a..9c587cf 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -2,21 +2,17 @@
// 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::cmp::Reverse;
+use std::collections::BTreeMap;
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};
use std::iter;
use std::mem;
use std::net::Ipv4Addr;
-use std::num::ParseIntError;
-use std::os::unix::io::FromRawFd;
use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf};
use std::ptr;
@@ -27,23 +23,26 @@
use std::thread;
use std::thread::JoinHandle;
-use libc::{self, c_int, gid_t, uid_t};
+use libc::{self, c_int, gid_t, uid_t, EINVAL};
use acpi_tables::sdt::SDT;
+use crate::error::{Error, Result};
use base::net::{UnixSeqpacket, UnixSeqpacketListener, UnlinkUnixSeqpacketListener};
use base::*;
+use devices::vfio::{VfioCommonSetup, VfioCommonTrait};
#[cfg(feature = "gpu")]
use devices::virtio::gpu::{DEFAULT_DISPLAY_HEIGHT, DEFAULT_DISPLAY_WIDTH};
use devices::virtio::vhost::user::{
- Block as VhostUserBlock, Console as VhostUserConsole, Error as VhostUserError,
- Fs as VhostUserFs, Net as VhostUserNet, Wl as VhostUserWl,
+ Block as VhostUserBlock, Console as VhostUserConsole, Fs as VhostUserFs,
+ Mac80211Hwsim as VhostUserMac80211Hwsim, Net as VhostUserNet, Wl as VhostUserWl,
};
#[cfg(feature = "gpu")]
use devices::virtio::EventDevice;
use devices::virtio::{self, Console, VirtioDevice};
#[cfg(feature = "audio")]
use devices::Ac97Dev;
+use devices::ProtectionType;
use devices::{
self, IrqChip, IrqEventIndex, KvmKernelIrqChip, PciDevice, VcpuRunState, VfioContainer,
VfioDevice, VfioPciDevice, VirtioPciDevice,
@@ -51,10 +50,9 @@
#[cfg(feature = "usb")]
use devices::{HostBackendDeviceProvider, XhciController};
use hypervisor::kvm::{Kvm, KvmVcpu, KvmVm};
-use hypervisor::{HypervisorCap, Vcpu, VcpuExit, VcpuRunHandle, Vm, VmCap};
+use hypervisor::{DeviceKind, HypervisorCap, Vcpu, VcpuExit, VcpuRunHandle, Vm, VmCap};
use minijail::{self, Minijail};
-use net_util::{Error as NetError, MacAddress, Tap};
-use remain::sorted;
+use net_util::{MacAddress, Tap};
use resources::{Alloc, MmioType, SystemAllocator};
use rutabaga_gfx::RutabagaGralloc;
use sync::Mutex;
@@ -85,290 +83,6 @@
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),
- BorrowVfioContainer,
- BuildVm(<Arch as LinuxArch>::Error),
- ChownTpmStorage(base::Error),
- CloneEvent(base::Error),
- CloneVcpu(base::Error),
- ConfigureVcpu(<Arch as LinuxArch>::Error),
- ConnectTube(io::Error),
- #[cfg(feature = "audio")]
- CreateAc97(devices::PciDeviceError),
- CreateConsole(arch::serial::Error),
- CreateControlServer(io::Error),
- CreateDiskError(disk::Error),
- CreateEvent(base::Error),
- CreateGrallocError(rutabaga_gfx::RutabagaError),
- CreateGuestMemory(vm_memory::GuestMemoryError),
- CreateIrqChip(base::Error),
- CreateKvm(base::Error),
- CreateSignalFd(base::SignalFdError),
- CreateSocket(io::Error),
- CreateTapDevice(NetError),
- CreateTimer(base::Error),
- CreateTpmStorage(PathBuf, io::Error),
- CreateTube(TubeError),
- #[cfg(feature = "usb")]
- 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),
- VhostUserConsoleDeviceNew(VhostUserError),
- VhostUserFsDeviceNew(VhostUserError),
- VhostUserNetDeviceNew(VhostUserError),
- VhostUserNetWithNetArgs,
- VhostUserWlDeviceNew(VhostUserError),
- 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),
- BorrowVfioContainer => write!(f, "failed to borrow global vfio container"),
- 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),
- ConnectTube(e) => write!(f, "failed to connect to tube: {}", 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),
- CreateGuestMemory(e) => write!(f, "failed to create guest memory: {}", e),
- CreateIrqChip(e) => write!(f, "failed to create IRQ chip: {}", 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),
- #[cfg(feature = "usb")]
- 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)
- }
- VhostUserConsoleDeviceNew(e) => {
- write!(f, "failed to set up vhost-user console 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"
- ),
- VhostUserWlDeviceNew(e) => {
- write!(f, "failed to set up vhost-user wl device: {}", e)
- }
- 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),
@@ -512,33 +226,9 @@
type DeviceResult<T = VirtioDeviceStub> = std::result::Result<T, Error>;
-/// 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.
-fn open_file<F>(path: &Path, read_only: bool, error_constructor: F) -> Result<File>
-where
- F: FnOnce(PathBuf, io::Error) -> Error,
-{
- // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
- Ok(
- if let Some(raw_descriptor) = raw_descriptor_from_path(path)? {
- // Safe because we will validate |raw_fd|.
- unsafe { File::from_raw_descriptor(raw_descriptor) }
- } else {
- OpenOptions::new()
- .read(true)
- .write(!read_only)
- .open(path)
- .map_err(|e| error_constructor(path.to_path_buf(), e))?
- },
- )
-}
-
fn create_block_device(cfg: &Config, disk: &DiskOption, disk_device_tube: Tube) -> DeviceResult {
- let raw_image: File = open_file(&disk.path, disk.read_only, Error::Disk)?;
+ let raw_image: File = open_file(&disk.path, disk.read_only, disk.o_direct)
+ .map_err(|e| Error::Disk(disk.path.clone(), e.into()))?;
// Lock the disk image to prevent other crosvm instances from using it.
let lock_op = if disk.read_only {
FlockOperation::LockShared
@@ -620,6 +310,17 @@
})
}
+fn create_vhost_user_mac80211_hwsim_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult {
+ let dev = VhostUserMac80211Hwsim::new(virtio::base_features(cfg.protected_vm), &opt.socket)
+ .map_err(Error::VhostUserMac80211HwsimNew)?;
+
+ 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)?;
@@ -942,7 +643,6 @@
x_display: Option<String>,
event_devices: Vec<EventDevice>,
map_request: Arc<Mutex<Option<ExternalMapping>>>,
- mem: &GuestMemory,
) -> DeviceResult {
let mut display_backends = vec![
virtio::DisplayBackend::X(x_display),
@@ -974,7 +674,6 @@
cfg.sandbox,
virtio::base_features(cfg.protected_vm),
cfg.wayland_socket_paths.clone(),
- mem.clone(),
);
let jail = match simple_jail(&cfg, "gpu_device")? {
@@ -1325,8 +1024,8 @@
index: usize,
pmem_device_tube: Tube,
) -> DeviceResult {
- let fd = open_file(&disk.path, disk.read_only, Error::Disk)?;
-
+ let fd = open_file(&disk.path, disk.read_only, false /*O_DIRECT*/)
+ .map_err(|e| Error::Disk(disk.path.clone(), e.into()))?;
let arena_size = {
let metadata =
std::fs::metadata(&disk.path).map_err(|e| Error::Disk(disk.path.to_path_buf(), e))?;
@@ -1402,6 +1101,24 @@
})
}
+fn create_iommu_device(
+ cfg: &Config,
+ phys_max_addr: u64,
+ endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+) -> DeviceResult {
+ let dev = virtio::Iommu::new(
+ virtio::base_features(cfg.protected_vm),
+ endpoints,
+ phys_max_addr,
+ )
+ .map_err(Error::CreateVirtioIommu)?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg, "iommu_device")?,
+ })
+}
+
fn create_console_device(cfg: &Config, param: &SerialParameters) -> DeviceResult {
let mut keep_rds = Vec::new();
let evt = Event::new().map_err(Error::CreateEvent)?;
@@ -1437,6 +1154,17 @@
})
}
+#[cfg(feature = "audio")]
+fn create_sound_device(path: &Path, cfg: &Config) -> DeviceResult {
+ let dev = virtio::new_sound(path, virtio::base_features(cfg.protected_vm))
+ .map_err(Error::SoundDeviceNew)?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg, "vios_audio_device")?,
+ })
+}
+
// gpu_device_tube is not used when GPU support is disabled.
#[cfg_attr(not(feature = "gpu"), allow(unused_variables))]
fn create_virtio_devices(
@@ -1667,7 +1395,6 @@
cfg.x_display.clone(),
event_devices,
map_request,
- vm.get_memory(),
)?);
}
}
@@ -1725,33 +1452,33 @@
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)
}
-thread_local!(static VFIO_CONTAINER: RefCell<Option<Arc<Mutex<VfioContainer>>>> = RefCell::new(None));
fn create_vfio_device(
cfg: &Config,
vm: &impl Vm,
resources: &mut SystemAllocator,
control_tubes: &mut Vec<TaggedControlTube>,
vfio_path: &Path,
+ kvm_vfio_file: &SafeDescriptor,
+ endpoints: &mut BTreeMap<u32, Arc<Mutex<VfioContainer>>>,
+ iommu_enabled: bool,
) -> DeviceResult<(Box<VfioPciDevice>, Option<Minijail>)> {
- let vfio_container =
- VFIO_CONTAINER.with::<_, DeviceResult<Arc<Mutex<VfioContainer>>>>(|v| {
- if v.borrow().is_some() {
- if let Some(container) = &(*v.borrow()) {
- Ok(container.clone())
- } else {
- Err(Error::BorrowVfioContainer)
- }
- } else {
- let container = Arc::new(Mutex::new(
- VfioContainer::new().map_err(Error::CreateVfioDevice)?,
- ));
- *v.borrow_mut() = Some(container.clone());
- Ok(container)
- }
- })?;
+ let vfio_container = VfioCommonSetup::vfio_get_container(vfio_path, iommu_enabled)
+ .map_err(Error::CreateVfioDevice)?;
// 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)?;
@@ -1763,8 +1490,14 @@
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 vfio_device = VfioDevice::new(vfio_path, vm, vm.get_memory(), vfio_container)
- .map_err(Error::CreateVfioDevice)?;
+ let vfio_device = VfioDevice::new(
+ vfio_path,
+ vm.get_memory(),
+ &kvm_vfio_file,
+ vfio_container.clone(),
+ iommu_enabled,
+ )
+ .map_err(Error::CreateVfioDevice)?;
let mut vfio_pci_device = Box::new(VfioPciDevice::new(
vfio_device,
vfio_device_tube_msi,
@@ -1772,13 +1505,18 @@
vfio_device_tube_mem,
));
// early reservation for pass-through PCI devices.
- if vfio_pci_device.allocate_address(resources).is_err() {
+ let endpoint_addr = vfio_pci_device.allocate_address(resources);
+ if endpoint_addr.is_err() {
warn!(
"address reservation failed for vfio {}",
vfio_pci_device.debug_label()
);
}
+ if iommu_enabled {
+ endpoints.insert(endpoint_addr.unwrap().to_u32(), vfio_container);
+ }
+
Ok((vfio_pci_device, simple_jail(cfg, "vfio_device")?))
}
@@ -1787,6 +1525,7 @@
vm: &mut impl Vm,
resources: &mut SystemAllocator,
exit_evt: &Event,
+ phys_max_addr: u64,
control_tubes: &mut Vec<TaggedControlTube>,
wayland_device_tube: Tube,
gpu_device_tube: Tube,
@@ -1837,10 +1576,43 @@
pci_devices.push((usb_controller, simple_jail(&cfg, "xhci")?));
}
- for vfio_path in &cfg.vfio {
- let (vfio_pci_device, jail) =
- create_vfio_device(cfg, vm, resources, control_tubes, vfio_path.as_path())?;
- pci_devices.push((vfio_pci_device, jail));
+ if !cfg.vfio.is_empty() {
+ let kvm_vfio_file = vm
+ .create_device(DeviceKind::Vfio)
+ .map_err(Error::CreateVfioKvmDevice)?;
+
+ let mut iommu_attached_endpoints: BTreeMap<u32, Arc<Mutex<VfioContainer>>> =
+ BTreeMap::new();
+
+ for (vfio_path, enable_iommu) in cfg.vfio.iter() {
+ let (vfio_pci_device, jail) = create_vfio_device(
+ cfg,
+ vm,
+ resources,
+ control_tubes,
+ vfio_path.as_path(),
+ &kvm_vfio_file,
+ &mut iommu_attached_endpoints,
+ *enable_iommu,
+ )?;
+
+ pci_devices.push((vfio_pci_device, jail));
+ }
+
+ if !iommu_attached_endpoints.is_empty() {
+ let iommu_dev = create_iommu_device(cfg, phys_max_addr, iommu_attached_endpoints)?;
+
+ let (msi_host_tube, msi_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
+ control_tubes.push(TaggedControlTube::VmIrq(msi_host_tube));
+ let mut dev =
+ VirtioPciDevice::new(vm.get_memory().clone(), iommu_dev.dev, msi_device_tube)
+ .map_err(Error::VirtioPciDev)?;
+ // early reservation for viommu.
+ dev.allocate_address(resources)
+ .map_err(|_| Error::VirtioPciDev(base::Error::new(EINVAL)))?;
+ let dev = Box::new(dev);
+ pci_devices.push((dev, iommu_dev.jail));
+ }
}
Ok(pci_devices)
@@ -1887,32 +1659,16 @@
})
}
-/// If the given path is of the form /proc/self/fd/N for some N, returns `Ok(Some(N))`. Otherwise
-/// returns `Ok(None`).
-fn raw_descriptor_from_path(path: &Path) -> Result<Option<RawDescriptor>> {
- 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::<c_int>().ok())
- .ok_or(Error::InvalidFdPath)?;
- validate_raw_descriptor(raw_descriptor)
- .map_err(Error::ValidateRawDescriptor)
- .map(Some)
- } else {
- Ok(None)
- }
-}
-
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(raw_descriptor) = raw_descriptor_from_path(self)? {
- // Safe because we will validate |raw_fd|.
- unsafe { Ok(UnixStream::from_raw_fd(raw_descriptor)) }
+ if let Some(fd) =
+ safe_descriptor_from_path(self).map_err(|e| Error::InputEventsOpen(e.into()))?
+ {
+ Ok(fd.into())
} else {
UnixStream::connect(self).map_err(Error::InputEventsOpen)
}
@@ -2115,6 +1871,7 @@
vcpu_count: usize,
run_rt: bool,
vcpu_affinity: Vec<usize>,
+ delay_rt: bool,
no_smt: bool,
start_barrier: Arc<Barrier>,
has_bios: bool,
@@ -2146,7 +1903,7 @@
vm,
irq_chip.as_mut(),
vcpu_count,
- run_rt,
+ run_rt && !delay_rt,
vcpu_affinity,
no_smt,
has_bios,
@@ -2247,6 +2004,21 @@
}
}
}
+ 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);
+ }
+ }
+ }
}
}
}
@@ -2274,12 +2046,12 @@
Ok(VcpuExit::IoIn { port, mut size }) => {
let mut data = [0; 8];
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();
}
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);
+ error!("failed to set return data for IoIn at port {:#x}: {}", port, e);
}
}
Ok(VcpuExit::IoOut {
@@ -2288,7 +2060,7 @@
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();
}
io_bus.write(port as u64, &data[..size]);
@@ -2375,27 +2147,53 @@
fn setup_vm_components(cfg: &Config) -> Result<VmComponents> {
let initrd_image = if let Some(initrd_path) = &cfg.initrd_path {
- Some(open_file(initrd_path, true, Error::OpenInitrd)?)
+ Some(
+ open_file(
+ initrd_path,
+ true, /*read_only*/
+ false, /*O_DIRECT*/
+ )
+ .map_err(|e| Error::OpenInitrd(initrd_path.to_owned(), e.into()))?,
+ )
} else {
None
};
let vm_image = match cfg.executable_path {
- Some(Executable::Kernel(ref kernel_path)) => {
- VmImage::Kernel(open_file(kernel_path, true, Error::OpenKernel)?)
- }
- Some(Executable::Bios(ref bios_path)) => {
- VmImage::Bios(open_file(bios_path, true, Error::OpenBios)?)
- }
+ Some(Executable::Kernel(ref kernel_path)) => VmImage::Kernel(
+ open_file(
+ kernel_path,
+ true, /*read_only*/
+ false, /*O_DIRECT*/
+ )
+ .map_err(|e| Error::OpenKernel(kernel_path.to_owned(), e.into()))?,
+ ),
+ Some(Executable::Bios(ref bios_path)) => VmImage::Bios(
+ open_file(bios_path, true /*read_only*/, false /*O_DIRECT*/)
+ .map_err(|e| Error::OpenBios(bios_path.to_owned(), e.into()))?,
+ ),
_ => 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(Error::SwiotlbTooLarge)?,
+ )
+ } else {
+ match cfg.protected_vm {
+ ProtectionType::Protected => Some(64 * 1024 * 1024),
+ ProtectionType::Unprotected => None,
+ }
+ };
+
Ok(VmComponents {
memory_size: cfg
.memory
.unwrap_or(256)
.checked_mul(1024 * 1024)
.ok_or(Error::MemoryTooLarge)?,
+ swiotlb,
vcpu_count: cfg.vcpu_count.unwrap_or(1),
vcpu_affinity: cfg.vcpu_affinity.clone(),
cpu_clusters: cfg.cpu_clusters.clone(),
@@ -2411,13 +2209,13 @@
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(),
+ delay_rt: cfg.delay_rt,
protected_vm: cfg.protected_vm,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
gdb: None,
@@ -2616,11 +2414,13 @@
let exit_evt = Event::new().map_err(Error::CreateEvent)?;
let mut sys_allocator = Arch::create_system_allocator(vm.get_memory());
- let pci_devices = create_devices(
+ let phys_max_addr = Arch::get_phys_max_addr();
+ let mut pci_devices = create_devices(
&cfg,
&mut vm,
&mut sys_allocator,
&exit_evt,
+ phys_max_addr,
&mut control_tubes,
wayland_device_tube,
gpu_device_tube,
@@ -2633,6 +2433,18 @@
Arc::clone(&map_request),
)?;
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ for (device, _jail) in pci_devices.iter_mut() {
+ let sdts = device
+ .generate_acpi(components.acpi_sdts)
+ .or_else(|| {
+ error!("ACPI table generation error");
+ None
+ })
+ .ok_or(Error::GenerateAcpi)?;
+ components.acpi_sdts = sdts;
+ }
+
#[cfg_attr(not(feature = "direct"), allow(unused_mut))]
let mut linux = Arch::build_vm::<V, Vcpu>(
components,
@@ -2828,6 +2640,7 @@
linux.vcpu_count,
linux.rt_cpus.contains(&cpu_id),
vcpu_affinity,
+ linux.delay_rt,
linux.no_smt,
vcpu_thread_barrier.clone(),
linux.has_bios,
@@ -2863,6 +2676,8 @@
vcpu_thread_barrier.wait();
+ let mut balloon_stats_id: u64 = 0;
+
'wait: loop {
let events = {
match wait_ctx.wait() {
@@ -2941,12 +2756,14 @@
let response = request.execute(
&mut run_mode_opt,
&balloon_host_tube,
+ &mut balloon_stats_id,
disk_host_tubes,
#[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);
@@ -2959,7 +2776,9 @@
}
other => {
if other == VmRunMode::Running {
- linux.io_bus.notify_resume();
+ for dev in &linux.resume_notify_devices {
+ dev.lock().resume_imminent();
+ }
}
kick_all_vcpus(
&vcpu_handles,
@@ -3084,25 +2903,17 @@
}
}
- for event in events.iter().filter(|e| e.is_hungup) {
- match event.token {
- Token::Exit => {}
- Token::Suspend => {}
- Token::ChildSignal => {}
- Token::IrqFd { index: _ } => {}
- 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);
- }
- }
+ // 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);
}
}
diff --git a/src/main.rs b/src/main.rs
index ada5715..1b9467b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -36,6 +36,10 @@
#[cfg(feature = "audio")]
use devices::{Ac97Backend, Ac97Parameters};
use disk::QcowFile;
+#[cfg(feature = "composite-disk")]
+use disk::{
+ create_composite_disk, create_disk_file, create_zero_filler, ImagePartitionType, PartitionInfo,
+};
use vm_control::{
client::{
do_modify_battery, do_usb_attach, do_usb_detach, do_usb_list, handle_request, vms_request,
@@ -529,6 +533,7 @@
argument::Error::Syntax(format!("invalid capture option: {}", e))
})?;
}
+ #[cfg(feature = "audio_cras")]
"client_type" => {
ac97_params
.set_client_type(v)
@@ -969,6 +974,9 @@
}
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(
@@ -986,6 +994,24 @@
})?,
)
}
+ #[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;
}
@@ -1001,6 +1027,11 @@
}
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;
@@ -1099,6 +1130,7 @@
let mut disk = DiskOption {
path: disk_path,
read_only,
+ o_direct: false,
sparse: true,
block_size: 512,
id: None,
@@ -1123,6 +1155,14 @@
})?;
disk.sparse = sparse;
}
+ "o_direct" => {
+ let o_direct =
+ value.parse().map_err(|_| argument::Error::InvalidValue {
+ value: value.to_owned(),
+ expected: String::from("`o_direct` must be a boolean"),
+ })?;
+ disk.o_direct = o_direct;
+ }
"block_size" => {
let block_size =
value.parse().map_err(|_| argument::Error::InvalidValue {
@@ -1171,6 +1211,7 @@
path: disk_path,
read_only: !name.starts_with("rw"),
sparse: false,
+ o_direct: false,
block_size: base::pagesize() as u32,
id: None,
});
@@ -1333,7 +1374,7 @@
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(
@@ -1695,7 +1736,12 @@
cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned())));
}
"vfio" => {
- let vfio_path = PathBuf::from(value.unwrap());
+ let mut param = value.unwrap().split(',');
+ let vfio_path =
+ PathBuf::from(param.next().ok_or_else(|| argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("missing vfio path"),
+ })?);
if !vfio_path.exists() {
return Err(argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
@@ -1709,7 +1755,33 @@
});
}
- cfg.vfio.push(vfio_path);
+ let mut enable_iommu = false;
+ if let Some(p) = param.next() {
+ let mut kv = p.splitn(2, '=');
+ if let (Some(kind), Some(value)) = (kv.next(), kv.next()) {
+ match kind {
+ "iommu" => (),
+ _ => {
+ return Err(argument::Error::InvalidValue {
+ value: p.to_owned(),
+ expected: String::from("option must be `iommu=on|off`"),
+ })
+ }
+ }
+ match value {
+ "on" => enable_iommu = true,
+ "off" => (),
+ _ => {
+ return Err(argument::Error::InvalidValue {
+ value: p.to_owned(),
+ expected: String::from("option must be `iommu=on|off`"),
+ })
+ }
+ }
+ };
+ }
+
+ cfg.vfio.insert(vfio_path, enable_iommu);
}
"video-decoder" => {
cfg.video_dec = true;
@@ -1736,7 +1808,6 @@
}
"protected-vm" => {
cfg.protected_vm = ProtectionType::Protected;
- cfg.params.push("swiotlb=force".to_string());
}
"battery" => {
let params = parse_battery_options(value)?;
@@ -1771,6 +1842,11 @@
"vhost-user-console" => cfg.vhost_user_console.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()),
}),
@@ -1960,6 +2036,7 @@
Argument::value("cpu-capacity", "CPU=CAP[,CPU=CAP[,...]]", "Set the relative capacity of the given CPU (default: no capacity)"),
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",
@@ -1977,7 +2054,8 @@
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."),
@@ -1993,33 +2071,35 @@
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.
+ 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",
@@ -2028,14 +2108,14 @@
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]",
"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 - 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.
"),
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."),
@@ -2059,25 +2139,23 @@
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"),
#[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.
- "),
+ 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(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"),
@@ -2090,23 +2168,26 @@
#[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[,iommu=on|off]", "Path to sysfs of pass through or mdev device.
+iommu=on|off - indicates whether to enable virtio IOMMU for this device"),
#[cfg(feature = "video-decoder")]
Argument::flag("video-decoder", "(EXPERIMENTAL) enable virtio-video decoder device"),
#[cfg(feature = "video-encoder")]
Argument::flag("video-encoder", "(EXPERIMENTAL) enable virtio-video encoder device"),
Argument::value("acpi-table", "PATH", "Path to user provided ACPI table"),
Argument::flag("protected-vm", "(EXPERIMENTAL) prevent host access to guest memory"),
+ #[cfg(target_arch = "aarch64")]
+ Argument::value("swiotlb", "N", "(EXPERIMENTAL) Size of virtio swiotlb buffer in MiB (default: 64 if `--protected-vm` 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::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-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"),
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",
@@ -2195,6 +2276,17 @@
vms_request(&VmRequest::Resume, 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 balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
if args.len() < 2 {
print_help("crosvm balloon", "SIZE VM_SOCKET...", &[]);
@@ -2239,6 +2331,108 @@
}
}
+#[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)
+ .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(())
+}
+
fn create_qcow2(args: std::env::Args) -> std::result::Result<(), ()> {
let arguments = [
Argument::positional("PATH", "where to create the qcow2 image"),
@@ -2459,6 +2653,8 @@
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!(" disk - Manage attached virtual disk devices.");
println!(" resume - Resumes the crosvm instance.");
@@ -2523,9 +2719,12 @@
Some("stop") => stop_vms(args),
Some("suspend") => suspend_vms(args),
Some("resume") => resume_vms(args),
+ Some("make_rt") => make_rt(args),
Some("run") => run_vm(args),
Some("balloon") => balloon_vms(args),
Some("balloon_stats") => balloon_stats(args),
+ #[cfg(feature = "composite-disk")]
+ Some("create_composite") => create_composite(args),
Some("create_qcow2") => create_qcow2(args),
Some("disk") => disk_cmd(args),
Some("usb") => modify_usb(args),
@@ -2654,7 +2853,7 @@
);
}
- #[cfg(feature = "audio")]
+ #[cfg(feature = "audio_cras")]
#[test]
fn parse_ac97_vaild() {
parse_ac97_options("backend=cras").expect("parse should have succeded");
@@ -2666,13 +2865,13 @@
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")
diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs
index f9444f8..28f8ac5 100644
--- a/src/plugin/mod.rs
+++ b/src/plugin/mod.rs
@@ -477,12 +477,18 @@
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 {
@@ -491,7 +497,7 @@
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);
diff --git a/src/plugin/process.rs b/src/plugin/process.rs
index c34b708..833616e 100644
--- a/src/plugin/process.rs
+++ b/src/plugin/process.rs
@@ -521,7 +521,7 @@
return Err(Error::PluginSocketHup);
}
- let request = protobuf::parse_from_bytes::<MainRequest>(&self.request_buffer[..msg_size])
+ let request: MainRequest = Message::parse_from_bytes(&self.request_buffer[..msg_size])
.map_err(Error::DecodeRequest)?;
/// Use this to make it easier to stuff various kinds of File-like objects into the
diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs
index d874f64..c5c93f7 100644
--- a/src/plugin/vcpu.rs
+++ b/src/plugin/vcpu.rs
@@ -13,7 +13,7 @@
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 @@
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::CodedOutputStream;
use protos::plugin::*;
use sync::Mutex;
@@ -592,9 +591,8 @@
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 {
diff --git a/sync/Android.bp b/sync/Android.bp
index 176f78e..97414ee 100644
--- a/sync/Android.bp
+++ b/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 {
@@ -21,7 +21,7 @@
}
rust_defaults {
- name: "sync_defaults",
+ name: "sync_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "sync",
srcs: ["src/lib.rs"],
@@ -32,7 +32,7 @@
rust_test_host {
name: "sync_host_test_src_lib",
- defaults: ["sync_defaults"],
+ defaults: ["sync_test_defaults"],
test_options: {
unit_test: true,
},
@@ -40,5 +40,5 @@
rust_test {
name: "sync_device_test_src_lib",
- defaults: ["sync_defaults"],
+ defaults: ["sync_test_defaults"],
}
diff --git a/sys_util/Android.bp b/sys_util/Android.bp
index ba98ac2..18d4048 100644
--- a/sys_util/Android.bp
+++ b/sys_util/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --no-subdir.
-// Manually added target and shared_libs to rules below.
+// 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
@@ -49,61 +49,3 @@
},
},
}
-
-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",
- "libthiserror",
- ],
- 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.97 "default,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/sys_util/cargo2android.json b/sys_util/cargo2android.json
new file mode 100644
index 0000000..02b80cc
--- /dev/null
+++ b/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
+}
\ No newline at end of file
diff --git a/sys_util/cargo2android_target.bp b/sys_util/cargo2android_target.bp
new file mode 100644
index 0000000..7e4db94
--- /dev/null
+++ b/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/sys_util/poll_token_derive/Android.bp b/sys_util/poll_token_derive/Android.bp
index e533b91..cc2cd01 100644
--- a/sys_util/poll_token_derive/Android.bp
+++ b/sys_util/poll_token_derive/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually disabled tests which depend on host on crate.
+// 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
@@ -22,37 +22,3 @@
"libsyn",
],
}
-
-rust_defaults {
- name: "poll_token_derive_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "poll_token_derive",
- srcs: ["poll_token_derive.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libproc_macro2",
- "libquote",
- "libsyn",
- ],
-}
-
-//rust_test_host {
-// name: "poll_token_derive_host_test_poll_token_derive",
-// defaults: ["poll_token_derive_defaults"],
-// test_options: {
-// unit_test: true,
-// },
-//}
-
-//rust_test {
-// name: "poll_token_derive_device_test_poll_token_derive",
-// defaults: ["poll_token_derive_defaults"],
-//}
-
-// dependent_library ["feature_list"]
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// syn-1.0.73 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.2 "default"
diff --git a/sys_util/poll_token_derive/cargo2android.json b/sys_util/poll_token_derive/cargo2android.json
new file mode 100644
index 0000000..9a6eee3
--- /dev/null
+++ b/sys_util/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/sys_util/src/descriptor.rs b/sys_util/src/descriptor.rs
index 4bccc58..7ea4dfb 100644
--- a/sys_util/src/descriptor.rs
+++ b/sys_util/src/descriptor.rs
@@ -161,6 +161,13 @@
}
}
+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()) }
+ }
+}
+
/// 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
diff --git a/sys_util/src/lib.rs b/sys_util/src/lib.rs
index e59b9cf..3313265 100644
--- a/sys_util/src/lib.rs
+++ b/sys_util/src/lib.rs
@@ -94,22 +94,25 @@
use std::cell::Cell;
use std::ffi::CStr;
-use std::fs::{remove_file, File};
+use std::fs::{remove_file, File, OpenOptions};
use std::mem;
+use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::net::UnixDatagram;
+use std::path::Path;
use std::ptr;
use std::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>>;
@@ -193,6 +196,20 @@
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,
@@ -465,8 +482,49 @@
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 {
+ let mut options = OpenOptions::new();
+ if o_direct {
+ options.custom_flags(O_DIRECT);
+ }
+ options.write(!read_only).read(true).open(path)?
+ })
+}
+
#[cfg(test)]
mod tests {
+ use libc::EBADF;
use std::io::Write;
use super::*;
@@ -481,4 +539,35 @@
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/sys_util/src/sched.rs b/sys_util/src/sched.rs
index d9972c6..2a44ab8 100644
--- a/sys_util/src/sched.rs
+++ b/sys_util/src/sched.rs
@@ -72,7 +72,7 @@
const PR_SCHED_CORE: i32 = 62;
const PR_SCHED_CORE_CREATE: i32 = 1;
- #[allow(non_camel_case_types, dead_code)]
+ #[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.
diff --git a/sys_util/src/vsock.rs b/sys_util/src/vsock.rs
index a51dfdb..b690fb7 100644
--- a/sys_util/src/vsock.rs
+++ b/sys_util/src/vsock.rs
@@ -53,7 +53,7 @@
}
/// 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 +113,7 @@
}
/// 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/tempfile/Android.bp b/tempfile/Android.bp
index 578922c..d089af2 100644
--- a/tempfile/Android.bp
+++ b/tempfile/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 {
@@ -23,7 +23,7 @@
}
rust_defaults {
- name: "tempfile_defaults",
+ name: "tempfile_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "tempfile",
srcs: ["src/lib.rs"],
@@ -37,7 +37,7 @@
rust_test_host {
name: "tempfile_host_test_src_lib",
- defaults: ["tempfile_defaults"],
+ defaults: ["tempfile_test_defaults"],
test_options: {
unit_test: true,
},
@@ -45,8 +45,5 @@
rust_test {
name: "tempfile_device_test_src_lib",
- defaults: ["tempfile_defaults"],
+ defaults: ["tempfile_test_defaults"],
}
-
-// dependent_library ["feature_list"]
-// libc-0.2.97 "default,std"
diff --git a/tests/plugins.rs b/tests/plugins.rs
index e7c872e..8992045 100644
--- a/tests/plugins.rs
+++ b/tests/plugins.rs
@@ -10,11 +10,11 @@
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;
struct RemovePath(PathBuf);
@@ -50,10 +50,13 @@
}
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)
diff --git a/tpm2-sys/Android.bp b/tpm2-sys/Android.bp
index f8d68d3..25e7b3e 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/cargo2android.json b/tpm2-sys/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /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
index 6ab308b..0000000
--- a/tpm2-sys/libtpm2
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 6ab308b4578233be5c65d3a334be48419ea677a9
diff --git a/tpm2/Android.bp b/tpm2/Android.bp
index f8d68d3..25e7b3e 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/cargo2android.json b/tpm2/cargo2android.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/tpm2/cargo2android.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/usb_sys/Android.bp b/usb_sys/Android.bp
index 502f4b3..5c99d31 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 {
@@ -23,7 +23,7 @@
}
rust_defaults {
- name: "usb_sys_defaults",
+ name: "usb_sys_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "usb_sys",
srcs: ["src/lib.rs"],
@@ -37,7 +37,7 @@
rust_test_host {
name: "usb_sys_host_test_src_lib",
- defaults: ["usb_sys_defaults"],
+ defaults: ["usb_sys_test_defaults"],
test_options: {
unit_test: true,
},
@@ -45,45 +45,5 @@
rust_test {
name: "usb_sys_device_test_src_lib",
- defaults: ["usb_sys_defaults"],
+ defaults: ["usb_sys_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/usb_util/Android.bp b/usb_util/Android.bp
index 250d7c2..84bad4e 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 {
@@ -28,7 +28,7 @@
}
rust_defaults {
- name: "usb_util_defaults",
+ name: "usb_util_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "usb_util",
srcs: ["src/lib.rs"],
@@ -47,7 +47,7 @@
rust_test_host {
name: "usb_util_host_test_src_lib",
- defaults: ["usb_util_defaults"],
+ defaults: ["usb_util_test_defaults"],
test_options: {
unit_test: true,
},
@@ -55,47 +55,5 @@
rust_test {
name: "usb_util_device_test_src_lib",
- defaults: ["usb_util_defaults"],
+ defaults: ["usb_util_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// remain-0.2.2
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/usb_util/src/descriptor.rs b/usb_util/src/descriptor.rs
index 88bdbc0..09da4b7 100644
--- a/usb_util/src/descriptor.rs
+++ b/usb_util/src/descriptor.rs
@@ -148,6 +148,13 @@
*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);
+ }
// Skip this entire descriptor, since it's not the right type.
*offset += hdr.bLength as usize;
}
@@ -173,43 +180,44 @@
interface_descriptors: BTreeMap::new(),
};
- for intf_idx in 0..config_descriptor.bNumInterfaces {
- if 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(),
- };
+ 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;
- }
+ 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);
}
}
@@ -542,4 +550,109 @@
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);
+ }
}
diff --git a/vfio_sys/Android.bp b/vfio_sys/Android.bp
index def2958..7d829bb 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 {
@@ -23,7 +23,7 @@
}
rust_defaults {
- name: "vfio_sys_defaults",
+ name: "vfio_sys_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "vfio_sys",
srcs: ["src/lib.rs"],
@@ -37,7 +37,7 @@
rust_test_host {
name: "vfio_sys_host_test_src_lib",
- defaults: ["vfio_sys_defaults"],
+ defaults: ["vfio_sys_test_defaults"],
test_options: {
unit_test: true,
},
@@ -45,45 +45,5 @@
rust_test {
name: "vfio_sys_device_test_src_lib",
- defaults: ["vfio_sys_defaults"],
+ defaults: ["vfio_sys_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/vfio_sys/src/vfio.rs b/vfio_sys/src/vfio.rs
index 622b5db..315ba50 100644
--- a/vfio_sys/src/vfio.rs
+++ b/vfio_sys/src/vfio.rs
@@ -91,12 +91,12 @@
pub const VFIO_REGION_INFO_FLAG_CAPS: u32 = 8;
pub const VFIO_REGION_INFO_CAP_SPARSE_MMAP: u32 = 1;
pub const VFIO_REGION_INFO_CAP_TYPE: u32 = 2;
+pub const VFIO_REGION_INFO_CAP_MSIX_MAPPABLE: u32 = 3;
pub const VFIO_REGION_TYPE_PCI_VENDOR_TYPE: u32 = 2147483648;
pub const VFIO_REGION_TYPE_PCI_VENDOR_MASK: u32 = 65535;
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_INFO_CAP_MSIX_MAPPABLE: u32 = 3;
pub const VFIO_IRQ_INFO_EVENTFD: u32 = 1;
pub const VFIO_IRQ_INFO_MASKABLE: u32 = 2;
pub const VFIO_IRQ_INFO_AUTOMASKED: u32 = 4;
diff --git a/vhost/Android.bp b/vhost/Android.bp
index c359eda..1af4254 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 {
@@ -28,7 +28,7 @@
}
rust_defaults {
- name: "vhost_defaults",
+ name: "vhost_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "vhost",
srcs: ["src/lib.rs"],
@@ -47,7 +47,7 @@
rust_test_host {
name: "vhost_host_test_src_lib",
- defaults: ["vhost_defaults"],
+ defaults: ["vhost_test_defaults"],
test_options: {
unit_test: true,
},
@@ -55,50 +55,5 @@
rust_test {
name: "vhost_device_test_src_lib",
- defaults: ["vhost_defaults"],
+ defaults: ["vhost_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/vhost_user_devices/Android.bp b/vhost_user_devices/Android.bp
index c492cab..9491e84 100644
--- a/vhost_user_devices/Android.bp
+++ b/vhost_user_devices/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --verbose.
-// Manually removed unused 'vhost_user_wl_device' related rules.
+// 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
@@ -13,22 +13,89 @@
rust_library {
name: "libvhost_user_devices",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "vhost_user_devices",
srcs: ["src/lib.rs"],
edition: "2018",
rustlibs: [
"libanyhow",
+ "libarch",
"libbase_rust",
"libcros_async",
+ "libdata_model",
"libdevices",
+ "libdisk",
"libfutures",
+ "libgetopts",
"liblibc",
+ "libnet_util",
"libonce_cell",
"libsync_rust",
"libsys_util",
"libthiserror",
+ "libvirtio_sys",
+ "libvm_memory",
+ "libvmm_vhost",
+ ],
+ proc_macros: ["libremain"],
+}
+
+rust_binary {
+ name: "vhost_user_block_device",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "vhost_user_block_device",
+ srcs: ["src/block.rs"],
+ edition: "2018",
+ rustlibs: [
+ "libanyhow",
+ "libarch",
+ "libbase_rust",
+ "libcros_async",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libfutures",
+ "libgetopts",
+ "liblibc",
+ "libnet_util",
+ "libonce_cell",
+ "libsync_rust",
+ "libsys_util",
+ "libthiserror",
+ "libvhost_user_devices",
+ "libvirtio_sys",
+ "libvm_memory",
+ "libvmm_vhost",
+ ],
+ proc_macros: ["libremain"],
+}
+
+rust_binary {
+ name: "vhost_user_console_device",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "vhost_user_console_device",
+ srcs: ["src/console.rs"],
+ edition: "2018",
+ rustlibs: [
+ "libanyhow",
+ "libarch",
+ "libbase_rust",
+ "libcros_async",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libfutures",
+ "libgetopts",
+ "liblibc",
+ "libnet_util",
+ "libonce_cell",
+ "libsync_rust",
+ "libsys_util",
+ "libthiserror",
+ "libvhost_user_devices",
+ "libvirtio_sys",
"libvm_memory",
"libvmm_vhost",
],
@@ -36,27 +103,123 @@
}
rust_defaults {
- name: "vhost_user_devices_defaults",
+ name: "vhost_user_devices_test_defaults",
+ defaults: ["crosvm_defaults"],
+ crate_name: "vhost_user_block_device",
+ srcs: ["src/block.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
+ rustlibs: [
+ "libanyhow",
+ "libarch",
+ "libbase_rust",
+ "libcros_async",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libfutures",
+ "libgetopts",
+ "liblibc",
+ "libnet_util",
+ "libonce_cell",
+ "libsync_rust",
+ "libsys_util",
+ "libtempfile",
+ "libthiserror",
+ "libvhost_user_devices",
+ "libvirtio_sys",
+ "libvm_memory",
+ "libvmm_vhost",
+ ],
+ proc_macros: ["libremain"],
+}
+
+rust_test_host {
+ name: "vhost_user_devices_host_test_src_block",
+ defaults: ["vhost_user_devices_test_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "vhost_user_devices_device_test_src_block",
+ defaults: ["vhost_user_devices_test_defaults"],
+}
+
+rust_defaults {
+ name: "vhost_user_devices_test_defaults_vhost_user_console_device",
+ defaults: ["crosvm_defaults"],
+ crate_name: "vhost_user_console_device",
+ srcs: ["src/console.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
+ rustlibs: [
+ "libanyhow",
+ "libarch",
+ "libbase_rust",
+ "libcros_async",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libfutures",
+ "libgetopts",
+ "liblibc",
+ "libnet_util",
+ "libonce_cell",
+ "libsync_rust",
+ "libsys_util",
+ "libtempfile",
+ "libthiserror",
+ "libvhost_user_devices",
+ "libvirtio_sys",
+ "libvm_memory",
+ "libvmm_vhost",
+ ],
+ proc_macros: ["libremain"],
+}
+
+rust_test_host {
+ name: "vhost_user_devices_host_test_src_console",
+ defaults: ["vhost_user_devices_test_defaults_vhost_user_console_device"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "vhost_user_devices_device_test_src_console",
+ defaults: ["vhost_user_devices_test_defaults_vhost_user_console_device"],
+}
+
+rust_defaults {
+ name: "vhost_user_devices_test_defaults_vhost_user_devices",
defaults: ["crosvm_defaults"],
crate_name: "vhost_user_devices",
- // has rustc warnings
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
edition: "2018",
rustlibs: [
"libanyhow",
+ "libarch",
"libbase_rust",
"libcros_async",
"libdata_model",
"libdevices",
+ "libdisk",
"libfutures",
+ "libgetopts",
"liblibc",
+ "libnet_util",
"libonce_cell",
"libsync_rust",
"libsys_util",
"libtempfile",
"libthiserror",
+ "libvirtio_sys",
"libvm_memory",
"libvmm_vhost",
],
@@ -65,7 +228,7 @@
rust_test_host {
name: "vhost_user_devices_host_test_src_lib",
- defaults: ["vhost_user_devices_defaults"],
+ defaults: ["vhost_user_devices_test_defaults_vhost_user_devices"],
test_options: {
unit_test: true,
},
@@ -73,97 +236,159 @@
rust_test {
name: "vhost_user_devices_device_test_src_lib",
- defaults: ["vhost_user_devices_defaults"],
+ defaults: ["vhost_user_devices_test_defaults_vhost_user_devices"],
}
-// 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
-// ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master,vhost-user-slave"
-// ../../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
-// ../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
-// ../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
-// anyhow-1.0.41 "default,std"
-// 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
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.15 "alloc,std"
-// futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-// futures-core-0.3.15 "alloc,std"
-// futures-io-0.3.15 "std"
-// futures-sink-0.3.15 "alloc,std"
-// futures-task-0.3.15 "alloc,std"
-// futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-// getopts-0.2.21
-// getrandom-0.2.3 "std"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// log-0.4.14
-// memchr-2.4.0 "default,std"
-// memoffset-0.5.6 "default"
-// once_cell-1.8.0 "alloc,default,race,std"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// protobuf-2.24.1
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.1 "std"
-// rand_core-0.6.3 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-width-0.1.8 "default"
-// unicode-xid-0.2.2 "default"
+rust_defaults {
+ name: "vhost_user_devices_test_defaults_vhost_user_net_device",
+ defaults: ["crosvm_defaults"],
+ crate_name: "vhost_user_net_device",
+ srcs: ["src/net.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
+ rustlibs: [
+ "libanyhow",
+ "libarch",
+ "libbase_rust",
+ "libcros_async",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libfutures",
+ "libgetopts",
+ "liblibc",
+ "libnet_util",
+ "libonce_cell",
+ "libsync_rust",
+ "libsys_util",
+ "libtempfile",
+ "libthiserror",
+ "libvhost_user_devices",
+ "libvirtio_sys",
+ "libvm_memory",
+ "libvmm_vhost",
+ ],
+ proc_macros: ["libremain"],
+}
+
+rust_test_host {
+ name: "vhost_user_devices_host_test_src_net",
+ defaults: ["vhost_user_devices_test_defaults_vhost_user_net_device"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "vhost_user_devices_device_test_src_net",
+ defaults: ["vhost_user_devices_test_defaults_vhost_user_net_device"],
+}
+
+rust_defaults {
+ name: "vhost_user_devices_test_defaults_vhost_user_wl_device",
+ defaults: ["crosvm_defaults"],
+ crate_name: "vhost_user_wl_device",
+ srcs: ["src/wl.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ edition: "2018",
+ rustlibs: [
+ "libanyhow",
+ "libarch",
+ "libbase_rust",
+ "libcros_async",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libfutures",
+ "libgetopts",
+ "liblibc",
+ "libnet_util",
+ "libonce_cell",
+ "libsync_rust",
+ "libsys_util",
+ "libtempfile",
+ "libthiserror",
+ "libvhost_user_devices",
+ "libvirtio_sys",
+ "libvm_memory",
+ "libvmm_vhost",
+ ],
+ proc_macros: ["libremain"],
+}
+
+rust_test_host {
+ name: "vhost_user_devices_host_test_src_wl",
+ defaults: ["vhost_user_devices_test_defaults_vhost_user_wl_device"],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+rust_test {
+ name: "vhost_user_devices_device_test_src_wl",
+ defaults: ["vhost_user_devices_test_defaults_vhost_user_wl_device"],
+}
+
+rust_binary {
+ name: "vhost_user_net_device",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "vhost_user_net_device",
+ srcs: ["src/net.rs"],
+ edition: "2018",
+ rustlibs: [
+ "libanyhow",
+ "libarch",
+ "libbase_rust",
+ "libcros_async",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libfutures",
+ "libgetopts",
+ "liblibc",
+ "libnet_util",
+ "libonce_cell",
+ "libsync_rust",
+ "libsys_util",
+ "libthiserror",
+ "libvhost_user_devices",
+ "libvirtio_sys",
+ "libvm_memory",
+ "libvmm_vhost",
+ ],
+ proc_macros: ["libremain"],
+}
+
+rust_binary {
+ name: "vhost_user_wl_device",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "vhost_user_wl_device",
+ srcs: ["src/wl.rs"],
+ edition: "2018",
+ rustlibs: [
+ "libanyhow",
+ "libarch",
+ "libbase_rust",
+ "libcros_async",
+ "libdata_model",
+ "libdevices",
+ "libdisk",
+ "libfutures",
+ "libgetopts",
+ "liblibc",
+ "libnet_util",
+ "libonce_cell",
+ "libsync_rust",
+ "libsys_util",
+ "libthiserror",
+ "libvhost_user_devices",
+ "libvirtio_sys",
+ "libvm_memory",
+ "libvmm_vhost",
+ ],
+ proc_macros: ["libremain"],
+}
diff --git a/vhost_user_devices/Cargo.toml b/vhost_user_devices/Cargo.toml
index b3ecc71..4c6c4a8 100644
--- a/vhost_user_devices/Cargo.toml
+++ b/vhost_user_devices/Cargo.toml
@@ -4,18 +4,16 @@
authors = ["The Chromium OS Authors"]
edition = "2018"
-[features]
-net = ["data_model", "net_util", "virtio_sys"]
-wl = ["devices/minigbm"]
-block = ["data_model", "disk"]
-
[lib]
path = "src/lib.rs"
[[bin]]
+name = "vhost-user-console-device"
+path = "src/console.rs"
+
+[[bin]]
name = "vhost-user-net-device"
path = "src/net.rs"
-required-features = ["net"]
[[bin]]
name = "vhost-user-wl-device"
@@ -24,24 +22,24 @@
[[bin]]
name = "vhost-user-block-device"
path = "src/block.rs"
-required-features = ["block"]
[dependencies]
anyhow = "*"
+arch = { path = "../arch" }
base = { path = "../base" }
cros_async = { path = "../cros_async" }
-data_model = { path = "../data_model", optional = true }
-devices = { path = "../devices" }
-disk = { path = "../disk", optional = true}
+data_model = { path = "../data_model" }
+devices = { path = "../devices", features = ["minigbm"] }
+disk = { path = "../disk" }
getopts = { version = "0.2" }
libc = "*"
-net_util = { path = "../net_util", optional = true }
+net_util = { path = "../net_util" }
once_cell = "1.7.2"
remain = "*"
sync = { path = "../sync" }
sys_util = { path = "../sys_util" }
thiserror = "*"
-virtio_sys = { path = "../virtio_sys", optional = true }
+virtio_sys = { path = "../virtio_sys" }
vm_memory = { path = "../vm_memory" }
vmm_vhost = { path = "../../rust/vmm_vhost", version = "*", features = ["vhost-user-slave"] }
diff --git a/vhost_user_devices/src/block.rs b/vhost_user_devices/src/block.rs
index 745931d..a732907 100644
--- a/vhost_user_devices/src/block.rs
+++ b/vhost_user_devices/src/block.rs
@@ -192,7 +192,7 @@
fn start_queue(
&mut self,
idx: usize,
- queue: virtio::Queue,
+ mut queue: virtio::Queue,
mem: GuestMemory,
call_evt: Arc<Mutex<CallEvent>>,
kick_evt: Event,
@@ -202,6 +202,9 @@
handle.abort();
}
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(self.acked_features);
+
// Safe because the executor is initialized in main() below.
let ex = BLOCK_EXECUTOR.get().expect("Executor not initialized");
diff --git a/vhost_user_devices/src/console.rs b/vhost_user_devices/src/console.rs
new file mode 100644
index 0000000..4275955
--- /dev/null
+++ b/vhost_user_devices/src/console.rs
@@ -0,0 +1,348 @@
+// 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::{self, stdin};
+use std::path::PathBuf;
+use std::sync::mpsc::Receiver;
+use std::sync::Arc;
+
+use devices::virtio::copy_config;
+
+use anyhow::{anyhow, bail, Context};
+use arch::serial::{SerialHardware, SerialParameters, SerialType};
+use base::{error, warn, Event, RawDescriptor, Terminal};
+use cros_async::{EventAsync, Executor};
+use data_model::DataInit;
+use devices::serial_device::SerialDevice;
+use devices::virtio;
+use devices::virtio::console::{
+ handle_input, process_transmit_queue, spawn_input_thread, virtio_console_config, ConsoleError,
+};
+use devices::ProtectionType;
+use futures::future::{AbortHandle, Abortable};
+use getopts::Options;
+use once_cell::sync::OnceCell;
+use sync::Mutex;
+use vhost_user_devices::{CallEvent, DeviceRequestHandler, VhostUserBackend};
+use vm_memory::GuestMemory;
+use vmm_vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+static CONSOLE_EXECUTOR: OnceCell<Executor> = OnceCell::new();
+
+async fn run_tx_queue(
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ call_evt: Arc<Mutex<CallEvent>>,
+ 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, &call_evt, &mut queue, &mut output);
+ }
+}
+
+async fn run_rx_queue(
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ call_evt: Arc<Mutex<CallEvent>>,
+ kick_evt: EventAsync,
+ in_channel: Receiver<Vec<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, &call_evt, &in_channel, &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;
+ }
+ }
+ Err(e) => {
+ error!("Failed to process rx queue: {}", e);
+ break;
+ }
+ }
+ }
+}
+
+struct ConsoleBackend {
+ input: Option<Box<dyn io::Read + Send>>,
+ output: Option<Box<dyn io::Write + Send>>,
+ avail_features: u64,
+ acked_features: u64,
+ acked_protocol_features: VhostUserProtocolFeatures,
+ workers: [Option<AbortHandle>; Self::MAX_QUEUE_NUM],
+}
+
+impl SerialDevice for ConsoleBackend {
+ fn new(
+ protected_vm: ProtectionType,
+ _evt: Event,
+ input: Option<Box<dyn io::Read + Send>>,
+ output: Option<Box<dyn io::Write + Send>>,
+ _keep_rds: Vec<RawDescriptor>,
+ ) -> ConsoleBackend {
+ let avail_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
+ | virtio::base_features(protected_vm)
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ ConsoleBackend {
+ input,
+ output,
+ avail_features,
+ 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.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
+ }
+
+ 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,
+ call_evt: Arc<Mutex<CallEvent>>,
+ 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);
+
+ // Safe because the executor is initialized in main() below.
+ let ex = CONSOLE_EXECUTOR.get().expect("Executor not initialized.");
+
+ 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 {
+ // 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
+ .input
+ .take()
+ .ok_or_else(|| anyhow!("input source unavailable"))?;
+ let in_channel = 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, ex)
+ .context("Failed to create EventAsync for in_avail_evt")?;
+
+ ex.spawn_local(Abortable::new(
+ run_rx_queue(
+ queue,
+ mem,
+ call_evt,
+ kick_evt,
+ in_channel,
+ 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
+ .output
+ .take()
+ .ok_or_else(|| anyhow!("no output available"))?;
+ ex.spawn_local(Abortable::new(
+ run_tx_queue(queue, mem, call_evt, 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();
+ }
+ }
+}
+
+fn run_console(params: &SerialParameters, socket: &String) -> anyhow::Result<()> {
+ // We need to pass an event as per Serial Device API but we don't really use it anyway.
+ let evt = Event::new()?;
+ // Same for keep_rds, we don't really use this.
+ let mut keep_rds = Vec::new();
+ let console = match params.create_serial_device::<ConsoleBackend>(
+ ProtectionType::Unprotected,
+ &evt,
+ &mut keep_rds,
+ ) {
+ Ok(c) => c,
+ Err(e) => bail!(e),
+ };
+
+ let handler = DeviceRequestHandler::new(console);
+ let ex = Executor::new().context("Failed to create executor")?;
+
+ let _ = CONSOLE_EXECUTOR.set(ex.clone());
+
+ if let Err(e) = ex.run_until(handler.run(socket, &ex)) {
+ bail!(e);
+ }
+ Ok(())
+}
+
+fn main() -> anyhow::Result<()> {
+ let mut args = std::env::args();
+ let mut opts = Options::new();
+ opts.optflag("h", "help", "print this help menu");
+ opts.optopt("", "socket", "path to a socket", "PATH");
+ opts.optopt("", "output-file", "path to a file", "OUTFILE");
+ opts.optopt("", "input-file", "path to a file", "INFILE");
+
+ let program_name = args.next().expect("empty args");
+
+ let matches = match opts.parse(args) {
+ Ok(m) => m,
+ Err(e) => {
+ eprintln!("{}", e);
+ eprintln!("{}", opts.short_usage(&program_name));
+ return Ok(());
+ }
+ };
+
+ if matches.opt_present("h") {
+ println!("{}", opts.usage(&program_name));
+ return Ok(());
+ }
+
+ if !matches.opt_present("socket") {
+ println!("Must specify the socket for the vhost user device.");
+ println!("{}", opts.usage(&program_name));
+ return Ok(());
+ }
+
+ // We can unwrap after `opt_str()` safely because we just checked for it being present.
+ let socket = matches.opt_str("socket").unwrap();
+
+ let output_file = matches.opt_str("output-file").map(PathBuf::from);
+ let input_file = matches.opt_str("input-file").map(PathBuf::from);
+
+ base::syslog::init().context("Failed to initialize syslog")?;
+
+ // 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 type_ = match 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: output_file,
+ input: input_file,
+ num: 1,
+ console: true,
+ earlycon: false,
+ // We do not support stdin-less mode
+ stdin: true,
+ };
+
+ if let Err(e) = run_console(¶ms, &socket) {
+ error!("Failed to run console device: {}", e);
+ }
+
+ // Restore terminal capabilities back to what they were before
+ stdin()
+ .set_canon_mode()
+ .context("Failed to restore canonical mode for terminal")?;
+
+ Ok(())
+}
diff --git a/vhost_user_devices/src/lib.rs b/vhost_user_devices/src/lib.rs
index 831112f..60eb6d6 100644
--- a/vhost_user_devices/src/lib.rs
+++ b/vhost_user_devices/src/lib.rs
@@ -46,12 +46,16 @@
// implementation (this is what our devices implement).
use std::convert::TryFrom;
+use std::fs::File;
use std::num::Wrapping;
-use std::os::unix::io::{AsRawFd, RawFd};
+use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::sync::Arc;
-use base::{error, Event, FromRawDescriptor, SafeDescriptor, SharedMemory, SharedMemoryUnix};
+use base::{
+ error, Event, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor, SharedMemory,
+ SharedMemoryUnix,
+};
use cros_async::{AsyncError, AsyncWrapper, Executor};
use devices::virtio::{Queue, SignalableInterrupt};
use remain::sorted;
@@ -60,12 +64,12 @@
use thiserror::Error as ThisError;
use vm_memory::{GuestAddress, GuestMemory, MemoryRegion};
use vmm_vhost::vhost_user::message::{
- VhostUserConfigFlags, VhostUserMemoryRegion, VhostUserProtocolFeatures,
+ VhostUserConfigFlags, VhostUserInflight, VhostUserMemoryRegion, VhostUserProtocolFeatures,
VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags,
VhostUserVringState,
};
use vmm_vhost::vhost_user::{
- Error as VhostError, Listener, Result as VhostResult, SlaveFsCacheReq, SlaveListener,
+ Error as VhostError, Listener, Result as VhostResult, SlaveListener,
VhostUserSlaveReqHandlerMut,
};
@@ -202,15 +206,9 @@
/// Failed to handle a vhost-user request.
#[error("failed to handle a vhost-user request: {0}")]
HandleVhostUserRequest(VhostError),
- /// Invalid queue index is given.
- #[error("invalid queue index is given: {index}")]
- InvalidQueueIndex { index: usize },
/// Failed to wait for the handler socket to become readable.
#[error("failed to wait for the handler socket to become readable: {0}")]
WaitForHandler(AsyncError),
- /// Failed to wait for the listener socket to become readable.
- #[error("failed to wait for the listener socket to become readable: {0}")]
- WaitForListener(AsyncError),
}
type HandlerResult<T> = std::result::Result<T, HandlerError>;
@@ -343,26 +341,19 @@
fn set_mem_table(
&mut self,
contexts: &[VhostUserMemoryRegion],
- fds: &[RawFd],
+ files: Vec<File>,
) -> VhostResult<()> {
- if fds.len() != contexts.len() {
+ if files.len() != contexts.len() {
return Err(VhostError::InvalidParam);
}
- let mut regions = Vec::with_capacity(fds.len());
- for (region, &fd) in contexts.iter().zip(fds.iter()) {
- let rd = base::validate_raw_descriptor(fd).map_err(|e| {
- error!("invalid fd is given: {}", e);
- VhostError::InvalidParam
- })?;
- // Safe because we verified that we are the unique owner of `rd`.
- let sd = unsafe { SafeDescriptor::from_raw_descriptor(rd) };
-
+ let mut regions = Vec::with_capacity(files.len());
+ for (region, file) in contexts.iter().zip(files.into_iter()) {
let region = MemoryRegion::new(
region.memory_size,
GuestAddress(region.guest_phys_addr),
region.mmap_offset,
- Arc::new(SharedMemory::from_safe_descriptor(sd).unwrap()),
+ Arc::new(SharedMemory::from_safe_descriptor(SafeDescriptor::from(file)).unwrap()),
)
.map_err(|e| {
error!("failed to create a memory region: {}", e);
@@ -457,7 +448,7 @@
))
}
- fn set_vring_kick(&mut self, index: u8, fd: Option<RawFd>) -> VhostResult<()> {
+ fn set_vring_kick(&mut self, index: u8, file: Option<File>) -> VhostResult<()> {
if index as usize >= self.vrings.len() {
return Err(VhostError::InvalidParam);
}
@@ -468,24 +459,16 @@
return Err(VhostError::InvalidOperation);
}
- if let Some(fd) = fd {
- // TODO(b/186625058): The current code returns an error when `FD_CLOEXEC` is already
- // set, which is not harmful. Once we update the vhost crate's API to pass around `File`
- // instead of `RawFd`, we won't need this validation.
- let rd = base::validate_raw_descriptor(fd).map_err(|e| {
- error!("invalid fd is given: {}", e);
- VhostError::InvalidParam
- })?;
-
+ if let Some(file) = file {
// 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(rd, libc::O_NONBLOCK) {
+ 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 the FD is now owned.
- let kick_evt = unsafe { Event::from_raw_descriptor(rd) };
+ // Safe because we own the file.
+ let kick_evt = unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) };
let vring = &mut self.vrings[index as usize];
vring.queue.ready = true;
@@ -515,18 +498,15 @@
Ok(())
}
- fn set_vring_call(&mut self, index: u8, fd: Option<RawFd>) -> VhostResult<()> {
+ fn set_vring_call(&mut self, index: u8, file: Option<File>) -> VhostResult<()> {
if index as usize >= self.vrings.len() {
return Err(VhostError::InvalidParam);
}
- if let Some(fd) = fd {
- let rd = base::validate_raw_descriptor(fd).map_err(|e| {
- error!("invalid fd is given: {}", e);
- VhostError::InvalidParam
- })?;
- // Safe because the FD is now owned.
- let call_evt = CallEvent(unsafe { Event::from_raw_descriptor(rd) });
+ if let Some(file) = file {
+ // Safe because we own the file.
+ let call_evt =
+ CallEvent(unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) });
match &self.vrings[index as usize].call_evt {
None => {
self.vrings[index as usize].call_evt = Some(Arc::new(Mutex::new(call_evt)));
@@ -541,7 +521,7 @@
Ok(())
}
- fn set_vring_err(&mut self, _index: u8, _fd: Option<RawFd>) -> VhostResult<()> {
+ fn set_vring_err(&mut self, _index: u8, _fd: Option<File>) -> VhostResult<()> {
// TODO
Ok(())
}
@@ -591,10 +571,21 @@
Ok(())
}
- fn set_slave_req_fd(&mut self, _vu_req: SlaveFsCacheReq) {
+ fn set_slave_req_fd(&mut self, _vu_req: File) {
// TODO
}
+ 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)
@@ -603,7 +594,7 @@
fn add_mem_region(
&mut self,
_region: &VhostUserSingleMemoryRegion,
- _fd: RawFd,
+ _fd: File,
) -> VhostResult<()> {
//TODO
Ok(())
diff --git a/vhost_user_devices/src/net.rs b/vhost_user_devices/src/net.rs
index 3ebb450..34d585b 100644
--- a/vhost_user_devices/src/net.rs
+++ b/vhost_user_devices/src/net.rs
@@ -262,7 +262,7 @@
fn start_queue(
&mut self,
idx: usize,
- queue: virtio::Queue,
+ mut queue: virtio::Queue,
mem: GuestMemory,
call_evt: Arc<Mutex<CallEvent>>,
kick_evt: Event,
@@ -272,6 +272,9 @@
handle.abort();
}
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(self.acked_features);
+
NET_EXECUTOR.with(|ex| {
// Safe because the executor is initialized in main() below.
let ex = ex.get().expect("Executor not initialized");
diff --git a/vhost_user_devices/src/wl.rs b/vhost_user_devices/src/wl.rs
index 511daee..bc2494d 100644
--- a/vhost_user_devices/src/wl.rs
+++ b/vhost_user_devices/src/wl.rs
@@ -79,6 +79,7 @@
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],
}
@@ -100,6 +101,7 @@
use_transition_flags: false,
use_send_vfd_v2: false,
features,
+ acked_features: 0,
wlstate: None,
workers: Default::default(),
}
@@ -117,17 +119,25 @@
}
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.features
+ self.acked_features
}
fn protocol_features(&self) -> VhostUserProtocolFeatures {
@@ -151,7 +161,7 @@
fn start_queue(
&mut self,
idx: usize,
- queue: Queue,
+ mut queue: Queue,
mem: GuestMemory,
call_evt: Arc<Mutex<CallEvent>>,
kick_evt: Event,
@@ -161,6 +171,9 @@
handle.abort();
}
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(self.acked_features);
+
// Safe because the executor is initialized in main() below.
let ex = WL_EXECUTOR.get().expect("Executor not initialized");
diff --git a/virtio_sys/Android.bp b/virtio_sys/Android.bp
index d564dfc..d82392d 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 {
@@ -13,6 +13,7 @@
rust_library {
name: "libvirtio_sys",
defaults: ["crosvm_defaults"],
+ // has rustc warnings
host_supported: true,
crate_name: "virtio_sys",
srcs: ["src/lib.rs"],
@@ -23,9 +24,10 @@
}
rust_defaults {
- name: "virtio_sys_defaults",
+ name: "virtio_sys_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "virtio_sys",
+ // has rustc warnings
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
@@ -37,7 +39,7 @@
rust_test_host {
name: "virtio_sys_host_test_src_lib",
- defaults: ["virtio_sys_defaults"],
+ defaults: ["virtio_sys_test_defaults"],
test_options: {
unit_test: true,
},
@@ -45,45 +47,5 @@
rust_test {
name: "virtio_sys_device_test_src_lib",
- defaults: ["virtio_sys_defaults"],
+ defaults: ["virtio_sys_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/vm_control/Android.bp b/vm_control/Android.bp
index c0250be..dbc326e 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
@@ -17,17 +17,6 @@
crate_name: "vm_control",
srcs: ["src/lib.rs"],
edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
- },
rustlibs: [
"libbase_rust",
"libdata_model",
@@ -40,27 +29,26 @@
"libsync_rust",
"libvm_memory",
],
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
}
rust_defaults {
- name: "vm_control_defaults",
+ name: "vm_control_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "vm_control",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
- },
rustlibs: [
"libbase_rust",
"libdata_model",
@@ -77,64 +65,33 @@
rust_test_host {
name: "vm_control_host_test_src_lib",
- defaults: ["vm_control_defaults"],
+ defaults: ["vm_control_test_defaults"],
test_options: {
unit_test: true,
},
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
}
rust_test {
name: "vm_control_device_test_src_lib",
- defaults: ["vm_control_defaults"],
+ defaults: ["vm_control_test_defaults"],
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
}
-
-// 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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/vm_control/Cargo.toml b/vm_control/Cargo.toml
index d2de37b..7dfa4f1 100644
--- a/vm_control/Cargo.toml
+++ b/vm_control/Cargo.toml
@@ -5,12 +5,12 @@
edition = "2018"
[features]
-gdb = ["gdbstub"]
+gdb = ["gdbstub_arch"]
[dependencies]
base = { path = "../base" }
data_model = { path = "../data_model" }
-gdbstub = { version = "0.4.0", optional = true }
+gdbstub_arch = { version = "0.1.0", optional = true }
hypervisor = { path = "../hypervisor" }
libc = "*"
resources = { path = "../resources" }
diff --git a/vm_control/cargo2android.json b/vm_control/cargo2android.json
new file mode 100644
index 0000000..2ae8879
--- /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 0000000..0787bb5
--- /dev/null
+++ b/vm_control/cargo2android_gdb.bp
@@ -0,0 +1,10 @@
+target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+}
\ No newline at end of file
diff --git a/vm_control/src/gdb.rs b/vm_control/src/gdb.rs
index 1cb4d47..4146a6f 100644
--- a/vm_control/src/gdb.rs
+++ b/vm_control/src/gdb.rs
@@ -3,7 +3,7 @@
// 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.
diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs
index 75a7ecd..59994b6 100644
--- a/vm_control/src/lib.rs
+++ b/vm_control/src/lib.rs
@@ -20,15 +20,18 @@
use std::os::raw::c_int;
use std::result::Result as StdResult;
use std::str::FromStr;
-use std::sync::Arc;
+use std::sync::{mpsc, Arc};
+
+use std::thread::JoinHandle;
use libc::{EINVAL, EIO, ENODEV};
use serde::{Deserialize, Serialize};
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,
+ FromRawDescriptor, IntoRawDescriptor, Killable, MappedRegion, MemoryMappingArena,
+ MemoryMappingBuilder, MemoryMappingBuilderUnix, MmapError, Protection, Result, SafeDescriptor,
+ SharedMemory, Tube, SIGRTMIN,
};
use hypervisor::{IrqRoute, IrqSource, Vm};
use resources::{Alloc, MmioType, SystemAllocator};
@@ -62,6 +65,7 @@
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
Debug(VcpuDebug),
RunState(VmRunMode),
+ MakeRT,
}
/// Mode of execution for the VM.
@@ -103,6 +107,7 @@
/// 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,6 +117,17 @@
Stats,
}
+// Balloon commands that are send on the balloon command tube.
+//
+// This is the same as BalloonControlCommand above, but includes an ID for the
+// Stats request so that we can discard stale stats if any previous stats
+// request failed or timed out.
+#[derive(Serialize, Deserialize, Debug)]
+pub enum BalloonTubeCommand {
+ Adjust { num_bytes: u64 },
+ Stats { id: u64 },
+}
+
// BalloonStats holds stats returned from the stats_queue.
#[derive(Default, Serialize, Deserialize, Debug)]
pub struct BalloonStats {
@@ -127,6 +143,7 @@
pub hugetlb_failures: Option<u64>,
}
+// BalloonControlResult holds results for BalloonControlCommand defined above.
#[derive(Serialize, Deserialize, Debug)]
pub enum BalloonControlResult {
Stats {
@@ -135,6 +152,18 @@
},
}
+// BalloonTubeResult are results to BalloonTubeCommand defined above. This is
+// the same as BalloonControlResult, but with an added ID so that we can detect
+// stale balloon stats values queued to the balloon command tube.
+#[derive(Serialize, Deserialize, Debug)]
+pub enum BalloonTubeResult {
+ Stats {
+ stats: BalloonStats,
+ balloon_actual: u64,
+ id: u64,
+ },
+}
+
#[derive(Serialize, Deserialize, Debug)]
pub enum DiskControlCommand {
/// Resize a disk to `new_size` in bytes.
@@ -870,6 +899,8 @@
Suspend,
/// Resume the VM's VCPUs that were previously suspended.
Resume,
+ /// 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`.
@@ -944,9 +975,11 @@
&self,
run_mode: &mut Option<VmRunMode>,
balloon_host_tube: &Tube,
+ balloon_stats_id: &mut u64,
disk_host_tubes: &[Tube],
usb_control_tube: Option<&Tube>,
bat_control: &mut Option<BatControl>,
+ vcpu_handles: &[(JoinHandle<()>, mpsc::Sender<VcpuControl>)],
) -> VmResponse {
match *self {
VmRequest::Exit => {
@@ -961,27 +994,60 @@
*run_mode = Some(VmRunMode::Running);
VmResponse::Ok
}
+ 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 }) {
+ match balloon_host_tube.send(&BalloonTubeCommand::Adjust { num_bytes }) {
Ok(_) => VmResponse::Ok,
Err(_) => VmResponse::Err(SysError::last()),
}
}
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())
+ // 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());
+ }
+ }
}
- },
+ }
Err(_) => VmResponse::Err(SysError::last()),
}
}
diff --git a/vm_memory/Android.bp b/vm_memory/Android.bp
index 6cf3342..852890f 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 {
@@ -27,7 +27,7 @@
}
rust_defaults {
- name: "vm_memory_defaults",
+ name: "vm_memory_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "vm_memory",
srcs: ["src/lib.rs"],
@@ -45,7 +45,7 @@
rust_test_host {
name: "vm_memory_host_test_src_lib",
- defaults: ["vm_memory_defaults"],
+ defaults: ["vm_memory_test_defaults"],
test_options: {
unit_test: true,
},
@@ -53,46 +53,5 @@
rust_test {
name: "vm_memory_device_test_src_lib",
- defaults: ["vm_memory_defaults"],
+ defaults: ["vm_memory_test_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.15 "alloc"
-// futures-channel-0.3.15 "alloc,futures-sink,sink"
-// futures-core-0.3.15 "alloc"
-// futures-io-0.3.15
-// futures-sink-0.3.15 "alloc"
-// futures-task-0.3.15 "alloc"
-// futures-util-0.3.15 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// proc-macro2-1.0.27 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/x86_64/Android.bp b/x86_64/Android.bp
index 3afaa60..4d8274a 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
@@ -14,21 +14,11 @@
name: "libx86_64_rust",
defaults: ["crosvm_defaults"],
stem: "libx86_64",
+ // has rustc warnings
host_supported: true,
crate_name: "x86_64",
srcs: ["src/lib.rs"],
edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
- },
rustlibs: [
"libacpi_tables",
"libarch",
@@ -47,6 +37,16 @@
"libvm_memory",
],
proc_macros: ["libremain"],
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
// Exclude arm family manually
arch: {
arm: {
@@ -59,24 +59,14 @@
}
rust_defaults {
- name: "x86_64_defaults",
+ name: "x86_64_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "x86_64",
+ // has rustc warnings
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
- },
rustlibs: [
"libacpi_tables",
"libarch",
@@ -95,6 +85,24 @@
"libvm_memory",
],
proc_macros: ["libremain"],
+}
+
+rust_test_host {
+ name: "x86_64_host_test_src_lib",
+ defaults: ["x86_64_test_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
// Exclude arm family manually
arch: {
arm: {
@@ -106,106 +114,26 @@
},
}
-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"],
+ defaults: ["x86_64_test_defaults"],
+ target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
+ // Exclude arm family manually
+ arch: {
+ arm: {
+ enabled: false,
+ },
+ arm64: {
+ 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
-// ../../rust/vmm_vhost/src/lib.rs "default,vhost-user,vhost-user-master"
-// ../../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
-// ../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
-// ../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.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// cfg-if-1.0.0
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.15 "alloc,std"
-// futures-channel-0.3.15 "alloc,futures-sink,sink,std"
-// futures-core-0.3.15 "alloc,std"
-// futures-io-0.3.15 "std"
-// futures-sink-0.3.15 "alloc,std"
-// futures-task-0.3.15 "alloc,std"
-// futures-util-0.3.15 "alloc,channel,futures-channel,futures-io,futures-sink,io,memchr,sink,slab,std"
-// getrandom-0.2.3 "std"
-// intrusive-collections-0.9.1 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.97 "default,std"
-// log-0.4.14
-// memchr-2.4.0 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.7
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.27 "default,proc-macro"
-// protobuf-2.24.1
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.4 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.1 "std"
-// rand_core-0.6.3 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.126 "default,derive,serde_derive,std"
-// serde_derive-1.0.126 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.73 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.25
-// thiserror-impl-1.0.25
-// unicode-xid-0.2.2 "default"
diff --git a/x86_64/Cargo.toml b/x86_64/Cargo.toml
index 07fac9d..06abdf1 100644
--- a/x86_64/Cargo.toml
+++ b/x86_64/Cargo.toml
@@ -5,14 +5,14 @@
edition = "2018"
[features]
-gdb = ["gdbstub", "arch/gdb"]
+gdb = ["gdbstub_arch", "arch/gdb"]
[dependencies]
arch = { path = "../arch" }
assertions = { path = "../assertions" }
data_model = { path = "../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" }
diff --git a/x86_64/cargo2android.json b/x86_64/cargo2android.json
new file mode 100644
index 0000000..2ae8879
--- /dev/null
+++ b/x86_64/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/x86_64/cargo2android_gdb.bp b/x86_64/cargo2android_gdb.bp
new file mode 100644
index 0000000..7e5892c
--- /dev/null
+++ b/x86_64/cargo2android_gdb.bp
@@ -0,0 +1,19 @@
+target: {
+ linux_glibc_x86_64: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+},
+// Exclude arm family manually
+arch: {
+ arm: {
+ enabled: false,
+ },
+ arm64: {
+ enabled: false,
+ },
+}
\ No newline at end of file
diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs
index 4165858..632d062 100644
--- a/x86_64/src/lib.rs
+++ b/x86_64/src/lib.rs
@@ -54,14 +54,14 @@
use std::sync::Arc;
use crate::bootparam::boot_params;
-use acpi_tables::aml::Aml;
use acpi_tables::sdt::SDT;
+use acpi_tables::{aml, aml::Aml};
use arch::{
- get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters,
- VmComponents, VmImage,
+ get_serial_cmdline, GetSerialCmdlineError, LinuxArch, RunnableLinuxVm, SerialHardware,
+ SerialParameters, VmComponents, VmImage,
};
use base::Event;
-use devices::{IrqChip, IrqChipX86_64, PciConfigIo, PciDevice, ProtectionType};
+use devices::{BusResumeDevice, IrqChip, IrqChipX86_64, PciConfigIo, PciDevice, ProtectionType};
use hypervisor::{HypervisorX86_64, VcpuX86_64, VmX86_64};
use minijail::Minijail;
use remain::sorted;
@@ -71,7 +71,7 @@
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},
};
@@ -359,6 +359,10 @@
Ok(arch_memory_regions(components.memory_size, bios_size))
}
+ fn get_phys_max_addr() -> u64 {
+ (1u64 << cpuid::phy_max_address_bits()) - 1
+ }
+
fn create_system_allocator(guest_mem: &GuestMemory) -> SystemAllocator {
let high_mmio_start = Self::get_high_mmio_base(guest_mem);
SystemAllocator::builder()
@@ -430,7 +434,10 @@
serial_jail,
)?;
+ let mut resume_notify_devices = Vec::new();
+
let (acpi_dev_resource, bat_control) = Self::setup_acpi_devices(
+ &mem,
&mut io_bus,
system_allocator,
suspend_evt.try_clone().map_err(Error::CloneEvent)?,
@@ -439,10 +446,17 @@
irq_chip.as_irq_chip_mut(),
battery,
&mut mmio_bus,
+ &mut resume_notify_devices,
)?;
- // Use ACPI description if provided by the user.
- let noacpi = acpi_dev_resource.sdts.is_empty();
+ // Use IRQ info in ACPI if provided by the user.
+ let mut noirq = true;
+
+ for sdt in acpi_dev_resource.sdts.iter() {
+ if sdt.is_signature(b"DSDT") || sdt.is_signature(b"APIC") {
+ noirq = false;
+ }
+ }
let ramoops_region = match components.pstore {
Some(pstore) => Some(
@@ -476,8 +490,8 @@
VmImage::Kernel(ref mut kernel_image) => {
let mut cmdline = Self::get_base_linux_cmdline();
- if noacpi {
- cmdline.insert_str("pci=noacpi").unwrap();
+ if noirq {
+ cmdline.insert_str("acpi=noirq").unwrap();
}
get_serial_cmdline(&mut cmdline, serial_parameters, "io")
@@ -533,7 +547,9 @@
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,
@@ -592,12 +608,14 @@
// 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.
@@ -640,12 +658,12 @@
// 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)?;
@@ -1029,6 +1047,7 @@
/// * - `battery` indicate whether to create the battery
/// * - `mmio_bus` the MMIO bus to add the devices to
fn setup_acpi_devices(
+ mem: &GuestMemory,
io_bus: &mut devices::Bus,
resources: &mut SystemAllocator,
suspend_evt: Event,
@@ -1037,6 +1056,7 @@
irq_chip: &mut dyn IrqChip,
battery: (&Option<BatteryType>, Option<Minijail>),
mmio_bus: &mut devices::Bus,
+ 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();
@@ -1056,6 +1076,43 @@
let pmresource = devices::ACPIPMResource::new(suspend_evt, exit_evt);
Aml::to_aml_bytes(&pmresource, &mut amls);
+
+ 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(vec![
+ &aml::AddressSpace::new_bus_number(0x0u16, 0xffu16),
+ &aml::IO::new(0xcf8, 0xcf8, 1, 0x8),
+ &aml::AddressSpace::new_memory(
+ aml::AddressSpaceCachable::NotCacheable,
+ true,
+ END_ADDR_BEFORE_32BITS as u32,
+ (END_ADDR_BEFORE_32BITS + MMIO_SIZE - 1) as u32,
+ ),
+ &aml::AddressSpace::new_memory(
+ aml::AddressSpaceCachable::NotCacheable,
+ true,
+ Self::get_high_mmio_base(mem),
+ X8664arch::get_phys_max_addr(),
+ ),
+ ]),
+ );
+ pci_dsdt_inner_data.push(&crs);
+
+ 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(
@@ -1064,7 +1121,7 @@
devices::acpi::ACPIPM_RESOURCE_LEN as u64,
)
.unwrap();
- io_bus.notify_on_resume(pm);
+ resume_notify_devices.push(pm);
let bat_control = if let Some(battery_type) = battery.0 {
match battery_type {
diff --git a/x86_64/src/test_integration.rs b/x86_64/src/test_integration.rs
index c3f77cd..75ecdf3 100644
--- a/x86_64/src/test_integration.rs
+++ b/x86_64/src/test_integration.rs
@@ -160,7 +160,7 @@
)
.unwrap();
- let param_args = "nokaslr pci=noacpi";
+ let param_args = "nokaslr acpi=noirq";
let mut cmdline = X8664arch::get_base_linux_cmdline();
@@ -178,7 +178,9 @@
// let (params, kernel_end) = X8664arch::load_kernel(&guest_mem, &mut kernel_image).expect("failed to load kernel");
let suspend_evt = Event::new().unwrap();
+ let mut resume_notify_devices = Vec::new();
let acpi_dev_resource = X8664arch::setup_acpi_devices(
+ &guest_mem,
&mut io_bus,
&mut resources,
suspend_evt
@@ -189,6 +191,7 @@
&mut irq_chip,
(&None, None),
&mut mmio_bus,
+ &mut resume_notify_devices,
)
.unwrap();