Implemented v2.5 changes
authorSven Rademakers <sven.rademakers@gmail.com>
Fri, 16 Feb 2024 11:34:14 +0000 (11:34 +0000)
committerSven Rademakers <sven.rademakers@gmail.com>
Fri, 16 Feb 2024 15:21:07 +0000 (15:21 +0000)
Changes to the `PinController` and `PowerController`.

* The `bmcd` remains fully compatible with v2.4 and v2.5 hardware.
* The additional Node1 USB source switching for v2.5 is
not yet implemented. The switches are currently configured so that: USB0 is routed to the
usb hub and usb2 is routed to the USB-A port.
* The actual location of gpio pins inside gpiod is still tentative at
  this point for TPv2.5.

src/api/legacy.rs
src/app/bmc_application.rs
src/hal.rs
src/hal/gpio_definitions.rs
src/hal/pin_controller.rs
src/hal/power_controller.rs

index 24533220cf4d5a31c87658ac530cfdf7966abfa4..71e80670758e18749b145c9256f58369fd475f43 100644 (file)
@@ -458,11 +458,11 @@ async fn set_usb_mode(bmc: &BmcApplication, query: Query) -> LegacyResult<()> {
     let route = if (mode_num >> 2) & 0x1 == 1 {
         UsbRoute::Bmc
     } else {
-        UsbRoute::UsbA
+        UsbRoute::AlternativePort
     };
 
     let cfg = match (mode, route) {
-        (UsbMode::Device, UsbRoute::UsbA) => UsbConfig::UsbA(node),
+        (UsbMode::Device, UsbRoute::AlternativePort) => UsbConfig::UsbA(node),
         (UsbMode::Device, UsbRoute::Bmc) => UsbConfig::Bmc(node),
         (UsbMode::Host, route) => UsbConfig::Node(node, route),
         (UsbMode::Flash, route) => UsbConfig::Flashing(node, route),
@@ -479,7 +479,7 @@ async fn get_usb_mode(bmc: &BmcApplication) -> impl Into<LegacyResponse> {
     let config = bmc.get_usb_mode().await;
 
     let (node, mode, route) = match config {
-        UsbConfig::UsbA(node) => (node, UsbMode::Device, UsbRoute::UsbA),
+        UsbConfig::UsbA(node) => (node, UsbMode::Device, UsbRoute::AlternativePort),
         UsbConfig::Bmc(node) => (node, UsbMode::Device, UsbRoute::Bmc),
         UsbConfig::Node(node, route) => (node, UsbMode::Host, route),
         UsbConfig::Flashing(node, route) => (node, UsbMode::Flash, route),
index 90197bb1c72c53a7cfa1c2efa0d0fe29b935c714..78f2a3eda2a9c21a3e3d71ec53b19b1c8f4b6a69 100644 (file)
@@ -73,8 +73,10 @@ pub struct BmcApplication {
 
 impl BmcApplication {
     pub async fn new(database_write_timeout: Option<Duration>) -> anyhow::Result<Self> {
-        let pin_controller = PinController::new().context("pin_controller")?;
-        let power_controller = PowerController::new().context("power_controller")?;
+        let model_string = std::fs::read_to_string("/proc/device-tree/model");
+        let is_legacy_dts = matches!(model_string, Ok(model) if model.contains("v2.4"));
+        let pin_controller = PinController::new(is_legacy_dts).context("pin_controller")?;
+        let power_controller = PowerController::new(is_legacy_dts).context("power_controller")?;
         let app_db = PersistencyBuilder::default()
             .register_key(ACTIVATED_NODES_KEY, &0u8)
             .register_key(USB_CONFIG, &UsbConfig::UsbA(NodeId::Node1))
@@ -210,7 +212,7 @@ impl BmcApplication {
     async fn configure_usb_internal(&self, config: UsbConfig) -> anyhow::Result<()> {
         log::info!("changing usb config to {:?}", config);
         let (mode, dest, route) = match config {
-            UsbConfig::UsbA(device) => (UsbMode::Device, device, UsbRoute::UsbA),
+            UsbConfig::UsbA(device) => (UsbMode::Device, device, UsbRoute::AlternativePort),
             UsbConfig::Bmc(device) => (UsbMode::Device, device, UsbRoute::Bmc),
             UsbConfig::Flashing(device, route) => (UsbMode::Flash, device, route),
             UsbConfig::Node(host, route) => (UsbMode::Host, host, route),
@@ -222,7 +224,7 @@ impl BmcApplication {
             }
         }
 
-        self.pin_controller.set_usb_route(route).await?;
+        self.pin_controller.set_usb_route(route)?;
         self.pin_controller.select_usb(dest, mode)?;
 
         Ok(())
index 796ffa3b1add84dfcd27cdbc8633cb57bfedba8d..2918efc716cc5219e890bb90604ddf697f6085e7 100644 (file)
@@ -96,7 +96,7 @@ pub enum NodeType {
 #[derive(Debug, Eq, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]
 pub enum UsbRoute {
     Bmc,
-    UsbA,
+    AlternativePort,
 }
 
 #[derive(Debug, Eq, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]
index a20751056d881a00acc99e4759ef9ca3716f4ff1..4621b62966a0e33ef0a82f9ee83551596b1b77b0 100644 (file)
@@ -14,6 +14,7 @@
 //! This module contains static pin numbers related to GPIO
 pub const GPIO_PIN_PG: u32 = 192;
 
+#[allow(unused)]
 pub const RTL_RESET: u32 = GPIO_PIN_PG + 13;
 #[allow(unused)]
 pub const SYS_RESET: u32 = GPIO_PIN_PG + 11;
@@ -27,3 +28,6 @@ pub const USB_OE1: u32 = GPIO_PIN_PG + 2;
 pub const USB_OE2: u32 = GPIO_PIN_PG + 3;
 
 pub const USB_SWITCH: u32 = GPIO_PIN_PG + 5;
+pub const USB_SWITCH_V2_5: u32 = GPIO_PIN_PG + 2;
+pub const NODE1_OUTPUT_SWITCH_V2_5: u32 = GPIO_PIN_PG;
+pub const NODE1_SOURCE_SWITCH_V2_5: u32 = GPIO_PIN_PG + 1;
index 4be8eaf28cf2285135f7991feb47123b3d66651d..47227a10f88458680b5a39a7977a0b0aabfb709c 100644 (file)
@@ -23,8 +23,7 @@ use super::UsbRoute;
 use anyhow::Context;
 use gpiod::{Chip, Lines, Output};
 use log::debug;
-use std::time::Duration;
-use tokio::time::sleep;
+use thiserror::Error;
 
 const USB_PORT_POWER: &str = "/sys/bus/platform/devices/usb-port-power/state";
 
@@ -38,36 +37,81 @@ const NODE2_RPIBOOT: &str = "node2-rpiboot";
 const NODE3_RPIBOOT: &str = "node3-rpiboot";
 const NODE4_RPIBOOT: &str = "node4-rpiboot";
 
+/// This class is responsible for switching USB busses to the various "USB
+/// endpoints", e.g. a USB port on the bus or a connection to the BMC(t113). The
+/// hardware changed over time, and depending on which version of the board is
+/// used different usecases can be realized. To expose an interface that has the
+/// best support for all hardware platforms, the USB switch is seen as a
+/// blackbox, which connects the Nodes with the BMC or the USB_OTG port.
+///
+///```text
+///                                           ┌─────┐
+/// ┌────────────────────┐     ┌─────────┐    │     │
+/// │                    │     │         │    │Node1│
+/// │  4XNODE USB_OTG /  │     │         ├────┤     │
+/// │  (v2.4) USB-A      ├─────┤         │    └─────┘
+/// │                    │     │USB switch
+/// └────────────────────┘     │Blackbox │    ┌─────┐
+///                            │         │    │     │
+/// ┌────────┐                 │         ├────┼Node2│
+/// │        │                 │         │    │     │
+/// │  T113  ├─────────────────┤         │    └─────┘
+/// │        │                 │         │
+/// └────────┘                 │         ├─┐  ┌─────┐
+///                            └─────────┘ │  │     │
+///                                        └──┤N... │
+///                                           │     │
+///                                           └─────┘
+/// ```
+///
+/// # Turing Pi v2.4
+///
+/// The hardware of the turing pi v2.4 is capable to switch one node to either
+/// the USB-A port on the board or to the T113. Both the Node or the USB_OTG/T113
+/// can be put in host mode. The only limitation is that only one connection can
+/// be routed.
+///
+/// # Turing Pi >= v2.5
+///
+/// On the turing pi 2.5 the USB-A port is replaced with an USB-C port and
+/// relabeled to 4XNODE_USB_OTG. The 2.5 board still has an USB-A port, but is
+/// exclusively connected to Node1.
+///
+/// ```text
+///                            ┌───────────────────┐
+///                       ┌────┴─┐                 │
+/// ┌───────┐             │      │    ┌──────┐     │
+/// │ USB-A ├─────────────┤switch├────┤switch├───┐ │
+/// └───────┴             └──────┘    └─┬────┘   │ │
+///                                     │     ┌──┴─┴┐
+/// ┌────────────────────┐     ┌────────┴┐    │     │
+/// │                    │     │         │    │Node1│
+/// │  4XNODE USB_OTG /  │     │         │    │     │
+/// │  (v2.4) USB-A      ├─────┤         │    └─────┘
+/// │                    │     │USB switch
+/// └────────────────────┘     │Blackbox │    ┌─────┐
+/// ```
+///
+/// The switches enable us to connect different USB ports on Node1 to the switch
+/// or USB-A. This way we can provide support to more devices devices.
 pub struct PinController {
-    usb_vbus: Lines<Output>,
-    usb_mux: Lines<Output>,
-    usb_switch: Lines<Output>,
+    usb_switch: Box<dyn UsbConfiguration + Sync + Send>,
     rpi_boot: [Lines<Output>; 4],
-    rtl_reset: Lines<Output>,
 }
 
 impl PinController {
     /// create a new Pin controller
-    pub fn new() -> anyhow::Result<Self> {
+    pub fn new(has_usb_switch: bool) -> anyhow::Result<Self> {
+        let chip1 = if has_usb_switch {
+            "/dev/gpiochip1"
+        } else {
+            "/dev/gpiochip2"
+        };
+
         let chip0 = Chip::new("/dev/gpiochip0").context("gpiod chip0")?;
-        let chip1 = Chip::new("/dev/gpiochip1").context("gpiod chip1")?;
+        let chip1 = Chip::new(chip1).context("gpiod chip1")?;
         let chip1_lines = load_lines(&chip1);
 
-        let usb1 = *chip1_lines
-            .get(NODE1_USBOTG_DEV)
-            .ok_or(anyhow::anyhow!("cannot find node-1-usbotg-dev gpio"))?;
-        let usb2 = *chip1_lines
-            .get(NODE2_USBOTG_DEV)
-            .ok_or(anyhow::anyhow!("cannot find node-2-usbotg-dev gpio"))?;
-        let usb3 = *chip1_lines
-            .get(NODE3_USBOTG_DEV)
-            .ok_or(anyhow::anyhow!("cannot find node-3-usbotg-dev gpio"))?;
-        let usb4 = *chip1_lines
-            .get(NODE4_USBOTG_DEV)
-            .ok_or(anyhow::anyhow!("cannot find node-4-usbotg-dev gpio"))?;
-
-        let usb_vbus = gpio_output_lines!(chip1, [usb1, usb2, usb3, usb4]);
-
         let rpi1 = *chip1_lines
             .get(NODE1_RPIBOOT)
             .ok_or(anyhow::anyhow!("cannot find node1-rpiboot gpio"))?;
@@ -83,37 +127,29 @@ impl PinController {
 
         let rpi_boot = gpio_output_array!(chip1, rpi1, rpi2, rpi3, rpi4);
 
-        let usb_mux = gpio_output_lines!(chip0, [USB_SEL1, USB_OE1, USB_SEL2, USB_OE2]);
-        let usb_switch = gpio_output_lines!(chip0, [USB_SWITCH]);
-        let rtl_reset = chip0
-            .request_lines(gpiod::Options::output([RTL_RESET]).active(gpiod::Active::Low))
-            .context(concat!("error initializing pin rtl reset"))?;
+        let usb_switch = if has_usb_switch {
+            Box::new(UsbMuxSwitch::new(&chip0, &chip1)?) as Box<dyn UsbConfiguration + Send + Sync>
+        } else {
+            // the node1 switching is not yet implemented. Make sure that the
+            // switch routes the usb0 to the USB hub and usb2 to the the usbA
+            // port.
+            let node_source =
+                gpio_output_lines!(chip0, [NODE1_OUTPUT_SWITCH_V2_5, NODE1_SOURCE_SWITCH_V2_5]);
+            node_source.set_values(0u8)?;
+
+            Box::new(UsbHub::new(&chip0)?)
+        };
 
         Ok(Self {
-            usb_vbus,
-            usb_mux,
             usb_switch,
             rpi_boot,
-            rtl_reset,
         })
     }
 
     /// Select which node is active in the multiplexer (see PORTx in `set_usb_route()`)
-    pub fn select_usb(&self, node: NodeId, mode: UsbMode) -> std::io::Result<()> {
+    pub fn select_usb(&self, node: NodeId, mode: UsbMode) -> Result<(), PowerControllerError> {
         debug!("select USB for node {:?}, mode:{:?}", node, mode);
-        let values: u8 = match node {
-            NodeId::Node1 => 0b1100,
-            NodeId::Node2 => 0b1101,
-            NodeId::Node3 => 0b0011,
-            NodeId::Node4 => 0b0111,
-        };
-        self.usb_mux.set_values(values)?;
-
-        let vbus = match mode {
-            UsbMode::Host => node.to_inverse_bitfield(),
-            UsbMode::Device | UsbMode::Flash => 0b1111,
-        };
-        self.usb_vbus.set_values(vbus)?;
+        self.usb_switch.configure_usb(node, mode)?;
 
         if UsbMode::Flash == mode {
             self.set_usb_boot(node.to_bitfield(), node.to_bitfield())?;
@@ -124,25 +160,20 @@ impl PinController {
         Ok(())
     }
 
-    /// Set which way the USB is routed: USB-A ↔ PORTx (`UsbRoute::UsbA`) or BMC ↔ PORTx
+    /// Set which way the USB is routed: USB-A ↔ PORTx (`UsbRoute::AlternativePort`) or BMC ↔ PORTx
     /// (`UsbRoute::Bmc`)
-    pub async fn set_usb_route(&self, route: UsbRoute) -> std::io::Result<()> {
+    pub fn set_usb_route(&self, route: UsbRoute) -> Result<(), PowerControllerError> {
         debug!("select USB route {:?}", route);
-        match route {
-            UsbRoute::UsbA => {
-                self.usb_switch.set_values(0_u8)?;
-                tokio::fs::write(USB_PORT_POWER, b"enabled").await
-            }
-            UsbRoute::Bmc => {
-                self.usb_switch.set_values(1_u8)?;
-                tokio::fs::write(USB_PORT_POWER, b"disabled").await
-            }
-        }
+        self.usb_switch.set_usb_route(route)
     }
 
     /// Set given nodes into usb boot mode. When powering the node on with this mode enabled, the
     /// given node will boot into USB mode. Typically means that booting of eMMC is disabled.
-    pub fn set_usb_boot(&self, nodes_state: u8, nodes_mask: u8) -> std::io::Result<()> {
+    pub fn set_usb_boot(
+        &self,
+        nodes_state: u8,
+        nodes_mask: u8,
+    ) -> Result<(), PowerControllerError> {
         let updates = bit_iterator(nodes_state, nodes_mask);
 
         for (idx, state) in updates {
@@ -156,9 +187,127 @@ impl PinController {
         Ok(())
     }
 
-    pub async fn rtl_reset(&self) -> std::io::Result<()> {
-        self.rtl_reset.set_values(1u8)?;
-        sleep(Duration::from_secs(1)).await;
-        self.rtl_reset.set_values(0u8)
+    pub async fn rtl_reset(&self) -> Result<(), PowerControllerError> {
+        //  self.rtl_reset.set_values(1u8)?;
+        //  sleep(Duration::from_secs(1)).await;
+        //  self.rtl_reset.set_values(0u8)?;
+        //  Ok(())
+        todo!()
+    }
+}
+
+trait UsbConfiguration {
+    fn set_usb_route(&self, route: UsbRoute) -> Result<(), PowerControllerError>;
+    fn configure_usb(&self, node: NodeId, mode: UsbMode) -> Result<(), PowerControllerError>;
+}
+
+struct UsbMuxSwitch {
+    usb_mux: Lines<Output>,
+    usb_vbus: Lines<Output>,
+    output_switch: Lines<Output>,
+}
+
+impl UsbMuxSwitch {
+    pub fn new(chip0: &Chip, chip1: &Chip) -> Result<Self, PowerControllerError> {
+        let usb_mux = gpio_output_lines!(chip0, [USB_SEL1, USB_OE1, USB_SEL2, USB_OE2]);
+        let output_switch = gpio_output_lines!(chip0, [USB_SWITCH]);
+        let chip1_lines = load_lines(chip1);
+
+        let usb1 = *chip1_lines
+            .get(NODE1_USBOTG_DEV)
+            .ok_or(anyhow::anyhow!("cannot find node-1-usbotg-dev gpio"))?;
+        let usb2 = *chip1_lines
+            .get(NODE2_USBOTG_DEV)
+            .ok_or(anyhow::anyhow!("cannot find node-2-usbotg-dev gpio"))?;
+        let usb3 = *chip1_lines
+            .get(NODE3_USBOTG_DEV)
+            .ok_or(anyhow::anyhow!("cannot find node-3-usbotg-dev gpio"))?;
+        let usb4 = *chip1_lines
+            .get(NODE4_USBOTG_DEV)
+            .ok_or(anyhow::anyhow!("cannot find node-4-usbotg-dev gpio"))?;
+
+        let usb_vbus = gpio_output_lines!(chip1, [usb1, usb2, usb3, usb4]);
+        Ok(Self {
+            usb_mux,
+            usb_vbus,
+            output_switch,
+        })
+    }
+}
+
+impl UsbConfiguration for UsbMuxSwitch {
+    fn set_usb_route(&self, route: UsbRoute) -> Result<(), PowerControllerError> {
+        match route {
+            UsbRoute::AlternativePort => {
+                self.output_switch.set_values(0_u8)?;
+                std::fs::write(USB_PORT_POWER, b"enabled")
+            }
+            UsbRoute::Bmc => {
+                self.output_switch.set_values(1_u8)?;
+                std::fs::write(USB_PORT_POWER, b"disabled")
+            }
+        }?;
+
+        Ok(())
+    }
+
+    fn configure_usb(&self, node: NodeId, mode: UsbMode) -> Result<(), PowerControllerError> {
+        let values: u8 = match node {
+            NodeId::Node1 => 0b1100,
+            NodeId::Node2 => 0b1101,
+            NodeId::Node3 => 0b0011,
+            NodeId::Node4 => 0b0111,
+        };
+        self.usb_mux.set_values(values)?;
+        let vbus = match mode {
+            UsbMode::Host => node.to_inverse_bitfield(),
+            UsbMode::Device | UsbMode::Flash => 0b1111,
+        };
+        self.usb_vbus.set_values(vbus)?;
+        Ok(())
+    }
+}
+
+struct UsbHub {
+    output_switch: Lines<Output>,
+}
+
+impl UsbHub {
+    pub fn new(chip: &Chip) -> Result<Self, PowerControllerError> {
+        let output_switch = gpio_output_lines!(chip, [USB_SWITCH_V2_5]);
+        Ok(Self { output_switch })
+    }
+}
+
+impl UsbConfiguration for UsbHub {
+    fn set_usb_route(&self, route: UsbRoute) -> Result<(), PowerControllerError> {
+        match route {
+            UsbRoute::AlternativePort => self.output_switch.set_values(0_u8),
+            UsbRoute::Bmc => self.output_switch.set_values(1_u8),
+        }?;
+
+        Ok(())
     }
+
+    fn configure_usb(&self, _: NodeId, mode: UsbMode) -> Result<(), PowerControllerError> {
+        if mode == UsbMode::Host {
+            return Err(PowerControllerError::HostModeNotSupported);
+        }
+        // nothing to do as all nodes are already connected as USB devices to
+        // the USB hub.
+        Ok(())
+    }
+}
+
+#[derive(Debug, Error)]
+pub enum PowerControllerError {
+    #[error(
+        "Selecting one of the nodes as USB Host role \
+        is not supported by the current hardware"
+    )]
+    HostModeNotSupported,
+    #[error(transparent)]
+    Io(#[from] std::io::Error),
+    #[error(transparent)]
+    Anyhow(#[from] anyhow::Error),
 }
index e1812348642b24f1962d5f196c17a9f5c6e60a84..ffc099be59b941fe3974accdde8828bf42f799cf 100644 (file)
@@ -41,8 +41,14 @@ pub struct PowerController {
 }
 
 impl PowerController {
-    pub fn new() -> anyhow::Result<Self> {
-        let chip1 = Chip::new("/dev/gpiochip1").context("gpiod chip1")?;
+    pub fn new(is_latching_system: bool) -> anyhow::Result<Self> {
+        let chip1 = if is_latching_system {
+            "/dev/gpiochip1"
+        } else {
+            "/dev/gpiochip2"
+        };
+
+        let chip1 = Chip::new(chip1).context(chip1)?;
         let lines = load_lines(&chip1);
         let port1 = *lines
             .get(PORT1_EN)
@@ -120,12 +126,6 @@ impl PowerController {
     }
 }
 
-impl std::fmt::Debug for PowerController {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "PowerController")
-    }
-}
-
 async fn set_mode(node_id: usize, node_state: u8) -> std::io::Result<()> {
     let node_value = if node_state > 0 {
         "enabled"