diff --git a/Cargo.lock b/Cargo.lock index b5f340d180..012f4bd3c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1320,14 +1320,20 @@ dependencies = [ name = "drv-front-io-server" version = "0.1.0" dependencies = [ + "build-i2c", "build-util", + "drv-fpga-api", "drv-fpga-user-api", "drv-front-io-api", - "drv-sidecar-seq-api", + "drv-i2c-api", + "drv-i2c-devices", + "drv-sidecar-mainboard-controller", + "drv-stm32xx-sys-api", "enum-map", "hubpack", "idol", "idol-runtime", + "multitimer", "num-traits", "ringbuf", "serde", @@ -2233,7 +2239,6 @@ dependencies = [ "drv-packrat-vpd-loader", "drv-sidecar-mainboard-controller", "drv-sidecar-seq-api", - "drv-stm32xx-sys-api", "ereports", "fixedstr", "hubpack", diff --git a/app/medusa/base.toml b/app/medusa/base.toml index 30a2b5b473..6bf7afbb12 100644 --- a/app/medusa/base.toml +++ b/app/medusa/base.toml @@ -45,7 +45,7 @@ size = 256 [tasks.hiffy] name = "task-hiffy" -priority = 7 +priority = 5 max-sizes = {flash = 32768, ram = 32768} stacksize = 2048 start = true @@ -87,12 +87,12 @@ interrupts = {"spi1.irq" = "spi-irq"} [tasks.monorail] name = "task-monorail-server" -priority = 6 +priority = 7 max-sizes = {flash = 262144, ram = 8192} features = ["mgmt", "medusa", "vlan", "use-spi-core", "h753", "spi2"] stacksize = 4096 start = true -task-slots = ["ecp5_front_io", "sys", { seq = "sequencer" }] +task-slots = ["front_io", "sys"] uses = ["spi2"] notifications = ["spi-irq", "wake-timer"] interrupts = {"spi2.irq" = "spi-irq"} @@ -100,7 +100,7 @@ interrupts = {"spi2.irq" = "spi-irq"} [tasks.net] name = "task-net" stacksize = 6040 -priority = 5 +priority = 6 features = ["mgmt", "h753", "medusa", "vlan", "vpd-mac", "use-spi-core", "spi3"] max-sizes = {flash = 131072, ram = 65536, sram1_mac = 16384} sections = {eth_bulk = "sram1_mac"} @@ -116,7 +116,7 @@ task-slots = ["sys", "packrat", { seq = "sequencer" }, "jefe"] [tasks.sequencer] name = "drv-medusa-seq-server" -priority = 4 +priority = 5 stacksize = 4096 start = true task-slots = [ @@ -124,12 +124,12 @@ task-slots = [ "i2c_driver", "auxflash", "packrat", - {front_io = "ecp5_front_io"}] + "front_io"] [tasks.transceivers] name = "drv-transceivers-server" features = ["vlan"] -priority = 6 +priority = 7 max-sizes = {flash = 65536, ram = 16384} stacksize = 4096 start = true @@ -145,11 +145,15 @@ notifications = ["socket", "timer"] [tasks.front_io] name = "drv-front-io-server" -priority = 5 +priority = 4 max-sizes = {flash = 65536, ram = 16384} stacksize = 4096 start = true -task-slots = ["sequencer"] +task-slots = [ + "sys", + "auxflash", + "i2c_driver", + "ecp5_front_io"] notifications = ["timer"] [tasks.packrat] @@ -205,7 +209,7 @@ task-slots = ["sys"] [tasks.udpecho] name = "task-udpecho" -priority = 6 +priority = 7 max-sizes = {flash = 16384, ram = 8192} stacksize = 4096 start = true @@ -215,7 +219,7 @@ notifications = ["socket"] [tasks.udpbroadcast] name = "task-udpbroadcast" -priority = 6 +priority = 7 max-sizes = {flash = 16384, ram = 8192} stacksize = 4096 start = true @@ -357,14 +361,14 @@ refdes = "U16" [[config.i2c.controllers]] controller = 2 [config.i2c.controllers.ports.F] -name = "front_io0" +name = "front_io" description = "Front I/O Board" scl.pin = 1 # I2C_FRONT_IO0_SCL sda.pin = 0 # I2C_FRONT_IO0_SDA af = 4 [[config.i2c.devices]] -bus = "front_io0" +bus = "front_io" address = 0b1010_000 device = "at24csw080" description = "Front IO board FRUID" @@ -375,7 +379,7 @@ refdes = [ ] [[config.i2c.devices]] -bus = "front_io0" +bus = "front_io" address = 0b1110_011 device = "pca9538" description = "Front IO GPIO expander" @@ -386,7 +390,7 @@ refdes = [ ] [[config.i2c.devices]] -bus = "front_io0" +bus = "front_io" address = 0b0001_010 device = "pca9956b" name = "front_leds_left" @@ -398,7 +402,7 @@ refdes = [ ] [[config.i2c.devices]] -bus = "front_io0" +bus = "front_io" address = 0b0001_011 device = "pca9956b" name = "front_leds_right" @@ -410,7 +414,7 @@ refdes = [ ] [[config.i2c.devices]] -bus = "front_io0" +bus = "front_io" address = 0b0011_011 device = "tps546b24a" description = "Front IO V3P3_SYS_A2 rail" @@ -423,7 +427,7 @@ refdes = [ ] [[config.i2c.devices]] -bus = "front_io0" +bus = "front_io" address = 0b0011_001 device = "tps546b24a" description = "Front IO V3P3_QSFP0_A0 rail" @@ -436,7 +440,7 @@ refdes = [ ] [[config.i2c.devices]] -bus = "front_io0" +bus = "front_io" address = 0b0011_010 device = "tps546b24a" description = "Front IO V3P3_QSFP1_A0 rail" diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index 790bd7eec9..a4a1e678bb 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -261,11 +261,16 @@ notifications = ["socket", "timer"] [tasks.front_io] name = "drv-front-io-server" -priority = 5 +priority = 4 max-sizes = {flash = 65536, ram = 16384} stacksize = 4096 start = true -task-slots = ["sequencer"] +task-slots = [ + "auxflash", + "ecp5_front_io", + "ecp5_mainboard", + "i2c_driver", + "sys"] notifications = ["timer"] [tasks.packrat] @@ -279,7 +284,7 @@ notifications = ["task-faulted"] [tasks.sequencer] name = "drv-sidecar-seq-server" -priority = 4 +priority = 5 stacksize = 4096 start = true task-slots = [ @@ -287,14 +292,14 @@ task-slots = [ "i2c_driver", "auxflash", "packrat", - {mainboard = "ecp5_mainboard"}, - {front_io = "ecp5_front_io"}] + "front_io", + {mainboard = "ecp5_mainboard"}] notifications = ["timer"] [tasks.thermal] name = "task-thermal" features = ["sidecar"] -priority = 5 +priority = 6 max-sizes = {flash = 32768, ram = 16384 } stacksize = 4000 start = true @@ -322,7 +327,7 @@ task-slots = ["i2c_driver"] [tasks.ignition] name = "drv-ignition-server" features = ["sequencer"] -priority = 5 +priority = 6 max-sizes = {flash = 16384, ram = 8192} stacksize = 2048 start = true diff --git a/drv/front-io-api/src/lib.rs b/drv/front-io-api/src/lib.rs index f60713b71a..67c906f759 100644 --- a/drv/front-io-api/src/lib.rs +++ b/drv/front-io-api/src/lib.rs @@ -7,6 +7,8 @@ use counters::Count; use derive_idol_err::IdolError; use drv_fpga_api::FpgaError; +use hubpack::SerializedSize; +use serde::{Deserialize, Serialize}; use userlib::*; #[cfg(feature = "controller")] @@ -26,7 +28,8 @@ pub enum FrontIOError { NotPresent, InvalidPhysicalToLogicalMap, InvalidModuleResult, - SeqError, + PowerNotGood, + PowerFault, #[idol(server_death)] ServerRestarted, @@ -38,6 +41,30 @@ impl From for FrontIOError { } } +#[derive( + Copy, + Clone, + Debug, + Count, + Eq, + PartialEq, + Deserialize, + Serialize, + SerializedSize, +)] +pub enum FrontIOStatus { + /// Start state + Init, + /// No board detected + NotPresent, + /// Begin configuring the FPGAs + ReadyForFpgaInit, + /// Confirming that the PHY oscillator is behaving + WaitForOscGood, + /// Board is present and fully operational + Ready, +} + include!(concat!( env!("OUT_DIR"), "/sidecar_qsfp_x32_controller_regs.rs" diff --git a/drv/front-io-server/Cargo.toml b/drv/front-io-server/Cargo.toml index 1521b804f1..9bc61462a1 100644 --- a/drv/front-io-server/Cargo.toml +++ b/drv/front-io-server/Cargo.toml @@ -13,14 +13,20 @@ serde.workspace = true zerocopy.workspace = true zerocopy-derive.workspace = true +drv-fpga-api = { path = "../fpga-api" } drv-fpga-user-api = { path = "../fpga-user-api" } -drv-front-io-api = { path = "../front-io-api", features = ["phy_smi", "transceivers"] } -drv-sidecar-seq-api = { path = "../sidecar-seq-api" } +drv-front-io-api = { path = "../front-io-api", features = ["controller", "phy_smi", "transceivers"] } +drv-i2c-api = { path = "../i2c-api" } +drv-i2c-devices = { path = "../i2c-devices" } +drv-sidecar-mainboard-controller = { path = "../../drv/sidecar-mainboard-controller" } +drv-stm32xx-sys-api = { path = "../stm32xx-sys-api", features = ["family-stm32h7"] } +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, diff --git a/drv/front-io-server/build.rs b/drv/front-io-server/build.rs index 9b022fa80b..acd50e2c20 100644 --- a/drv/front-io-server/build.rs +++ b/drv/front-io-server/build.rs @@ -6,6 +6,8 @@ fn main() -> Result<(), Box> { build_util::expose_target_board(); build_util::build_notifications()?; + build_i2c::codegen(build_i2c::Disposition::Devices)?; + let board = build_util::env_var("HUBRIS_BOARD")?; if board != "sidecar-b" && board != "sidecar-c" diff --git a/drv/front-io-server/src/bsp.rs b/drv/front-io-server/src/bsp.rs new file mode 100644 index 0000000000..2c97e3a10e --- /dev/null +++ b/drv/front-io-server/src/bsp.rs @@ -0,0 +1,6 @@ +// 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/. + +// This file should never be included; if it is, the build configuration is bad +compile_error!("no BSP for the given target board"); diff --git a/drv/front-io-server/src/bsp/medusa_a.rs b/drv/front-io-server/src/bsp/medusa_a.rs new file mode 100644 index 0000000000..78f86fc8de --- /dev/null +++ b/drv/front-io-server/src/bsp/medusa_a.rs @@ -0,0 +1,60 @@ +// 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 drv_fpga_api::FpgaError; +use drv_fpga_user_api::power_rail::PowerRailStatus; +use drv_front_io_api::FrontIOError; +use drv_stm32xx_sys_api as sys_api; +use userlib::task_slot; + +task_slot!(SYS, sys); + +pub struct Bsp { + sys: sys_api::Sys, + en_pin: sys_api::PinSet, + pg_pin: sys_api::PinSet, +} + +impl Bsp { + pub fn new() -> Result { + let sys = sys_api::Sys::from(SYS.get_task_id()); + let en_pin = sys_api::Port::J.pin(2); // SP_TO_V12_QSFP_OUT_EN + let pg_pin = sys_api::Port::J.pin(1); // OUTPUT_HS_TO_SP_PG + + sys.gpio_configure_output( + en_pin, + sys_api::OutputType::PushPull, + sys_api::Speed::Low, + sys_api::Pull::None, + ); + + sys.gpio_configure_input(pg_pin, sys_api::Pull::None); + + Ok(Self { + sys, + en_pin, + pg_pin, + }) + } + + // PowerRailStatus is a FPGA concept baked into the Sidecar Mainboard design. Since we don't + // have an FPGA here, we simplify things to just being enabled or disabled + pub fn power_rail_status(&self) -> Result { + let status = if self.sys.gpio_read(self.en_pin) != 0 { + PowerRailStatus::Enabled + } else { + PowerRailStatus::Disabled + }; + Ok(status) + } + + pub fn power_good(&self) -> bool { + self.sys.gpio_read(self.pg_pin) != 0 + } + + pub fn set_power_enable(&self, enabled: bool) -> Result<(), FrontIOError> { + self.sys.gpio_set_to(self.en_pin, enabled); + Ok(()) + } +} diff --git a/drv/front-io-server/src/bsp/sidecar_bcd.rs b/drv/front-io-server/src/bsp/sidecar_bcd.rs new file mode 100644 index 0000000000..73d5587f47 --- /dev/null +++ b/drv/front-io-server/src/bsp/sidecar_bcd.rs @@ -0,0 +1,60 @@ +// 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 drv_fpga_api::{FpgaError, FpgaUserDesign}; +use drv_fpga_user_api::power_rail::{PowerRailStatus, RawPowerRailState}; +use drv_front_io_api::FrontIOError; +use drv_sidecar_mainboard_controller::{Addr, MainboardController, Reg}; +use drv_stm32xx_sys_api as sys_api; +use userlib::task_slot; + +task_slot!(SYS, sys); +task_slot!(MAINBOARD_FPGA, ecp5_mainboard); + +pub struct Bsp { + fpga: FpgaUserDesign, + sys: sys_api::Sys, + pg_pin: sys_api::PinSet, +} + +impl Bsp { + pub fn new() -> Result { + let sys = sys_api::Sys::from(SYS.get_task_id()); + let pg_pin = sys_api::Port::F.pin(12); // QSFP_2_SP_A2_PG + sys.gpio_configure_input(pg_pin, sys_api::Pull::None); + + Ok(Self { + fpga: FpgaUserDesign::new( + MAINBOARD_FPGA.get_task_id(), + MainboardController::DEVICE_INDEX, + ), + sys, + pg_pin, + }) + } + + #[inline] + fn raw_state(&self) -> Result { + self.fpga.read(Addr::FRONT_IO_STATE) + } + + #[inline] + pub fn power_rail_status(&self) -> Result { + PowerRailStatus::try_from(self.raw_state()?) + } + + pub fn power_good(&self) -> bool { + self.sys.gpio_read(self.pg_pin) != 0 + } + + pub fn set_power_enable(&self, enable: bool) -> Result<(), FrontIOError> { + self.fpga + .write( + enable.into(), + Addr::FRONT_IO_STATE, + Reg::FRONT_IO_STATE::ENABLE, + ) + .map_err(FrontIOError::from) + } +} diff --git a/drv/front-io-server/src/main.rs b/drv/front-io-server/src/main.rs index 4c43be2f33..fcd9f632d8 100644 --- a/drv/front-io-server/src/main.rs +++ b/drv/front-io-server/src/main.rs @@ -7,33 +7,236 @@ #![no_std] #![no_main] +#[cfg_attr( + any( + target_board = "sidecar-b", + target_board = "sidecar-c", + target_board = "sidecar-d" + ), + path = "bsp/sidecar_bcd.rs" +)] +#[cfg_attr(target_board = "medusa-a", path = "bsp/medusa_a.rs")] +mod bsp; + +use crate::bsp::Bsp; use core::convert::Infallible; -use drv_front_io_api::FrontIOError; -use drv_sidecar_seq_api::Sequencer; +use drv_fpga_api::{DeviceState, FpgaError}; +use drv_fpga_user_api::power_rail::PowerRailStatus; +use drv_front_io_api::{ + FrontIOError, FrontIOStatus, + controller::FrontIOController, + phy_smi::{PhyOscState, PhySmi}, +}; +use drv_i2c_devices::{Validate, at24csw080::At24Csw080}; +use enum_map::Enum; use idol_runtime::{NotificationHandler, RequestError}; +use multitimer::{Multitimer, Repeat}; use ringbuf::*; use userlib::*; -task_slot!(SEQUENCER, sequencer); +task_slot!(AUXFLASH, auxflash); +task_slot!(FRONT_IO_FPGA, ecp5_front_io); +task_slot!(I2C, i2c_driver); #[allow(dead_code)] #[derive(Copy, Clone, PartialEq)] enum Trace { None, + BspInit, + BspInitComplete, + BspInitFailed, + PowerEnabled(bool), + PowerGood, + PowerNotGood, + PowerFault, + FpgaInitError(FpgaError), + PhyPowerEnabled(bool), + PhyOscGood, + PhyOscBad, + SeqStatus(FrontIOStatus), + 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); +/// How often we should attempt the next sequencing step (in milliseconds) +const SEQ_INTERVAL: u64 = 100; + struct ServerImpl { - seq: Sequencer, + /// A BSP to help deliver core functionality whose implementation varies from board to board + bsp: Bsp, + + /// Handle for the auxflash task + auxflash_task: userlib::TaskId, + + /// Handles for each FPGA + controllers: [FrontIOController; 2], + + /// VSC8562 SMI Interface + phy_smi: PhySmi, + + /// Status of the Front IO board + board_status: FrontIOStatus, +} + +impl ServerImpl { + // We don't have a good way to tell if the board is present purely electrically, so instead we + // rely on our ability to talk to the board's FRUID as a proxy for presence + power good + fn is_board_present_and_powered(&self) -> bool { + let fruid = + i2c_config::devices::at24csw080_front_io(I2C.get_task_id())[0]; + At24Csw080::validate(&fruid).unwrap_or(false) + } + + // Make sure the front IO hot swap controller is enabled and good. + fn power_on_check(&self) -> Result<(), FrontIOError> { + // power rail FSM will reach either the GoodTimeout, Aborted or Enabled + // state or experience an FpgaError, so an open loop is safe. + while match self.bsp.power_rail_status()? { + PowerRailStatus::GoodTimeout | PowerRailStatus::Aborted => { + ringbuf_entry!(Trace::PowerFault); + return Err(FrontIOError::PowerFault); + } + PowerRailStatus::Disabled => { + ringbuf_entry!(Trace::PowerEnabled(true)); + self.bsp.set_power_enable(true)?; + true // Retry HSC status. + } + PowerRailStatus::RampingUp => { + true // Retry HSC status. + } + PowerRailStatus::Enabled => false, + } { + // The front IO HSC was observed to take as long as 35 ms to assert power good + userlib::hl::sleep_for(100); + } + + // Check if the power is good via the PG pin + if self.bsp.power_good() { + ringbuf_entry!(Trace::PowerGood); + } else { + ringbuf_entry!(Trace::PowerNotGood); + return Err(FrontIOError::PowerNotGood); + } + + Ok(()) + } + + // Encapsulates the logic of verifying checksums and loading FPGA images as necessary with the + // goal of only reloading FPGAs when there are new images in order to preserve state. + 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) + } + + // Helper function to query if both FPGAs are ready + fn fpga_ready(&self) -> bool { + self.controllers.iter().all(|c| c.ready().unwrap_or(false)) + } + + fn do_server_reset(&mut self) { + *self = ServerImpl::default(); + } } impl idl::InOrderFrontIOImpl for ServerImpl { + /// Combines turning the front IO board power on and checking that it is good + fn power_on( + &mut self, + _: &RecvMessage, + ) -> Result<(), RequestError> { + self.power_on_check().map_err(RequestError::from) + } + + /// Returns the current status of the front IO board + fn board_status( + &mut self, + _: &RecvMessage, + ) -> Result> { + Ok(self.board_status) + } + /// Returns true if a front IO board was determined to be present and powered on fn board_present( &mut self, _: &RecvMessage, ) -> Result> { - Ok(self.seq.front_io_board_present()) + Ok(self.is_board_present_and_powered()) } /// Returns if the front IO board has completely sequenced and is ready @@ -41,60 +244,280 @@ impl idl::InOrderFrontIOImpl for ServerImpl { &mut self, _: &RecvMessage, ) -> Result> { - self.seq - .front_io_board_ready() - .map_err(|_| FrontIOError::SeqError) - .map_err(RequestError::from) + Ok(self.board_status == FrontIOStatus::Ready) } fn phy_reset( &mut self, _: &RecvMessage, ) -> Result<(), RequestError> { - self.seq - .reset_front_io_phy() - .map_err(|_| FrontIOError::SeqError) - .map_err(RequestError::from) + // 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 + .phy_smi + .osc_state() + .map_err(FrontIOError::from) + .map_err(RequestError::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. + ringbuf_entry!(Trace::PowerEnabled(false)); + self.bsp.set_power_enable(false)?; + + // After removing power to the board we reset the server to + // clear internal state related to sequencing and status of + // parts on the board, forcing us to rebuild all that when + // powered on again. + self.do_server_reset(); + + // 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.phy_smi + .set_phy_power_enabled(false) + .map_err(FrontIOError::from) + .map_err(RequestError::from)?; + ringbuf_entry!(Trace::PhyPowerEnabled(false)); + + userlib::hl::sleep_for(10); + } + PhyOscState::Unknown => { + // Do nothing (yet) since the oscillator state is + // unknown. + } + } + + // Handle re-enabling power as needed + self.power_on_check().map_err(RequestError::from)?; + + if self.is_board_present_and_powered() { + // At this point the front IO board has either not yet been + // initalized or may have been power cycled and should be + // initialized. + while !self.fpga_ready() { + userlib::hl::sleep_for(20); + } + + // The PHY is still powered down. Request the sequencer to power up + // and wait for it to be ready. + self.phy_smi + .set_phy_power_enabled(true) + .map_err(FrontIOError::from) + .map_err(RequestError::from)?; + ringbuf_entry!(Trace::PhyPowerEnabled(true)); + + while !self + .phy_smi + .powered_up_and_ready() + .map_err(FrontIOError::from) + .map_err(RequestError::from)? + { + userlib::hl::sleep_for(20); + } + + Ok(()) + } else { + Err(RequestError::from(FrontIOError::NotPresent)) + } } - /// Set the internal state of the PHY's oscillator + /// Set the FPGA-internal state of the PHY's oscillator fn phy_set_osc_state( &mut self, _: &RecvMessage, good: bool, ) -> Result<(), RequestError> { - self.seq - .set_front_io_phy_osc_state(good) - .map_err(|_| FrontIOError::SeqError) - .map_err(RequestError::from) + 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 + }); + + // This uses the FPGA as external memory so the state will + // persist even if the task is restarted. + 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(()), + } } } -// notifications are not supported at this time +// notifications stubbed out to support multitimer firing impl NotificationHandler for ServerImpl { fn current_notification_mask(&self) -> u32 { - 0 + notifications::TIMER_MASK } - fn handle_notification(&mut self, _bits: NotificationBits) { - unreachable!() + fn handle_notification(&mut self, _bits: NotificationBits) {} +} + +impl Default for ServerImpl { + fn default() -> Self { + ringbuf_entry!(Trace::BspInit); + let bsp = match Bsp::new() { + Ok(bsp) => { + ringbuf_entry!(Trace::BspInitComplete); + bsp + } + Err(_) => { + ringbuf_entry!(Trace::BspInitFailed); + panic!(); + } + }; + + let fpga_task = FRONT_IO_FPGA.get_task_id(); + + ServerImpl { + bsp, + auxflash_task: AUXFLASH.get_task_id(), + controllers: [ + FrontIOController::new(fpga_task, 0), + FrontIOController::new(fpga_task, 1), + ], + phy_smi: PhySmi::new(fpga_task), + board_status: FrontIOStatus::Init, + } } } #[unsafe(export_name = "main")] fn main() -> ! { - let mut server = ServerImpl { - seq: Sequencer::from(SEQUENCER.get_task_id()), - }; + let mut server = ServerImpl::default(); + + // TODO: there will be more timers when I2C gets moved into this server + #[derive(Copy, Clone, Enum)] + enum Timers { + Seq, + } + let mut multitimer = Multitimer::::new(notifications::TIMER_BIT); + let now = sys_get_timer().now; + multitimer.set_timer( + Timers::Seq, + now, + Some(Repeat::AfterDeadline(SEQ_INTERVAL)), + ); let mut buffer = [0; idl::INCOMING_SIZE]; loop { + multitimer.poll_now(); + for t in multitimer.iter_fired() { + match t { + Timers::Seq => { + // 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::Init | FrontIOStatus::NotPresent => { + ringbuf_entry!(Trace::SeqStatus( + server.board_status + )); + + if server.is_board_present_and_powered() { + server.board_status = + FrontIOStatus::ReadyForFpgaInit; + } else { + server.board_status = FrontIOStatus::NotPresent; + } + } + + // Once we know there is a board present, configure its + // FPGAs and wait for its PHY oscillator to be functional. + FrontIOStatus::ReadyForFpgaInit => { + ringbuf_entry!(Trace::SeqStatus( + server.board_status + )); + match server.fpga_init() { + Ok(done) => { + if done && server.fpga_ready() { + server.board_status = + FrontIOStatus::WaitForOscGood; + } + } + 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::WaitForOscGood => { + ringbuf_entry!(Trace::SeqStatus( + server.board_status + )); + if server + .phy_smi + .osc_state() + .unwrap_or(PhyOscState::Unknown) + == PhyOscState::Good + { + server.board_status = FrontIOStatus::Ready; + ringbuf_entry!(Trace::SeqStatus( + server.board_status + )); + } + } + + // The board is operational, not further action needed + FrontIOStatus::Ready => (), + } + } + } + } + idol_runtime::dispatch(&mut buffer, &mut server); } } mod idl { - use super::FrontIOError; + use super::{FrontIOError, FrontIOStatus}; include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); } + +include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); +include!(concat!(env!("OUT_DIR"), "/notifications.rs")); diff --git a/drv/medusa-seq-server/src/front_io.rs b/drv/medusa-seq-server/src/front_io.rs deleted file mode 100644 index 18b83f4c93..0000000000 --- a/drv/medusa-seq-server/src/front_io.rs +++ /dev/null @@ -1,111 +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_fpga_api::{DeviceState, FpgaError}; -use drv_front_io_api::controller::FrontIOController; -use drv_front_io_api::phy_smi::PhySmi; -use drv_i2c_devices::{Validate, at24csw080::At24Csw080}; - -#[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_io0(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/medusa-seq-server/src/main.rs b/drv/medusa-seq-server/src/main.rs index d759f58aa8..ac1c230799 100644 --- a/drv/medusa-seq-server/src/main.rs +++ b/drv/medusa-seq-server/src/main.rs @@ -7,10 +7,9 @@ #![no_std] #![no_main] -use crate::front_io::FrontIOBoard; use crate::power_control::PowerControl; use core::convert::Infallible; -use drv_front_io_api::phy_smi::PhyOscState; +use drv_front_io_api::{FrontIO, FrontIOError}; use drv_medusa_seq_api::{MedusaError, RailName}; use idol_runtime::{NotificationHandler, RequestError}; use ringbuf::{ringbuf, ringbuf_entry}; @@ -20,40 +19,19 @@ task_slot!(I2C, i2c_driver); task_slot!(FRONT_IO, front_io); task_slot!(AUXFLASH, auxflash); task_slot!(PACKRAT, packrat); +task_slot!(SYS, sys); include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); -mod front_io; mod power_control; -#[allow(dead_code)] #[derive(Copy, Clone, PartialEq)] enum Trace { None, - FpgaBitstreamError(u32), FrontIOBoardNotPresent, FrontIOBoardPresent, - FrontIOBoardPowerEnable(bool), FrontIOBoardPowerGood, - FrontIOBoardPowerFault, - 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], - }, + FrontIOBoardPowerNotGood, PowerEnable(RailName, bool), PowerFault(RailName), MgmtPowerGood, @@ -64,103 +42,7 @@ ringbuf!(Trace, 32, Trace::None); struct ServerImpl { power_control: PowerControl, - front_io_board: Option, -} - -impl ServerImpl { - fn front_io_board_preinit(&self) -> Result { - // Enable the V12_QSFP_OUT rail - self.power_control.v12_qsfp_out.set_enable(true); - - // Wait a bit for it to ramp and then check that it is happy. - // The EN->PG time for this part was experimentally determined to be - // 35ms, so we roughly double that. - userlib::hl::sleep_for(75); - - // Power is not good. Disable the rail and log that this happened. - if !self.power_control.v12_qsfp_out.check_power_good() { - return Err(MedusaError::FrontIOBoardPowerFault); - } - - // Determine if a front IO board is present. - Ok(FrontIOBoard::present(I2C.get_task_id())) - } - - fn actually_reset_front_io_phy(&mut self) -> Result<(), MedusaError> { - if let Some(front_io_board) = self.front_io_board.as_mut() - && 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.power_control.v12_qsfp_out.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) - .map_err(MedusaError::from)?; - ringbuf_entry!(Trace::FrontIOBoardPhyPowerEnable(false)); - - userlib::hl::sleep_for(10); - } - PhyOscState::Unknown => { - // Do nothing (yet) since the oscillator state is - // unknown. - } - } - } - - // Run preinit to check HSC status. - self.front_io_board_preinit()?; - - let front_io_board = self - .front_io_board - .as_mut() - .ok_or(MedusaError::NoFrontIOBoard)?; - - // At this point the front IO board has either not yet been - // initialized or may have been power cycled and should be - // initialized. - if !front_io_board.initialized() { - front_io_board.init()?; - } - - // 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)?; - ringbuf_entry!(Trace::FrontIOBoardPhyPowerEnable(true)); - while !front_io_board.phy().powered_up_and_ready()? { - userlib::hl::sleep_for(20); - } - - Ok(()) - } + front_io_board: FrontIO, } impl idl::InOrderSequencerImpl for ServerImpl { @@ -213,64 +95,6 @@ impl idl::InOrderSequencerImpl for ServerImpl { rail.set_enable(enabled); Ok(()) } - - fn front_io_board_present( - &mut self, - _: &RecvMessage, - ) -> Result> { - Ok(self.front_io_board.is_some()) - } - - 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(MedusaError::NoFrontIOBoard)?; - - match front_io_board - .phy() - .osc_state() - .map_err(MedusaError::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(MedusaError::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 reset_front_io_phy( - &mut self, - _: &RecvMessage, - ) -> Result<(), RequestError> { - self.actually_reset_front_io_phy() - .map_err(RequestError::from) - } } impl NotificationHandler for ServerImpl { @@ -289,39 +113,25 @@ fn main() -> ! { let mut server = ServerImpl { power_control: PowerControl::new(), - front_io_board: None, + front_io_board: FrontIO::from(FRONT_IO.get_task_id()), }; // Enable the front IO hot swap controller and probe for a front IO board. - match server.front_io_board_preinit() { - Ok(true) => { - ringbuf_entry!(Trace::FrontIOBoardPresent); - ringbuf_entry!(Trace::FrontIOBoardPowerGood); - - let mut front_io_board = FrontIOBoard::new( - FRONT_IO.get_task_id(), - AUXFLASH.get_task_id(), - ); - - front_io_board.init().unwrap_lite(); - - // TODO: 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); - server.power_control.v12_qsfp_out.set_enable(false); + match server.front_io_board.power_on() { + Ok(_) => { + if server.front_io_board.board_present() { + ringbuf_entry!(Trace::FrontIOBoardPresent); + ringbuf_entry!(Trace::FrontIOBoardPowerGood); + // TODO: check/load VPD data into packrat. + } else { + ringbuf_entry!(Trace::FrontIOBoardNotPresent) + } } - Err(MedusaError::FrontIOBoardPowerFault) => { - ringbuf_entry!(Trace::FrontIOBoardPowerFault) + Err(FrontIOError::PowerFault | FrontIOError::PowerNotGood) => { + ringbuf_entry!(Trace::FrontIOBoardPowerNotGood) } - // `front_io_board_preinit` currently only returns a - // MedusaError::FrontIOBoardPowerFault - Err(_) => unreachable!(), + // Something went wrong getting the HSC status, eject. + Err(_) => panic!("unknown front IO board preinit failure"), } // The MGMT and PHY rails are enabled automatically by pullups, so we will diff --git a/drv/sidecar-mainboard-controller/src/front_io.rs b/drv/sidecar-mainboard-controller/src/front_io.rs deleted file mode 100644 index 40a4c16bba..0000000000 --- a/drv/sidecar-mainboard-controller/src/front_io.rs +++ /dev/null @@ -1,40 +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::{Addr, MainboardController, Reg}; -use drv_fpga_api::{FpgaError, FpgaUserDesign}; -pub use drv_fpga_user_api::power_rail::*; - -pub struct HotSwapController { - fpga: FpgaUserDesign, -} - -impl HotSwapController { - pub fn new(task_port: userlib::TaskId) -> Self { - Self { - fpga: FpgaUserDesign::new( - task_port, - MainboardController::DEVICE_INDEX, - ), - } - } - - #[inline] - fn raw_state(&self) -> Result { - self.fpga.read(Addr::FRONT_IO_STATE) - } - - #[inline] - pub fn status(&self) -> Result { - PowerRailStatus::try_from(self.raw_state()?) - } - - pub fn set_enable(&self, enable: bool) -> Result<(), FpgaError> { - self.fpga.write( - enable.into(), - Addr::FRONT_IO_STATE, - Reg::FRONT_IO_STATE::ENABLE, - ) - } -} diff --git a/drv/sidecar-mainboard-controller/src/lib.rs b/drv/sidecar-mainboard-controller/src/lib.rs index d58433e546..8c551f612b 100644 --- a/drv/sidecar-mainboard-controller/src/lib.rs +++ b/drv/sidecar-mainboard-controller/src/lib.rs @@ -9,7 +9,6 @@ use drv_fpga_api::*; include!(concat!(env!("OUT_DIR"), "/sidecar_mainboard_controller.rs")); pub mod fan_modules; -pub mod front_io; pub mod ignition; pub mod tofino2; diff --git a/drv/sidecar-seq-server/Cargo.toml b/drv/sidecar-seq-server/Cargo.toml index c227940a52..168597a31f 100644 --- a/drv/sidecar-seq-server/Cargo.toml +++ b/drv/sidecar-seq-server/Cargo.toml @@ -19,10 +19,9 @@ drv-fpga-user-api = { path = "../fpga-user-api" } drv-i2c-api = { path = "../i2c-api", features = ["component-id"] } drv-i2c-devices = { path = "../i2c-devices" } drv-packrat-vpd-loader = { path = "../packrat-vpd-loader" } -drv-front-io-api = { path = "../front-io-api", features = ["controller", "phy_smi"] } +drv-front-io-api = { path = "../front-io-api" } drv-sidecar-mainboard-controller = { path = "../sidecar-mainboard-controller", features = ["bitstream"] } drv-sidecar-seq-api = { path = "../sidecar-seq-api" } -drv-stm32xx-sys-api = { path = "../stm32xx-sys-api", features = ["family-stm32h7"] } ereports = { path = "../../lib/ereports", features = ["ereporter-macro"] } ringbuf = { path = "../../lib/ringbuf" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } 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 7d56bc591b..0000000000 --- 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_front_io_api::controller::FrontIOController; -use drv_front_io_api::phy_smi::PhySmi; -use drv_i2c_devices::{Validate, at24csw080::At24Csw080}; - -#[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 c6e5cb90be..c4b332778f 100644 --- a/drv/sidecar-seq-server/src/main.rs +++ b/drv/sidecar-seq-server/src/main.rs @@ -8,22 +8,20 @@ #![no_main] use crate::clock_generator::ClockGenerator; -use crate::front_io::FrontIOBoard; use crate::i2c_config::MAX_COMPONENT_ID_LEN as REFDES_LEN; use crate::tofino::Tofino; use core::convert::Infallible; use drv_fpga_api::{DeviceState, FpgaError, WriteOp}; -use drv_front_io_api::phy_smi::PhyOscState; +use drv_fpga_user_api::power_rail::{PowerRailStatus, RawPowerRailState}; +use drv_front_io_api::{FrontIO, FrontIOError, FrontIOStatus}; use drv_i2c_api::{I2cDevice, ResponseCode}; use drv_packrat_vpd_loader::{Packrat, read_vpd_and_load_packrat}; use drv_sidecar_mainboard_controller::MainboardController; use drv_sidecar_mainboard_controller::fan_modules::*; -use drv_sidecar_mainboard_controller::front_io::*; use drv_sidecar_mainboard_controller::tofino2::*; use drv_sidecar_seq_api::{ FanModuleIndex, FanModulePresence, SeqError, TofinoSequencerPolicy, }; -use drv_stm32xx_sys_api as sys_api; use fixedstr::FixedStr; use idol_runtime::{ ClientError, Leased, NotificationHandler, R, RequestError, W, @@ -36,12 +34,10 @@ task_slot!(MAINBOARD, mainboard); task_slot!(FRONT_IO, front_io); task_slot!(AUXFLASH, auxflash); task_slot!(PACKRAT, packrat); -task_slot!(SYS, sys); include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); mod clock_generator; -mod front_io; mod tofino; #[allow(dead_code)] @@ -88,30 +84,9 @@ enum Trace { SetPCIePresent, ClearPCIePresent, ClearingTofinoSequencerFault(TofinoSeqError), - FrontIOBoardPowerEnable(bool), - FrontIOBoardPowerFault, - FrontIOBoardPowerNotGood, - FrontIOBoardPowerGood, + FrontIOBoardPowerProblem, FrontIOBoardPresent, FrontIOBoardNotPresent, - 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), @@ -122,9 +97,6 @@ ringbuf!(Trace, 32, Trace::None); const TIMER_INTERVAL: u64 = 1000; const NO_PCIE_LIMIT: u8 = 120; -// QSFP_2_SP_A2_PG -const POWER_GOOD: sys_api::PinSet = sys_api::Port::F.pin(12); - #[derive(Copy, Clone, PartialEq)] enum TofinoStateDetails { A0 { @@ -144,12 +116,10 @@ struct ServerImpl { mainboard_controller: MainboardController, 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, - sys: sys_api::Sys, // used to track how many notification loops elapsed while in A0 without a // PCIe link. This will be capped at NO_PCIE_LIMIT loops and should not // overflow. @@ -228,138 +198,17 @@ impl ServerImpl { } } - fn front_io_board_preinit(&self) -> Result { - // Make sure the front IO hot swap controller is enabled and good. The - // power rail FSM will reach either the GoodTimeout, Aborted or Enabled - // state or experience an FpgaError, so an open loop is safe. - while match self.front_io_hsc.status()? { - PowerRailStatus::GoodTimeout | PowerRailStatus::Aborted => { - return Err(SeqError::FrontIOBoardPowerFault); - } - PowerRailStatus::Disabled => { - self.front_io_hsc.set_enable(true)?; - ringbuf_entry!(Trace::FrontIOBoardPowerEnable(true)); - - true // Retry HSC status. - } - PowerRailStatus::RampingUp => { - true // Retry HSC status. - } - PowerRailStatus::Enabled => false, - } { - userlib::hl::sleep_for(25); - } - - // Check if the power is good via the PG pin - if self.sys.gpio_read(POWER_GOOD) == 0 { - ringbuf_entry!(Trace::FrontIOBoardPowerNotGood); - return Err(SeqError::FrontIOPowerNotGood); - } else { - ringbuf_entry!(Trace::FrontIOBoardPowerGood); - } - - // 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) - } - } - - fn actually_reset_front_io_phy(&mut self) -> Result<(), SeqError> { - if let Some(front_io_board) = self.front_io_board.as_mut() - && 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. - } - } - } - - // Run preinit to check HSC status. - self.front_io_board_preinit()?; - - if let Some(front_io_board) = self.front_io_board.as_mut() { - // 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()?; - } - - // 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)?; - ringbuf_entry!(Trace::FrontIOBoardPhyPowerEnable(true)); - - while !front_io_board.phy().powered_up_and_ready()? { - userlib::hl::sleep_for(20); - } - - Ok(()) - } else { - Err(SeqError::NoFrontIOBoard) - } - } - // Determine if Tofino can be powered up. Some front IO boards were // assembled using oscillators which require the board to be power cycled // before they operate nominally. To avoid doing this and interrupting QSFP // 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 + ) } // Monitor the status of Tofino's PCIe link to the host. When a host is @@ -549,71 +398,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, - ) -> Result<(), RequestError> { - self.actually_reset_front_io_phy() - .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, @@ -827,8 +611,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() { @@ -890,7 +673,6 @@ fn main() -> ! { MainboardController::new(MAINBOARD.get_task_id()); let clock_generator = ClockGenerator::new(i2c_task); 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 packrat = Packrat::from(PACKRAT.get_task_id()); @@ -928,18 +710,13 @@ fn main() -> ! { } } - let sys = sys_api::Sys::from(SYS.get_task_id()); - sys.gpio_configure_input(POWER_GOOD, sys_api::Pull::None); - let mut server = ServerImpl { mainboard_controller, clock_generator, tofino, - front_io_hsc, - front_io_board: None, + front_io_board: FrontIO::from(FRONT_IO.get_task_id()), fan_modules, led_blink_on: false, - sys, no_pcie_count: 0, resequenced: false, }; @@ -1072,27 +849,17 @@ fn main() -> ! { ringbuf_entry!(Trace::ClockConfigurationComplete); // Enable the front IO hot swap controller and probe for a front IO board. - 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); + match server.front_io_board.power_on() { + Ok(_) => { + if server.front_io_board.board_present() { + ringbuf_entry!(Trace::FrontIOBoardPresent); + // TODO: check/load VPD data into packrat. + } else { + ringbuf_entry!(Trace::FrontIOBoardNotPresent) + } } - Ok(false) => ringbuf_entry!(Trace::FrontIOBoardNotPresent), - Err(SeqError::FrontIOBoardPowerFault) => { - ringbuf_entry!(Trace::FrontIOBoardPowerFault) + Err(FrontIOError::PowerFault | FrontIOError::PowerNotGood) => { + ringbuf_entry!(Trace::FrontIOBoardPowerProblem) } // Something went wrong getting the HSC status, eject. Err(_) => panic!("unknown front IO board preinit failure"), diff --git a/idl/front-io.idol b/idl/front-io.idol index 39a108217d..f60b742b3a 100644 --- a/idl/front-io.idol +++ b/idl/front-io.idol @@ -3,6 +3,21 @@ Interface( name: "FrontIO", ops: { + "power_on": ( + args: {}, + reply: Result( + ok: "()", + err: CLike("FrontIOError"), + ), + ), + + "board_status": ( + args: {}, + reply: Simple("FrontIOStatus"), + idempotent: true, + encoding: Hubpack, + ), + "board_present": ( args: {}, reply: Simple("bool"), diff --git a/idl/medusa-seq.idol b/idl/medusa-seq.idol index c75b858e11..97db61361f 100644 --- a/idl/medusa-seq.idol +++ b/idl/medusa-seq.idol @@ -3,30 +3,6 @@ Interface( name: "Sequencer", ops: { - "front_io_board_present": ( - args: {}, - reply: Simple("bool"), - idempotent: true, - ), - - "set_front_io_phy_osc_state": ( - args: { - "good": "bool", - }, - reply: Result( - ok: "()", - err: CLike("MedusaError"), - ), - ), - - "reset_front_io_phy": ( - args: {}, - reply: Result( - ok: "()", - err: CLike("MedusaError"), - ), - ), - "control_mgmt_rails": ( args: { "enabled": "bool", diff --git a/idl/sidecar-seq.idol b/idl/sidecar-seq.idol index 5fedf98c9e..8802fd9af0 100644 --- a/idl/sidecar-seq.idol +++ b/idl/sidecar-seq.idol @@ -151,35 +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( - ok: "()", - 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", args: {}, diff --git a/task/monorail-server/src/bsp/medusa_a.rs b/task/monorail-server/src/bsp/medusa_a.rs index 9fbe555050..d584d6ae4f 100644 --- a/task/monorail-server/src/bsp/medusa_a.rs +++ b/task/monorail-server/src/bsp/medusa_a.rs @@ -2,8 +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_front_io_api::phy_smi::PhySmi; -use drv_medusa_seq_api::Sequencer; +use drv_front_io_api::{FrontIO, phy_smi::PhySmi}; use drv_monorail_api::MonorailError; use idol_runtime::{ClientError, RequestError}; use ringbuf::*; @@ -14,8 +13,7 @@ use vsc7448::{ }; use vsc7448_pac::{DEVCPU_GCB, HSIO, VAUI0, VAUI1}; -task_slot!(SEQ, seq); -task_slot!(FRONT_IO, ecp5_front_io); +task_slot!(FRONT_IO, front_io); /// Interval in milliseconds at which `Bsp::wake()` is called by the main loop pub const WAKE_INTERVAL: Option = Some(500); @@ -39,8 +37,8 @@ ringbuf!(Trace, 16, Trace::None); pub struct Bsp<'a, R> { vsc7448: &'a Vsc7448<'a, R>, - /// Handle for the sequencer task - seq: Sequencer, + /// Handle for the front-io task + front_io: FrontIO, /// PHY for the on-board PHY ("PHY4") vsc8504: Vsc8504, @@ -146,8 +144,8 @@ 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(), @@ -158,7 +156,7 @@ impl<'a, R: Vsc7448Rw> Bsp<'a, R> { }, front_io_speed: [Speed::Speed1G; 2], link_down_at: None, - seq, + front_io, }; out.reinit()?; @@ -225,11 +223,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 server 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 { @@ -383,8 +381,8 @@ impl<'a, R: Vsc7448Rw> Bsp<'a, R> { // Request a reset of the PHY. If we had previously marked the PHY // oscillator as bad, then this power-cycles the entire front IO // board; otherwise, it only power-cycles the PHY. - self.seq - .reset_front_io_phy() + self.front_io + .phy_reset() .map_err(|e| VscError::ProxyError(e.into()))?; for p in 0..2 {