diff --git a/.github/workflows/build-boards.yml b/.github/workflows/build-boards.yml index f6f36b6305..5580f3a4c6 100644 --- a/.github/workflows/build-boards.yml +++ b/.github/workflows/build-boards.yml @@ -192,6 +192,33 @@ jobs: image: ${{ matrix.image }} os: ${{ inputs.os }} + build-cosmo: + if: ${{ inputs.board-set == 'cosmo' || inputs.board-set == 'all' }} + name: build-cosmo + strategy: + matrix: + build: [cosmo-a, cosmo-a-lab, cosmo-a-dev] + include: + - build: cosmo-a + app_name: cosmo-a + app_toml: app/cosmo/rev-a.toml + image: default + - build: cosmo-a-lab + app_name: cosmo-a-lab + app_toml: app/cosmo/rev-a-lab.toml + image: default + - build: cosmo-a-dev + app_name: cosmo-a-dev + app_toml: app/cosmo/rev-a-dev.toml + image: default + uses: ./.github/workflows/build-one.yml + with: + build: ${{ matrix.build }} + app_name: ${{ matrix.app_name }} + app_toml: ${{ matrix.app_toml }} + image: ${{ matrix.image }} + os: ${{ inputs.os }} + build-devboards: if: ${{ inputs.board-set == 'devboards' || inputs.board-set == 'all' }} name: build-devboards diff --git a/Cargo.lock b/Cargo.lock index ad380e2f0d..b0b45f0555 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -689,6 +689,20 @@ dependencies = [ "syn 1.0.94", ] +[[package]] +name = "cosmo" +version = "0.1.0" +dependencies = [ + "build-util", + "cfg-if", + "cortex-m", + "cortex-m-rt", + "drv-stm32h7-startup", + "kern", + "ringbuf", + "stm32h7", +] + [[package]] name = "counters" version = "0.1.0" @@ -1055,6 +1069,33 @@ dependencies = [ "zerocopy 0.6.6", ] +[[package]] +name = "drv-cosmo-seq-server" +version = "0.1.0" +dependencies = [ + "build-fpga-regmap", + "build-util", + "cfg-if", + "counters", + "drv-auxflash-api", + "drv-cpu-power-state", + "drv-cpu-seq-api", + "drv-hf-api", + "drv-ice40-spi-program", + "drv-packrat-vpd-loader", + "drv-spartan7-loader-api", + "drv-spi-api", + "drv-stm32xx-sys-api", + "gnarle", + "idol", + "idol-runtime", + "num-traits", + "ringbuf", + "task-jefe-api", + "userlib", + "zerocopy 0.6.6", +] + [[package]] name = "drv-cpu-power-state" version = "0.1.0" @@ -2163,7 +2204,6 @@ dependencies = [ "cfg-if", "cortex-m", "counters", - "drv-stm32xx-sys-api", "hubpack", "idol", "idol-runtime", diff --git a/app/cosmo/Cargo.toml b/app/cosmo/Cargo.toml new file mode 100644 index 0000000000..0114246070 --- /dev/null +++ b/app/cosmo/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +readme = "README.md" +name = "cosmo" +version = "0.1.0" + +[features] +traptrace = ["ringbuf"] +dump = ["kern/dump"] + +[dependencies] +cortex-m = { workspace = true } +cortex-m-rt ={ workspace = true } +cfg-if = { workspace = true } +stm32h7 = { workspace = true, features = ["rt", "stm32h753"] } +ringbuf = { path = "../../lib/ringbuf", optional = true } + +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 = "cosmo" +test = false +bench = false + +[lints] +workspace = true diff --git a/app/cosmo/README.md b/app/cosmo/README.md new file mode 100644 index 0000000000..373af79173 --- /dev/null +++ b/app/cosmo/README.md @@ -0,0 +1,8 @@ +# Cosmo Service Processor (SP) firmware + +The Cosmo is the SP5-based compute sled in the Oxide rack. + +This folder contains the firmware that runs on its service processor (SP). + +The Root of Trust firmware is common across multiple boards and can be found +in the [`oxide-rot-1` subfolder](../oxide-rot-1). diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml new file mode 100644 index 0000000000..8049df5dc2 --- /dev/null +++ b/app/cosmo/base.toml @@ -0,0 +1,1383 @@ +target = "thumbv7em-none-eabihf" +chip = "../../chips/stm32h7" +memory = "memory-large.toml" +stacksize = 896 +fwid = true + +[kernel] +name = "cosmo" +requires = {flash = 32768, ram = 8192} +features = ["dump"] + +[mmio] +peripheral-region = "fmc_nor_psram_bank_1" +register-map = "../../drv/spartan7-loader/cosmo-seq/cosmo_seq_top.json" + +[caboose] +tasks = ["control_plane_agent"] +region = "flash" +size = 256 +default = true + +[tasks.jefe] +name = "task-jefe" +priority = 0 +max-sizes = {flash = 16384, ram = 2048} +start = true +features = ["dump"] +stacksize = 1536 +notifications = ["fault", "timer"] +extern-regions = ["sram1", "sram2", "sram3", "sram4"] + +[tasks.jefe.config.on-state-change] +net = "jefe-state-change" +host_sp_comms = "jefe-state-change" + +[tasks.jefe.config.allowed-callers] +set_state = ["cosmo_seq"] +set_reset_reason = ["sys"] +request_reset = ["hiffy", "control_plane_agent"] + +[tasks.net] +name = "task-net" +stacksize = 8000 +priority = 5 +features = ["mgmt", "h753", "cosmo", "vlan", "vpd-mac"] +max-sizes = {flash = 131072, ram = 65536, sram1_mac = 16384} +sections = {eth_bulk = "sram1_mac"} +uses = ["eth", "tim16"] +start = true +interrupts = {"eth.irq" = "eth-irq", "tim16.irq" = "mdio-timer-irq"} +task-slots = ["sys", "packrat", { spi_driver = "spi2_driver" }, "jefe"] +notifications = ["eth-irq", "mdio-timer-irq", "wake-timer", "jefe-state-change"] + +[tasks.sys] +name = "drv-stm32xx-sys" +features = ["h753", "exti", "no-panic"] +priority = 1 +uses = ["rcc", "gpios", "system_flash", "syscfg", "exti"] +start = true +task-slots = ["jefe"] +notifications = ["exti-wildcard-irq"] + +[tasks.sys.interrupts] +"exti.exti0" = "exti-wildcard-irq" +"exti.exti1" = "exti-wildcard-irq" +"exti.exti2" = "exti-wildcard-irq" +"exti.exti3" = "exti-wildcard-irq" +"exti.exti4" = "exti-wildcard-irq" +"exti.exti9_5" = "exti-wildcard-irq" +"exti.exti15_10" = "exti-wildcard-irq" + +[tasks.sys.config.gpio-irqs.rot_irq] +port = "F" +pin = 2 +owner = {name = "sprot", notification = "rot_irq"} + +[tasks.spi2_driver] +name = "drv-stm32h7-spi-server" +priority = 3 +max-sizes = {flash = 16384, ram = 4096} +features = ["spi2", "h753"] +uses = ["spi2"] +start = true +interrupts = {"spi2.irq" = "spi-irq"} +stacksize = 872 +task-slots = ["sys"] +notifications = ["spi-irq"] + +# XXX this is only used by cosmo_seq; could we merge it? +[tasks.spi3_driver] +name = "drv-stm32h7-spi-server" +priority = 3 +max-sizes = {flash = 16384, ram = 4096} +features = ["spi3", "h753"] +uses = ["spi3"] +start = true +interrupts = {"spi3.irq" = "spi-irq"} +stacksize = 872 +task-slots = ["sys"] +notifications = ["spi-irq"] + +[tasks.i2c_driver] +name = "drv-stm32xx-i2c-server" +stacksize = 1048 +features = ["h753"] +priority = 3 +uses = ["i2c1", "i2c2", "i2c3", "i2c4"] +start = true +task-slots = ["sys"] +notifications = ["i2c1-irq", "i2c2-irq", "i2c3-irq", "i2c4-irq"] + +[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.packrat] +name = "task-packrat" +priority = 1 +start = true +# task-slots is explicitly empty: packrat should not send IPCs! +task-slots = [] +features = ["cosmo"] + +[tasks.thermal] +name = "task-thermal" +features = ["cosmo"] +priority = 8 +max-sizes = {flash = 32768, ram = 8192 } +stacksize = 6000 +start = true +task-slots = ["i2c_driver", "sensor", "cosmo_seq", "jefe"] +notifications = ["timer"] + +[tasks.power] +name = "task-power" +features = ["cosmo"] +priority = 8 +max-sizes = {flash = 65536, ram = 16384 } +stacksize = 3800 +start = true +task-slots = ["i2c_driver", "sensor", "cosmo_seq"] +notifications = ["timer"] + +[tasks.hiffy] +name = "task-hiffy" +features = ["h753", "stm32h7", "i2c", "gpio", "spi", "qspi", "hash", "sprot"] +priority = 7 +max-sizes = {flash = 32768, ram = 32768 } +stacksize = 1200 +start = true +task-slots = ["sys", "hf", "i2c_driver", "hash_driver", "update_server", "sprot"] + +[tasks.cosmo_seq] +name = "drv-cosmo-seq-server" +features = ["h753"] +priority = 7 +max-sizes = {flash = 131072, ram = 16384 } +stacksize = 2600 +start = true +task-slots = ["sys", "i2c_driver", {spi_front = "spi3_driver"}, "jefe", "packrat", "auxflash", "spartan7_loader", "hf"] +uses = ["mmio_sequencer", "mmio_info"] +notifications = ["timer", "vcore"] + +[tasks.spartan7_loader] +name = "drv-spartan7-loader" +features = ["h753"] +priority = 4 +max-sizes = {flash = 131072, ram = 16384 } +stacksize = 2600 +start = true +task-slots = ["sys", {spi = "spi2_driver"}, "auxflash"] + +[tasks.spartan7_loader.config] +program_l = "sys_api::Port::B.pin(1)" +init_l = "sys_api::Port::B.pin(6)" +config_done = "sys_api::Port::B.pin(4)" +user_reset_l = "sys_api::Port::A.pin(6)" + +[tasks.cosmo_seq.config] +fpga_image = "cosmo-a.bin" +register_defs = "cosmo-regs-a.json" + +[tasks.hash_driver] +name = "drv-stm32h7-hash-server" +features = ["h753"] +priority = 2 +max-sizes = {flash = 16384, ram=4096 } +stacksize = 2048 +start = true +uses = ["hash"] +interrupts = {"hash.irq" = "hash-irq"} +task-slots = ["sys"] +notifications = ["hash-irq"] + +[tasks.hf] +name = "drv-cosmo-hf" +priority = 6 +start = true +uses = ["mmio_spi_nor"] +task-slots = ["hash_driver", "spartan7_loader"] +stacksize = 4000 + +[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"] +interrupts = {"flash_controller.irq" = "flash-irq"} +notifications = ["flash-irq"] + +[tasks.sensor] +name = "task-sensor" +priority = 4 +max-sizes = {flash = 16384, ram = 8192 } +stacksize = 1024 +start = true + +[tasks.host_sp_comms] +name = "task-host-sp-comms" +features = ["stm32h753", "usart6", "baud_rate_3M", "hardware_flow_control", "vlan", "cosmo"] +uses = ["usart6", "dbgmcu"] +interrupts = {"usart6.irq" = "usart-irq"} +priority = 9 +max-sizes = {flash = 65536, ram = 65536} +stacksize = 5080 +start = true +task-slots = ["sys", { cpu_seq = "cosmo_seq" }, "hf", "control_plane_agent", "net", "packrat", "i2c_driver", { spi_driver = "spi2_driver" }, "sprot"] +notifications = ["jefe-state-change", "usart-irq", "multitimer", "control-plane-agent"] + +[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 = 2048 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + +[tasks.control_plane_agent] +name = "task-control-plane-agent" +priority = 8 +max-sizes = {flash = 131072, ram = 32768} +# This can probably overkill and can be tuned later +stacksize = 6000 +start = true +uses = ["usart1"] +task-slots = [ + "jefe", + "dump_agent", + "net", + "update_server", + "sys", + "hf", + { cpu_seq = "cosmo_seq" }, + "validate", + "sensor", + "sprot", + "i2c_driver", + "packrat", + "user_leds", + "vpd", +] +features = [ + "cosmo", + "usart1", + "vlan", + "baud_rate_3M", + "vpd", +] +notifications = ["usart-irq", "socket", "timer"] +interrupts = {"usart1.irq" = "usart-irq"} + +[tasks.sprot] +name = "drv-stm32h7-sprot-server" +priority = 4 +max-sizes = {flash = 65536, ram = 32768} +stacksize = 16384 +start = true +task-slots = ["sys"] +features = ["sink_test", "use-spi-core", "h753", "spi5"] +uses = ["spi5"] +notifications = ["spi-irq", "rot-irq", "timer"] +interrupts = {"spi5.irq" = "spi-irq"} + +[tasks.validate] +name = "task-validate" +priority = 5 +max-sizes = {flash = 16384, ram = 4096 } +stacksize = 1000 +start = true +task-slots = ["i2c_driver"] + +[tasks.vpd] +name = "task-vpd" +priority = 4 +max-sizes = {flash = 8192, ram = 1024} +start = true +task-slots = ["sys", "i2c_driver"] +stacksize = 800 + +[tasks.user_leds] +name = "drv-user-leds" +features = ["stm32h7"] +priority = 2 +max-sizes = {flash = 2048, ram = 1024} +start = true +task-slots = ["sys"] +notifications = ["timer"] + +[tasks.dump_agent] +name = "task-dump-agent" +priority = 6 +max-sizes = {flash = 32768, ram = 16384 } +start = true +task-slots = ["sprot", "jefe", "net"] +stacksize = 2400 +extern-regions = ["sram1", "sram2", "sram3", "sram4"] +notifications = ["socket"] +features = ["net", "vlan"] + +[tasks.sbrmi] +name = "drv-sbrmi" +priority = 4 +max-sizes = {flash = 8192, ram = 2048 } +start = true +task-slots = ["i2c_driver"] +stacksize = 800 + +[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.idle] +name = "task-idle" +priority = 15 +max-sizes = {flash = 128, ram = 256} +stacksize = 256 +start = true + +[config] + +# +# I2C1: Front FPGA I2C mux +# +[[config.i2c.controllers]] +controller = 1 + +# +# SMBUS_SP_TO_FPGA2_SMCLK +# SMBUS_SP_TO_FPGA2_SMDAT +# +[config.i2c.controllers.ports.B] +name = "front" +description = "Front FPGA mux" +scl.pin = 8 +sda.pin = 9 +af = 4 + +# The front FPGA has five virtual muxes, which pretend to be PCA9545s. +# We only use the lower 3 channels on each mux (which is normally 4-channel) +[[config.i2c.controllers.ports.B.muxes]] +driver = "pca9545" +address = 0x70 + +[[config.i2c.controllers.ports.B.muxes]] +driver = "pca9545" +address = 0x71 + +[[config.i2c.controllers.ports.B.muxes]] +driver = "pca9545" +address = 0x72 + +[[config.i2c.controllers.ports.B.muxes]] +driver = "pca9545" +address = 0x73 + +[[config.i2c.controllers.ports.B.muxes]] +driver = "pca9545" +address = 0x74 + +# +# I2C2: Main FPGA I2C mux +# +[[config.i2c.controllers]] +controller = 2 + +# +# I2C_SP_TO_FPGA1_SCL +# I2C_SP_TO_FPGA1_SDA +# +[config.i2c.controllers.ports.F] +name = "main" +description = "Main FPGA mux" +scl.pin = 1 +sda.pin = 0 +af = 4 + +# The main FPGA has two virtual muxes, which pretend to be PCA9545s. +[[config.i2c.controllers.ports.F.muxes]] +driver = "pca9545" +address = 0x70 + +[[config.i2c.controllers.ports.F.muxes]] +driver = "pca9545" +address = 0x71 + +[[config.i2c.controllers.ports.F.muxes]] +driver = "pca9545" +address = 0x72 + +# +# I2C3: Mid bus +# +[[config.i2c.controllers]] +controller = 3 + +# +# SMBUS_SP_TO_MID_SMCLK_A2 +# SMBUS_SP_TO_MID_SMDAT_A2 +# +[config.i2c.controllers.ports.H] +name = "mid" +description = "Mid bus" +scl.pin = 7 +sda.pin = 8 +af = 4 + +# +# I2C4: Rear bus +# +[[config.i2c.controllers]] +controller = 4 + +# +# SMBUS_SP_TO_REAR_SMCLK_A2 +# SMBUS_SP_TO_REAR_SMDAT_A2 +# +[config.i2c.controllers.ports.F] +name = "rear" +description = "Rear bus" +scl.pin = 14 +sda.pin = 15 +af = 4 + +################################################################################ + +# The `front` bus is managed by FPGA2, which impersonates a set of muxes +# The connection between SP and FPGA is SMBUS_SP_TO_FPGA2 +[[config.i2c.devices]] +bus = "front" +address = 0x70 +device = "pca9545" +description = "Front FPGA virtual mux 1" +refdes = "U31_1" + +[[config.i2c.devices]] +bus = "front" +address = 0x71 +device = "pca9545" +description = "Front FPGA virtual mux 2" +refdes = "U31_2" + +[[config.i2c.devices]] +bus = "front" +address = 0x72 +device = "pca9545" +description = "Front FPGA virtual mux 3" +refdes = "U31_3" + +[[config.i2c.devices]] +bus = "front" +address = 0x73 +device = "pca9545" +description = "Front FPGA virtual mux 4" +refdes = "U31_4" + +[[config.i2c.devices]] +bus = "front" +address = 0x74 +device = "pca9545" +description = "Front FPGA virtual mux 5" +refdes = "U31_5" + +# Welcome to the sharkfin zone +# SMBUS_FPGA2_TO_CEMA +[[config.i2c.devices]] +bus = "front" +mux = 1 +segment = 1 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin A VPD" +name = "sharkfin_a_vpd" +refdes = "J200" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 1 +segment = 1 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin A hot swap controller" +power = { rails = [ "V12_U2A_A0", "V3P3_U2A_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_a_hsc" +refdes = "J200" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 1 +segment = 1 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 A NVMe Basic Management Command" +sensors = { temperature = 1 } +name = "U2_N0" +refdes = "J200" +removable = true + +# SMBUS_FPGA2_TO_CEMB +[[config.i2c.devices]] +bus = "front" +mux = 1 +segment = 2 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin B VPD" +name = "sharkfin_b_vpd" +refdes = "J201" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 1 +segment = 2 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin B hot swap controller" +power = { rails = [ "V12_U2B_A0", "V3P3_U2B_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_b_hsc" +refdes = "J201" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 1 +segment = 2 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 B NVMe Basic Management Control" +sensors = { temperature = 1 } +name = "U2_N1" +refdes = "J201" +removable = true + +# SMBUS_FPGA2_TO_CEMC +[[config.i2c.devices]] +bus = "front" +mux = 1 +segment = 3 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin C VPD" +name = "sharkfin_c_vpd" +refdes = "J202" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 1 +segment = 3 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin C hot swap controller" +power = { rails = [ "V12_U2C_A0", "V3P3_U2C_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_c_hsc" +refdes = "J202" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 1 +segment = 3 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 C NVMe Basic Management Control" +sensors = { temperature = 1 } +name = "U2_N2" +refdes = "J202" +removable = true + +# SMBUS_FPGA2_TO_CEMD +[[config.i2c.devices]] +bus = "front" +mux = 2 +segment = 1 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin D VPD" +name = "sharkfin_d_vpd" +refdes = "J203" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 2 +segment = 1 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin D hot swap controller" +power = { rails = [ "V12_U2D_A0", "V3P3_U2D_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_d_hsc" +refdes = "J203" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 2 +segment = 1 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 D NVMe Basic Management Control" +sensors = { temperature = 1 } +name = "U2_N3" +refdes = "J203" +removable = true + +# SMBUS_FPGA2_TO_CEME +[[config.i2c.devices]] +bus = "front" +mux = 2 +segment = 2 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin E VPD" +name = "sharkfin_e_vpd" +refdes = "J204" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 2 +segment = 2 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin E hot swap controller" +power = { rails = [ "V12_U2E_A0", "V3P3_U2E_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_e_hsc" +refdes = "J204" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 2 +segment = 2 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 E NVMe Basic Management Control" +sensors = { temperature = 1 } +name = "U2_N4" +refdes = "J204" +removable = true + +# SMBUS_FPGA2_TO_CEMF +[[config.i2c.devices]] +bus = "front" +mux = 2 +segment = 3 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin F VPD" +name = "sharkfin_f_vpd" +refdes = "J205" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 2 +segment = 3 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin F hot swap controller" +power = { rails = [ "V12_U2F_A0", "V3P3_U2F_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_f_hsc" +refdes = "J205" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 2 +segment = 3 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 F NVMe Basic Management Control" +sensors = { temperature = 1 } +name = "U2_N5" +refdes = "J205" +removable = true + + +# SMBUS_FPGA2_TO_CEMG +[[config.i2c.devices]] +bus = "front" +mux = 3 +segment = 3 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin G VPD" +name = "sharkfin_g_vpd" +refdes = "J206" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 3 +segment = 3 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin G hot swap controller" +power = { rails = [ "V12_U2G_A0", "V3P3_U2G_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_g_hsc" +refdes = "J206" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 3 +segment = 3 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 G NVMe Basic Management Control" +sensors = { temperature = 1 } +name = "U2_N6" +refdes = "J206" +removable = true + +# Note that we skip the remaining two slots on (virtual) mux 3, which are +# I2C_FPGA2_TO_MCIO1 and I2C_FPGA2_TO_MCIO2 + + +# SMBUS_FPGA2_TO_CEMH +[[config.i2c.devices]] +bus = "front" +mux = 4 +segment = 1 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin H VPD" +name = "sharkfin_h_vpd" +refdes = "J207" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 4 +segment = 1 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin H hot swap controller" +power = { rails = [ "V12_U2H_A0", "V3P3_U2H_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_h_hsc" +refdes = "J207" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 4 +segment = 1 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 H NVMe Basic Management Control" +sensors = { temperature = 1 } +name = "U2_N7" +refdes = "J207" +removable = true + +# SMBUS_FPGA2_TO_CEMI +[[config.i2c.devices]] +bus = "front" +mux = 4 +segment = 2 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin I VPD" +name = "sharkfin_i_vpd" +refdes = "J208" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 4 +segment = 2 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin I hot swap controller" +power = { rails = [ "V12_U2I_A0", "V3P3_U2I_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_i_hsc" +refdes = "J208" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 4 +segment = 2 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 I NVMe Basic Management Control" +sensors = { temperature = 1 } +name = "U2_N8" +refdes = "J208" +removable = true + +# SMBUS_FPGA2_TO_CEMJ +[[config.i2c.devices]] +bus = "front" +mux = 4 +segment = 3 +address = 0b1010_000 +device = "at24csw080" +description = "U.2 Sharkfin J VPD" +name = "sharkfin_j_vpd" +refdes = "J209" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 4 +segment = 3 +address = 0b0111_000 +device = "max5970" +description = "U.2 Sharkfin J hot swap controller" +power = { rails = [ "V12_U2J_A0", "V3P3_U2J_A0" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +name = "sharkfin_j_hsc" +refdes = "J209" +removable = true + +[[config.i2c.devices]] +bus = "front" +mux = 4 +segment = 3 +address = 0b110_1010 +device = "nvme_bmc" +description = "U.2 J NVMe Basic Management Control" +sensors = { temperature = 1 } +name = "U2_N9" +refdes = "J209" +removable = true +# you are now leaving the sharkfin zone + +# SMBUS_FPGA2_TO_FRONT +[[config.i2c.devices]] +bus = "front" +mux = 5 +segment = 1 +address = 0x48 +device = "tmp117" +name = "Southwest" +description = "Southwest temperature sensor" +sensors = { temperature = 1 } +removable = true +refdes = "J44" + +[[config.i2c.devices]] +bus = "front" +mux = 5 +segment = 1 +address = 0x49 +device = "tmp117" +name = "South" +description = "South temperature sensor" +sensors = { temperature = 1 } +removable = true +refdes = "J45" + +[[config.i2c.devices]] +bus = "front" +mux = 5 +segment = 1 +address = 0x4a +device = "tmp117" +name = "Southeast" +description = "Southeast temperature sensor" +sensors = { temperature = 1 } +removable = true +refdes = "J46" + +################################################################################ + +# The `main` bus is managed by FPGA1, which impersonates a set of muxes +# The connection between SP and FPGA is I2C_SP_TO_FPGA1 +[[config.i2c.devices]] +bus = "main" +address = 0x70 +device = "pca9545" +description = "Main FPGA virtual mux 1" +refdes = "U27_1" + +[[config.i2c.devices]] +bus = "main" +address = 0x71 +device = "pca9545" +description = "Main FPGA virtual mux 2" +refdes = "U27_2" + +[[config.i2c.devices]] +bus = "main" +address = 0x72 +device = "pca9545" +description = "Main FPGA virtual mux 3" +refdes = "U27_3" + +# SMBUS_FPGA1_TO_M2A +[[config.i2c.devices]] +bus = "main" +mux = 1 +segment = 1 +address = 0b110_1010 +device = "nvme_bmc" +description = "M.2 A NVMe Basic Management Command" +name = "M2_A" +sensors = { temperature = 1 } +removable = true + +# SMBUS_FPGA1_TO_M2B +[[config.i2c.devices]] +bus = "main" +mux = 1 +segment = 2 +address = 0b110_1010 +device = "nvme_bmc" +description = "M.2 B NVMe Basic Management Command" +name = "M2_B" +sensors = { temperature = 1 } +removable = true + +[[config.i2c.devices]] +bus = "main" +mux = 2 +segment = 1 +address = 0x3c +device = "sbrmi" +name = "RMI" +description = "CPU via SB-RMI" + +[[config.i2c.devices]] +bus = "main" +mux = 2 +segment = 1 +address = 0x4c +device = "sbtsi" +name = "CPU" +description = "CPU temperature sensor" +sensors = { temperature = 1 } + +# Mux 2, segment 2 is SEC_SP5_TO_FPGA1 + +# I2C_FPGA1_TO_FAN_VPD +[[config.i2c.devices]] +bus = "main" +mux = 3 +segment = 1 +address = 0b1010_000 +device = "at24csw080" +description = "Fan VPD" +refdes = "J34" +name = "fan_vpd" +removable = true + +# SMBUS_FPGA1_TO_NIC_THERM +[[config.i2c.devices]] +bus = "main" +mux = 3 +segment = 2 +address = 0x4c +device = "tmp451" +name = "t6" +sensors = { temperature = 1 } +description = "T6 temperature sensor" +refdes = "U53" + +# XXX DIMM temperature sensors are on this bus, proxied by the FPGA + +################################################################################ +# mid bus, on SMBUS_SP_TO_MID_SMCLK_A2 and SMBUS_SP_TO_MID_SMDAT_A2 +[[config.i2c.devices]] +bus = "mid" +address = 0x24 +device = "tps546b24a" +description = "A2 3.3V rail" +power = { rails = [ "V3P3_SP_A2" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U82" +name = "v3p3_sp_a2" + +[[config.i2c.devices]] +bus = "mid" +address = 0x27 +device = "tps546b24a" +description = "A2 5V rail" +power = { rails = [ "V5_SYS_A2" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U83" +name = "v5p0_sys_a2" + +[[config.i2c.devices]] +bus = "mid" +address = 0x29 +device = "tps546b24a" +description = "A2 1.8V rail" +power = { rails = [ "V1P8_SYS_A2" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U81" +name = "v1p8_sys_a2" + +[[config.i2c.devices]] +bus = "mid" +address = 0x3a +device = "max5970" +name = "m2" +description = "M.2 hot plug controller" +power = { rails = [ "V3P3_M2A_A0HP", "V3P3_M2B_A0HP" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +refdes = "U15" + +[[config.i2c.devices]] +bus = "mid" +address = 0x54 +device = "ltc4282" +name = "mcio" +description = "12V MCIO hot plug controller" +power = { rails = ["V12_MCIO_A0HP"], pmbus = false } +sensors = { voltage = 1, current = 1 } +refdes = "U16" + +[[config.i2c.devices]] +bus = "mid" +address = 0x56 +device = "ltc4282" +name = "dimm_hsc_ghijkl" +description = "DIMM GHIJKL hot plug controller" +power = { rails = ["V12_DDR5_GHIJKL_A0"], pmbus = false } +sensors = { voltage = 1, current = 1 } +refdes = "U42" + +[[config.i2c.devices]] +bus = "mid" +address = 0x55 +device = "ltc4282" +name = "dimm_hsc_abcdef" +description = "DIMM ABCDEF hot plug controller" +power = { rails = ["V12_DDR5_ABCDEF_A0"], pmbus = false } +sensors = { voltage = 1, current = 1 } +refdes = "U127" + +[[config.i2c.devices]] +bus = "mid" +address = 0x75 +device = "raa229620a" +description = "South power controller (Core 0, SOC)" +power.rails = [ "VDDCR_CPU0_A0", "VDDCR_SOC_A0" ] +power.phases = [ [ 0, 1, 2, 3, 4, 5, 6, 7 ], [ 8, 9, 10, 11 ] ] +sensors = { temperature = 2, power = 2, voltage = 2, current = 2 } +refdes = "U90" + +[[config.i2c.devices]] +bus = "mid" +address = 0x76 +device = "raa229620a" +description = "North power controller (Core 1, VDDIO)" +power.rails = [ "VDDCR_CPU1_A0", "VDDIO_SP5_A0" ] +power.phases = [ [ 0, 1, 2, 3, 4, 5, 6, 7 ], [ 8, 9, 10, 11 ] ] +sensors = { temperature = 2, power = 2, voltage = 2, current = 2 } +refdes = "U103" + +[[config.i2c.devices]] +bus = "mid" +address = 0x5c +device = "isl68224" +description = "SP5 power controller (V1P1, V1P8, V3P3)" +power.rails = [ "V1P1_SP5_A0", "V1P8_SP5_A1", "V3P3_SP5_A1" ] +power.phases = [ [ 0, 1, 2 ], [ 3 ], [ 4 ] ] +sensors = { voltage = 3, current = 3 } # XXX add temperature sensors? +refdes = "U116" + +################################################################################ + +[[config.i2c.devices]] +bus = "rear" +address = 0x39 +device = "max5970" +description = "NIC hot swap" +power = { rails = [ "V12P0_NIC_A0HP", "V5P0_NIC_A0HP" ], pmbus = false } +sensors = { voltage = 2, current = 2 } +refdes = "U54" + +[[config.i2c.devices]] +bus = "rear" +address = 0x25 +device = "tps546b24a" +description = "T6 power controller" +power = { rails = [ "V0P96_NIC_VDD_A0HP" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U123" +name = "v0p96_nic" + +[[config.i2c.devices]] +bus = "rear" +address = 0x48 +device = "tmp117" +name = "Northwest" +description = "Northwest temperature sensor" +sensors = { temperature = 1 } +removable = true +refdes = "J47" + +[[config.i2c.devices]] +bus = "rear" +address = 0x49 +device = "tmp117" +name = "North" +description = "North temperature sensor" +sensors = { temperature = 1 } +removable = true +refdes = "J48" + +[[config.i2c.devices]] +bus = "rear" +address = 0x4a +device = "tmp117" +name = "Northeast" +description = "Northeast temperature sensor" +sensors = { temperature = 1 } +removable = true +refdes = "J49" + +[[config.i2c.devices]] +bus = "rear" +address = 0x20 +device = "max31790" +description = "Fan controller" +sensors = { speed = 6, names = [ + "Southeast", "Northeast", "South", "North", "Southwest", "Northwest" +] } +refdes = "U58" + +[[config.i2c.devices]] +bus = "rear" +address = 0x67 +device = "bmr491" +name = "IBC" +description = "Intermediate bus converter" +power = { rails = [ "V12_SYS_A2" ] } +sensors = { temperature = 1, power = 1, voltage = 1, current = 1 } +refdes = "U80" + +[[config.i2c.devices]] +bus = "rear" +address = 0b1010_000 +device = "at24csw080" +name = "local_vpd" +description = "Cosmo VPD" +refdes = "U32" + +# The `rear` bus also spans the isolation barrier to the 54V zone. On the other +# side of the isolation barrier, data is split between a shared SDAI line +# (SMBUS_SP_TO_REAR_SMDAT_ISO) and per-HSC SDAO lines. + +# SMBUS_SP_TO_REAR_SMDAT_ISO +# SMBUS_FAN_EAST_HSC_TO_SP_SMDAT_ISO +[[config.i2c.devices]] +bus = "rear" +address = 0x11 +device = "lm5066" +description = "Fan hot swap controller (east)" +power = { rails = [ "V54P5_FAN_EAST" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U71" + +# SMBUS_SP_TO_REAR_SMDAT_ISO +# SMBUS_FAN_CENTRAL_HSC_TO_SP_SMDAT_ISO +[[config.i2c.devices]] +bus = "rear" +address = 0x12 +device = "lm5066" +description = "Fan hot swap controller (central)" +power = { rails = [ "V54P5_FAN_CENTRAL" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U72" + +# SMBUS_SP_TO_REAR_SMDAT_ISO +# SMBUS_FAN_WEST_HSC_TO_SP_SMDAT_ISO +[[config.i2c.devices]] +bus = "rear" +address = 0x13 +device = "lm5066" +description = "Fan hot swap controller (west)" +power = { rails = [ "V54P5_FAN_WEST" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U73" + +# SMBUS_SP_TO_REAR_SMDAT_ISO +# SMBUS_MAIN_HSC_TO_SP_SMDAT_ISO +[[config.i2c.devices]] +bus = "rear" +address = 0x14 +device = "adm1272" +description = "Sled hot swap controller" +power = { rails = [ "V54P5_IBC_A3" ] } +sensors = { temperature = 1, voltage = 1, current = 1 } +refdes = "U79" + +################################################################################ +[config.spi.spi2] +controller = 2 + +# SP_TO_FPGA1_CFG +# SP_TO_FPGA1_DAT +[config.spi.spi2.mux_options.port_b] +outputs = [ + {port = "B", pins = [13, 15], af = 5}, +] +input = {port = "B", pin = 14, af = 5} + +[config.spi.spi2.devices.spartan7_fpga] +mux = "port_b" +cs = [] +# no CS pin; we're using the SPI peripheral to send synchronized CLK + DATA + +# SPI_SP_TO_KSZ8463_SCK +# SPI_SP_TO_KSZ8463_DAT +# SPI_KSZ8463_TO_SP_DAT +[config.spi.spi2.mux_options.port_i] +outputs = [ + {port = "I", pins = [1, 3], af = 5}, +] +input = {port = "I", pin = 2, af = 5} + +[config.spi.spi2.devices.ksz8463] +mux = "port_i" +cs = [{port = "I", pin = 0}] # SPI_SP_TO_KSZ8463_CS_L + +################################################################################ +# SPI3 goes through a mux controlled by the main FPGA (FPGA1), and can talk to +# either the front FPGA (FPGA2) or the Ignition flash chip +[config.spi.spi3] +controller = 3 + +# SPI_SP_TO_SP_MUX_SCK (PC10) +# SPI_SP_TO_SP_MUX_DAT (PB5) +# SPI_SP_MUX_TO_SP_DAT (PC11) +[config.spi.spi3.mux_options.port_c] +outputs = [ + { port = "C", pins = [10], af = 6}, + { port = "B", pins = [5], af = 7}, +] +input = {port = "C", pin = 11, af = 6} + +[config.spi.spi3.devices.mux] +mux = "port_c" +cs = [{port = "A", pin = 15}] # SPI_SP_TO_SP_MUX_CS_L + +################################################################################ + +[config.spi.spi5] +controller = 5 + +# SPI_SP_TO_ROT_DAT (PJ10) +# SPI_ROT_TO_SP_DAT (PJ11) +# SPI_SP_TO_ROT_SCK (PK0) +[config.spi.spi5.mux_options.port_j] +outputs = [ + {port = "J", pins = [10], af = 5}, + {port = "K", pins = [0], af = 5}, +] +input = {port = "J", pin = 11, af = 5} + +# SPI_SP_TO_ROT_CL_L (PK1) +[config.spi.spi5.devices.rot] +mux = "port_j" +cs = [{port = "K", pin = 1}] +clock_divider = "DIV256" + +################################################################################ + +# VLAN configuration +[config.net.vlans.sidecar1] +vid = 0x301 +trusted = true +port = 1 + +[config.net.vlans.sidecar2] +vid = 0x302 +trusted = true +port = 2 + +# UDP ports in sockets below are assigned in oxidecomputer/oana + +[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.control_plane_agent] +kind = "udp" +owner = {name = "control_plane_agent", notification = "socket"} +port = 11111 +tx = { packets = 3, bytes = 2048 } +rx = { packets = 3, bytes = 2048 } + +[config.net.sockets.dump_agent] +kind = "udp" +owner = {name = "dump_agent", notification = "socket"} +port = 11113 +tx = { packets = 3, bytes = 1024 } +rx = { packets = 3, bytes = 1024 } + +[config.sprot] +# ROT_IRQ (af=0 for GPIO, af=15 when EXTI is implemneted) +rot_irq = { port = "F", pin = 2, af = 0} # XXX can we use EXTI now? + +[config.auxflash] +memory-size = 33_554_432 # 256 Mib / 32 MiB +slot-count = 16 # 2 MiB slots + +[[auxflash.blobs]] +file = "drv/spartan7-loader/cosmo-seq/cosmo_seq.bz2" +unzip = "bz2" +compress = true +tag = "SPA7" + +[[auxflash.blobs]] +file = "drv/cosmo-seq-server/cosmo-hp/cosmo_hp.bz2" +unzip = "bz2" +compress = true +tag = "ICE4" diff --git a/app/cosmo/build.rs b/app/cosmo/build.rs new file mode 100644 index 0000000000..7e465bc6b9 --- /dev/null +++ b/app/cosmo/build.rs @@ -0,0 +1,8 @@ +// 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(); + build_util::expose_m_profile().unwrap(); +} diff --git a/app/cosmo/dev.toml b/app/cosmo/dev.toml new file mode 100644 index 0000000000..70ab20e45b --- /dev/null +++ b/app/cosmo/dev.toml @@ -0,0 +1,37 @@ +# Configuration fragment for -dev images + +[tasks.jefe.config.allowed-callers] +request_reset = ["udprpc"] + +[tasks.udprpc] +name = "task-udprpc" +priority = 6 +max-sizes = {flash = 32768, ram = 8192} +stacksize = 4096 +start = true +task-slots = ["net"] +features = ["vlan"] +notifications = ["socket"] + +[tasks.fmc_demo] +name = "drv-stm32h7-fmc-demo-server" +features = ["h753"] +priority = 6 +start = true +task-slots = ["sys", "net"] +uses = ["fmc_nor_psram_bank_1"] # yolo +notifications = ["socket"] + +[config.net.sockets.rpc] +kind = "udp" +owner = {name = "udprpc", notification = "socket"} +port = 998 +tx = { packets = 3, bytes = 1024 } +rx = { packets = 3, bytes = 1024 } + +[config.net.sockets.fmc_test] +kind = "udp" +owner = {name = "fmc_demo", notification = "socket"} +port = 11114 +tx = { packets = 3, bytes = 4096 } +rx = { packets = 3, bytes = 4096 } diff --git a/app/cosmo/lab.toml b/app/cosmo/lab.toml new file mode 100644 index 0000000000..2ce6398966 --- /dev/null +++ b/app/cosmo/lab.toml @@ -0,0 +1,3 @@ +# Configuration fragment for -lab images +tasks.cosmo_seq.features = ["stay-in-a2"] +tasks.packrat.features = ["boot-kmdb"] diff --git a/app/cosmo/rev-a-dev.toml b/app/cosmo/rev-a-dev.toml new file mode 100644 index 0000000000..fc7137794d --- /dev/null +++ b/app/cosmo/rev-a-dev.toml @@ -0,0 +1,2 @@ +name = "cosmo-a-dev" +inherit = ["rev-a.toml", "dev.toml"] diff --git a/app/cosmo/rev-a-lab.toml b/app/cosmo/rev-a-lab.toml new file mode 100644 index 0000000000..46a1a3635b --- /dev/null +++ b/app/cosmo/rev-a-lab.toml @@ -0,0 +1,2 @@ +name = "cosmo-a-lab" +inherit = ["rev-a-dev.toml", "lab.toml"] diff --git a/app/cosmo/rev-a.toml b/app/cosmo/rev-a.toml new file mode 100644 index 0000000000..75facb95db --- /dev/null +++ b/app/cosmo/rev-a.toml @@ -0,0 +1,4 @@ +# This is the production image. We expect `name` to match `board` +name = "cosmo-a" +board = "cosmo-a" +inherit = "base.toml" diff --git a/app/cosmo/src/main.rs b/app/cosmo/src/main.rs new file mode 100644 index 0000000000..443c71570f --- /dev/null +++ b/app/cosmo/src/main.rs @@ -0,0 +1,448 @@ +// 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; + +#[cfg(feature = "traptrace")] +mod tracing; + +#[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 (Cosmo boards use BGA240) + // + // 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!(); + } + } + + // Cosmo has resistors strapping three pins to indicate the board revision. + // + // We read the board version very early in boot to try and detect the + // firmware being flashed on the wrong board. In particular, we read the + // version _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 revs. We still + // require different firmware per revision; this check serves to detect if + // you've flashed the wrong one, only. + // + // The revision is on pins PG[2:0], with PG2 as the MSB. + + // Un-gate the clock to GPIO bank G. + p.RCC.ahb4enr.modify(|_, w| w.gpiogen().set_bit()); + cortex_m::asm::dsb(); + // PG2:0 are already inputs after reset, but without any pull resistors. + #[rustfmt::skip] + p.GPIOG.moder.modify(|_, w| w + .moder0().input() + .moder1().input() + .moder2().input()); + // Enable the pullups. + #[rustfmt::skip] + p.GPIOG.pupdr.modify(|_, w| w + .pupdr0().pull_up() + .pupdr1().pull_up() + .pupdr2().pull_up()); + // We are now charging up the board revision traces through the ~40kR + // internal pullup resistors. The floating trace is the biggie, since we're + // responsible for putting in any charge that we detect. While the + // capacitance should be low, it's not zero, and even running at the reset + // frequency of 64MHz, we are very much racing the trace charging. + // + // Assuming 50pF for the trace plus the FPGA's tristated input on the far + // end, we get + // + // RC = 40 kR * 50 pF = 2e-6 + // Time to reach Vil of 2.31 V (0.7 VDD) = 2.405 us + // + // Maximum speed of 64MHz oscillator after ST manufacturing calibration, per + // the datasheet, is 64.3 MHz. + // + // 2.405us @ 64.3MHz = 154.64 cycles ~= 155 cycles. + // + // The cortex_m delay routine is written for single-issue simple cores and + // is simply wrong on the M7 (they know this). So, let's conservatively pad + // it by a factor of 2. + cortex_m::asm::delay(155 * 2); + + // Okay! What does the fox^Wpins say? + let rev = p.GPIOG.idr.read().bits() & 0b111; + + cfg_if::cfg_if! { + if #[cfg(target_board = "cosmo-a")] { + let expected_rev = 0b000; + } else { + compile_error!("not a recognized cosmo board") + } + } + + assert_eq!(rev, expected_rev); + + // Do most of the setup with the common implementation. + let p = 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, + }, + ); + + // Now that our clock tree is configured appropriately, we need to set up + // the external bus to the FPGA. This ensures that we're not relying on any + // particular task to do it, so we can direct-map its peripherals into other + // tasks. If those tasks try to use the peripheral before the FPGA is + // initialized, the peripherals won't work, but the result should not be + // _fatal._ + + // Pin mapping: + // PB7 ADV_L (this is PB2 on the schematic, manually jumpered) + // + // PD0 DA2 + // PD1 DA3 + // PD2 - + // PD3 CLK + // PD4 OE_L + // PD5 WE_L + // PD6 WAIT_L <-- pulled up in hardware + // PD7 CS1_L + // PD8 DA13 + // PD9 DA14 + // PD10 DA15 + // PD11 A16 + // PD12 A17 + // PD13 A18 + // PD14 DA0 + // PD15 DA1 + // + // PE0 BL0_L + // PE1 BL1_L + // PE2 A23 <-- new + // PE3 A19 + // PE4 A20 <-- new + // PE5 A21 <-- new + // PE6 A22 <-- new + // PE7 DA4 + // PE8 DA5 + // PE9 DA6 + // PE10 DA7 + // PE11 DA8 + // PE12 DA9 + // PE13 DA10 + // PE14 DA11 + // PE15 DA12 + // + // Our goal is to put all of these into the appropriate AF setting (which, + // conveniently, is AF12 across all ports) and prepare the memory controller. + + // Ensure clock is enabled to both the GPIO ports we touch, and the FMC + // itself. + p.RCC.ahb3enr.modify(|_, w| w.fmcen().set_bit()); + p.RCC.ahb4enr.modify(|_, w| { + w.gpioben().set_bit(); + w.gpioden().set_bit(); + w.gpioeen().set_bit(); + w + }); + cortex_m::asm::dsb(); + + // Expose all the pins _first._ This seems to work best. + + // GPIOB + p.GPIOB.afrl.modify(|_, w| { + w.afr7().af12(); + w + }); + p.GPIOB.ospeedr.modify(|_, w| { + w.ospeedr7().very_high_speed(); + w + }); + p.GPIOB.moder.modify(|_, w| { + w.moder7().alternate(); + w + }); + + // GPIOD + p.GPIOD.afrl.modify(|_, w| { + w.afr0().af12(); + w.afr1().af12(); + // pin 2 used for something else + w.afr3().af12(); + w.afr4().af12(); + w.afr5().af12(); + w.afr6().af12(); + w.afr7().af12(); + w + }); + p.GPIOD.afrh.modify(|_, w| { + w.afr8().af12(); + w.afr9().af12(); + w.afr10().af12(); + w.afr11().af12(); + w.afr12().af12(); + w.afr13().af12(); + w.afr14().af12(); + w.afr15().af12(); + w + }); + p.GPIOD.ospeedr.modify(|_, w| { + w.ospeedr0().very_high_speed(); + w.ospeedr1().very_high_speed(); + // pin 2 used elsewhere + w.ospeedr3().very_high_speed(); + w.ospeedr4().very_high_speed(); + w.ospeedr5().very_high_speed(); + w.ospeedr6().very_high_speed(); + w.ospeedr7().very_high_speed(); + + w.ospeedr8().very_high_speed(); + w.ospeedr9().very_high_speed(); + w.ospeedr10().very_high_speed(); + w.ospeedr11().very_high_speed(); + w.ospeedr12().very_high_speed(); + w.ospeedr13().very_high_speed(); + w.ospeedr14().very_high_speed(); + w.ospeedr15().very_high_speed(); + w + }); + p.GPIOD.moder.modify(|_, w| { + w.moder0().alternate(); + w.moder1().alternate(); + // pin 2 used elsewhere + w.moder3().alternate(); + w.moder4().alternate(); + w.moder5().alternate(); + w.moder6().alternate(); + w.moder7().alternate(); + + w.moder8().alternate(); + w.moder9().alternate(); + w.moder10().alternate(); + w.moder11().alternate(); + w.moder12().alternate(); + w.moder13().alternate(); + w.moder14().alternate(); + w.moder15().alternate(); + w + }); + + // GPIOE + // NOTE: this implementation only brings up a 20-bit address on the FMC bus, + // because that's what worked on Grapefruit. The hardware supports a 24-bit + // address! + p.GPIOE.afrl.modify(|_, w| { + w.afr0().af12(); + w.afr1().af12(); + w.afr3().af12(); + w.afr7().af12(); + w + }); + p.GPIOE.afrh.modify(|_, w| { + w.afr8().af12(); + w.afr9().af12(); + w.afr10().af12(); + w.afr11().af12(); + w.afr12().af12(); + w.afr13().af12(); + w.afr14().af12(); + w.afr15().af12(); + w + }); + p.GPIOE.ospeedr.modify(|_, w| { + w.ospeedr0().very_high_speed(); + w.ospeedr1().very_high_speed(); + w.ospeedr3().very_high_speed(); + w.ospeedr7().very_high_speed(); + + w.ospeedr8().very_high_speed(); + w.ospeedr9().very_high_speed(); + w.ospeedr10().very_high_speed(); + w.ospeedr11().very_high_speed(); + w.ospeedr12().very_high_speed(); + w.ospeedr13().very_high_speed(); + w.ospeedr14().very_high_speed(); + w.ospeedr15().very_high_speed(); + w + }); + p.GPIOE.moder.modify(|_, w| { + w.moder0().alternate(); + w.moder1().alternate(); + w.moder3().alternate(); + w.moder7().alternate(); + + w.moder8().alternate(); + w.moder9().alternate(); + w.moder10().alternate(); + w.moder11().alternate(); + w.moder12().alternate(); + w.moder13().alternate(); + w.moder14().alternate(); + w.moder15().alternate(); + w + }); + + // Basic memory controller setup: + p.FMC.bcr1.write(|w| { + // Emit clock signal continuously, the FPGA likes that. + w.cclken().set_bit(); + + // Use synchronous bursts for both writes and reads. + w.bursten().set_bit(); + w.cburstrw().set_bit(); + + // Enable wait states. + w.waiten().set_bit(); + // Expect the wait state to be active _during_ a wait, not one cycle + // early. + w.waitcfg().set_bit(); + + // Enable writes through the controller. + w.wren().set_bit(); + + // Disable NOR flash memory access (may not be necessary?) + w.faccen().clear_bit(); + + // Configure the memory as "PSRAM" since that's the closest to the + // behavior we want. + // + // Safety: this enum value is not modeled in the PAC, but is defined in + // the reference manual, so this has no implications for safety. + unsafe { + w.mtyp().bits(0b01); + } + + // Turn on the bank (note that we have not turned on the _controller_ + // still). + w.mbken().set_bit(); + + // The following fields are being deliberately left in their reset + // states: + // - FMCEN is being left off + // - BMAP default (no remapping) is retained + // - Write FIFO is being left on (TODO is this correct?) + // - CPSIZE is being left with no special behavior on page-crossing + // - ASYNCWAIT is being left off since we're synchronous + // - EXTMOD is being left off, since it seems to only affect async + // - WAITPOL is treating NWAIT as active low (could change if desired) + // - MWID is being left at a 16 bit data bus. + // - MUXEN is being left with a multiplexed A/D bus. + + w + }); + + // Now for the timings. + // + // Synchronous access write/read latency, minus 2. That is, 0 means 2 cycle + // latency. Max value: 15 (for 17 cycles). NWAIT is not sampled until this + // period has elapsed, so if you're handshaking with a device using NWAIT, + // you almost certainly want this to be 0. + const DATLAT: u8 = 0; + // FMC_CLK division ratio relative to input (AHB3) clock, minus 1. Range: + // 1..=15. + // + // Note from the clock config earlier in this function that AHB3 is running + // at 200 MHz. + const CLKDIV: u8 = 3; // /4, for 50 MHz -- field is divisor minus 1 + + // Bus turnaround time in FMC_CLK cycles, 0..=15 + const BUSTURN: u8 = 0; + + p.FMC.btr1.write(|w| { + unsafe { + w.datlat().bits(DATLAT); + } + unsafe { + w.clkdiv().bits(CLKDIV); + } + unsafe { + w.busturn().bits(BUSTURN); + } + + // Deliberately left in reset state and/or ignored: + // - ACCMOD: only applies when EXTMOD is set in BCR above; also probably + // async only + // - DATAST: async only + // - ADDHLD: async only + // - ADDSET: async only + // + w + }); + + // BWTR1 register is irrelevant if we're not using EXTMOD, which we're not, + // currently. + + // Turn on the controller. + p.FMC.bcr1.modify(|_, w| w.fmcen().set_bit()); +} diff --git a/app/cosmo/src/tracing.rs b/app/cosmo/src/tracing.rs new file mode 100644 index 0000000000..224aa30f7c --- /dev/null +++ b/app/cosmo/src/tracing.rs @@ -0,0 +1,90 @@ +// 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/. + +// +// If you are cutting-and-pasting this code into another kernel (and that +// kernel is armv6m), it is hoped that you will cut-and-paste this compile +// error along with it and take heed of its admonition! +// +#[cfg(not(any(armv7m, armv8m)))] +compile_error!("ringbuf is unsound in the kernel on armv6m"); + +use ringbuf::*; + +#[derive(Copy, Clone, PartialEq)] +enum Event { + SyscallEnter(u32), + SyscallExit, + SecondarySyscallEnter, + SecondarySyscallExit, + IsrEnter, + IsrExit, + TimerIsrEnter, + TimerIsrExit, + ContextSwitch(usize), +} + +#[derive(Copy, Clone, PartialEq)] +enum Trace { + Event(Event), + None, +} + +ringbuf!(Trace, 128, Trace::None); + +fn trace(event: Event) { + ringbuf_entry!(Trace::Event(event)); +} + +fn syscall_enter(nr: u32) { + trace(Event::SyscallEnter(nr)); +} + +fn syscall_exit() { + trace(Event::SyscallExit); +} + +fn secondary_syscall_enter() { + trace(Event::SecondarySyscallEnter); +} + +fn secondary_syscall_exit() { + trace(Event::SecondarySyscallExit); +} + +fn isr_enter() { + trace(Event::IsrEnter); +} + +fn isr_exit() { + trace(Event::IsrExit); +} + +fn timer_isr_enter() { + trace(Event::TimerIsrEnter); +} + +fn timer_isr_exit() { + trace(Event::TimerIsrExit); +} + +fn context_switch(addr: usize) { + trace(Event::ContextSwitch(addr)); +} + +static TRACING: kern::profiling::EventsTable = kern::profiling::EventsTable { + syscall_enter, + syscall_exit, + secondary_syscall_enter, + secondary_syscall_exit, + isr_enter, + isr_exit, + timer_isr_enter, + timer_isr_exit, + context_switch, +}; + +pub fn table() -> &'static kern::profiling::EventsTable { + &TRACING +} diff --git a/boards/cosmo-a.toml b/boards/cosmo-a.toml new file mode 100644 index 0000000000..eb91eeac9c --- /dev/null +++ b/boards/cosmo-a.toml @@ -0,0 +1,2 @@ +[probe-rs] +chip-name = "STM32H753ZITx" diff --git a/build/fpga-regmap/src/lib.rs b/build/fpga-regmap/src/lib.rs index 906e368a35..68226182c8 100644 --- a/build/fpga-regmap/src/lib.rs +++ b/build/fpga-regmap/src/lib.rs @@ -433,6 +433,10 @@ pub fn build_peripheral( }; assert_eq!(*regwidth, 32, "only 32-bit registers are supported"); let mut struct_fns = vec![]; + let mut debug_values = vec![]; + let mut debug_names = vec![]; + let mut debug_types = vec![]; + let mut encode_types = vec![]; for c in children { let Node::Field { inst_name, @@ -447,7 +451,6 @@ pub fn build_peripheral( }; let msb = u32::try_from(*msb).unwrap(); let lsb = u32::try_from(*lsb).unwrap(); - assert!(encode.is_none(), "encode must be none, not {encode:?}"); let setter: syn::Ident = syn::parse_str(&format!("set_{}", inst_name.to_snake_case())) .unwrap(); @@ -476,12 +479,92 @@ pub fn build_peripheral( (d & (1 << #msb)) != 0 } }); + debug_values.push(quote! { + let #getter = (d & (1 << #msb)) != 0; + }); + debug_names.push(quote! { #getter }); + debug_types.push(quote! { pub #getter: bool }); + } + } else if let Some(encode) = encode { + let ty: syn::Ident = + syn::parse_str(&inst_name.to_upper_camel_case()).unwrap(); + let width = msb - lsb + 1; + let mask = u32::try_from((1u64 << width) - 1).unwrap(); + let raw_ty = match width { + 1 => unreachable!("1-bit integers should be bools"), + 2..=8 => "u8", + 9..=16 => "u16", + 17..=32 => "u32", + _ => panic!("invalid width {width}"), + }; + assert_eq!(width, 8, "EnumEncode must be 8 bits wide"); + let quoted = encode + .iter() + .map(|e| { + let v: syn::Ident = + syn::parse_str(&e.name.to_upper_camel_case()) + .unwrap(); + let i: syn::LitInt = + syn::parse_str(&format!("{}{raw_ty}", e.value)) + .unwrap(); + (v, i) + }) + .collect::>(); + + let variants = quoted.iter().map(|(v, i)| quote! { #v = #i }); + let matches = + quoted.iter().map(|(v, i)| quote! { #i => Ok(Self::#v), }); + let raw_ty: syn::Ident = syn::parse_str(raw_ty).unwrap(); + encode_types.push(quote! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(#raw_ty)] + pub enum #ty { + #(#variants),* + } + + impl core::convert::TryFrom<#raw_ty> for #ty { + type Error = #raw_ty; + fn try_from(t: #raw_ty) -> Result { + match t { + #(#matches)* + _ => Err(t), + } + } + } + }); + if sw_access.is_write() { + struct_fns.push(quote! { + #[doc = #desc] + pub fn #setter(&self, t: #ty) { + let mut d = self.get_raw(); + d &= !(#mask << #lsb); + d |= (u32::from(t as #raw_ty) & #mask) << #lsb; + self.set_raw(d); + } + }); + } + if sw_access.is_read() { + struct_fns.push(quote! { + #[doc = #desc] + pub fn #getter(&self) -> Result<#ty, #raw_ty> { + let d = self.get_raw(); + let t = ((d >> #lsb) & #mask) as #raw_ty; + #ty::try_from(t) + } + }); + debug_values.push(quote! { + let t = ((d >> #lsb) & #mask) as #raw_ty; + let #getter = #ty::try_from(t); + }); + debug_names.push(quote! { #getter }); + debug_types + .push(quote! { pub #getter: Result<#ty, #raw_ty> }); } } else { let width = msb - lsb + 1; let mask = u32::try_from((1u64 << width) - 1).unwrap(); let ty = match width { - 1 => unreachable!(), + 1 => unreachable!("1-bit integers should be bools"), 2..=8 => "u8", 9..=16 => "u16", 17..=32 => "u32", @@ -507,17 +590,21 @@ pub fn build_peripheral( ((d >> #lsb) & #mask) as #ty } }); + debug_values.push(quote! { + let #getter = ((d >> #lsb) & #mask) as #ty; + }); + debug_names.push(quote! { #getter }); + debug_types.push(quote! { pub #getter: #ty }); } } } - let struct_name: syn::Ident = - syn::parse_str(&inst_name.to_upper_camel_case()).unwrap(); - let handle_name: syn::Ident = syn::parse_str(&format!( - "{}Handle", - inst_name.to_upper_camel_case() - )) - .unwrap(); + let inst_name = inst_name.to_upper_camel_case(); + let struct_name: syn::Ident = syn::parse_str(&inst_name).unwrap(); + let handle_name: syn::Ident = + syn::parse_str(&format!("{}Handle", inst_name)).unwrap(); + let debug_name: syn::Ident = + syn::parse_str(&format!("{}Debug", inst_name)).unwrap(); let reg_addr = base_addr + u32::try_from(*periph_offset).unwrap() + u32::try_from(*addr_offset).unwrap(); @@ -560,6 +647,24 @@ pub fn build_peripheral( } #(#struct_fns)* } + + #[derive(Copy, Clone, Eq, PartialEq)] + #[allow(dead_code, clippy::useless_conversion, clippy::unnecessary_cast)] + pub struct #debug_name { + #(#debug_types),* + } + #[allow(dead_code, clippy::useless_conversion, clippy::unnecessary_cast)] + impl<'a> From<&'a #struct_name> for #debug_name { + fn from(s: &'a #struct_name) -> #debug_name { + let d = s.get_raw(); + #(#debug_values)* + #debug_name { + #(#debug_names),* + } + } + } + + #(#encode_types)* }; reg_definitions.push(struct_def); let reg_name: syn::Ident = diff --git a/drv/cosmo-hf/src/hf.rs b/drv/cosmo-hf/src/hf.rs index 68a54cd5e2..db7f6e8230 100644 --- a/drv/cosmo-hf/src/hf.rs +++ b/drv/cosmo-hf/src/hf.rs @@ -48,8 +48,8 @@ impl ServerImpl { out.ensure_persistent_data_is_redundant(); if let Ok(p) = out.get_persistent_data() { out.dev = p.dev_select; - out.drv.set_espi_addr_offset(out.flash_base()); } + out.drv.set_espi_addr_offset(out.flash_base()); out } @@ -78,7 +78,7 @@ impl ServerImpl { fn flash_addr(&self, offset: u32, size: u32) -> Result { if offset .checked_add(size) - .is_some_and(|a| a < SLOT_SIZE_BYTES) + .is_some_and(|a| a <= SLOT_SIZE_BYTES) { Self::flash_addr_for(offset, self.dev) } else { @@ -90,7 +90,7 @@ impl ServerImpl { fn bonus_addr(offset: u32, len: u32) -> Result { if offset .checked_add(len) - .is_some_and(|a| a < BONUS_SIZE_BYTES) + .is_some_and(|a| a <= BONUS_SIZE_BYTES) { let addr = offset .checked_add(2 * SLOT_SIZE_BYTES) diff --git a/drv/cosmo-hf/src/main.rs b/drv/cosmo-hf/src/main.rs index 93d43f0caa..9442ab9283 100644 --- a/drv/cosmo-hf/src/main.rs +++ b/drv/cosmo-hf/src/main.rs @@ -348,7 +348,13 @@ impl FlashDriver { } fn set_espi_addr_offset(&self, v: FlashAddr) { - self.drv.sp5_flash_offset.set_offset(v.0); + // The SP5 does all of its reads from a particular base address (found + // by sniffing the SPI bus), so we have to subtract that out when + // calculating the flash offset used by the FPGA + const SP5_BASE: u32 = 0x3000000; + self.drv + .sp5_flash_offset + .set_offset(v.0.wrapping_sub(SP5_BASE)); } } diff --git a/drv/cosmo-seq-server/Cargo.toml b/drv/cosmo-seq-server/Cargo.toml new file mode 100644 index 0000000000..9b3849ed9b --- /dev/null +++ b/drv/cosmo-seq-server/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "drv-cosmo-seq-server" +version = "0.1.0" +edition = "2021" + +[dependencies] +drv-auxflash-api = { path = "../auxflash-api" } +drv-cpu-power-state = { path = "../cpu-power-state" } +counters = { path = "../../lib/counters" } +drv-cpu-seq-api = { path = "../cpu-seq-api" } +drv-hf-api = { path = "../hf-api" } +drv-ice40-spi-program = { path = "../ice40-spi-program" } +drv-packrat-vpd-loader = { path = "../packrat-vpd-loader" } +drv-spartan7-loader-api = { path = "../spartan7-loader-api" } +drv-spi-api = { path = "../spi-api" } +drv-stm32xx-sys-api = { path = "../stm32xx-sys-api" } +gnarle = { path = "../../lib/gnarle" } +ringbuf = { path = "../../lib/ringbuf" } +userlib = { path = "../../sys/userlib", features = ["panic-messages"] } +task-jefe-api = { path = "../../task/jefe-api" } + +cfg-if = { workspace = true } +idol-runtime.workspace = true +num-traits = { workspace = true } +zerocopy = { workspace = true } + +[build-dependencies] +build-fpga-regmap = { path = "../../build/fpga-regmap" } +build-util = { path = "../../build/util" } +idol = { workspace = true } + +[features] +h753 = ["drv-stm32xx-sys-api/h753"] +stay-in-a2 = [] + +[[bin]] +name = "drv-cosmo-seq-server" +test = false +doctest = false +bench = false + +[lints] +workspace = true diff --git a/drv/cosmo-seq-server/build.rs b/drv/cosmo-seq-server/build.rs new file mode 100644 index 0000000000..44eb1f43f0 --- /dev/null +++ b/drv/cosmo-seq-server/build.rs @@ -0,0 +1,58 @@ +// 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 std::{fs, io::Write}; + +fn main() -> Result<(), Box> { + build_util::expose_target_board(); + build_util::build_notifications()?; + + let out_dir = build_util::out_dir(); + let out_file = out_dir.join("cosmo_fpga.rs"); + let mut file = fs::File::create(out_file)?; + + // Check that a valid bitstream is available for this board. + let board = build_util::env_var("HUBRIS_BOARD")?; + if board != "cosmo-a" { + panic!("unknown target board"); + } + + // Pull the bitstream checksums from environment variables + // (injected by `xtask` itself as part of auxiliary flash packing) + let ice40_checksum = + build_util::env_var("HUBRIS_AUXFLASH_CHECKSUM_ICE4").unwrap(); + writeln!( + &mut file, + "\npub const FRONT_FPGA_BITSTREAM_CHECKSUM: [u8; 32] = {};", + ice40_checksum, + )?; + let spartan7_checksum = + build_util::env_var("HUBRIS_AUXFLASH_CHECKSUM_SPA7").unwrap(); + writeln!( + &mut file, + "\npub const SPARTAN7_FPGA_BITSTREAM_CHECKSUM: [u8; 32] = {};", + spartan7_checksum, + )?; + + idol::Generator::new().build_server_support( + "../../idl/cpu-seq.idol", + "server_stub.rs", + idol::server::ServerStyle::InOrder, + )?; + + let out_file = out_dir.join("fmc_sequencer.rs"); + let mut file = std::fs::File::create(out_file)?; + for periph in ["sequencer", "info"] { + write!( + &mut file, + "{}", + build_fpga_regmap::fpga_peripheral( + periph, + "drv_spartan7_loader_api::Spartan7Token" + )? + )?; + } + + Ok(()) +} diff --git a/drv/cosmo-seq-server/cosmo-hp/README.md b/drv/cosmo-seq-server/cosmo-hp/README.md new file mode 100644 index 0000000000..99aac6ee05 --- /dev/null +++ b/drv/cosmo-seq-server/cosmo-hp/README.md @@ -0,0 +1,3 @@ +FPGA images and collateral are generated from: +[this sha](https://github.com/oxidecomputer/quartz/commit/ed11e90df260e8f34156dd295f994788a3e05cb2) +[release](https://api.github.com/repos/oxidecomputer/quartz/releases/214360677) \ No newline at end of file diff --git a/drv/cosmo-seq-server/cosmo-hp/cosmo_hp.bz2 b/drv/cosmo-seq-server/cosmo-hp/cosmo_hp.bz2 new file mode 100644 index 0000000000..fe7c0262c4 Binary files /dev/null and b/drv/cosmo-seq-server/cosmo-hp/cosmo_hp.bz2 differ diff --git a/drv/cosmo-seq-server/cosmo-hp/info_regs.json b/drv/cosmo-seq-server/cosmo-hp/info_regs.json new file mode 100644 index 0000000000..1a2d5ffcb7 --- /dev/null +++ b/drv/cosmo-seq-server/cosmo-hp/info_regs.json @@ -0,0 +1,108 @@ +{ + "type": "addrmap", + "addr_span_bytes": 20, + "inst_name": "info_regs", + "addr_offset": 0, + "children": [ + { + "type": "reg", + "inst_name": "identity", + "addr_offset": 0, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "data", + "lsb": 0, + "msb": 31, + "reset": 478, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Read-only bits showing 0x1de" + } + ] + }, + { + "type": "reg", + "inst_name": "hubris_compat", + "addr_offset": 4, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Read-only bits showing resistor strapping for hubris compatibility value" + } + ] + }, + { + "type": "reg", + "inst_name": "git_info", + "addr_offset": 8, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "sha", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Read-only bits showing the 4byte short-sha of the git commit" + } + ] + }, + { + "type": "reg", + "inst_name": "fpga_checksum", + "addr_offset": 12, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Scribble register, nominally intended to hold the FPGA checksum,\nused for knowing if the FPGA needs to be re-programmed or not" + } + ] + }, + { + "type": "reg", + "inst_name": "scratchpad", + "addr_offset": 16, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Scribble register scratchpad suitable for any software purpose" + } + ] + } + ] +} \ No newline at end of file diff --git a/drv/cosmo-seq-server/src/main.rs b/drv/cosmo-seq-server/src/main.rs new file mode 100644 index 0000000000..93add58cc2 --- /dev/null +++ b/drv/cosmo-seq-server/src/main.rs @@ -0,0 +1,596 @@ +// 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 Grapefruit FPGA process. + +#![no_std] +#![no_main] + +use drv_cpu_seq_api::{PowerState, SeqError as CpuSeqError, StateChangeReason}; +use drv_ice40_spi_program as ice40; +use drv_packrat_vpd_loader::{read_vpd_and_load_packrat, Packrat}; +use drv_spartan7_loader_api::Spartan7Loader; +use drv_spi_api::{SpiDevice, SpiServer}; +use drv_stm32xx_sys_api::{self as sys_api, Sys}; +use idol_runtime::{NotificationHandler, RequestError}; +use task_jefe_api::Jefe; +use userlib::{ + hl, set_timer_relative, sys_get_timer, sys_recv_notification, task_slot, + RecvMessage, +}; + +use drv_hf_api::HostFlash; +use ringbuf::{counted_ringbuf, ringbuf_entry, Count}; + +task_slot!(JEFE, jefe); +task_slot!(LOADER, spartan7_loader); +task_slot!(HF, hf); +task_slot!(SYS, sys); +task_slot!(SPI_FRONT, spi_front); +task_slot!(AUXFLASH, auxflash); +task_slot!(I2C, i2c_driver); +task_slot!(PACKRAT, packrat); + +//////////////////////////////////////////////////////////////////////////////// + +#[derive(Copy, Clone, PartialEq, Count)] +enum Trace { + FpgaInit, + StartFailed(#[count(children)] SeqError), + ContinueBitstreamLoad(usize), + WaitForDone, + Programmed, + + Startup { + early_power_rdbks: fmc_periph::EarlyPowerRdbksDebug, + }, + RegStateValues { + seq_api_status: fmc_periph::SeqApiStatusDebug, + seq_raw_status: fmc_periph::SeqRawStatusDebug, + nic_api_status: fmc_periph::NicApiStatusDebug, + nic_raw_status: fmc_periph::NicRawStatusDebug, + }, + RegPgValues { + rail_pgs: fmc_periph::RailPgsDebug, + rail_pgs_max_hold: fmc_periph::RailPgsMaxHoldDebug, + }, + SetState { + prev: Option, + next: PowerState, + #[count(children)] + why: StateChangeReason, + now: u64, + }, + UnexpectedPowerOff { + our_state: PowerState, + seq_state: Result, + }, + PowerDownError(drv_cpu_seq_api::SeqError), + + #[count(skip)] + None, +} +counted_ringbuf!(Trace, 128, Trace::None); + +//////////////////////////////////////////////////////////////////////////////// + +#[derive(Copy, Clone, PartialEq, Count)] +enum SeqError { + AuxFlashError(#[count(children)] drv_auxflash_api::AuxFlashError), + AuxChecksumMismatch, + Ice40(#[count(children)] ice40::Ice40Error), +} + +impl From for SeqError { + fn from(v: drv_auxflash_api::AuxFlashError) -> Self { + SeqError::AuxFlashError(v) + } +} + +//////////////////////////////////////////////////////////////////////////////// + +const SP_TO_SP5_NMI_SYNC_FLOOD_L: sys_api::PinSet = sys_api::Port::J.pin(2); +const SP_TO_SP5_PROCHOT_L: sys_api::PinSet = sys_api::Port::H.pin(5); +const SP_CHASSIS_STATUS_LED: sys_api::PinSet = sys_api::Port::C.pin(6); +const SP_TO_FPGA2_SYSTEM_RESET_L: sys_api::PinSet = sys_api::Port::A.pin(5); + +// Disabled due to hardware-cosmo#659 (on Cosmo rev A this is PB7, but we need +// to use that pin for FMC). +const SP_TO_IGN_TRGT_FPGA_FAULT_L: Option = None; + +//////////////////////////////////////////////////////////////////////////////// + +/// Helper type which includes both sequencer and NIC state machine states +struct StateMachineStates { + seq: Result, + nic: Result, +} + +#[export_name = "main"] +fn main() -> ! { + // Populate packrat with our mac address and identity. + let packrat = Packrat::from(PACKRAT.get_task_id()); + read_vpd_and_load_packrat(&packrat, I2C.get_task_id()); + + match init() { + // Set up everything nicely, time to start serving incoming messages. + Ok(mut server) => { + // Power on, unless suppressed by the `stay-in-a2` feature + if !cfg!(feature = "stay-in-a2") { + _ = server.set_state_impl( + PowerState::A0, + StateChangeReason::InitialPowerOn, + ); + } + + let mut buffer = [0; idl::INCOMING_SIZE]; + loop { + idol_runtime::dispatch(&mut buffer, &mut server); + } + } + + // Initializing the sequencer failed. + Err(e) => { + // Tell everyone that something's broken, as loudly as possible. + ringbuf_entry!(Trace::StartFailed(e)); + // Leave FAULT_PIN_L low (which is done at the start of init) + + // All these moments will be lost in time, like tears in rain... + // Time to die. + loop { + // Sleeping with all bits in the notification mask clear means + // we should never be notified --- and if one never wakes up, + // the difference between sleeping and dying seems kind of + // irrelevant. But, `rustc` doesn't realize that this should + // never return, we'll stick it in a `loop` anyway so the main + // function can return `!` + sys_recv_notification(0); + } + } + } +} + +fn init() -> Result { + let sys = sys_api::Sys::from(SYS.get_task_id()); + + // Pull the fault line low while we're loading + if let Some(pin) = SP_TO_IGN_TRGT_FPGA_FAULT_L { + sys.gpio_configure_output( + pin, + sys_api::OutputType::OpenDrain, + sys_api::Speed::Low, + sys_api::Pull::None, + ); + sys.gpio_reset(pin); + } + + // Turn off the chassis LED, in case this is a task restart (and not a + // full chip restart, which would leave the GPIO unconfigured). + sys.gpio_configure_output( + SP_CHASSIS_STATUS_LED, + sys_api::OutputType::PushPull, + sys_api::Speed::Low, + sys_api::Pull::None, + ); + sys.gpio_reset(SP_CHASSIS_STATUS_LED); + + let spi_front = drv_spi_api::Spi::from(SPI_FRONT.get_task_id()); + let aux = drv_auxflash_api::AuxFlash::from(AUXFLASH.get_task_id()); + + // Hold the ice40 in reset + let config = ice40::Config { + creset: sys_api::Port::A.pin(4), + cdone: sys_api::Port::A.pin(3), + }; + preinit_front_fpga(&sys, &config); + + // Wait for the Spartan-7 to be loaded, then update its checksum registers + let loader = Spartan7Loader::from(LOADER.get_task_id()); + + // Set up the checksum registers for the Spartan7 FPGA + let token = loader.get_token(); + let info = fmc_periph::Info::new(token); + let short_checksum = gen::SPARTAN7_FPGA_BITSTREAM_CHECKSUM[..4] + .try_into() + .unwrap(); + info.fpga_checksum + .modify(|r| r.set_data(u32::from_be_bytes(short_checksum))); + + init_front_fpga( + &sys, + &spi_front.device(drv_spi_api::devices::MUX), + &aux, + &config, + )?; + + // Bring up the SP5 NMI and PROCHOT pins + sys.gpio_set(SP_TO_SP5_NMI_SYNC_FLOOD_L); + sys.gpio_configure_output( + SP_TO_SP5_NMI_SYNC_FLOOD_L, + sys_api::OutputType::PushPull, + sys_api::Speed::Low, + sys_api::Pull::None, + ); + sys.gpio_set(SP_TO_SP5_PROCHOT_L); + sys.gpio_configure_output( + SP_TO_SP5_PROCHOT_L, + sys_api::OutputType::PushPull, + sys_api::Speed::Low, + sys_api::Pull::None, + ); + + // Clear the fault pin + if let Some(pin) = SP_TO_IGN_TRGT_FPGA_FAULT_L { + sys.gpio_set(pin); + } + + // Turn on the chassis LED! + sys.gpio_set(SP_CHASSIS_STATUS_LED); + + let token = loader.get_token(); + Ok(ServerImpl::new(token)) +} + +/// Configures the front FPGA pins and holds it in reset +fn preinit_front_fpga(sys: &sys_api::Sys, config: &ice40::Config) { + // Make the user reset pin a low output + sys.gpio_reset(SP_TO_FPGA2_SYSTEM_RESET_L); + sys.gpio_configure_output( + config.creset, + sys_api::OutputType::PushPull, + sys_api::Speed::Low, + sys_api::Pull::None, + ); + + ice40::configure_pins(sys, config); + + // This is also called in `ice40::begin_bitstream_load`, but we're going to + // wait for the sequencer to be loaded first, and want this to be in reset + // while we're waiting. + sys.gpio_reset(config.creset); +} + +/// Initialize the front FPGA, which is an ICE40 +fn init_front_fpga( + sys: &sys_api::Sys, + dev: &SpiDevice, + aux: &drv_auxflash_api::AuxFlash, + config: &ice40::Config, +) -> Result<(), SeqError> { + ringbuf_entry!(Trace::FpgaInit); + + ice40::begin_bitstream_load(dev, sys, config).map_err(SeqError::Ice40)?; + + let r = aux.get_compressed_blob_streaming( + *b"ICE4", + |chunk| -> Result<(), SeqError> { + ice40::continue_bitstream_load(dev, chunk) + .map_err(|e| SeqError::Ice40(ice40::Ice40Error::Spi(e)))?; + ringbuf_entry!(Trace::ContinueBitstreamLoad(chunk.len())); + Ok(()) + }, + ); + let sha_out = match r { + Ok(s) => s, + Err(e) => { + let _ = dev.release(); + return Err(e); + } + }; + + if sha_out != gen::FRONT_FPGA_BITSTREAM_CHECKSUM { + // Drop the device into reset and hold it there + sys.gpio_reset(config.creset); + hl::sleep_for(1); + let _ = dev.release(); + + return Err(SeqError::AuxChecksumMismatch); + } + + ringbuf_entry!(Trace::WaitForDone); + ice40::finish_bitstream_load(dev, sys, config).map_err(SeqError::Ice40)?; + ringbuf_entry!(Trace::Programmed); + + // Bring the user design out of reset + sys.gpio_set(SP_TO_FPGA2_SYSTEM_RESET_L); + + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////// + +#[allow(unused)] +struct ServerImpl { + state: PowerState, + jefe: Jefe, + sys: Sys, + hf: HostFlash, + seq: fmc_periph::Sequencer, +} + +impl ServerImpl { + fn new(token: drv_spartan7_loader_api::Spartan7Token) -> Self { + let now = sys_get_timer().now; + let seq = fmc_periph::Sequencer::new(token); + ringbuf_entry!(Trace::Startup { + early_power_rdbks: (&seq.early_power_rdbks).into(), + }); + ringbuf_entry!(Trace::SetState { + prev: None, // dummy value + next: PowerState::A2, + why: StateChangeReason::InitialPowerOn, + now, + }); + let jefe = Jefe::from(JEFE.get_task_id()); + jefe.set_state(PowerState::A2 as u32); + + ServerImpl { + state: PowerState::A2, + jefe, + sys: Sys::from(SYS.get_task_id()), + hf: HostFlash::from(HF.get_task_id()), + seq, + } + } + + fn get_state_impl(&self) -> PowerState { + self.state + } + + /// Logs a set of state registers, returning the state machine states + fn log_state_registers(&self) -> StateMachineStates { + let seq_api_status = (&self.seq.seq_api_status).into(); + let nic_api_status = (&self.seq.nic_api_status).into(); + ringbuf_entry!(Trace::RegStateValues { + seq_api_status, + seq_raw_status: (&self.seq.seq_raw_status).into(), + nic_api_status, + nic_raw_status: (&self.seq.nic_raw_status).into(), + }); + StateMachineStates { + seq: seq_api_status.a0_sm, + nic: nic_api_status.nic_sm, + } + } + + /// Logs a set of power good registers + fn log_pg_registers(&self) { + ringbuf_entry!(Trace::RegPgValues { + rail_pgs: (&self.seq.rail_pgs).into(), + rail_pgs_max_hold: (&self.seq.rail_pgs_max_hold).into(), + }); + } + + fn set_state_impl( + &mut self, + state: PowerState, + why: StateChangeReason, + ) -> Result<(), CpuSeqError> { + let now = sys_get_timer().now; + ringbuf_entry!(Trace::SetState { + prev: Some(self.state), + next: state, + why, + now, + }); + + use fmc_periph::A0Sm; + match (self.get_state_impl(), state) { + (PowerState::A2, PowerState::A0) => { + self.seq.power_ctrl.modify(|m| m.set_a0_en(true)); + let mut okay = false; + // Wait 2 seconds for power-up + for _ in 0..200 { + let state = self.log_state_registers(); + match state.seq { + Ok(A0Sm::Done) => { + okay = true; + break; + } + Ok(A0Sm::Faulted) | Err(_) => { + break; + } + _ => (), + } + hl::sleep_for(10); + } + + if !okay { + // We'll return to A2, leaving jefe and our local state + // unchanged (since they're set after this block). + self.log_state_registers(); + self.log_pg_registers(); + self.seq.power_ctrl.modify(|m| m.set_a0_en(false)); + + // XXX faulted isn't strictly a timeout, but this is the + // closest available error code + return Err(CpuSeqError::A0Timeout); + } + + // Flip the host flash mux so the CPU can read from it + // (this is secretly infallible on Cosmo, so we can unwrap it) + self.hf.set_mux(drv_hf_api::HfMuxState::HostCPU).unwrap(); + } + (PowerState::A0, PowerState::A2) + | (PowerState::A0PlusHP, PowerState::A2) + | (PowerState::A0Thermtrip, PowerState::A2) => { + self.seq.power_ctrl.modify(|m| m.set_a0_en(false)); + let mut okay = false; + for _ in 0..200 { + let state = self.log_state_registers(); + match state.seq { + Ok(A0Sm::Idle) => { + okay = true; + break; + } + Ok(A0Sm::Faulted) | Err(_) => { + break; + } + _ => (), + } + hl::sleep_for(10); + } + if !okay { + self.log_state_registers(); + self.log_pg_registers(); + // We can't do much else here, since we already cleared the + // a0_en flag to disable the sequencer. + } + + // Flip the host flash mux so the SP can read from it + // (this is secretly infallible on Cosmo, so we can unwrap it) + self.hf.set_mux(drv_hf_api::HfMuxState::SP).unwrap(); + } + + // This is purely an accounting change + (PowerState::A0, PowerState::A0PlusHP) => (), + + _ => return Err(CpuSeqError::IllegalTransition), + } + + self.state = state; + self.jefe.set_state(state as u32); + self.poke_timer(); + Ok(()) + } + + /// Returns the current timer interval, in milliseconds + /// + /// If we are in `A0`, then we are waiting for the NIC to come up; if we are + /// in `A0PlusHP`, we're polling for a thermtrip or for someone disabling + /// the NIC. In other states, there's no need to poll. + fn poll_interval(&self) -> Option { + match self.state { + PowerState::A0 => Some(10), + PowerState::A0PlusHP => Some(100), + _ => None, + } + } + + /// Updates the system timer + fn poke_timer(&self) { + if let Some(interval) = self.poll_interval() { + set_timer_relative(interval, notifications::TIMER_MASK); + } + } +} + +impl idl::InOrderSequencerImpl for ServerImpl { + fn get_state( + &mut self, + _: &RecvMessage, + ) -> Result> { + Ok(self.get_state_impl()) + } + + fn set_state( + &mut self, + _: &RecvMessage, + state: PowerState, + ) -> Result<(), RequestError> { + self.set_state_impl(state, StateChangeReason::Other)?; + Ok(()) + } + + fn set_state_with_reason( + &mut self, + _: &RecvMessage, + state: PowerState, + reason: StateChangeReason, + ) -> Result<(), RequestError> { + self.set_state_impl(state, reason)?; + Ok(()) + } + + fn send_hardware_nmi( + &mut self, + _: &RecvMessage, + ) -> Result<(), RequestError> { + // The required length for an NMI pulse is apparently not documented. + // + // Let's try 25 ms! + self.sys.gpio_reset(SP_TO_SP5_NMI_SYNC_FLOOD_L); + hl::sleep_for(25); + self.sys.gpio_set(SP_TO_SP5_NMI_SYNC_FLOOD_L); + Ok(()) + } + + fn read_fpga_regs( + &mut self, + _: &RecvMessage, + ) -> Result<[u8; 64], RequestError> { + Err(RequestError::Fail( + idol_runtime::ClientError::BadMessageContents, + )) + } +} + +impl NotificationHandler for ServerImpl { + fn current_notification_mask(&self) -> u32 { + notifications::TIMER_MASK + } + + fn handle_notification(&mut self, bits: u32) { + if (bits & notifications::TIMER_MASK) == 0 { + return; + } + let state = self.log_state_registers(); + use fmc_periph::{A0Sm, NicSm}; + + // Detect unexpected power-off + match (self.state, state.seq) { + (PowerState::A0 | PowerState::A0PlusHP, Ok(A0Sm::Done)) => (), + (PowerState::A0 | PowerState::A0PlusHP, seq_state) => { + ringbuf_entry!(Trace::UnexpectedPowerOff { + our_state: self.state, + seq_state, + }); + self.log_pg_registers(); + + // Power down to A2, updating our internal state. We can't + // handle errors here, so log them and continue. + if let Err(e) = self + .set_state_impl(PowerState::A2, StateChangeReason::Other) + { + ringbuf_entry!(Trace::PowerDownError(e)) + } + } + // TODO are there other states that we should check here? + _ => (), + } + + // Detect when the NIC comes online + match (self.state, state.nic) { + (PowerState::A0, Ok(NicSm::Done)) => { + self.set_state_impl( + PowerState::A0PlusHP, + StateChangeReason::InitialPowerOn, + ) + .unwrap(); // this should be infallible + } + // TODO: should we handle the NIC powering down while the main CPU + // power remains up? + _ => (), + } + + self.poke_timer(); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +mod idl { + use drv_cpu_seq_api::StateChangeReason; + include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); +} + +mod gen { + include!(concat!(env!("OUT_DIR"), "/cosmo_fpga.rs")); +} + +mod fmc_periph { + include!(concat!(env!("OUT_DIR"), "/fmc_sequencer.rs")); +} + +include!(concat!(env!("OUT_DIR"), "/notifications.rs")); diff --git a/drv/cpu-seq-api/src/lib.rs b/drv/cpu-seq-api/src/lib.rs index 4dc3ba20b0..c5b6db6970 100644 --- a/drv/cpu-seq-api/src/lib.rs +++ b/drv/cpu-seq-api/src/lib.rs @@ -67,4 +67,6 @@ pub enum StateChangeReason { // packrat, all of which want to know at compile-time how many banks there are. pub const NUM_SPD_BANKS: usize = 2; +use crate as drv_cpu_seq_api; + include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/drv/gimlet-seq-server/src/main.rs b/drv/gimlet-seq-server/src/main.rs index 2e66c25b50..5c0bc2fb17 100644 --- a/drv/gimlet-seq-server/src/main.rs +++ b/drv/gimlet-seq-server/src/main.rs @@ -1430,7 +1430,7 @@ cfg_if::cfg_if! { } mod idl { - use super::{SeqError, StateChangeReason}; + use super::StateChangeReason; include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); } diff --git a/drv/grapefruit-seq-server/src/main.rs b/drv/grapefruit-seq-server/src/main.rs index 6b2ff946cc..1b23a43049 100644 --- a/drv/grapefruit-seq-server/src/main.rs +++ b/drv/grapefruit-seq-server/src/main.rs @@ -192,7 +192,7 @@ impl NotificationHandler for ServerImpl { } mod idl { - use drv_cpu_seq_api::{SeqError, StateChangeReason}; + use drv_cpu_seq_api::StateChangeReason; include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); } diff --git a/drv/i2c-devices/src/lm5066.rs b/drv/i2c-devices/src/lm5066.rs index 91654a14e9..ff8460fbf0 100644 --- a/drv/i2c-devices/src/lm5066.rs +++ b/drv/i2c-devices/src/lm5066.rs @@ -218,7 +218,7 @@ impl Lm5066 { impl Validate for Lm5066 { fn validate(device: &I2cDevice) -> Result { - let expected = b"LM5066\0\0"; + let expected = b"LM5066I\0"; pmbus_validate(device, CommandCode::MFR_MODEL, expected) .map_err(Into::into) } diff --git a/drv/i2c-devices/src/ltc4282.rs b/drv/i2c-devices/src/ltc4282.rs index 9e737a3e1f..e2d7d7e606 100644 --- a/drv/i2c-devices/src/ltc4282.rs +++ b/drv/i2c-devices/src/ltc4282.rs @@ -271,7 +271,6 @@ impl Validate for Ltc4282 { && control.on_delay() == false && control.on_enb() == true && control.mass_write_enable() == true - && control.fet_on() == true && control.oc_autoretry() == false && control.uv_autoretry() == true && control.ov_autoretry() == true) diff --git a/drv/i2c-devices/src/max5970.rs b/drv/i2c-devices/src/max5970.rs index fea5914959..dcb9023573 100644 --- a/drv/i2c-devices/src/max5970.rs +++ b/drv/i2c-devices/src/max5970.rs @@ -154,7 +154,8 @@ pub enum Register { oithr_chx_lsb_ch2 = 0x2d, /// Fast-comparator threshold DAC setting - dac_chx_fast = 0x2e, + dac_ch0_fast = 0x2e, + dac_ch1_fast = 0x2f, /// Current threshold fast-to-slow ratio setting ifast2slow = 0x30, @@ -407,6 +408,14 @@ impl Max5970 { self.write_reg(Register::peak_log_rst, rst)?; self.write_reg(Register::peak_log_rst, 0) } + + pub fn set_dac_fast(&self, v: u8) -> Result<(), ResponseCode> { + if self.rail == 0 { + self.write_reg(Register::dac_ch0_fast, v) + } else { + self.write_reg(Register::dac_ch1_fast, v) + } + } } impl Validate for Max5970 { diff --git a/drv/i2c-types/src/lib.rs b/drv/i2c-types/src/lib.rs index 5e52d79416..da54e6d65e 100644 --- a/drv/i2c-types/src/lib.rs +++ b/drv/i2c-types/src/lib.rs @@ -196,6 +196,7 @@ pub enum Mux { M2 = 2, M3 = 3, M4 = 4, + M5 = 5, } /// diff --git a/drv/mock-gimlet-seq-server/src/main.rs b/drv/mock-gimlet-seq-server/src/main.rs index 46d9f48c45..1d934cf2cb 100644 --- a/drv/mock-gimlet-seq-server/src/main.rs +++ b/drv/mock-gimlet-seq-server/src/main.rs @@ -113,7 +113,7 @@ impl NotificationHandler for ServerImpl { } mod idl { - use super::{SeqError, StateChangeReason}; + use super::StateChangeReason; include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); } diff --git a/drv/spartan7-loader/build.rs b/drv/spartan7-loader/build.rs index 4529be4780..3e882b3153 100644 --- a/drv/spartan7-loader/build.rs +++ b/drv/spartan7-loader/build.rs @@ -12,9 +12,10 @@ fn main() -> Result<(), Box> { let mut file = fs::File::create(out_file)?; // Check that a valid bitstream is available for this board. - let board = build_util::env_var("HUBRIS_BOARD")?; - if board != "grapefruit" { - panic!("unknown target board"); + let board = build_util::target_board().expect("could not get target board"); + match board.as_str() { + "grapefruit" | "cosmo-a" => (), + _ => panic!("unknown target board '{board}'"), } // Pull the bitstream checksum from an environment variable diff --git a/drv/spartan7-loader/cosmo-seq/README.md b/drv/spartan7-loader/cosmo-seq/README.md new file mode 100644 index 0000000000..823cb3e884 --- /dev/null +++ b/drv/spartan7-loader/cosmo-seq/README.md @@ -0,0 +1,3 @@ +FPGA images and collateral are generated from: +[this sha](https://github.com/oxidecomputer/quartz/commit/02e28df1cf3fa400924d563529a4d7b89036be9f) +[release](https://api.github.com/repos/oxidecomputer/quartz/releases/215329697) \ No newline at end of file diff --git a/drv/spartan7-loader/cosmo-seq/cosmo_seq.bz2 b/drv/spartan7-loader/cosmo-seq/cosmo_seq.bz2 new file mode 100644 index 0000000000..03260a05c9 Binary files /dev/null and b/drv/spartan7-loader/cosmo-seq/cosmo_seq.bz2 differ diff --git a/drv/spartan7-loader/cosmo-seq/cosmo_seq_top.json b/drv/spartan7-loader/cosmo-seq/cosmo_seq_top.json new file mode 100644 index 0000000000..1716392f8e --- /dev/null +++ b/drv/spartan7-loader/cosmo-seq/cosmo_seq_top.json @@ -0,0 +1,57 @@ +{ + "type": "addrmap", + "addr_span_bytes": 1428, + "inst_name": "cosmo_seq_top", + "orig_type_name": "cosmo_seq_top", + "addr_offset": 0, + "children": [ + { + "type": "addrmap", + "addr_span_bytes": 20, + "inst_name": "info", + "orig_type_name": "info_regs", + "addr_offset": 0, + "children": [] + }, + { + "type": "addrmap", + "addr_span_bytes": 36, + "inst_name": "spi_nor", + "orig_type_name": "spi_nor_regs", + "addr_offset": 256, + "children": [] + }, + { + "type": "addrmap", + "addr_span_bytes": 36, + "inst_name": "espi", + "orig_type_name": "espi_regs", + "addr_offset": 512, + "children": [] + }, + { + "type": "addrmap", + "addr_span_bytes": 76, + "inst_name": "sequencer", + "orig_type_name": "sequencer_regs", + "addr_offset": 768, + "children": [] + }, + { + "type": "addrmap", + "addr_span_bytes": 4, + "inst_name": "sp_i2c", + "orig_type_name": "sp_i2c_regs", + "addr_offset": 1024, + "children": [] + }, + { + "type": "addrmap", + "addr_span_bytes": 148, + "inst_name": "fpga1_hotplug", + "orig_type_name": "pca9506_axi_regs", + "addr_offset": 1280, + "children": [] + } + ] +} \ No newline at end of file diff --git a/drv/spartan7-loader/cosmo-seq/espi_regs.json b/drv/spartan7-loader/cosmo-seq/espi_regs.json new file mode 100644 index 0000000000..8195427318 --- /dev/null +++ b/drv/spartan7-loader/cosmo-seq/espi_regs.json @@ -0,0 +1,243 @@ +{ + "type": "addrmap", + "addr_span_bytes": 36, + "inst_name": "espi_regs", + "addr_offset": 0, + "children": [ + { + "type": "reg", + "inst_name": "flags", + "addr_offset": 0, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "alert", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Sticky bit for alert, set to 1 when alert is needed, cleared by writing 1" + } + ] + }, + { + "type": "reg", + "inst_name": "control", + "addr_offset": 4, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "dbg_mode_en", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one mux in the debug FIFOs and ignore the real eSPI interface" + }, + { + "type": "field", + "inst_name": "resp_fifo_reset", + "lsb": 1, + "msb": 1, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to reset response FIFO. Cleared by hardware after reset." + }, + { + "type": "field", + "inst_name": "cmd_size_fifo_reset", + "lsb": 2, + "msb": 2, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to reset the command size FIFO. Cleared by hardware after reset." + }, + { + "type": "field", + "inst_name": "cmd_fifo_reset", + "lsb": 3, + "msb": 3, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to reset the command FIFO. Cleared by hardware after reset." + }, + { + "type": "field", + "inst_name": "msg_en", + "lsb": 4, + "msb": 4, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Respond back on periph 0 channel vs oob" + } + ] + }, + { + "type": "reg", + "inst_name": "status", + "addr_offset": 8, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "busy", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when hw is running the spi transaction, no new transactions\nmay be issued until it is finished. Technically represents cs_n being low." + } + ] + }, + { + "type": "reg", + "inst_name": "fifo_status", + "addr_offset": 12, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "resp_used_wds", + "lsb": 0, + "msb": 15, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Show used FIFO words in response FIFO (word = 32bits/4 bytes),\n1024 words so 4kB" + }, + { + "type": "field", + "inst_name": "cmd_used_wds", + "lsb": 16, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Show used words in command FIFO (word = 32bits/4 bytes),\n1024 words so 4kB" + } + ] + }, + { + "type": "reg", + "inst_name": "cmd_fifo_wdata", + "addr_offset": 16, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fifo_data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Writing stores data in fifo" + } + ] + }, + { + "type": "reg", + "inst_name": "resp_fifo_rdata", + "addr_offset": 20, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fifo_data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Note: Reading side-effects the data by popping the fifo" + } + ] + }, + { + "type": "reg", + "inst_name": "cmd_size_fifo_wdata", + "addr_offset": 24, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fifo_data", + "lsb": 0, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "store command byte size in fifo. Because we are sending 4bytes at a time into the fifo, but only reading\n1 byte at a time, we need to store the number of cmd bytes in the fifo. This allows queueing up multiple commands\nif desired. left over data bytes are discarded if this fifo is empty and there are <4 bytes left in cmd_fifo" + } + ] + }, + { + "type": "reg", + "inst_name": "uart_thresh", + "addr_offset": 28, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "bytes", + "lsb": 0, + "msb": 7, + "reset": 32, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "" + } + ] + }, + { + "type": "reg", + "inst_name": "last_post_code", + "addr_offset": 32, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "payload", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "MSB is bit 31" + } + ] + } + ] +} \ No newline at end of file diff --git a/drv/spartan7-loader/cosmo-seq/info_regs.json b/drv/spartan7-loader/cosmo-seq/info_regs.json new file mode 100644 index 0000000000..1a2d5ffcb7 --- /dev/null +++ b/drv/spartan7-loader/cosmo-seq/info_regs.json @@ -0,0 +1,108 @@ +{ + "type": "addrmap", + "addr_span_bytes": 20, + "inst_name": "info_regs", + "addr_offset": 0, + "children": [ + { + "type": "reg", + "inst_name": "identity", + "addr_offset": 0, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "data", + "lsb": 0, + "msb": 31, + "reset": 478, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Read-only bits showing 0x1de" + } + ] + }, + { + "type": "reg", + "inst_name": "hubris_compat", + "addr_offset": 4, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Read-only bits showing resistor strapping for hubris compatibility value" + } + ] + }, + { + "type": "reg", + "inst_name": "git_info", + "addr_offset": 8, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "sha", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Read-only bits showing the 4byte short-sha of the git commit" + } + ] + }, + { + "type": "reg", + "inst_name": "fpga_checksum", + "addr_offset": 12, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Scribble register, nominally intended to hold the FPGA checksum,\nused for knowing if the FPGA needs to be re-programmed or not" + } + ] + }, + { + "type": "reg", + "inst_name": "scratchpad", + "addr_offset": 16, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Scribble register scratchpad suitable for any software purpose" + } + ] + } + ] +} \ No newline at end of file diff --git a/drv/spartan7-loader/cosmo-seq/sequencer_regs.json b/drv/spartan7-loader/cosmo-seq/sequencer_regs.json new file mode 100644 index 0000000000..539ad361b3 --- /dev/null +++ b/drv/spartan7-loader/cosmo-seq/sequencer_regs.json @@ -0,0 +1,1537 @@ +{ + "type": "addrmap", + "addr_span_bytes": 76, + "inst_name": "sequencer_regs", + "addr_offset": 0, + "children": [ + { + "type": "reg", + "inst_name": "IFR", + "addr_offset": 0, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fanfault", + "lsb": 0, + "msb": 0, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Fan Fault" + }, + { + "type": "field", + "inst_name": "thermtrip", + "lsb": 1, + "msb": 1, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Thermtrip- Thermal trip indicated from SP5 (sticky since fpga reset or last clear)" + }, + { + "type": "field", + "inst_name": "smerr_assert", + "lsb": 2, + "msb": 2, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "SMERR_L asserted low while CPU was powered up" + }, + { + "type": "field", + "inst_name": "a0mapo", + "lsb": 3, + "msb": 3, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "A1A0 MAPO- A fault in the A1-A0 domain(s) caused a MAPO (sticky since fpga reset or last clear)" + }, + { + "type": "field", + "inst_name": "nicmapo", + "lsb": 4, + "msb": 4, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Nic MAPO- A fault in the A0 domain caused a MAPO (sticky since fpga reset or last clear)" + }, + { + "type": "field", + "inst_name": "amd_pwrok_fedge", + "lsb": 5, + "msb": 5, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "AMD PWROK falling edge while in >=A0 (sticky since fpga reset or last clear)" + }, + { + "type": "field", + "inst_name": "amd_rstn_fedge", + "lsb": 6, + "msb": 6, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "AMD RESET falling edge while in >=A0 (sticky since fpga reset or last clear)" + } + ] + }, + { + "type": "reg", + "inst_name": "IER", + "addr_offset": 4, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fanfault", + "lsb": 0, + "msb": 0, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Fan Fault" + }, + { + "type": "field", + "inst_name": "thermtrip", + "lsb": 1, + "msb": 1, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Thermtrip- Thermal trip indicated from SP5 (sticky since fpga reset or last clear)" + }, + { + "type": "field", + "inst_name": "smerr_assert", + "lsb": 2, + "msb": 2, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "SMERR_L asserted low while CPU was powered up" + }, + { + "type": "field", + "inst_name": "a0mapo", + "lsb": 3, + "msb": 3, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "A1A0 MAPO- A fault in the A1-A0 domain(s) caused a MAPO (sticky since fpga reset or last clear)" + }, + { + "type": "field", + "inst_name": "nicmapo", + "lsb": 4, + "msb": 4, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Nic MAPO- A fault in the A0 domain caused a MAPO (sticky since fpga reset or last clear)" + }, + { + "type": "field", + "inst_name": "amd_pwrok_fedge", + "lsb": 5, + "msb": 5, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "AMD PWROK falling edge while in >=A0 (sticky since fpga reset or last clear)" + }, + { + "type": "field", + "inst_name": "amd_rstn_fedge", + "lsb": 6, + "msb": 6, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "AMD RESET falling edge while in >=A0 (sticky since fpga reset or last clear)" + } + ] + }, + { + "type": "reg", + "inst_name": "status", + "addr_offset": 8, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fanpwrok", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Fan power OK" + }, + { + "type": "field", + "inst_name": "a0pwrok", + "lsb": 1, + "msb": 1, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "A0 power OK" + }, + { + "type": "field", + "inst_name": "nicpwrok", + "lsb": 2, + "msb": 2, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC power OK" + }, + { + "type": "field", + "inst_name": "int_pend", + "lsb": 31, + "msb": 31, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Interrupt Pending Status (set if any enabled IRQ has its flag set)" + } + ] + }, + { + "type": "reg", + "inst_name": "early_power_ctrl", + "addr_offset": 12, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fan_hsc_east_disable", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Fan East hotswap disable" + }, + { + "type": "field", + "inst_name": "fan_hsc_central_disable", + "lsb": 1, + "msb": 1, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Fan Central hotswap disable" + }, + { + "type": "field", + "inst_name": "fan_hsc_west_disable", + "lsb": 2, + "msb": 2, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Fan West hotswap disable" + } + ] + }, + { + "type": "reg", + "inst_name": "early_power_rdbks", + "addr_offset": 16, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fan_fail", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Fan Fail: inversion of MAX31790ATI FAN_FAIL_N output (1=Fail asserted)" + }, + { + "type": "field", + "inst_name": "fan_hsc_east_pg", + "lsb": 1, + "msb": 1, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Readback of fan East hotswap PG pin" + }, + { + "type": "field", + "inst_name": "fan_hsc_central_pg", + "lsb": 2, + "msb": 2, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Readback of fan Central hotswap PG pin" + }, + { + "type": "field", + "inst_name": "fan_hsc_west_pg", + "lsb": 3, + "msb": 3, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Readback of fan West hotswap PG pin" + }, + { + "type": "field", + "inst_name": "fan_hsc_east_disable", + "lsb": 4, + "msb": 4, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Readback of fan East hotswap disable pin" + }, + { + "type": "field", + "inst_name": "fan_hsc_central_disable", + "lsb": 5, + "msb": 5, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Readback of fan Central hotswap disable pin" + }, + { + "type": "field", + "inst_name": "fan_hsc_west_disable", + "lsb": 6, + "msb": 6, + "reset": 0, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Readback of fan West hotswap disable pin" + } + ] + }, + { + "type": "reg", + "inst_name": "power_ctrl", + "addr_offset": 20, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "a0_en", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "A0 Group power enable (starts state machine). \nNote, no discrete A1 state control, it's a transitory state on the way to A0.\nSet to '1' to start the A2->A0/HP sequence. Set to '0' to disable, power off.\nNote that for fault cases, sw must clear and re-enable this bit as the state\nmachine only enables in response to rising edge changes." + } + ] + }, + { + "type": "reg", + "inst_name": "seq_api_status", + "addr_offset": 24, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "a0_sm", + "lsb": 0, + "msb": 7, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Sequencer Status", + "encode": [ + { + "name": "IDLE", + "value": 0 + }, + { + "name": "ENABLE_GRP_A", + "value": 1 + }, + { + "name": "SP5_EARLY_CHECKPOINT", + "value": 2 + }, + { + "name": "ENABLE_GRP_B", + "value": 3 + }, + { + "name": "ENABLE_GRP_C", + "value": 4 + }, + { + "name": "POWER_GOOD", + "value": 5 + }, + { + "name": "SP5_FINAL_CHECKPOINT", + "value": 8 + }, + { + "name": "DONE", + "value": 9 + }, + { + "name": "DISABLING", + "value": 10 + }, + { + "name": "FAULTED", + "value": 11 + } + ] + } + ] + }, + { + "type": "reg", + "inst_name": "seq_raw_status", + "addr_offset": 28, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "hw_sm", + "lsb": 0, + "msb": 7, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Sequencer Raw Status" + } + ] + }, + { + "type": "reg", + "inst_name": "nic_api_status", + "addr_offset": 32, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "nic_sm", + "lsb": 0, + "msb": 7, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Nic SM Status", + "encode": [ + { + "name": "IDLE", + "value": 0 + }, + { + "name": "ENABLE_POWER", + "value": 1 + }, + { + "name": "NIC_RESET", + "value": 2 + }, + { + "name": "DONE", + "value": 3 + } + ] + } + ] + }, + { + "type": "reg", + "inst_name": "nic_raw_status", + "addr_offset": 36, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "hw_sm", + "lsb": 0, + "msb": 7, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC Sequencer Raw Status" + } + ] + }, + { + "type": "reg", + "inst_name": "amd_reset_fedges", + "addr_offset": 40, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "counts", + "lsb": 0, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": "wclr", + "desc": "Falling edge counter of AMD's reset output while in A0/A0HP. Saturates at 255. Cleared by any write or starting a new power up" + } + ] + }, + { + "type": "reg", + "inst_name": "amd_pwrok_fedges", + "addr_offset": 44, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "counts", + "lsb": 0, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": "wclr", + "desc": "Falling edge counter of AMD's PowerOK output while in A0/A0HP. Saturates at 255. Cleared by any write or starting a new power up" + } + ] + }, + { + "type": "reg", + "inst_name": "rail_enables", + "addr_offset": 48, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "abcdef_hsc", + "lsb": 0, + "msb": 0, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "DDR ABCDEF Hot Swap 12V controller" + }, + { + "type": "field", + "inst_name": "ghijkl_hsc", + "lsb": 1, + "msb": 1, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "DDR GHIJKL Hot Swap 12V controller" + }, + { + "type": "field", + "inst_name": "v1p5_rtc", + "lsb": 2, + "msb": 2, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group A V1p5 RTC 1.5V" + }, + { + "type": "field", + "inst_name": "v3p3_sp5", + "lsb": 3, + "msb": 3, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group A rail V3p3 SP5" + }, + { + "type": "field", + "inst_name": "v1p8_sp5", + "lsb": 4, + "msb": 4, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group A rail V1p8 SP5" + }, + { + "type": "field", + "inst_name": "v1p1_sp5", + "lsb": 5, + "msb": 5, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group B rail V1p1 SP5" + }, + { + "type": "field", + "inst_name": "vddio_sp5", + "lsb": 6, + "msb": 6, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDIO SP5" + }, + { + "type": "field", + "inst_name": "vddcr_cpu1", + "lsb": 7, + "msb": 7, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDCR CPU1" + }, + { + "type": "field", + "inst_name": "vddcr_cpu0", + "lsb": 8, + "msb": 8, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDCR CPU0" + }, + { + "type": "field", + "inst_name": "vddcr_soc", + "lsb": 9, + "msb": 9, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDCR SOC" + }, + { + "type": "field", + "inst_name": "nic_hsc_12v", + "lsb": 10, + "msb": 10, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC nic_hsc_12v (discrete pg, shared enable with 5V)" + }, + { + "type": "field", + "inst_name": "nic_hsc_5v", + "lsb": 11, + "msb": 11, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC nic_hsc_5v (discrete pg, shared enable with 12V)" + }, + { + "type": "field", + "inst_name": "v1p5_nic_a0hp", + "lsb": 12, + "msb": 12, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p5_nic_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v1p2_nic_pcie_a0hp", + "lsb": 13, + "msb": 13, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p2_nic_pcie_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v1p2_nic_enet_a0hp", + "lsb": 14, + "msb": 14, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p2_nic_enet_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v3p3_nic_a0hp", + "lsb": 15, + "msb": 15, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v3p3_nic_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v1p1_nic_a0hp", + "lsb": 16, + "msb": 16, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p1_nic_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v0p96_nic_vdd_a0hp", + "lsb": 17, + "msb": 17, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v0p96_nic_vdd_a0hp (discrete pg, enable cascade from nic 5V)" + } + ] + }, + { + "type": "reg", + "inst_name": "rail_pgs", + "addr_offset": 52, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "abcdef_hsc", + "lsb": 0, + "msb": 0, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "DDR ABCDEF Hot Swap 12V controller" + }, + { + "type": "field", + "inst_name": "ghijkl_hsc", + "lsb": 1, + "msb": 1, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "DDR GHIJKL Hot Swap 12V controller" + }, + { + "type": "field", + "inst_name": "v1p5_rtc", + "lsb": 2, + "msb": 2, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Group A V1p5 RTC 1.5V" + }, + { + "type": "field", + "inst_name": "v3p3_sp5", + "lsb": 3, + "msb": 3, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Group A rail V3p3 SP5" + }, + { + "type": "field", + "inst_name": "v1p8_sp5", + "lsb": 4, + "msb": 4, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Group A rail V1p8 SP5" + }, + { + "type": "field", + "inst_name": "v1p1_sp5", + "lsb": 5, + "msb": 5, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Group B rail V1p1 SP5" + }, + { + "type": "field", + "inst_name": "vddio_sp5", + "lsb": 6, + "msb": 6, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDIO SP5" + }, + { + "type": "field", + "inst_name": "vddcr_cpu1", + "lsb": 7, + "msb": 7, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDCR CPU1" + }, + { + "type": "field", + "inst_name": "vddcr_cpu0", + "lsb": 8, + "msb": 8, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDCR CPU0" + }, + { + "type": "field", + "inst_name": "vddcr_soc", + "lsb": 9, + "msb": 9, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDCR SOC" + }, + { + "type": "field", + "inst_name": "nic_hsc_12v", + "lsb": 10, + "msb": 10, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC nic_hsc_12v (discrete pg, shared enable with 5V)" + }, + { + "type": "field", + "inst_name": "nic_hsc_5v", + "lsb": 11, + "msb": 11, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC nic_hsc_5v (discrete pg, shared enable with 12V)" + }, + { + "type": "field", + "inst_name": "v1p5_nic_a0hp", + "lsb": 12, + "msb": 12, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p5_nic_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v1p2_nic_pcie_a0hp", + "lsb": 13, + "msb": 13, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p2_nic_pcie_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v1p2_nic_enet_a0hp", + "lsb": 14, + "msb": 14, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p2_nic_enet_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v3p3_nic_a0hp", + "lsb": 15, + "msb": 15, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v3p3_nic_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v1p1_nic_a0hp", + "lsb": 16, + "msb": 16, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p1_nic_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v0p96_nic_vdd_a0hp", + "lsb": 17, + "msb": 17, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v0p96_nic_vdd_a0hp (discrete pg, enable cascade from nic 5V)" + } + ] + }, + { + "type": "reg", + "inst_name": "rail_pgs_max_hold", + "addr_offset": 56, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "abcdef_hsc", + "lsb": 0, + "msb": 0, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "DDR ABCDEF Hot Swap 12V controller" + }, + { + "type": "field", + "inst_name": "ghijkl_hsc", + "lsb": 1, + "msb": 1, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "DDR GHIJKL Hot Swap 12V controller" + }, + { + "type": "field", + "inst_name": "v1p5_rtc", + "lsb": 2, + "msb": 2, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group A V1p5 RTC 1.5V" + }, + { + "type": "field", + "inst_name": "v3p3_sp5", + "lsb": 3, + "msb": 3, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group A rail V3p3 SP5" + }, + { + "type": "field", + "inst_name": "v1p8_sp5", + "lsb": 4, + "msb": 4, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group A rail V1p8 SP5" + }, + { + "type": "field", + "inst_name": "v1p1_sp5", + "lsb": 5, + "msb": 5, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group B rail V1p1 SP5" + }, + { + "type": "field", + "inst_name": "vddio_sp5", + "lsb": 6, + "msb": 6, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDIO SP5" + }, + { + "type": "field", + "inst_name": "vddcr_cpu1", + "lsb": 7, + "msb": 7, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDCR CPU1" + }, + { + "type": "field", + "inst_name": "vddcr_cpu0", + "lsb": 8, + "msb": 8, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDCR CPU0" + }, + { + "type": "field", + "inst_name": "vddcr_soc", + "lsb": 9, + "msb": 9, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Group C VDDCR SOC" + }, + { + "type": "field", + "inst_name": "nic_hsc_12v", + "lsb": 10, + "msb": 10, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC nic_hsc_12v (discrete pg, shared enable with 5V)" + }, + { + "type": "field", + "inst_name": "nic_hsc_5v", + "lsb": 11, + "msb": 11, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC nic_hsc_5v (discrete pg, shared enable with 12V)" + }, + { + "type": "field", + "inst_name": "v1p5_nic_a0hp", + "lsb": 12, + "msb": 12, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p5_nic_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v1p2_nic_pcie_a0hp", + "lsb": 13, + "msb": 13, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p2_nic_pcie_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v1p2_nic_enet_a0hp", + "lsb": 14, + "msb": 14, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p2_nic_enet_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v3p3_nic_a0hp", + "lsb": 15, + "msb": 15, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v3p3_nic_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v1p1_nic_a0hp", + "lsb": 16, + "msb": 16, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v1p1_nic_a0hp (discrete pg, enable cascade from nic 5V)" + }, + { + "type": "field", + "inst_name": "v0p96_nic_vdd_a0hp", + "lsb": 17, + "msb": 17, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "NIC v0p96_nic_vdd_a0hp (discrete pg, enable cascade from nic 5V)" + } + ] + }, + { + "type": "reg", + "inst_name": "sp5_readbacks", + "addr_offset": 60, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "smerr_l", + "lsb": 0, + "msb": 0, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "SMERR_L live status, note: only valid when SP5 rails are up (From SP5)" + }, + { + "type": "field", + "inst_name": "thermtrip_l", + "lsb": 1, + "msb": 1, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "Thermtrip_l live status, note: only valid when SP5 rails are up (From SP5)" + }, + { + "type": "field", + "inst_name": "reset_l", + "lsb": 2, + "msb": 2, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "reset_l live status, note: only valid when SP5 rails are up (From SP5)" + }, + { + "type": "field", + "inst_name": "pwr_ok", + "lsb": 3, + "msb": 3, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "pwr_ok live status, note: only meaningful when SP5 rails are up (From SP5)" + }, + { + "type": "field", + "inst_name": "slp_s3_l", + "lsb": 4, + "msb": 4, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "slp_s3_l live status, note: only valid when SP5 rails are up (or being sequenced up) (From SP5)" + }, + { + "type": "field", + "inst_name": "slp_s5_l", + "lsb": 5, + "msb": 5, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "slp_s5_l live status, note: only valid when SP5 rails are up (or being sequenced up) (From SP5)" + }, + { + "type": "field", + "inst_name": "rsmrst_l", + "lsb": 6, + "msb": 6, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "rsmrst_l live status, (From FPGA to SP5)" + }, + { + "type": "field", + "inst_name": "pwr_btn_l", + "lsb": 7, + "msb": 7, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "pwr_btn_l live status, (From FPGA to SP5)" + }, + { + "type": "field", + "inst_name": "pwr_good", + "lsb": 8, + "msb": 8, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "pwr_btn_l live status, (From FPGA to SP5)" + } + ] + }, + { + "type": "reg", + "inst_name": "nic_readbacks", + "addr_offset": 64, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "cld_rst_l", + "lsb": 0, + "msb": 0, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "cld_rst_l live status, (From FPGA to NIC)" + }, + { + "type": "field", + "inst_name": "perst_l", + "lsb": 1, + "msb": 1, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "perst_l live status, (From FPGA to NIC)" + }, + { + "type": "field", + "inst_name": "nic_mfg_mode_l", + "lsb": 2, + "msb": 2, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "nic_mfg_mode_l live status, (From FPGA to NIC)" + }, + { + "type": "field", + "inst_name": "eeprom_wp_l", + "lsb": 3, + "msb": 3, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "eeprom_wp_l live status, note: only valid when SP5 rails are up (From FPGA to eeprom)" + }, + { + "type": "field", + "inst_name": "eeprom_wp_buffer_oe_l", + "lsb": 4, + "msb": 4, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "eeprom_wp_buffer_oe_l live status, note: only valid when SP5 rails are up (From FPGA to buffer)" + }, + { + "type": "field", + "inst_name": "flash_wp_l", + "lsb": 5, + "msb": 5, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "flash_wp_l live status, note: only valid when SP5 rails are up (From FPGA to flash)" + }, + { + "type": "field", + "inst_name": "nic_pcie_clk_buff_oe_l", + "lsb": 6, + "msb": 6, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "nic_pcie_clk_buff_oe_l live status, note: only valid when SP5 rails are up (From FPGA to buffer)" + }, + { + "type": "field", + "inst_name": "ext_rst_l", + "lsb": 7, + "msb": 7, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "ext_rst_l live status, note: only valid when NIC rails are up (From NIC)" + }, + { + "type": "field", + "inst_name": "sp5_mfg_mode_l", + "lsb": 8, + "msb": 8, + "reset": null, + "sw_access": "r", + "se_onread": null, + "se_onwrite": null, + "desc": "sp5_mfg_mode_l live status, (From SP5 to FPGA)" + } + ] + }, + { + "type": "reg", + "inst_name": "debug_enables", + "addr_offset": 68, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "ignore_sp5", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Ignore sp5 allows the sequencer to bypass any SP5-related handshakes\nfor power debugging. Some rails may not completely power up without a load-slammer\nor other feedback device in place." + }, + { + "type": "field", + "inst_name": "nic_override", + "lsb": 1, + "msb": 1, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to mux in the nic_override signals instead of the normal nic control signals" + }, + { + "type": "field", + "inst_name": "force_nic_reset", + "lsb": 2, + "msb": 2, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Used for manually controlling the nic reset for debugging purposes" + }, + { + "type": "field", + "inst_name": "force_mfg_mode", + "lsb": 3, + "msb": 3, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Let other logic run but directly force mfg mode_l to be asserted when set" + } + ] + }, + { + "type": "reg", + "inst_name": "nic_overrides", + "addr_offset": 72, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "cld_rst_l", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "cld_rst_l live status, (From FPGA to NIC)" + }, + { + "type": "field", + "inst_name": "perst_l", + "lsb": 1, + "msb": 1, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "perst_l live status, (From FPGA to NIC)" + }, + { + "type": "field", + "inst_name": "nic_mfg_mode_l", + "lsb": 2, + "msb": 2, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "nic_mfg_mode_l live status, (From FPGA to NIC)" + }, + { + "type": "field", + "inst_name": "eeprom_wp_l", + "lsb": 3, + "msb": 3, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "eeprom_wp_l live status, note: only valid when SP5 rails are up (From FPGA to eeprom)" + }, + { + "type": "field", + "inst_name": "eeprom_wp_buffer_oe_l", + "lsb": 4, + "msb": 4, + "reset": 1, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "eeprom_wp_buffer_oe_l live status, note: only valid when SP5 rails are up (From FPGA to buffer)" + }, + { + "type": "field", + "inst_name": "flash_wp_l", + "lsb": 5, + "msb": 5, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "flash_wp_l live status, note: only valid when SP5 rails are up (From FPGA to flash)" + }, + { + "type": "field", + "inst_name": "nic_pcie_clk_buff_oe_l", + "lsb": 6, + "msb": 6, + "reset": 1, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "nic_pcie_clk_buff_oe_l live status, note: only valid when SP5 rails are up (From FPGA to buffer)" + } + ] + } + ] +} \ No newline at end of file diff --git a/drv/spartan7-loader/cosmo-seq/sp_i2c_regs.json b/drv/spartan7-loader/cosmo-seq/sp_i2c_regs.json new file mode 100644 index 0000000000..eef1ae540c --- /dev/null +++ b/drv/spartan7-loader/cosmo-seq/sp_i2c_regs.json @@ -0,0 +1,28 @@ +{ + "type": "addrmap", + "addr_span_bytes": 4, + "inst_name": "sp_i2c_regs", + "addr_offset": 0, + "children": [ + { + "type": "reg", + "inst_name": "mux_resets", + "addr_offset": 0, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "main_bus_reset", + "lsb": 0, + "msb": 0, + "reset": null, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Reset the FPGA-controlled I2C muxes on the, write '1', self clearing" + } + ] + } + ] +} \ No newline at end of file diff --git a/drv/spartan7-loader/cosmo-seq/spi_nor_reg_map.json b/drv/spartan7-loader/cosmo-seq/spi_nor_reg_map.json new file mode 100644 index 0000000000..260516b363 --- /dev/null +++ b/drv/spartan7-loader/cosmo-seq/spi_nor_reg_map.json @@ -0,0 +1,275 @@ +{ + "type": "addrmap", + "inst_name": "nor_flash_regs", + "addr_offset": 0, + "children": [ + { + "type": "reg", + "inst_name": "SPICR", + "addr_offset": 0, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "tx_fifo_reset", + "lsb": 7, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to reset TX FIFO. Cleared by hardware after reset." + }, + { + "type": "field", + "inst_name": "rx_fifo_reset", + "lsb": 15, + "msb": 15, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to reset RX FIFO. Cleared by hardware after reset." + }, + { + "type": "field", + "inst_name": "sp5_owns_flash", + "lsb": 31, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to allow eSPI <-> SP5 interface read from flash. Hubris should\nnot attempt any flash accesses with this bit set. This bit also enforces the\nflash offset in" + } + ] + }, + { + "type": "reg", + "inst_name": "SPISR", + "addr_offset": 4, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "busy", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when hw is running the spi transaction, no new transactions\nmay be issued until it is finished. Technically represents cs_n being low." + }, + { + "type": "field", + "inst_name": "rx_empty", + "lsb": 6, + "msb": 6, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when RX FIFO is empty" + }, + { + "type": "field", + "inst_name": "rx_full", + "lsb": 7, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when RX FIFO is full" + }, + { + "type": "field", + "inst_name": "rx_used_wds", + "lsb": 8, + "msb": 14, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Show used FIFO words in RX_FIFO (word = 32bits/4 bytes),\nmax of 256 bytes, so 64 words" + }, + { + "type": "field", + "inst_name": "tx_empty", + "lsb": 22, + "msb": 22, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when TX FIFO is empty" + }, + { + "type": "field", + "inst_name": "tx_full", + "lsb": 23, + "msb": 23, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when TX FIFO is full" + }, + { + "type": "field", + "inst_name": "tx_used_wds", + "lsb": 24, + "msb": 30, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Show used FIFO words in TX_FIFO (word = 32bits/4 bytes),\nmax of 256 bytes, so 64 words" + } + ] + }, + { + "type": "reg", + "inst_name": "Addr", + "addr_offset": 8, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "addr", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Address to be used for 24 or 32bit accesses" + } + ] + }, + { + "type": "reg", + "inst_name": "DummyCycles", + "addr_offset": 12, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "count", + "lsb": 0, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Specify # of dummy cycles required for this instruction" + } + ] + }, + { + "type": "reg", + "inst_name": "DataBytes", + "addr_offset": 16, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "count", + "lsb": 0, + "msb": 8, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Specify # data bytes -1 (not counting instruction, addr or dummy cycles to be transferred),\nmax of 255 here representing 255 + 1 as that's the largest transaction the flash can support" + } + ] + }, + { + "type": "reg", + "inst_name": "Instr", + "addr_offset": 20, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "opcode", + "lsb": 0, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "SPI Opcode used in the instruction phase. Write side-effect: starts a new transaction.\nThis should be the last register written to during setup" + } + ] + }, + { + "type": "reg", + "inst_name": "tx_fifo_wdata", + "addr_offset": 24, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fifo_data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Writing stores data in fifo" + } + ] + }, + { + "type": "reg", + "inst_name": "rx_fifo_rdata", + "addr_offset": 28, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fifo_data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Note: Reading side-effects the data by popping the fifo" + } + ] + }, + { + "type": "reg", + "inst_name": "SP5FlashOffset", + "addr_offset": 32, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "offset", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "*Signed* Address added (ie subtracted for negative) to raw eSPI transactions to generate physical flash address" + } + ] + } + ] +} \ No newline at end of file diff --git a/drv/spartan7-loader/cosmo-seq/spi_nor_regs.json b/drv/spartan7-loader/cosmo-seq/spi_nor_regs.json new file mode 100644 index 0000000000..61de412682 --- /dev/null +++ b/drv/spartan7-loader/cosmo-seq/spi_nor_regs.json @@ -0,0 +1,276 @@ +{ + "type": "addrmap", + "addr_span_bytes": 36, + "inst_name": "spi_nor_regs", + "addr_offset": 0, + "children": [ + { + "type": "reg", + "inst_name": "SPICR", + "addr_offset": 0, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "tx_fifo_reset", + "lsb": 7, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to reset TX FIFO. Cleared by hardware after reset." + }, + { + "type": "field", + "inst_name": "rx_fifo_reset", + "lsb": 15, + "msb": 15, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to reset RX FIFO. Cleared by hardware after reset." + }, + { + "type": "field", + "inst_name": "sp5_owns_flash", + "lsb": 31, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to allow eSPI <-> SP5 interface read from flash. Hubris should\nnot attempt any flash accesses with this bit set. This bit also enforces the\nflash offset in" + } + ] + }, + { + "type": "reg", + "inst_name": "SPISR", + "addr_offset": 4, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "busy", + "lsb": 0, + "msb": 0, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when hw is running the spi transaction, no new transactions\nmay be issued until it is finished. Technically represents cs_n being low." + }, + { + "type": "field", + "inst_name": "rx_empty", + "lsb": 6, + "msb": 6, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when RX FIFO is empty" + }, + { + "type": "field", + "inst_name": "rx_full", + "lsb": 7, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when RX FIFO is full" + }, + { + "type": "field", + "inst_name": "rx_used_wds", + "lsb": 8, + "msb": 14, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Show used FIFO words in RX_FIFO (word = 32bits/4 bytes),\nmax of 256 bytes, so 64 words" + }, + { + "type": "field", + "inst_name": "tx_empty", + "lsb": 22, + "msb": 22, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when TX FIFO is empty" + }, + { + "type": "field", + "inst_name": "tx_full", + "lsb": 23, + "msb": 23, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Set to one to 1 when TX FIFO is full" + }, + { + "type": "field", + "inst_name": "tx_used_wds", + "lsb": 24, + "msb": 30, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Show used FIFO words in TX_FIFO (word = 32bits/4 bytes),\nmax of 256 bytes, so 64 words" + } + ] + }, + { + "type": "reg", + "inst_name": "Addr", + "addr_offset": 8, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "addr", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Address to be used for 24 or 32bit accesses" + } + ] + }, + { + "type": "reg", + "inst_name": "DummyCycles", + "addr_offset": 12, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "count", + "lsb": 0, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Specify # of dummy cycles required for this instruction" + } + ] + }, + { + "type": "reg", + "inst_name": "DataBytes", + "addr_offset": 16, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "count", + "lsb": 0, + "msb": 8, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Specify # data bytes -1 (not counting instruction, addr or dummy cycles to be transferred),\nmax of 255 here representing 255 + 1 as that's the largest transaction the flash can support" + } + ] + }, + { + "type": "reg", + "inst_name": "Instr", + "addr_offset": 20, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "opcode", + "lsb": 0, + "msb": 7, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "SPI Opcode used in the instruction phase. Write side-effect: starts a new transaction.\nThis should be the last register written to during setup" + } + ] + }, + { + "type": "reg", + "inst_name": "tx_fifo_wdata", + "addr_offset": 24, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fifo_data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Writing stores data in fifo" + } + ] + }, + { + "type": "reg", + "inst_name": "rx_fifo_rdata", + "addr_offset": 28, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "fifo_data", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "Note: Reading side-effects the data by popping the fifo" + } + ] + }, + { + "type": "reg", + "inst_name": "SP5FlashOffset", + "addr_offset": 32, + "regwidth": 32, + "min_accesswidth": 32, + "children": [ + { + "type": "field", + "inst_name": "offset", + "lsb": 0, + "msb": 31, + "reset": 0, + "sw_access": "rw", + "se_onread": null, + "se_onwrite": null, + "desc": "*Signed* Address added (ie subtracted for negative) to raw eSPI transactions to generate physical flash address" + } + ] + } + ] +} \ No newline at end of file diff --git a/drv/spartan7-loader/src/main.rs b/drv/spartan7-loader/src/main.rs index c5eb1f2d7c..3678b40051 100644 --- a/drv/spartan7-loader/src/main.rs +++ b/drv/spartan7-loader/src/main.rs @@ -172,6 +172,10 @@ fn init() -> Result<(), LoaderError> { ringbuf_entry!(Trace::WaitForDone); loader.finish_bitstream_load()?; + // We need to wait for a little while before other tasks can start talking + // to FMC-based peripherals implemented in the FPGA. This specific delay is + // probably overkill, but it's known to work! + hl::sleep_for(100); ringbuf_entry!(Trace::Programmed); Ok(()) diff --git a/drv/stm32h7-fmc-demo-server/Cargo.toml b/drv/stm32h7-fmc-demo-server/Cargo.toml index 7c919fcd9c..fbbc03ac43 100644 --- a/drv/stm32h7-fmc-demo-server/Cargo.toml +++ b/drv/stm32h7-fmc-demo-server/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -drv-stm32xx-sys-api = { path = "../stm32xx-sys-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } counters = { path = "../../lib/counters" } static-cell = { path = "../../lib/static-cell" } @@ -23,9 +22,8 @@ build-util = { path = "../../build/util" } idol = { workspace = true } [features] -h743 = ["stm32h7/stm32h743", "drv-stm32xx-sys-api/h743"] -h753 = ["stm32h7/stm32h753", "drv-stm32xx-sys-api/h753"] -init-hw = [] +h743 = ["stm32h7/stm32h743"] +h753 = ["stm32h7/stm32h753"] [[bin]] name = "drv-stm32h7-fmc-demo-server" diff --git a/drv/stm32h7-fmc-demo-server/src/main.rs b/drv/stm32h7-fmc-demo-server/src/main.rs index 95a5f8a95c..e53dbb5b9d 100644 --- a/drv/stm32h7-fmc-demo-server/src/main.rs +++ b/drv/stm32h7-fmc-demo-server/src/main.rs @@ -10,7 +10,6 @@ use core::{convert::Infallible, mem::take}; use counters::{count, Count}; use static_cell::ClaimOnceCell; -use sys_api::{Alternate, OutputType, Peripheral, Port, Pull, Speed}; use task_net_api::{ LargePayloadBehavior, Net, RecvError, SendError, SocketName, }; @@ -26,7 +25,6 @@ cfg_if::cfg_if! { } } -use drv_stm32xx_sys_api as sys_api; use idol_runtime::{NotificationHandler, RequestError}; task_slot!(SYS, sys); @@ -41,198 +39,12 @@ enum Event { counters::counters!(Event); -fn initialize_hardware() { - let sys = sys_api::Sys::from(SYS.get_task_id()); - - // Alright. We assume, for these purposes, that the FMC clock generation is - // left at its reset default of using AHB3's clock. The AHB bus in general - // is limited to a lower clock speed than the theoretical max of the FMC, so - // if the system is _running_ it means our clock constraints are likely met. - // We won't worry about them further. - // - // With the clock source reasonable, we need to turn on the clock to the - // controller itself: - sys.enable_clock(Peripheral::Fmc); - - // We don't need a barrier here because that call implies a kernel entry, - // which serves as a barrier on this architecture. Programming in userland - // is easy and fun! - // - // Now that the clock is on we can poke the peripheral, so, manifest it: - let fmc = unsafe { &*device::FMC::ptr() }; - - // Configure all our pins. We're configuring a subset of pins for this demo. - // Pin mapping is as follows: - // B7 FMC_NL - // - // D0 FMC_DA2 - // D1 FMC_DA3 - // D3 FMC_CLK - // D4 FMC_NOE - // D5 FMC_NWE - // D6 FMC_NWAIT (also available on C6 as AF9) - // D7 FMC_NE1 (also available on C7 as AF9) - // D8 FMC_DA13 - // D9 FMC_DA14 - // D10 FMC_DA15 - // D11 FMC_A16 - // D12 FMC_A17 - // D13 FMC_A18 - // D14 FMC_DA0 - // D15 FMC_DA1 - // - // E0 FMC_NBL0 - // E1 FMC_NBL1 - // E3 FMC_A19 - // E7 FMC_DA4 - // E8 FMC_DA5 - // E9 FMC_DA6 - // E10 FMC_DA7 - // E11 FMC_DA8 - // E12 FMC_DA9 - // E13 FMC_DA10 - // E14 FMC_DA11 - // E15 FMC_DA12 - // - // If you're probing this on a Nucleo: - // - // FMC_CLK CN9 pin 10 (right side, lowest pin labeled as USART) - // FMC_NOE CN9 pin 8 (one up from FMC_CLK) - // FMC_NWE CN9 pin 6 (one up from FMC_NOE) - // FMC_NWAIT CN9 pin 4 (one up from FMC_NWE) - // FMC_NE1 CN9 pin 2 - // - // FMC_DA0 CN7 pin 4 - // FMC_DA1 CN7 pin 2 - // FMC_DA2 CN9 pin 25 - // FMC_DA3 CN9 pin 27 - // - // FMC_NBL0 CN10 pin 33 - // FMC_NBL1 CN11, outside, fifth hole from bottom (no connector) - - let the_pins = [ - (Port::B.pin(7), Alternate::AF12), - ( - Port::D.pins([0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), - Alternate::AF12, - ), - ( - Port::E.pins([0, 1, 3, 7, 8, 9, 10, 11, 12, 13, 14, 15]), - Alternate::AF12, - ), - ]; - for (pinset, af) in the_pins { - sys.gpio_configure_alternate( - pinset, - OutputType::PushPull, - Speed::VeryHigh, - Pull::None, - af, - ); - } - - // Program up the controller bank. Don't turn it on yet. - fmc.bcr1.write(|w| { - // Do not set FMCEN to turn on the controller just yet. - - // BMAP default should be OK. - - // TODO should we disable the write FIFO? - - // Emit the clock continuously. - w.cclken().set_bit(); - - // Use synchronous bursts for writes. - w.cburstrw().set_bit(); - // ...and also reads. - w.bursten().set_bit(); - - // Have FPGA, enable wait states. - w.waiten().set_bit(); - - // Set waitconfig to 1. - w.waitcfg().set_bit(); - - // Enable writes. - w.wren().set_bit(); - - // Disable NOR flash memory access (may not be necessary?) - w.faccen().clear_bit(); - - // Configure the memory as PSRAM (TODO: verify) - unsafe { - w.mtyp().bits(0b01); - } - - // Turn on the memory bank. - w.mbken().set_bit(); - - // The following fields are being deliberately left in their reset - // states: - // - FMCEN is being left off - // - BMAP default (no remapping) is retained - // - Write FIFO is being left on (TODO is this correct?) - // - CPSIZE is being left with no special behavior on page-crossing - // - ASYNCWAIT is being left off since we're synchronous - // - EXTMOD is being left off, since it seems to only affect async - // - WAITCFG is being left default (TODO tweak later) - // - WAITPOL is treating NWAIT as active low (could change if desired) - // - MWID is being left at a 16 bit data bus. - // - MUXEN is being left with a multiplexed A/D bus. - - w - }); - - // Bank timings! - - // Synchronous access write/read latency, minus 2. That is, 0 means 2 cycle - // latency. Max value: 15 (for 17 cycles). NWAIT is not sampled until this - // period has elapsed, so if you're handshaking with a device using NWAIT, - // you almost certainly want this to be 0. - const DATLAT: u8 = 0; - // FMC_CLK division ratio relative to input (AHB3) clock, minus 1. Range: - // 1..=15. - const CLKDIV: u8 = 3; // /4, for 50 MHz (field is divisor-minus-one) - - // Bus turnaround time in FMC_CLK cycles, 0..=15 - const BUSTURN: u8 = 0; - - fmc.btr1.write(|w| { - unsafe { - w.datlat().bits(DATLAT); - } - unsafe { - w.clkdiv().bits(CLKDIV); - } - unsafe { - w.busturn().bits(BUSTURN); - } - - // Deliberately left in reset state and/or ignored: - // - ACCMOD: only applies when EXTMOD is set in BCR above; also probably - // async only - // - DATAST: async only - // - ADDHLD: async only - // - ADDSET: async only - // - w - }); - - // BWTR1 register is irrelevant if we're not using EXTMOD, which we're not, - // currently. - - // Turn on the controller. - fmc.bcr1.modify(|_, w| w.fmcen().set_bit()); -} - static RX_PACKET: ClaimOnceCell<[u8; 1024]> = ClaimOnceCell::new([0; 1024]); static TX_PACKET: ClaimOnceCell<[u8; 1024]> = ClaimOnceCell::new([0; 1024]); #[export_name = "main"] fn main() -> ! { - if cfg!(feature = "init-hw") { - initialize_hardware(); - } + // The FMC must be manually initialized in the kernel startup routine! // Safety: we're materializing our sole pointer into the FMC controller // space, which is fine even if it aliases (which it doesn't). diff --git a/drv/stm32h7-sprot-server/src/main.rs b/drv/stm32h7-sprot-server/src/main.rs index 822c8765b8..32e50ff62c 100644 --- a/drv/stm32h7-sprot-server/src/main.rs +++ b/drv/stm32h7-sprot-server/src/main.rs @@ -125,6 +125,7 @@ cfg_if::cfg_if! { target_board = "gemini-bu-1", target_board = "grapefruit", target_board = "minibar", + target_board = "cosmo-a", ))] { const ROT_SPI_DEVICE: u8 = drv_spi_api::devices::ROT; fn debug_config(_sys: &sys_api::Sys) { } diff --git a/drv/user-leds/src/main.rs b/drv/user-leds/src/main.rs index db85fecd20..714c4ee3eb 100644 --- a/drv/user-leds/src/main.rs +++ b/drv/user-leds/src/main.rs @@ -48,8 +48,19 @@ task_config::optional_task_config! { const BLINK_INTERVAL: u32 = 500; cfg_if::cfg_if! { + if #[cfg(target_board = "cosmo-a")] { + #[derive(enum_map::Enum, Copy, Clone, FromPrimitive)] + #[allow(clippy::enum_variant_names)] + enum Led { + // chassis LED is controlled by cosmo-seq + DebugWhite = 0, + DebugRed = 1, + DebugGreen = 2, + DebugBlue = 3, + } + } // Target boards with 4 leds - if #[cfg(any( + else if #[cfg(any( target_board = "gemini-bu-1", target_board = "gimletlet-1", target_board = "gimletlet-2" @@ -481,6 +492,13 @@ cfg_if::cfg_if! { const LEDS: &[(drv_stm32xx_sys_api::PinSet, bool)] = &[ (drv_stm32xx_sys_api::Port::C.pin(6), false), ]; + } else if #[cfg(target_board = "cosmo-a")] { + const LEDS: &[(drv_stm32xx_sys_api::PinSet, bool)] = &[ + (drv_stm32xx_sys_api::Port::H.pin(6), true), // debug W + (drv_stm32xx_sys_api::Port::H.pin(10), true), // debug R + (drv_stm32xx_sys_api::Port::H.pin(11), true), // debug G + (drv_stm32xx_sys_api::Port::H.pin(12), true), // debug B + ]; } else { compile_error!("no LED mapping for unknown board"); } @@ -510,35 +528,7 @@ fn enable_led_pins() { #[cfg(feature = "stm32h7")] fn led_info(led: Led) -> (drv_stm32xx_sys_api::PinSet, bool) { - match led { - Led::Zero => LEDS[0], - #[cfg(any( - target_board = "gemini-bu-1", - target_board = "gimletlet-1", - target_board = "gimletlet-2", - target_board = "nucleo-h753zi", - target_board = "nucleo-h743zi2", - target_board = "gemini-bu-1", - target_board = "gimletlet-1", - target_board = "gimletlet-2", - target_board = "grapefruit", - ))] - Led::One => LEDS[1], - #[cfg(any( - target_board = "gemini-bu-1", - target_board = "gimletlet-1", - target_board = "gimletlet-2", - target_board = "nucleo-h753zi", - target_board = "nucleo-h743zi2" - ))] - Led::Two => LEDS[2], - #[cfg(any( - target_board = "gemini-bu-1", - target_board = "gimletlet-1", - target_board = "gimletlet-2" - ))] - Led::Three => LEDS[3], - } + LEDS[led as usize] } #[cfg(feature = "stm32h7")] diff --git a/idl/cpu-seq.idol b/idl/cpu-seq.idol index 12015daa7d..771d765e60 100644 --- a/idl/cpu-seq.idol +++ b/idl/cpu-seq.idol @@ -22,7 +22,7 @@ Interface( }, reply: Result( ok: "()", - err: CLike("SeqError"), + err: CLike("drv_cpu_seq_api::SeqError"), ), ), "set_state_with_reason": ( @@ -39,7 +39,7 @@ Interface( }, reply: Result( ok: "()", - err: CLike("SeqError"), + err: CLike("drv_cpu_seq_api::SeqError"), ), ), "send_hardware_nmi": ( diff --git a/lib/host-sp-messages/src/lib.rs b/lib/host-sp-messages/src/lib.rs index 5981290bb1..2f24f0f959 100644 --- a/lib/host-sp-messages/src/lib.rs +++ b/lib/host-sp-messages/src/lib.rs @@ -459,6 +459,47 @@ pub enum InventoryData { /// MAX31790 fan controller Max31790 { speed_sensors: [SensorIndex; 6] }, + + Raa229620a { + /// MFR_ID (PMBus operation 0x99) + mfr_id: [u8; 4], + /// MFR_MODEL (PMBus operation 0x9A) + mfr_model: [u8; 4], + /// MFR_REVISION (PMBus operation 0x9B) + mfr_revision: [u8; 4], + /// MFR_DATE, PMBus operation 0x9D + mfr_date: [u8; 4], + /// IC_DEVICE_ID, PMBus operation 0xAD + ic_device_id: [u8; 4], + /// IC_DEVICE_REV, PMBus operation 0xAE + ic_device_rev: [u8; 4], + + temp_sensors: [SensorIndex; 2], + power_sensors: [SensorIndex; 2], + voltage_sensors: [SensorIndex; 2], + current_sensors: [SensorIndex; 2], + }, + + /// LTC4282 hot-swap controller + Ltc4282 { + voltage_sensor: SensorIndex, + current_sensor: SensorIndex, + }, + + /// LM5066 hot-swap controller + Lm5066 { + /// MFR_ID (PMBus operation 0x99) + mfr_id: [u8; 3], + /// MFR_MODEL (PMBus operation 0x9A) + mfr_model: [u8; 8], + /// MFR_REVISION (PMBus operation 0x9B) + mfr_revision: [u8; 2], + + temp_sensor: SensorIndex, + power_sensor: SensorIndex, + voltage_sensor: SensorIndex, + current_sensor: SensorIndex, + }, } #[derive( diff --git a/task/host-sp-comms/Cargo.toml b/task/host-sp-comms/Cargo.toml index a9f3981b8f..0a10d522a7 100644 --- a/task/host-sp-comms/Cargo.toml +++ b/task/host-sp-comms/Cargo.toml @@ -63,6 +63,7 @@ hardware_flow_control = [] vlan = ["task-net-api/vlan"] gimlet = ["pmbus", "tlvc", "drv-i2c-api", "drv-i2c-devices", "drv-spi-api", "ksz8463", "build-i2c", "task-sensor-api"] grapefruit = ["drv-spi-api", "ksz8463"] +cosmo = ["pmbus", "tlvc", "drv-i2c-api", "drv-i2c-devices", "drv-spi-api", "ksz8463", "build-i2c", "task-sensor-api"] [[bin]] name = "task-host-sp-comms" diff --git a/task/host-sp-comms/build.rs b/task/host-sp-comms/build.rs index d022e8a967..7b0a008f0b 100644 --- a/task/host-sp-comms/build.rs +++ b/task/host-sp-comms/build.rs @@ -6,7 +6,7 @@ fn main() -> Result<(), Box> { build_util::expose_target_board(); build_util::build_notifications()?; - #[cfg(feature = "gimlet")] + #[cfg(any(feature = "gimlet", feature = "cosmo"))] build_i2c::codegen(build_i2c::Disposition::Sensors)?; idol::Generator::new() diff --git a/task/host-sp-comms/src/bsp/cosmo_a.rs b/task/host-sp-comms/src/bsp/cosmo_a.rs new file mode 100644 index 0000000000..7a3461280d --- /dev/null +++ b/task/host-sp-comms/src/bsp/cosmo_a.rs @@ -0,0 +1,711 @@ +// 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/. + +//! SP inventory types and implementation +//! +//! This reduces clutter in the main `ServerImpl` implementation +use super::{inventory::by_refdes, ServerImpl}; + +use drv_i2c_api::I2cDevice; +use drv_i2c_api::ResponseCode; +use drv_i2c_devices::at24csw080::{At24Csw080, Error as EepromError}; +use drv_oxide_vpd::VpdError; +use drv_spi_api::SpiServer; +use task_sensor_api::SensorId; +use userlib::TaskId; +use zerocopy::AsBytes; + +use host_sp_messages::{InventoryData, InventoryDataResult}; + +userlib::task_slot!(I2C, i2c_driver); +userlib::task_slot!(SPI, spi_driver); + +// SP_TO_SP5_CPU_INT_L +pub(crate) const SP_TO_HOST_CPU_INT_L: drv_stm32xx_sys_api::PinSet = + drv_stm32xx_sys_api::Port::I.pin(7); +pub(crate) const SP_TO_HOST_CPU_INT_TYPE: drv_stm32xx_sys_api::OutputType = + drv_stm32xx_sys_api::OutputType::PushPull; + +impl ServerImpl { + /// Number of devices in our inventory + pub(crate) const INVENTORY_COUNT: u32 = 59; + + /// Look up a device in our inventory, by index + /// + /// Indexes are assigned arbitrarily and may change freely with SP + /// revisions. + /// + /// On success, we will have already filled `self.tx_buf` with our response; + /// this _may_ be an error if the index was valid but we can't communicate + /// with the target device. + /// + /// This function should only return an error if the index is invalid; + /// in that case, our caller is responsible for encoding the error as + /// ``` + /// SpToHost::InventoryData{ + /// result: err + /// name: [0; u32], + /// } + /// ``` + pub(crate) fn perform_inventory_lookup( + &mut self, + sequence: u64, + index: u32, + ) -> Result<(), InventoryDataResult> { + #[forbid(unreachable_patterns)] + match index { + 0 => { + // U32/ID: SP barcode is available in packrat + let packrat = &self.packrat; + let mut data = InventoryData::VpdIdentity(Default::default()); + self.tx_buf.try_encode_inventory(sequence, b"U32/ID", || { + let InventoryData::VpdIdentity(identity) = &mut data else { + unreachable!(); + }; + *identity = packrat + .get_identity() + .map_err(|_| InventoryDataResult::DeviceAbsent)? + .into(); + Ok(&data) + }); + } + 1 => { + // U32: Gimlet VPD EEPROM + // + // Note that for VPD AT24CSW080 identities, we allocate our + // InventoryData in the outer frame then pass it in as a + // reference; `read_at24csw080_id` typically isn't inlined, and + // we're already paying a stack frame for the data in this + // function, so it saves us 512 bytes of stack. + let (name, f, _sensors) = by_refdes!(U32, at24csw080); + let mut data = InventoryData::At24csw08xSerial([0u8; 16]); + self.read_at24csw080_id(sequence, &name, f, &mut data) + } + 2 => { + // J34/ID: Fan VPD barcode (not available in packrat) + self.read_fan_barcodes( + sequence, + b"J34/ID", + i2c_config::devices::at24csw080_fan_vpd, + ) + } + 3 => { + // J34: Fan VPD EEPROM (on the daughterboard) + let mut data = InventoryData::At24csw08xSerial([0u8; 16]); + self.read_at24csw080_id( + sequence, + b"J34/U1", + i2c_config::devices::at24csw080_fan_vpd, + &mut data, + ) + } + // Welcome to The Sharkfin Zone + // + // Each Sharkfin has 3 inventory items: + // - Oxide barcode + // - Raw VPD EEPROM ID register + // - Hot-swap controller + // + // Sharkfin connectors start at J200 and are numbered sequentially + 4..=13 => { + let (designator, f): ([u8; 4], _) = + Self::get_sharkfin_vpd(index as usize - 4); + let mut name = *b"____/U7/ID"; + name[0..4].copy_from_slice(&designator); + self.read_eeprom_barcode(sequence, &name, f) + } + 14..=23 => { + let (designator, f): ([u8; 4], _) = + Self::get_sharkfin_vpd(index as usize - 14); + let mut name = *b"____/U7"; + name[0..4].copy_from_slice(&designator); + let mut data = InventoryData::At24csw08xSerial([0u8; 16]); + self.read_at24csw080_id(sequence, &name, f, &mut data) + } + 24 => { + // U20: the service processor itself + // The UID is readable by stm32xx_sys + let sys = + drv_stm32xx_sys_api::Sys::from(crate::SYS.get_task_id()); + let uid = sys.read_uid(); + + let idc = drv_stm32h7_dbgmcu::read_idc(); + let dbgmcu_rev_id = (idc >> 16) as u16; + let dbgmcu_dev_id = (idc & 4095) as u16; + let data = InventoryData::Stm32H7 { + uid, + dbgmcu_rev_id, + dbgmcu_dev_id, + }; + self.tx_buf + .try_encode_inventory(sequence, b"U20", || Ok(&data)); + } + 25 => { + // U80: BMR491 + let (name, f, sensors) = by_refdes!(U80, bmr491); + let dev = f(I2C.get_task_id()); + // To be stack-friendly, we declare our output here, + // then bind references to all the member variables. + let mut data = InventoryData::Bmr491 { + mfr_id: [0u8; 12], + mfr_model: [0u8; 20], + mfr_revision: [0u8; 12], + mfr_location: [0u8; 12], + mfr_date: [0u8; 12], + mfr_serial: [0u8; 20], + mfr_firmware_data: [0u8; 20], + temp_sensor: sensors.temperature.into(), + voltage_sensor: sensors.voltage.into(), + current_sensor: sensors.current.into(), + power_sensor: sensors.power.into(), + }; + self.tx_buf.try_encode_inventory(sequence, &name, || { + use pmbus::commands::bmr491::CommandCode; + let InventoryData::Bmr491 { + mfr_id, + mfr_model, + mfr_revision, + mfr_location, + mfr_date, + mfr_serial, + mfr_firmware_data, + temp_sensor: _, + voltage_sensor: _, + current_sensor: _, + power_sensor: _, + } = &mut data + else { + unreachable!() + }; + dev.read_block(CommandCode::MFR_ID as u8, mfr_id)?; + dev.read_block(CommandCode::MFR_MODEL as u8, mfr_model)?; + dev.read_block( + CommandCode::MFR_REVISION as u8, + mfr_revision, + )?; + dev.read_block( + CommandCode::MFR_LOCATION as u8, + mfr_location, + )?; + dev.read_block(CommandCode::MFR_DATE as u8, mfr_date)?; + dev.read_block(CommandCode::MFR_SERIAL as u8, mfr_serial)?; + dev.read_block( + CommandCode::MFR_FIRMWARE_DATA as u8, + mfr_firmware_data, + )?; + Ok(&data) + }) + } + 26 => { + let (name, f, sensors) = by_refdes!(U116, isl68224); + let dev = f(I2C.get_task_id()); + // To be stack-friendly, we declare our output here, + // then bind references to all the member variables. + let mut data = InventoryData::Isl68224 { + mfr_id: [0u8; 4], + mfr_model: [0u8; 4], + mfr_revision: [0u8; 4], + mfr_date: [0u8; 4], + ic_device_id: [0u8; 4], + ic_device_rev: [0u8; 4], + voltage_sensors: SensorId::into_u32_array(sensors.voltage), + current_sensors: SensorId::into_u32_array(sensors.current), + }; + self.tx_buf.try_encode_inventory(sequence, &name, || { + use pmbus::commands::isl68224::CommandCode; + let InventoryData::Isl68224 { + mfr_id, + mfr_model, + mfr_revision, + mfr_date, + ic_device_id, + ic_device_rev, + voltage_sensors: _, + current_sensors: _, + } = &mut data + else { + unreachable!() + }; + dev.read_block(CommandCode::MFR_ID as u8, mfr_id)?; + dev.read_block(CommandCode::MFR_MODEL as u8, mfr_model)?; + dev.read_block( + CommandCode::MFR_REVISION as u8, + mfr_revision, + )?; + dev.read_block(CommandCode::MFR_DATE as u8, mfr_date)?; + dev.read_block( + CommandCode::IC_DEVICE_ID as u8, + ic_device_id, + )?; + dev.read_block( + CommandCode::IC_DEVICE_REV as u8, + ic_device_rev, + )?; + Ok(&data) + }) + } + 27..=28 => { + let (name, f, sensors) = match index - 27 { + 0 => by_refdes!(U90, raa229620a, 4), + 1 => by_refdes!(U103, raa229620a), + _ => unreachable!(), + }; + let dev = f(I2C.get_task_id()); + + // To be stack-friendly, we declare our output here, + // then bind references to all the member variables. + let mut data = InventoryData::Raa229620a { + mfr_id: [0u8; 4], + mfr_model: [0u8; 4], + mfr_revision: [0u8; 4], + mfr_date: [0u8; 4], + ic_device_id: [0u8; 4], + ic_device_rev: [0u8; 4], + temp_sensors: SensorId::into_u32_array(sensors.temperature), + power_sensors: SensorId::into_u32_array(sensors.power), + voltage_sensors: SensorId::into_u32_array(sensors.voltage), + current_sensors: SensorId::into_u32_array(sensors.current), + }; + self.tx_buf.try_encode_inventory(sequence, &name, || { + use pmbus::commands::raa229620a::CommandCode; + let InventoryData::Raa229620a { + mfr_id, + mfr_model, + mfr_revision, + mfr_date, + ic_device_id, + ic_device_rev, + temp_sensors: _, + power_sensors: _, + voltage_sensors: _, + current_sensors: _, + } = &mut data + else { + unreachable!() + }; + dev.read_block(CommandCode::MFR_ID as u8, mfr_id)?; + dev.read_block(CommandCode::MFR_MODEL as u8, mfr_model)?; + dev.read_block( + CommandCode::MFR_REVISION as u8, + mfr_revision, + )?; + dev.read_block(CommandCode::MFR_DATE as u8, mfr_date)?; + dev.read_block( + CommandCode::IC_DEVICE_ID as u8, + ic_device_id, + )?; + dev.read_block( + CommandCode::IC_DEVICE_REV as u8, + ic_device_rev, + )?; + Ok(&data) + }) + } + 29..=32 => { + let (name, f, sensors) = match index - 29 { + 0 => by_refdes!(U81, tps546b24a, 4), + 1 => by_refdes!(U82, tps546b24a, 4), + 2 => by_refdes!(U83, tps546b24a, 4), + 3 => by_refdes!(U123, tps546b24a), + _ => unreachable!(), + }; + let dev = f(I2C.get_task_id()); + let mut data = InventoryData::Tps546b24a { + mfr_id: [0u8; 3], + mfr_model: [0u8; 3], + mfr_revision: [0u8; 3], + mfr_serial: [0u8; 3], + ic_device_id: [0u8; 6], + ic_device_rev: [0u8; 2], + nvm_checksum: 0u16, + temp_sensor: sensors.temperature.into(), + voltage_sensor: sensors.voltage.into(), + current_sensor: sensors.current.into(), + }; + self.tx_buf.try_encode_inventory(sequence, &name, || { + use pmbus::commands::tps546b24a::CommandCode; + let InventoryData::Tps546b24a { + mfr_id, + mfr_model, + mfr_revision, + mfr_serial, + ic_device_id, + ic_device_rev, + nvm_checksum, + temp_sensor: _, + voltage_sensor: _, + current_sensor: _, + } = &mut data + else { + unreachable!() + }; + dev.read_block(CommandCode::MFR_ID as u8, mfr_id)?; + dev.read_block(CommandCode::MFR_MODEL as u8, mfr_model)?; + dev.read_block( + CommandCode::MFR_REVISION as u8, + mfr_revision, + )?; + dev.read_block(CommandCode::MFR_SERIAL as u8, mfr_serial)?; + dev.read_block( + CommandCode::IC_DEVICE_ID as u8, + ic_device_id, + )?; + dev.read_block( + CommandCode::IC_DEVICE_REV as u8, + ic_device_rev, + )?; + dev.read_reg_into( + CommandCode::NVM_CHECKSUM as u8, + nvm_checksum.as_bytes_mut(), + )?; + Ok(&data) + }) + } + 33 => { + let (name, f, sensors) = by_refdes!(U79, adm1272); + let dev = f(I2C.get_task_id()); + + let mut data = InventoryData::Adm1272 { + mfr_id: [0u8; 3], + mfr_model: [0u8; 10], + mfr_revision: [0u8; 2], + mfr_date: [0u8; 6], + + temp_sensor: sensors.temperature.into(), + voltage_sensor: sensors.voltage.into(), + current_sensor: sensors.current.into(), + }; + self.tx_buf.try_encode_inventory(sequence, &name, || { + use pmbus::commands::tps546b24a::CommandCode; + let InventoryData::Adm1272 { + mfr_id, + mfr_model, + mfr_revision, + mfr_date, + temp_sensor: _, + voltage_sensor: _, + current_sensor: _, + } = &mut data + else { + unreachable!() + }; + dev.read_block(CommandCode::MFR_ID as u8, mfr_id)?; + dev.read_block(CommandCode::MFR_MODEL as u8, mfr_model)?; + dev.read_block( + CommandCode::MFR_REVISION as u8, + mfr_revision, + )?; + dev.read_block(CommandCode::MFR_DATE as u8, mfr_date)?; + Ok(&data) + }) + } + 34..=36 => { + let (name, f, sensors) = match index - 34 { + 0 => by_refdes!(U71, lm5066), + 1 => by_refdes!(U72, lm5066), + 2 => by_refdes!(U73, lm5066), + _ => unreachable!(), + }; + let dev = f(I2C.get_task_id()); + let mut data = InventoryData::Lm5066 { + mfr_id: [0u8; 3], + mfr_model: [0u8; 8], + mfr_revision: [0u8; 2], + + temp_sensor: sensors.temperature.into(), + power_sensor: sensors.temperature.into(), + voltage_sensor: sensors.voltage.into(), + current_sensor: sensors.current.into(), + }; + self.tx_buf.try_encode_inventory(sequence, &name, || { + use pmbus::commands::lm5066::CommandCode; + let InventoryData::Lm5066 { + mfr_id, + mfr_model, + mfr_revision, + .. + } = &mut data + else { + unreachable!() + }; + dev.read_block(CommandCode::MFR_ID as u8, mfr_id)?; + dev.read_block(CommandCode::MFR_MODEL as u8, mfr_model)?; + dev.read_block( + CommandCode::MFR_REVISION as u8, + mfr_revision, + )?; + Ok(&data) + }) + } + 37..=42 => { + let (connector_name, f, sensors): ([u8; 3], _, _) = + match index - 37 { + 0 => by_refdes!(J44, tmp117), + 1 => by_refdes!(J45, tmp117), + 2 => by_refdes!(J46, tmp117), + 3 => by_refdes!(J47, tmp117), + 4 => by_refdes!(J48, tmp117), + 5 => by_refdes!(J49, tmp117), + _ => unreachable!(), + }; + let dev = f(I2C.get_task_id()); + + // Convert the name from Jxxx (in the TOML file) -> Jxxx/U1 + let mut name = *b"Jxx/U1"; + // All connector names should have length 3; that's checked by + // the type in the tuple assignment above. + name[..3].copy_from_slice(&connector_name); + + let mut data = InventoryData::Tmp117 { + id: 0, + eeprom1: 0, + eeprom2: 0, + eeprom3: 0, + temp_sensor: sensors.temperature.into(), + }; + self.tx_buf.try_encode_inventory(sequence, &name, || { + let InventoryData::Tmp117 { + id, + eeprom1, + eeprom2, + eeprom3, + temp_sensor: _, + } = &mut data + else { + unreachable!(); + }; + *id = dev.read_reg(0x0Fu8)?; + *eeprom1 = dev.read_reg(0x05u8)?; + *eeprom2 = dev.read_reg(0x06u8)?; + *eeprom3 = dev.read_reg(0x08u8)?; + Ok(&data) + }) + } + 43 => { + let spi = drv_spi_api::Spi::from(SPI.get_task_id()); + let ksz8463_dev = spi.device(drv_spi_api::devices::KSZ8463); + let ksz8463 = ksz8463::Ksz8463::new(ksz8463_dev); + let mut data = InventoryData::Ksz8463 { cider: 0 }; + self.tx_buf.try_encode_inventory(sequence, b"U37", || { + let InventoryData::Ksz8463 { cider } = &mut data else { + unreachable!(); + }; + *cider = ksz8463 + .read(ksz8463::Register::CIDER) + .map_err(|_| InventoryDataResult::DeviceFailed)?; + Ok(&data) + }); + } + 44..=55 => { + let i = index - 44; + let (name, _f, sensors) = match i { + 0 => by_refdes!(J200, max5970), + 1 => by_refdes!(J201, max5970), + 2 => by_refdes!(J202, max5970), + 3 => by_refdes!(J203, max5970), + 4 => by_refdes!(J204, max5970), + 5 => by_refdes!(J205, max5970), + 6 => by_refdes!(J206, max5970), + 7 => by_refdes!(J207, max5970), + 8 => by_refdes!(J208, max5970), + 9 => by_refdes!(J209, max5970), + 10 => by_refdes!(U15, max5970, 4), + 11 => by_refdes!(U54, max5970, 4), + _ => panic!(), + }; + let data = InventoryData::Max5970 { + voltage_sensors: SensorId::into_u32_array(sensors.voltage), + current_sensors: SensorId::into_u32_array(sensors.current), + }; + self.tx_buf + .try_encode_inventory(sequence, &name, || Ok(&data)); + } + 56 => { + let (name, _f, sensors) = by_refdes!(U58, max31790); + let data = InventoryData::Max31790 { + speed_sensors: SensorId::into_u32_array(sensors.speed), + }; + self.tx_buf + .try_encode_inventory(sequence, &name, || Ok(&data)); + } + 57..=58 => { + let (name, _f, sensors) = match index - 57 { + 0 => by_refdes!(U42, ltc4282, 4), + 1 => by_refdes!(U127, ltc4282), + _ => unreachable!(), + }; + let data = InventoryData::Ltc4282 { + voltage_sensor: sensors.voltage.into(), + current_sensor: sensors.current.into(), + }; + self.tx_buf + .try_encode_inventory(sequence, &name, || Ok(&data)) + } + // TODO add DIMMS to inventory here? + + // We need to specify INVENTORY_COUNT individually here to trigger + // an error if we've overlapped it with a previous range + Self::INVENTORY_COUNT | Self::INVENTORY_COUNT..=u32::MAX => { + return Err(InventoryDataResult::InvalidIndex) + } + } + + Ok(()) + } + + /// Looks up a Sharkfin VPD EEPROM by sharkfin index (0-9) + /// + /// Returns a designator (e.g. J200) and constructor function + fn get_sharkfin_vpd(i: usize) -> ([u8; 4], fn(TaskId) -> I2cDevice) { + let (name, f, _sensors) = match i { + 0 => by_refdes!(J200, at24csw080), + 1 => by_refdes!(J201, at24csw080), + 2 => by_refdes!(J202, at24csw080), + 3 => by_refdes!(J203, at24csw080), + 4 => by_refdes!(J204, at24csw080), + 5 => by_refdes!(J205, at24csw080), + 6 => by_refdes!(J206, at24csw080), + 7 => by_refdes!(J207, at24csw080), + 8 => by_refdes!(J208, at24csw080), + 9 => by_refdes!(J209, at24csw080), + _ => panic!("bad VPD index"), + }; + (name, f) + } + + /// Reads the 128-byte unique ID from an AT24CSW080 EEPROM + /// + /// `data` is passed in to reduce stack frame size, since we already require + /// an allocation for it on the caller's stack frame. + fn read_at24csw080_id( + &mut self, + sequence: u64, + name: &[u8], + f: fn(userlib::TaskId) -> I2cDevice, + data: &mut InventoryData, + ) { + // This should be done by the caller, but let's make it obviously + // correct (since we destructure it below). + *data = InventoryData::At24csw08xSerial([0u8; 16]); + let dev = At24Csw080::new(f(I2C.get_task_id())); + self.tx_buf.try_encode_inventory(sequence, name, || { + let InventoryData::At24csw08xSerial(id) = data else { + unreachable!(); + }; + for (i, b) in id.iter_mut().enumerate() { + *b = dev.read_security_register_byte(i as u8).map_err(|e| { + match e { + EepromError::I2cError(ResponseCode::NoDevice) => { + InventoryDataResult::DeviceAbsent + } + _ => InventoryDataResult::DeviceFailed, + } + })?; + } + Ok(data) + }); + } + + /// Reads the "BARC" value from a TLV-C blob in an AT24CSW080 EEPROM + /// + /// On success, packs the barcode into `self.tx_buf`; on failure, return an + /// error (`DeviceAbsent` if we saw `NoDevice`, or `DeviceFailed` on all + /// other errors). + fn read_eeprom_barcode( + &mut self, + sequence: u64, + name: &[u8], + f: fn(userlib::TaskId) -> I2cDevice, + ) { + let dev = f(I2C.get_task_id()); + let mut data = InventoryData::VpdIdentity(Default::default()); + self.tx_buf.try_encode_inventory(sequence, name, || { + let InventoryData::VpdIdentity(identity) = &mut data else { + unreachable!(); + }; + *identity = read_one_barcode(dev, &[(*b"BARC", 0)])?.into(); + Ok(&data) + }) + } + + /// Reads the fan EEPROM barcode values + /// + /// The fan EEPROM includes nested barcodes: + /// - The top-level `BARC`, for the assembly + /// - A nested value `SASY`, which contains four more `BARC` values for each + /// individual fan + /// + /// On success, packs the barcode into `self.tx_buf`; on failure, return an + /// error (`DeviceAbsent` if we saw `NoDevice`, or `DeviceFailed` on all + /// other errors). + fn read_fan_barcodes( + &mut self, + sequence: u64, + name: &[u8], + f: fn(userlib::TaskId) -> I2cDevice, + ) { + let dev = f(I2C.get_task_id()); + let mut data = InventoryData::FanIdentity { + identity: Default::default(), + vpd_identity: Default::default(), + fans: Default::default(), + }; + self.tx_buf.try_encode_inventory(sequence, name, || { + let InventoryData::FanIdentity { + identity, + vpd_identity, + fans: [fan0, fan1, fan2], + } = &mut data + else { + unreachable!(); + }; + *identity = read_one_barcode(dev, &[(*b"BARC", 0)])?.into(); + *vpd_identity = + read_one_barcode(dev, &[(*b"SASY", 0), (*b"BARC", 0)])?.into(); + *fan0 = + read_one_barcode(dev, &[(*b"SASY", 0), (*b"BARC", 1)])?.into(); + *fan1 = + read_one_barcode(dev, &[(*b"SASY", 0), (*b"BARC", 2)])?.into(); + *fan2 = + read_one_barcode(dev, &[(*b"SASY", 0), (*b"BARC", 3)])?.into(); + Ok(&data) + }) + } +} + +/// Free function to read a nested barcode, translating errors appropriately +fn read_one_barcode( + dev: I2cDevice, + path: &[([u8; 4], usize)], +) -> Result { + let eeprom = At24Csw080::new(dev); + let mut barcode = [0; 32]; + match drv_oxide_vpd::read_config_nested_from_into( + eeprom, + path, + &mut barcode, + ) { + Ok(n) => { + // extract barcode! + let identity = oxide_barcode::VpdIdentity::parse(&barcode[..n]) + .map_err(|_| InventoryDataResult::DeviceFailed)?; + Ok(identity) + } + Err( + VpdError::ErrorOnBegin(err) + | VpdError::ErrorOnRead(err) + | VpdError::ErrorOnNext(err) + | VpdError::InvalidChecksum(err), + ) if err + == tlvc::TlvcReadError::User(EepromError::I2cError( + ResponseCode::NoDevice, + )) => + { + Err(InventoryDataResult::DeviceAbsent) + } + Err(..) => Err(InventoryDataResult::DeviceFailed), + } +} + +include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); diff --git a/task/host-sp-comms/src/bsp/gimlet_bcde.rs b/task/host-sp-comms/src/bsp/gimlet_bcde.rs index b3281c10c4..3a5284d8dc 100644 --- a/task/host-sp-comms/src/bsp/gimlet_bcde.rs +++ b/task/host-sp-comms/src/bsp/gimlet_bcde.rs @@ -21,6 +21,12 @@ use host_sp_messages::{InventoryData, InventoryDataResult}; userlib::task_slot!(I2C, i2c_driver); userlib::task_slot!(SPI, spi_driver); +// This net is named SP_TO_SP3_INT_L in the schematic +pub(crate) const SP_TO_HOST_CPU_INT_L: drv_stm32xx_sys_api::PinSet = + drv_stm32xx_sys_api::Port::I.pin(7); +pub(crate) const SP_TO_HOST_CPU_INT_TYPE: drv_stm32xx_sys_api::OutputType = + drv_stm32xx_sys_api::OutputType::OpenDrain; + impl ServerImpl { /// Number of devices in our inventory pub(crate) const INVENTORY_COUNT: u32 = 72; diff --git a/task/host-sp-comms/src/bsp/gimletlet.rs b/task/host-sp-comms/src/bsp/gimletlet.rs index 266256bd63..e61f5eddda 100644 --- a/task/host-sp-comms/src/bsp/gimletlet.rs +++ b/task/host-sp-comms/src/bsp/gimletlet.rs @@ -8,6 +8,13 @@ use super::ServerImpl; use host_sp_messages::{InventoryData, InventoryDataResult}; +// gimletlet doesn't have an SP3 to interrupt, but we can wire up an LED +// to one of the exposed E2-E6 pins to see it visually. +pub(crate) const SP_TO_HOST_CPU_INT_L: drv_stm32xx_sys_api::PinSet = + drv_stm32xx_sys_api::Port::E.pin(2); +pub(crate) const SP_TO_HOST_CPU_INT_TYPE: drv_stm32xx_sys_api::OutputType = + drv_stm32xx_sys_api::OutputType::OpenDrain; + impl ServerImpl { /// Number of devices in our inventory pub(crate) const INVENTORY_COUNT: u32 = 1; diff --git a/task/host-sp-comms/src/bsp/grapefruit.rs b/task/host-sp-comms/src/bsp/grapefruit.rs index ff73ac14d6..7ab617803e 100644 --- a/task/host-sp-comms/src/bsp/grapefruit.rs +++ b/task/host-sp-comms/src/bsp/grapefruit.rs @@ -13,6 +13,13 @@ use host_sp_messages::{InventoryData, InventoryDataResult}; userlib::task_slot!(SPI, spi_driver); +// the CPU interrupt is not connected on grapefruit, so pick an +// unconnected GPIO +pub(crate) const SP_TO_HOST_CPU_INT_L: drv_stm32xx_sys_api::PinSet = + drv_stm32xx_sys_api::Port::B.pin(1); +pub(crate) const SP_TO_HOST_CPU_INT_TYPE: drv_stm32xx_sys_api::OutputType = + drv_stm32xx_sys_api::OutputType::OpenDrain; + impl ServerImpl { /// Number of devices in our inventory pub(crate) const INVENTORY_COUNT: u32 = 2; diff --git a/task/host-sp-comms/src/main.rs b/task/host-sp-comms/src/main.rs index cd337099d1..88923d98f2 100644 --- a/task/host-sp-comms/src/main.rs +++ b/task/host-sp-comms/src/main.rs @@ -55,8 +55,11 @@ use inventory::INVENTORY_API_VERSION; )] #[cfg_attr(target_board = "gimletlet-2", path = "bsp/gimletlet.rs")] #[cfg_attr(target_board = "grapefruit", path = "bsp/grapefruit.rs")] +#[cfg_attr(target_board = "cosmo-a", path = "bsp/cosmo_a.rs")] mod bsp; +use bsp::SP_TO_HOST_CPU_INT_L; + mod tx_buf; use tx_buf::TxBuf; @@ -1609,35 +1612,12 @@ fn configure_uart_device(sys: &sys_api::Sys) -> Usart { ) } -cfg_if::cfg_if! { - if #[cfg(any( - target_board = "gimlet-b", - target_board = "gimlet-c", - target_board = "gimlet-d", - target_board = "gimlet-e", - target_board = "gimlet-f", - ))] { - // This net is named SP_TO_SP3_INT_L in the schematic - const SP_TO_HOST_CPU_INT_L: sys_api::PinSet = sys_api::Port::I.pin(7); - } else if #[cfg(target_board = "gimletlet-2")] { - // gimletlet doesn't have an SP3 to interrupt, but we can wire up an LED - // to one of the exposed E2-E6 pins to see it visually. - const SP_TO_HOST_CPU_INT_L: sys_api::PinSet = sys_api::Port::E.pin(2); - } else if #[cfg(target_board = "grapefruit")] { - // the CPU interrupt is not connected on grapefruit, so pick an - // unconnected GPIO - const SP_TO_HOST_CPU_INT_L: sys_api::PinSet = sys_api::Port::B.pin(1); - } else { - compile_error!("unsupported target board"); - } -} - fn sp_to_sp3_interrupt_enable(sys: &sys_api::Sys) { sys.gpio_set(SP_TO_HOST_CPU_INT_L); sys.gpio_configure_output( SP_TO_HOST_CPU_INT_L, - sys_api::OutputType::OpenDrain, + bsp::SP_TO_HOST_CPU_INT_TYPE, sys_api::Speed::Low, sys_api::Pull::None, ); diff --git a/task/host-sp-comms/src/tx_buf.rs b/task/host-sp-comms/src/tx_buf.rs index ab74ead5a9..a37834f86a 100644 --- a/task/host-sp-comms/src/tx_buf.rs +++ b/task/host-sp-comms/src/tx_buf.rs @@ -144,7 +144,7 @@ impl TxBuf { matches!(self.state, State::Idle) } - #[cfg(feature = "grapefruit")] + #[cfg(any(feature = "grapefruit", feature = "cosmo"))] pub(crate) fn should_send_periodic_zero_bytes(&self) -> bool { false } diff --git a/task/net/Cargo.toml b/task/net/Cargo.toml index 8aeadbc54c..258e19a585 100644 --- a/task/net/Cargo.toml +++ b/task/net/Cargo.toml @@ -46,6 +46,7 @@ use-spi-core = ["drv-stm32h7-spi-server-core"] mgmt = ["drv-spi-api", "ksz8463", "drv-user-leds-api", "task-net-api/mgmt"] vpd-mac = ["task-packrat-api"] gimlet = ["drv-cpu-seq-api"] +cosmo = ["drv-cpu-seq-api"] sidecar = ["drv-sidecar-seq-api"] minibar = [] medusa = ["drv-medusa-seq-api"] diff --git a/task/net/src/bsp/cosmo_a.rs b/task/net/src/bsp/cosmo_a.rs new file mode 100644 index 0000000000..0dc05d7bab --- /dev/null +++ b/task/net/src/bsp/cosmo_a.rs @@ -0,0 +1,153 @@ +// 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 Cosmo rev A hardware + +#[cfg(not(all(feature = "ksz8463", feature = "mgmt", feature = "vlan")))] +compile_error!("this BSP requires the ksz8463, mgmt, and vlan features"); + +use crate::{ + bsp_support::{self, Ksz8463}, + mgmt, notifications, pins, +}; +use drv_cpu_seq_api::PowerState; +use drv_spi_api::SpiServer; +use drv_stm32h7_eth as eth; +use drv_stm32xx_sys_api::{Alternate, Port, Sys}; +use task_jefe_api::Jefe; +use task_net_api::{ + ManagementCounters, ManagementLinkStatus, MgmtError, PhyError, +}; +use userlib::{sys_recv_notification, FromPrimitive}; +use vsc7448_pac::types::PhyRegisterAddress; + +//////////////////////////////////////////////////////////////////////////////// + +pub struct BspImpl(mgmt::Bsp); + +impl crate::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), + crs_dv: Port::A.pin(7), + tx_en: Port::G.pin(11), + txd0: Port::G.pin(13), + txd1: Port::G.pin(12), + rxd0: Port::C.pin(4), + rxd1: Port::C.pin(5), + af: Alternate::AF11, + } + .configure(sys); + + pins::MdioPins { + mdio: Port::A.pin(2), + mdc: Port::C.pin(1), + af: Alternate::AF11, + } + .configure(sys); + } + + fn preinit() { + // Wait for the sequencer to turn on the clock. This requires that Jefe + // state change notifications are routed to our notification bit 3. + let jefe = Jefe::from(crate::JEFE.get_task_id()); + + loop { + // This laborious list is intended to ensure that new power states + // have to be added explicitly here. + match PowerState::from_u32(jefe.get_state()) { + Some(PowerState::A2) + | Some(PowerState::A2PlusFans) + | Some(PowerState::A1) + | Some(PowerState::A0) + | Some(PowerState::A0PlusHP) + | Some(PowerState::A0Thermtrip) + | Some(PowerState::A0Reset) => { + break; + } + None => { + // This happens before we're in a valid power state. + // + // Only listen to our Jefe notification. + sys_recv_notification( + notifications::JEFE_STATE_CHANGE_MASK, + ); + } + } + } + } + + fn new(eth: ð::Ethernet, sys: &Sys) -> Self { + let spi = bsp_support::claim_spi(sys); + let ksz8463_dev = spi.device(drv_spi_api::devices::KSZ8463); + Self( + mgmt::Config { + power_en: None, // power is enabled automatically + slow_power_en: false, + power_good: &[], // TODO + + ksz8463: Ksz8463::new(ksz8463_dev), + + // SP_TO_KSZ8463_RESET_L + ksz8463_nrst: Port::C.pin(2), + ksz8463_rst_type: mgmt::Ksz8463ResetSpeed::Normal, + ksz8463_vlan_mode: ksz8463::VLanMode::Mandatory, + // SP_TO_VSC8562_COMA_MODE_V3P3 + vsc85x2_coma_mode: Some(Port::C.pin(3)), + + // SP_TO_VSC8562_RESET_L_V3P3 + vsc85x2_nrst: Port::A.pin(8), + + vsc85x2_base_port: 0b11110, // Based on resistor strapping + } + .build(sys, eth), + ) + } + + 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 67165537d3..9098d3e1ea 100644 --- a/task/net/src/main.rs +++ b/task/net/src/main.rs @@ -47,6 +47,7 @@ mod server; #[cfg_attr(target_board = "medusa-a", path = "bsp/medusa_a.rs")] #[cfg_attr(target_board = "grapefruit", path = "bsp/grapefruit.rs")] #[cfg_attr(target_board = "minibar", path = "bsp/minibar.rs")] +#[cfg_attr(target_board = "cosmo-a", path = "bsp/cosmo_a.rs")] mod bsp; #[cfg_attr(feature = "vlan", path = "server_vlan.rs")] diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index 8b7aeb80e8..461eeb2dd7 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -25,6 +25,7 @@ idol.workspace = true build-util = { path = "../../build/util" } [features] +cosmo = ["drv-cpu-seq-api"] gimlet = ["drv-cpu-seq-api"] grapefruit = [] boot-kmdb = [] diff --git a/task/packrat/src/cosmo.rs b/task/packrat/src/cosmo.rs new file mode 100644 index 0000000000..28297e44ec --- /dev/null +++ b/task/packrat/src/cosmo.rs @@ -0,0 +1,61 @@ +// 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/. + +//! Cosmo-specific packrat data. + +use task_packrat_api::HostStartupOptions; + +pub(crate) struct CosmoData { + host_startup_options: &'static mut HostStartupOptions, +} + +const fn default_host_startup_options() -> HostStartupOptions { + if cfg!(feature = "boot-kmdb") { + // We have to do this because const fn. + let bits = HostStartupOptions::STARTUP_KMDB.bits() + | HostStartupOptions::STARTUP_PROM.bits() + | HostStartupOptions::STARTUP_VERBOSE.bits(); + match HostStartupOptions::from_bits(bits) { + Some(options) => options, + None => panic!("must be valid at compile-time"), + } + } else { + HostStartupOptions::empty() + } +} + +pub(crate) struct StaticBufs { + host_startup_options: HostStartupOptions, +} + +impl StaticBufs { + pub(crate) const fn new() -> Self { + Self { + host_startup_options: default_host_startup_options(), + } + } +} + +impl CosmoData { + pub(crate) fn new( + StaticBufs { + ref mut host_startup_options, + }: &'static mut StaticBufs, + ) -> Self { + Self { + host_startup_options, + } + } + + pub(crate) fn host_startup_options(&self) -> HostStartupOptions { + *self.host_startup_options + } + + pub(crate) fn set_host_startup_options( + &mut self, + options: HostStartupOptions, + ) { + *self.host_startup_options = options; + } +} diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index bcd004df15..6c24d914a7 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -45,6 +45,9 @@ mod gimlet; #[cfg(feature = "grapefruit")] mod grapefruit; +#[cfg(feature = "cosmo")] +mod cosmo; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[allow(dead_code)] // not all variants are used, depending on cargo features enum Trace { @@ -92,12 +95,16 @@ fn main() -> ! { identity: Option, #[cfg(feature = "gimlet")] gimlet_bufs: gimlet::StaticBufs, + #[cfg(feature = "cosmo")] + cosmo_bufs: cosmo::StaticBufs, } let StaticBufs { ref mut mac_address_block, ref mut identity, #[cfg(feature = "gimlet")] ref mut gimlet_bufs, + #[cfg(feature = "cosmo")] + ref mut cosmo_bufs, } = { static BUFS: ClaimOnceCell = ClaimOnceCell::new(StaticBufs { @@ -105,6 +112,8 @@ fn main() -> ! { identity: None, #[cfg(feature = "gimlet")] gimlet_bufs: gimlet::StaticBufs::new(), + #[cfg(feature = "cosmo")] + cosmo_bufs: cosmo::StaticBufs::new(), }); BUFS.claim() }; @@ -116,6 +125,8 @@ fn main() -> ! { gimlet_data: gimlet::GimletData::new(gimlet_bufs), #[cfg(feature = "grapefruit")] grapefruit_data: grapefruit::GrapefruitData::new(), + #[cfg(feature = "cosmo")] + cosmo_data: cosmo::CosmoData::new(cosmo_bufs), }; let mut buffer = [0; idl::INCOMING_SIZE]; @@ -131,6 +142,8 @@ struct ServerImpl { gimlet_data: gimlet::GimletData, #[cfg(feature = "grapefruit")] grapefruit_data: grapefruit::GrapefruitData, + #[cfg(feature = "cosmo")] + cosmo_data: cosmo::CosmoData, } impl ServerImpl { @@ -219,7 +232,19 @@ impl idl::InOrderPackratImpl for ServerImpl { Ok(self.grapefruit_data.host_startup_options()) } - #[cfg(not(any(feature = "gimlet", feature = "grapefruit")))] + #[cfg(feature = "cosmo")] + fn get_next_boot_host_startup_options( + &mut self, + _: &RecvMessage, + ) -> Result> { + Ok(self.cosmo_data.host_startup_options()) + } + + #[cfg(not(any( + feature = "gimlet", + feature = "grapefruit", + feature = "cosmo" + )))] fn get_next_boot_host_startup_options( &mut self, _: &RecvMessage, @@ -257,7 +282,25 @@ impl idl::InOrderPackratImpl for ServerImpl { Ok(()) } - #[cfg(not(any(feature = "gimlet", feature = "grapefruit")))] + #[cfg(feature = "cosmo")] + fn set_next_boot_host_startup_options( + &mut self, + _: &RecvMessage, + host_startup_options: HostStartupOptions, + ) -> Result<(), RequestError> { + ringbuf_entry!(Trace::SetNextBootHostStartupOptions( + host_startup_options + )); + self.cosmo_data + .set_host_startup_options(host_startup_options); + Ok(()) + } + + #[cfg(not(any( + feature = "gimlet", + feature = "cosmo", + feature = "grapefruit" + )))] fn set_next_boot_host_startup_options( &mut self, _: &RecvMessage, diff --git a/task/power/Cargo.toml b/task/power/Cargo.toml index 2582db2a21..e86641563f 100644 --- a/task/power/Cargo.toml +++ b/task/power/Cargo.toml @@ -36,6 +36,7 @@ build-util = { path = "../../build/util" } [features] gimlet = ["drv-cpu-seq-api", "h753"] +cosmo = ["drv-cpu-seq-api", "h753"] sidecar = ["drv-sidecar-seq-api", "h753"] psc = ["drv-stm32xx-sys-api", "h753"] dc2024 = ["drv-stm32xx-sys-api", "h753"] diff --git a/task/power/src/bsp/cosmo_a.rs b/task/power/src/bsp/cosmo_a.rs new file mode 100644 index 0000000000..29cc69b631 --- /dev/null +++ b/task/power/src/bsp/cosmo_a.rs @@ -0,0 +1,379 @@ +// 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/. + +// Based on the `gimlet_bcdef.rs` implementation in this folder +use crate::{ + i2c_config, i2c_config::sensors, Device, PowerControllerConfig, PowerState, + SensorId, +}; + +use drv_i2c_devices::max5970::*; +use ringbuf::*; +use userlib::units::*; + +pub(crate) const CONTROLLER_CONFIG_LEN: usize = 43; +const MAX5970_CONFIG_LEN: usize = 22; + +pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; + CONTROLLER_CONFIG_LEN] = [ + rail_controller!(IBC, bmr491, v12_sys_a2, A2), + rail_controller!(Core, raa229620A, vddcr_cpu0_a0, A0), + rail_controller!(Core, raa229620A, vddcr_soc_a0, A0), + rail_controller!(Core, raa229620A, vddcr_cpu1_a0, A0), + rail_controller!(Core, raa229620A, vddio_sp5_a0, A0), + rail_controller_notemp!(Core, isl68224, v1p1_sp5_a0, A0), + rail_controller_notemp!(Core, isl68224, v1p8_sp5_a1, A0), // XXX A0 or A2? + rail_controller_notemp!(Core, isl68224, v3p3_sp5_a1, A0), // XXX A0 or A2? + rail_controller!(Sys, tps546B24A, v3p3_sp_a2, A2), + rail_controller!(Sys, tps546B24A, v5_sys_a2, A2), + rail_controller!(Sys, tps546B24A, v1p8_sys_a2, A2), + rail_controller!(Sys, tps546B24A, v0p96_nic_vdd_a0hp, A0), + adm1272_controller!(HotSwap, v54p5_ibc_a3, A2, Ohms(0.000_750)), + lm5066_controller!( + Fan, + v54p5_fan_east, + A2, + Ohms(0.007), + drv_i2c_devices::lm5066::CurrentLimitStrap::VDD + ), + lm5066_controller!( + Fan, + v54p5_fan_central, + A2, + Ohms(0.007), + drv_i2c_devices::lm5066::CurrentLimitStrap::VDD + ), + lm5066_controller!( + Fan, + v54p5_fan_west, + A2, + Ohms(0.007), + drv_i2c_devices::lm5066::CurrentLimitStrap::VDD + ), + max5970_controller!(HotSwapIO, v3p3_m2a_a0hp, A0, Ohms(0.003)), + max5970_controller!(HotSwapIO, v3p3_m2b_a0hp, A0, Ohms(0.003)), + max5970_controller!(HotSwapIO, v12_u2a_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2a_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v12_u2b_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2b_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v12_u2c_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2c_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v12_u2d_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2d_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v12_u2e_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2e_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v12_u2f_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2f_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v12_u2g_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2g_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v12_u2h_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2h_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v12_u2i_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2i_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v12_u2j_a0, A0, Ohms(0.005)), + max5970_controller!(HotSwapIO, v3p3_u2j_a0, A0, Ohms(0.005)), + ltc4282_controller!(HotSwapIO, v12_mcio_a0hp, A0, Ohms(0.001)), + ltc4282_controller!(HotSwapIO, v12_ddr5_abcdef_a0, A0, Ohms(0.001)), + ltc4282_controller!(HotSwapIO, v12_ddr5_ghijkl_a0, A0, Ohms(0.001)), + max5970_controller!(HotSwapIO, v12p0_nic_a0hp, A0, Ohms(0.003)), + max5970_controller!(HotSwapIO, v5p0_nic_a0hp, A0, Ohms(0.003)), +]; + +pub(crate) fn get_state() -> PowerState { + userlib::task_slot!(SEQUENCER, cosmo_seq); + + use drv_cpu_seq_api as seq_api; + + let sequencer = seq_api::Sequencer::from(SEQUENCER.get_task_id()); + + // + // We deliberately enumerate all power states to force the addition of + // new ones to update this code. + // + match sequencer.get_state() { + seq_api::PowerState::A0 + | seq_api::PowerState::A0PlusHP + | seq_api::PowerState::A0Thermtrip + | seq_api::PowerState::A0Reset => PowerState::A0, + seq_api::PowerState::A1 + | seq_api::PowerState::A2 + | seq_api::PowerState::A2PlusFans => PowerState::A2, + } +} + +/// Number of seconds (really, timer firings) between writes to the trace +/// buffer. +const TRACE_SECONDS: u32 = 10; + +/// Number of trace records to store. +/// +/// TODO: explain rationale for this value. +const TRACE_DEPTH: usize = 52; + +/// This enum and its corresponding ringbuf are being used to attempt to isolate +/// cases of this bug: +/// +/// https://github.com/oxidecomputer/mfg-quality/issues/140 +/// +/// Unless that bug report is closed or says otherwise, be careful modifying +/// this type, as you may break data collection. +/// +/// The basic theory here is: +/// +/// - Every `TRACE_SECONDS` seconds, the task wakes up and writes one `Now` +/// entry. +/// +/// - We then record one `Max5970` trace entry per MAX5970 being monitored. +/// +/// Tooling can then collect this ringbuf periodically and get recent events. +#[derive(Copy, Clone, PartialEq)] +enum Trace { + /// Configuration of the MAX5970 failed + Max5970ConfigFailed { + u2_index: usize, + err: drv_i2c_api::ResponseCode, + }, + + /// Written before trace records; the `u32` is the number of times the task + /// has woken up to process its timer. This is not exactly equivalent to + /// seconds because of the way the timer is maintained, but is approximately + /// seconds. + Now(u32), + + /// Trace record written for each MAX5970. + /// + /// The `last_bounce_detected` field and those starting with `crossbounce_` + /// are copied from running state and may not be updated on every trace + /// event. The other fields are read while emitting the trace record and + /// should be current. + Max5970 { + sensor: SensorId, + last_bounce_detected: Option, + status0: u8, + status1: u8, + status3: u8, + fault0: u8, + fault1: u8, + fault2: u8, + min_iout: f32, + max_iout: f32, + min_vout: f32, + max_vout: f32, + crossbounce_min_iout: f32, + crossbounce_max_iout: f32, + crossbounce_min_vout: f32, + crossbounce_max_vout: f32, + }, + None, +} + +ringbuf!(Trace, TRACE_DEPTH, Trace::None); + +/// Records fields from `dev` and merges them with previous state from `peaks`, +/// updating `peaks` in the process. +/// +/// If any I2C operation fails, this will abort its work and return. +fn trace_max5970( + dev: &Max5970, + sensor: SensorId, + peaks: &mut Max5970Peaks, + now: u32, +) { + let max_vout = match dev.max_vout() { + Ok(Volts(v)) => v, + _ => return, + }; + + let min_vout = match dev.min_vout() { + Ok(Volts(v)) => v, + _ => return, + }; + + let max_iout = match dev.max_iout() { + Ok(Amperes(a)) => a, + _ => return, + }; + + let min_iout = match dev.min_iout() { + Ok(Amperes(a)) => a, + _ => return, + }; + + // TODO: this update should probably happen after all I/O is done. + if peaks.iout.bounced(min_iout, max_iout) + || peaks.vout.bounced(min_vout, max_vout) + { + peaks.last_bounce_detected = Some(now); + } + + ringbuf_entry!(Trace::Max5970 { + sensor, + last_bounce_detected: peaks.last_bounce_detected, + status0: match dev.read_reg(Register::status0) { + Ok(reg) => reg, + _ => return, + }, + status1: match dev.read_reg(Register::status1) { + Ok(reg) => reg, + _ => return, + }, + status3: match dev.read_reg(Register::status3) { + Ok(reg) => reg, + _ => return, + }, + fault0: match dev.read_reg(Register::fault0) { + Ok(reg) => reg, + _ => return, + }, + fault1: match dev.read_reg(Register::fault1) { + Ok(reg) => reg, + _ => return, + }, + fault2: match dev.read_reg(Register::fault2) { + Ok(reg) => reg, + _ => return, + }, + min_iout, + max_iout, + min_vout, + max_vout, + crossbounce_min_iout: peaks.iout.crossbounce_min, + crossbounce_max_iout: peaks.iout.crossbounce_max, + crossbounce_min_vout: peaks.vout.crossbounce_min, + crossbounce_max_vout: peaks.vout.crossbounce_max, + }); +} + +#[derive(Copy, Clone)] +struct Max5970Peak { + min: f32, + max: f32, + crossbounce_min: f32, + crossbounce_max: f32, +} + +impl Default for Max5970Peak { + fn default() -> Self { + Self { + min: f32::MAX, + max: f32::MIN, + crossbounce_min: f32::MAX, + crossbounce_max: f32::MIN, + } + } +} + +impl Max5970Peak { + /// + /// If we see the drives lose power, it is helpful to disambiguate PDN issues + /// from the power being explicitly disabled via system software (e.g., via + /// CEM_TO_PCIEHP_PWREN on Sharkfin). The MAX5970 doesn't have a way of + /// recording power cycles, but we know that if we see the peaks travel in + /// the wrong direction (that is, a max that is less than the previous max + /// or a minimum that is greater than our previous minimum) then there must + /// have been a power cycle. This can clearly yield false negatives, but + /// it will not yield false positives: if [`bounced`] returns true, one can + /// know with confidence that the power has been cycled. Note that we also + /// use this opportunity to retain the peaks across a bounce, which would + /// would otherwise be lost. + /// + fn bounced(&mut self, min: f32, max: f32) -> bool { + let bounced = min > self.min || max < self.max; + self.min = min; + self.max = max; + + if min < self.crossbounce_min { + self.crossbounce_min = min; + } + + if max > self.crossbounce_max { + self.crossbounce_max = max; + } + + bounced + } +} + +#[derive(Copy, Clone, Default)] +struct Max5970Peaks { + iout: Max5970Peak, + vout: Max5970Peak, + last_bounce_detected: Option, +} + +pub(crate) struct State { + fired: u32, + peaks: [Max5970Peaks; MAX5970_CONFIG_LEN], +} + +impl State { + pub(crate) fn init() -> Self { + // Tweak the thresholds for the U.2 MAX5970 12V outputs + // + // Current Sense Range: 50 mV (set by a resistor on the board) + // Desired Fast Trip Current: 6A (DAC_CH0: 0x99) + // Desired Fast-to-Slow Ratio: 200% (default value) + // Resulting Slow Trip Current: 3A + let i2c_task = super::I2C.get_task_id(); + + for (i, builder) in [ + i2c_config::pmbus::v12_u2a_a0, + i2c_config::pmbus::v12_u2b_a0, + i2c_config::pmbus::v12_u2c_a0, + i2c_config::pmbus::v12_u2d_a0, + i2c_config::pmbus::v12_u2e_a0, + i2c_config::pmbus::v12_u2f_a0, + i2c_config::pmbus::v12_u2g_a0, + i2c_config::pmbus::v12_u2h_a0, + i2c_config::pmbus::v12_u2i_a0, + i2c_config::pmbus::v12_u2j_a0, + ] + .iter() + .enumerate() + { + let (dev, rail) = (builder)(i2c_task); + let m = Max5970::new(&dev, rail, Ohms(0.005)); + if let Err(err) = m.set_dac_fast(0x99) { + ringbuf_entry!(Trace::Max5970ConfigFailed { u2_index: i, err }); + } + } + + Self { + fired: 0, + peaks: [Max5970Peaks::default(); MAX5970_CONFIG_LEN], + } + } + + pub(crate) fn handle_timer_fired( + &mut self, + devices: &[Device], + state: PowerState, + ) { + // + // Trace the detailed state every ten seconds, provided that we are in A0. + // + if state == PowerState::A0 && self.fired % TRACE_SECONDS == 0 { + ringbuf_entry!(Trace::Now(self.fired)); + + for ((dev, sensor), peak) in CONTROLLER_CONFIG + .iter() + .zip(devices.iter()) + .filter_map(|(c, dev)| { + if let Device::Max5970(dev) = dev { + Some((dev, c.current)) + } else { + None + } + }) + .zip(self.peaks.iter_mut()) + { + trace_max5970(dev, sensor, peak, self.fired); + } + } + + self.fired += 1; + } +} + +pub const HAS_RENDMP_BLACKBOX: bool = true; diff --git a/task/power/src/main.rs b/task/power/src/main.rs index a5a9a0d6be..32697c0faa 100644 --- a/task/power/src/main.rs +++ b/task/power/src/main.rs @@ -469,6 +469,7 @@ macro_rules! mwocp68_controller { )] #[cfg_attr(target_board = "gimletlet-2", path = "bsp/gimletlet_2.rs")] #[cfg_attr(target_board = "minibar", path = "bsp/minibar.rs")] +#[cfg_attr(target_board = "cosmo-a", path = "bsp/cosmo_a.rs")] mod bsp; //////////////////////////////////////////////////////////////////////////////// diff --git a/task/thermal/Cargo.toml b/task/thermal/Cargo.toml index 7a4447d8a5..484e4b78b2 100644 --- a/task/thermal/Cargo.toml +++ b/task/thermal/Cargo.toml @@ -36,6 +36,7 @@ build-util = { path = "../../build/util" } [features] gimlet = ["drv-cpu-seq-api", "h753"] +cosmo = ["drv-cpu-seq-api", "h753"] sidecar = ["drv-sidecar-seq-api", "drv-transceivers-api", "h753"] medusa = ["h753", "drv-transceivers-api"] grapefruit = ["h753"] diff --git a/task/thermal/src/bsp/cosmo_a.rs b/task/thermal/src/bsp/cosmo_a.rs new file mode 100644 index 0000000000..812a915a9c --- /dev/null +++ b/task/thermal/src/bsp/cosmo_a.rs @@ -0,0 +1,370 @@ +// 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 Cosmo rev A hardware + +use crate::{ + control::{ + ChannelType, ControllerInitError, Device, FanControl, Fans, + InputChannel, Max31790State, PidConfig, TemperatureSensor, + }, + i2c_config::{devices, sensors}, +}; +pub use drv_cpu_seq_api::SeqError; +use drv_cpu_seq_api::{PowerState, Sequencer, StateChangeReason}; +use task_sensor_api::SensorId; +use task_thermal_api::ThermalProperties; +use userlib::{task_slot, units::Celsius, TaskId, UnwrapLite}; + +task_slot!(SEQ, cosmo_seq); + +// We monitor the TMP117 air temperature sensors, but don't use them as part of +// the control loop. +const NUM_TEMPERATURE_SENSORS: usize = sensors::NUM_TMP117_TEMPERATURE_SENSORS; + +const NUM_NVME_BMC_TEMPERATURE_SENSORS: usize = + sensors::NUM_NVME_BMC_TEMPERATURE_SENSORS; + +// The control loop is driven by CPU, NIC, and BMC temperatures +// XXX we should also monitor DIMM temperatures here +pub const NUM_TEMPERATURE_INPUTS: usize = sensors::NUM_SBTSI_TEMPERATURE_SENSORS + + sensors::NUM_TMP451_TEMPERATURE_SENSORS + + NUM_NVME_BMC_TEMPERATURE_SENSORS; + +// Every temperature sensor on Cosmo is owned by this task +pub const NUM_DYNAMIC_TEMPERATURE_INPUTS: usize = 0; + +// We've got 6 fans, driven from a single MAX31790 IC +pub const NUM_FANS: usize = drv_i2c_devices::max31790::MAX_FANS as usize; + +/// This controller is tuned and ready to go +pub const USE_CONTROLLER: bool = true; + +pub(crate) struct Bsp { + /// Controlled sensors + pub inputs: &'static [InputChannel], + pub dynamic_inputs: &'static [SensorId], + + /// Monitored sensors + pub misc_sensors: &'static [TemperatureSensor], + + /// Fan control IC + fctrl: Max31790State, + + /// Handle to the sequencer task, to query power state + seq: Sequencer, + + /// Tuning for the PID controller + pub pid_config: PidConfig, +} + +bitflags::bitflags! { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub struct PowerBitmask: u32 { + // As far as I know, we don't have any devices which are active only + // in A2; you probably want to use `A0_OR_A2` instead. + const A2 = 0b00000001; + const A0 = 0b00000010; + const A0_OR_A2 = Self::A0.bits() | Self::A2.bits(); + } +} + +impl Bsp { + pub fn fan_control( + &mut self, + fan: crate::Fan, + ) -> Result, ControllerInitError> { + let fctrl = self.fctrl.try_initialize()?; + Ok(FanControl::Max31790(fctrl, fan.0.try_into().unwrap_lite())) + } + + pub fn for_each_fctrl( + &mut self, + mut fctrl: impl FnMut(FanControl<'_>), + ) -> Result<(), ControllerInitError> { + fctrl(self.fan_control(0.into())?); + Ok(()) + } + + pub fn power_down(&self) -> Result<(), SeqError> { + self.seq + .set_state_with_reason(PowerState::A2, StateChangeReason::Overheat) + } + + pub fn power_mode(&self) -> PowerBitmask { + match self.seq.get_state() { + PowerState::A0PlusHP + | PowerState::A0 + | PowerState::A1 + | PowerState::A0Reset => PowerBitmask::A0, + PowerState::A2 + | PowerState::A2PlusFans + | PowerState::A0Thermtrip => PowerBitmask::A2, + } + } + + // We assume Cosmo fan presence cannot change + pub fn get_fan_presence(&self) -> Result, SeqError> { + // Awkwardly build the fan array, because there's not a great way to + // build a fixed-size array from a function + let mut fans = Fans::new(); + for i in 0..fans.len() { + fans[i] = Some(sensors::MAX31790_SPEED_SENSORS[i]); + } + Ok(fans) + } + + pub fn new(i2c_task: TaskId) -> Self { + // Initializes and build a handle to the fan controller IC + let fctrl = Max31790State::new(&devices::max31790(i2c_task)[0]); + + // Handle for the sequencer task, which we check for power state + let seq = Sequencer::from(SEQ.get_task_id()); + + Self { + seq, + fctrl, + + // Based on experimental tuning! + pid_config: PidConfig { + zero: 35.0, + gain_p: 1.75, + gain_i: 0.0135, + gain_d: 0.4, + min_output: 0.0, + max_output: 100.0, + }, + + inputs: &INPUTS, + dynamic_inputs: &[], + + // We monitor and log all of the air temperatures + misc_sensors: &MISC_SENSORS, + } + } +} + +// In general, see RFD 276 Detailed Thermal Loop Design for references. +// TODO: temperature_slew_deg_per_sec is made up. + +// Thermal throttling begins at 78° for WD-SN840 (primary source) and +// 75° for Micron-9300 (secondary source). +// +// For the WD part, thermal shutdown is at 84°C, which also voids the +// warranty. The Micron drive doesn't specify a thermal shutdown +// temperature, but the "critical" temperature is 80°C. +// +// All temperature are "composite" temperatures. +const U2_THERMALS: ThermalProperties = ThermalProperties { + target_temperature: Celsius(65f32), + critical_temperature: Celsius(70f32), + power_down_temperature: Celsius(75f32), + temperature_slew_deg_per_sec: 0.5, +}; + +// The Micron-7300 (primary source) begins throttling at 72°, and its "critical +// composite temperature" is 76°. The WD-SN640 (secondary source) begins +// throttling at 77°C. +const M2_THERMALS: ThermalProperties = ThermalProperties { + target_temperature: Celsius(65f32), + critical_temperature: Celsius(70f32), + power_down_temperature: Celsius(75f32), + temperature_slew_deg_per_sec: 0.5, +}; + +// The CPU doesn't actually report true temperature; it reports a +// unitless "temperature control value". Throttling starts at 95, and +// becomes more aggressive at 100. Let's aim for 80, to stay well below +// the throttling range. +const CPU_THERMALS: ThermalProperties = ThermalProperties { + target_temperature: Celsius(80f32), + critical_temperature: Celsius(90f32), + power_down_temperature: Celsius(100f32), + temperature_slew_deg_per_sec: 0.5, +}; + +// The T6's specifications aren't clearly detailed anywhere. +const T6_THERMALS: ThermalProperties = ThermalProperties { + target_temperature: Celsius(70f32), + critical_temperature: Celsius(80f32), + power_down_temperature: Celsius(85f32), + temperature_slew_deg_per_sec: 0.5, +}; + +const INPUTS: [InputChannel; NUM_TEMPERATURE_INPUTS] = [ + InputChannel::new( + TemperatureSensor::new( + Device::M2, + devices::nvme_bmc_m2_a, + sensors::NVME_BMC_M2_A_TEMPERATURE_SENSOR, + ), + M2_THERMALS, + PowerBitmask::A0, + ChannelType::Removable, + ), + InputChannel::new( + TemperatureSensor::new( + Device::M2, + devices::nvme_bmc_m2_b, + sensors::NVME_BMC_M2_B_TEMPERATURE_SENSOR, + ), + M2_THERMALS, + PowerBitmask::A0, + ChannelType::Removable, + ), + InputChannel::new( + TemperatureSensor::new( + Device::CPU, + devices::sbtsi_cpu, + sensors::SBTSI_CPU_TEMPERATURE_SENSOR, + ), + CPU_THERMALS, + PowerBitmask::A0, + ChannelType::MustBePresent, + ), + InputChannel::new( + TemperatureSensor::new( + Device::Tmp451(drv_i2c_devices::tmp451::Target::Remote), + devices::tmp451_t6, + sensors::TMP451_T6_TEMPERATURE_SENSOR, + ), + T6_THERMALS, + PowerBitmask::A0, + ChannelType::MustBePresent, + ), + // U.2 drives + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n0, + sensors::NVME_BMC_U2_N0_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n1, + sensors::NVME_BMC_U2_N1_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n2, + sensors::NVME_BMC_U2_N2_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n3, + sensors::NVME_BMC_U2_N3_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n4, + sensors::NVME_BMC_U2_N4_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n5, + sensors::NVME_BMC_U2_N5_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n6, + sensors::NVME_BMC_U2_N6_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n7, + sensors::NVME_BMC_U2_N7_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n8, + sensors::NVME_BMC_U2_N8_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), + InputChannel::new( + TemperatureSensor::new( + Device::U2, + devices::nvme_bmc_u2_n9, + sensors::NVME_BMC_U2_N9_TEMPERATURE_SENSOR, + ), + U2_THERMALS, + PowerBitmask::A0, + ChannelType::RemovableAndErrorProne, + ), +]; + +const MISC_SENSORS: [TemperatureSensor; NUM_TEMPERATURE_SENSORS] = [ + TemperatureSensor::new( + Device::Tmp117, + devices::tmp117_southwest, + sensors::TMP117_SOUTHWEST_TEMPERATURE_SENSOR, + ), + TemperatureSensor::new( + Device::Tmp117, + devices::tmp117_southeast, + sensors::TMP117_SOUTHEAST_TEMPERATURE_SENSOR, + ), + TemperatureSensor::new( + Device::Tmp117, + devices::tmp117_northwest, + sensors::TMP117_NORTHWEST_TEMPERATURE_SENSOR, + ), + TemperatureSensor::new( + Device::Tmp117, + devices::tmp117_northeast, + sensors::TMP117_NORTHEAST_TEMPERATURE_SENSOR, + ), + TemperatureSensor::new( + Device::Tmp117, + devices::tmp117_north, + sensors::TMP117_NORTH_TEMPERATURE_SENSOR, + ), + TemperatureSensor::new( + Device::Tmp117, + devices::tmp117_south, + sensors::TMP117_SOUTH_TEMPERATURE_SENSOR, + ), +]; diff --git a/task/thermal/src/control.rs b/task/thermal/src/control.rs index e43aec989a..4c3b69ed9f 100644 --- a/task/thermal/src/control.rs +++ b/task/thermal/src/control.rs @@ -1056,7 +1056,9 @@ impl<'a> ThermalControl<'a> { ControlResult::Pwm(PWMDuty(pwm as u8)) } else { - ControlResult::Pwm(PWMDuty(100)) + ControlResult::Pwm(PWMDuty( + self.pid_config.max_output as u8, + )) } } ThermalControlState::Running { values, pid } => { @@ -1091,7 +1093,9 @@ impl<'a> ThermalControl<'a> { }; ringbuf_entry!(Trace::AutoState(self.get_state())); - ControlResult::Pwm(PWMDuty(100)) + ControlResult::Pwm(PWMDuty( + self.pid_config.max_output as u8, + )) } else { // We adjust the worst component margin by our target // margin, which must be > 0. This effectively tells the @@ -1155,7 +1159,9 @@ impl<'a> ThermalControl<'a> { ControlResult::PowerDown } else { - ControlResult::Pwm(PWMDuty(100)) + ControlResult::Pwm(PWMDuty( + self.pid_config.max_output as u8, + )) } } ThermalControlState::Uncontrollable => ControlResult::PowerDown, diff --git a/task/thermal/src/main.rs b/task/thermal/src/main.rs index 117ddb5ea3..b77ca38207 100644 --- a/task/thermal/src/main.rs +++ b/task/thermal/src/main.rs @@ -33,6 +33,7 @@ #[cfg_attr(any(target_board = "medusa-a"), path = "bsp/medusa_a.rs")] #[cfg_attr(any(target_board = "grapefruit"), path = "bsp/grapefruit.rs")] #[cfg_attr(any(target_board = "minibar"), path = "bsp/minibar.rs")] +#[cfg_attr(any(target_board = "cosmo-a"), path = "bsp/cosmo_a.rs")] mod bsp; mod control;