devices: gpu: add render server support

When "--gpu-render-server path=<path>" is specified, start the render
server shipped with virglrenderer and initialize virglrenderer with
VIRGLRENDERER_MULTI_PROCESS flag.

The flag makes virgl_renderer_context_create_with_flags create proxy
contexts instead of venus contexts.  Each proxy context requests the
render server to fork a subprocess and executes GPU commands in the
subprocess.

BUG=b:177267762
TEST=run vk and gl apps on volteer

Change-Id: If5e2dc3353572cadb60b0c25a3e0ad14f633db91
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3283508
Reviewed-by: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Chia-I Wu <olv@google.com>
diff --git a/src/main.rs b/src/main.rs
index 557847a..2cb3361 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -39,7 +39,8 @@
 #[cfg(feature = "gpu")]
 use devices::virtio::{
     gpu::{
-        GpuDisplayParameters, GpuMode, GpuParameters, DEFAULT_DISPLAY_HEIGHT, DEFAULT_DISPLAY_WIDTH,
+        GpuDisplayParameters, GpuMode, GpuParameters, GpuRenderServerParameters,
+        DEFAULT_DISPLAY_HEIGHT, DEFAULT_DISPLAY_WIDTH,
     },
     vhost::user::device::run_gpu_device,
 };
@@ -541,6 +542,52 @@
     Ok(())
 }
 
+#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
+fn parse_gpu_render_server_options(
+    s: Option<&str>,
+    gpu_params: &mut GpuParameters,
+) -> argument::Result<()> {
+    let mut path: Option<PathBuf> = None;
+
+    if let Some(s) = s {
+        let opts = s
+            .split(',')
+            .map(|frag| frag.split('='))
+            .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
+
+        for (k, v) in opts {
+            match k {
+                "path" => {
+                    path =
+                        Some(
+                            PathBuf::from_str(v).map_err(|e| argument::Error::InvalidValue {
+                                value: v.to_string(),
+                                expected: e.to_string(),
+                            })?,
+                        )
+                }
+                "" => {}
+                _ => {
+                    return Err(argument::Error::UnknownArgument(format!(
+                        "gpu-render-server parameter {}",
+                        k
+                    )));
+                }
+            }
+        }
+    }
+
+    if let Some(p) = path {
+        gpu_params.render_server = Some(GpuRenderServerParameters { path: p });
+        Ok(())
+    } else {
+        Err(argument::Error::InvalidValue {
+            value: s.unwrap_or("").to_string(),
+            expected: String::from("gpu-render-server must include 'path'"),
+        })
+    }
+}
+
 #[cfg(feature = "audio")]
 fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
     let mut ac97_params: Ac97Parameters = Default::default();
@@ -1802,17 +1849,18 @@
         }
         #[cfg(feature = "gpu")]
         "gpu" => {
-            if cfg.gpu_parameters.is_none() {
-                cfg.gpu_parameters = Some(Default::default());
-            }
-            parse_gpu_options(value, cfg.gpu_parameters.as_mut().unwrap())?;
+            let gpu_parameters = cfg.gpu_parameters.get_or_insert_with(Default::default);
+            parse_gpu_options(value, gpu_parameters)?;
         }
         #[cfg(feature = "gpu")]
         "gpu-display" => {
-            if cfg.gpu_parameters.is_none() {
-                cfg.gpu_parameters = Some(Default::default());
-            }
-            parse_gpu_display_options(value, cfg.gpu_parameters.as_mut().unwrap())?;
+            let gpu_parameters = cfg.gpu_parameters.get_or_insert_with(Default::default);
+            parse_gpu_display_options(value, gpu_parameters)?;
+        }
+        #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
+        "gpu-render-server" => {
+            let gpu_parameters = cfg.gpu_parameters.get_or_insert_with(Default::default);
+            parse_gpu_render_server_options(value, gpu_parameters)?;
         }
         "software-tpm" => {
             cfg.software_tpm = true;
@@ -2351,6 +2399,12 @@
                               Possible key values:
                               width=INT - The width of the virtual display connected to the virtio-gpu.
                               height=INT - The height of the virtual display connected to the virtio-gpu."),
+          #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
+          Argument::flag_or_value("gpu-render-server",
+                                  "[path=PATH]",
+                                  "(EXPERIMENTAL) Comma separated key=value pairs for setting up a render server for the virtio-gpu device
+                              Possible key values:
+                              path=PATH - The path to the render server executable."),
           #[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"),