Node MSC over BMC_USB_OTG
authorSven Rademakers <sven.rademakers@gmail.com>
Mon, 18 Dec 2023 15:13:37 +0000 (15:13 +0000)
committerSven Rademakers <sven.rademakers@gmail.com>
Tue, 23 Jan 2024 09:53:10 +0000 (09:53 +0000)
src/app.rs
src/app/bmc_application.rs
src/app/usb_gadget.rs [new file with mode: 0644]
src/usb_boot.rs
src/utils.rs

index c21eb0fa64b3e96aae254176628252628b240a85..bb88b974c7f04fa315dd8cd8539469f3f895da65 100644 (file)
@@ -16,3 +16,4 @@ pub mod bmc_info;
 pub mod event_application;
 pub mod transfer_action;
 pub mod upgrade_worker;
+pub mod usb_gadget;
index 2466c734ef11ccb06da6bc68b0643e874b2de6f0..5e5225a07e1c8fda1f9f6eac129c3fedaff63e8c 100644 (file)
@@ -19,7 +19,13 @@ use crate::persistency::app_persistency::ApplicationPersistency;
 use crate::persistency::app_persistency::PersistencyBuilder;
 use crate::usb_boot::NodeDrivers;
 use crate::utils::get_timestamp_unix;
+use crate::{
+    app::usb_gadget::append_msd_config_to_usb_gadget,
+    app::usb_gadget::remove_msd_function_from_usb_gadget,
+};
+
 use anyhow::{ensure, Context};
+use log::info;
 use log::{debug, trace};
 use serde::{Deserialize, Serialize};
 use std::path::PathBuf;
@@ -227,6 +233,12 @@ impl BmcApplication {
             UsbConfig::Node(host, route) => (UsbMode::Host, host, route),
         };
 
+        if mode != UsbMode::Flash {
+            if let Err(e) = remove_msd_function_from_usb_gadget().await {
+                log::error!("{:#}", e);
+            }
+        }
+
         self.pin_controller.set_usb_route(route).await?;
         self.pin_controller.select_usb(dest, mode)?;
 
@@ -252,9 +264,19 @@ impl BmcApplication {
     }
 
     pub async fn node_in_msd(&self, node: NodeId) -> anyhow::Result<PathBuf> {
+        // stop_usb_gadget_if_running().await?;
+
         self.reboot_into_usb(node, UsbConfig::Flashing(node, UsbRoute::Bmc))
             .await?;
-        Ok(self.node_drivers.load_as_block_device().await?)
+        let blk_dev = self.node_drivers.load_as_block_device().await?;
+
+        if let Err(e) = append_msd_config_to_usb_gadget(&blk_dev).await {
+            log::error!("msd usb-gadget: {:#}", e);
+        } else {
+            info!("BMC-OTG: Node mass storage CDC enabled");
+        }
+
+        Ok(blk_dev)
     }
 
     pub async fn node_in_flash(
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(())
+}
index 031ae7c2a97677795feb8d846cff30b2c67e3129..a1d6a3989317e0bba5e945f80be9f1de7f3d837c 100644 (file)
@@ -13,6 +13,7 @@
 // limitations under the License.
 mod rockusb;
 mod rpiboot;
+use self::{rockusb::RockusbBoot, rpiboot::RpiBoot};
 use async_trait::async_trait;
 use log::info;
 use rusb::GlobalContext;
@@ -20,8 +21,6 @@ use std::{fmt::Display, path::PathBuf};
 use thiserror::Error;
 use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
 
-use self::{rockusb::RockusbBoot, rpiboot::RpiBoot};
-
 pub trait DataTransport: AsyncRead + AsyncWrite + AsyncSeek + Send + Unpin {}
 impl DataTransport for tokio::fs::File {}
 
@@ -65,7 +64,7 @@ impl NodeDrivers {
     /// This function tries to find the first USB device which exist a backend for.
     fn find_first(&self) -> Result<(rusb::Device<GlobalContext>, &dyn UsbBoot), UsbBootError> {
         log::info!("Checking for presence of a USB device...");
-        let devices = rusb::devices().map_err(UsbBootError::internal_error)?;
+        let devices = rusb::devices()?;
         let mut backends = self.backends.iter().filter_map(|backend| {
             let found = devices.iter().find(|dev| {
                 let Ok(descriptor) = dev.device_descriptor() else {
index 1cce066c018f59d25339b3d56ad8baac83726199..b4b2c3e4c32be8917d11054cc08ffc550100cd10 100644 (file)
 mod event_listener;
 mod io;
 
-use std::path::PathBuf;
-use std::time::{SystemTime, UNIX_EPOCH};
-
 use anyhow::bail;
+use std::time::{SystemTime, UNIX_EPOCH};
 
 #[doc(inline)]
 pub use event_listener::*;
 pub use io::*;
+use std::{path::PathBuf, process::Output};
+use tokio::io::AsyncBufReadExt;
 
 pub fn string_from_utf16(bytes: &[u8], little_endian: bool) -> String {
     let u16s = bytes.chunks_exact(2).map(|pair| {
@@ -115,3 +115,16 @@ pub fn get_timestamp_unix() -> Option<u64> {
         .ok()
         .map(|x| x.as_secs())
 }
+
+pub async fn logging_sink_stdio(output: &Output) -> std::io::Result<()> {
+    let mut lines = output.stdout.lines();
+    while let Some(line) = lines.next_line().await? {
+        log::info!("{}", line);
+    }
+
+    let mut lines = output.stderr.lines();
+    while let Some(line) = lines.next_line().await? {
+        log::error!("{}", line);
+    }
+    Ok(())
+}