Skip to content

Commit

Permalink
Gh-10 rework pin controller
Browse files Browse the repository at this point in the history
Rework to accomodate changes in the linux subsystem by #49.
  • Loading branch information
svenrademakers committed Sep 22, 2023
1 parent 71c0126 commit 08d27b0
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 90 deletions.
1 change: 1 addition & 0 deletions bmcd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 3 additions & 1 deletion tpi_rs/src/app/bmc_application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -153,13 +153,15 @@ impl BmcApplication {
pub async fn power_on(&self) -> anyhow::Result<()> {
let activated = self.app_db.get::<u8>(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
}

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
}

Expand Down
23 changes: 21 additions & 2 deletions tpi_rs/src/middleware/gpio_definitions.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -37,4 +36,24 @@ 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 aggregator
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;

Check failure on line 46 in tpi_rs/src/middleware/gpio_definitions.rs

View workflow job for this annotation

GitHub Actions / clippy

the name `PORT1_EN` is defined multiple times

error[E0428]: the name `PORT1_EN` is defined multiple times --> tpi_rs/src/middleware/gpio_definitions.rs:46:1 | 12 | pub const PORT1_EN: u32 = GPIO_PIN_PD + 11; | ------------------------------------------- previous definition of the value `PORT1_EN` here ... 46 | pub const PORT1_EN: u32 = 0; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `PORT1_EN` redefined here | = note: `PORT1_EN` must be defined only once in the value namespace of this module
pub const PORT2_EN: u32 = 4;

Check failure on line 47 in tpi_rs/src/middleware/gpio_definitions.rs

View workflow job for this annotation

GitHub Actions / clippy

the name `PORT2_EN` is defined multiple times

error[E0428]: the name `PORT2_EN` is defined multiple times --> tpi_rs/src/middleware/gpio_definitions.rs:47:1 | 13 | pub const PORT2_EN: u32 = GPIO_PIN_PD + 10; | ------------------------------------------- previous definition of the value `PORT2_EN` here ... 47 | pub const PORT2_EN: u32 = 4; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `PORT2_EN` redefined here | = note: `PORT2_EN` must be defined only once in the value namespace of this module
pub const PORT3_EN: u32 = 8;

Check failure on line 48 in tpi_rs/src/middleware/gpio_definitions.rs

View workflow job for this annotation

GitHub Actions / clippy

the name `PORT3_EN` is defined multiple times

error[E0428]: the name `PORT3_EN` is defined multiple times --> tpi_rs/src/middleware/gpio_definitions.rs:48:1 | 14 | pub const PORT3_EN: u32 = GPIO_PIN_PD + 9; | ------------------------------------------ previous definition of the value `PORT3_EN` here ... 48 | pub const PORT3_EN: u32 = 8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `PORT3_EN` redefined here | = note: `PORT3_EN` must be defined only once in the value namespace of this module
pub const PORT4_EN: u32 = 12;

Check failure on line 49 in tpi_rs/src/middleware/gpio_definitions.rs

View workflow job for this annotation

GitHub Actions / clippy

the name `PORT4_EN` is defined multiple times

error[E0428]: the name `PORT4_EN` is defined multiple times --> tpi_rs/src/middleware/gpio_definitions.rs:49:1 | 15 | pub const PORT4_EN: u32 = GPIO_PIN_PD + 8; | ------------------------------------------ previous definition of the value `PORT4_EN` here ... 49 | pub const PORT4_EN: u32 = 12; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `PORT4_EN` redefined here | = note: `PORT4_EN` must be defined only once in the value namespace of this module

pub const PORT1_RST: u32 = 1;
pub const PORT2_RST: u32 = 5;
pub const PORT3_RST: u32 = 9;
pub const PORT4_RST: u32 = 13;

pub const PORT1_RPIBOOT: u32 = 3;

Check failure on line 56 in tpi_rs/src/middleware/gpio_definitions.rs

View workflow job for this annotation

GitHub Actions / clippy

the name `PORT1_RPIBOOT` is defined multiple times

error[E0428]: the name `PORT1_RPIBOOT` is defined multiple times --> tpi_rs/src/middleware/gpio_definitions.rs:56:1 | 28 | pub const PORT1_RPIBOOT: u32 = GPIO_PIN_PD + 15; | ------------------------------------------------ previous definition of the value `PORT1_RPIBOOT` here ... 56 | pub const PORT1_RPIBOOT: u32 = 3; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `PORT1_RPIBOOT` redefined here | = note: `PORT1_RPIBOOT` must be defined only once in the value namespace of this module
pub const PORT2_RPIBOOT: u32 = 7;

Check failure on line 57 in tpi_rs/src/middleware/gpio_definitions.rs

View workflow job for this annotation

GitHub Actions / clippy

the name `PORT2_RPIBOOT` is defined multiple times

error[E0428]: the name `PORT2_RPIBOOT` is defined multiple times --> tpi_rs/src/middleware/gpio_definitions.rs:57:1 | 29 | pub const PORT2_RPIBOOT: u32 = GPIO_PIN_PD + 14; | ------------------------------------------------ previous definition of the value `PORT2_RPIBOOT` here ... 57 | pub const PORT2_RPIBOOT: u32 = 7; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `PORT2_RPIBOOT` redefined here | = note: `PORT2_RPIBOOT` must be defined only once in the value namespace of this module
pub const PORT3_RPIBOOT: u32 = 11;

Check failure on line 58 in tpi_rs/src/middleware/gpio_definitions.rs

View workflow job for this annotation

GitHub Actions / clippy

the name `PORT3_RPIBOOT` is defined multiple times

error[E0428]: the name `PORT3_RPIBOOT` is defined multiple times --> tpi_rs/src/middleware/gpio_definitions.rs:58:1 | 30 | pub const PORT3_RPIBOOT: u32 = GPIO_PIN_PD + 12; | ------------------------------------------------ previous definition of the value `PORT3_RPIBOOT` here ... 58 | pub const PORT3_RPIBOOT: u32 = 11; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `PORT3_RPIBOOT` redefined here | = note: `PORT3_RPIBOOT` must be defined only once in the value namespace of this module
pub const PORT4_RPIBOOT: u32 = 15;

Check failure on line 59 in tpi_rs/src/middleware/gpio_definitions.rs

View workflow job for this annotation

GitHub Actions / clippy

the name `PORT4_RPIBOOT` is defined multiple times

error[E0428]: the name `PORT4_RPIBOOT` is defined multiple times --> tpi_rs/src/middleware/gpio_definitions.rs:59:1 | 31 | pub const PORT4_RPIBOOT: u32 = GPIO_PIN_PD + 13; | ------------------------------------------------ previous definition of the value `PORT4_RPIBOOT` here ... 59 | pub const PORT4_RPIBOOT: u32 = 15; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `PORT4_RPIBOOT` redefined here | = note: `PORT4_RPIBOOT` must be defined only once in the value namespace of this module
31 changes: 15 additions & 16 deletions tpi_rs/src/middleware/pin_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ 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
/// time. This structure the GPIOD device library internally.
pub struct PinController {
usb_vbus: Lines<Output>,
usb_mux: Lines<Output>,
usb_pwen: Lines<Output>,
usb_switch: Lines<Output>,
rpi_boot: Lines<Output>,
rtl_reset: Lines<Output>,
Expand All @@ -27,32 +27,31 @@ impl PinController {
/// create a new Pin controller
pub fn new() -> anyhow::Result<Self> {
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,
chip1,
Active::High,
[
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,
chip1,
Active::Low,
[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]);

Ok(Self {
usb_vbus,
usb_mux,
usb_pwen,
usb_switch,
rpi_boot,
rtl_reset,
Expand All @@ -61,7 +60,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,
Expand All @@ -80,29 +79,29 @@ 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());
debug!("setting usbboot {:#06b}", node.to_bitfield());
self.rpi_boot.set_values(node.to_bitfield())
}

/// Clear USB boot mode of all nodes
pub fn clear_usb_boot(&self) -> std::io::Result<()> {
trace!("clearing usbboot pins");
debug!("clearing usbboot pins");
self.rpi_boot.set_values(0_u8)
}

Expand Down
101 changes: 30 additions & 71 deletions tpi_rs/src/middleware/power_controller.rs
Original file line number Diff line number Diff line change
@@ -1,99 +1,53 @@
use super::{gpio_definitions::*, NodeId};
use crate::{gpio_output_array, gpio_output_lines};
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 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<Output>; 4],
enable: [Lines<Output>; 4],
atx: Lines<Output>,
cache: AtomicU8,
}

impl PowerController {
pub fn new() -> anyhow::Result<Self> {
let chip0 = Chip::new("/dev/gpiochip0").context("gpiod chip0")?;
let chip1 = Chip::new("/dev/gpiochip1").context("gpiod chip1")?;
let enable =
gpio_output_array!(chip1, Active::High, PORT1_EN, PORT2_EN, PORT3_EN, PORT4_EN);

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<u8> {
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])?;
}

Ok(new)
Ok(PowerController { reset, enable })

Check failure on line 24 in tpi_rs/src/middleware/power_controller.rs

View workflow job for this annotation

GitHub Actions / clippy

struct `middleware::power_controller::PowerController` has no field named `reset`

error[E0560]: struct `middleware::power_controller::PowerController` has no field named `reset` --> tpi_rs/src/middleware/power_controller.rs:24:30 | 24 | Ok(PowerController { reset, enable }) | ^^^^^ `middleware::power_controller::PowerController` does not have this field | = note: available fields are: `mode`

Check failure on line 24 in tpi_rs/src/middleware/power_controller.rs

View workflow job for this annotation

GitHub Actions / clippy

cannot find value `reset` in this scope

error[E0425]: cannot find value `reset` in this scope --> tpi_rs/src/middleware/power_controller.rs:24:30 | 24 | Ok(PowerController { reset, enable }) | ^^^^^ not found in this scope
}

/// 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))
});

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)?;
}
Expand All @@ -103,24 +57,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 idx = node as usize;

