diff --git a/bmcd/src/main.rs b/bmcd/src/main.rs index f4e026d..79fa4ff 100644 --- a/bmcd/src/main.rs +++ b/bmcd/src/main.rs @@ -91,6 +91,7 @@ fn init_logger() { .with_level(level) .with_module_level("bmcd", LevelFilter::Info) .with_module_level("actix_http", LevelFilter::Info) + .with_module_level("h2", LevelFilter::Info) .with_colors(true) .env() .init() diff --git a/tpi_rs/src/app/bmc_application.rs b/tpi_rs/src/app/bmc_application.rs index a452248..8dcac6f 100644 --- a/tpi_rs/src/app/bmc_application.rs +++ b/tpi_rs/src/app/bmc_application.rs @@ -75,7 +75,7 @@ impl BmcApplication { let current = self.nodes_on.load(Ordering::Relaxed); info!( - "toggling nodes {:#6b} to {}. reset hselfened: {}", + "toggling nodes {:#6b} to {}. reset: {}", node_values, if current { "off" } else { "on" }, reset_activation, @@ -153,6 +153,7 @@ impl BmcApplication { pub async fn power_on(&self) -> anyhow::Result<()> { let activated = self.app_db.get::(ACTIVATED_NODES_KEY).await; self.nodes_on.store(true, Ordering::Relaxed); + self.power_controller.power_led(true).await?; self.power_controller .set_power_node(activated, activated) .await @@ -160,6 +161,7 @@ impl BmcApplication { pub async fn power_off(&self) -> anyhow::Result<()> { self.nodes_on.store(false, Ordering::Relaxed); + self.power_controller.power_led(false).await?; self.power_controller.set_power_node(0b0000, 0b1111).await } @@ -178,13 +180,13 @@ impl BmcApplication { } pub async fn usb_boot(&self, node: NodeId, on: bool) -> anyhow::Result<()> { - let result = if on { - self.pin_controller.set_usb_boot(node) + let node_bits = node.to_bitfield(); + let (state, mask) = if on { + (node_bits, node_bits) } else { - self.pin_controller.clear_usb_boot() + (0u8, node_bits) }; - - Ok(result?) + Ok(self.pin_controller.set_usb_boot(state, mask)?) } pub async fn rtl_reset(&self) -> anyhow::Result<()> { @@ -238,7 +240,8 @@ impl BmcApplication { self.activate_slot(!node.to_bitfield(), node.to_bitfield()) .await?; - self.pin_controller.clear_usb_boot()?; + self.pin_controller + .set_usb_boot(!node.to_bitfield(), node.to_bitfield())?; sleep(REBOOT_DELAY).await; @@ -278,7 +281,7 @@ impl BmcApplication { pub fn clear_usb_boot(&self) -> anyhow::Result<()> { self.pin_controller - .clear_usb_boot() + .set_usb_boot(0u8, 0b1111) .context("error clearing usbboot") } diff --git a/tpi_rs/src/middleware/gpio_definitions.rs b/tpi_rs/src/middleware/gpio_definitions.rs index 78953dd..6e73bab 100644 --- a/tpi_rs/src/middleware/gpio_definitions.rs +++ b/tpi_rs/src/middleware/gpio_definitions.rs @@ -1,6 +1,5 @@ //! This module contains static pin numbers related to GPIO pub const GPIO_PIN_PG: u32 = 192; -pub const GPIO_PIN_PD: u32 = 96; pub const RTL_RESET: u32 = GPIO_PIN_PG + 13; #[allow(unused)] @@ -9,32 +8,25 @@ pub const SYS_RESET: u32 = GPIO_PIN_PG + 11; pub const POWER_DETECT: u32 = GPIO_PIN_PG + 10; #[allow(unused)] pub const POWER_BOARD: u32 = GPIO_PIN_PG + 15; - -pub const PORT1_EN: u32 = GPIO_PIN_PD + 11; -pub const PORT2_EN: u32 = GPIO_PIN_PD + 10; -pub const PORT3_EN: u32 = GPIO_PIN_PD + 9; -pub const PORT4_EN: u32 = GPIO_PIN_PD + 8; - -pub const MODE1_EN: u32 = GPIO_PIN_PD + 7; -pub const MODE2_EN: u32 = GPIO_PIN_PD + 6; -pub const MODE3_EN: u32 = GPIO_PIN_PD + 5; -pub const MODE4_EN: u32 = GPIO_PIN_PD + 4; -pub const POWER_EN: u32 = GPIO_PIN_PD + 3; - -pub const PORT1_USB_VBUS: u32 = GPIO_PIN_PD + 19; -pub const PORT2_USB_VBUS: u32 = GPIO_PIN_PD + 18; -pub const PORT3_USB_VBUS: u32 = GPIO_PIN_PD + 17; -pub const PORT4_USB_VBUS: u32 = GPIO_PIN_PD + 16; - -pub const PORT1_RPIBOOT: u32 = GPIO_PIN_PD + 15; -pub const PORT2_RPIBOOT: u32 = GPIO_PIN_PD + 14; -pub const PORT3_RPIBOOT: u32 = GPIO_PIN_PD + 12; -pub const PORT4_RPIBOOT: u32 = GPIO_PIN_PD + 13; - pub const USB_SEL1: u32 = GPIO_PIN_PG + 1; pub const USB_SEL2: u32 = GPIO_PIN_PG; // PG 0 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_PWEN: u32 = GPIO_PIN_PG + 4; + +// gpiochip1 aggregater +pub const NODE1_USBOTG_DEV: u32 = 2; +pub const NODE2_USBOTG_DEV: u32 = 6; +pub const NODE3_USBOTG_DEV: u32 = 10; +pub const NODE4_USBOTG_DEV: u32 = 14; + +pub const PORT1_EN: u32 = 0; +pub const PORT2_EN: u32 = 4; +pub const PORT3_EN: u32 = 8; +pub const PORT4_EN: u32 = 12; + +pub const PORT1_RPIBOOT: u32 = 3; +pub const PORT2_RPIBOOT: u32 = 7; +pub const PORT3_RPIBOOT: u32 = 11; +pub const PORT4_RPIBOOT: u32 = 15; diff --git a/tpi_rs/src/middleware/helpers.rs b/tpi_rs/src/middleware/helpers.rs index 32145fa..0338b0f 100644 --- a/tpi_rs/src/middleware/helpers.rs +++ b/tpi_rs/src/middleware/helpers.rs @@ -1,9 +1,11 @@ +const NODE_COUNT: u8 = 4; + /// small helper macro which handles the code duplication of declaring gpio lines. #[macro_export] macro_rules! gpio_output_lines { - ($chip:ident, $direction:expr, $output:expr) => { + ($chip:ident, $output:expr) => { $chip - .request_lines(gpiod::Options::output($output).active($direction)) + .request_lines(gpiod::Options::output($output)) .context(concat!("error initializing pin ", stringify!($output)))? }; } @@ -11,11 +13,33 @@ macro_rules! gpio_output_lines { /// uses [`gpio_output_lines`] to declare an array of `gpiod::Lines` objects #[macro_export] macro_rules! gpio_output_array { - ($chip:ident, $direction:expr, $($pin:ident),+) => { + ($chip:ident, $($pin:ident),+) => { [ $( - $crate::gpio_output_lines!($chip, $direction, [$pin]) + $crate::gpio_output_lines!($chip, [$pin]) ),* ] }; } + +/// Helper function that converts a bitfield + mask into an iterator. This +/// iterator iterates over each bit, and skips the bits that are not set in the +/// nodes_mask. +/// +/// # Arguments +/// +/// * `node_states` bit-field where each bit represents a node on the +/// turing-pi board, if bit(n) = 1 equals 'select' and bit(n) = 0 equals +/// 'unselect'. +/// * `node_mask` mask which bits to select. +/// +/// # Returns +/// +/// iterator returns a tuple containing the index of a bit + the new value. +pub fn bit_iterator(nodes_state: u8, nodes_mask: u8) -> impl Iterator { + (0..NODE_COUNT).filter_map(move |n| { + let mask = nodes_mask & (1 << n); + let state = (nodes_state & mask) >> n; + (mask != 0).then_some((n as usize, state)) + }) +} diff --git a/tpi_rs/src/middleware/pin_controller.rs b/tpi_rs/src/middleware/pin_controller.rs index 122bcd9..d9a9fb6 100644 --- a/tpi_rs/src/middleware/pin_controller.rs +++ b/tpi_rs/src/middleware/pin_controller.rs @@ -1,15 +1,17 @@ +use crate::gpio_output_array; use crate::gpio_output_lines; +use crate::middleware::helpers::bit_iterator; use super::gpio_definitions::*; use super::NodeId; use super::UsbMode; use super::UsbRoute; use anyhow::Context; -use gpiod::Active; use gpiod::{Chip, Lines, Output}; -use log::trace; +use log::debug; use std::time::Duration; use tokio::time::sleep; +const USB_PORT_POWER: &str = "/sys/bus/platform/devices/usb-port-power/state"; /// This middleware is responsible for controlling the gpio pins on the board, which includes USB /// multiplexers. Due to hardware limitations, only one node can be connected over the USB bus at a @@ -17,9 +19,8 @@ use tokio::time::sleep; pub struct PinController { usb_vbus: Lines, usb_mux: Lines, - usb_pwen: Lines, usb_switch: Lines, - rpi_boot: Lines, + rpi_boot: [Lines; 4], rtl_reset: Lines, } @@ -27,32 +28,33 @@ impl PinController { /// create a new Pin controller pub fn new() -> anyhow::Result { let chip0 = Chip::new("/dev/gpiochip0").context("gpiod chip0")?; + let chip1 = Chip::new("/dev/gpiochip1").context("gpiod chip1")?; let usb_vbus = gpio_output_lines!( - chip0, - Active::High, + chip1, [ - PORT1_USB_VBUS, - PORT2_USB_VBUS, - PORT3_USB_VBUS, - PORT4_USB_VBUS + NODE1_USBOTG_DEV, + NODE2_USBOTG_DEV, + NODE3_USBOTG_DEV, + NODE4_USBOTG_DEV ] ); - let rpi_boot = gpio_output_lines!( - chip0, - Active::Low, - [PORT1_RPIBOOT, PORT2_RPIBOOT, PORT3_RPIBOOT, PORT4_RPIBOOT] + let rpi_boot = gpio_output_array!( + chip1, + PORT1_RPIBOOT, + PORT2_RPIBOOT, + PORT3_RPIBOOT, + PORT4_RPIBOOT ); - let usb_mux = - gpio_output_lines!(chip0, Active::High, [USB_SEL1, USB_OE1, USB_SEL2, USB_OE2]); - let usb_switch = gpio_output_lines!(chip0, Active::High, [USB_SWITCH]); - let usb_pwen = gpio_output_lines!(chip0, Active::Low, [USB_PWEN]); - let rtl_reset = gpio_output_lines!(chip0, Active::Low, [RTL_RESET]); + 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"))?; Ok(Self { usb_vbus, usb_mux, - usb_pwen, usb_switch, rpi_boot, rtl_reset, @@ -61,7 +63,7 @@ impl PinController { /// 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<()> { - trace!("select USB for node {:?}, mode:{:?}", node, mode); + debug!("select USB for node {:?}, mode:{:?}", node, mode); let values: u8 = match node { NodeId::Node1 => 0b1100, NodeId::Node2 => 0b1101, @@ -80,30 +82,33 @@ impl PinController { /// Set which way the USB is routed: USB-A ↔ PORTx (`UsbRoute::UsbA`) or BMC ↔ PORTx /// (`UsbRoute::Bmc`) pub async fn set_usb_route(&self, route: UsbRoute) -> std::io::Result<()> { - trace!("select USB route {:?}", route); + debug!("select USB route {:?}", route); match route { UsbRoute::UsbA => { self.usb_switch.set_values(0_u8)?; - self.usb_pwen.set_values(1_u8) + tokio::fs::write(USB_PORT_POWER, b"enabled").await } UsbRoute::Bmc => { self.usb_switch.set_values(1_u8)?; - self.usb_pwen.set_values(0_u8) + tokio::fs::write(USB_PORT_POWER, b"disabled").await } } } /// 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, node: NodeId) -> std::io::Result<()> { - trace!("setting usbboot {:#06b}", node.to_bitfield()); - self.rpi_boot.set_values(node.to_bitfield()) - } + pub fn set_usb_boot(&self, nodes_state: u8, nodes_mask: u8) -> std::io::Result<()> { + let updates = bit_iterator(nodes_state, nodes_mask); - /// Clear USB boot mode of all nodes - pub fn clear_usb_boot(&self) -> std::io::Result<()> { - trace!("clearing usbboot pins"); - self.rpi_boot.set_values(0_u8) + for (idx, state) in updates { + debug!( + "updating usb_boot state of node {} to {}", + idx + 1, + if state != 0 { "enable" } else { "disable" } + ); + self.rpi_boot[idx].set_values(state)?; + } + Ok(()) } pub async fn rtl_reset(&self) -> std::io::Result<()> { diff --git a/tpi_rs/src/middleware/power_controller.rs b/tpi_rs/src/middleware/power_controller.rs index 1020dcf..7134be6 100644 --- a/tpi_rs/src/middleware/power_controller.rs +++ b/tpi_rs/src/middleware/power_controller.rs @@ -1,99 +1,45 @@ -use super::{gpio_definitions::*, NodeId}; -use crate::{gpio_output_array, gpio_output_lines}; +use super::{gpio_definitions::*, helpers::bit_iterator, NodeId}; +use crate::gpio_output_array; use anyhow::Context; -use gpiod::{Active, Chip, Lines, Output}; -use log::{debug, error, trace}; -use std::{ - sync::atomic::{AtomicU8, Ordering}, - time::Duration, -}; +use gpiod::{Chip, Lines, Output}; +use log::debug; +use std::time::Duration; use tokio::time::sleep; -const NODE_COUNT: u8 = 4; -const SYS_LED: &str = "/sys/class/leds/fp:sys/brightness"; - // This structure is a thin layer that abstracts away the interaction details // with Linux's power subsystem. pub struct PowerController { - mode: [Lines; 4], enable: [Lines; 4], - atx: Lines, - cache: AtomicU8, } impl PowerController { pub fn new() -> anyhow::Result { - let chip0 = Chip::new("/dev/gpiochip0").context("gpiod chip0")?; - - let current_state = { - let inputs = chip0.request_lines(gpiod::Options::input([ - PORT1_EN, PORT2_EN, PORT3_EN, PORT4_EN, - ]))?; - inputs.get_values(0b0000u8)? - }; - - let mode = gpio_output_array!(chip0, Active::High, MODE1_EN, MODE2_EN, MODE3_EN, MODE4_EN); - let enable = gpio_output_array!(chip0, Active::Low, PORT1_EN, PORT2_EN, PORT3_EN, PORT4_EN); - - let atx = gpio_output_lines!(chip0, Active::High, [POWER_EN]); - atx.set_values(0b1_u8)?; - - let cache = AtomicU8::new(current_state); - debug!("cache value:{:#06b}", cache.load(Ordering::Relaxed)); - - Ok(PowerController { - mode, - enable, - atx, - cache, - }) - } - - async fn update_state_and_atx(&self, node_states: u8, node_mask: u8) -> anyhow::Result { - let current = self - .cache - .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| { - Some((current & !node_mask) | (node_states & node_mask)) - }) - .expect("cas always returns Some"); - let new = self.cache.load(Ordering::Relaxed); - - if let Some(on) = need_atx_change(current, new) { - tokio::fs::write(SYS_LED, if on { "1" } else { "0" }).await?; - self.atx.set_values([on])?; - } + let chip1 = Chip::new("/dev/gpiochip1").context("gpiod chip1")?; + let enable = gpio_output_array!(chip1, PORT1_EN, PORT2_EN, PORT3_EN, PORT4_EN); - Ok(new) + Ok(PowerController { enable }) } - /// Function to power on/off given nodes. powering of the nodes is controlled by - /// the linux subsystem. + /// Function to power on/off given nodes. Powering of the nodes is controlled by + /// the Linux subsystem. /// /// # Arguments /// - /// * `node_states` bitfield representing the nodes on the turing-pi board, + /// * `node_states` bit-field representing the nodes on the turing-pi board, /// where bit 1 is on and 0 equals off. - /// * `node_mask` bitfield to describe which nodes to control. + /// * `node_mask` bit-field to describe which nodes to control. /// /// # Returns /// /// * `Ok(())` when routine was executed successfully. - /// * `Err(io error)` in the case there was a failure to write to the linux + /// * `Err(io error)` in the case there was a failure to write to the Linux /// subsystem that handles the node powering. pub async fn set_power_node(&self, node_states: u8, node_mask: u8) -> anyhow::Result<()> { - if let Err(e) = self.update_state_and_atx(node_states, node_mask).await { - error!("error updating atx regulator {}", e); - } - - let updates = (0..NODE_COUNT).filter_map(|n| { - let mask = node_mask & (1 << n); - let state = (node_states & mask) >> n; - (mask != 0).then_some((n as usize, state)) - }); + let updates = bit_iterator(node_states, node_mask); for (idx, state) in updates { - trace!("setting power of node {}. state:{}", idx + 1, state); - self.mode[idx].set_values(state)?; + debug!("setting power of node {}. state:{}", idx + 1, state); + set_mode(idx + 1, state).await?; sleep(Duration::from_millis(100)).await; self.enable[idx].set_values(state)?; } @@ -103,24 +49,18 @@ impl PowerController { /// Reset a given node by setting the reset pin logically high for 1 second pub async fn reset_node(&self, node: NodeId) -> anyhow::Result<()> { - trace!("reset node {:?}", node); - let idx = node as u8; + debug!("reset node {:?}", node); + let bits = node.to_bitfield(); - self.set_power_node(0u8, idx).await?; + self.set_power_node(0u8, bits).await?; sleep(Duration::from_secs(1)).await; - self.set_power_node(idx, idx).await?; + self.set_power_node(bits, bits).await?; Ok(()) } -} -/// Helper function that returns the new state of ATX power -fn need_atx_change(current_node_state: u8, next_node_state: u8) -> Option { - if current_node_state == 0 && next_node_state > 0 { - Some(true) - } else if current_node_state > 0 && next_node_state == 0 { - Some(false) - } else { - None + pub async fn power_led(&self, on: bool) -> std::io::Result<()> { + const SYS_LED: &str = "/sys/class/leds/fp:sys/brightness"; + tokio::fs::write(SYS_LED, if on { "1" } else { "0" }).await } } @@ -129,3 +69,14 @@ impl std::fmt::Debug for PowerController { write!(f, "PowerController") } } + +async fn set_mode(node_id: usize, node_state: u8) -> std::io::Result<()> { + let node_value = if node_state > 0 { + "enabled" + } else { + "disabled" + }; + + let sys_path = format!("/sys/bus/platform/devices/node{}-power/state", node_id); + tokio::fs::write(sys_path, node_value).await +}