diff --git a/.gitignore b/.gitignore index d95706df5..f068b441e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ itm.txt *.swp +.vscode/ diff --git a/Cargo.lock b/Cargo.lock index 903597506..3b5762fa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -926,6 +926,53 @@ dependencies = [ "zerocopy 0.6.6", ] +[[package]] +name = "drv-front-io-api" +version = "0.1.0" +dependencies = [ + "build-fpga-regmap", + "build-util", + "counters", + "derive-idol-err", + "drv-auxflash-api", + "drv-fpga-api", + "drv-i2c-api", + "drv-i2c-devices", + "hubpack", + "idol", + "idol-runtime", + "num-traits", + "serde", + "transceiver-messages", + "userlib", + "zerocopy 0.6.6", +] + +[[package]] +name = "drv-front-io-server" +version = "0.1.0" +dependencies = [ + "build-i2c", + "build-util", + "drv-auxflash-api", + "drv-fpga-api", + "drv-fpga-user-api", + "drv-front-io-api", + "drv-i2c-api", + "drv-i2c-devices", + "drv-transceivers-api", + "enum-map", + "hubpack", + "idol", + "idol-runtime", + "multitimer", + "num-traits", + "ringbuf", + "serde", + "userlib", + "zerocopy 0.6.6", +] + [[package]] name = "drv-gimlet-hf-api" version = "0.1.0" @@ -1559,30 +1606,6 @@ dependencies = [ "zerocopy 0.6.6", ] -[[package]] -name = "drv-sidecar-front-io" -version = "0.1.0" -dependencies = [ - "build-fpga-regmap", - "build-util", - "cfg-if", - "drv-auxflash-api", - "drv-fpga-api", - "drv-i2c-api", - "drv-i2c-devices", - "drv-transceivers-api", - "gnarle", - "idol", - "num-derive", - "num-traits", - "ringbuf", - "transceiver-messages", - "userlib", - "vsc7448-pac", - "vsc85xx", - "zerocopy 0.6.6", -] - [[package]] name = "drv-sidecar-mainboard-controller" version = "0.1.0" @@ -1623,6 +1646,7 @@ dependencies = [ "derive-idol-err", "drv-fpga-api", "drv-fpga-user-api", + "drv-front-io-api", "drv-sidecar-mainboard-controller", "hubpack", "idol", @@ -1644,10 +1668,10 @@ dependencies = [ "cortex-m", "drv-fpga-api", "drv-fpga-user-api", + "drv-front-io-api", "drv-i2c-api", "drv-i2c-devices", "drv-packrat-vpd-loader", - "drv-sidecar-front-io", "drv-sidecar-mainboard-controller", "drv-sidecar-seq-api", "hubpack", @@ -2071,6 +2095,7 @@ dependencies = [ "counters", "derive-idol-err", "drv-fpga-api", + "drv-front-io-api", "idol", "idol-runtime", "num-traits", @@ -2084,21 +2109,16 @@ dependencies = [ name = "drv-transceivers-server" version = "0.1.0" dependencies = [ - "build-i2c", "build-util", "cfg-if", "counters", "drv-fpga-api", - "drv-i2c-api", - "drv-i2c-devices", - "drv-sidecar-front-io", + "drv-front-io-api", "drv-sidecar-seq-api", "drv-transceivers-api", - "enum-map", "hubpack", "idol", "idol-runtime", - "multitimer", "num-traits", "ringbuf", "serde", @@ -4283,6 +4303,7 @@ dependencies = [ "drv-auxflash-api", "drv-caboose", "drv-caboose-pos", + "drv-front-io-api", "drv-gimlet-hf-api", "drv-gimlet-seq-api", "drv-ignition-api", @@ -4292,7 +4313,6 @@ dependencies = [ "drv-sprot-api", "drv-stm32h7-update-api", "drv-stm32h7-usart", - "drv-transceivers-api", "drv-update-api", "drv-user-leds-api", "gateway-messages", @@ -4545,8 +4565,8 @@ dependencies = [ "build-util", "cfg-if", "counters", + "drv-front-io-api", "drv-monorail-api", - "drv-sidecar-front-io", "drv-sidecar-mainboard-controller", "drv-sidecar-seq-api", "drv-spi-api", @@ -4869,6 +4889,7 @@ dependencies = [ "build-util", "cortex-m", "counters", + "drv-front-io-api", "drv-gimlet-seq-api", "drv-i2c-api", "drv-i2c-devices", diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index f5e25fac5..fc0fdf38f 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -113,7 +113,7 @@ task-slots = [ "sprot", "ignition", "packrat", - "transceivers", + "front_io", "vpd", ] features = ["sidecar", "vlan", "auxflash", "vpd"] @@ -158,7 +158,7 @@ max-sizes = {flash = 262144, ram = 16384} features = ["mgmt", "sidecar", "vlan", "use-spi-core", "h753", "spi2"] stacksize = 4096 start = true -task-slots = ["ecp5_front_io", "sys", { seq = "sequencer" }] +task-slots = ["front_io", "sys", { seq = "sequencer" }] uses = ["spi2"] notifications = ["spi-irq", "wake-timer"] interrupts = {"spi2.irq" = "spi-irq"} @@ -232,7 +232,6 @@ max-sizes = {flash = 65536, ram = 16384} stacksize = 4096 start = true task-slots = [ - "i2c_driver", "net", "sensor", "sys", @@ -241,6 +240,19 @@ task-slots = [ {seq = "sequencer"}] notifications = ["socket", "timer"] +[tasks.front_io] +name = "drv-front-io-server" +priority = 4 +max-sizes = {flash = 65536, ram = 16384} +stacksize = 4096 +start = true +task-slots = [ + "sys", + "auxflash", + "i2c_driver", + {front_io = "ecp5_front_io"}] +notifications = ["timer"] + [tasks.packrat] name = "task-packrat" priority = 3 @@ -1134,7 +1146,7 @@ memory-size = 33_554_432 # 256 Mib / 32 MiB slot-count = 16 # 2 MiB slots [[auxflash.blobs]] -file = "drv/sidecar-front-io/sidecar_qsfp_x32_controller_rev_b_c.bit" +file = "drv/front-io-api/sidecar_qsfp_x32_controller_rev_b_c.bit" compress = true tag = "QSFP" diff --git a/build/fpga-regmap/src/lib.rs b/build/fpga-regmap/src/lib.rs index 639ddecbd..2aefe656a 100644 --- a/build/fpga-regmap/src/lib.rs +++ b/build/fpga-regmap/src/lib.rs @@ -173,7 +173,10 @@ fn write_reg_fields( writeln!( output, " -{prefix} #[derive(Copy, Clone, Eq, PartialEq)] +{prefix} use hubpack::SerializedSize; +{prefix} use serde::{{Deserialize, Serialize}}; +{prefix} #[derive(Copy, Clone, Eq, PartialEq, Deserialize, Serialize, SerializedSize)] +{prefix} #[repr(u8)] {prefix} #[allow(dead_code)] {prefix} pub enum {encode_name} {{" ) diff --git a/drv/front-io-api/Cargo.toml b/drv/front-io-api/Cargo.toml new file mode 100644 index 000000000..745d4f52c --- /dev/null +++ b/drv/front-io-api/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "drv-front-io-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +hubpack.workspace = true +num-traits.workspace = true +idol-runtime.workspace = true +serde.workspace = true +transceiver-messages.workspace = true +zerocopy.workspace = true + +counters = { path = "../../lib/counters", features = ["derive"] } +drv-auxflash-api = { path = "../../drv/auxflash-api" } +drv-fpga-api = { path = "../fpga-api", features = ["auxflash"] } +drv-i2c-api = { path = "../i2c-api" } +drv-i2c-devices = { path = "../i2c-devices" } +derive-idol-err = { path = "../../lib/derive-idol-err" } +userlib = { path = "../../sys/userlib" } + +[build-dependencies] +build-fpga-regmap = { path = "../../build/fpga-regmap" } +build-util = { path = "../../build/util" } +idol.workspace = true + +[lib] +test = false +doctest = false +bench = false diff --git a/drv/sidecar-front-io/build.rs b/drv/front-io-api/build.rs similarity index 85% rename from drv/sidecar-front-io/build.rs rename to drv/front-io-api/build.rs index 98ba6465c..f5049077c 100644 --- a/drv/sidecar-front-io/build.rs +++ b/drv/front-io-api/build.rs @@ -1,17 +1,15 @@ // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. - use build_fpga_regmap::fpga_regs; use std::{fs, io::Write}; fn main() -> Result<(), Box> { build_util::expose_target_board(); - let board = build_util::env_var("HUBRIS_BOARD")?; - if board != "sidecar-b" && board != "sidecar-c" && board != "sidecar-d" { - panic!("unknown target board"); - } + idol::Generator::new() + .with_counters(idol::CounterSettings::default()) + .build_client_stub("../../idl/front-io.idol", "client_stub.rs")?; let out_dir = build_util::out_dir(); let out_file = out_dir.join("sidecar_qsfp_x32_controller_regs.rs"); diff --git a/drv/sidecar-front-io/sidecar_qsfp_x32_controller_regs.adoc b/drv/front-io-api/sidecar_qsfp_x32_controller_regs.adoc similarity index 100% rename from drv/sidecar-front-io/sidecar_qsfp_x32_controller_regs.adoc rename to drv/front-io-api/sidecar_qsfp_x32_controller_regs.adoc diff --git a/drv/sidecar-front-io/sidecar_qsfp_x32_controller_regs.html b/drv/front-io-api/sidecar_qsfp_x32_controller_regs.html similarity index 100% rename from drv/sidecar-front-io/sidecar_qsfp_x32_controller_regs.html rename to drv/front-io-api/sidecar_qsfp_x32_controller_regs.html diff --git a/drv/sidecar-front-io/sidecar_qsfp_x32_controller_regs.json b/drv/front-io-api/sidecar_qsfp_x32_controller_regs.json similarity index 100% rename from drv/sidecar-front-io/sidecar_qsfp_x32_controller_regs.json rename to drv/front-io-api/sidecar_qsfp_x32_controller_regs.json diff --git a/drv/sidecar-front-io/sidecar_qsfp_x32_controller_rev_b_c.bit b/drv/front-io-api/sidecar_qsfp_x32_controller_rev_b_c.bit similarity index 100% rename from drv/sidecar-front-io/sidecar_qsfp_x32_controller_rev_b_c.bit rename to drv/front-io-api/sidecar_qsfp_x32_controller_rev_b_c.bit diff --git a/drv/sidecar-front-io/src/controller.rs b/drv/front-io-api/src/controller.rs similarity index 98% rename from drv/sidecar-front-io/src/controller.rs rename to drv/front-io-api/src/controller.rs index ef7a20556..0c40e651a 100644 --- a/drv/sidecar-front-io/src/controller.rs +++ b/drv/front-io-api/src/controller.rs @@ -8,7 +8,7 @@ use userlib::UnwrapLite; pub struct FrontIOController { fpga: Fpga, - user_design: FpgaUserDesign, + pub user_design: FpgaUserDesign, } impl FrontIOController { diff --git a/drv/sidecar-front-io/src/leds.rs b/drv/front-io-api/src/leds.rs similarity index 99% rename from drv/sidecar-front-io/src/leds.rs rename to drv/front-io-api/src/leds.rs index ffd73d1af..e0bfc4229 100644 --- a/drv/sidecar-front-io/src/leds.rs +++ b/drv/front-io-api/src/leds.rs @@ -1,10 +1,9 @@ // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use super::transceivers::{LogicalPort, LogicalPortMask}; +use crate::transceivers::{LogicalPort, LogicalPortMask, NUM_PORTS}; use drv_i2c_api::I2cDevice; use drv_i2c_devices::pca9956b::{Error, LedErr, Pca9956B}; -use drv_transceivers_api::NUM_PORTS; /// Leds controllers and brightness state pub struct Leds { diff --git a/drv/front-io-api/src/lib.rs b/drv/front-io-api/src/lib.rs new file mode 100644 index 000000000..5f7055378 --- /dev/null +++ b/drv/front-io-api/src/lib.rs @@ -0,0 +1,79 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! API crate for the Front IO server. + +#![no_std] + +use crate::phy_smi::PhyOscState; +use counters::Count; +use derive_idol_err::IdolError; +use drv_fpga_api::FpgaError; +use hubpack::SerializedSize; +use serde::{Deserialize, Serialize}; +use userlib::*; + +// Re-export for use in the server +pub use transceiver_messages::message::LedState; + +pub mod controller; +pub mod leds; +pub mod phy_smi; +pub mod transceivers; + +#[derive( + Copy, Clone, Debug, FromPrimitive, Eq, PartialEq, IdolError, Count, +)] +pub enum FrontIOError { + FpgaError = 1, + NotPresent, + NotReady, + InvalidPortNumber, + InvalidNumberOfBytes, + InvalidPhysicalToLogicalMap, + InvalidModuleResult, + LedInitFailure, + + #[idol(server_death)] + ServerRestarted, +} + +impl From for FrontIOError { + fn from(_: FpgaError) -> Self { + Self::FpgaError + } +} + +#[derive( + Copy, + Clone, + Debug, + Count, + Eq, + PartialEq, + Deserialize, + Serialize, + SerializedSize, +)] +pub enum FrontIOStatus { + /// No board detected + NotPresent, + /// The FPGAs are being configured + FpgaInit, + /// Confirming that the PHY oscillator is behaving + OscInit, + /// Board is present and fully operational + Ready, +} + +include!(concat!( + env!("OUT_DIR"), + "/sidecar_qsfp_x32_controller_regs.rs" +)); + +use crate::transceivers::{ + LogicalPort, LogicalPortMask, ModuleResult, ModuleResultNoFailure, + PortI2CStatus, TransceiverStatus, +}; +include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/drv/sidecar-front-io/src/phy_smi.rs b/drv/front-io-api/src/phy_smi.rs similarity index 89% rename from drv/sidecar-front-io/src/phy_smi.rs rename to drv/front-io-api/src/phy_smi.rs index 4b6a04bb7..3a6f271e6 100644 --- a/drv/sidecar-front-io/src/phy_smi.rs +++ b/drv/front-io-api/src/phy_smi.rs @@ -2,18 +2,18 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use core::cell::Cell; - use crate::{Addr, Reg}; +use core::cell::Cell; use drv_fpga_api::{FpgaError, FpgaUserDesign, WriteOp}; -use vsc85xx::{PhyRw, VscError}; +use userlib::FromPrimitive; use zerocopy::{byteorder, AsBytes, FromBytes, Unaligned, U16}; -#[derive(Copy, Clone, Eq, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, FromPrimitive, Eq, PartialEq, AsBytes)] +#[repr(u8)] pub enum PhyOscState { - Unknown, - Bad, - Good, + Unknown = 0, + Bad = 1, + Good = 2, } pub struct PhySmi { @@ -119,7 +119,7 @@ impl PhySmi { } #[inline(never)] - fn read_raw_inner(&self, phy: u8, reg: u8) -> Result { + pub fn read_raw(&self, phy: u8, reg: u8) -> Result { let request = SmiReadRequest { phy, reg, @@ -152,7 +152,7 @@ impl PhySmi { } #[inline(never)] - fn write_raw_inner( + pub fn write_raw( &self, phy: u8, reg: u8, @@ -174,20 +174,6 @@ impl PhySmi { } } -impl PhyRw for PhySmi { - #[inline(always)] - fn read_raw(&self, phy: u8, reg: u8) -> Result { - self.read_raw_inner(phy, reg) - .map_err(|e| VscError::ProxyError(e.into())) - } - - #[inline(always)] - fn write_raw(&self, phy: u8, reg: u8, value: u16) -> Result<(), VscError> { - self.write_raw_inner(phy, reg, value) - .map_err(|e| VscError::ProxyError(e.into())) - } -} - #[derive(AsBytes, FromBytes, Unaligned)] #[repr(C)] struct SmiWriteRequest { diff --git a/drv/sidecar-front-io/src/transceivers.rs b/drv/front-io-api/src/transceivers.rs similarity index 91% rename from drv/sidecar-front-io/src/transceivers.rs rename to drv/front-io-api/src/transceivers.rs index d339f0af8..e0331ea77 100644 --- a/drv/sidecar-front-io/src/transceivers.rs +++ b/drv/front-io-api/src/transceivers.rs @@ -2,13 +2,57 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::{Addr, Reg}; +use crate::{Addr, FrontIOError, Reg}; use drv_fpga_api::{FpgaError, FpgaUserDesign, ReadOp, WriteOp}; -use drv_transceivers_api::{ModuleStatus, TransceiversError, NUM_PORTS}; -use transceiver_messages::ModuleId; +use hubpack::SerializedSize; +use serde::{Deserialize, Serialize}; +use transceiver_messages::{message::LedState, ModuleId}; use userlib::UnwrapLite; use zerocopy::{byteorder, AsBytes, FromBytes, Unaligned, U16}; +/// The only instantiation of Front IO board that exists is one with 32 QSFP +/// ports. +pub const NUM_PORTS: u8 = 32; + +/// Size in bytes of a page section we will read or write +/// +/// QSFP module's internal memory map is 256 bytes, with the lower 128 being +/// static and then the upper 128 are paged in. The internal address register +/// is only 7 bits, so you can only access half in any single transaction and +/// thus our communication mechanisms have been designed for that. +/// See SFF-8636 and CMIS specifications for details. +pub const PAGE_SIZE_BYTES: usize = 128; + +#[derive(Copy, Clone)] +pub struct LedStates([LedState; NUM_PORTS as usize]); + +impl LedStates { + pub fn set(mut self, mask: LogicalPortMask, state: LedState) { + for port in mask.to_indices() { + self.0[port.0 as usize] = state + } + } + + pub fn get(self, port: LogicalPort) -> LedState { + self.0[port.0 as usize] + } +} + +impl Default for LedStates { + fn default() -> Self { + LedStates([LedState::Off; NUM_PORTS as usize]) + } +} + +impl<'a> IntoIterator for &'a LedStates { + type Item = &'a LedState; + type IntoIter = core::slice::Iter<'a, LedState>; + + fn into_iter(self) -> Self::IntoIter { + self.0.as_slice().iter() + } +} + // The transceiver modules are split across two FPGAs on the QSFP Front IO // board, so while we present the modules as a unit, the communication is // actually bifurcated. @@ -18,14 +62,16 @@ pub struct Transceivers { // There are two FPGA controllers, each controlling the FPGA on either the left // or right of the board. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive( + Copy, Clone, PartialEq, Eq, Deserialize, Serialize, SerializedSize, +)] pub enum FpgaController { Left = 0, Right = 1, } /// Physical port location -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, SerializedSize)] pub struct PortLocation { pub controller: FpgaController, pub port: PhysicalPort, @@ -38,7 +84,7 @@ impl From for PortLocation { } /// Physical port location within a particular FPGA, as a 0-15 index -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, SerializedSize)] pub struct PhysicalPort(pub u8); impl PhysicalPort { pub fn as_mask(&self) -> PhysicalPortMask { @@ -52,14 +98,14 @@ impl PhysicalPort { pub fn to_logical_port( &self, fpga: FpgaController, - ) -> Result { + ) -> Result { let loc = PortLocation { controller: fpga, port: *self, }; match PORT_MAP.into_iter().position(|&l| l == loc) { Some(p) => Ok(LogicalPort(p as u8)), - None => Err(TransceiversError::InvalidPhysicalToLogicalMap), + None => Err(FrontIOError::InvalidPhysicalToLogicalMap), } } } @@ -120,7 +166,19 @@ impl FpgaPortMasks { } /// Represents a single logical port (0-31) -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +#[derive( + Copy, + Clone, + Debug, + PartialEq, + PartialOrd, + FromBytes, + AsBytes, + Deserialize, + Serialize, + SerializedSize, +)] +#[repr(transparent)] pub struct LogicalPort(pub u8); impl LogicalPort { pub fn as_mask(&self) -> LogicalPortMask { @@ -132,7 +190,20 @@ impl LogicalPort { } } /// Represents a set of selected logical ports, i.e. a 32-bit bitmask -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[derive( + Copy, + Clone, + Debug, + Default, + PartialEq, + Eq, + FromBytes, + AsBytes, + Deserialize, + Serialize, + SerializedSize, +)] +#[repr(transparent)] pub struct LogicalPortMask(pub u32); impl LogicalPortMask { @@ -489,7 +560,10 @@ const RIGHT_LOGICAL_MASK: LogicalPortMask = LogicalPortMask(0xff00ff00); /// - The module operation succeeded /// - The module operation failed /// - The module could not be interacted with due to an FPGA communication error -#[derive(Copy, Clone, Default, PartialEq)] +#[derive( + Copy, Clone, Default, PartialEq, Deserialize, Serialize, SerializedSize, +)] +#[repr(C)] pub struct ModuleResult { success: LogicalPortMask, failure: LogicalPortMask, @@ -524,12 +598,12 @@ impl ModuleResult { failure: LogicalPortMask, error: LogicalPortMask, failure_types: LogicalPortFailureTypes, - ) -> Result { + ) -> Result { if !(success & failure).is_empty() || !(success & error).is_empty() || !(failure & error).is_empty() { - return Err(TransceiversError::InvalidModuleResult); + return Err(FrontIOError::InvalidModuleResult); } Ok(Self { success, @@ -623,7 +697,18 @@ impl ModuleResult { /// handle a mix of the following cases on a per-module basis: /// - The module operation succeeded /// - The module could not be interacted with due to an FPGA communication error -#[derive(Copy, Clone, Default, PartialEq)] +#[derive( + Copy, + Clone, + Default, + PartialEq, + FromBytes, + AsBytes, + Deserialize, + Serialize, + SerializedSize, +)] +#[repr(C)] pub struct ModuleResultNoFailure { success: LogicalPortMask, error: LogicalPortMask, @@ -634,9 +719,9 @@ impl ModuleResultNoFailure { pub fn new( success: LogicalPortMask, error: LogicalPortMask, - ) -> Result { + ) -> Result { if !(success & error).is_empty() { - return Err(TransceiversError::InvalidModuleResult); + return Err(FrontIOError::InvalidModuleResult); } Ok(Self { success, error }) } @@ -651,6 +736,7 @@ impl ModuleResultNoFailure { } /// A type to provide more ergonomic access to the FPGA generated type +// #[derive(AsBytes, FromBytes)] pub type FpgaI2CFailure = Reg::QSFP::PORT0_STATUS::ErrorEncoded; /// A type to consolidate per-module failure types. @@ -658,7 +744,8 @@ pub type FpgaI2CFailure = Reg::QSFP::PORT0_STATUS::ErrorEncoded; /// Currently the only types of operations that can be considered failures are /// those that involve the FPGA doing I2C. Thus, that is the only supported type /// right now. -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Deserialize, Serialize, SerializedSize)] +#[repr(C)] pub struct LogicalPortFailureTypes(pub [FpgaI2CFailure; NUM_PORTS as usize]); impl Default for LogicalPortFailureTypes { @@ -675,7 +762,7 @@ impl core::ops::Index for LogicalPortFailureTypes { } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Deserialize, Serialize, SerializedSize)] pub struct PortI2CStatus { pub rdata_fifo_empty: bool, pub wdata_fifo_empty: bool, @@ -883,12 +970,6 @@ impl Transceivers { ) } - /// Clear a fault for each port per the specified `mask`. - /// - /// The meaning of the - /// returned `ModuleResultNoFailure`: - /// success: we were able to write to the FPGA - /// error: an `FpgaError` occurred pub fn clear_power_fault( &self, mask: LogicalPortMask, @@ -928,36 +1009,6 @@ impl Transceivers { ModuleResultNoFailure::new(success, error).unwrap_lite() } - /// Initiate an I2C random read on all ports per the specified `mask`. - /// - /// The maximum value of `num_bytes` is 128.The meaning of the returned - /// `ModuleResultNoFailure`: - /// success: we were able to write to the FPGA - /// error: an `FpgaError` occurred - pub fn setup_i2c_read( - &self, - reg: u8, - num_bytes: u8, - mask: LogicalPortMask, - ) -> ModuleResultNoFailure { - self.setup_i2c_op(true, reg, num_bytes, mask) - } - - /// Initiate an I2C write on all ports per the specified `mask`. - /// - /// The maximum value of `num_bytes` is 128. The meaning of the - /// returned `ModuleResultNoFailure`: - /// success: we were able to write to the FPGA - /// error: an `FpgaError` occurred - pub fn setup_i2c_write( - &self, - reg: u8, - num_bytes: u8, - mask: LogicalPortMask, - ) -> ModuleResultNoFailure { - self.setup_i2c_op(false, reg, num_bytes, mask) - } - /// Initiate an I2C operation on all ports per the specified `mask`. /// /// When `is_read` is true, the operation will be a random-read, not a pure @@ -965,7 +1016,7 @@ impl Transceivers { /// The meaning of the returned `ModuleResultNoFailure`: /// success: we were able to write to the FPGA /// error: an `FpgaError` occurred - fn setup_i2c_op( + pub fn setup_i2c_op( &self, is_read: bool, reg: u8, @@ -1125,45 +1176,6 @@ impl Transceivers { ModuleResultNoFailure::new(success, error).unwrap_lite() } - /// Apply reset to the LED controller - /// - /// Per section 7.6 of the datasheet the minimum required pulse width here - /// is 2.5 microseconds. Given the SPI interface runs at 3MHz, the - /// transaction to clear the reset would take ~10 microseconds on its own, - /// so there is no additional delay here. - pub fn assert_led_controllers_reset(&mut self) -> Result<(), FpgaError> { - for fpga in &self.fpgas { - fpga.write(WriteOp::BitSet, Addr::LED_CTRL, Reg::LED_CTRL::RESET)?; - } - Ok(()) - } - - /// Remove reset from the LED controller - /// - /// Per section 7.6 of the datasheet the device has a maximum wait time of - /// 1.5 milliseconds after the release of reset to normal operation, so - /// there is a 2 millisecond wait here. - pub fn deassert_led_controllers_reset(&mut self) -> Result<(), FpgaError> { - for fpga in &self.fpgas { - fpga.write( - WriteOp::BitClear, - Addr::LED_CTRL, - Reg::LED_CTRL::RESET, - )?; - } - userlib::hl::sleep_for(2); - Ok(()) - } - - /// Releases the LED controller from reset and enables the output - pub fn enable_led_controllers(&mut self) -> Result<(), FpgaError> { - self.deassert_led_controllers_reset()?; - for fpga in &self.fpgas { - fpga.write(WriteOp::BitSet, Addr::LED_CTRL, Reg::LED_CTRL::OE)?; - } - Ok(()) - } - /// Waits for all of the I2C busy bits to go low /// /// Returns a set of masks indicating which channels (among the ones active @@ -1267,3 +1279,27 @@ pub struct TransceiversI2CRequest { mask: U16, op: u8, } + +/// Each field is a bitmask of the 32 transceivers in big endian order, which +/// results in Port 31 being bit 31, and so forth. +#[derive(Copy, Clone, Default, FromBytes, AsBytes)] +#[repr(C)] +pub struct ModuleStatus { + pub power_enable: u32, + pub power_good: u32, + pub power_good_timeout: u32, + pub power_good_fault: u32, + pub resetl: u32, + pub lpmode_txdis: u32, + pub modprsl: u32, + pub intl_rxlosl: u32, +} + +/// Composite struct to package a ModuleStatus and a ModuleResultNoFailure for +/// IPC +#[derive(Copy, Clone, Default, FromBytes, AsBytes)] +#[repr(C)] +pub struct TransceiverStatus { + pub status: ModuleStatus, + pub result: ModuleResultNoFailure, +} diff --git a/drv/front-io-server/Cargo.toml b/drv/front-io-server/Cargo.toml new file mode 100644 index 000000000..1e5b11a20 --- /dev/null +++ b/drv/front-io-server/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "drv-front-io-server" +version = "0.1.0" +edition = "2021" +authors = ["Arjen Roodselaar ", "Aaron Hartwig "] + +[dependencies] +enum-map.workspace = true +hubpack.workspace = true +idol-runtime.workspace = true +num-traits.workspace = true +serde.workspace = true +zerocopy.workspace = true + +drv-auxflash-api = { path = "../../drv/auxflash-api" } +drv-front-io-api = { path = "../front-io-api" } +drv-fpga-api = { path = "../fpga-api" } +drv-fpga-user-api = { path = "../fpga-user-api" } +drv-i2c-api = { path = "../i2c-api" } +drv-i2c-devices = { path = "../i2c-devices" } +drv-transceivers-api = { path = "../../drv/transceivers-api" } +multitimer = { path = "../../lib/multitimer" } +ringbuf = { path = "../../lib/ringbuf" } +userlib = { path = "../../sys/userlib", features = ["panic-messages"] } + +[build-dependencies] +build-util = { path = "../../build/util" } +build-i2c = { path = "../../build/i2c" } +idol = { workspace = true } + +# This section is here to discourage RLS/rust-analyzer from doing test builds, +# since test builds don't work for cross compilation. +[[bin]] +name = "drv-front-io-server" +test = false +doctest = false +bench = false diff --git a/drv/sidecar-front-io/README.md b/drv/front-io-server/README.md similarity index 100% rename from drv/sidecar-front-io/README.md rename to drv/front-io-server/README.md diff --git a/drv/front-io-server/build.rs b/drv/front-io-server/build.rs new file mode 100644 index 000000000..4b77dc2dd --- /dev/null +++ b/drv/front-io-server/build.rs @@ -0,0 +1,30 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +fn main() -> Result<(), Box> { + build_util::build_notifications()?; + + let disposition = build_i2c::Disposition::Devices; + if let Err(e) = build_i2c::codegen(disposition) { + println!("code generation failed: {}", e); + std::process::exit(1); + } + + let board = build_util::env_var("HUBRIS_BOARD")?; + if board != "sidecar-b" && board != "sidecar-c" && board != "sidecar-d" { + panic!("unknown target board"); + } + + idol::Generator::new() + .with_counters( + idol::CounterSettings::default().with_server_counters(false), + ) + .build_server_support( + "../../idl/front-io.idol", + "server_stub.rs", + idol::server::ServerStyle::InOrder, + )?; + + Ok(()) +} diff --git a/drv/front-io-server/src/main.rs b/drv/front-io-server/src/main.rs new file mode 100644 index 000000000..fa53361a7 --- /dev/null +++ b/drv/front-io-server/src/main.rs @@ -0,0 +1,886 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Server for managing the Front IO board + +#![no_std] +#![no_main] + +use core::convert::Infallible; +use drv_fpga_api::{DeviceState, FpgaError, WriteOp}; +use drv_front_io_api::{ + controller::FrontIOController, + leds::{FullErrorSummary, Leds}, + phy_smi::{PhyOscState, PhySmi}, + transceivers::{ + LedStates, LogicalPort, LogicalPortMask, ModuleResult, + ModuleResultNoFailure, PortI2CStatus, TransceiverStatus, Transceivers, + NUM_PORTS, PAGE_SIZE_BYTES, + }, + Addr, FrontIOError, FrontIOStatus, LedState, Reg, +}; +use drv_i2c_devices::{at24csw080::At24Csw080, pca9956b, Validate}; +use enum_map::Enum; +use idol_runtime::{ + ClientError, Leased, NotificationHandler, RequestError, R, W, +}; +use multitimer::{Multitimer, Repeat}; +use ringbuf::*; +use userlib::*; + +task_slot!(I2C, i2c_driver); +task_slot!(FRONT_IO, front_io); +task_slot!(AUXFLASH, auxflash); + +include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); + +#[derive(Copy, Clone, PartialEq)] +enum Trace { + None, + SequenceUpdate(FrontIOStatus), + FpgaInitError(FpgaError), + PhyOscGood, + PhyOscBad, + LEDInitComplete, + LEDInitError(pca9956b::Error), + LEDUpdateError(pca9956b::Error), + LEDReadError(pca9956b::Error), + LEDErrorSummary(FullErrorSummary), + LEDUninitialized, + FpgaBitstreamError(u32), + LoadingFrontIOControllerBitstream { + fpga_id: usize, + }, + SkipLoadingFrontIOControllerBitstream { + fpga_id: usize, + }, + FrontIOControllerIdent { + fpga_id: usize, + ident: u32, + }, + FrontIOControllerChecksum { + fpga_id: usize, + checksum: [u8; 4], + expected: [u8; 4], + }, +} +ringbuf!(Trace, 32, Trace::None); + +struct ServerImpl { + /// Handle for the auxflash task + auxflash_task: userlib::TaskId, + + /// Handles for each FPGA + controllers: [FrontIOController; 2], + + /// Interface for the LED controllers + leds: Leds, + + /// VSC8562 SMI Interface + phy_smi: PhySmi, + + /// Interface for the trnasceivers + transceivers: Transceivers, + + /// Status of the Front IO board + board_status: FrontIOStatus, + + /// State to allow blinking LEDs to be in phase + led_blink_on: bool, + /// State around LED management + led_error: FullErrorSummary, + leds_initialized: bool, + led_states: LedStates, + system_led_state: LedState, +} + +/// Controls how often we update the LED controllers (in milliseconds). +const I2C_INTERVAL: u64 = 100; + +/// Blink LEDs at a 50% duty cycle (in milliseconds) +const BLINK_INTERVAL: u64 = 500; + +/// How often we should attempt the next sequencing step (in milliseconds) +const SEQ_INTERVAL: u64 = 500; + +impl ServerImpl { + fn initialized(&self) -> bool { + self.controllers.iter().all(|c| c.ready().unwrap_or(false)) + } + + fn fpga_init(&mut self) -> Result { + let mut controllers_ready = true; + + for (i, controller) in self.controllers.iter_mut().enumerate() { + let state = controller.await_fpga_ready(25)?; + let mut ident; + let mut ident_valid = false; + let mut checksum; + let mut checksum_valid = false; + + if state == DeviceState::RunningUserDesign { + (ident, ident_valid) = controller.ident_valid()?; + ringbuf_entry!(Trace::FrontIOControllerIdent { + fpga_id: i, + ident + }); + + (checksum, checksum_valid) = controller.checksum_valid()?; + ringbuf_entry!(Trace::FrontIOControllerChecksum { + fpga_id: i, + checksum, + expected: FrontIOController::short_checksum(), + }); + + if !ident_valid || !checksum_valid { + // Attempt to correct the invalid IDENT by reloading the + // bitstream. + controller.fpga_reset()?; + } + } + + if ident_valid && checksum_valid { + ringbuf_entry!(Trace::SkipLoadingFrontIOControllerBitstream { + fpga_id: i + }); + } else { + ringbuf_entry!(Trace::LoadingFrontIOControllerBitstream { + fpga_id: i + }); + + if let Err(e) = controller.load_bitstream(self.auxflash_task) { + ringbuf_entry!(Trace::FpgaBitstreamError(u32::from(e))); + return Err(e); + } + + (ident, ident_valid) = controller.ident_valid()?; + ringbuf_entry!(Trace::FrontIOControllerIdent { + fpga_id: i, + ident + }); + + controller.write_checksum()?; + (checksum, checksum_valid) = controller.checksum_valid()?; + ringbuf_entry!(Trace::FrontIOControllerChecksum { + fpga_id: i, + checksum, + expected: FrontIOController::short_checksum(), + }); + } + + controllers_ready &= ident_valid & checksum_valid; + } + + Ok(controllers_ready) + } + + fn leds_set_reset(&self, assert: bool) -> Result<(), FrontIOError> { + let op = match assert { + true => WriteOp::BitSet, + false => WriteOp::BitClear, + }; + + for c in &self.controllers { + c.user_design + .write(op, Addr::LED_CTRL, Reg::LED_CTRL::RESET) + .map_err(FrontIOError::from)?; + } + + Ok(()) + } + + fn set_system_led_state(&mut self, state: LedState) { + // TODO: Add a ringbuf entry? + self.system_led_state = state; + } + + fn update_leds(&mut self) { + // handle port LEDs + let mut next_state = LogicalPortMask(0); + for (i, state) in self.led_states.into_iter().enumerate() { + let i = LogicalPort(i as u8); + match state { + LedState::On => next_state.set(i), + LedState::Blink => { + if self.led_blink_on { + next_state.set(i) + } + } + LedState::Off => (), + } + } + if let Err(e) = self.leds.update_led_state(next_state) { + ringbuf_entry!(Trace::LEDUpdateError(e)); + } + // handle system LED + let system_led_on = match self.system_led_state { + LedState::On => true, + LedState::Blink => self.led_blink_on, + LedState::Off => false, + }; + if let Err(e) = self.leds.update_system_led_state(system_led_on) { + ringbuf_entry!(Trace::LEDUpdateError(e)); + } + } + + fn handle_i2c_loop(&mut self) { + if self.leds_initialized { + self.update_leds(); + let errors = match self.leds.error_summary() { + Ok(errs) => errs, + Err(e) => { + ringbuf_entry!(Trace::LEDReadError(e)); + Default::default() + } + }; + if errors != self.led_error { + self.led_error = errors; + ringbuf_entry!(Trace::LEDErrorSummary(errors)); + } + } else { + ringbuf_entry!(Trace::LEDUninitialized); + } + } +} + +impl idl::InOrderFrontIOImpl for ServerImpl { + fn board_reset( + &mut self, + _: &RecvMessage, + ) -> Result<(), RequestError> { + *self = ServerImpl::default(); + Ok(()) + } + + fn board_status( + &mut self, + _: &RecvMessage, + ) -> Result> { + Ok(self.board_status) + } + + fn board_present( + &mut self, + _: &RecvMessage, + ) -> Result> { + Ok(self.board_status != FrontIOStatus::NotPresent) + } + + fn board_ready( + &mut self, + _: &RecvMessage, + ) -> Result> { + Ok(self.board_status == FrontIOStatus::Ready) + } + + fn phy_osc_state( + &mut self, + _: &RecvMessage, + ) -> Result> { + self.phy_smi + .osc_state() + .map_err(FrontIOError::from) + .map_err(RequestError::from) + } + + fn phy_ready( + &mut self, + _: &RecvMessage, + ) -> Result> { + self.phy_smi + .powered_up_and_ready() + .map_err(FrontIOError::from) + .map_err(RequestError::from) + } + + fn phy_set_osc_state( + &mut self, + _: &RecvMessage, + good: bool, + ) -> Result<(), RequestError> { + match self + .phy_smi + .osc_state() + .map_err(FrontIOError::from) + .map_err(RequestError::from)? + { + // The state of the oscillator has not yet been examined or was + // marked bad in the previous run. Update as appropriate. + PhyOscState::Unknown | PhyOscState::Bad => { + ringbuf_entry!(if good { + Trace::PhyOscGood + } else { + Trace::PhyOscBad + }); + + self.phy_smi + .set_osc_good(good) + .map_err(FrontIOError::from) + .map_err(RequestError::from) + } + // The oscillator is already marked good and this state only changes + // if it (and by extension the whole front IO board) is power + // cycled. In that case the value of this register in the FPGA is + // automatically reset when the bitstream is loaded and the other + // arm of this match would be taken. + // + // So ignore this call if the oscillator has been found good since the last power + // cycle of the front IO board. + PhyOscState::Good => Ok(()), + } + } + + fn phy_enable_power( + &mut self, + _: &RecvMessage, + ) -> Result<(), RequestError> { + self.phy_smi + .set_phy_power_enabled(true) + .map_err(FrontIOError::from) + .map_err(RequestError::from) + } + + fn phy_disable_power( + &mut self, + _: &RecvMessage, + ) -> Result<(), RequestError> { + self.phy_smi + .set_phy_power_enabled(false) + .map_err(FrontIOError::from) + .map_err(RequestError::from) + } + + fn phy_set_coma_mode( + &mut self, + _: &RecvMessage, + asserted: bool, + ) -> Result<(), RequestError> { + self.phy_smi + .set_coma_mode(asserted) + .map_err(FrontIOError::from) + .map_err(RequestError::from) + } + + fn phy_read( + &mut self, + _: &RecvMessage, + phy: u8, + reg: u8, + ) -> Result> { + self.phy_smi + .read_raw(phy, reg) + .map_err(FrontIOError::from) + .map_err(RequestError::from) + } + + fn phy_write( + &mut self, + _: &RecvMessage, + phy: u8, + reg: u8, + value: u16, + ) -> Result<(), RequestError> { + self.phy_smi + .write_raw(phy, reg, value) + .map_err(FrontIOError::from) + .map_err(RequestError::from) + } + + /// Apply reset to the LED controller + /// + /// Per section 7.6 of the datasheet the minimum required pulse width here + /// is 2.5 microseconds. Given the SPI interface runs at 3MHz, the + /// transaction to clear the reset would take ~10 microseconds on its own, + /// so there is no additional delay here. + fn leds_assert_reset( + &mut self, + _: &RecvMessage, + ) -> Result<(), RequestError> { + self.leds_set_reset(true).map_err(RequestError::from) + } + + /// Remove reset from the LED controller + /// + /// Per section 7.6 of the datasheet the device has a maximum wait time of + /// 1.5 milliseconds after the release of reset to normal operation, so + /// there is a 2 millisecond wait here. + fn leds_deassert_reset( + &mut self, + _: &RecvMessage, + ) -> Result<(), RequestError> { + self.leds_set_reset(false).map_err(RequestError::from)?; + userlib::hl::sleep_for(2); + Ok(()) + } + + /// Releases the LED controller from reset and enables the output + fn leds_enable( + &mut self, + _: &RecvMessage, + ) -> Result<(), RequestError> { + self.leds_set_reset(false).map_err(RequestError::from)?; + for c in &self.controllers { + c.user_design + .write(WriteOp::BitSet, Addr::LED_CTRL, Reg::LED_CTRL::OE) + .map_err(FrontIOError::from) + .map_err(RequestError::from)?; + } + + // Once we've initialized the LED driver we do not need to do so again + if !self.leds_initialized { + match self.leds.initialize_current() { + Ok(_) => { + self.set_system_led_state(LedState::On); + self.leds_initialized = true; + ringbuf_entry!(Trace::LEDInitComplete); + } + Err(e) => { + ringbuf_entry!(Trace::LEDInitError(e)); + return Err(RequestError::from( + FrontIOError::LedInitFailure, + )); + } + } + } + Ok(()) + } + + /// Asserts the LED controller reset and disables the output + fn leds_disable( + &mut self, + _: &RecvMessage, + ) -> Result<(), RequestError> { + self.leds_set_reset(true).map_err(RequestError::from)?; + for c in &self.controllers { + c.user_design + .write(WriteOp::BitClear, Addr::LED_CTRL, Reg::LED_CTRL::OE) + .map_err(FrontIOError::from) + .map_err(RequestError::from)?; + } + Ok(()) + } + + fn led_set_state( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + state: LedState, + ) -> Result<(), RequestError> { + self.led_states.set(mask, state); + Ok(()) + } + + fn led_get_state( + &mut self, + _: &RecvMessage, + port: LogicalPort, + ) -> Result> { + Ok(self.led_states.get(port)) + } + + fn led_get_system_state( + &mut self, + _: &RecvMessage, + ) -> Result> { + Ok(self.system_led_state) + } + + fn led_set_system_on( + &mut self, + _msg: &userlib::RecvMessage, + ) -> Result<(), idol_runtime::RequestError> { + self.set_system_led_state(LedState::On); + Ok(()) + } + + fn led_set_system_off( + &mut self, + _msg: &userlib::RecvMessage, + ) -> Result<(), idol_runtime::RequestError> { + self.set_system_led_state(LedState::Off); + Ok(()) + } + + fn led_set_system_blink( + &mut self, + _msg: &userlib::RecvMessage, + ) -> Result<(), idol_runtime::RequestError> { + self.set_system_led_state(LedState::Blink); + Ok(()) + } + + /// Get the current status of all modules + /// + /// This operation is considered infallible because the error cases are + /// handled by the transceivers crate, which then passes back a + /// TransceiverStatus to be consumed or ignored by the caller. + fn transceivers_status( + &mut self, + _: &RecvMessage, + ) -> Result> { + let (status, result) = self.transceivers.get_module_status(); + + Ok(TransceiverStatus { status, result }) + } + + /// Enable power for modules in `mask` + /// + /// This operation is considered infallible because the error cases are + /// handled by the transceivers crate, which then passes back a + /// ModuleResultNoFailure to be consumed or ignored by the caller. The + /// meaning of the returned `ModuleResultNoFailure`: + /// success: we were able to write to the FPGA + /// error: an `FpgaError` occurred + fn transceivers_enable_power( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + ) -> Result> { + Ok(self.transceivers.enable_power(mask)) + } + + /// Disable power for modules in `mask` + /// + /// This operation is considered infallible because the error cases are + /// handled by the transceivers crate, which then passes back a + /// ModuleResultNoFailure to be consumed or ignored by the caller. The + /// meaning of the returned `ModuleResultNoFailure`: + /// success: we were able to write to the FPGA + /// error: an `FpgaError` occurred + fn transceivers_disable_power( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + ) -> Result> { + Ok(self.transceivers.disable_power(mask)) + } + + /// Clear a fault for each port per the specified `mask`. + /// + /// The meaning of the + /// returned `ModuleResultNoFailure`: + /// success: we were able to write to the FPGA + /// error: an `FpgaError` occurred + fn transceivers_clear_power_fault( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + ) -> Result> { + Ok(self.transceivers.clear_power_fault(mask)) + } + + /// Assert reset for modules in `mask` + /// + /// This operation is considered infallible because the error cases are + /// handled by the transceivers crate, which then passes back a + /// ModuleResultNoFailure to be consumed or ignored by the caller. The + /// meaning of the returned `ModuleResultNoFailure`: + /// success: we were able to write to the FPGA + /// error: an `FpgaError` occurred + fn transceivers_assert_reset( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + ) -> Result> { + Ok(self.transceivers.assert_reset(mask)) + } + + /// Deassert reset for modules in `mask` + /// + /// This operation is considered infallible because the error cases are + /// handled by the transceivers crate, which then passes back a + /// ModuleResultNoFailure to be consumed or ignored by the caller. The + /// meaning of the returned `ModuleResultNoFailure`: + /// success: we were able to write to the FPGA + /// error: an `FpgaError` occurred + fn transceivers_deassert_reset( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + ) -> Result> { + Ok(self.transceivers.deassert_reset(mask)) + } + + /// Assert LpMode for modules in `mask` + /// + /// This operation is considered infallible because the error cases are + /// handled by the transceivers crate, which then passes back a + /// ModuleResultNoFailure to be consumed or ignored by the caller. The + /// meaning of the returned `ModuleResultNoFailure`: + /// success: we were able to write to the FPGA + /// error: an `FpgaError` occurred + fn transceivers_assert_lpmode( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + ) -> Result> { + Ok(self.transceivers.assert_lpmode(mask)) + } + + /// Deassert LpMode for modules in `mask` + /// + /// This operation is considered infallible because the error cases are + /// handled by the transceivers crate, which then passes back a + /// ModuleResultNoFailure to be consumed or ignored by the caller. The + /// meaning of the returned `ModuleResultNoFailure`: + /// success: we were able to write to the FPGA + /// error: an `FpgaError` occurred + fn transceivers_deassert_lpmode( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + ) -> Result> { + Ok(self.transceivers.deassert_lpmode(mask)) + } + + /// Initiate an I2C random read on all ports per the specified `mask`. + /// + /// The maximum value of `num_bytes` is 128. This operation is considered + /// infallible because the error cases are handled by the transceivers + /// crate, which then passes back a ModuleResultNoFailure to be consumed or + /// ignored by the caller. The meaning of the returned + /// `ModuleResultNoFailure`: + /// success: we were able to write to the FPGA + /// error: an `FpgaError` occurred + fn transceivers_setup_i2c_read( + &mut self, + _: &RecvMessage, + reg: u8, + num_bytes: u8, + mask: LogicalPortMask, + ) -> Result> { + if usize::from(num_bytes) > PAGE_SIZE_BYTES { + return Err(FrontIOError::InvalidNumberOfBytes.into()); + } + Ok(self.transceivers.setup_i2c_op(true, reg, num_bytes, mask)) + } + + /// Initiate an I2C write on all ports per the specified `mask`. + /// + /// The maximum value of `num_bytes` is 128.This operation is considered + /// infallible because the error cases are handled by the transceivers + /// crate, which then passes back a ModuleResultNoFailure to be consumed or + /// ignored by the caller. The meaning of the returned + /// `ModuleResultNoFailure`: + /// success: we were able to write to the FPGA + /// error: an `FpgaError` occurred + fn transceivers_setup_i2c_write( + &mut self, + _msg: &userlib::RecvMessage, + reg: u8, + num_bytes: u8, + mask: LogicalPortMask, + ) -> Result> { + if usize::from(num_bytes) > PAGE_SIZE_BYTES { + return Err(FrontIOError::InvalidNumberOfBytes.into()); + } + Ok(self.transceivers.setup_i2c_op(false, reg, num_bytes, mask)) + } + + fn transceivers_get_i2c_status_and_read_buffer( + &mut self, + _: &RecvMessage, + port: LogicalPort, + dest: Leased, + ) -> Result> { + if port.0 >= NUM_PORTS { + return Err(FrontIOError::InvalidPortNumber.into()); + } + + if dest.len() > PAGE_SIZE_BYTES { + return Err(FrontIOError::InvalidNumberOfBytes.into()); + } + + // PAGE_SIZE_BYTES + 1 since we have a status byte alongside data + let mut buf = [0u8; PAGE_SIZE_BYTES + 1]; + + let status = self + .transceivers + .get_i2c_status_and_read_buffer(port, &mut buf[..dest.len()]) + .map_err(FrontIOError::from)?; + + dest.write_range(0..dest.len(), &buf[..dest.len()]) + .map_err(|_| RequestError::Fail(ClientError::WentAway))?; + Ok(status) + } + + fn transceivers_set_i2c_write_buffer( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + data: Leased, + ) -> Result> { + if data.len() > PAGE_SIZE_BYTES { + return Err(FrontIOError::InvalidNumberOfBytes.into()); + } + + let mut buf = [0u8; PAGE_SIZE_BYTES]; + data.read_range(0..data.len(), &mut buf[..data.len()]) + .map_err(|_| RequestError::Fail(ClientError::WentAway))?; + + Ok(self.transceivers.set_i2c_write_buffer(&buf, mask)) + } + + fn transceivers_wait_and_check_i2c( + &mut self, + _: &RecvMessage, + mask: LogicalPortMask, + ) -> Result> { + Ok(self.transceivers.wait_and_check_i2c(mask)) + } +} + +impl NotificationHandler for ServerImpl { + fn current_notification_mask(&self) -> u32 { + notifications::TIMER_MASK + } + + fn handle_notification(&mut self, _bits: u32) {} +} + +impl Default for ServerImpl { + fn default() -> Self { + let i2c_task = I2C.get_task_id(); + let fpga_task = FRONT_IO.get_task_id(); + let auxflash_task = AUXFLASH.get_task_id(); + + ServerImpl { + auxflash_task, + controllers: [ + FrontIOController::new(fpga_task, 0), + FrontIOController::new(fpga_task, 1), + ], + leds: Leds::new( + &i2c_config::devices::pca9956b_front_leds_left(i2c_task), + &i2c_config::devices::pca9956b_front_leds_right(i2c_task), + ), + phy_smi: PhySmi::new(fpga_task), + transceivers: Transceivers::new(fpga_task), + board_status: FrontIOStatus::NotPresent, + led_blink_on: false, + led_error: Default::default(), + leds_initialized: false, + led_states: LedStates::default(), + system_led_state: LedState::Off, + } + } +} + +#[export_name = "main"] +fn main() -> ! { + let mut buffer = [0; idl::INCOMING_SIZE]; + + let mut server = ServerImpl::default(); + + #[derive(Copy, Clone, Enum)] + #[allow(clippy::upper_case_acronyms)] + enum Timers { + I2C, + Blink, + Seq, + } + let mut multitimer = Multitimer::::new(notifications::TIMER_BIT); + let now = sys_get_timer().now; + multitimer.set_timer( + Timers::I2C, + now, + Some(Repeat::AfterDeadline(I2C_INTERVAL)), + ); + multitimer.set_timer( + Timers::Blink, + now, + Some(Repeat::AfterDeadline(BLINK_INTERVAL)), + ); + multitimer.set_timer( + Timers::Seq, + now, + Some(Repeat::AfterDeadline(SEQ_INTERVAL)), + ); + + // This will put our timer in the past, and should immediately kick us. + let deadline = sys_get_timer().now; + sys_set_timer(Some(deadline), notifications::TIMER_MASK); + + loop { + multitimer.poll_now(); + for t in multitimer.iter_fired() { + match t { + Timers::I2C => { + // There's no point to try to talk to the I2C bus if a board + // is not present. + if server.board_status != FrontIOStatus::NotPresent { + server.handle_i2c_loop(); + } + } + Timers::Blink => { + server.led_blink_on = !server.led_blink_on; + } + Timers::Seq => { + ringbuf_entry!(Trace::SequenceUpdate(server.board_status)); + + // Sequencing of the Front IO board + match server.board_status { + // The best way we have to detect the presence of a + // Front IO board is our ability to talk to its FRUID + // device. + FrontIOStatus::NotPresent => { + let fruid = + i2c_config::devices::at24csw080_front_io( + I2C.get_task_id(), + )[0]; + if At24Csw080::validate(&fruid).unwrap_or(false) { + server.board_status = FrontIOStatus::FpgaInit; + } + } + + // Once there is a board present, configure its FPGAs + // and wait for its oscillator to be functional. + FrontIOStatus::FpgaInit => match server.fpga_init() { + Ok(done) => { + if done && server.initialized() { + server.board_status = + FrontIOStatus::OscInit; + } + } + Err(e) => ringbuf_entry!(Trace::FpgaInitError(e)), + }, + + // Wait for the PHY oscillator to be deemed operational. + // Currently this server does not control the power to + // the Front IO board, so it relies on whatever task + // _does_ have that control to power cycle the board and + // make a judgement about the oscillator. + FrontIOStatus::OscInit => { + if server + .phy_smi + .osc_state() + .unwrap_or(PhyOscState::Unknown) + == PhyOscState::Good + { + server.board_status = FrontIOStatus::Ready; + } + } + + // The board is operational, not further action needed + FrontIOStatus::Ready => (), + } + } + } + } + + idol_runtime::dispatch(&mut buffer, &mut server); + } +} + +mod idl { + use super::{ + FrontIOError, FrontIOStatus, LedState, LogicalPort, LogicalPortMask, + ModuleResult, ModuleResultNoFailure, PhyOscState, PortI2CStatus, + TransceiverStatus, + }; + + include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); +} + +include!(concat!(env!("OUT_DIR"), "/notifications.rs")); diff --git a/drv/sidecar-front-io/Cargo.toml b/drv/sidecar-front-io/Cargo.toml deleted file mode 100644 index cb12e0c63..000000000 --- a/drv/sidecar-front-io/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "drv-sidecar-front-io" -version = "0.1.0" -edition = "2021" - -[dependencies] -cfg-if = { workspace = true } -num-derive = { workspace = true } -num-traits = { workspace = true } -transceiver-messages = { workspace = true } -vsc7448-pac = { workspace = true, optional = true } -zerocopy = { workspace = true } - -drv-auxflash-api = { path = "../../drv/auxflash-api" } -drv-fpga-api = { path = "../../drv/fpga-api", features = ["auxflash"] } -drv-i2c-api = { path = "../i2c-api" } -drv-i2c-devices = { path = "../i2c-devices" } -drv-transceivers-api = { path = "../../drv/transceivers-api" } -ringbuf = { path = "../../lib/ringbuf" } -userlib = { path = "../../sys/userlib" } -vsc85xx = { path = "../../drv/vsc85xx", optional = true } - -[features] -controller = [] -phy_smi = ["vsc85xx", "vsc7448-pac"] -transceivers = [] -leds = [] -no-ipc-counters = ["idol/no-counters"] - -[build-dependencies] -build-fpga-regmap = { path = "../../build/fpga-regmap" } -build-util = { path = "../../build/util" } -gnarle = { path = "../../lib/gnarle", features=["std"] } -idol = { workspace = true } - -[lib] -test = false -doctest = false -bench = false diff --git a/drv/sidecar-front-io/src/lib.rs b/drv/sidecar-front-io/src/lib.rs deleted file mode 100644 index 2013212df..000000000 --- a/drv/sidecar-front-io/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -#![no_std] - -include!(concat!( - env!("OUT_DIR"), - "/sidecar_qsfp_x32_controller_regs.rs" -)); - -#[cfg(feature = "controller")] -pub mod controller; -#[cfg(feature = "leds")] -pub mod leds; -#[cfg(feature = "phy_smi")] -pub mod phy_smi; -#[cfg(feature = "transceivers")] -pub mod transceivers; diff --git a/drv/sidecar-seq-api/Cargo.toml b/drv/sidecar-seq-api/Cargo.toml index c5329ad74..2366b1590 100644 --- a/drv/sidecar-seq-api/Cargo.toml +++ b/drv/sidecar-seq-api/Cargo.toml @@ -10,9 +10,10 @@ num-traits.workspace = true serde.workspace = true zerocopy.workspace = true counters = { path = "../../lib/counters" } -derive-idol-err = { path = "../../lib/derive-idol-err" } +derive-idol-err = { path = "../../lib/derive-idol-err" } drv-fpga-api = { path = "../fpga-api" } drv-fpga-user-api = { path = "../fpga-user-api" } +drv-front-io-api = { path = "../front-io-api" } drv-sidecar-mainboard-controller = { path = "../sidecar-mainboard-controller" } userlib = { path = "../../sys/userlib" } diff --git a/drv/sidecar-seq-api/src/lib.rs b/drv/sidecar-seq-api/src/lib.rs index df5de9077..d9a79f21f 100644 --- a/drv/sidecar-seq-api/src/lib.rs +++ b/drv/sidecar-seq-api/src/lib.rs @@ -8,6 +8,7 @@ use derive_idol_err::IdolError; use drv_fpga_api::FpgaError; +use drv_front_io_api::FrontIOError; pub use drv_sidecar_mainboard_controller::{ fan_modules::{FanModuleStatus, NUM_FAN_MODULES}, tofino2::{ @@ -35,6 +36,7 @@ pub enum SeqError { SetVddCoreVoutFailed, NoFrontIOBoard, FrontIOBoardPowerFault, + FrontIOError, #[idol(server_death)] ServerRestarted, @@ -46,6 +48,12 @@ impl From for SeqError { } } +impl From for SeqError { + fn from(_: FrontIOError) -> Self { + Self::FrontIOError + } +} + #[derive(Copy, Clone, Debug, FromPrimitive, Eq, PartialEq, AsBytes)] #[repr(u8)] pub enum TofinoSequencerPolicy { diff --git a/drv/sidecar-seq-server/Cargo.toml b/drv/sidecar-seq-server/Cargo.toml index 17e228085..cf2391914 100644 --- a/drv/sidecar-seq-server/Cargo.toml +++ b/drv/sidecar-seq-server/Cargo.toml @@ -15,10 +15,10 @@ zerocopy.workspace = true drv-fpga-api = { path = "../fpga-api", features = ["auxflash"] } drv-fpga-user-api = { path = "../fpga-user-api" } +drv-front-io-api = {path = "../front-io-api" } drv-i2c-api = { path = "../i2c-api" } drv-i2c-devices = { path = "../i2c-devices" } drv-packrat-vpd-loader = { path = "../packrat-vpd-loader" } -drv-sidecar-front-io = { path = "../sidecar-front-io", features = ["controller", "phy_smi"] } drv-sidecar-mainboard-controller = { path = "../sidecar-mainboard-controller", features = ["bitstream"] } drv-sidecar-seq-api = { path = "../sidecar-seq-api" } ringbuf = { path = "../../lib/ringbuf" } diff --git a/drv/sidecar-seq-server/src/front_io.rs b/drv/sidecar-seq-server/src/front_io.rs deleted file mode 100644 index 2aae1f459..000000000 --- a/drv/sidecar-seq-server/src/front_io.rs +++ /dev/null @@ -1,110 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use crate::*; -use drv_i2c_devices::{at24csw080::At24Csw080, Validate}; -use drv_sidecar_front_io::controller::FrontIOController; -use drv_sidecar_front_io::phy_smi::PhySmi; - -#[allow(dead_code)] -pub(crate) struct FrontIOBoard { - pub controllers: [FrontIOController; 2], - fpga_task: userlib::TaskId, - auxflash_task: userlib::TaskId, -} - -impl FrontIOBoard { - pub fn new( - fpga_task: userlib::TaskId, - auxflash_task: userlib::TaskId, - ) -> Self { - Self { - controllers: [ - FrontIOController::new(fpga_task, 0), - FrontIOController::new(fpga_task, 1), - ], - fpga_task, - auxflash_task, - } - } - - pub fn phy(&self) -> PhySmi { - PhySmi::new(self.fpga_task) - } - - pub fn present(i2c_task: userlib::TaskId) -> bool { - let fruid = i2c_config::devices::at24csw080_front_io(i2c_task)[0]; - At24Csw080::validate(&fruid).unwrap_or(false) - } - - pub fn initialized(&self) -> bool { - self.controllers.iter().all(|c| c.ready().unwrap_or(false)) - } - - pub fn init(&mut self) -> Result { - let mut controllers_ready = true; - - for (i, controller) in self.controllers.iter_mut().enumerate() { - let state = controller.await_fpga_ready(25)?; - let mut ident; - let mut ident_valid = false; - let mut checksum; - let mut checksum_valid = false; - - if state == DeviceState::RunningUserDesign { - (ident, ident_valid) = controller.ident_valid()?; - ringbuf_entry!(Trace::FrontIOControllerIdent { - fpga_id: i, - ident - }); - - (checksum, checksum_valid) = controller.checksum_valid()?; - ringbuf_entry!(Trace::FrontIOControllerChecksum { - fpga_id: i, - checksum, - expected: FrontIOController::short_checksum(), - }); - - if !ident_valid || !checksum_valid { - // Attempt to correct the invalid IDENT by reloading the - // bitstream. - controller.fpga_reset()?; - } - } - - if ident_valid && checksum_valid { - ringbuf_entry!(Trace::SkipLoadingFrontIOControllerBitstream { - fpga_id: i - }); - } else { - ringbuf_entry!(Trace::LoadingFrontIOControllerBitstream { - fpga_id: i - }); - - if let Err(e) = controller.load_bitstream(self.auxflash_task) { - ringbuf_entry!(Trace::FpgaBitstreamError(u32::from(e))); - return Err(e); - } - - (ident, ident_valid) = controller.ident_valid()?; - ringbuf_entry!(Trace::FrontIOControllerIdent { - fpga_id: i, - ident - }); - - controller.write_checksum()?; - (checksum, checksum_valid) = controller.checksum_valid()?; - ringbuf_entry!(Trace::FrontIOControllerChecksum { - fpga_id: i, - checksum, - expected: FrontIOController::short_checksum(), - }); - } - - controllers_ready &= ident_valid & checksum_valid; - } - - Ok(controllers_ready) - } -} diff --git a/drv/sidecar-seq-server/src/main.rs b/drv/sidecar-seq-server/src/main.rs index d2424d3d4..76f7626f2 100644 --- a/drv/sidecar-seq-server/src/main.rs +++ b/drv/sidecar-seq-server/src/main.rs @@ -8,13 +8,12 @@ #![no_main] use crate::clock_generator::ClockGenerator; -use crate::front_io::FrontIOBoard; use crate::tofino::Tofino; use core::convert::Infallible; use drv_fpga_api::{DeviceState, FpgaError, WriteOp}; +use drv_front_io_api::{phy_smi::PhyOscState, FrontIO, FrontIOStatus}; use drv_i2c_api::{I2cDevice, ResponseCode}; use drv_packrat_vpd_loader::{read_vpd_and_load_packrat, Packrat}; -use drv_sidecar_front_io::phy_smi::PhyOscState; use drv_sidecar_mainboard_controller::fan_modules::*; use drv_sidecar_mainboard_controller::front_io::*; use drv_sidecar_mainboard_controller::tofino2::*; @@ -37,7 +36,6 @@ task_slot!(PACKRAT, packrat); include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); mod clock_generator; -mod front_io; mod tofino; #[allow(dead_code)] @@ -85,21 +83,6 @@ enum Trace { FrontIOBoardPhyPowerEnable(bool), FrontIOBoardPhyOscGood, FrontIOBoardPhyOscBad, - LoadingFrontIOControllerBitstream { - fpga_id: usize, - }, - SkipLoadingFrontIOControllerBitstream { - fpga_id: usize, - }, - FrontIOControllerIdent { - fpga_id: usize, - ident: u32, - }, - FrontIOControllerChecksum { - fpga_id: usize, - checksum: [u8; 4], - expected: [u8; 4], - }, FpgaFanModuleFailure(FpgaError), FanModulePowerFault(FanModuleIndex, FanModuleStatus), FanModuleLedUpdate(FanModuleIndex, FanModuleLedState), @@ -129,7 +112,7 @@ struct ServerImpl { clock_generator: ClockGenerator, tofino: Tofino, front_io_hsc: HotSwapController, - front_io_board: Option, + front_io_board: FrontIO, fan_modules: FanModules, // a piece of state to allow blinking LEDs to be in phase led_blink_on: bool, @@ -228,67 +211,61 @@ impl ServerImpl { } // Determine if a front IO board is present. - Ok(FrontIOBoard::present(I2C.get_task_id())) - } - - fn front_io_phy_osc_good(&self) -> Result { - if let Some(front_io_board) = self.front_io_board.as_ref() { - Ok(front_io_board.initialized() - && front_io_board - .phy() - .osc_state() - .unwrap_or(PhyOscState::Unknown) - == PhyOscState::Good) - } else { - Err(SeqError::NoFrontIOBoard) - } + Ok(self.front_io_board.board_present()) } fn actually_reset_front_io_phy(&mut self) -> Result<(), SeqError> { - if let Some(front_io_board) = self.front_io_board.as_mut() { - if front_io_board.initialized() { - // The board was initialized prior and this function is called - // by the monorail task because it is initializing the front IO - // PHY. Unfortunately some front IO boards have PHY oscillators - // which do not start reliably when their enable pin is used and - // the only way to resolve this is by power cycling the front IO - // board. But power cycling the board also bounces any QSFP - // transceivers which may be running, so this function attempts - // to determine what the monorail task wants to do. - // - // Whether or not the PHY oscillator was found to be operating - // nominally is recorded in the front IO board controller. Look - // up what this value is to determine if a power reset of the - // front IO board is needed. - match front_io_board.phy().osc_state()? { - PhyOscState::Bad => { - // The PHY was attempted to be initialized but its - // oscillator was deemed not functional. Unfortunately - // the only course of action is to power cycle the - // entire front IO board, so do so now. - self.front_io_hsc.set_enable(false)?; - ringbuf_entry!(Trace::FrontIOBoardPowerEnable(false)); - - // Wait some cool down period to allow caps to bleed off - // etc. - userlib::hl::sleep_for(1000); - } - PhyOscState::Good => { - // The PHY was initialized properly before and its - // oscillator declared operating nominally. Assume this - // has not changed and only a reset the PHY itself is - // desired. - front_io_board.phy().set_phy_power_enabled(false)?; - ringbuf_entry!(Trace::FrontIOBoardPhyPowerEnable( - false - )); - - userlib::hl::sleep_for(10); - } - PhyOscState::Unknown => { - // Do nothing (yet) since the oscillator state is - // unknown. - } + // The OscInit status means the board is present and has had its FPGAs + // initialized but its PHY oscillator has not yet been marked as good. + if self.front_io_board.board_status() == FrontIOStatus::OscInit { + // The board was initialized prior and this function is called + // by the monorail task because it is initializing the front IO + // PHY. Unfortunately some front IO boards have PHY oscillators + // which do not start reliably when their enable pin is used and + // the only way to resolve this is by power cycling the front IO + // board. But power cycling the board also bounces any QSFP + // transceivers which may be running, so this function attempts + // to determine what the monorail task wants to do. + // + // Whether or not the PHY oscillator was found to be operating + // nominally is recorded in the front IO board controller. Look + // up what this value is to determine if a power reset of the + // front IO board is needed. + match self + .front_io_board + .phy_osc_state() + .map_err(SeqError::from)? + { + PhyOscState::Bad => { + // The PHY was attempted to be initialized but its + // oscillator was deemed not functional. Unfortunately + // the only course of action is to power cycle the + // entire front IO board, so do so now. + self.front_io_hsc.set_enable(false)?; + // After removing power to the board we must reset its + // server + self.front_io_board.board_reset(); + ringbuf_entry!(Trace::FrontIOBoardPowerEnable(false)); + + // Wait some cool down period to allow caps to bleed off + // etc. + userlib::hl::sleep_for(1000); + } + PhyOscState::Good => { + // The PHY was initialized properly before and its + // oscillator declared operating nominally. Assume this + // has not changed and only a reset the PHY itself is + // desired. + self.front_io_board + .phy_enable_power() + .map_err(SeqError::from)?; + ringbuf_entry!(Trace::FrontIOBoardPhyPowerEnable(false)); + + userlib::hl::sleep_for(10); + } + PhyOscState::Unknown => { + // Do nothing (yet) since the oscillator state is + // unknown. } } } @@ -296,20 +273,22 @@ impl ServerImpl { // Run preinit to check HSC status. self.front_io_board_preinit()?; - if let Some(front_io_board) = self.front_io_board.as_mut() { + if self.front_io_board.board_present() { // At this point the front IO board has either not yet been // initalized or may have been power cycled and should be // initialized. - if !front_io_board.initialized() { - front_io_board.init()?; + while self.front_io_board.board_status() != FrontIOStatus::OscInit { + userlib::hl::sleep_for(20); } // The PHY is still powered down. Request the sequencer to power up // and wait for it to be ready. - front_io_board.phy().set_phy_power_enabled(true)?; + self.front_io_board + .phy_enable_power() + .map_err(SeqError::from)?; ringbuf_entry!(Trace::FrontIOBoardPhyPowerEnable(true)); - while !front_io_board.phy().powered_up_and_ready()? { + while !self.front_io_board.phy_ready().map_err(SeqError::from)? { userlib::hl::sleep_for(20); } @@ -325,12 +304,11 @@ impl ServerImpl { // interface initialization the chassis either should not have a front IO // board or the oscillator should have been found operating nominally before // transitioning to A0. - fn ready_for_tofino_power_up(&self) -> Result { - match self.front_io_phy_osc_good() { - Ok(osc_good) => Ok(osc_good), - Err(SeqError::NoFrontIOBoard) => Ok(true), - Err(e) => Err(e), - } + fn ready_for_tofino_power_up(&self) -> bool { + matches!( + self.front_io_board.board_status(), + FrontIOStatus::NotPresent | FrontIOStatus::Ready + ) } } @@ -488,20 +466,6 @@ impl idl::InOrderSequencerImpl for ServerImpl { Ok(self.clock_generator.config_loaded) } - fn front_io_board_present( - &mut self, - _: &RecvMessage, - ) -> Result> { - Ok(self.front_io_board.is_some()) - } - - fn front_io_board_ready( - &mut self, - _: &RecvMessage, - ) -> Result> { - self.front_io_phy_osc_good().map_err(RequestError::from) - } - fn reset_front_io_phy( &mut self, _: &RecvMessage, @@ -510,49 +474,6 @@ impl idl::InOrderSequencerImpl for ServerImpl { .map_err(RequestError::from) } - fn set_front_io_phy_osc_state( - &mut self, - _: &RecvMessage, - good: bool, - ) -> Result<(), RequestError> { - let front_io_board = self - .front_io_board - .as_ref() - .ok_or(SeqError::NoFrontIOBoard)?; - - match front_io_board - .phy() - .osc_state() - .map_err(SeqError::from) - .map_err(RequestError::from)? - { - // The state of the oscillator has not yet been examined or was - // marked bad in the previous run. Update as appropriate. - PhyOscState::Unknown | PhyOscState::Bad => { - ringbuf_entry!(if good { - Trace::FrontIOBoardPhyOscGood - } else { - Trace::FrontIOBoardPhyOscBad - }); - - front_io_board - .phy() - .set_osc_good(good) - .map_err(SeqError::from) - .map_err(RequestError::from) - } - // The oscillator is already marked good and this state only changes - // if it (and by extension the whole front IO board) is power - // cycled. In that case the value of this register in the FPGA is - // automatically reset when the bitstream is loaded and the other - // arm of this match would be taken. - // - // So ignore this call if the oscillator has been found good since the last power - // cycle of the front IO board. - PhyOscState::Good => Ok(()), - } - } - fn tofino_debug_port_state( &mut self, _: &RecvMessage, @@ -766,8 +687,7 @@ impl NotificationHandler for ServerImpl { // plane. See the comment of `ready_for_tofino_power_up` for more // context. if !self.tofino.ready_for_power_up { - self.tofino.ready_for_power_up = - self.ready_for_tofino_power_up().unwrap_or(false); + self.tofino.ready_for_power_up = self.ready_for_tofino_power_up(); } if let Err(e) = self.tofino.handle_tick() { @@ -811,13 +731,14 @@ fn main() -> ! { let tofino = Tofino::new(i2c_task); let front_io_hsc = HotSwapController::new(MAINBOARD.get_task_id()); let fan_modules = FanModules::new(MAINBOARD.get_task_id()); + let front_io_board = FrontIO::from(FRONT_IO.get_task_id()); let mut server = ServerImpl { mainboard_controller, clock_generator, tofino, front_io_hsc, - front_io_board: None, + front_io_board, fan_modules, led_blink_on: false, }; @@ -954,20 +875,7 @@ fn main() -> ! { match server.front_io_board_preinit() { Ok(true) => { ringbuf_entry!(Trace::FrontIOBoardPresent); - - let mut front_io_board = FrontIOBoard::new( - FRONT_IO.get_task_id(), - AUXFLASH.get_task_id(), - ); - - front_io_board.init().unwrap_lite(); - // TODO (arjen): check/load VPD data into packrat. - - // So far the front IO board looks functional. Assign it to the - // server, implicitly marking it present for the lifetime of this - // task. - server.front_io_board = Some(front_io_board); } Ok(false) => ringbuf_entry!(Trace::FrontIOBoardNotPresent), Err(SeqError::FrontIOBoardPowerFault) => { diff --git a/drv/transceivers-api/Cargo.toml b/drv/transceivers-api/Cargo.toml index 9267529e5..7b4b2f4f7 100644 --- a/drv/transceivers-api/Cargo.toml +++ b/drv/transceivers-api/Cargo.toml @@ -12,6 +12,7 @@ zerocopy = { workspace = true } counters = { path = "../../lib/counters" } derive-idol-err = { path = "../../lib/derive-idol-err" } drv-fpga-api = { path = "../fpga-api" } +drv-front-io-api = { path = "../front-io-api" } task-sensor-api = { path = "../../task/sensor-api" } userlib = { path = "../../sys/userlib" } diff --git a/drv/transceivers-api/build.rs b/drv/transceivers-api/build.rs index 5e1c25465..27630d239 100644 --- a/drv/transceivers-api/build.rs +++ b/drv/transceivers-api/build.rs @@ -3,11 +3,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. fn main() -> Result<(), Box> { - idol::client::build_client_stub( - "../../idl/transceivers.idol", - "client_stub.rs", - )?; - let disposition = build_i2c::Disposition::Sensors; if let Err(e) = build_i2c::codegen(disposition) { println!("code generation failed: {}", e); diff --git a/drv/transceivers-api/src/lib.rs b/drv/transceivers-api/src/lib.rs index 0fe6188af..4367e9bf1 100644 --- a/drv/transceivers-api/src/lib.rs +++ b/drv/transceivers-api/src/lib.rs @@ -8,21 +8,17 @@ use derive_idol_err::IdolError; use drv_fpga_api::FpgaError; +use drv_front_io_api::transceivers::NUM_PORTS; use task_sensor_api::{config::other_sensors, SensorId}; -use userlib::{sys_send, FromPrimitive}; -use zerocopy::{AsBytes, FromBytes}; +use userlib::FromPrimitive; #[derive( Copy, Clone, Debug, FromPrimitive, Eq, PartialEq, IdolError, counters::Count, )] pub enum TransceiversError { FpgaError = 1, - InvalidPortNumber, - InvalidNumberOfBytes, InvalidPowerState, - InvalidModuleResult, LedI2cError, - InvalidPhysicalToLogicalMap, #[idol(server_death)] ServerRestarted, @@ -34,34 +30,6 @@ impl From for TransceiversError { } } -/// Each field is a bitmask of the 32 transceivers in big endian order, which -/// results in Port 31 being bit 31, and so forth. -#[derive(Copy, Clone, Default, FromBytes, AsBytes)] -#[repr(C)] -pub struct ModuleStatus { - pub power_enable: u32, - pub power_good: u32, - pub power_good_timeout: u32, - pub power_good_fault: u32, - pub resetl: u32, - pub lpmode_txdis: u32, - pub modprsl: u32, - pub intl_rxlosl: u32, -} - -/// Size in bytes of a page section we will read or write -/// -/// QSFP module's internal memory map is 256 bytes, with the lower 128 being -/// static and then the upper 128 are paged in. The internal address register -/// is only 7 bits, so you can only access half in any single transaction and -/// thus our communication mechanisms have been designed for that. -/// See SFF-8636 and CMIS specifications for details. -pub const PAGE_SIZE_BYTES: usize = 128; - -/// The only instantiation of Front IO board that exists is one with 32 QSFP -/// ports. -pub const NUM_PORTS: u8 = 32; - //////////////////////////////////////////////////////////////////////////////// pub const TRANSCEIVER_TEMPERATURE_SENSORS: [SensorId; NUM_PORTS as usize] = [ @@ -98,6 +66,3 @@ pub const TRANSCEIVER_TEMPERATURE_SENSORS: [SensorId; NUM_PORTS as usize] = [ other_sensors::QSFP_XCVR30_TEMPERATURE_SENSOR, other_sensors::QSFP_XCVR31_TEMPERATURE_SENSOR, ]; -//////////////////////////////////////////////////////////////////////////////// - -include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/drv/transceivers-server/Cargo.toml b/drv/transceivers-server/Cargo.toml index e10c5c800..3d19fcb7b 100644 --- a/drv/transceivers-server/Cargo.toml +++ b/drv/transceivers-server/Cargo.toml @@ -7,12 +7,9 @@ edition = "2021" [dependencies] counters = { path = "../../lib/counters" } drv-fpga-api = { path = "../fpga-api" } -drv-i2c-api = { path = "../i2c-api" } -drv-i2c-devices = { path = "../i2c-devices" } -drv-sidecar-front-io = { path = "../sidecar-front-io", features = ["transceivers", "leds"] } +drv-front-io-api = { path = "../front-io-api" } drv-sidecar-seq-api = { path = "../sidecar-seq-api" } drv-transceivers-api = { path = "../transceivers-api" } -multitimer = { path = "../../lib/multitimer" } ringbuf = { path = "../../lib/ringbuf" } static-cell = { path = "../../lib/static-cell" } task-net-api = { path = "../../task/net-api" } @@ -20,16 +17,15 @@ task-sensor-api = { path = "../../task/sensor-api" } task-thermal-api = { path = "../../task/thermal-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } -cfg-if = { workspace = true } -enum-map = { workspace = true } -hubpack = { workspace = true } -idol-runtime = { workspace = true } -num-traits = { workspace = true } -serde = { workspace = true } -ssmarshal = { workspace = true } -stm32h7 = { workspace = true } -transceiver-messages = { workspace = true } -zerocopy = { workspace = true } +cfg-if.workspace = true +hubpack.workspace = true +idol-runtime.workspace = true +num-traits.workspace = true +serde.workspace = true +ssmarshal.workspace = true +stm32h7.workspace = true +transceiver-messages.workspace = true +zerocopy.workspace = true [features] vlan = ["task-net-api/vlan"] @@ -37,7 +33,6 @@ no-ipc-counters = ["idol/no-counters"] [build-dependencies] build-util = { path = "../../build/util" } -build-i2c = { path = "../../build/i2c" } idol = { workspace = true } diff --git a/drv/transceivers-server/build.rs b/drv/transceivers-server/build.rs index 9242e7fd8..61cf76e84 100644 --- a/drv/transceivers-server/build.rs +++ b/drv/transceivers-server/build.rs @@ -6,22 +6,5 @@ fn main() -> Result<(), Box> { build_util::expose_target_board(); build_util::build_notifications()?; - let disposition = build_i2c::Disposition::Devices; - - if let Err(e) = build_i2c::codegen(disposition) { - println!("code generation failed: {}", e); - std::process::exit(1); - } - - idol::Generator::new() - .with_counters( - idol::CounterSettings::default().with_server_counters(false), - ) - .build_server_support( - "../../idl/transceivers.idol", - "server_stub.rs", - idol::server::ServerStyle::InOrder, - )?; - Ok(()) } diff --git a/drv/transceivers-server/src/main.rs b/drv/transceivers-server/src/main.rs index 2618526a3..cb59a4fe0 100644 --- a/drv/transceivers-server/src/main.rs +++ b/drv/transceivers-server/src/main.rs @@ -6,60 +6,42 @@ #![no_main] use counters::Count; -use idol_runtime::{NotificationHandler, RequestError}; -use multitimer::{Multitimer, Repeat}; +use idol_runtime::NotificationHandler; use ringbuf::*; use static_cell::ClaimOnceCell; -use userlib::{sys_get_timer, task_slot, units::Celsius}; +use userlib::{sys_get_timer, sys_set_timer, task_slot, units::Celsius}; use drv_fpga_api::FpgaError; -use drv_i2c_devices::pca9956b::Error; -use drv_sidecar_front_io::{ - leds::{FullErrorSummary, Leds}, +use drv_front_io_api::{ transceivers::{ - FpgaI2CFailure, LogicalPort, LogicalPortMask, Transceivers, + FpgaI2CFailure, LogicalPort, LogicalPortMask, ModuleStatus, NUM_PORTS, }, - Reg, + FrontIO, FrontIOError, FrontIOStatus, Reg, }; -use drv_sidecar_seq_api::{SeqError, Sequencer}; use drv_transceivers_api::{ - ModuleStatus, TransceiversError, NUM_PORTS, TRANSCEIVER_TEMPERATURE_SENSORS, + TransceiversError, TRANSCEIVER_TEMPERATURE_SENSORS, }; -use enum_map::Enum; use task_sensor_api::{NoData, Sensor}; use task_thermal_api::{Thermal, ThermalError, ThermalProperties}; -use transceiver_messages::{ - message::LedState, mgmt::ManagementInterface, MAX_PACKET_SIZE, -}; +use transceiver_messages::{mgmt::ManagementInterface, MAX_PACKET_SIZE}; use zerocopy::{AsBytes, FromBytes}; mod udp; // UDP API is implemented in a separate file -task_slot!(I2C, i2c_driver); task_slot!(FRONT_IO, front_io); -task_slot!(SEQ, seq); task_slot!(NET, net); task_slot!(THERMAL, thermal); task_slot!(SENSOR, sensor); -include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); - #[allow(dead_code)] #[derive(Copy, Clone, PartialEq, Eq, Count)] enum Trace { #[count(skip)] None, - FrontIOBoardReady(#[count(children)] bool), - FrontIOSeqErr(SeqError), + FrontIOStatus(#[count(children)] FrontIOStatus), LEDInit, - LEDInitComplete, - LEDInitError(Error), - LEDErrorSummary(FullErrorSummary), - LEDUninitialized, - LEDEnableError(FpgaError), - LEDReadError(Error), - LEDUpdateError(Error), + LEDEnableError(FrontIOError), ModulePresenceUpdate(LogicalPortMask), TransceiversError(#[count(children)] TransceiversError), GotInterface(u8, ManagementInterface), @@ -94,31 +76,14 @@ const MAX_CONSECUTIVE_NACKS: u8 = 3; //////////////////////////////////////////////////////////////////////////////// -#[derive(Copy, Clone)] -struct LedStates([LedState; NUM_PORTS as usize]); - -#[derive(Copy, Clone, PartialEq)] -enum FrontIOStatus { - NotReady, - NotPresent, - Ready, -} - struct ServerImpl { - transceivers: Transceivers, - leds: Leds, + /// The FrontIO server is the interface to the transceivers and LED drivers + front_io: FrontIO, net: task_net_api::Net, modules_present: LogicalPortMask, /// The Front IO board is not guaranteed to be present and ready - front_io_board_present: FrontIOStatus, - - /// State around LED management - led_error: FullErrorSummary, - leds_initialized: bool, - led_states: LedStates, - blink_on: bool, - system_led_state: LedState, + front_io_status: FrontIOStatus, /// Modules that are physically present but disabled by Hubris disabled: LogicalPortMask, @@ -151,73 +116,6 @@ struct ThermalModel { /// their temperature and send it to the `thermal` task. const SPI_INTERVAL: u64 = 500; -/// Controls how often we update the LED controllers (in milliseconds). -const I2C_INTERVAL: u64 = 100; - -/// Blink LEDs at a 50% duty cycle (in milliseconds) -const BLINK_INTERVAL: u64 = 500; - -impl ServerImpl { - fn led_init(&mut self) { - match self.leds.initialize_current() { - Ok(_) => { - self.set_system_led_state(LedState::On); - self.leds_initialized = true; - ringbuf_entry!(Trace::LEDInitComplete); - } - Err(e) => ringbuf_entry!(Trace::LEDInitError(e)), - }; - } - - fn set_led_state(&mut self, mask: LogicalPortMask, state: LedState) { - for index in mask.to_indices() { - self.led_states.0[index.0 as usize] = state; - } - } - - fn get_led_state(&self, port: LogicalPort) -> LedState { - self.led_states.0[port.0 as usize] - } - - fn set_system_led_state(&mut self, state: LedState) { - self.system_led_state = state; - } - - #[allow(dead_code)] - fn get_system_led_state(&self) -> LedState { - self.system_led_state - } - - fn update_leds(&mut self) { - // handle port LEDs - let mut next_state = LogicalPortMask(0); - for (i, state) in self.led_states.0.into_iter().enumerate() { - let i = LogicalPort(i as u8); - match state { - LedState::On => next_state.set(i), - LedState::Blink => { - if self.blink_on { - next_state.set(i) - } - } - LedState::Off => (), - } - } - if let Err(e) = self.leds.update_led_state(next_state) { - ringbuf_entry!(Trace::LEDUpdateError(e)); - } - // handle system LED - let system_led_on = match self.system_led_state { - LedState::On => true, - LedState::Blink => self.blink_on, - LedState::Off => false, - }; - if let Err(e) = self.leds.update_system_led_state(system_led_on) { - ringbuf_entry!(Trace::LEDUpdateError(e)); - } - } -} - impl ServerImpl { /// Returns the temperature from a CMIS transceiver. /// @@ -248,7 +146,15 @@ impl ServerImpl { port: LogicalPort, reg: u8, ) -> Result { - let result = self.transceivers.setup_i2c_read(reg, 2, port.as_mask()); + let result = match self.front_io.transceivers_setup_i2c_read( + reg, + 2, + port.as_mask(), + ) { + Ok(r) => r, + Err(e) => return Err(FpgaError::ImplError(e as u8)), + }; + if !result.error().is_empty() { return Err(FpgaError::CommsError); } @@ -261,8 +167,12 @@ impl ServerImpl { let mut out = Temperature::new_zeroed(); let status = self - .transceivers - .get_i2c_status_and_read_buffer(port, out.as_bytes_mut())?; + .front_io + .transceivers_get_i2c_status_and_read_buffer( + port, + out.as_bytes_mut(), + ) + .map_err(|e| FpgaError::ImplError(e as u8))?; if status.error == FpgaI2CFailure::NoError { // "Internally measured free side device temperatures are @@ -280,7 +190,15 @@ impl ServerImpl { &mut self, port: LogicalPort, ) -> Result { - let result = self.transceivers.setup_i2c_read(0, 1, port.as_mask()); + let result = match self.front_io.transceivers_setup_i2c_read( + 0, + 1, + port.as_mask(), + ) { + Ok(r) => r, + Err(e) => return Err(FpgaError::ImplError(e as u8)), + }; + if !result.error().is_empty() { return Err(FpgaError::CommsError); } @@ -289,8 +207,9 @@ impl ServerImpl { // Wait for the I2C transaction to complete let status = self - .transceivers - .get_i2c_status_and_read_buffer(port, &mut out)?; + .front_io + .transceivers_get_i2c_status_and_read_buffer(port, &mut out) + .map_err(|e| FpgaError::ImplError(e as u8))?; if status.error == FpgaI2CFailure::NoError { match out[0] { @@ -480,14 +399,14 @@ impl ServerImpl { fn disable_ports(&mut self, mask: LogicalPortMask) { ringbuf_entry!(Trace::DisablingPorts(mask)); for (step, f) in [ - Transceivers::assert_reset, - Transceivers::deassert_lpmode, - Transceivers::disable_power, + FrontIO::transceivers_assert_reset, + FrontIO::transceivers_deassert_lpmode, + FrontIO::transceivers_disable_power, ] .iter() .enumerate() { - let err = f(&mut self.transceivers, mask).error(); + let err = f(&mut self.front_io, mask).error(); if !err.is_empty() { ringbuf_entry!(Trace::DisableFailed(step, err)); } @@ -498,30 +417,11 @@ impl ServerImpl { // the `sensors` and `thermal` tasks. } - fn handle_i2c_loop(&mut self) { - if self.leds_initialized { - self.update_leds(); - let errors = match self.leds.error_summary() { - Ok(errs) => errs, - Err(e) => { - ringbuf_entry!(Trace::LEDReadError(e)); - Default::default() - } - }; - if errors != self.led_error { - self.led_error = errors; - ringbuf_entry!(Trace::LEDErrorSummary(errors)); - } - } else { - ringbuf_entry!(Trace::LEDUninitialized); - } - } - fn handle_spi_loop(&mut self) { // Query module presence as this drives other state - let (status, _) = self.transceivers.get_module_status(); + let xcvr_status = self.front_io.transceivers_status(); - let modules_present = LogicalPortMask(!status.modprsl); + let modules_present = LogicalPortMask(!xcvr_status.status.modprsl); if modules_present != self.modules_present { // check to see if any disabled ports had their modules removed and // allow their power to be turned on when a module is reinserted @@ -529,7 +429,8 @@ impl ServerImpl { self.modules_present & !modules_present & self.disabled; if !disabled_ports_removed.is_empty() { self.disabled &= !disabled_ports_removed; - self.transceivers.enable_power(disabled_ports_removed); + self.front_io + .transceivers_enable_power(disabled_ports_removed); ringbuf_entry!(Trace::ClearDisabledPorts( disabled_ports_removed )); @@ -539,59 +440,25 @@ impl ServerImpl { ringbuf_entry!(Trace::ModulePresenceUpdate(modules_present)); } - self.update_thermal_loop(status); + self.update_thermal_loop(xcvr_status.status); } } //////////////////////////////////////////////////////////////////////////////// -impl idl::InOrderTransceiversImpl for ServerImpl { - fn get_module_status( - &mut self, - _msg: &userlib::RecvMessage, - ) -> Result> - { - let (mod_status, result) = self.transceivers.get_module_status(); - if result.error().is_empty() { - Ok(mod_status) - } else { - Err(RequestError::from(TransceiversError::FpgaError)) - } - } - - fn set_system_led_on( - &mut self, - _msg: &userlib::RecvMessage, - ) -> Result<(), idol_runtime::RequestError> { - self.set_system_led_state(LedState::On); - Ok(()) - } - - fn set_system_led_off( - &mut self, - _msg: &userlib::RecvMessage, - ) -> Result<(), idol_runtime::RequestError> { - self.set_system_led_state(LedState::Off); - Ok(()) - } - - fn set_system_led_blink( - &mut self, - _msg: &userlib::RecvMessage, - ) -> Result<(), idol_runtime::RequestError> { - self.set_system_led_state(LedState::Blink); - Ok(()) - } -} - impl NotificationHandler for ServerImpl { fn current_notification_mask(&self) -> u32 { notifications::SOCKET_MASK | notifications::TIMER_MASK } fn handle_notification(&mut self, _bits: u32) { - // Nothing to do here; notifications are just to wake up this task, and - // all of the actual work is handled in the main loop + if self.front_io_status == FrontIOStatus::Ready { + self.handle_spi_loop(); + } + + // Set next timer + let next_deadline = sys_get_timer().now + SPI_INTERVAL; + sys_set_timer(Some(next_deadline), notifications::TIMER_MASK); } } @@ -600,13 +467,7 @@ fn main() -> ! { // This is a temporary workaround that makes sure the FPGAs are up // before we start doing things with them. A more sophisticated // notification system will be put in place. - let seq = Sequencer::from(SEQ.get_task_id()); - - let transceivers = Transceivers::new(FRONT_IO.get_task_id()); - let leds = Leds::new( - &i2c_config::devices::pca9956b_front_leds_left(I2C.get_task_id()), - &i2c_config::devices::pca9956b_front_leds_right(I2C.get_task_id()), - ); + let front_io = FrontIO::from(FRONT_IO.get_task_id()); let net = task_net_api::Net::from(NET.get_task_id()); let thermal_api = Thermal::from(THERMAL.get_task_id()); @@ -621,16 +482,10 @@ fn main() -> ! { }; let mut server = ServerImpl { - transceivers, - leds, + front_io, net, modules_present: LogicalPortMask(0), - front_io_board_present: FrontIOStatus::NotReady, - led_error: Default::default(), - leds_initialized: false, - led_states: LedStates([LedState::Off; NUM_PORTS as usize]), - blink_on: false, - system_led_state: LedState::Off, + front_io_status: FrontIOStatus::NotPresent, disabled: LogicalPortMask(0), consecutive_nacks: [0; NUM_PORTS as usize], thermal_api, @@ -638,96 +493,29 @@ fn main() -> ! { thermal_models: [None; NUM_PORTS as usize], }; - // There are two timers, one for each communication bus: - #[derive(Copy, Clone, Enum)] - #[allow(clippy::upper_case_acronyms)] - enum Timers { - I2C, - SPI, - Blink, - } - let mut multitimer = Multitimer::::new(notifications::TIMER_BIT); - // Immediately fire each timer, then begin to service regularly - let now = sys_get_timer().now; - multitimer.set_timer( - Timers::I2C, - now, - Some(Repeat::AfterDeadline(I2C_INTERVAL)), - ); - multitimer.set_timer( - Timers::SPI, - now, - Some(Repeat::AfterDeadline(SPI_INTERVAL)), - ); - multitimer.set_timer( - Timers::Blink, - now, - Some(Repeat::AfterDeadline(BLINK_INTERVAL)), - ); - - let mut buffer = [0; idl::INCOMING_SIZE]; + // + // This will put our timer in the past, and should immediately kick us. + // + let deadline = sys_get_timer().now; + sys_set_timer(Some(deadline), notifications::TIMER_MASK); + loop { - if server.front_io_board_present == FrontIOStatus::NotReady { - server.front_io_board_present = match seq.front_io_board_ready() { - Ok(true) => { - ringbuf_entry!(Trace::FrontIOBoardReady(true)); - FrontIOStatus::Ready - } - Err(SeqError::NoFrontIOBoard) => { - ringbuf_entry!(Trace::FrontIOSeqErr( - SeqError::NoFrontIOBoard - )); - FrontIOStatus::NotPresent - } - _ => { - ringbuf_entry!(Trace::FrontIOBoardReady(false)); - FrontIOStatus::NotReady - } + // We do this check within the main loop because we still want to + // service any requests from `net` even if a front IO board is not ready + // If a board is ready, attempt to initialize its LED drivers + if server.front_io_status == FrontIOStatus::Ready { + if let Err(e) = server.front_io.leds_enable() { + ringbuf_entry!(Trace::LEDEnableError(e)); }; - - // If a board is present, attempt to initialize its - // LED drivers - if server.front_io_board_present == FrontIOStatus::Ready { - ringbuf_entry!(Trace::LEDInit); - match server.transceivers.enable_led_controllers() { - Ok(_) => server.led_init(), - Err(e) => { - ringbuf_entry!(Trace::LEDEnableError(e)) - } - }; - } - } - - multitimer.poll_now(); - for t in multitimer.iter_fired() { - match t { - Timers::I2C => { - // Handle the Front IO status checking as part of this - // loop because the frequency is what we had before and - // the server itself has no knowledge of the sequencer. - server.handle_i2c_loop(); - } - Timers::SPI => { - if server.front_io_board_present == FrontIOStatus::Ready { - server.handle_spi_loop(); - } - } - Timers::Blink => { - server.blink_on = !server.blink_on; - } - } + } else { + server.front_io_status = server.front_io.board_status(); + ringbuf_entry!(Trace::FrontIOStatus(server.front_io_status)); } + // monitor messages from the host server .check_net(tx_data_buf.as_mut_slice(), rx_data_buf.as_mut_slice()); - idol_runtime::dispatch(&mut buffer, &mut server); } } -mod idl { - use super::{ModuleStatus, TransceiversError}; - - include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); -} - include!(concat!(env!("OUT_DIR"), "/notifications.rs")); diff --git a/drv/transceivers-server/src/udp.rs b/drv/transceivers-server/src/udp.rs index cd0ec26cf..81f8be56c 100644 --- a/drv/transceivers-server/src/udp.rs +++ b/drv/transceivers-server/src/udp.rs @@ -17,9 +17,12 @@ use ringbuf::*; use userlib::UnwrapLite; use crate::{FrontIOStatus, ServerImpl}; -use drv_sidecar_front_io::transceivers::{ - FpgaI2CFailure, LogicalPort, LogicalPortFailureTypes, LogicalPortMask, - ModuleResult, ModuleResultNoFailure, ModuleResultSlim, +use drv_front_io_api::{ + transceivers::{ + FpgaI2CFailure, LogicalPort, LogicalPortFailureTypes, LogicalPortMask, + ModuleResult, ModuleResultNoFailure, ModuleResultSlim, + }, + FrontIOError, }; use task_net_api::*; use transceiver_messages::{ @@ -65,6 +68,7 @@ enum Trace { PageSelectI2CFailures(LogicalPort, FpgaI2CFailure), ReadI2CFailures(LogicalPort, FpgaI2CFailure), WriteI2CFailures(LogicalPort, FpgaI2CFailure), + FrontIOError(FrontIOError), } counted_ringbuf!(Trace, 32, Trace::None); @@ -310,9 +314,7 @@ impl ServerImpl { HostRequest::Status(modules) => { ringbuf_entry!(Trace::Status(modules)); let mask = LogicalPortMask::from(modules); - let (data_len, result) = if self.front_io_board_present - == FrontIOStatus::Ready - { + let (data_len, result) = if self.front_io.board_ready() { self.get_status(mask, out) } else { ( @@ -339,9 +341,7 @@ impl ServerImpl { HostRequest::ExtendedStatus(modules) => { ringbuf_entry!(Trace::ExtendedStatus(modules)); let mask = LogicalPortMask::from(modules); - let (data_len, result) = if self.front_io_board_present - == FrontIOStatus::Ready - { + let (data_len, result) = if self.front_io.board_ready() { self.get_extended_status(mask, out) } else { ( @@ -389,23 +389,22 @@ impl ServerImpl { ); } - let (data_len, result) = - if self.front_io_board_present == FrontIOStatus::Ready { - let r = self.read(read, mask & !self.disabled, out); - let len = r.success().count() * read.len() as usize; - (len, r) - } else { - ( - 0, - ModuleResult::new( - LogicalPortMask(0), - LogicalPortMask(0), - mask, - LogicalPortFailureTypes::default(), - ) - .unwrap_lite(), + let (data_len, result) = if self.front_io.board_ready() { + let r = self.read(read, mask & !self.disabled, out); + let len = r.success().count() * read.len() as usize; + (len, r) + } else { + ( + 0, + ModuleResult::new( + LogicalPortMask(0), + LogicalPortMask(0), + mask, + LogicalPortFailureTypes::default(), ) - }; + .unwrap_lite(), + ) + }; ringbuf_entry!(Trace::OperationResult(result.to_slim())); let success = ModuleId::from(result.success()); @@ -439,18 +438,17 @@ impl ServerImpl { } let mask = LogicalPortMask::from(modules); - let result = - if self.front_io_board_present == FrontIOStatus::Ready { - self.write(write, mask & !self.disabled, data) - } else { - ModuleResult::new( - LogicalPortMask(0), - LogicalPortMask(0), - mask, - LogicalPortFailureTypes::default(), - ) - .unwrap_lite() - }; + let result = if self.front_io.board_ready() { + self.write(write, mask & !self.disabled, data) + } else { + ModuleResult::new( + LogicalPortMask(0), + LogicalPortMask(0), + mask, + LogicalPortFailureTypes::default(), + ) + .unwrap_lite() + }; ringbuf_entry!(Trace::OperationResult(result.to_slim())); let success = ModuleId::from(result.success()); @@ -472,13 +470,12 @@ impl ServerImpl { ringbuf_entry!(Trace::AssertReset(modules)); let mask = LogicalPortMask::from(modules) & !self.disabled; - let result = - if self.front_io_board_present == FrontIOStatus::Ready { - self.transceivers.assert_reset(mask) - } else { - ModuleResultNoFailure::new(LogicalPortMask(0), mask) - .unwrap_lite() - }; + let result = if self.front_io.board_ready() { + self.front_io.transceivers_assert_reset(mask) + } else { + ModuleResultNoFailure::new(LogicalPortMask(0), mask) + .unwrap_lite() + }; ringbuf_entry!(Trace::OperationNoFailResult(result)); let success = ModuleId::from(result.success()); @@ -497,13 +494,12 @@ impl ServerImpl { ringbuf_entry!(Trace::DeassertReset(modules)); let mask = LogicalPortMask::from(modules) & !self.disabled; - let result = - if self.front_io_board_present == FrontIOStatus::Ready { - self.transceivers.deassert_reset(mask) - } else { - ModuleResultNoFailure::new(LogicalPortMask(0), mask) - .unwrap_lite() - }; + let result = if self.front_io.board_ready() { + self.front_io.transceivers_deassert_reset(mask) + } else { + ModuleResultNoFailure::new(LogicalPortMask(0), mask) + .unwrap_lite() + }; ringbuf_entry!(Trace::OperationNoFailResult(result)); let success = ModuleId::from(result.success()); @@ -522,13 +518,12 @@ impl ServerImpl { ringbuf_entry!(Trace::AssertLpMode(modules)); let mask = LogicalPortMask::from(modules) & !self.disabled; - let result = - if self.front_io_board_present == FrontIOStatus::Ready { - self.transceivers.assert_lpmode(mask) - } else { - ModuleResultNoFailure::new(LogicalPortMask(0), mask) - .unwrap_lite() - }; + let result = if self.front_io.board_ready() { + self.front_io.transceivers_assert_lpmode(mask) + } else { + ModuleResultNoFailure::new(LogicalPortMask(0), mask) + .unwrap_lite() + }; ringbuf_entry!(Trace::OperationNoFailResult(result)); let success = ModuleId::from(result.success()); @@ -547,13 +542,12 @@ impl ServerImpl { ringbuf_entry!(Trace::DeassertLpMode(modules)); let mask = LogicalPortMask::from(modules) & !self.disabled; - let result = - if self.front_io_board_present == FrontIOStatus::Ready { - self.transceivers.deassert_lpmode(mask) - } else { - ModuleResultNoFailure::new(LogicalPortMask(0), mask) - .unwrap_lite() - }; + let result = if self.front_io.board_ready() { + self.front_io.transceivers_deassert_lpmode(mask) + } else { + ModuleResultNoFailure::new(LogicalPortMask(0), mask) + .unwrap_lite() + }; ringbuf_entry!(Trace::OperationNoFailResult(result)); let success = ModuleId::from(result.success()); @@ -572,13 +566,12 @@ impl ServerImpl { ringbuf_entry!(Trace::EnablePower(modules)); let mask = LogicalPortMask::from(modules) & !self.disabled; - let result = - if self.front_io_board_present == FrontIOStatus::Ready { - self.transceivers.enable_power(mask) - } else { - ModuleResultNoFailure::new(LogicalPortMask(0), mask) - .unwrap_lite() - }; + let result = if self.front_io.board_ready() { + self.front_io.transceivers_enable_power(mask) + } else { + ModuleResultNoFailure::new(LogicalPortMask(0), mask) + .unwrap_lite() + }; ringbuf_entry!(Trace::OperationNoFailResult(result)); let success = ModuleId::from(result.success()); @@ -597,13 +590,12 @@ impl ServerImpl { ringbuf_entry!(Trace::DisablePower(modules)); let mask = LogicalPortMask::from(modules) & !self.disabled; - let result = - if self.front_io_board_present == FrontIOStatus::Ready { - self.transceivers.disable_power(mask) - } else { - ModuleResultNoFailure::new(LogicalPortMask(0), mask) - .unwrap_lite() - }; + let result = if self.front_io.board_ready() { + self.front_io.transceivers_disable_power(mask) + } else { + ModuleResultNoFailure::new(LogicalPortMask(0), mask) + .unwrap_lite() + }; ringbuf_entry!(Trace::OperationNoFailResult(result)); let success = ModuleId::from(result.success()); @@ -648,13 +640,12 @@ impl ServerImpl { ringbuf_entry!(Trace::ClearPowerFault(modules)); let mask = LogicalPortMask::from(modules) & !self.disabled; - let result = - if self.front_io_board_present == FrontIOStatus::Ready { - self.transceivers.clear_power_fault(mask) - } else { - ModuleResultNoFailure::new(LogicalPortMask(0), mask) - .unwrap_lite() - }; + let result = if self.front_io.board_ready() { + self.front_io.transceivers_clear_power_fault(mask) + } else { + ModuleResultNoFailure::new(LogicalPortMask(0), mask) + .unwrap_lite() + }; ringbuf_entry!(Trace::OperationNoFailResult(result)); let success = ModuleId::from(result.success()); @@ -672,9 +663,7 @@ impl ServerImpl { HostRequest::LedState(modules) => { ringbuf_entry!(Trace::LedState(modules)); let mask = LogicalPortMask::from(modules); - let (data_len, result) = if self.front_io_board_present - == FrontIOStatus::Ready - { + let (data_len, result) = if self.front_io.board_ready() { self.get_led_state_response(mask, out) } else { ( @@ -702,15 +691,14 @@ impl ServerImpl { ringbuf_entry!(Trace::SetLedState(modules, state)); let mask = LogicalPortMask::from(modules); - let result = - if self.front_io_board_present == FrontIOStatus::Ready { - self.set_led_state(mask, state); - ModuleResultNoFailure::new(mask, LogicalPortMask(0)) - .unwrap_lite() - } else { - ModuleResultNoFailure::new(LogicalPortMask(0), mask) - .unwrap_lite() - }; + let result = if self.front_io.board_ready() { + self.front_io.led_set_state(mask, state); + ModuleResultNoFailure::new(mask, LogicalPortMask(0)) + .unwrap_lite() + } else { + ModuleResultNoFailure::new(LogicalPortMask(0), mask) + .unwrap_lite() + }; // This operation just sets internal SP state, so it is always // successful. However, invalid modules may been specified by @@ -883,12 +871,14 @@ impl ServerImpl { // shared logic between the various functions which handle errors fn resolve_error_type(&self) -> HwError { - match self.front_io_board_present { + match self.front_io.board_status() { // Front IO is present and ready, so the only other error // path currently is if we handle an FpgaError. FrontIOStatus::Ready => HwError::FpgaError, FrontIOStatus::NotPresent => HwError::NoFrontIo, - FrontIOStatus::NotReady => HwError::FrontIoNotReady, + FrontIOStatus::FpgaInit | FrontIOStatus::OscInit => { + HwError::FrontIoNotReady + } } } @@ -961,7 +951,9 @@ impl ServerImpl { ) -> (usize, ModuleResultNoFailure) { // This will get the status of every module, so we will have to only // select the data which was requested. - let (mod_status, full_result) = self.transceivers.get_module_status(); + let xcvr_status = self.front_io.transceivers_status(); + let mod_status = xcvr_status.status; + let full_result = xcvr_status.result; // adjust the result success mask to be only our requested modules let desired_result = ModuleResultNoFailure::new( full_result.success() & modules, @@ -1016,7 +1008,9 @@ impl ServerImpl { ) -> (usize, ModuleResultNoFailure) { // This will get the status of every module, so we will have to only // select the data which was requested. - let (mod_status, full_result) = self.transceivers.get_module_status(); + let xcvr_status = self.front_io.transceivers_status(); + let mod_status = xcvr_status.status; + let full_result = xcvr_status.result; // adjust the result success mask to be only our requested modules let desired_result = ModuleResultNoFailure::new( full_result.success() & modules, @@ -1086,12 +1080,29 @@ impl ServerImpl { // We can always write the lower page; upper pages require modifying // registers in the transceiver to select it. if let Some(page) = page.page() { - self.transceivers.set_i2c_write_buffer(&[page], mask); - result = result.chain(self.transceivers.setup_i2c_write( + result = match self + .front_io + .transceivers_set_i2c_write_buffer(mask, &[page]) + { + Ok(r) => result.chain(r), + Err(e) => { + ringbuf_entry!(Trace::FrontIOError(e)); + result + } + }; + + result = match self.front_io.transceivers_setup_i2c_write( PAGE_SELECT, 1, mask, - )); + ) { + Ok(r) => result.chain(r), + Err(e) => { + ringbuf_entry!(Trace::FrontIOError(e)); + result + } + }; + result = result.chain(self.wait_and_check_i2c(result.success())); } else { // If the request is to the lower page it is always successful @@ -1105,12 +1116,29 @@ impl ServerImpl { } if let Some(bank) = page.bank() { - self.transceivers.set_i2c_write_buffer(&[bank], mask); - result = result.chain(self.transceivers.setup_i2c_write( + result = match self + .front_io + .transceivers_set_i2c_write_buffer(mask, &[bank]) + { + Ok(r) => result.chain(r), + Err(e) => { + ringbuf_entry!(Trace::FrontIOError(e)); + result + } + }; + + result = match self.front_io.transceivers_setup_i2c_write( BANK_SELECT, 1, result.success(), - )); + ) { + Ok(r) => result.chain(r), + Err(e) => { + ringbuf_entry!(Trace::FrontIOError(e)); + result + } + }; + result = result.chain(self.wait_and_check_i2c(result.success())); } @@ -1129,7 +1157,7 @@ impl ServerImpl { // failure: The I2C operation failed. // error: The SP could not communicate with the FPGA. fn wait_and_check_i2c(&mut self, mask: LogicalPortMask) -> ModuleResult { - self.transceivers.wait_and_check_i2c(mask) + self.front_io.transceivers_wait_and_check_i2c(mask) } fn read( @@ -1142,11 +1170,17 @@ impl ServerImpl { let mut result = self.select_page(*mem.page(), modules); // Ask the FPGA to start the read - result = result.chain(self.transceivers.setup_i2c_read( + result = match self.front_io.transceivers_setup_i2c_read( mem.offset(), mem.len(), result.success(), - )); + ) { + Ok(r) => result.chain(r), + Err(e) => { + ringbuf_entry!(Trace::FrontIOError(e)); + result + } + }; let mut success = LogicalPortMask(0); let mut failure = LogicalPortMask(0); @@ -1157,14 +1191,15 @@ impl ServerImpl { for port in result.success().to_indices() { let mut buf = [0u8; 128]; - let port_loc = port.get_physical_location(); // If we have not encountered any errors, keep pulling full // status + buffer payloads. let status = match self - .transceivers - .get_i2c_status_and_read_buffer(port_loc, &mut buf[0..buf_len]) - { + .front_io + .transceivers_get_i2c_status_and_read_buffer( + port, + &mut buf[0..buf_len], + ) { Ok(status) => status, Err(_) => { error.set(port); @@ -1211,15 +1246,29 @@ impl ServerImpl { let mut result = self.select_page(*mem.page(), modules); // Copy data into the FPGA write buffer - self.transceivers - .set_i2c_write_buffer(&data[..mem.len() as usize], modules); + result = match self.front_io.transceivers_set_i2c_write_buffer( + modules, + &data[..mem.len() as usize], + ) { + Ok(r) => result.chain(r), + Err(e) => { + ringbuf_entry!(Trace::FrontIOError(e)); + result + } + }; // Trigger a multicast write to all transceivers in the mask - result = result.chain(self.transceivers.setup_i2c_write( + result = match self.front_io.transceivers_setup_i2c_write( mem.offset(), mem.len(), result.success(), - )); + ) { + Ok(r) => result.chain(r), + Err(e) => { + ringbuf_entry!(Trace::FrontIOError(e)); + result + } + }; result = result.chain(self.wait_and_check_i2c(result.success())); for port in result.failure().to_indices() { @@ -1237,7 +1286,9 @@ impl ServerImpl { out: &mut [u8], ) -> (usize, ModuleResultNoFailure) { let mut led_state_len = 0; - for led_state in modules.to_indices().map(|m| self.get_led_state(m)) { + for led_state in + modules.to_indices().map(|m| self.front_io.led_get_state(m)) + { // LedState will serialize to a u8, so we aren't concerned about // buffer overflow here let led_state_size = diff --git a/idl/front-io.idol b/idl/front-io.idol new file mode 100644 index 000000000..8e7c1ca8b --- /dev/null +++ b/idl/front-io.idol @@ -0,0 +1,308 @@ +// Front IO API + +Interface( + name: "FrontIO", + ops: { + "board_reset": ( + args: {}, + reply: Simple("()"), + idempotent: true, + ), + + "board_status": ( + args: {}, + reply: Simple("FrontIOStatus"), + idempotent: true, + encoding: Hubpack, + ), + + "board_present": ( + args: {}, + reply: Simple("bool"), + idempotent: true, + ), + + "board_ready": ( + args: {}, + reply: Simple("bool"), + idempotent: true, + ), + + "phy_osc_state": ( + args: {}, + reply: Result( + ok: ( + type: "PhyOscState", + recv: FromPrimitive("u8"), + ), + err: CLike("FrontIOError"), + ), + ), + + "phy_ready": ( + args: {}, + reply: Result( + ok: "bool", + err: CLike("FrontIOError"), + ), + ), + + "phy_set_osc_state": ( + args: { + "good": "bool", + }, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "phy_enable_power": ( + args: {}, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "phy_disable_power": ( + args: {}, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "phy_set_coma_mode": ( + args: { + "asserted": "bool", + }, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "phy_read": ( + args: { + "phy": "u8", + "reg": "u8", + }, + reply: Result( + ok: "u16", + err: CLike("FrontIOError"), + ), + ), + + "phy_write": ( + args: { + "phy": "u8", + "reg": "u8", + "value": "u16", + }, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "leds_assert_reset": ( + args: {}, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "leds_deassert_reset": ( + args: {}, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "leds_enable": ( + args: {}, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "leds_disable": ( + args: {}, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "led_set_state": ( + args: { + "mask": "LogicalPortMask", + "state": "LedState", + }, + reply: Simple("()"), + idempotent: true, + encoding: Hubpack, + ), + + "led_get_state": ( + args: { + "mask": "LogicalPort", + }, + reply: Simple("LedState"), + idempotent: true, + encoding: Hubpack, + ), + + "led_set_system_on": ( + doc: "Turn on the System LED.", + reply: Simple("()"), + idempotent: true, + ), + + "led_set_system_off": ( + doc: "Turn off the System LED.", + reply: Simple("()"), + idempotent: true, + ), + + "led_set_system_blink": ( + doc: "Blink the System LED.", + reply: Simple("()"), + idempotent: true, + ), + + "led_get_system_state": ( + args: {}, + reply: Simple("LedState"), + idempotent: true, + encoding: Hubpack, + ), + + "transceivers_status": ( + args: {}, + reply: Simple("TransceiverStatus"), + idempotent: true, + ), + + "transceivers_enable_power": ( + args: { + "mask": "LogicalPortMask", + }, + reply: Simple("ModuleResultNoFailure"), + idempotent: true, + ), + + "transceivers_disable_power": ( + args: { + "mask": "LogicalPortMask", + }, + reply: Simple("ModuleResultNoFailure"), + idempotent: true, + ), + + "transceivers_clear_power_fault": ( + args: { + "mask": "LogicalPortMask", + }, + reply: Simple("ModuleResultNoFailure"), + idempotent: true, + ), + + "transceivers_assert_reset": ( + args: { + "mask": "LogicalPortMask", + }, + reply: Simple("ModuleResultNoFailure"), + idempotent: true, + ), + + "transceivers_deassert_reset": ( + args: { + "mask": "LogicalPortMask", + }, + reply: Simple("ModuleResultNoFailure"), + idempotent: true, + ), + + "transceivers_assert_lpmode": ( + args: { + "mask": "LogicalPortMask", + }, + reply: Simple("ModuleResultNoFailure"), + idempotent: true, + ), + + "transceivers_deassert_lpmode": ( + args: { + "mask": "LogicalPortMask", + }, + reply: Simple("ModuleResultNoFailure"), + idempotent: true, + ), + + "transceivers_setup_i2c_read": ( + args: { + "reg": "u8", + "num_bytes": "u8", + "mask": "LogicalPortMask", + }, + reply: Result( + ok: "ModuleResultNoFailure", + err: CLike("FrontIOError"), + ), + ), + + "transceivers_setup_i2c_write": ( + args: { + "reg": "u8", + "num_bytes": "u8", + "mask": "LogicalPortMask", + }, + reply: Result( + ok: "ModuleResultNoFailure", + err: CLike("FrontIOError"), + ), + ), + + "transceivers_get_i2c_status_and_read_buffer": ( + args: { + "port": "LogicalPort", + }, + leases: { + "dest": (type: "[u8]", write: true), + }, + reply: Result( + ok: "PortI2CStatus", + err: CLike("FrontIOError"), + ), + encoding: Hubpack, + ), + + "transceivers_set_i2c_write_buffer": ( + args: { + "mask": "LogicalPortMask", + }, + leases: { + "data": (type: "[u8]", read: true), + }, + reply: Result( + ok: "ModuleResultNoFailure", + err: CLike("FrontIOError"), + ), + encoding: Hubpack, + ), + + "transceivers_wait_and_check_i2c": ( + args: { + "mask": "LogicalPortMask", + }, + reply: Simple("ModuleResult"), + idempotent: true, + encoding: Hubpack, + ), + }, +) diff --git a/idl/sidecar-seq.idol b/idl/sidecar-seq.idol index 3e037f520..88cf73de3 100644 --- a/idl/sidecar-seq.idol +++ b/idl/sidecar-seq.idol @@ -1,4 +1,4 @@ -// Gimlet Sequencer API +// Sidecar Sequencer API Interface( name: "Sequencer", @@ -151,18 +151,6 @@ Interface( ), ), - "front_io_board_present": ( - args: {}, - reply: Simple("bool"), - idempotent: true, - ), - "front_io_board_ready": ( - args: {}, - reply: Result( - ok: "bool", - err: CLike("SeqError"), - ), - ), "reset_front_io_phy": ( args: {}, reply: Result( @@ -170,15 +158,6 @@ Interface( err: CLike("SeqError"), ), ), - "set_front_io_phy_osc_state": ( - args: { - "good": "bool", - }, - reply: Result( - ok: "()", - err: CLike("SeqError"), - ), - ), "tofino_debug_port_state": ( doc: "Return the state of the Tofino debug port", diff --git a/idl/transceivers.idol b/idl/transceivers.idol deleted file mode 100644 index 0497e5aad..000000000 --- a/idl/transceivers.idol +++ /dev/null @@ -1,38 +0,0 @@ -// Transceivers API - -Interface( - name: "Transceivers", - ops: { - "get_module_status": ( - doc: "Collect the status of each modules control and status signals", - reply: Result( - ok: "ModuleStatus", - err: CLike("TransceiversError"), - ), - ), - - "set_system_led_on": ( - doc: "Turn on the System LED.", - reply: Result( - ok: "()", - err: CLike("TransceiversError"), - ), - ), - - "set_system_led_off": ( - doc: "Turn off the System LED.", - reply: Result( - ok: "()", - err: CLike("TransceiversError"), - ), - ), - - "set_system_led_blink": ( - doc: "Blink the System LED.", - reply: Result( - ok: "()", - err: CLike("TransceiversError"), - ), - ), - } -) diff --git a/task/control-plane-agent/Cargo.toml b/task/control-plane-agent/Cargo.toml index a049c7a41..eba09f787 100644 --- a/task/control-plane-agent/Cargo.toml +++ b/task/control-plane-agent/Cargo.toml @@ -17,6 +17,7 @@ zerocopy.workspace = true drv-auxflash-api = { path = "../../drv/auxflash-api", optional = true } drv-caboose = { path = "../../drv/caboose" } drv-caboose-pos = { path = "../../drv/caboose-pos" } +drv-front-io-api = { path = "../../drv/front-io-api", optional = true } drv-gimlet-hf-api = { path = "../../drv/gimlet-hf-api", optional = true } drv-gimlet-seq-api = { path = "../../drv/gimlet-seq-api", optional = true } drv-ignition-api = { path = "../../drv/ignition-api", optional = true } @@ -26,7 +27,6 @@ drv-sidecar-seq-api = { path = "../../drv/sidecar-seq-api", optional = true } drv-sprot-api = { path = "../../drv/sprot-api" } drv-stm32h7-update-api = { path = "../../drv/stm32h7-update-api" } drv-stm32h7-usart = { path = "../../drv/stm32h7-usart", features = ["h753"], optional = true } -drv-transceivers-api = { path = "../../drv/transceivers-api", optional = true } drv-update-api = { path = "../../drv/update-api" } drv-user-leds-api = { path = "../../drv/user-leds-api", optional = true } task-vpd-api = { path = "../../task/vpd-api", optional = true } @@ -51,7 +51,7 @@ idol = { workspace = true } [features] no-ipc-counters = ["idol/no-counters"] gimlet = ["drv-gimlet-hf-api", "drv-gimlet-seq-api", "drv-stm32h7-usart", "drv-user-leds-api"] -sidecar = ["drv-sidecar-seq-api", "drv-monorail-api", "drv-ignition-api", "drv-transceivers-api"] +sidecar = ["drv-sidecar-seq-api", "drv-monorail-api", "drv-ignition-api", "drv-front-io-api"] psc = ["drv-user-leds-api"] vpd = ["task-vpd-api"] vlan = ["task-net-api/vlan"] diff --git a/task/control-plane-agent/src/mgs_sidecar.rs b/task/control-plane-agent/src/mgs_sidecar.rs index cacdeb33f..da24c24b1 100644 --- a/task/control-plane-agent/src/mgs_sidecar.rs +++ b/task/control-plane-agent/src/mgs_sidecar.rs @@ -6,10 +6,10 @@ use crate::{ mgs_common::MgsCommon, update::rot::RotUpdate, update::sp::SpUpdate, update::ComponentUpdater, usize_max, CriticalEvent, Log, MgsMessage, }; +use drv_front_io_api::FrontIO; use drv_ignition_api::IgnitionError; use drv_monorail_api::{Monorail, MonorailError}; use drv_sidecar_seq_api::Sequencer; -use drv_transceivers_api::Transceivers; use gateway_messages::sp_impl::{ BoundsChecked, DeviceDescription, SocketAddrV6, SpHandler, }; @@ -38,7 +38,7 @@ use ignition_handler::IgnitionController; userlib::task_slot!(SIDECAR_SEQ, sequencer); userlib::task_slot!(MONORAIL, monorail); -userlib::task_slot!(TRANSCEIVERS, transceivers); +userlib::task_slot!(FRONT_IO, front_io); // How big does our shared update buffer need to be? Has to be able to handle SP // update blocks for now, no other updateable components. @@ -62,7 +62,7 @@ pub(crate) struct MgsHandler { common: MgsCommon, sequencer: Sequencer, monorail: Monorail, - transceivers: Transceivers, + front_io: FrontIO, ignition: IgnitionController, } @@ -74,7 +74,7 @@ impl MgsHandler { common: MgsCommon::claim_static_resources(base_mac_address), sequencer: Sequencer::from(SIDECAR_SEQ.get_task_id()), monorail: Monorail::from(MONORAIL.get_task_id()), - transceivers: Transceivers::from(TRANSCEIVERS.get_task_id()), + front_io: FrontIO::from(FRONT_IO.get_task_id()), ignition: IgnitionController::new(), } } @@ -331,16 +331,15 @@ impl SpHandler for MgsHandler { use gateway_messages::LedComponentAction; match action { LedComponentAction::TurnOn => { - self.transceivers.set_system_led_on() + self.front_io.led_set_system_on(); } LedComponentAction::Blink => { - self.transceivers.set_system_led_blink() + self.front_io.led_set_system_blink(); } LedComponentAction::TurnOff => { - self.transceivers.set_system_led_off() + self.front_io.led_set_system_off(); } } - .unwrap(); Ok(()) } _ => Err(SpError::RequestUnsupportedForComponent), diff --git a/task/monorail-server/Cargo.toml b/task/monorail-server/Cargo.toml index 827218c1b..353f7a5e8 100644 --- a/task/monorail-server/Cargo.toml +++ b/task/monorail-server/Cargo.toml @@ -4,9 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] +drv-front-io-api = { path = "../../drv/front-io-api", optional = true } drv-monorail-api = { path = "../../drv/monorail-api" } drv-sidecar-mainboard-controller = { path = "../../drv/sidecar-mainboard-controller" } -drv-sidecar-front-io = { path = "../../drv/sidecar-front-io", features = ["phy_smi"], optional = true } drv-sidecar-seq-api = { path = "../../drv/sidecar-seq-api", optional = true } drv-spi-api = { path = "../../drv/spi-api" } drv-stm32h7-spi-server-core = { path = "../../drv/stm32h7-spi-server-core", optional = true } @@ -30,7 +30,7 @@ zerocopy.workspace = true [features] leds = ["drv-user-leds-api"] mgmt = ["task-net-api"] -sidecar = ["drv-sidecar-seq-api", "drv-sidecar-front-io"] +sidecar = ["drv-sidecar-seq-api", "drv-front-io-api"] vlan = ["task-net-api?/vlan"] no-ipc-counters = ["idol/no-counters"] use-spi-core = ["drv-stm32h7-spi-server-core"] diff --git a/task/monorail-server/src/bsp/sidecar_bcd.rs b/task/monorail-server/src/bsp/sidecar_bcd.rs index 159956dde..cfc30a5f6 100644 --- a/task/monorail-server/src/bsp/sidecar_bcd.rs +++ b/task/monorail-server/src/bsp/sidecar_bcd.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use drv_sidecar_front_io::phy_smi::PhySmi; +use drv_front_io_api::FrontIO; use drv_sidecar_seq_api::Sequencer; use ringbuf::*; use userlib::{hl::sleep_for, task_slot, UnwrapLite}; @@ -13,7 +13,7 @@ use vsc7448_pac::{DEVCPU_GCB, HSIO, VAUI0, VAUI1}; use vsc85xx::{vsc8504::Vsc8504, vsc8562::Vsc8562Phy, PhyRw}; task_slot!(SEQ, seq); -task_slot!(FRONT_IO, ecp5_front_io); +task_slot!(FRONT_IO, front_io); /// Interval at which `Bsp::wake()` is called by the main loop pub const WAKE_INTERVAL: Option = Some(500); @@ -46,6 +46,9 @@ pub struct Bsp<'a, R> { /// PHY for the on-board PHY ("PHY4") vsc8504: Vsc8504, + /// handle for the front-io task + front_io: FrontIO, + /// RPC handle for the front IO board's PHY, which is a VSC8562. This is /// used for PHY control via a Rube Goldberg machine of /// Hubris RPC -> SPI -> FPGA -> MDIO -> PHY @@ -151,19 +154,20 @@ pub fn preinit() { impl<'a, R: Vsc7448Rw> Bsp<'a, R> { /// Constructs and initializes a new BSP handle pub fn new(vsc7448: &'a Vsc7448<'a, R>) -> Result { - let seq = Sequencer::from(SEQ.get_task_id()); - let has_front_io = seq.front_io_board_present(); + let front_io = FrontIO::from(FRONT_IO.get_task_id()); + let has_front_io = front_io.board_present(); let mut out = Bsp { vsc7448, vsc8504: Vsc8504::empty(), + front_io_speed: [Speed::Speed1G; 2], + link_down_at: None, + seq: Sequencer::from(SEQ.get_task_id()), + front_io, vsc8562: if has_front_io { Some(PhySmi::new(FRONT_IO.get_task_id())) } else { None }, - front_io_speed: [Speed::Speed1G; 2], - link_down_at: None, - seq, }; out.reinit()?; @@ -229,11 +233,11 @@ impl<'a, R: Vsc7448Rw> Bsp<'a, R> { osc_good = self.is_front_io_link_good()?; - // Notify the sequencer about the state of the oscillator. If the + // Notify the front IO about the state of the oscillator. If the // oscillator is good any future resets of the PHY do not require a // full power cycle of the front IO board. - self.seq - .set_front_io_phy_osc_state(osc_good) + self.front_io + .phy_set_osc_state(osc_good) .map_err(|e| VscError::ProxyError(e.into()))?; if !osc_good { @@ -396,8 +400,8 @@ impl<'a, R: Vsc7448Rw> Bsp<'a, R> { let mut v = Vsc8562Phy { phy: &mut phy }; v.init_qsgmii()?; } - phy_rw - .set_coma_mode(false) + self.front_io + .phy_set_coma_mode(false) .map_err(|e| VscError::ProxyError(e.into()))?; } @@ -572,3 +576,31 @@ impl<'a, R: Vsc7448Rw> PhyRw for GenericPhyRw<'a, R> { } } } + +pub struct PhySmi { + server: FrontIO, +} + +impl PhySmi { + pub fn new(front_io_task: userlib::TaskId) -> Self { + Self { + server: FrontIO::from(front_io_task), + } + } +} + +impl PhyRw for PhySmi { + #[inline(always)] + fn read_raw(&self, phy: u8, reg: u8) -> Result { + self.server + .phy_read(phy, reg) + .map_err(|e| VscError::ProxyError(e.into())) + } + + #[inline(always)] + fn write_raw(&self, phy: u8, reg: u8, value: u16) -> Result<(), VscError> { + self.server + .phy_write(phy, reg, value) + .map_err(|e| VscError::ProxyError(e.into())) + } +} diff --git a/task/thermal/Cargo.toml b/task/thermal/Cargo.toml index 931ad1058..3a62a284d 100644 --- a/task/thermal/Cargo.toml +++ b/task/thermal/Cargo.toml @@ -12,6 +12,7 @@ serde.workspace = true hubpack.workspace = true zerocopy.workspace = true +drv-front-io-api = { path = "../../drv/front-io-api", optional = true } drv-gimlet-seq-api = { path = "../../drv/gimlet-seq-api", optional = true } drv-sidecar-seq-api = { path = "../../drv/sidecar-seq-api", optional = true } drv-transceivers-api = { path = "../../drv/transceivers-api", optional = true } @@ -36,7 +37,7 @@ build-util = { path = "../../build/util" } [features] gimlet = ["drv-gimlet-seq-api", "h753"] -sidecar = ["drv-sidecar-seq-api", "drv-transceivers-api", "h753"] +sidecar = ["drv-sidecar-seq-api", "drv-front-io-api", "drv-transceivers-api", "h753"] h743 = ["build-i2c/h743"] h753 = ["build-i2c/h753"] h7b3 = ["build-i2c/h7b3"] diff --git a/task/thermal/src/bsp/sidecar_bcd.rs b/task/thermal/src/bsp/sidecar_bcd.rs index e1b35dde5..62b06beda 100644 --- a/task/thermal/src/bsp/sidecar_bcd.rs +++ b/task/thermal/src/bsp/sidecar_bcd.rs @@ -34,7 +34,7 @@ pub const NUM_TEMPERATURE_INPUTS: usize = // External temperature inputs, which are provided to the task over IPC // In practice, these are our transceivers. pub const NUM_DYNAMIC_TEMPERATURE_INPUTS: usize = - drv_transceivers_api::NUM_PORTS as usize; + drv_front_io_api::transceivers::NUM_PORTS as usize; // Number of individual fans pub const NUM_FANS: usize = sensors::NUM_MAX31790_SPEED_SENSORS;