Node MSC over BMC_USB_OTG
[bmcd] / src / app / usb_gadget.rs
diff --git a/src/app/usb_gadget.rs b/src/app/usb_gadget.rs
new file mode 100644 (file)
index 0000000..58f4bc3
--- /dev/null
@@ -0,0 +1,138 @@
+// Copyright 2023 Turing Machines
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+use anyhow::{anyhow, bail, Context};
+use std::{
+    ops::Deref,
+    path::Path,
+    process::{Command, Stdio},
+};
+use tokio::{
+    fs::{symlink, OpenOptions},
+    io::AsyncWriteExt,
+    task::spawn_blocking,
+};
+
+use crate::utils::logging_sink_stdio;
+
+const BMC_USB_OTG: &str = "/sys/kernel/config/usb_gadget/g1";
+
+pub async fn append_msd_config_to_usb_gadget(block_device: &Path) -> anyhow::Result<()> {
+    if is_gadget_running().await? {
+        remove_msd_function_from_usb_gadget().await?;
+        usb_gadget_service(GadgetCmd::Stop)
+            .await
+            .context("usb_gadget")?;
+    }
+
+    let usb_gadget = Path::new(BMC_USB_OTG);
+    if !usb_gadget.exists() {
+        bail!("{} does not exist", BMC_USB_OTG);
+    }
+
+    let config = usb_gadget.join("configs/c.1");
+
+    let mass_storage_function = usb_gadget.join("functions/mass_storage.0");
+    tokio::fs::create_dir_all(&mass_storage_function)
+        .await
+        .with_context(|| mass_storage_function.to_string_lossy().to_string())?;
+
+    let lun0 = mass_storage_function.join("lun.0/file");
+    let mut file = OpenOptions::new()
+        .write(true)
+        .truncate(true)
+        .create(true)
+        .open(lun0)
+        .await?;
+
+    file.write_all(
+        block_device
+            .to_str()
+            .ok_or(anyhow!(
+                "{} not convertable to string",
+                block_device.to_str().unwrap_or_default()
+            ))?
+            .as_bytes(),
+    )
+    .await?;
+
+    symlink(&mass_storage_function, &config.join("mass_storage.0"))
+        .await
+        .with_context(|| {
+            format!(
+                "symlink {} to {}",
+                mass_storage_function.to_string_lossy(),
+                config.to_string_lossy()
+            )
+        })?;
+
+    usb_gadget_service(GadgetCmd::Start)
+        .await
+        .context("usb_gadget")?;
+
+    Ok(())
+}
+
+pub async fn remove_msd_function_from_usb_gadget() -> anyhow::Result<()> {
+    let mass_storage_config = Path::new(BMC_USB_OTG).join("configs/c.1/mass_storage.0");
+    if mass_storage_config.exists() {
+        usb_gadget_service(GadgetCmd::Stop).await?;
+        tokio::fs::remove_file(&mass_storage_config)
+            .await
+            .with_context(|| mass_storage_config.to_string_lossy().to_string())?;
+        usb_gadget_service(GadgetCmd::Start).await?;
+    }
+    Ok(())
+}
+
+async fn is_gadget_running() -> anyhow::Result<bool> {
+    let udc = Path::new(BMC_USB_OTG).join("UDC");
+    Ok(tokio::fs::read_to_string(udc)
+        .await
+        .map(|s| !s.trim().is_empty())?)
+}
+
+enum GadgetCmd {
+    Start,
+    Stop,
+}
+
+impl Deref for GadgetCmd {
+    type Target = str;
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            GadgetCmd::Start => "start",
+            GadgetCmd::Stop => "stop",
+        }
+    }
+}
+
+async fn usb_gadget_service(command: GadgetCmd) -> anyhow::Result<()> {
+    let output = spawn_blocking(move || {
+        Command::new("/etc/init.d/S11bmc-otg")
+            .arg(command.deref())
+            .stdout(Stdio::piped())
+            .stderr(Stdio::piped())
+            .output()
+    })
+    .await??;
+
+    logging_sink_stdio(&output).await?;
+
+    if !output.status.success() {
+        bail!("S11bmc-otg: {}", output.status);
+    }
+
+    Ok(())
+}