From cf7cc76c33fd4e6e78bc0555c309e56140814e57 Mon Sep 17 00:00:00 2001 From: Sven Rademakers Date: Fri, 16 Feb 2024 11:34:14 +0000 Subject: [PATCH] Implemented v2.5 changes 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 | 6 +- src/app/bmc_application.rs | 10 +- src/hal.rs | 2 +- src/hal/gpio_definitions.rs | 4 + src/hal/pin_controller.rs | 273 ++++++++++++++++++++++++++++-------- src/hal/power_controller.rs | 16 +-- 6 files changed, 233 insertions(+), 78 deletions(-) diff --git a/src/api/legacy.rs b/src/api/legacy.rs index 2453322..71e8067 100644 --- a/src/api/legacy.rs +++ b/src/api/legacy.rs @@ -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 { 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), diff --git a/src/app/bmc_application.rs b/src/app/bmc_application.rs index 90197bb..78f2a3e 100644 --- a/src/app/bmc_application.rs +++ b/src/app/bmc_application.rs @@ -73,8 +73,10 @@ pub struct BmcApplication { impl BmcApplication { pub async fn new(database_write_timeout: Option) -> anyhow::Result { - 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(()) diff --git a/src/hal.rs b/src/hal.rs index 796ffa3..2918efc 100644 --- a/src/hal.rs +++ b/src/hal.rs @@ -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)] diff --git a/src/hal/gpio_definitions.rs b/src/hal/gpio_definitions.rs index a207510..4621b62 100644 --- a/src/hal/gpio_definitions.rs +++ b/src/hal/gpio_definitions.rs @@ -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; diff --git a/src/hal/pin_controller.rs b/src/hal/pin_controller.rs index 4be8eaf..47227a1 100644 --- a/src/hal/pin_controller.rs +++ b/src/hal/pin_controller.rs @@ -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, - usb_mux: Lines, - usb_switch: Lines, + usb_switch: Box, rpi_boot: [Lines; 4], - rtl_reset: Lines, } impl PinController { /// create a new Pin controller - pub fn new() -> anyhow::Result { + pub fn new(has_usb_switch: bool) -> anyhow::Result { + 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 + } 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, + usb_vbus: Lines, + output_switch: Lines, +} + +impl UsbMuxSwitch { + pub fn new(chip0: &Chip, chip1: &Chip) -> Result { + 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, +} + +impl UsbHub { + pub fn new(chip: &Chip) -> Result { + 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), } diff --git a/src/hal/power_controller.rs b/src/hal/power_controller.rs index e181234..ffc099b 100644 --- a/src/hal/power_controller.rs +++ b/src/hal/power_controller.rs @@ -41,8 +41,14 @@ pub struct PowerController { } impl PowerController { - pub fn new() -> anyhow::Result { - let chip1 = Chip::new("/dev/gpiochip1").context("gpiod chip1")?; + pub fn new(is_latching_system: bool) -> anyhow::Result { + 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"