diff --git a/Cargo.lock b/Cargo.lock index d7c1111e80..fd38198247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1405,6 +1405,49 @@ dependencies = [ "zerocopy 0.6.6", ] +[[package]] +name = "drv-medusa-seq-api" +version = "0.1.0" +dependencies = [ + "counters", + "derive-idol-err", + "drv-fpga-api", + "hubpack", + "idol", + "idol-runtime", + "num-traits", + "serde", + "userlib", + "zerocopy 0.6.6", +] + +[[package]] +name = "drv-medusa-seq-server" +version = "0.1.0" +dependencies = [ + "build-i2c", + "build-util", + "cfg-if", + "cortex-m", + "drv-fpga-api", + "drv-fpga-user-api", + "drv-i2c-api", + "drv-i2c-devices", + "drv-medusa-seq-api", + "drv-packrat-vpd-loader", + "drv-sidecar-front-io", + "drv-sidecar-mainboard-controller", + "drv-stm32xx-sys-api", + "hubpack", + "idol", + "idol-runtime", + "num-traits", + "ringbuf", + "serde", + "userlib", + "zerocopy 0.6.6", +] + [[package]] name = "drv-mock-gimlet-hf-server" version = "0.1.0" @@ -3075,6 +3118,19 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" +[[package]] +name = "medusa" +version = "0.1.0" +dependencies = [ + "build-util", + "cfg-if", + "cortex-m", + "cortex-m-rt", + "drv-stm32h7-startup", + "kern", + "stm32h7", +] + [[package]] name = "memchr" version = "2.4.1" @@ -4566,6 +4622,7 @@ dependencies = [ "build-util", "cfg-if", "counters", + "drv-medusa-seq-api", "drv-monorail-api", "drv-sidecar-front-io", "drv-sidecar-mainboard-controller", @@ -4599,6 +4656,7 @@ dependencies = [ "cortex-m", "counters", "drv-gimlet-seq-api", + "drv-medusa-seq-api", "drv-psc-seq-api", "drv-sidecar-seq-api", "drv-spi-api", diff --git a/app/medusa/Cargo.toml b/app/medusa/Cargo.toml new file mode 100644 index 0000000000..2d680780b8 --- /dev/null +++ b/app/medusa/Cargo.toml @@ -0,0 +1,27 @@ +[package] +edition = "2021" +readme = "README.md" +name = "medusa" +version = "0.1.0" + +[features] +dump = ["kern/dump"] + +[dependencies] +cortex-m = { workspace = true } +cortex-m-rt = { workspace = true } +cfg-if = { workspace = true } +stm32h7 = { workspace = true, features = ["rt", "stm32h753"] } + +drv-stm32h7-startup = { path = "../../drv/stm32h7-startup", features = ["h753"] } +kern = { path = "../../sys/kern" } + +[build-dependencies] +build-util = {path = "../../build/util"} + +# this lets you use `cargo fix`! +[[bin]] +name = "medusa" +test = false +doctest = false +bench = false diff --git a/app/medusa/README.md b/app/medusa/README.md new file mode 100644 index 0000000000..26f29c256f --- /dev/null +++ b/app/medusa/README.md @@ -0,0 +1,5 @@ +# Medusa Service Processor (SP) firmware + +The Medusa is a test fixture for the QSFP Front IO board. + +This folder contains the firmware that runs on its service processor (SP). diff --git a/app/medusa/base.toml b/app/medusa/base.toml new file mode 100644 index 0000000000..a5a83cc127 --- /dev/null +++ b/app/medusa/base.toml @@ -0,0 +1,676 @@ +target = "thumbv7em-none-eabihf" +chip = "../../chips/stm32h7" +memory = "memory-large.toml" +stacksize = 896 +fwid = true + +[kernel] +name = "medusa" +requires = {flash = 24600, ram = 6256} + +[tasks.jefe] +name = "task-jefe" +priority = 0 +max-sizes = {flash = 16384, ram = 2048} +start = true +stacksize = 1536 +notifications = ["fault", "timer"] +extern-regions = ["sram2", "sram3", "sram4"] + +[tasks.jefe.config.allowed-callers] +set_reset_reason = ["sys"] +request_reset = ["hiffy"] + +[tasks.sys] +name = "drv-stm32xx-sys" +features = ["h753"] +priority = 1 +uses = ["rcc", "gpios", "system_flash"] +start = true +task-slots = ["jefe"] + +[tasks.update_server] +name = "stm32h7-update-server" +priority = 3 +max-sizes = {flash = 16384, ram = 4096} +stacksize = 2048 +start = true +uses = ["flash_controller"] +extern-regions = ["bank2"] +notifications = ["flash-irq"] +interrupts = {"flash_controller.irq" = "flash-irq"} + +[caboose] +region = "flash" +size = 256 +default = true + +[tasks.caboose_reader] +name = "task-caboose-reader" +priority = 2 +max-sizes = {flash = 16384, ram = 2048} +start = true + +[tasks.hiffy] +name = "task-hiffy" +priority = 7 +max-sizes = {flash = 32768, ram = 32768} +stacksize = 2048 +start = true +task-slots = ["sys", "i2c_driver"] + +[tasks.i2c_driver] +name = "drv-stm32xx-i2c-server" +features = ["h753"] +priority = 2 +max-sizes = {flash = 16384, ram = 4096} +uses = ["i2c1", "i2c2", "i2c3", "i2c4"] +notifications = ["i2c1-irq", "i2c2-irq", "i2c3-irq", "i2c4-irq"] +start = true +task-slots = ["sys"] + +[tasks.i2c_driver.interrupts] +"i2c1.event" = "i2c1-irq" +"i2c1.error" = "i2c1-irq" +"i2c2.event" = "i2c2-irq" +"i2c2.error" = "i2c2-irq" +"i2c3.event" = "i2c3-irq" +"i2c3.error" = "i2c3-irq" +"i2c4.event" = "i2c4-irq" +"i2c4.error" = "i2c4-irq" + +[tasks.ecp5_front_io] +name = "drv-fpga-server" +features = ["front_io", "use-spi-core", "h753", "spi1"] +priority = 3 +max-sizes = {flash = 32768, ram = 8192} +stacksize = 2048 +start = true +uses = ["spi1"] +task-slots = ["sys", "i2c_driver"] +notifications = ["spi-irq"] +interrupts = {"spi1.irq" = "spi-irq"} + +[tasks.monorail] +name = "task-monorail-server" +priority = 6 +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" }] +uses = ["spi2"] +notifications = ["spi-irq", "wake-timer"] +interrupts = {"spi2.irq" = "spi-irq"} + +[tasks.net] +name = "task-net" +stacksize = 6040 +priority = 5 +features = ["mgmt", "h753", "medusa", "vlan", "vpd-mac", "use-spi-core", "spi3"] +max-sizes = {flash = 131072, ram = 65536, sram1 = 16384} +sections = {eth_bulk = "sram1"} +uses = ["eth", "tim16", "spi3"] +start = true +notifications = ["eth-irq", "mdio-timer-irq", "spi-irq", "wake-timer"] +task-slots = ["sys", "packrat", { seq = "sequencer" }, "jefe"] + +[tasks.net.interrupts] +"eth.irq" = "eth-irq" +"tim16.irq" = "mdio-timer-irq" +"spi3.irq" = "spi-irq" + +[tasks.sequencer] +name = "drv-medusa-seq-server" +priority = 4 +stacksize = 4096 +start = true +task-slots = [ + "sys", + "i2c_driver", + "auxflash", + "packrat", + {front_io = "ecp5_front_io"}] +notifications = ["timer"] + +[tasks.transceivers] +name = "drv-transceivers-server" +features = ["vlan"] +priority = 6 +max-sizes = {flash = 65536, ram = 16384} +stacksize = 4096 +start = true +task-slots = [ + "i2c_driver", + "net", + "sensor", + "sys", + {front_io = "ecp5_front_io"}, + {seq = "sequencer"}] +notifications = ["socket", "timer"] + +[tasks.packrat] +name = "task-packrat" +priority = 3 +max-sizes = {flash = 8192, ram = 2048} +start = true +# task-slots is explicitly empty: packrat should not send IPCs! +task-slots = [] + +[tasks.validate] +name = "task-validate" +priority = 5 +max-sizes = {flash = 16384, ram = 4096 } +stacksize = 1000 +start = true +task-slots = ["i2c_driver"] + +[tasks.sensor] +name = "task-sensor" +features = [] +priority = 4 +stacksize = 1024 +start = true +notifications = ["timer"] + +[tasks.vpd] +name = "task-vpd" +priority = 3 +max-sizes = {flash = 8192, ram = 1024} +start = true +task-slots = ["sys", "i2c_driver"] +stacksize = 800 + +[tasks.idle] +name = "task-idle" +priority = 9 +max-sizes = {flash = 128, ram = 256} +stacksize = 256 +start = true + +[tasks.auxflash] +name = "drv-auxflash-server" +priority = 3 +max-sizes = {flash = 32768, ram = 4096} +features = ["h753"] +uses = ["quadspi"] +start = true +notifications = ["qspi-irq"] +interrupts = {"quadspi.irq" = "qspi-irq"} +stacksize = 3504 +task-slots = ["sys"] + +[tasks.udpecho] +name = "task-udpecho" +priority = 6 +max-sizes = {flash = 16384, ram = 8192} +stacksize = 4096 +start = true +task-slots = ["net"] +features = ["vlan"] +notifications = ["socket"] + +[tasks.udpbroadcast] +name = "task-udpbroadcast" +priority = 6 +max-sizes = {flash = 16384, ram = 8192} +stacksize = 4096 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + +# +# Configuration +# +[config] + +# +# Net +# +[config.net] +vlan = { start = 0x301, count = 2 } + +[config.net.sockets.echo] +kind = "udp" +owner = {name = "udpecho", notification = "socket"} +port = 7 +tx = { packets = 3, bytes = 1024 } +rx = { packets = 3, bytes = 1024 } + +[config.net.sockets.broadcast] +kind = "udp" +owner = {name = "udpbroadcast", notification = "socket"} +port = 997 +tx = { packets = 3, bytes = 1024 } +rx = { packets = 3, bytes = 1024 } + +[config.net.sockets.transceivers] +kind = "udp" +owner = {name = "transceivers", notification = "socket"} +port = 11112 +tx = { packets = 3, bytes = 2048 } +rx = { packets = 3, bytes = 2048 } + +# +# Auxflash +# +[config.auxflash] +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" +compress = true +tag = "QSFP" + +# +# I2C1: Primary Bus +# +[[config.i2c.controllers]] +controller = 1 + +[config.i2c.controllers.ports.B1] +name = "primary" +description = "Primary I2C bus to all on-board devices" +scl = { gpio_port = "B", pin = 6 } # I2C_SP_TO_ALL_SCL +sda = { gpio_port = "B", pin = 7 } # I2C_SP_TO_ALL_SDA +af = 4 + +[[config.i2c.devices]] +bus = "primary" +address = 0b1010_100 +device = "ltc4282" +description = "Front I/O hotswap controller" +power = { rails = [ "V12_SYS" ], pmbus = false } +sensors = { voltage = 1, current = 1 } +refdes = "U1" + +[[config.i2c.devices]] +bus = "primary" +address = 0b1010_000 +device = "at24csw080" +description = "Medusa FRUID" +name = "local_vpd" +refdes = "U9" + +[[config.i2c.devices]] +bus = "primary" +address = 0b0011_001 +device = "tps546b24a" +description = "V5P0_SYS rail" +power = { rails = [ "V5P0_SYS" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U10" + +[[config.i2c.devices]] +bus = "primary" +address = 0b0011_010 +device = "tps546b24a" +description = "V3P3_SYS rail" +power = { rails = [ "V3P3_SYS" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U11" + +[[config.i2c.devices]] +bus = "primary" +address = 0b0011_100 +device = "tps546b24a" +description = "V1P8_SYS rail" +power = { rails = [ "V1P8_SYS" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U12" + +[[config.i2c.devices]] +bus = "primary" +address = 0b0011_011 +device = "tps546b24a" +description = "V1P0_MGMT rail" +power = { rails = [ "V1P0_MGMT" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U13" + +[[config.i2c.devices]] +bus = "primary" +address = 0b1000_000 +device = "ltc4282" +description = "Front I/O hotswap controller" +power = { rails = [ "V12_QSFP_OUT" ], pmbus = false } +sensors = { voltage = 1, current = 1 } +refdes = "U16" + +# +# I2C2: Front I/O 0 +# +[[config.i2c.controllers]] +controller = 2 +[config.i2c.controllers.ports.F] +name = "front_io0" +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" +address = 0b1010_000 +device = "at24csw080" +description = "Front IO board FRUID" +removable = true + +[[config.i2c.devices]] +bus = "front_io0" +address = 0b1110_011 +device = "pca9538" +description = "Front IO GPIO expander" +removable = true + +[[config.i2c.devices]] +bus = "front_io0" +address = 0b0001_010 +device = "pca9956b" +name = "front_leds_left" +description = "Front IO LED driver (left)" +removable = true +refdes = "U5" + +[[config.i2c.devices]] +bus = "front_io0" +address = 0b0001_011 +device = "pca9956b" +name = "front_leds_right" +description = "Front IO LED driver (right)" +removable = true +refdes = "U6" + +[[config.i2c.devices]] +bus = "front_io0" +address = 0b0011_011 +device = "tps546b24a" +description = "Front IO V3P3_SYS_A2 rail" +removable = true +power = {rails = ["V3P3_SYS_A2"] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "J61_U7" # on front IO board + +[[config.i2c.devices]] +bus = "front_io0" +address = 0b0011_001 +device = "tps546b24a" +description = "Front IO V3P3_QSFP0_A0 rail" +removable = true +power = {rails = ["V3P3_QSFP0_A0"] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "J61_U15" # on front IO board + +[[config.i2c.devices]] +bus = "front_io0" +address = 0b0011_010 +device = "tps546b24a" +description = "Front IO V3P3_QSFP1_A0 rail" +removable = true +power = {rails = ["V3P3_QSFP1_A0"] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "J61_U18" # on front IO board + +# +# I2C3: Front I/O 1 +# +[[config.i2c.controllers]] +controller = 3 +[config.i2c.controllers.ports.H] +name = "front_io1" +description = "Front I/O Board" +scl.pin = 7 # I2C_FRONT_IO1_SCL +sda.pin = 8 # I2C_FRONT_IO1_SDA +af = 4 + +# +# SPI1: Front IO FPGAs +# +[config.spi.spi1] +controller = 1 + +[config.spi.spi1.mux_options.port_adg] +outputs = [ + {port = "A", pins = [5], af = 5}, # SPI_SP_TO_FRONT_IO_SCK + {port = "D", pins = [7], af = 5}, # SPI_SP_TO_FRONT_IO_MOSI +] +input = {port = "G", pin = 9, af = 5} # SPI_SP_TO_FRONT_IO_MISO + +[config.spi.spi1.devices.ecp5_front_io_fpga] +mux = "port_adg" +cs = [{port = "G", pin = 10}] # SPI_SP_TO_FRONT_IO_CS_CFG_L + +[config.spi.spi1.devices.ecp5_front_io_user_design] +mux = "port_adg" +cs = [{port = "A", pin = 15}] # SPI_SP_TO_FRONT_IO_CS_USER_L + +# +# SPI2: VSC7448 +# +[config.spi.spi2] +controller = 2 + +[config.spi.spi2.mux_options.port_i] +outputs = [ + # SPI_SP_TO_MGMT_SCK, SPI_SP_TO_MGMT_MOSI + {port = "I", pins = [1, 3], af = 5}, +] +input = {port = "I", pin = 2, af = 5} # SPI_SP_TO_MGMT_MISO + +[config.spi.spi2.devices.vsc7448] +mux = "port_i" +cs = [{port = "I", pin = 0}] # SPI_SP_TO_MGMT_CS_L + +# +# SPI3: KSZ8463 +# +[config.spi.spi3] +controller = 3 + +[config.spi.spi3.mux_options.port_c] +outputs = [ + # SPI_SP_TO_EPE_SCK, SPI_SP_TO_EPE_MOSI + {port = "C", pins = [10, 12], af = 6}, +] +input = {port = "C", pin = 11, af = 6} # SPI_SP_TO_EPE_MISO + +[config.spi.spi3.devices.ksz8463] +mux = "port_c" +cs = [{port = "A", pin = 4}] # SPI_SP_TO_EPE_CS_L + +# +# QSFP Module Temperature Sensors +# +[[config.sensor.devices]] +name = "xcvr0" +device = "qsfp" +description = "QSFP transceiver 0" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr1" +device = "qsfp" +description = "QSFP transceiver 1" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr2" +device = "qsfp" +description = "QSFP transceiver 2" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr3" +device = "qsfp" +description = "QSFP transceiver 3" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr4" +device = "qsfp" +description = "QSFP transceiver 4" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr5" +device = "qsfp" +description = "QSFP transceiver 5" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr6" +device = "qsfp" +description = "QSFP transceiver 6" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr7" +device = "qsfp" +description = "QSFP transceiver 7" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr8" +device = "qsfp" +description = "QSFP transceiver 8" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr9" +device = "qsfp" +description = "QSFP transceiver 9" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr10" +device = "qsfp" +description = "QSFP transceiver 10" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr11" +device = "qsfp" +description = "QSFP transceiver 11" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr12" +device = "qsfp" +description = "QSFP transceiver 12" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr13" +device = "qsfp" +description = "QSFP transceiver 13" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr14" +device = "qsfp" +description = "QSFP transceiver 14" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr15" +device = "qsfp" +description = "QSFP transceiver 15" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr16" +device = "qsfp" +description = "QSFP transceiver 16" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr17" +device = "qsfp" +description = "QSFP transceiver 17" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr18" +device = "qsfp" +description = "QSFP transceiver 18" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr19" +device = "qsfp" +description = "QSFP transceiver 19" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr20" +device = "qsfp" +description = "QSFP transceiver 20" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr21" +device = "qsfp" +description = "QSFP transceiver 21" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr22" +device = "qsfp" +description = "QSFP transceiver 22" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr23" +device = "qsfp" +description = "QSFP transceiver 23" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr24" +device = "qsfp" +description = "QSFP transceiver 24" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr25" +device = "qsfp" +description = "QSFP transceiver 25" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr26" +device = "qsfp" +description = "QSFP transceiver 26" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr27" +device = "qsfp" +description = "QSFP transceiver 27" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr28" +device = "qsfp" +description = "QSFP transceiver 28" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr29" +device = "qsfp" +description = "QSFP transceiver 29" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr30" +device = "qsfp" +description = "QSFP transceiver 30" +sensors.temperature = 1 + +[[config.sensor.devices]] +name = "xcvr31" +device = "qsfp" +description = "QSFP transceiver 31" +sensors.temperature = 1 diff --git a/app/medusa/build.rs b/app/medusa/build.rs new file mode 100644 index 0000000000..2800895934 --- /dev/null +++ b/app/medusa/build.rs @@ -0,0 +1,7 @@ +// 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() { + build_util::expose_target_board(); +} diff --git a/app/medusa/model-a.toml b/app/medusa/model-a.toml new file mode 100644 index 0000000000..0531527cbd --- /dev/null +++ b/app/medusa/model-a.toml @@ -0,0 +1,3 @@ +name = "medusa-a" +board = "medusa-a" +inherit = "base.toml" diff --git a/app/medusa/src/main.rs b/app/medusa/src/main.rs new file mode 100644 index 0000000000..ca6260f187 --- /dev/null +++ b/app/medusa/src/main.rs @@ -0,0 +1,136 @@ +// 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] +#![no_main] + +// We have to do this if we don't otherwise use it to ensure its vector table +// gets linked in. +extern crate stm32h7; + +use stm32h7::stm32h753 as device; + +use drv_stm32h7_startup::ClockConfig; + +use cortex_m_rt::entry; + +#[entry] +fn main() -> ! { + system_init(); + + const CYCLES_PER_MS: u32 = 400_000; + + #[cfg(feature = "traptrace")] + kern::profiling::configure_events_table(tracing::table()); + + unsafe { kern::startup::start_kernel(CYCLES_PER_MS) } +} + +fn system_init() { + let cp = cortex_m::Peripherals::take().unwrap(); + let p = device::Peripherals::take().unwrap(); + + // Check the package we've been flashed on. Medusa boards use BGA240. + // Gimletlet boards are very similar but use QFPs. This is designed to fail + // a Medusa firmware that was accidentally flashed onto a Gimletlet. + // + // We need to turn the SYSCFG block on to do this. + p.RCC.apb4enr.modify(|_, w| w.syscfgen().enabled()); + cortex_m::asm::dsb(); + // Now, we can read the appropriately-named package register to find out + // what package we're on. + match p.SYSCFG.pkgr.read().pkg().bits() { + 0b1000 => { + // TFBGA240, yay + } + _ => { + // uh + panic!(); + } + } + + // We read the board model very early in boot to try and detect the + // firmware being flashed on the wrong board. In particular, we read the + // model _before_ setting up the clock tree below, just in case we change + // the crystal configuration in a subsequent rev. + // + // Note that the firmware _does not_ adapt to different board models. We + // still require different firmware per model; this check serves to detect + // if you've flashed the wrong one, only. + // + // The model is on the following pins: + // - ID0: PC6 + // - ID1: PC7 + // - ID2: PC13 + // Un-gate the clock to GPIO bank C. + p.RCC.ahb4enr.modify(|_, w| w.gpiogen().set_bit()); + cortex_m::asm::dsb(); + // PE{13,7,6} are already inputs after reset + #[rustfmt::skip] + p.GPIOC.moder.modify(|_, w| w + .moder6().input() + .moder7().input() + .moder13().input()); + + // Unlike other designs, we aren't using any internal pullup resistors, so + // we won't wait for the inputs or ID traces to charge. + + let id_mask = (1 << 6) | (1 << 7) | (1 << 13); + let model = p.GPIOC.idr.read().bits() & id_mask; + + cfg_if::cfg_if! { + if #[cfg(target_board = "medusa-a")] { + let expected_model = 0b000; + } else { + compile_error!("not a recognized medusa board"); + } + } + + assert_eq!(model, expected_model); + + drv_stm32h7_startup::system_init_custom( + cp, + p, + ClockConfig { + source: drv_stm32h7_startup::ClockSource::ExternalCrystal, + // 8MHz HSE freq is within VCO input range of 2-16, so, DIVM=1 to bypass + // the prescaler. + divm: 1, + // VCO must tolerate an 8MHz input range: + vcosel: device::rcc::pllcfgr::PLL1VCOSEL_A::WIDEVCO, + pllrange: device::rcc::pllcfgr::PLL1RGE_A::RANGE8, + // DIVN governs the multiplication of the VCO input frequency to produce + // the intermediate frequency. We want an IF of 800MHz, or a + // multiplication of 100x. + // + // We subtract 1 to get the DIVN value because the PLL effectively adds + // one to what we write. + divn: 100 - 1, + // P is the divisor from the VCO IF to the system frequency. We want + // 400MHz, so: + divp: device::rcc::pll1divr::DIVP1_A::DIV2, + // Q produces kernel clocks; we set it to 200MHz: + divq: 4 - 1, + // R is mostly used by the trace unit and we leave it fast: + divr: 2 - 1, + + // We run the CPU at the full core rate of 400MHz: + cpu_div: device::rcc::d1cfgr::D1CPRE_A::DIV1, + // We down-shift the AHB by a factor of 2, to 200MHz, to meet its + // constraints: + ahb_div: device::rcc::d1cfgr::HPRE_A::DIV2, + // We configure all APB for 100MHz. These are relative to the AHB + // frequency. + apb1_div: device::rcc::d2cfgr::D2PPRE1_A::DIV2, + apb2_div: device::rcc::d2cfgr::D2PPRE2_A::DIV2, + apb3_div: device::rcc::d1cfgr::D1PPRE_A::DIV2, + apb4_div: device::rcc::d3cfgr::D3PPRE_A::DIV2, + + // Flash runs at 200MHz: 2WS, 2 programming cycles. See reference manual + // Table 13. + flash_latency: 2, + flash_write_delay: 2, + }, + ); +} diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index f5e25fac5a..16ebb765d6 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -226,7 +226,7 @@ interrupts = {"spi1.irq" = "spi-irq"} [tasks.transceivers] name = "drv-transceivers-server" -features = ["vlan"] +features = ["vlan", "thermal-control"] priority = 6 max-sizes = {flash = 65536, ram = 16384} stacksize = 4096 diff --git a/build/xtask/src/flash.rs b/build/xtask/src/flash.rs index 49b47f57e6..e067fe9f8c 100644 --- a/build/xtask/src/flash.rs +++ b/build/xtask/src/flash.rs @@ -174,9 +174,10 @@ pub fn config( "stm32f3-discovery" | "stm32f4-discovery" | "nucleo-h743zi2" | "nucleo-h753zi" | "stm32h7b3i-dk" | "gemini-bu-1" | "gimletlet-1" | "gimletlet-2" | "gimlet-b" | "gimlet-c" | "gimlet-d" | "gimlet-e" - | "gimlet-f" | "psc-a" | "psc-b" | "psc-c" | "sidecar-b" - | "sidecar-c" | "sidecar-d" | "stm32g031-nucleo" | "donglet-g030" - | "donglet-g031" | "oxcon2023g0" | "stm32g070" | "stm32g0b1" => { + | "psc-a" | "psc-b" | "psc-c" | "sidecar-b" | "sidecar-c" + | "sidecar-d" | "stm32g031-nucleo" | "donglet-g030" + | "donglet-g031" | "oxcon2023g0" | "stm32g070" | "stm32g0b1" + | "medusa-a" => { let cfg = FlashProgramConfig::new(chip_dir.join("openocd.cfg")); let mut flash = FlashConfig::new(FlashProgram::OpenOcd(cfg)); @@ -212,8 +213,8 @@ pub fn chip_name(board: &str) -> anyhow::Result<&'static str> { "nucleo-h753zi" => "STM32H753ZITx", "stm32h7b3i-dk" => "STM32H7B3IITx", "gemini-bu-1" | "gimletlet-1" | "gimletlet-2" | "gimlet-b" - | "gimlet-c" | "gimlet-d" | "gimlet-e" | "gimlet-f" | "psc-a" - | "psc-b" | "psc-c" | "sidecar-b" | "sidecar-c" | "sidecar-d" => { + | "gimlet-c" | "gimlet-d" | "gimlet-e" | "psc-a" | "psc-b" + | "psc-c" | "sidecar-b" | "sidecar-c" | "sidecar-d" | "medusa-a" => { "STM32H753ZITx" } "donglet-g030" => "STM32G030F6Px", diff --git a/drv/fpga-server/src/main.rs b/drv/fpga-server/src/main.rs index 563d38d8af..1c28450d42 100644 --- a/drv/fpga-server/src/main.rs +++ b/drv/fpga-server/src/main.rs @@ -90,7 +90,8 @@ fn main() -> ! { let devices = [ecp5::Ecp5::new(driver)]; } else if #[cfg(all(any(target_board = "sidecar-b", target_board = "sidecar-c", - target_board = "sidecar-d"), + target_board = "sidecar-d", + target_board = "medusa-a"), feature = "front_io"))] { let configuration_port = spi.device(drv_spi_api::devices::ECP5_FRONT_IO_FPGA); diff --git a/drv/medusa-seq-api/Cargo.toml b/drv/medusa-seq-api/Cargo.toml new file mode 100644 index 0000000000..ccc0a311f6 --- /dev/null +++ b/drv/medusa-seq-api/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "drv-medusa-seq-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +hubpack.workspace = true +idol-runtime.workspace = true +num-traits.workspace = true +serde.workspace = true +zerocopy.workspace = true + +counters = { path = "../../lib/counters" } +derive-idol-err = { path = "../../lib/derive-idol-err" } +drv-fpga-api = { path = "../fpga-api" } +userlib = { path = "../../sys/userlib" } + +[build-dependencies] +idol.workspace = true + +[lib] +test = false +doctest = false +bench = false diff --git a/drv/medusa-seq-api/build.rs b/drv/medusa-seq-api/build.rs new file mode 100644 index 0000000000..0195421217 --- /dev/null +++ b/drv/medusa-seq-api/build.rs @@ -0,0 +1,11 @@ +// 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> { + idol::client::build_client_stub( + "../../idl/medusa-seq.idol", + "client_stub.rs", + )?; + Ok(()) +} diff --git a/drv/medusa-seq-api/src/lib.rs b/drv/medusa-seq-api/src/lib.rs new file mode 100644 index 0000000000..25b7eda25d --- /dev/null +++ b/drv/medusa-seq-api/src/lib.rs @@ -0,0 +1,48 @@ +// 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 Medusa Sequencer server. + +#![no_std] + +use derive_idol_err::IdolError; +use drv_fpga_api::FpgaError; +use hubpack::SerializedSize; +use serde::{Deserialize, Serialize}; +use userlib::{sys_send, FromPrimitive}; + +#[derive( + Copy, Clone, Debug, PartialEq, Deserialize, Serialize, SerializedSize, +)] +pub enum RailName { + V1P0Mgmt, + V1P2Mgmt, + V2P5Mgmt, + V1P0Phy, + V2P5Phy, + V12QsfpOut, +} + +#[derive( + Copy, Clone, Debug, FromPrimitive, Eq, PartialEq, IdolError, counters::Count, +)] +pub enum MedusaError { + FpgaError = 1, + NoFrontIOBoard, + // The Front IO board power faulted + FrontIOBoardPowerFault, + // An power supply on Medusa faulted + PowerFault, + + #[idol(server_death)] + ServerRestarted, +} + +impl From for MedusaError { + fn from(_: FpgaError) -> Self { + Self::FpgaError + } +} + +include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/drv/medusa-seq-server/Cargo.toml b/drv/medusa-seq-server/Cargo.toml new file mode 100644 index 0000000000..eb4e4bc11b --- /dev/null +++ b/drv/medusa-seq-server/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "drv-medusa-seq-server" +version = "0.1.0" +edition = "2021" + +[dependencies] +cfg-if.workspace = true +cortex-m.workspace = true +hubpack.workspace = true +idol-runtime.workspace = true +num-traits.workspace = true +serde.workspace = true +zerocopy.workspace = true + +drv-fpga-api = { path = "../fpga-api", features = ["auxflash"] } +drv-fpga-user-api = { path = "../fpga-user-api" } +drv-i2c-api = { path = "../i2c-api" } +drv-i2c-devices = { path = "../i2c-devices" } +drv-medusa-seq-api = { path = "../medusa-seq-api" } +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" } +drv-stm32xx-sys-api = { path = "../../drv/stm32xx-sys-api", features = ["family-stm32h7"] } +ringbuf = { path = "../../lib/ringbuf" } +userlib = { path = "../../sys/userlib", features = ["panic-messages"] } + +[features] +h753 = ["build-i2c/h753"] +stay-in-a2 = [] +no-ipc-counters = ["idol/no-counters"] + +[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-medusa-seq-server" +test = false +doctest = false +bench = false diff --git a/drv/medusa-seq-server/build.rs b/drv/medusa-seq-server/build.rs new file mode 100644 index 0000000000..2b1451d72c --- /dev/null +++ b/drv/medusa-seq-server/build.rs @@ -0,0 +1,27 @@ +// 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::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/medusa-seq.idol", + "server_stub.rs", + idol::server::ServerStyle::InOrder, + )?; + + Ok(()) +} diff --git a/drv/medusa-seq-server/src/front_io.rs b/drv/medusa-seq-server/src/front_io.rs new file mode 100644 index 0000000000..7dd35d7f7c --- /dev/null +++ b/drv/medusa-seq-server/src/front_io.rs @@ -0,0 +1,111 @@ +// 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_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_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 new file mode 100644 index 0000000000..dac64e3423 --- /dev/null +++ b/drv/medusa-seq-server/src/main.rs @@ -0,0 +1,357 @@ +// 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 Medusa sequencing process. + +#![no_std] +#![no_main] + +use crate::front_io::FrontIOBoard; +use crate::power_control::PowerControl; +use core::convert::Infallible; +use drv_medusa_seq_api::{MedusaError, RailName}; +use drv_sidecar_front_io::phy_smi::PhyOscState; +use idol_runtime::{NotificationHandler, RequestError}; +use ringbuf::{ringbuf, ringbuf_entry}; +use userlib::*; + +task_slot!(I2C, i2c_driver); +task_slot!(FRONT_IO, front_io); +task_slot!(AUXFLASH, auxflash); +task_slot!(PACKRAT, packrat); + +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], + }, + PowerEnable(RailName, bool), + PowerFault(RailName), + MgmtPowerGood, + PhyPowerGood, +} + +ringbuf!(Trace, 32, Trace::None); + +const TIMER_INTERVAL: u64 = 1000; + +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() { + 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.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(()) + } +} + +impl idl::InOrderSequencerImpl for ServerImpl { + fn control_mgmt_rails( + &mut self, + _: &RecvMessage, + enabled: bool, + ) -> Result<(), RequestError> { + self.power_control.v1p0_mgmt.set_enable(enabled); + self.power_control.v1p2_mgmt.set_enable(enabled); + self.power_control.v2p5_mgmt.set_enable(enabled); + + if enabled { + userlib::hl::sleep_for(10); + if !self.power_control.mgmt_power_check() { + return Err(RequestError::from(MedusaError::PowerFault)); + } + ringbuf_entry!(Trace::MgmtPowerGood); + } + + Ok(()) + } + + fn control_phy_rails( + &mut self, + _: &RecvMessage, + enabled: bool, + ) -> Result<(), RequestError> { + self.power_control.v1p0_phy.set_enable(enabled); + self.power_control.v2p5_phy.set_enable(enabled); + + if enabled { + userlib::hl::sleep_for(10); + if !self.power_control.phy_power_check() { + return Err(RequestError::from(MedusaError::PowerFault)); + } + ringbuf_entry!(Trace::PhyPowerGood); + } + + Ok(()) + } + + fn control_rail( + &mut self, + _: &RecvMessage, + name: RailName, + enabled: bool, + ) -> Result<(), RequestError> { + let rail = self.power_control.get_rail(name); + 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 { + fn current_notification_mask(&self) -> u32 { + notifications::TIMER_MASK + } + + fn handle_notification(&mut self, _bits: u32) { + let next_deadline = sys_get_timer().now + TIMER_INTERVAL; + + sys_set_timer(Some(next_deadline), notifications::TIMER_MASK); + } +} + +#[export_name = "main"] +fn main() -> ! { + let mut buffer = [0; idl::INCOMING_SIZE]; + + let mut server = ServerImpl { + power_control: PowerControl::new(), + front_io_board: None, + }; + + // 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); + } + Err(MedusaError::FrontIOBoardPowerFault) => { + ringbuf_entry!(Trace::FrontIOBoardPowerFault) + } + // `front_io_board_preinit` currently only returns a + // MedusaError::FrontIOBoardPowerFault + Err(_) => unreachable!(), + } + + // The MGMT and PHY rails are enabled automatically by pullups, so we will + // check their power good signals and take action as appropriate. + if server.power_control.mgmt_power_check() { + ringbuf_entry!(Trace::MgmtPowerGood); + } + if server.power_control.phy_power_check() { + ringbuf_entry!(Trace::PhyPowerGood); + } + + // 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 { + idol_runtime::dispatch(&mut buffer, &mut server); + } +} + +mod idl { + use super::{MedusaError, RailName}; + + include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); +} + +include!(concat!(env!("OUT_DIR"), "/notifications.rs")); diff --git a/drv/medusa-seq-server/src/power_control.rs b/drv/medusa-seq-server/src/power_control.rs new file mode 100644 index 0000000000..f778316a7f --- /dev/null +++ b/drv/medusa-seq-server/src/power_control.rs @@ -0,0 +1,156 @@ +// 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/. + +//! A crate for managing the power supplies on the Medusa board + +use crate::*; +use drv_stm32xx_sys_api as sys_api; +use sys_api::{OutputType, Port, Pull, Speed, Sys}; + +task_slot!(SYS, sys); + +pub struct PowerRail { + /// The output GPIO for the power rail's enable pin + enable: sys_api::PinSet, + /// The input GPIO for the power rail's power good pin + power_good: sys_api::PinSet, + /// A RailName variant for ringbuf activity + name: RailName, +} + +impl PowerRail { + pub fn new( + enable: sys_api::PinSet, + power_good: sys_api::PinSet, + name: RailName, + ) -> Self { + let sys = Sys::from(SYS.get_task_id()); + + sys.gpio_configure_output( + enable, + OutputType::PushPull, + Speed::Low, + Pull::None, + ); + + sys.gpio_configure_input(power_good, Pull::None); + + Self { + enable, + power_good, + name, + } + } + + /// Sets the enable pin for the power rail to HIGH if `enabled` is true or + /// LOW if it is false. + pub fn set_enable(&self, enabled: bool) { + let sys = Sys::from(SYS.get_task_id()); + sys.gpio_set_to(self.enable, enabled); + ringbuf_entry!(Trace::PowerEnable(self.name, enabled)); + } + + /// Returns the status of the power good pin. If power good is not HIGH this + /// function disables the power rail automatically. + pub fn check_power_good(&self) -> bool { + if !self.power_good() { + ringbuf_entry!(Trace::PowerFault(self.name)); + self.set_enable(false); + return false; + } + true + } + + /// Returns the status of the power good signal for the rail + fn power_good(&self) -> bool { + let sys = Sys::from(SYS.get_task_id()); + sys.gpio_read(self.power_good) != 0 + } +} + +pub struct PowerControl { + pub v12_qsfp_out: PowerRail, + pub v1p0_mgmt: PowerRail, + pub v1p2_mgmt: PowerRail, + pub v2p5_mgmt: PowerRail, + pub v1p0_phy: PowerRail, + pub v2p5_phy: PowerRail, +} + +impl PowerControl { + pub fn new() -> Self { + // 12V HSC for the Front IO board + let v12_qsfp_out = PowerRail::new( + Port::J.pin(2), + Port::J.pin(1), + RailName::V12QsfpOut, + ); + + // VSC7448 rails + let v1p0_mgmt = + PowerRail::new(Port::J.pin(4), Port::J.pin(3), RailName::V1P0Mgmt); + let v1p2_mgmt = + PowerRail::new(Port::J.pin(6), Port::J.pin(5), RailName::V1P2Mgmt); + let v2p5_mgmt = + PowerRail::new(Port::J.pin(8), Port::J.pin(7), RailName::V2P5Mgmt); + + // The VSC8562 rails are generated from the same LDO which shares an + // enable pin + let v1p0_phy = + PowerRail::new(Port::J.pin(10), Port::J.pin(11), RailName::V1P0Phy); + let v2p5_phy = + PowerRail::new(Port::J.pin(10), Port::J.pin(12), RailName::V2P5Phy); + + Self { + v12_qsfp_out, + v1p0_mgmt, + v1p2_mgmt, + v2p5_mgmt, + v1p0_phy, + v2p5_phy, + } + } + + /// Returns true if all MGMT power rails are good. If that is not the case, + /// disable all management rails and returns false. + pub fn mgmt_power_check(&self) -> bool { + let all_good = self.v1p0_mgmt.check_power_good() + && self.v1p2_mgmt.check_power_good() + && self.v2p5_mgmt.check_power_good(); + + if !all_good { + self.v1p0_mgmt.set_enable(false); + self.v1p2_mgmt.set_enable(false); + self.v2p5_mgmt.set_enable(false); + } + + all_good + } + + /// Returns true if all PHY power rails are good. If that is not the case, + /// disable all PHY rails and returns false. + pub fn phy_power_check(&self) -> bool { + let all_good = self.v1p0_phy.check_power_good() + && self.v2p5_phy.check_power_good(); + + if !all_good { + self.v1p0_phy.set_enable(false); + self.v2p5_phy.set_enable(false); + } + + all_good + } + + pub fn get_rail(&self, name: RailName) -> &PowerRail { + use RailName::*; + match name { + V1P0Mgmt => &self.v1p0_mgmt, + V1P2Mgmt => &self.v1p2_mgmt, + V2P5Mgmt => &self.v2p5_mgmt, + V1P0Phy => &self.v1p0_phy, + V2P5Phy => &self.v2p5_phy, + V12QsfpOut => &self.v12_qsfp_out, + } + } +} diff --git a/drv/sidecar-front-io/build.rs b/drv/sidecar-front-io/build.rs index 98ba6465c2..ce49969834 100644 --- a/drv/sidecar-front-io/build.rs +++ b/drv/sidecar-front-io/build.rs @@ -9,7 +9,11 @@ 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" { + if board != "sidecar-b" + && board != "sidecar-c" + && board != "sidecar-d" + && board != "medusa-a" + { panic!("unknown target board"); } diff --git a/drv/sidecar-front-io/src/leds.rs b/drv/sidecar-front-io/src/leds.rs index ffd73d1afe..c00f84a301 100644 --- a/drv/sidecar-front-io/src/leds.rs +++ b/drv/sidecar-front-io/src/leds.rs @@ -188,7 +188,11 @@ const LED_MAP: LedMap = LedMap([ controller: LedController::Left, output: 1, }, - #[cfg(any(target_board = "sidecar-c", target_board = "sidecar-d"))] + #[cfg(any( + target_board = "sidecar-c", + target_board = "sidecar-d", + target_board = "medusa-a" + ))] // Port 16 LedLocation { controller: LedController::Left, @@ -200,7 +204,11 @@ const LED_MAP: LedMap = LedMap([ controller: LedController::Left, output: 3, }, - #[cfg(any(target_board = "sidecar-c", target_board = "sidecar-d"))] + #[cfg(any( + target_board = "sidecar-c", + target_board = "sidecar-d", + target_board = "medusa-a" + ))] // Port 17 LedLocation { controller: LedController::Left, @@ -212,7 +220,11 @@ const LED_MAP: LedMap = LedMap([ controller: LedController::Left, output: 5, }, - #[cfg(any(target_board = "sidecar-c", target_board = "sidecar-d"))] + #[cfg(any( + target_board = "sidecar-c", + target_board = "sidecar-d", + target_board = "medusa-a" + ))] // Port 18 LedLocation { controller: LedController::Left, @@ -224,7 +236,11 @@ const LED_MAP: LedMap = LedMap([ controller: LedController::Left, output: 7, }, - #[cfg(any(target_board = "sidecar-c", target_board = "sidecar-d"))] + #[cfg(any( + target_board = "sidecar-c", + target_board = "sidecar-d", + target_board = "medusa-a" + ))] // Port 19 LedLocation { controller: LedController::Left, diff --git a/drv/sidecar-seq-api/Cargo.toml b/drv/sidecar-seq-api/Cargo.toml index c5329ad74e..9fe990d81e 100644 --- a/drv/sidecar-seq-api/Cargo.toml +++ b/drv/sidecar-seq-api/Cargo.toml @@ -10,7 +10,7 @@ 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-sidecar-mainboard-controller = { path = "../sidecar-mainboard-controller" } diff --git a/drv/transceivers-server/Cargo.toml b/drv/transceivers-server/Cargo.toml index e10c5c8006..24a9c07aa4 100644 --- a/drv/transceivers-server/Cargo.toml +++ b/drv/transceivers-server/Cargo.toml @@ -33,6 +33,7 @@ zerocopy = { workspace = true } [features] vlan = ["task-net-api/vlan"] +thermal-control = [] no-ipc-counters = ["idol/no-counters"] [build-dependencies] diff --git a/drv/transceivers-server/src/main.rs b/drv/transceivers-server/src/main.rs index 2618526a35..50fc5cf4c7 100644 --- a/drv/transceivers-server/src/main.rs +++ b/drv/transceivers-server/src/main.rs @@ -27,6 +27,7 @@ use drv_transceivers_api::{ }; use enum_map::Enum; use task_sensor_api::{NoData, Sensor}; +#[allow(unused_imports)] use task_thermal_api::{Thermal, ThermalError, ThermalProperties}; use transceiver_messages::{ message::LedState, mgmt::ManagementInterface, MAX_PACKET_SIZE, @@ -40,9 +41,11 @@ 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); +#[cfg(feature = "thermal-control")] +task_slot!(THERMAL, thermal); + include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); #[allow(dead_code)] @@ -127,6 +130,7 @@ struct ServerImpl { consecutive_nacks: [u8; NUM_PORTS as usize], /// Handle to write thermal models and presence to the `thermal` task + #[cfg(feature = "thermal-control")] thermal_api: Thermal, /// Handle to write temperatures to the `sensors` task @@ -135,13 +139,13 @@ struct ServerImpl { /// Thermal models are populated by the host thermal_models: [Option; NUM_PORTS as usize], } - #[derive(Copy, Clone)] struct ThermalModel { /// What kind of transceiver is this? interface: ManagementInterface, /// What are its thermal properties, e.g. critical temperature? + #[allow(dead_code)] model: ThermalProperties, } @@ -378,9 +382,12 @@ impl ServerImpl { } } } else if !operational && self.thermal_models[i].is_some() { - // This transceiver went away; remove it from the thermal loop - if let Err(e) = self.thermal_api.remove_dynamic_input(i) { - ringbuf_entry!(Trace::ThermalError(i, e)); + #[cfg(feature = "thermal-control")] + { + // This transceiver went away; remove it from the thermal loop + if let Err(e) = self.thermal_api.remove_dynamic_input(i) { + ringbuf_entry!(Trace::ThermalError(i, e)); + } } // Tell the `sensor` task that this device is no longer present @@ -408,14 +415,17 @@ impl ServerImpl { None => continue, }; - // *Always* post the thermal model over to the thermal task, so that - // the thermal task still has it in case of restart. This will - // return a `NotInAutoMode` error if the thermal loop is in manual - // mode; this is harmless and will be ignored (instead of cluttering - // up the logs). - match self.thermal_api.update_dynamic_input(i, m.model) { - Ok(()) | Err(ThermalError::NotInAutoMode) => (), - Err(e) => ringbuf_entry!(Trace::ThermalError(i, e)), + #[cfg(feature = "thermal-control")] + { + // *Always* post the thermal model over to the thermal task, so + // that the thermal task still has it in case of restart. This + // will return a `NotInAutoMode` error if the thermal loop is in + // manual mode; this is harmless and will be ignored (instead of + // cluttering up the logs). + match self.thermal_api.update_dynamic_input(i, m.model) { + Ok(()) | Err(ThermalError::NotInAutoMode) => (), + Err(e) => ringbuf_entry!(Trace::ThermalError(i, e)), + } } let temperature = match m.interface { @@ -609,7 +619,6 @@ fn main() -> ! { ); let net = task_net_api::Net::from(NET.get_task_id()); - let thermal_api = Thermal::from(THERMAL.get_task_id()); let sensor_api = Sensor::from(SENSOR.get_task_id()); let (tx_data_buf, rx_data_buf) = { @@ -620,6 +629,9 @@ fn main() -> ! { BUFS.claim() }; + #[cfg(feature = "thermal-control")] + let thermal_api = Thermal::from(THERMAL.get_task_id()); + let mut server = ServerImpl { transceivers, leds, @@ -633,6 +645,7 @@ fn main() -> ! { system_led_state: LedState::Off, disabled: LogicalPortMask(0), consecutive_nacks: [0; NUM_PORTS as usize], + #[cfg(feature = "thermal-control")] thermal_api, sensor_api, thermal_models: [None; NUM_PORTS as usize], diff --git a/idl/medusa-seq.idol b/idl/medusa-seq.idol new file mode 100644 index 0000000000..c75b858e11 --- /dev/null +++ b/idl/medusa-seq.idol @@ -0,0 +1,60 @@ +// Medusa Sequencer API + +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", + }, + reply: Result( + ok: "()", + err: CLike("MedusaError"), + ), + ), + + "control_phy_rails": ( + args: { + "enabled": "bool", + }, + reply: Result( + ok: "()", + err: CLike("MedusaError"), + ), + ), + + "control_rail": ( + args: { + "name": "RailName", + "enabled": "bool", + }, + reply: Simple("()"), + idempotent: true, + encoding: Hubpack, + ), + }, +) diff --git a/idl/sidecar-seq.idol b/idl/sidecar-seq.idol index 3e037f520a..2eeaed53d2 100644 --- a/idl/sidecar-seq.idol +++ b/idl/sidecar-seq.idol @@ -1,4 +1,4 @@ -// Gimlet Sequencer API +// Sidecar Sequencer API Interface( name: "Sequencer", diff --git a/task/monorail-server/Cargo.toml b/task/monorail-server/Cargo.toml index 827218c1ba..7f0de74d7f 100644 --- a/task/monorail-server/Cargo.toml +++ b/task/monorail-server/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +drv-medusa-seq-api = { path = "../../drv/medusa-seq-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 } @@ -29,6 +30,7 @@ zerocopy.workspace = true [features] leds = ["drv-user-leds-api"] +medusa = ["drv-medusa-seq-api", "drv-sidecar-front-io"] mgmt = ["task-net-api"] sidecar = ["drv-sidecar-seq-api", "drv-sidecar-front-io"] vlan = ["task-net-api?/vlan"] diff --git a/task/monorail-server/src/bsp/medusa_a.rs b/task/monorail-server/src/bsp/medusa_a.rs new file mode 100644 index 0000000000..73034b92c6 --- /dev/null +++ b/task/monorail-server/src/bsp/medusa_a.rs @@ -0,0 +1,528 @@ +// 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_medusa_seq_api::Sequencer; +use drv_sidecar_front_io::phy_smi::PhySmi; +use ringbuf::*; +use userlib::{task_slot, UnwrapLite}; +use vsc7448::{ + config::Speed, miim_phy::Vsc7448MiimPhy, Vsc7448, Vsc7448Rw, VscError, +}; +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); + +/// Interval in milliseconds at which `Bsp::wake()` is called by the main loop +pub const WAKE_INTERVAL: Option = Some(500); + +#[derive(Copy, Clone, PartialEq)] +enum Trace { + None, + FrontIoSpeedChange { + port: u8, + before: Speed, + after: Speed, + }, + FrontIoPhyOscillatorBad, + AnegCheckFailed(VscError), + Reinit, +} +ringbuf!(Trace, 16, Trace::None); + +//////////////////////////////////////////////////////////////////////////////// + +pub struct Bsp<'a, R> { + vsc7448: &'a Vsc7448<'a, R>, + + /// Handle for the sequencer task + seq: Sequencer, + + /// PHY for the on-board PHY ("PHY4") + vsc8504: Vsc8504, + + /// 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 + /// + /// This is `None` if the front IO board isn't connected. + vsc8562: Option, + + /// Configured speed of ports on the front IO board, from the perspective of + /// the VSC7448. + /// + /// They are initially configured to 1G, but the VSC8562 PHY may + /// autonegotiate to a different speed, in which case we have to reconfigure + /// the port on the VSC7448 to match. + front_io_speed: [Speed; 2], + + /// Time at which the 10G link went down + link_down_at: Option, +} + +pub const REFCLK_SEL: vsc7448::RefClockFreq = + vsc7448::RefClockFreq::Clk156p25MHz; +pub const REFCLK2_SEL: Option = None; + +mod map { + // Local module to avoid leaking imports + use vsc7448::config::{ + PortMap, + PortMode::{self, *}, + Speed::*, + }; + const SGMII: Option = Some(Sgmii(Speed100M)); + const QSGMII_100M: Option = Some(Qsgmii(Speed100M)); + const QSGMII_1G: Option = Some(Qsgmii(Speed1G)); + const BASE_KR: Option = Some(BaseKr); + + // See RFD144 for a detailed look at the design + pub const PORT_MAP: PortMap = PortMap::new([ + SGMII, // 0 | DEV1G_0 | SERDES1G_1 | Cubby 0 + SGMII, // 1 | DEV1G_1 | SERDES1G_2 | Cubby 1 + SGMII, // 2 | DEV1G_2 | SERDES1G_3 | Cubby 2 + SGMII, // 3 | DEV1G_3 | SERDES1G_4 | Cubby 3 + SGMII, // 4 | DEV1G_4 | SERDES1G_5 | Cubby 4 + SGMII, // 5 | DEV1G_5 | SERDES1G_6 | Cubby 5 + SGMII, // 6 | DEV1G_6 | SERDES1G_7 | Cubby 6 + SGMII, // 7 | DEV1G_7 | SERDES1G_8 | Cubby 7 + SGMII, // 8 | DEV2G5_0 | SERDES6G_0 | Cubby 8 + SGMII, // 9 | DEV2G5_1 | SERDES6G_1 | Cubby 9 + SGMII, // 10 | DEV2G5_2 | SERDES6G_2 | Cubby 10 + SGMII, // 11 | DEV2G5_3 | SERDES6G_3 | Cubby 11 + SGMII, // 12 | DEV2G5_4 | SERDES6G_4 | Cubby 12 + SGMII, // 13 | DEV2G5_5 | SERDES6G_5 | Cubby 13 + SGMII, // 14 | DEV2G5_6 | SERDES6G_6 | Cubby 14 + SGMII, // 15 | DEV2G5_7 | SERDES6G_7 | Cubby 15 + SGMII, // 16 | DEV2G5_8 | SERDES6G_8 | Cubby 16 + SGMII, // 17 | DEV2G5_9 | SERDES6G_9 | Cubby 17 + SGMII, // 18 | DEV2G5_10 | SERDES6G_10 | Cubby 18 + SGMII, // 19 | DEV2G5_11 | SERDES6G_11 | Cubby 19 + SGMII, // 20 | DEV2G5_12 | SERDES6G_12 | Cubby 20 + SGMII, // 21 | DEV2G5_13 | SERDES6G_13 | Cubby 21 + None, // 22 + None, // 23 + SGMII, // 24 | DEV2G5_16 | SERDES6G_16 | Cubby 22 + SGMII, // 25 | DEV2G5_17 | SERDES6G_17 | Cubby 23 + SGMII, // 26 | DEV2G5_18 | SERDES6G_18 | Cubby 24 + SGMII, // 27 | DEV2G5_19 | SERDES6G_19 | Cubby 25 + SGMII, // 28 | DEV2G5_20 | SERDES6G_20 | Cubby 26 + SGMII, // 29 | DEV2G5_21 | SERDES6G_21 | Cubby 27 + SGMII, // 30 | DEV2G5_22 | SERDES6G_22 | Cubby 28 + SGMII, // 31 | DEV2G5_23 | SERDES6G_23 | Cubby 29 + None, // 32 + None, // 33 + None, // 34 + None, // 35 + None, // 36 + None, // 37 + None, // 38 + None, // 39 + QSGMII_100M, // 40 | DEV1G_16 | SERDES6G_14 | Peer SP + QSGMII_100M, // 41 | DEV1G_17 | SERDES6G_14 | PSC0 + QSGMII_100M, // 42 | DEV1G_18 | SERDES6G_14 | PSC1 + QSGMII_100M, // 43 | Unused + QSGMII_1G, // 44 | DEV1G_20 | SERDES6G_15 | Technician 1 + QSGMII_1G, // 45 | DEV1G_21 | SERDES6G_15 | Technician 2 + None, // 46 | Unused (configured in QSGMII mode by port 44) + None, // 47 | Unused (configured in QSGMII mode by port 44) + SGMII, // 48 | DEV2G5_24 | SERDES1G_0 | Local SP + BASE_KR, // 49 | DEV10G_0 | SERDES10G_0 | Tofino 2 + None, // 50 | Unused + SGMII, // 51 | DEV2G5_27 | SERDES10G_2 | Cubby 30 (shadows DEV10G_2) + SGMII, // 52 | DEV2G5_28 | SERDES10G_3 | Cubby 31 (shadows DEV10G_3) + ]); +} +pub use map::PORT_MAP; + +pub fn preinit() { + // Nothing to do here, just stubbing out for the BSP interface +} + +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 mut out = Bsp { + vsc7448, + vsc8504: Vsc8504::empty(), + 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()?; + Ok(out) + } + + pub fn reinit(&mut self) -> Result<(), VscError> { + ringbuf_entry!(Trace::Reinit); + self.vsc7448.init()?; + + // By default, the SERDES6G are grouped into 4x chunks for XAUI, + // where a single DEV10G runs 4x SERDES6G at 2.5G. This leads to very + // confusing behavior when only running a few SERDES6G: in particularly, + // we noticed that SERDES6G_14 seemed to depend on SERDES6G_12. + // + // We're never using this "lane sync" feature, so disable it everywhere. + for i in 0..=1 { + self.vsc7448.modify( + VAUI0().VAUI_CHANNEL_CFG().VAUI_CHANNEL_CFG(i), + |r| { + r.set_lane_sync_ena(0); + }, + )?; + self.vsc7448.modify( + VAUI1().VAUI_CHANNEL_CFG().VAUI_CHANNEL_CFG(i), + |r| { + r.set_lane_sync_ena(0); + }, + )?; + } + + // We must disable frame copying before configuring ports; otherwise, a + // rare failure mode can result in queues getting stuck (forever!). We + // disable frame copying by enabling VLANs, then removing all ports from + // them! + // + // (ports will be added back to VLANs after configuration is done, in + // the call to `configure_vlan_semistrict` below) + // + // The root cause is unknown, but we suspect a hardware race condition + // in the switch IC; see this issue for detailed discussion: + // https://github.com/oxidecomputer/hubris/issues/1399 + self.vsc7448.configure_vlan_none()?; + + // Reset internals + self.vsc8504 = Vsc8504::empty(); + self.front_io_speed = [Speed::Speed1G; 2]; + + self.phy_vsc8504_init()?; + + self.vsc7448.configure_ports_from_map(&PORT_MAP)?; + self.vsc7448.configure_vlan_semistrict()?; + self.vsc7448_postconfig()?; + + // Some front IO boards have a faulty oscillator driving the PHY, + // causing its clock to misbehave some fraction of (re-)boots. Init + // the PHY in a loop, requesting the sequencer to reset as much as + // necessary to try and correct the problem. + let mut osc_good = false; + + while self.vsc8562.is_some() && !osc_good { + self.phy_vsc8562_init()?; + + osc_good = self.is_front_io_link_good()?; + + // Notify the sequencer 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) + .map_err(|e| VscError::ProxyError(e.into()))?; + + if !osc_good { + ringbuf_entry!(Trace::FrontIoPhyOscillatorBad) + } + } + + if let Some(phy_rw) = &mut self.vsc8562 { + // Read the MAC_SERDES_PCS_STATUS register to clear a spurious + // MAC_CGBAD error that shows up on startup. + for p in 0..2 { + use vsc7448_pac::phy; + vsc85xx::Phy::new(p, phy_rw) + .read(phy::EXTENDED_3::MAC_SERDES_PCS_STATUS())?; + } + } + + Ok(()) + } + + fn vsc7448_postconfig(&mut self) -> Result<(), VscError> { + // The SERDES6G going to the front IO board needs to be tuned from + // its default settings, otherwise the signal quality is bad. + const FRONT_IO_SERDES6G: u8 = 15; + vsc7448::serdes6g::serdes6g_read(self.vsc7448, FRONT_IO_SERDES6G)?; + + // h monorail write HSIO:SERDES6G_ANA_CFG:SERDES6G_OB_CFG 0x28441001 + // h monorail write HSIO:SERDES6G_ANA_CFG:SERDES6G_OB_CFG1 0x3F + self.vsc7448.modify( + HSIO().SERDES6G_ANA_CFG().SERDES6G_OB_CFG(), + |r| { + r.set_ob_post0(0x10); + r.set_ob_prec(0x11); // -1, since MSB is sign + r.set_ob_post1(0x2); + r.set_ob_sr_h(0); // Full-rate mode + r.set_ob_sr(0); // Very fast edges (30 ps) + }, + )?; + self.vsc7448.modify( + HSIO().SERDES6G_ANA_CFG().SERDES6G_OB_CFG1(), + |r| { + r.set_ob_lev(0x3F); + }, + )?; + vsc7448::serdes6g::serdes6g_write(self.vsc7448, FRONT_IO_SERDES6G)?; + + // Same for the on-board QSGMII link to the VSC8504, with different + // settings. + // h monorail write SERDES6G_OB_CFG 0x26000131 + // h monorail write SERDES6G_OB_CFG1 0x20 + const VSC8504_SERDES6G: u8 = 14; + vsc7448::serdes6g::serdes6g_read(self.vsc7448, VSC8504_SERDES6G)?; + self.vsc7448.modify( + HSIO().SERDES6G_ANA_CFG().SERDES6G_OB_CFG(), + |r| { + // Leave all other values as default + r.set_ob_post0(0xc); + r.set_ob_sr_h(1); // half-rate mode + r.set_ob_sr(3); // medium speed edges (about 105 ps) + }, + )?; + self.vsc7448.modify( + HSIO().SERDES6G_ANA_CFG().SERDES6G_OB_CFG1(), + |r| { + r.set_ob_lev(0x20); + }, + )?; + vsc7448::serdes6g::serdes6g_write(self.vsc7448, VSC8504_SERDES6G)?; + + // Write to the base port on the VSC8504, patching the SERDES6G + // config to improve signal integrity. This is based on benchtop + // scoping of the QSGMII signals going from the VSC8504 to the VSC7448. + use vsc85xx::tesla::{TeslaPhy, TeslaSerdes6gObConfig}; + let rw = &mut Vsc7448MiimPhy::new(self.vsc7448, 0); + let mut vsc8504 = self.vsc8504.phy(0, rw); + let mut tesla = TeslaPhy { + phy: &mut vsc8504.phy, + }; + tesla.tune_serdes6g_ob(TeslaSerdes6gObConfig { + ob_post0: 0x6, + ob_post1: 0, + ob_prec: 0, + ob_sr_h: 1, // half rate + ob_sr: 0, + })?; + + // Tune QSGMII link from the front IO board's PHY + // These values are captured empirically with an oscilloscope + if let Some(phy) = self.vsc8562.as_mut() { + use vsc85xx::vsc8562::{Sd6gObCfg, Sd6gObCfg1}; + let mut p = vsc85xx::Phy::new(0, phy); // port 0 + let mut v = Vsc8562Phy { phy: &mut p }; + v.tune_sd6g_ob_cfg(Sd6gObCfg { + ob_ena1v_mode: 1, + ob_pol: 1, + ob_post0: 20, + ob_post1: 0, + ob_sr_h: 0, + ob_resistor_ctr: 1, + ob_sr: 15, + })?; + v.tune_sd6g_ob_cfg1(Sd6gObCfg1 { + ob_ena_cas: 0, + ob_lev: 48, + })?; + } + + Ok(()) + } + + /// Configures the local PHY ("PHY4"), which is an on-board VSC8504 + fn phy_vsc8504_init(&mut self) -> Result<(), VscError> { + // Let's configure the on-board PHY first + // + // It's always powered on, and COMA_MODE is controlled via the VSC7448 + // on GPIO_47. + const COMA_MODE_GPIO: u32 = 47; + + // The PHY talks on MIIM addresses 0x4-0x7 (configured by resistors + // on the board), using the VSC7448 as a MIIM bridge. + + // When the VSC7448 comes out of reset, GPIO_47 is an input and low. + // It's pulled up by a resistor on the board, keeping the PHY in + // COMA_MODE. That's fine! + + // Initialize the PHY + let rw = &mut Vsc7448MiimPhy::new(self.vsc7448, 0); + self.vsc8504 = Vsc8504::init(4, rw)?; + for p in 5..8 { + Vsc8504::init(p, rw)?; + } + + // The VSC8504 on the sidecar has its SIGDET GPIOs pulled down, + // for some reason. + self.vsc8504.set_sigdet_polarity(rw, true).unwrap_lite(); + + // Switch the GPIO to an output. Since the output register is low + // by default, this pulls COMA_MODE low, bringing the VSC8504 into + // mission mode. + self.vsc7448.modify(DEVCPU_GCB().GPIO().GPIO_OE1(), |r| { + let mut g_oe1 = r.g_oe1(); + g_oe1 |= 1 << (COMA_MODE_GPIO - 32); + r.set_g_oe1(g_oe1); + })?; + + Ok(()) + } + + pub fn phy_vsc8562_init(&mut self) -> Result<(), VscError> { + if let Some(phy_rw) = &mut self.vsc8562 { + // 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() + .map_err(|e| VscError::ProxyError(e.into()))?; + + for p in 0..2 { + let mut phy = vsc85xx::Phy::new(p, phy_rw); + let mut v = Vsc8562Phy { phy: &mut phy }; + v.init_qsgmii()?; + } + phy_rw + .set_coma_mode(false) + .map_err(|e| VscError::ProxyError(e.into()))?; + } + + Ok(()) + } + + fn check_aneg_speed( + &mut self, + switch_port: u8, + phy_port: u8, + ) -> Result<(), VscError> { + if let Some(phy_rw) = &mut self.vsc8562 { + use vsc7448_pac::phy::*; + + let phy = vsc85xx::Phy::new(phy_port, phy_rw); + let status = phy.read(STANDARD::MODE_STATUS())?; + + // If autonegotiation is complete, then decide on a speed + let target_speed = if status.0 & (1 << 5) != 0 { + let status = phy.read(STANDARD::REG_1000BASE_T_STATUS())?; + // Check "LP 1000BASE-T FDX capable" bit + if status.0 & (1 << 11) != 0 { + Some(Speed::Speed1G) + } else { + Some(Speed::Speed100M) + } + // TODO: 10M? + } else { + None + }; + if let Some(target_speed) = target_speed { + let current_speed = self.front_io_speed[phy_port as usize]; + if target_speed != current_speed { + ringbuf_entry!(Trace::FrontIoSpeedChange { + port: switch_port, + before: current_speed, + after: target_speed, + }); + let cfg = PORT_MAP.port_config(switch_port).unwrap(); + self.vsc7448.reinit_sgmii(cfg.dev, target_speed)?; + self.front_io_speed[phy_port as usize] = target_speed; + + // Clear a spurious MAC_CGBAD flag that pops up when we + // change the link speed here. + for p in 0..2 { + use vsc7448_pac::phy; + vsc85xx::Phy::new(p, phy_rw) + .read(phy::EXTENDED_3::MAC_SERDES_PCS_STATUS())?; + } + } + } + } + Ok(()) + } + + fn is_front_io_link_good(&self) -> Result { + // Determine if the link is up which implies the PHY oscillator is good. + Ok(self + .vsc7448 + .read(HSIO().HW_CFGSTAT().HW_QSGMII_STAT(11))? + .sync() + == 1) + } + + pub fn wake(&mut self) -> Result<(), VscError> { + // Check for autonegotiation on the front IO board, then reconfigure + // on the switch side to change speeds. + for port in 44..=45 { + match self.check_aneg_speed(port, port - 44) { + Ok(()) => (), + Err(e) => ringbuf_entry!(Trace::AnegCheckFailed(e)), + } + } + + self.link_down_at = None; + + Ok(()) + } + + /// Calls a function on a `Phy` associated with the given port. + /// + /// Returns `None` if the given port isn't associated with a PHY + /// (for example, because it's an SGMII link) + pub fn phy_fn>) -> T>( + &mut self, + port: u8, + callback: F, + ) -> Option { + let (mut phy_rw, phy_port) = match port { + // Ports 40-43 connect to a VSC8504 PHY over QSGMII and represent + // ports 4-7 on the PHY. + 40..=43 => { + let phy_rw = GenericPhyRw::Vsc7448(Vsc7448MiimPhy::new( + self.vsc7448.rw, + 0, + )); + let phy_port = port - 40 + 4; + (phy_rw, phy_port) + } + 44..=45 => { + if let Some(phy_rw) = &self.vsc8562 { + (GenericPhyRw::FrontIo(phy_rw), port - 44) + } else { + return None; + } + } + _ => return None, + }; + let phy = vsc85xx::Phy::new(phy_port, &mut phy_rw); + Some(callback(phy)) + } +} + +/// Simple enum that contains all possible `PhyRw` handle types +pub enum GenericPhyRw<'a, R> { + Vsc7448(Vsc7448MiimPhy<'a, R>), + FrontIo(&'a PhySmi), +} + +impl<'a, R: Vsc7448Rw> PhyRw for GenericPhyRw<'a, R> { + #[inline(always)] + fn read_raw(&self, port: u8, reg: u8) -> Result { + match self { + GenericPhyRw::Vsc7448(n) => n.read_raw(port, reg), + GenericPhyRw::FrontIo(n) => n.read_raw(port, reg), + } + } + #[inline(always)] + fn write_raw(&self, port: u8, reg: u8, value: u16) -> Result<(), VscError> { + match self { + GenericPhyRw::Vsc7448(n) => n.write_raw(port, reg, value), + GenericPhyRw::FrontIo(n) => n.write_raw(port, reg, value), + } + } +} diff --git a/task/monorail-server/src/main.rs b/task/monorail-server/src/main.rs index 17f3e8525a..0add1f7e2c 100644 --- a/task/monorail-server/src/main.rs +++ b/task/monorail-server/src/main.rs @@ -13,6 +13,7 @@ ), path = "bsp/sidecar_bcd.rs" )] +#[cfg_attr(target_board = "medusa-a", path = "bsp/medusa_a.rs")] mod bsp; mod server; diff --git a/task/net/Cargo.toml b/task/net/Cargo.toml index 0f24272bdf..9ee6fe93a0 100644 --- a/task/net/Cargo.toml +++ b/task/net/Cargo.toml @@ -21,6 +21,7 @@ zerocopy = { workspace = true } counters = { path = "../../lib/counters" } drv-gimlet-seq-api = { path = "../../drv/gimlet-seq-api", optional = true } +drv-medusa-seq-api = { path = "../../drv/medusa-seq-api", optional = true } drv-psc-seq-api = { path = "../../drv/psc-seq-api", optional = true } drv-sidecar-seq-api = { path = "../../drv/sidecar-seq-api", optional = true } drv-spi-api = { path = "../../drv/spi-api", optional = true } @@ -46,6 +47,7 @@ mgmt = ["drv-spi-api", "ksz8463", "drv-user-leds-api", "task-net-api/mgmt"] vpd-mac = ["task-packrat-api"] gimlet = ["drv-gimlet-seq-api"] sidecar = ["drv-sidecar-seq-api"] +medusa = ["drv-medusa-seq-api"] psc = ["drv-psc-seq-api"] h743 = ["drv-stm32h7-eth/h743", "stm32h7/stm32h743", "drv-stm32xx-sys-api/h743", "drv-stm32h7-spi-server-core?/h743"] h753 = ["drv-stm32h7-eth/h753", "stm32h7/stm32h753", "drv-stm32xx-sys-api/h753", "drv-stm32h7-spi-server-core?/h753"] diff --git a/task/net/src/bsp/medusa_a.rs b/task/net/src/bsp/medusa_a.rs new file mode 100644 index 0000000000..457da2a9dd --- /dev/null +++ b/task/net/src/bsp/medusa_a.rs @@ -0,0 +1,135 @@ +// 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/. + +//! BSP for the Medusa model A + +#[cfg(not(all(feature = "ksz8463", feature = "mgmt")))] +compile_error!("this BSP requires the ksz8463 and mgmt features"); + +use crate::{ + bsp_support::{self, Ksz8463}, + mgmt, + miim_bridge::MiimBridge, + pins, +}; +use drv_spi_api::SpiServer; +use drv_stm32h7_eth as eth; +use drv_stm32xx_sys_api::{Alternate, Port, Sys}; +use task_net_api::{ + ManagementCounters, ManagementLinkStatus, MgmtError, PhyError, +}; +use userlib::UnwrapLite; +use vsc7448_pac::types::PhyRegisterAddress; + +//////////////////////////////////////////////////////////////////////////////// + +pub struct BspImpl(mgmt::Bsp); + +impl bsp_support::Bsp for BspImpl { + // This system wants to be woken periodically to do logging + const WAKE_INTERVAL: Option = Some(500); + + /// Stateless function to configure ethernet pins before the Bsp struct + /// is actually constructed + fn configure_ethernet_pins(sys: &Sys) { + pins::RmiiPins { + refclk: Port::A.pin(1), // CLK_50M_SP_RMII_REFCLK + crs_dv: Port::A.pin(7), // RMII_SP_TO_EPE_RX_DV + tx_en: Port::G.pin(11), // RMII_SP_TO_EPE_TX_EN + txd0: Port::G.pin(13), // RMII_SP_TO_EPE_TXD0 + txd1: Port::G.pin(12), // RMII_SP_TO_EPE_TXD1 + rxd0: Port::C.pin(4), // RMII_SP_TO_EPE_RDX0 (typo in schematic) + rxd1: Port::C.pin(5), // RMII_SP_TO_EPE_RXD1 + af: Alternate::AF11, + } + .configure(sys); + + pins::MdioPins { + mdio: Port::A.pin(2), // MIIM_SP_TO_PHY_MDIO_3V3 + mdc: Port::C.pin(1), // MIIM_SP_TO_PHY_MDC_3V3 + af: Alternate::AF11, + } + .configure(sys); + } + + fn preinit() { + // TODO + } + + fn new(eth: ð::Ethernet, sys: &Sys) -> Self { + let spi = bsp_support::claim_spi(sys); + let ksz8463_dev = spi.device(drv_spi_api::devices::KSZ8463); + let bsp = mgmt::Config { + // SP_TO_LDO_PHY2_EN (turns on both P2V5 and P1V0) + power_en: Some(Port::I.pin(11)), + slow_power_en: false, + power_good: &[], // TODO + + ksz8463: Ksz8463::new(ksz8463_dev), + // SP_TO_EPE_RESET_L + ksz8463_nrst: Port::A.pin(0), + ksz8463_rst_type: mgmt::Ksz8463ResetSpeed::Normal, + + #[cfg(feature = "vlan")] + ksz8463_vlan_mode: ksz8463::VLanMode::Mandatory, + #[cfg(not(feature = "vlan"))] + ksz8463_vlan_mode: ksz8463::VLanMode::Optional, + + // SP_TO_PHY2_COMA_MODE_3V3 + vsc85x2_coma_mode: Some(Port::I.pin(15)), + // SP_TO_PHY2_RESET_3V3_L + vsc85x2_nrst: Port::I.pin(14), + vsc85x2_base_port: 0, + } + .build(sys, eth); + + // The VSC8552 on the sidecar has its SIGDET GPIOs pulled down, + // for some reason. + let rw = &mut MiimBridge::new(eth); + bsp.vsc85x2.set_sigdet_polarity(rw, true).unwrap_lite(); + + Self(bsp) + } + + fn wake(&self, eth: ð::Ethernet) { + self.0.wake(eth); + } + + fn phy_read( + &mut self, + port: u8, + reg: PhyRegisterAddress, + eth: ð::Ethernet, + ) -> Result { + self.0.phy_read(port, reg, eth) + } + + fn phy_write( + &mut self, + port: u8, + reg: PhyRegisterAddress, + value: u16, + eth: ð::Ethernet, + ) -> Result<(), PhyError> { + self.0.phy_write(port, reg, value, eth) + } + + fn ksz8463(&self) -> &Ksz8463 { + &self.0.ksz8463 + } + + fn management_link_status( + &self, + eth: ð::Ethernet, + ) -> Result { + self.0.management_link_status(eth) + } + + fn management_counters( + &self, + eth: &crate::eth::Ethernet, + ) -> Result { + self.0.management_counters(eth) + } +} diff --git a/task/net/src/main.rs b/task/net/src/main.rs index 130ba220cd..a133787a7d 100644 --- a/task/net/src/main.rs +++ b/task/net/src/main.rs @@ -45,6 +45,7 @@ mod server; all(target_board = "gimletlet-2", feature = "gimletlet-nic"), path = "bsp/gimletlet_nic.rs" )] +#[cfg_attr(target_board = "medusa-a", path = "bsp/medusa_a.rs")] mod bsp; #[cfg_attr(feature = "vlan", path = "server_vlan.rs")] diff --git a/task/thermal/Cargo.toml b/task/thermal/Cargo.toml index 931ad10589..cfdd05bdf9 100644 --- a/task/thermal/Cargo.toml +++ b/task/thermal/Cargo.toml @@ -37,6 +37,7 @@ build-util = { path = "../../build/util" } [features] gimlet = ["drv-gimlet-seq-api", "h753"] sidecar = ["drv-sidecar-seq-api", "drv-transceivers-api", "h753"] +medusa = ["h753", "drv-transceivers-api"] h743 = ["build-i2c/h743"] h753 = ["build-i2c/h753"] h7b3 = ["build-i2c/h7b3"] diff --git a/task/thermal/src/bsp/medusa_a.rs b/task/thermal/src/bsp/medusa_a.rs new file mode 100644 index 0000000000..711017c81d --- /dev/null +++ b/task/thermal/src/bsp/medusa_a.rs @@ -0,0 +1,103 @@ +// 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/. + +//! BSP for Medusa + +use crate::control::{ + FanControl, Fans, InputChannel, PidConfig, TemperatureSensor, +}; +use task_sensor_api::SensorId; +use userlib::TaskId; + +include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); + +//////////////////////////////////////////////////////////////////////////////// +// Constants! + +// Air temperature sensors, which aren't used in the control loop +const NUM_TEMPERATURE_SENSORS: usize = 0; + +// Temperature inputs (I2C devices), which are used in the control loop. +pub const NUM_TEMPERATURE_INPUTS: usize = 0; + +// 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; + +// Number of individual fans - Medusa has none! +pub const NUM_FANS: usize = 0; + +// Run the PID loop on startup +pub const USE_CONTROLLER: bool = false; + +//////////////////////////////////////////////////////////////////////////////// + +bitflags::bitflags! { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub struct PowerBitmask: u32 {} +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SeqError {} + +#[allow(dead_code)] +pub(crate) struct Bsp { + pub inputs: &'static [InputChannel], + pub dynamic_inputs: &'static [SensorId], + + /// Monitored sensors + pub misc_sensors: &'static [TemperatureSensor], + + pub pid_config: PidConfig, +} + +impl Bsp { + pub fn fan_control( + &self, + _fan: crate::Fan, + ) -> crate::control::FanControl<'_> { + // Because we have zero fans, nothing should ever call fan_control. + unreachable!() + } + + pub fn for_each_fctrl(&self, mut _fctrl: impl FnMut(FanControl<'_>)) { + // This one's reeeeal easy. + } + + pub fn power_mode(&self) -> PowerBitmask { + PowerBitmask::empty() + } + + pub fn power_down(&self) -> Result<(), SeqError> { + Ok(()) + } + + pub fn get_fan_presence(&self) -> Result, SeqError> { + Ok(Fans::new()) + } + + pub fn new(_i2c_task: TaskId) -> Self { + Self { + // PID config doesn't matter since we have no fans. + pid_config: PidConfig { + zero: 0., + gain_p: 0., + gain_i: 0., + gain_d: 0., + }, + + inputs: &INPUTS, + dynamic_inputs: + &drv_transceivers_api::TRANSCEIVER_TEMPERATURE_SENSORS, + + // We monitor and log all of the air temperatures + misc_sensors: &MISC_SENSORS, + } + } +} + +const INPUTS: [InputChannel; NUM_TEMPERATURE_INPUTS] = []; + +const MISC_SENSORS: [TemperatureSensor; NUM_TEMPERATURE_SENSORS] = []; diff --git a/task/thermal/src/control.rs b/task/thermal/src/control.rs index 930a5eee1a..f33864f074 100644 --- a/task/thermal/src/control.rs +++ b/task/thermal/src/control.rs @@ -46,6 +46,7 @@ pub enum Device { /// The sensor includes a device type, used to decide how to read it; /// a free function that returns the raw `I2cDevice`, so that this can be /// `const`); and the sensor ID, to post data to the `sensors` task. +#[allow(dead_code)] // not all BSPS pub struct TemperatureSensor { device: Device, builder: fn(TaskId) -> drv_i2c_api::I2cDevice, @@ -53,6 +54,7 @@ pub struct TemperatureSensor { } impl TemperatureSensor { + #[allow(dead_code)] // not all BSPS pub const fn new( device: Device, builder: fn(TaskId) -> drv_i2c_api::I2cDevice, @@ -125,6 +127,7 @@ impl Fans<{ bsp::NUM_FANS }> { /// Enum representing any of our fan controller types, bound to one of their /// fans. This lets us handle heterogeneous fan controller ICs generically /// (although there's only one at the moment) +#[allow(dead_code)] // a typical BSP uses only _one_ of these pub enum FanControl<'a> { Max31790(&'a Max31790, drv_i2c_devices::max31790::Fan), } @@ -168,6 +171,7 @@ pub(crate) struct InputChannel { } #[derive(Copy, Clone, Eq, PartialEq)] +#[allow(dead_code)] // a typical BSP uses only a subset of these. pub(crate) enum ChannelType { /// `MustBePresent` is exactly what it says on the tin /// @@ -207,6 +211,7 @@ pub(crate) enum ChannelType { } impl InputChannel { + #[allow(dead_code)] // not all BSPS pub const fn new( sensor: TemperatureSensor, model: ThermalProperties, diff --git a/task/thermal/src/main.rs b/task/thermal/src/main.rs index 2ddf20ee4e..aa9e95810e 100644 --- a/task/thermal/src/main.rs +++ b/task/thermal/src/main.rs @@ -30,6 +30,7 @@ ), path = "bsp/sidecar_bcd.rs" )] +#[cfg_attr(any(target_board = "medusa-a"), path = "bsp/medusa_a.rs")] mod bsp; mod control;