self.set_power_node(0u8, idx).await?;

Check failure on line 63 in tpi_rs/src/middleware/power_controller.rs

View workflow job for this annotation

GitHub Actions / clippy

mismatched types

error[E0308]: mismatched types --> tpi_rs/src/middleware/power_controller.rs:63:34 | 63 | self.set_power_node(0u8, idx).await?; | -------------- ^^^ expected `u8`, found `usize` | | | arguments to this method are incorrect | note: method defined here --> tpi_rs/src/middleware/power_controller.rs:41:18 | 41 | pub async fn set_power_node(&self, node_states: u8, node_mask: u8) -> anyhow::Result<()> { | ^^^^^^^^^^^^^^ ------------- help: you can convert a `usize` to a `u8` and panic if the converted value doesn't fit | 63 | self.set_power_node(0u8, idx.try_into().unwrap()).await?; | ++++++++++++++++++++
sleep(Duration::from_secs(1)).await;
self.set_power_node(idx, idx).await?;

Check failure on line 65 in tpi_rs/src/middleware/power_controller.rs

View workflow job for this annotation

GitHub Actions / clippy

arguments to this method are incorrect

error[E0308]: arguments to this method are incorrect --> tpi_rs/src/middleware/power_controller.rs:65:14 | 65 | self.set_power_node(idx, idx).await?; | ^^^^^^^^^^^^^^ --- --- expected `u8`, found `usize` | | | expected `u8`, found `usize` | note: method defined here --> tpi_rs/src/middleware/power_controller.rs:41:18 | 41 | pub async fn set_power_node(&self, node_states: u8, node_mask: u8) -> anyhow::Result<()> { | ^^^^^^^^^^^^^^ --------------- ------------- help: you can convert a `usize` to a `u8` and panic if the converted value doesn't fit | 65 | self.set_power_node(idx.try_into().unwrap(), idx).await?; | ++++++++++++++++++++ help: you can convert a `usize` to a `u8` and panic if the converted value doesn't fit | 65 | self.set_power_node(idx, idx.try_into().unwrap()).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<bool> {
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 { "off" }).await
}
}

Expand All @@ -129,3 +77,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
}

0 comments on commit 08d27b0

Please sign in to comment.