crosvm-direct: enable interrupt passthrough.

Simple command line option to enable host interrupt passthrough.

BUG=b:173824544
TEST=None

Change-Id: I75a0224b8885b4129c64811ac315b995b2120d46
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2734594
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Tomasz Jeznach <tjeznach@chromium.org>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
diff --git a/src/crosvm.rs b/src/crosvm.rs
index c79d0ff..45b0485 100644
--- a/src/crosvm.rs
+++ b/src/crosvm.rs
@@ -257,6 +257,10 @@
     pub vhost_user_net: Vec<VhostUserOption>,
     #[cfg(feature = "direct")]
     pub direct_pmio: Option<DirectIoOption>,
+    #[cfg(feature = "direct")]
+    pub direct_level_irq: Vec<u32>,
+    #[cfg(feature = "direct")]
+    pub direct_edge_irq: Vec<u32>,
 }
 
 impl Default for Config {
@@ -326,6 +330,10 @@
             vhost_user_net: Vec::new(),
             #[cfg(feature = "direct")]
             direct_pmio: None,
+            #[cfg(feature = "direct")]
+            direct_level_irq: Vec::new(),
+            #[cfg(feature = "direct")]
+            direct_edge_irq: Vec::new(),
         }
     }
 }
diff --git a/src/linux.rs b/src/linux.rs
index 4e268c5..493f551 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -117,7 +117,10 @@
     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),
@@ -233,7 +236,10 @@
             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),
@@ -2507,6 +2513,41 @@
         }
     };
 
+    #[cfg(feature = "direct")]
+    let mut irqs = Vec::new();
+
+    #[cfg(feature = "direct")]
+    for irq in &cfg.direct_level_irq {
+        if !linux.resources.reserve_irq(*irq) {
+            warn!("irq {} already reserved.", irq);
+        }
+        let trigger = Event::new().map_err(Error::CreateEvent)?;
+        let resample = Event::new().map_err(Error::CreateEvent)?;
+        linux
+            .irq_chip
+            .register_irq_event(*irq, &trigger, Some(&resample))
+            .unwrap();
+        let direct_irq =
+            devices::DirectIrq::new(trigger, Some(resample)).map_err(Error::DirectIrq)?;
+        direct_irq.irq_enable(*irq).map_err(Error::DirectIrq)?;
+        irqs.push(direct_irq);
+    }
+
+    #[cfg(feature = "direct")]
+    for irq in &cfg.direct_edge_irq {
+        if !linux.resources.reserve_irq(*irq) {
+            warn!("irq {} already reserved.", irq);
+        }
+        let trigger = Event::new().map_err(Error::CreateEvent)?;
+        linux
+            .irq_chip
+            .register_irq_event(*irq, &trigger, None)
+            .unwrap();
+        let direct_irq = devices::DirectIrq::new(trigger, None).map_err(Error::DirectIrq)?;
+        direct_irq.irq_enable(*irq).map_err(Error::DirectIrq)?;
+        irqs.push(direct_irq);
+    }
+
     run_control(
         linux,
         control_server_socket,
diff --git a/src/main.rs b/src/main.rs
index f1f6bc6..de4cfc1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1679,6 +1679,36 @@
             }
             cfg.direct_pmio = Some(parse_direct_io_options(value)?);
         }
+        #[cfg(feature = "direct")]
+        "direct-level-irq" => {
+            cfg.direct_level_irq
+                .push(
+                    value
+                        .unwrap()
+                        .parse()
+                        .map_err(|_| argument::Error::InvalidValue {
+                            value: value.unwrap().to_owned(),
+                            expected: String::from(
+                                "this value for `direct-level-irq` must be an unsigned integer",
+                            ),
+                        })?,
+                );
+        }
+        #[cfg(feature = "direct")]
+        "direct-edge-irq" => {
+            cfg.direct_edge_irq
+                .push(
+                    value
+                        .unwrap()
+                        .parse()
+                        .map_err(|_| argument::Error::InvalidValue {
+                            value: value.unwrap().to_owned(),
+                            expected: String::from(
+                                "this value for `direct-edge-irq` must be an unsigned integer",
+                            ),
+                        })?,
+                );
+        }
         "help" => return Err(argument::Error::PrintHelp),
         _ => unreachable!(),
     }
@@ -1894,6 +1924,10 @@
                           "Path to a socket path for vhost-user fs, and tag for the shared dir"),
           #[cfg(feature = "direct")]
           Argument::value("direct-pmio", "PATH@RANGE[,RANGE[,...]]", "Path and ranges for direct port I/O access"),
+          #[cfg(feature = "direct")]
+          Argument::value("direct-level-irq", "irq", "Enable interrupt passthrough"),
+          #[cfg(feature = "direct")]
+          Argument::value("direct-edge-irq", "irq", "Enable interrupt passthrough"),
           Argument::short_flag('h', "help", "Print help message.")];
 
     let mut cfg = Config::default();