From c7f0b3ed6214c313c49071680b431b3d2af29505 Mon Sep 17 00:00:00 2001 From: Aaron Hartwig Date: Thu, 13 Jun 2024 15:49:18 -0500 Subject: [PATCH] Adds a Medusa image (#1806) The primary purpose of this PR is to sketch out an initial stab at an image for Medusa, our QSFP front IO test fixture (details on that in RFD 405). The Medusa-specific logic lives in `medusa-seq-server`. The current goal is just to support hardware bringup, and thus that sequencer just attempts to power on the front IO board and then program its FPGAs. It also checks the status of the various power rails on Medusa and exposes an interface to control those rails (on Sidecar this functionality is handled by the FPGA). An adjacent piece of work here is that Medusa does not have any fans to control and that broke the previous mold of "everything with `thermal` has fans". `thermal` logs `sensor` data _and_ wants to control fan speeds off that data. So the Medusa thermal BSP looks a bit odd compared to the others. Additionally, the `transceivers` task had a `thermal-control` feature flag added to gate the interactions with `thermal` (Sidecar will have this feature set, Medusa will not). Lastly, there's some ugliness that I did for the sake of expediency given my personal situation. Until I can actually land #1805, Medusa simply copies some code from Sidecar. Once this PR lands, I will extend #1805 to cleanup Medusa as well. Closes #1571 --------- Co-authored-by: Cliff L. Biffle --- Cargo.lock | 58 ++ app/medusa/Cargo.toml | 27 + app/medusa/README.md | 5 + app/medusa/base.toml | 676 +++++++++++++++++++++ app/medusa/build.rs | 7 + app/medusa/model-a.toml | 3 + app/medusa/src/main.rs | 136 +++++ app/sidecar/base.toml | 2 +- build/xtask/src/flash.rs | 11 +- drv/fpga-server/src/main.rs | 3 +- drv/medusa-seq-api/Cargo.toml | 24 + drv/medusa-seq-api/build.rs | 11 + drv/medusa-seq-api/src/lib.rs | 48 ++ drv/medusa-seq-server/Cargo.toml | 43 ++ drv/medusa-seq-server/build.rs | 27 + drv/medusa-seq-server/src/front_io.rs | 111 ++++ drv/medusa-seq-server/src/main.rs | 357 +++++++++++ drv/medusa-seq-server/src/power_control.rs | 156 +++++ drv/sidecar-front-io/build.rs | 6 +- drv/sidecar-front-io/src/leds.rs | 24 +- drv/sidecar-seq-api/Cargo.toml | 2 +- drv/transceivers-server/Cargo.toml | 1 + drv/transceivers-server/src/main.rs | 41 +- idl/medusa-seq.idol | 60 ++ idl/sidecar-seq.idol | 2 +- task/monorail-server/Cargo.toml | 2 + task/monorail-server/src/bsp/medusa_a.rs | 528 ++++++++++++++++ task/monorail-server/src/main.rs | 1 + task/net/Cargo.toml | 2 + task/net/src/bsp/medusa_a.rs | 135 ++++ task/net/src/main.rs | 1 + task/thermal/Cargo.toml | 1 + task/thermal/src/bsp/medusa_a.rs | 103 ++++ task/thermal/src/control.rs | 5 + task/thermal/src/main.rs | 1 + 35 files changed, 2592 insertions(+), 28 deletions(-) create mode 100644 app/medusa/Cargo.toml create mode 100644 app/medusa/README.md create mode 100644 app/medusa/base.toml create mode 100644 app/medusa/build.rs create mode 100644 app/medusa/model-a.toml create mode 100644 app/medusa/src/main.rs create mode 100644 drv/medusa-seq-api/Cargo.toml create mode 100644 drv/medusa-seq-api/build.rs create mode 100644 drv/medusa-seq-api/src/lib.rs create mode 100644 drv/medusa-seq-server/Cargo.toml create mode 100644 drv/medusa-seq-server/build.rs create mode 100644 drv/medusa-seq-server/src/front_io.rs create mode 100644 drv/medusa-seq-server/src/main.rs create mode 100644 drv/medusa-seq-server/src/power_control.rs create mode 100644 idl/medusa-seq.idol create mode 100644 task/monorail-server/src/bsp/medusa_a.rs create mode 100644 task/net/src/bsp/medusa_a.rs create mode 100644 task/thermal/src/bsp/medusa_a.rs diff --git a/Cargo.lock b/Cargo.lock index d7c1111e8..fd3819824 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 000000000..2d680780b --- /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 000000000..26f29c256 --- /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 000000000..a5a83cc12 --- /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 000000000..280089593 --- /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 000000000..0531527cb --- /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 000000000..ca6260f18 --- /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 f5e25fac5..16ebb765d 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 49b47f57e..e067fe9f8 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 563d38d8a..1c28450d4 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 000000000..ccc0a311f --- /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 000000000..019542121 --- /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 000000000..25b7eda25 --- /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 000000000..eb4e4bc11 --- /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 000000000..2b1451d72 --- /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 000000000..7dd35d7f7 --- /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 000000000..dac64e342 --- /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 000000000..f778316a7 --- /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 98ba6465c..ce4996983 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 ffd73d1af..c00f84a30 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 c5329ad74..9fe990d81 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 e10c5c800..24a9c07aa 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 2618526a3..50fc5cf4c 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 000000000..c75b858e1 --- /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 3e037f520..2eeaed53d 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 827218c1b..7f0de74d7 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 000000000..73034b92c --- /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 17f3e8525..0add1f7e2 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 0f24272bd..9ee6fe93a 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 000000000..457da2a9d --- /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 130ba220c..a133787a7 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 931ad1058..cfdd05bdf 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 000000000..711017c81 --- /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 930a5eee1..f33864f07 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 2ddf20ee4..aa9e95810 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;