diff --git a/Cargo.lock b/Cargo.lock index 2cc45d6e7..c300ec589 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,12 +156,28 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd769563b4ea2953e2825c9e6b7470a5f55f67e0be00030bf3e390a2a6071f64" +[[package]] +name = "build-i2c" +version = "0.1.0" +dependencies = [ + "anyhow", + "build-util", + "cfg-if 0.1.10", + "convert_case 0.4.0", + "indexmap", + "multimap", + "serde", +] + [[package]] name = "build-util" version = "0.1.0" dependencies = [ + "anyhow", + "indexmap", "serde", "serde_json", + "toml", ] [[package]] @@ -240,7 +256,7 @@ dependencies = [ "ansi_term", "atty", "bitflags", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", @@ -258,6 +274,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1f025f441cdfb75831bec89b9d6a6ed02e5e763f78fc5e1ff30d4870fefaec" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "cortex-m" version = "0.7.3" @@ -381,6 +403,41 @@ dependencies = [ "winapi", ] +[[package]] +name = "darling" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "debug-helper" version = "0.3.12" @@ -714,6 +771,8 @@ dependencies = [ name = "drv-stm32h7-i2c-server" version = "0.1.0" dependencies = [ + "anyhow", + "build-i2c", "build-util", "cfg-if 0.1.10", "cortex-m", @@ -909,6 +968,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1145,6 +1210,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "1.7.0" @@ -1348,6 +1419,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +dependencies = [ + "serde", +] + [[package]] name = "nb" version = "0.1.3" @@ -1582,12 +1662,13 @@ name = "pmbus" version = "0.1.0" dependencies = [ "anyhow", - "convert_case", + "convert_case 0.3.2", "libm", "num-derive", "num-traits", "ron", "serde", + "serde_with", ] [[package]] @@ -1756,6 +1837,12 @@ dependencies = [ "semver 0.9.0", ] +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + [[package]] name = "ryu" version = "1.0.5" @@ -1853,6 +1940,29 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad6056b4cb69b6e43e3a0f055def223380baecc99da683884f205bf347f7c4b3" +dependencies = [ + "rustversion", + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serialport" version = "3.3.0" @@ -1883,6 +1993,22 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sidecar" +version = "0.1.0" +dependencies = [ + "build-util", + "cfg-if 0.1.10", + "cortex-m", + "cortex-m-rt", + "cortex-m-semihosting", + "kern", + "panic-halt", + "panic-itm", + "panic-semihosting", + "stm32h7", +] + [[package]] name = "signature" version = "1.3.2" @@ -2001,6 +2127,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "structopt" version = "0.3.25" @@ -2064,6 +2196,8 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" name = "task-hiffy" version = "0.1.0" dependencies = [ + "anyhow", + "build-i2c", "build-util", "byteorder", "cfg-if 0.1.10", @@ -2074,6 +2208,8 @@ dependencies = [ "drv-lpc55-gpio-api", "drv-spi-api", "drv-stm32h7-gpio-api", + "drv-stm32h7-i2c", + "drv-stm32h7-rcc-api", "hif", "num-traits", "ringbuf", @@ -2125,6 +2261,8 @@ dependencies = [ name = "task-power" version = "0.1.0" dependencies = [ + "anyhow", + "build-i2c", "build-util", "cfg-if 0.1.10", "cortex-m", @@ -2150,6 +2288,8 @@ dependencies = [ name = "task-spd" version = "0.1.0" dependencies = [ + "anyhow", + "build-i2c", "build-util", "cfg-if 0.1.10", "cortex-m", @@ -2176,6 +2316,8 @@ dependencies = [ name = "task-thermal" version = "0.1.0" dependencies = [ + "anyhow", + "build-i2c", "build-util", "cfg-if 0.1.10", "cortex-m", diff --git a/Cargo.toml b/Cargo.toml index e5ee19415..98680f9be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "kern", "ringbuf", "userlib", + "sidecar", "stage0", "hypocalls", "lpc55_romapi", diff --git a/build-i2c/Cargo.toml b/build-i2c/Cargo.toml new file mode 100644 index 000000000..2349b336a --- /dev/null +++ b/build-i2c/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "build-i2c" +version = "0.1.0" +authors = ["Bryan Cantrill "] +edition = "2018" + +[dependencies] +build-util = {path = "../build-util"} +serde = { version = "1.0.114", features = ["derive"] } +indexmap = { version = "1.4.0", features = ["serde-1"] } +anyhow = "1.0.31" +cfg-if = "0.1.10" +multimap = "0.8.3" +convert_case = "0.4" + diff --git a/build-i2c/src/lib.rs b/build-i2c/src/lib.rs new file mode 100644 index 000000000..e4ab07f80 --- /dev/null +++ b/build-i2c/src/lib.rs @@ -0,0 +1,795 @@ +use anyhow::{bail, Result}; +use convert_case::{Case, Casing}; +use indexmap::IndexMap; +use multimap::MultiMap; +use serde::Deserialize; +use std::collections::HashMap; +use std::env; +use std::fmt::Write; +use std::fs::File; +use std::path::Path; + +// +// Our definition of the `Config` type. We share this type with all other +// build-specific types; we must not set `deny_unknown_fields` here. +// +#[derive(Clone, Debug, Deserialize)] +struct Config { + i2c: I2cConfig, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct I2cConfig { + controllers: Vec, + devices: Option>, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct I2cController { + controller: u8, + ports: IndexMap, + #[serde(default)] + target: bool, +} + +// +// Unfortunately, the toml-rs parsing of enums isn't quite right (see +// https://github.com/alexcrichton/toml-rs/issues/390 for details). As a +// result, we currently flatten what really should be enums around topology +// (i.e., [`controller`]/[`port`] vs. [`bus`]) and device class parameters +// (i.e., [`pmbus`]) into optional fields in [`I2cDevice`]. This makes it +// easier to accidentally create invalid entries (e.g., a device that has both +// a controller *and* a named bus), so the validation code should go to +// additional lengths to assure that these mistakes are caught in compilation. +// +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +#[allow(dead_code)] +struct I2cDevice { + /// device part name + device: String, + + /// device name + name: Option, + + /// I2C controller, if bus not named + controller: Option, + + /// I2C bus name, if controller not specified + bus: Option, + + /// I2C port, if required + port: Option, + + /// I2C address + address: u8, + + /// I2C mux, if any + mux: Option, + + /// I2C segment, if any + segment: Option, + + /// description of device + description: String, + + /// reference designator, if any + refdes: Option, + + /// PMBus information, if any + pmbus: Option, + + /// device is removable + #[serde(default)] + removable: bool, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct I2cPort { + name: Option, + #[allow(dead_code)] + description: Option, + pins: Vec, + #[serde(default)] + muxes: Vec, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct I2cPinSet { + gpio_port: Option, + pins: Vec, + af: u8, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct I2cMux { + driver: String, + address: u8, + enable: Option, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +#[allow(dead_code)] +struct I2cPmbus { + rails: Option>, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Artifact { + /// part of a complete distribution of an application + Dist, + + /// standalone build of a single task + Standalone, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Disposition { + /// controller is an initiator + Initiator, + + /// controller is a target + Target, + + /// only devices are used (i.e., controller is not used) + Devices, +} + +struct ConfigGenerator { + /// output that we're building + output: String, + + /// disposition of this configuration: target v. initiator v. devices + disposition: Disposition, + + /// artifact that we're creating: standalone v. dist + artifact: Artifact, + + /// all controllers + controllers: Vec, + + /// all devices + devices: Vec, + + /// hash bus name to controller/port index pair + buses: HashMap, + + /// hash controller/port pair to port index + ports: IndexMap<(u8, String), usize>, + + /// hash of controllers to single port indices + singletons: HashMap, +} + +impl ConfigGenerator { + fn new(disposition: Disposition, artifact: Artifact) -> Self { + let i2c = match artifact { + Artifact::Standalone => I2cConfig { + controllers: vec![], + devices: None, + }, + Artifact::Dist => match build_util::config::() { + Ok(config) => config.i2c, + Err(err) => { + panic!("malformed config.i2c: {:?}", err); + } + }, + }; + + let mut controllers = vec![]; + let mut buses = HashMap::new(); + let mut ports = IndexMap::new(); + let mut singletons = HashMap::new(); + + for c in i2c.controllers { + // + // We always insert our buses (even for controllers that don't + // match our dispostion) to assure that devices can always find + // their bus. + // + for (index, (p, port)) in c.ports.iter().enumerate() { + if let Some(name) = &port.name { + match buses.insert(name.clone(), (c.controller, index)) { + Some(_) => { + panic!("i2c bus {} appears twice", name); + } + None => {} + } + } + + if c.ports.len() == 1 { + singletons.insert(c.controller, index); + } + + ports.insert((c.controller, p.clone()), index); + } + + if c.target != (disposition == Disposition::Target) { + continue; + } + + controllers.push(c); + } + + if let Some(devices) = &i2c.devices { + for d in devices { + match (d.controller, d.bus.as_ref()) { + (None, None) => { + panic!( + "device {} at address 0x{:x} must have \ + a bus or controller", + d.device, d.address + ); + } + (Some(_), Some(_)) => { + panic!( + "device {} at address 0x{:x} has both \ + a bus and a controller", + d.device, d.address + ); + } + (_, Some(bus)) if buses.get(bus).is_none() => { + panic!( + "device {} at address 0x{:x} specifies \ + unknown bus \"{}\"", + d.device, d.address, bus + ); + } + (_, _) => {} + } + } + } + + Self { + output: String::new(), + disposition: disposition, + artifact: artifact, + controllers: controllers, + buses: buses, + ports: ports, + singletons: singletons, + devices: i2c.devices.unwrap_or(Vec::new()), + } + } + + pub fn ncontrollers(&self) -> usize { + self.controllers.len() + } + + pub fn generate_header(&mut self) -> Result<()> { + writeln!(&mut self.output, "mod i2c_config {{")?; + Ok(()) + } + + pub fn generate_footer(&mut self) -> Result<()> { + writeln!(&mut self.output, "}}")?; + Ok(()) + } + + pub fn generate_controllers(&mut self) -> Result<()> { + let mut s = &mut self.output; + + assert!(self.disposition != Disposition::Devices); + + writeln!( + &mut s, + r##" + use drv_stm32h7_i2c::I2cController; + + pub fn controllers() -> [I2cController<'static>; {}] {{"##, + self.controllers.len() + )?; + + if self.controllers.len() > 0 { + writeln!( + &mut s, + r##" + use drv_stm32h7_rcc_api::Peripheral; + use drv_i2c_api::Controller; + + #[cfg(feature = "h7b3")] + use stm32h7::stm32h7b3 as device; + + #[cfg(feature = "h743")] + use stm32h7::stm32h743 as device;"## + )?; + } + + write!( + &mut s, + r##" + ["## + )?; + + for c in &self.controllers { + write!( + &mut s, + r##" + I2cController {{ + controller: Controller::I2C{controller}, + peripheral: Peripheral::I2c{controller}, + notification: (1 << ({controller} - 1)), + registers: unsafe {{ &*device::I2C{controller}::ptr() }}, + }},"##, + controller = c.controller + )?; + } + + writeln!( + &mut s, + r##" + ] + }}"## + )?; + + Ok(()) + } + + pub fn generate_pins(&mut self) -> Result<()> { + let mut s = &mut self.output; + let mut len = 0; + + assert!(self.disposition != Disposition::Devices); + + for c in &self.controllers { + for (_, port) in &c.ports { + len += port.pins.len(); + } + } + + writeln!( + &mut s, + r##" + use drv_stm32h7_i2c::I2cPin; + + pub fn pins() -> [I2cPin; {}] {{"##, + len + )?; + + if len > 0 { + writeln!( + &mut s, + r##" + use drv_i2c_api::{{Controller, PortIndex}}; + use drv_stm32h7_gpio_api::{{self as gpio_api, Alternate}};"## + )?; + } + + write!( + &mut s, + r##" + ["## + )?; + + for c in &self.controllers { + for (index, (p, port)) in c.ports.iter().enumerate() { + for pin in &port.pins { + let mut pinstr = String::new(); + write!(&mut pinstr, "pin({})", pin.pins[0])?; + + for i in 1..pin.pins.len() { + write!(&mut pinstr, ".and_pin({})", pin.pins[i])?; + } + + write!( + &mut s, + r##" + I2cPin {{ + controller: Controller::I2C{controller}, + port: PortIndex({i2c_port}), + gpio_pins: gpio_api::Port::{gpio_port}.{pinstr}, + function: Alternate::AF{af}, + }},"##, + controller = c.controller, + i2c_port = index, + gpio_port = match pin.gpio_port { + Some(ref port) => port, + None => p, + }, + pinstr = pinstr, + af = pin.af + )?; + } + } + } + + writeln!( + &mut s, + r##" + ] + }}"## + )?; + + Ok(()) + } + + pub fn generate_muxes(&mut self) -> Result<()> { + if self.disposition == Disposition::Target { + panic!("cannot generate muxes when configured as target"); + } + + let mut s = &mut self.output; + let mut len = 0; + + for c in &self.controllers { + for (_, port) in &c.ports { + len += port.muxes.len(); + } + } + + write!( + &mut s, + r##" + use drv_stm32h7_i2c::I2cMux; + + pub fn muxes() -> [I2cMux<'static>; {}] {{"##, + len + )?; + + if len > 0 { + writeln!( + &mut s, + r##" + use drv_i2c_api::{{Controller, PortIndex, Mux}}; + + #[allow(unused_imports)] + use drv_stm32h7_gpio_api::{{self as gpio_api, Alternate}};"## + )?; + } + + write!( + &mut s, + r##" + ["## + )?; + + for c in &self.controllers { + for (index, (p, port)) in c.ports.iter().enumerate() { + for (mindex, mux) in port.muxes.iter().enumerate() { + let enablestr = if let Some(enable) = &mux.enable { + let mut enablestr = String::new(); + write!( + &mut enablestr, + r##"Some(I2cPin {{ + controller: Controller::I2C{controller}, + port: PortIndex({port}), + gpio_pins: gpio_api::Port::{gpio_port}.pin({gpio_pin}), + function: Alternate::AF{af}, + }})"##, + controller = c.controller, + port = index, + gpio_port = match enable.gpio_port { + Some(ref port) => port, + None => bail!( + "missing pin port on mux enable \ + on I2C{}, port {}, mux {}", + c.controller, + p, + mindex + 1 + ), + }, + gpio_pin = enable.pins[0], + af = enable.af + )?; + enablestr + } else { + "None".to_string() + }; + + let driver_struct = format!( + "{}{}", + (&mux.driver[..1].to_string()).to_uppercase(), + &mux.driver[1..] + ); + + write!( + &mut s, + r##" + I2cMux {{ + controller: Controller::I2C{controller}, + port: PortIndex({i2c_port}), + id: Mux::M{mindex}, + driver: &drv_stm32h7_i2c::{driver}::{driver_struct}, + enable: {enable}, + address: 0x{address:x}, + }},"##, + controller = c.controller, + i2c_port = index, + mindex = mindex + 1, + driver = mux.driver, + driver_struct = driver_struct, + enable = enablestr, + address = mux.address, + )?; + } + } + } + + writeln!( + &mut s, + r##" + ] + }}"## + )?; + + Ok(()) + } + + fn generate_device(&self, d: &I2cDevice) -> String { + let controller = match &d.bus { + Some(bus) => self.buses.get(bus).unwrap().0, + None => d.controller.unwrap(), + }; + + let port = match (&d.bus, &d.port) { + (Some(_), Some(_)) => { + panic!("device {} has both port and bus", d.device); + } + + (Some(bus), None) => match self.buses.get(bus) { + Some((_, port)) => port, + None => { + panic!("device {} has invalid bus", d.device); + } + }, + + (None, Some(port)) => { + match self.ports.get(&(controller, port.to_string())) { + None => { + panic!("device {} has invalid port", d.device); + } + Some(port) => port, + } + } + + // + // We allow ports to be unspecified if the specified + // controller has only a single port; check the singletons. + // + (None, None) => match self.singletons.get(&controller) { + Some(port) => port, + None => { + panic!("device {} has ambiguous port", d.device) + } + }, + }; + + format!( + r##" + // {description} + I2cDevice::new(task, + Controller::I2C{controller}, + PortIndex({port}), + {segment}, + 0x{address:x} + )"##, + description = d.description, + controller = controller, + port = port, + segment = "None", + address = d.address, + ) + } + + pub fn generate_devices(&mut self) -> Result<()> { + if self.artifact == Artifact::Standalone { + // + // For the standalone build, we generate a single, mock + // device. + // + writeln!( + &mut self.output, + r##" + pub mod devices {{ + use drv_i2c_api::I2cDevice; + use userlib::TaskId; + + pub fn mock(task: TaskId) -> I2cDevice {{ + I2cDevice::mock(task) + }} + }}"## + )?; + return Ok(()); + } + + // + // Throw all devices into a MultiMap based on device. + // + let mut bydevice = MultiMap::new(); + let mut byname = HashMap::new(); + let mut bybus = MultiMap::new(); + + for d in &self.devices { + bydevice.insert(&d.device, d); + + if let Some(bus) = &d.bus { + bybus.insert((&d.device, bus), d); + + if let Some(name) = &d.name { + if byname.insert((&d.device, bus, name), d).is_some() { + panic!( + "duplicate name {} for device {} on bus {}", + name, d.device, bus + ) + } + } + } else { + if let Some(name) = &d.name { + panic!("named device {} is on unnamed bus", name); + } + } + } + + write!( + &mut self.output, + r##" + pub mod devices {{ + use drv_i2c_api::{{I2cDevice, Controller, PortIndex}}; + use userlib::TaskId; +"## + )?; + + for (device, devices) in bydevice.iter_all() { + write!( + &mut self.output, + r##" + #[allow(dead_code)] + pub fn {}(task: TaskId) -> [I2cDevice; {}] {{ + ["##, + device, + devices.len() + )?; + + for d in devices { + let out = self.generate_device(d); + write!(&mut self.output, "{},", out)?; + } + + writeln!( + &mut self.output, + r##" + ] + }}"## + )?; + } + + for ((device, bus), devices) in bybus.iter_all() { + write!( + &mut self.output, + r##" + #[allow(dead_code)] + pub fn {}_{}(task: TaskId) -> [I2cDevice; {}] {{ + ["##, + device, + bus, + devices.len() + )?; + + for d in devices { + let out = self.generate_device(d); + write!(&mut self.output, "{},", out)?; + } + writeln!( + &mut self.output, + r##" + ] + }}"## + )?; + } + + for ((device, bus, name), d) in &byname { + write!( + &mut self.output, + r##" + #[allow(dead_code)] + pub fn {}_{}_{}(task: TaskId) -> I2cDevice {{"##, + device, bus, name + )?; + + let out = self.generate_device(d); + write!(&mut self.output, "{}", out)?; + + writeln!( + &mut self.output, + r##" + }}"## + )?; + } + + writeln!(&mut self.output, " }}")?; + Ok(()) + } + + pub fn generate_ports(&mut self) -> Result<()> { + writeln!( + &mut self.output, + r##" + pub mod ports {{"## + )?; + + if self.artifact == Artifact::Standalone { + // + // For the standalone build, we generate a mock port. + // + writeln!( + &mut self.output, + r##" + #[allow(dead_code)] + pub const fn i2c_mock() -> drv_i2c_api::PortIndex {{ + drv_i2c_api::PortIndex(0) + }}"## + )?; + } + + for ((controller, port), index) in &self.ports { + writeln!( + &mut self.output, + r##" + #[allow(dead_code)] + pub const fn i2c{controller}_{port}() -> drv_i2c_api::PortIndex {{ + drv_i2c_api::PortIndex({index}) + }}"##, + controller = controller, + port = port.to_case(Case::Snake), + index = index, + )?; + } + + writeln!(&mut self.output, " }}")?; + Ok(()) + } +} + +pub fn codegen(disposition: Disposition, artifact: Artifact) -> Result<()> { + use std::io::Write; + + let out_dir = env::var("OUT_DIR")?; + let dest_path = Path::new(&out_dir).join("i2c_config.rs"); + let mut file = File::create(&dest_path)?; + + let mut g = ConfigGenerator::new(disposition, artifact); + + g.generate_header()?; + + match disposition { + Disposition::Target => { + let n = g.ncontrollers(); + + if n != 1 && artifact == Artifact::Dist { + // + // If we have the disposition of a target, we expect exactly one + // controller to be configured as a target; if none have been + // specified, the task should be deconfigured. + // + panic!("found {} I2C controller(s); expected exactly one", n); + } + + g.generate_controllers()?; + g.generate_pins()?; + g.generate_ports()?; + } + + Disposition::Initiator => { + g.generate_controllers()?; + g.generate_pins()?; + g.generate_ports()?; + g.generate_muxes()?; + } + + Disposition::Devices => { + g.generate_devices()?; + } + } + + g.generate_footer()?; + + file.write_all(g.output.as_bytes())?; + + Ok(()) +} diff --git a/build-util/Cargo.toml b/build-util/Cargo.toml index c4a7552c7..0c14d2276 100644 --- a/build-util/Cargo.toml +++ b/build-util/Cargo.toml @@ -5,5 +5,8 @@ authors = ["Cliff L. Biffle "] edition = "2018" [dependencies] -serde = "1.0.114" +serde = { version = "1.0.114", features = ["derive"] } +toml = "0.5.6" +indexmap = { version = "1.4.0", features = ["serde-1"] } serde_json = "1.0.56" +anyhow = "1.0.31" diff --git a/build-util/src/lib.rs b/build-util/src/lib.rs index 5bbac3903..e3b6db912 100644 --- a/build-util/src/lib.rs +++ b/build-util/src/lib.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use serde::de::DeserializeOwned; use std::env; /// Exposes the CPU's M-profile architecture version. This isn't available in @@ -26,3 +28,21 @@ pub fn expose_target_board() { } println!("cargo:rerun-if-env-changed=HUBRIS_BOARD"); } + +/// +/// Pulls the app-wide configuration for purposes of a build task. This +/// will fail if the app-wide configuration doesn't exist or can't parse. +/// Note that -- thanks to the magic of Serde -- `T` need not (and indeed, +/// should not) contain the entire app-wide configuration, but rather only +/// those parts that a particular build task cares about. (It should go +/// without saying that `deny_unknown_fields` should *not* be set on this +/// type -- but it may well be set within the task-specific types that +/// this type contains.) If the configuration field is optional, `T` should +/// reflect that by having its member (or members) be an `Option` type. +/// +pub fn config() -> Result { + let config = env::var("HUBRIS_APP_CONFIG")?; + let rval = toml::from_slice(config.as_bytes())?; + println!("cargo:rerun-if-env-changed=HUBRIS_APP_CONFIG"); + Ok(rval) +} diff --git a/demo-stm32h7/app-h743.toml b/demo-stm32h7/app-h743.toml index af693b97a..b17571f14 100644 --- a/demo-stm32h7/app-h743.toml +++ b/demo-stm32h7/app-h743.toml @@ -146,27 +146,31 @@ user_leds = "user_leds" [tasks.hiffy] path = "../task-hiffy" name = "task-hiffy" -features = ["stm32h7", "itm", "i2c"] +features = ["stm32h7", "itm", "i2c", "gpio", "qspi"] priority = 3 -requires = {flash = 32768, ram = 16384 } +requires = {flash = 32768, ram = 32768 } stacksize = 2048 start = true [tasks.hiffy.task-slots] +gpio_driver = "gpio_driver" +hf = "hf" i2c_driver = "i2c_driver" -[tasks.ping] -path = "../task-ping" -name = "task-ping" -features = ["uart"] -priority = 4 -requires = {flash = 8192, ram = 512} -stacksize = 512 +[tasks.hf] +path = "../drv/gimlet-hf-server" +name = "drv-gimlet-hf-server" +features = [] +priority = 3 +requires = {flash = 16384, ram = 8192 } +stacksize = 2048 start = true +uses = ["quadspi"] +interrupts = {92 = 1} -[tasks.ping.task-slots] -peer = "pong" -usart_driver = "usart_driver" +[tasks.hf.task-slots] +gpio_driver = "gpio_driver" +rcc_driver = "rcc_driver" [tasks.idle] path = "../task-idle" @@ -236,3 +240,23 @@ size = 1024 address = 0x58001c00 size = 1024 +[peripherals.quadspi] +address = 0x52005000 +size = 4096 + +[config] + +[[config.i2c.controllers]] +controller = 2 + +[[config.i2c.controllers.ports.F.pins]] +pins = [ 0, 1 ] +af = 4 + +# +# To use the Nucleo board as an SPD initiator, uncomment the following: +# +# [[config.i2c.controllers.ports.F.muxes]] +# driver = "ltc4306" +# address = 0b1001_010 + diff --git a/demo-stm32h7/app-h7b3.toml b/demo-stm32h7/app-h7b3.toml index 6f740155c..d925991cc 100644 --- a/demo-stm32h7/app-h7b3.toml +++ b/demo-stm32h7/app-h7b3.toml @@ -187,3 +187,12 @@ size = 1024 address = 0x58001c00 size = 1024 +[config] + +[[config.i2c.controllers]] +controller = 4 + +[[config.i2c.controllers.ports.D.pins]] +pins = [ 12, 13 ] +af = 4 + diff --git a/drv/gimlet-hf-server/src/main.rs b/drv/gimlet-hf-server/src/main.rs index 4fd8b5c7a..4bd072b28 100644 --- a/drv/gimlet-hf-server/src/main.rs +++ b/drv/gimlet-hf-server/src/main.rs @@ -133,6 +133,60 @@ fn main() -> ! { gpio_api::Pull::None, ).unwrap(); + let reset_pin = gpio_api::Port::F.pin(4); + } else if #[cfg(target_board = "nucleo-h743zi2")] { + qspi.configure( + 50, // 200MHz kernel / 5 = 4MHz clock + 25, // 2**25 = 32MiB = 256Mib + ); + // Nucleo-144 pin mapping + // PB2 SP_QSPI1_CLK + // PD11 SP_QSPI1_IO0 + // PD12 SP_QSPI1_IO1 + // PD13 SP_QSPI1_IO3 + // PE2 SP_QSPI1_IO2 + // + // PG6 SP_QSPI1_CS + // + gpio_driver.configure_alternate( + gpio_api::Port::B.pin(2), + gpio_api::OutputType::PushPull, + gpio_api::Speed::VeryHigh, + gpio_api::Pull::None, + gpio_api::Alternate::AF9, + ).unwrap(); + gpio_driver.configure_alternate( + gpio_api::Port::D.pin(11).and_pin(12).and_pin(13), + gpio_api::OutputType::PushPull, + gpio_api::Speed::VeryHigh, + gpio_api::Pull::None, + gpio_api::Alternate::AF9, + ).unwrap(); + gpio_driver.configure_alternate( + gpio_api::Port::E.pin(2), + gpio_api::OutputType::PushPull, + gpio_api::Speed::VeryHigh, + gpio_api::Pull::None, + gpio_api::Alternate::AF9, + ).unwrap(); + gpio_driver.configure_alternate( + gpio_api::Port::G.pin(6), + gpio_api::OutputType::PushPull, + gpio_api::Speed::VeryHigh, + gpio_api::Pull::None, + gpio_api::Alternate::AF10, + ).unwrap(); + + // start reset and select off low + gpio_driver.reset(gpio_api::Port::F.pin(4).and_pin(5)).unwrap(); + + gpio_driver.configure_output( + gpio_api::Port::F.pin(4).and_pin(5), + gpio_api::OutputType::PushPull, + gpio_api::Speed::High, + gpio_api::Pull::None, + ).unwrap(); + let reset_pin = gpio_api::Port::F.pin(4); } else if #[cfg(feature = "standalone")] { let reset_pin = gpio_api::Port::B.pin(2); @@ -266,6 +320,7 @@ fn main() -> ! { fn set_and_check_write_enable(qspi: &Qspi) -> Result<(), HfError> { qspi.write_enable(); let status = qspi.read_status(); + if status & 0b10 == 0 { // oh oh return Err(HfError::WriteEnableFailed.into()); diff --git a/drv/i2c-api/src/lib.rs b/drv/i2c-api/src/lib.rs index 64defebbe..f8b24b32b 100644 --- a/drv/i2c-api/src/lib.rs +++ b/drv/i2c-api/src/lib.rs @@ -47,10 +47,8 @@ pub enum ResponseCode { BadController = 4, /// Device address is reserved ReservedAddress = 5, - /// Inidcated port is invalid + /// Indicated port is invalid BadPort = 6, - /// Default port indicated, but port must be specified - BadDefaultPort = 7, /// Device does not have indicated register NoRegister = 8, /// Indicated mux is an invalid mux identifier @@ -117,41 +115,39 @@ pub enum ReservedAddress { } /// -/// The port for a given I2C device. Some controllers can have multiple -/// ports (which themselves are connected to different I2C busses), but only -/// one port can be active at a time. For these controllers, a port must -/// be specified (generally lettered). For controllers that have only one -/// port, [`Port::Default`] should be specified. +/// The port index for a given I2C device. Some controllers can have multiple +/// ports (which themselves are connected to different I2C buses), but only +/// one port can be active at a time. For these controllers, a port index +/// must be specified. The mapping between these indices and values that make +/// sense in terms of the I2C controller (e.g., the lettered port) is +/// specified in the application configuration; to minimize confusion, the +/// letter should generally match the GPIO port of the I2C bus (assuming that +/// GPIO ports are lettered), but these values are in fact strings and can +/// take any value. Note that if a given I2C controller straddles two ports, +/// the port of SDA should generally be used when naming the port; if a GPIO +/// port contains multiple SDAs on it from the same controller, the +/// letter/number convention should be used (e.g., "B1") -- but this is purely +/// convention. /// #[derive(Copy, Clone, Debug, FromPrimitive, PartialEq)] -#[repr(u8)] -pub enum Port { - Default = 0, - A = 1, - B = 2, - C = 3, - D = 4, - E = 5, - F = 6, - G = 7, - H = 8, - I = 9, - J = 10, - K = 11, -} +pub struct PortIndex(pub u8); /// -/// A multiplexer for a given I2C device. Multiplexers are numbered starting -/// from 1. +/// A multiplexer identifier for a given I2C bus. Multiplexer identifiers +/// need not start at 0. /// #[derive(Copy, Clone, Debug, FromPrimitive, PartialEq)] #[repr(u8)] pub enum Mux { M1 = 1, + M2 = 2, + M3 = 3, + M4 = 4, } /// -/// A segment on a given multiplexer. Segments are nubered starting from 1. +/// A segment identifier on a given multiplexer. Segment identifiers +/// need not start at 0. /// #[derive(Copy, Clone, Debug, FromPrimitive, PartialEq)] #[repr(u8)] @@ -174,12 +170,12 @@ pub enum Segment { pub struct I2cDevice { pub task: TaskId, pub controller: Controller, - pub port: Port, + pub port: PortIndex, pub segment: Option<(Mux, Segment)>, pub address: u8, } -type I2cMessage = (u8, Controller, Port, Option<(Mux, Segment)>); +type I2cMessage = (u8, Controller, PortIndex, Option<(Mux, Segment)>); pub trait Marshal { fn marshal(&self) -> T; @@ -193,7 +189,7 @@ impl Marshal<[u8; 4]> for I2cMessage { [ self.0, self.1 as u8, - self.2 as u8, + self.2 .0 as u8, match self.3 { Some((mux, seg)) => { 0b1000_0000 | ((mux as u8) << 4) | (seg as u8) @@ -206,7 +202,7 @@ impl Marshal<[u8; 4]> for I2cMessage { Ok(( val[0], Controller::from_u8(val[1]).ok_or(ResponseCode::BadController)?, - Port::from_u8(val[2]).ok_or(ResponseCode::BadPort)?, + PortIndex(val[2]), if val[3] == 0 { None } else { @@ -225,21 +221,11 @@ impl core::fmt::Display for I2cDevice { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let addr = self.address; - match (self.port, self.segment) { - (Port::Default, None) => { - write!(f, "{:?} 0x{:x}", self.controller, addr) - } - (Port::Default, Some((mux, segment))) => { - write!( - f, - "{:?}, {:?}:{:?} 0x{:x}", - self.controller, mux, segment, addr - ) - } - (_, None) => { + match self.segment { + None => { write!(f, "{:?}:{:?} 0x{:x}", self.controller, self.port, addr) } - (_, Some((mux, segment))) => { + Some((mux, segment)) => { write!( f, "{:?}:{:?}, {:?}:{:?} 0x{:x}", @@ -259,7 +245,7 @@ impl I2cDevice { pub fn new( task: TaskId, controller: Controller, - port: Port, + port: PortIndex, segment: Option<(Mux, Segment)>, address: u8, ) -> Self { @@ -283,7 +269,7 @@ impl I2cDevice { Self { task: task, controller: Controller::Mock, - port: Port::Default, + port: PortIndex(0), segment: None, address: 0, } diff --git a/drv/stm32h7-i2c-server/Cargo.toml b/drv/stm32h7-i2c-server/Cargo.toml index 4451a5968..9db3eb6c0 100644 --- a/drv/stm32h7-i2c-server/Cargo.toml +++ b/drv/stm32h7-i2c-server/Cargo.toml @@ -20,6 +20,9 @@ stm32h7 = { version = "0.13.0" } [build-dependencies] build-util = {path = "../../build-util"} +build-i2c = {path = "../../build-i2c"} +anyhow = "1.0.31" +cfg-if = "0.1.10" [features] default = ["standalone"] diff --git a/drv/stm32h7-i2c-server/build.rs b/drv/stm32h7-i2c-server/build.rs index 941cd93f4..5b4be0693 100644 --- a/drv/stm32h7-i2c-server/build.rs +++ b/drv/stm32h7-i2c-server/build.rs @@ -1,3 +1,16 @@ fn main() { build_util::expose_target_board(); + + let disposition = build_i2c::Disposition::Initiator; + + #[cfg(feature = "standalone")] + let artifact = build_i2c::Artifact::Standalone; + + #[cfg(not(feature = "standalone"))] + let artifact = build_i2c::Artifact::Dist; + + if let Err(e) = build_i2c::codegen(disposition, artifact) { + println!("code generation failed: {}", e); + std::process::exit(1); + } } diff --git a/drv/stm32h7-i2c-server/src/main.rs b/drv/stm32h7-i2c-server/src/main.rs index 902247fcb..be99e5d76 100644 --- a/drv/stm32h7-i2c-server/src/main.rs +++ b/drv/stm32h7-i2c-server/src/main.rs @@ -3,19 +3,10 @@ #![no_std] #![no_main] -#[cfg(feature = "h7b3")] -use stm32h7::stm32h7b3 as device; - -#[cfg(feature = "h743")] -use stm32h7::stm32h743 as device; - -use drv_i2c_api::Port; use drv_i2c_api::*; -use drv_stm32h7_gpio_api::{ - self as gpio_api, Alternate, Gpio, OutputType, Pull, Speed, -}; +use drv_stm32h7_gpio_api::{Gpio, OutputType, Pull, Speed}; use drv_stm32h7_i2c::*; -use drv_stm32h7_rcc_api::{Peripheral, Rcc}; +use drv_stm32h7_rcc_api::Rcc; use fixedmap::*; use ringbuf::*; @@ -38,51 +29,23 @@ fn lookup_controller<'a>( } /// -/// Validates a port for the specified controller, translating `Port::Default` -/// to the matching port (or returning an error if there is more than one port -/// and `Port::Default` has been specified). +/// Validates a port for the specified controller. /// fn validate_port<'a>( pins: &'a [I2cPin], controller: Controller, - port: Port, -) -> Result { - if port != Port::Default { - // - // The more straightforward case is when our port has been explicitly - // provided -- we just need to verify that it's valid. - // - pins.iter() - .find(|pin| pin.controller == controller && pin.port == port) - .map(|pin| pin.port) - .ok_or(ResponseCode::BadPort) - } else { - let mut found = pins - .iter() - .filter(|pin| pin.controller == controller) - .map(|pin| pin.port); - - // - // A default port has been requested; we need to verify that there is - // but one port for this controller -- if there is more than one, we - // require the port to be explicitly provided. - // - match found.next() { - None => Err(ResponseCode::BadController), - Some(port) => { - if found.any(|p| p != port) { - Err(ResponseCode::BadDefaultPort) - } else { - Ok(port) - } - } - } - } + port: PortIndex, +) -> Result<(), ResponseCode> { + pins.iter() + .find(|pin| pin.controller == controller && pin.port == port) + .ok_or(ResponseCode::BadPort)?; + + Ok(()) } fn find_mux( controller: &I2cController, - port: Port, + port: PortIndex, muxes: &[I2cMux], mux: Option<(Mux, Segment)>, mut func: impl FnMut(&I2cMux, Mux, Segment) -> Result<(), ResponseCode>, @@ -110,7 +73,7 @@ fn find_mux( fn configure_mux( map: &mut MuxMap, controller: &I2cController, - port: Port, + port: PortIndex, mux: Option<(Mux, Segment)>, muxes: &[I2cMux], ctrl: &I2cControl, @@ -143,7 +106,7 @@ ringbuf!(Option, 16, None); fn reset_if_needed( code: ResponseCode, controller: &I2cController, - port: Port, + port: PortIndex, muxes: &[I2cMux], mux: Option<(Mux, Segment)>, ) { @@ -174,316 +137,16 @@ fn reset_if_needed( }); } -type PortMap = FixedMap; -type MuxMap = FixedMap; +type PortMap = FixedMap; +type MuxMap = FixedMap; + +include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); #[export_name = "main"] fn main() -> ! { - cfg_if::cfg_if! { - if #[cfg(target_board = "stm32h7b3i-dk")] { - let controllers = [ I2cController { - controller: Controller::I2C4, - peripheral: Peripheral::I2c4, - notification: (1 << (4 - 1)), - registers: unsafe { &*device::I2C4::ptr() }, - } ]; - - let pins = [ I2cPin { - controller: Controller::I2C4, - port: Port::D, - gpio_pins: gpio_api::Port::D.pin(12).and_pin(13), - function: Alternate::AF4, - } ]; - - let muxes = []; - } else if #[cfg(target_board = "nucleo-h743zi2")] { - let controllers = [ I2cController { - controller: Controller::I2C2, - peripheral: Peripheral::I2c2, - notification: (1 << (2 - 1)), - registers: unsafe { &*device::I2C2::ptr() }, - } ]; - - let pins = [ I2cPin { - controller: Controller::I2C2, - port: Port::F, - gpio_pins: gpio_api::Port::F.pin(0).and_pin(1), - function: Alternate::AF4, - } ]; - - let muxes = [ - #[cfg(feature = "external-spd")] - I2cMux { - controller: Controller::I2C2, - port: Port::F, - id: Mux::M1, - driver: &drv_stm32h7_i2c::ltc4306::Ltc4306, - enable: None, - address: 0b1001_010, - }, - - #[cfg(feature = "external-max7358")] - I2cMux { - controller: Controller::I2C2, - port: Port::F, - id: Mux::M1, - driver: &drv_stm32h7_i2c::max7358::Max7358, - enable: None, - address: 0x70, - }, - - ]; - } else if #[cfg(target_board = "gemini-bu-1")] { - let controllers = [ I2cController { - controller: Controller::I2C1, - peripheral: Peripheral::I2c1, - notification: (1 << (1 - 1)), - registers: unsafe { &*device::I2C1::ptr() }, - }, I2cController { - controller: Controller::I2C3, - peripheral: Peripheral::I2c3, - notification: (1 << (3 - 1)), - registers: unsafe { &*device::I2C3::ptr() }, - }, I2cController { - controller: Controller::I2C4, - peripheral: Peripheral::I2c4, - notification: (1 << (4 - 1)), - registers: unsafe { &*device::I2C4::ptr() }, - } ]; - - let pins = [ I2cPin { - controller: Controller::I2C1, - port: Port::B, - gpio_pins: gpio_api::Port::B.pin(8).and_pin(9), - function: Alternate::AF4, - }, I2cPin { - controller: Controller::I2C4, - port: Port::D, - gpio_pins: gpio_api::Port::D.pin(12).and_pin(13), - function: Alternate::AF4, - }, I2cPin { - controller: Controller::I2C4, - port: Port::F, - gpio_pins: gpio_api::Port::F.pin(14).and_pin(15), - function: Alternate::AF4, - }, I2cPin { - controller: Controller::I2C3, - port: Port::H, - gpio_pins: gpio_api::Port::H.pin(7).and_pin(8), - function: Alternate::AF4, - }, I2cPin { - controller: Controller::I2C4, - port: Port::H, - gpio_pins: gpio_api::Port::H.pin(11).and_pin(12), - function: Alternate::AF4, - } ]; - - let muxes = [ - I2cMux { - controller: Controller::I2C4, - port: Port::F, - id: Mux::M1, - driver: &drv_stm32h7_i2c::ltc4306::Ltc4306, - enable: Some(I2cPin { - controller: Controller::I2C4, - port: Port::Default, - gpio_pins: gpio_api::Port::G.pin(0), - function: Alternate::AF0, - }), - address: 0x44, - }, - #[cfg(feature = "external-max7358")] - I2cMux { - controller: Controller::I2C4, - port: Port::D, - id: Mux::M1, - driver: &drv_stm32h7_i2c::max7358::Max7358, - enable: None, - address: 0x70, - }, - - #[cfg(feature = "external-pca9548")] - I2cMux { - controller: Controller::I2C4, - port: Port::H, - id: Mux::M1, - driver: &drv_stm32h7_i2c::pca9548::Pca9548, - enable: None, - address: 0x70, - }, - - ]; - } else if #[cfg(target_board = "gimletlet-2")] { - let controllers = [ - - #[cfg(not(feature = "target-enable"))] - I2cController { - controller: Controller::I2C2, - peripheral: Peripheral::I2c2, - notification: (1 << (2 - 1)), - registers: unsafe { &*device::I2C2::ptr() }, - }, - - I2cController { - controller: Controller::I2C3, - peripheral: Peripheral::I2c3, - notification: (1 << (3 - 1)), - registers: unsafe { &*device::I2C3::ptr() }, - }, I2cController { - controller: Controller::I2C4, - peripheral: Peripheral::I2c4, - notification: (1 << (4 - 1)), - registers: unsafe { &*device::I2C4::ptr() }, - } ]; - - // - // Note that I2C3 is a bit unusual in that its SCL and SDA are on - // two different ports (port A and port C, respectively); we - // therefore have two `I2cPin` structures for I2C3, but for - // purposes of the abstraction that we export to consumers, we - // call the pair logical port A. - // - let pins = [ - #[cfg(not(feature = "target-enable"))] - I2cPin { - controller: Controller::I2C2, - port: Port::F, - gpio_pins: gpio_api::Port::F.pin(0).and_pin(1), - function: Alternate::AF4, - }, - - I2cPin { - controller: Controller::I2C3, - port: Port::A, - gpio_pins: gpio_api::Port::A.pin(8), - function: Alternate::AF4, - }, I2cPin { - controller: Controller::I2C3, - port: Port::A, - gpio_pins: gpio_api::Port::C.pin(9), - function: Alternate::AF4, - }, I2cPin { - controller: Controller::I2C4, - port: Port::F, - gpio_pins: gpio_api::Port::F.pin(14).and_pin(15), - function: Alternate::AF4, - } ]; - - let muxes = []; - } else if #[cfg(target_board = "gimlet-1")] { - // SP3 proxy is handled in task-spd - - let controllers = [ - // Front M.2 - I2cController { - controller: Controller::I2C2, - peripheral: Peripheral::I2c2, - notification: (1 << (2 - 1)), - registers: unsafe { &*device::I2C2::ptr() }, - }, - // Mid - I2cController { - controller: Controller::I2C3, - peripheral: Peripheral::I2c3, - notification: (1 << (3 - 1)), - registers: unsafe { &*device::I2C3::ptr() }, - }, - // Rear - I2cController { - controller: Controller::I2C4, - peripheral: Peripheral::I2c4, - notification: (1 << (4 - 1)), - registers: unsafe { &*device::I2C4::ptr() }, - } ]; - - - let pins = [ - // Note we have two different sets of pins on two different - // ports for I2C2! - - // SMBUS_SP_TO_LVL_FRONT_SMDAT - // SMBUS_SP_TO_LVL_FRONT_SMCLK - I2cPin { - controller: Controller::I2C2, - port: Port::F, - gpio_pins: gpio_api::Port::F.pin(0).and_pin(1), - function: Alternate::AF4, - }, - - // SMBUS_SP_TO_M2_SMCLK_A2_V3P3 - // SMBUS_SP_TO_M2_SMDAT_A2_V3P3 - I2cPin { - controller: Controller::I2C2, - port: Port::B, - gpio_pins: gpio_api::Port::B.pin(10).and_pin(11), - function: Alternate::AF4, - }, - - // SMBUS_SP_TO_LVL_MID_SMCLK - // SMBUS_SP_TO_LVL_MID_SMDAT - I2cPin { - controller: Controller::I2C3, - port: Port::H, - gpio_pins: gpio_api::Port::H.pin(7).and_pin(8), - function: Alternate::AF4, - }, - // SMBUS_SP_TO_LVL_REAR_SMCLK - // SMBUS_SP_TO_LVL_REAR_SMDAT - I2cPin { - controller: Controller::I2C4, - port: Port::F, - gpio_pins: gpio_api::Port::F.pin(14).and_pin(15), - function: Alternate::AF4, - }, - ]; - - let muxes = [ - // Front muxes for - // SMBUS_SP_TO_FRONT_SMCLK_A2_V3P3 - // SMBUS_SP_TO_FRONT_SMDAT_A2_V3P3 - I2cMux { - controller: Controller::I2C2, - port: Port::F, - id: Mux::M1, - driver: &drv_stm32h7_i2c::pca9548::Pca9548, - enable: None, - address: 0x70, - }, - - I2cMux { - controller: Controller::I2C2, - port: Port::F, - id: Mux::M1, - driver: &drv_stm32h7_i2c::pca9548::Pca9548, - enable: None, - address: 0x71, - }, - - I2cMux { - controller: Controller::I2C2, - port: Port::F, - id: Mux::M1, - driver: &drv_stm32h7_i2c::pca9548::Pca9548, - enable: None, - address: 0x72, - }, - - // M.2 mux on - // SMBUS_SP_TO_M2_SMCLK_A2_V3P3 - // SMBUS_SP_TO_M2_SMDAT_A2_V3P3 - I2cMux { - controller: Controller::I2C2, - port: Port::B, - id: Mux::M1, - driver: &drv_stm32h7_i2c::pca9548::Pca9548, - enable: None, - address: 0x73, - }, - ]; - } else { - compile_error!("no I2C controllers/pins for unknown board"); - } - } + let controllers = i2c_config::controllers(); + let pins = i2c_config::pins(); + let muxes = i2c_config::muxes(); // This is our actual mutable state let mut portmap = PortMap::new(); @@ -523,7 +186,7 @@ fn main() -> ! { } let controller = lookup_controller(&controllers, controller)?; - let port = validate_port(&pins, controller.controller, port)?; + validate_port(&pins, controller.controller, port)?; configure_port(&mut portmap, controller, port, &pins); @@ -618,13 +281,11 @@ fn configure_controllers(controllers: &[I2cController]) { fn configure_port( map: &mut PortMap, controller: &I2cController, - port: Port, + port: PortIndex, pins: &[I2cPin], ) { let current = map.get(controller.controller).unwrap(); - assert!(port != Port::Default); - if current == port { return; } diff --git a/drv/stm32h7-i2c/src/lib.rs b/drv/stm32h7-i2c/src/lib.rs index feccf70e8..54e35521c 100644 --- a/drv/stm32h7-i2c/src/lib.rs +++ b/drv/stm32h7-i2c/src/lib.rs @@ -23,7 +23,7 @@ use userlib::*; pub struct I2cPin { pub controller: drv_i2c_api::Controller, - pub port: drv_i2c_api::Port, + pub port: drv_i2c_api::PortIndex, pub gpio_pins: drv_stm32h7_gpio_api::PinSet, pub function: drv_stm32h7_gpio_api::Alternate, } @@ -85,7 +85,7 @@ pub trait I2cMuxDriver { pub struct I2cMux<'a> { pub controller: drv_i2c_api::Controller, - pub port: drv_i2c_api::Port, + pub port: drv_i2c_api::PortIndex, pub id: drv_i2c_api::Mux, pub driver: &'a dyn I2cMuxDriver, pub enable: Option, diff --git a/gemini-bu/app.toml b/gemini-bu/app.toml index 7598463df..277eb11f4 100644 --- a/gemini-bu/app.toml +++ b/gemini-bu/app.toml @@ -285,3 +285,115 @@ size = 1024 address = 0x58001c00 size = 1024 +[config] + +[[config.i2c.controllers]] +controller = 1 + +[config.i2c.controllers.ports.B] +name = "onboard" + +[[config.i2c.controllers.ports.B.pins]] +pins = [ 8, 9 ] +af = 4 + +[[config.i2c.controllers]] +controller = 2 +target = true + +[[config.i2c.controllers.ports.F.pins]] +pins = [ 0, 1] +af = 4 + +[[config.i2c.controllers]] +controller = 3 + +[[config.i2c.controllers.ports.H.pins]] +pins = [ 7, 8 ] +af = 4 + +[[config.i2c.controllers]] +controller = 4 + +[[config.i2c.controllers.ports.D.pins]] +pins = [ 12, 13 ] +af = 4 + +[[config.i2c.controllers.ports.F.pins]] +pins = [ 14, 15 ] +af = 4 + +[[config.i2c.controllers.ports.F.muxes]] +driver = "ltc4306" +address = 0x44 +enable = { gpio_port = "G", pins = [ 0 ], af = 0 } + +[[config.i2c.controllers.ports.H.pins]] +pins = [ 11, 12 ] +af = 4 + +[[config.i2c.devices]] +device = "max31790" +bus = "onboard" +address = 0x20 +description = "Fan controller" + +[[config.i2c.devices]] +device = "pca9555" +bus = "onboard" +address = 0x21 +description = "GPIO expander" + +[[config.i2c.devices]] +device = "ina219" +bus = "onboard" +address = 0x40 +description = "Current sensor" + +[[config.i2c.devices]] +device = "ina219" +bus = "onboard" +address = 0x41 +description = "Current sensor" + +[[config.i2c.devices]] +device = "ltc4306" +controller = 4 +port = "F" +address = 0x44 +description = "Multiplexer" + +# +# The following are devices for which Gemini BU has been or can be used as +# a development or evaluation vehicle +# +[[config.i2c.devices]] +device = "adm1272" +controller = 4 +port = "F" +mux = 1 +segment = 1 +address = 0x10 +description = "ADM1272 evaluation board" +pmbus = { rails = [ "ADM_EVL_VOUT" ] } + +[[config.i2c.devices]] +device = "isl68224" +controller = 4 +port = "F" +mux = 1 +segment = 3 +address = 0x60 +description = "ISL68224 evaluation board" +pmbus = { rails = [ "ISL_EVL_VOUT0", "ISL_EVL_VOUT1", "ISL_EVL_VOUT2" ] } + +[[config.i2c.devices]] +device = "tps546b24a" +controller = 4 +port = "F" +mux = 1 +segment = 4 +address = 0x24 +description = "TPS546B24A evaluation board" +pmbus = { rails = [ "TPS_EVL_VOUT" ] } + diff --git a/gemini-bu/src/apiwrap.rs b/gemini-bu/src/apiwrap.rs deleted file mode 100644 index aa0acf89a..000000000 --- a/gemini-bu/src/apiwrap.rs +++ /dev/null @@ -1,288 +0,0 @@ -//! Alternate register access sketch -//! -//! Principles: -//! -//! - Values must be read faithfully. The result of a read should contain all of -//! the bits read, even if they're invalid. -//! -//! - If a field is extracted into a limited-domain type, such as an enum, and -//! its value is invalid, we must notice. Return None or panic. -//! -//! - We'll try our best not to write invalid values to reserved bits. The type -//! used to write must maintain WZ/WO and "keep at reset value" fields in -//! their proper shape. -//! -//! - The "read and write it back with changes" pattern should be simple and -//! cheap. In the case of reset values that must be preserved, etc., we'll -//! need to replace the bits. - -use core::marker::PhantomData; - -pub struct PwrRegisters { - pub cr1: Reg, - pub csr1: Reg, - pub cr2: Reg, - pub cr3: Reg, -// pub cpucr: CPUCR, -// _reserved5: [u8; 4usize], -// pub d3cr: D3CR, -// _reserved6: [u8; 4usize], -// pub wkupcr: WKUPCR, -// pub wkupfr: WKUPFR, -// pub wkupepr: WKUPEPR, -} - -impl PwrRegisters { - pub unsafe fn get() -> &'static Self { - &*(0x5802_4800 as *const _) - } -} - -///////////////////////////////// -// CR1 definition - -pub enum CR1 {} - -impl RegPersonality for CR1 { - type Rep = u32; -} - -impl CanRead for CR1 { - type R = Cr1Value; -} - -#[derive(Copy, Clone)] -pub struct Cr1Value(u32); - -impl From for Cr1Value { - fn from(bits: u32) -> Self { - Cr1Value(bits) - } -} - -///////////////////////////////// -// CSR1 definition - -pub enum CSR1 {} - -impl RegPersonality for CSR1 { - type Rep = u32; -} - -impl CanRead for CSR1 { - type R = Csr1Value; -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Csr1Value(u32); - -impl From for Csr1Value { - fn from(bits: u32) -> Self { - Csr1Value(bits) - } -} - -impl Csr1Value { - pub fn mmcvdo(self) -> bool { - self.0 & (1 << 17) != 0 - } - pub fn avdo(self) -> bool { - self.0 & (1 << 16) != 0 - } - pub fn actvosrdy(self) -> bool { - self.0 & (1 << 13) != 0 - } - pub fn pvdo(self) -> bool { - self.0 & (1 << 4) != 0 - } -} - -///////////////////////////////// -// CR2 definition - -pub enum CR2 {} - -impl RegPersonality for CR2 { - type Rep = u32; -} - -impl CanRead for CR2 { - type R = Cr2Value; -} - -#[derive(Copy, Clone)] -pub struct Cr2Value(u32); - -impl From for Cr2Value { - fn from(bits: u32) -> Self { - Cr2Value(bits) - } -} - -///////////////////////////////// -// CR3 definition - -pub enum CR3 {} - -impl RegPersonality for CR3 { - type Rep = u32; -} - -impl CanRead for CR3 { - type R = Cr3Value; -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Cr3Value(u32); - -impl From for Cr3Value { - fn from(bits: u32) -> Self { - Cr3Value(bits) - } -} - -impl Cr3Value { - pub fn usb33rdy(self) -> bool { - self.0 & (1 << 26) != 0 - } - pub fn usbregen(self) -> bool { - self.0 & (1 << 25) != 0 - } - pub fn usb33den(self) -> bool { - self.0 & (1 << 24) != 0 - } - pub fn smpsextrdy(self) -> bool { - self.0 & (1 << 16) != 0 - } - pub fn vbrs(self) -> bool { - self.0 & (1 << 9) != 0 - } - pub fn vbe(self) -> bool { - self.0 & (1 << 8) != 0 - } - pub fn smpslevel(self) -> SmpsLevel { - match (self.0 >> 4) & 0b11 { - 0b00 => SmpsLevel::ResetValue, - 0b01 => SmpsLevel::V1_8, - 0b10 => SmpsLevel::V2_5, - _ => SmpsLevel::V2_5a, - } - } - pub fn smpsexthp(self) -> SmpsExtHP { - if self.0 & (1 << 3) != 0 { - SmpsExtHP::External - } else { - SmpsExtHP::Normal - } - } - pub fn smpsen(self) -> bool { - self.0 & (1 << 2) != 0 - } - pub fn ldoen(self) -> bool { - self.0 & (1 << 1) != 0 - } - pub fn bypass(self) -> bool { - self.0 & (1 << 0) != 0 - } -} - -impl CanWrite for CR3 { - type W = Cr3Update; -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Cr3Update(u32); - -impl From for u32 { - fn from(x: Cr3Update) -> Self { - x.0 - } -} - -impl Cr3Update { - pub fn with_usbregen(self, v: bool) -> Self { - Self(self.0 & !(1 << 25) | u32::from(v) << 25) - } - pub fn with_usb33den(self, v: bool) -> Self { - Self(self.0 & !(1 << 24) | u32::from(v) << 24) - } - pub fn with_vbrs(self, v: bool) -> Self { - Self(self.0 & !(1 << 9) | u32::from(v) << 9) - } - pub fn with_vbe(self, v: bool) -> Self { - Self(self.0 & !(1 << 8) | u32::from(v) << 8) - } - pub fn with_smpslevel(self, v: SmpsLevel) -> Self { - Self(self.0 & !(0b11 << 4) | (v as u32) << 4) - } - pub fn with_smpsexthp(self, v: SmpsExtHP) -> Self { - Self(self.0 & !(1 << 3) | (v as u32) << 3) - } - pub fn with_smpsen(self, v: bool) -> Self { - Self(self.0 & !(1 << 2) | u32::from(v) << 2) - } - pub fn with_ldoen(self, v: bool) -> Self { - Self(self.0 & !(1 << 1) | u32::from(v) << 1) - } - pub fn with_bypass(self, v: bool) -> Self { - Self(self.0 & !(1 << 0) | u32::from(v) << 0) - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum SmpsLevel { - ResetValue = 0b00, - V1_8 = 0b01, - V2_5 = 0b10, - V2_5a = 0b11, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum SmpsExtHP { - Normal = 0, - External = 1, -} - -impl CanModify for CR3 { - fn write_from_read(r: Self::R) -> Self::W { - Cr3Update(r.0 & 0x0701_033f) - } -} - -///////////////////////////////// -// Generic register support code - -pub struct Reg { - inner: vcell::VolatileCell, -} - -pub trait RegPersonality { - type Rep: Copy; -} - -pub trait CanRead: RegPersonality { - type R: From + Copy; -} - -pub trait CanWrite: RegPersonality { - type W: Into; -} - -pub trait CanModify: CanRead + CanWrite { - fn write_from_read(_: Self::R) -> Self::W; -} - -impl Reg { - pub fn read(&self) -> Pers::R where Pers: CanRead { - Pers::R::from(self.inner.get()) - } - pub fn write(&self, value: Pers::W) where Pers: CanWrite { - self.inner.set(value.into()); - } - pub fn modify(&self, f: impl FnOnce(Pers::R, Pers::W) -> Pers::W) - where Pers: CanModify - { - let r = self.read(); - self.write(f(r, Pers::write_from_read(r))) - } -} diff --git a/gimlet/app.toml b/gimlet/app.toml index 241ed9d60..31e6eb575 100644 --- a/gimlet/app.toml +++ b/gimlet/app.toml @@ -136,6 +136,18 @@ rcc_driver = "rcc_driver" gpio_driver = "gpio_driver" i2c_driver = "i2c_driver" +[tasks.thermal] +path = "../task-thermal" +name = "task-thermal" +features = ["itm"] +priority = 3 +requires = {flash = 65536, ram = 8192 } +stacksize = 2048 +start = true + +[tasks.thermal.task-slots] +i2c_driver = "i2c_driver" + [tasks.hiffy] path = "../task-hiffy" name = "task-hiffy" @@ -232,3 +244,276 @@ size = 1024 [peripherals.quadspi] address = 0x52005000 size = 4096 + +[config] + +# +# I2C1: SPD proxy bus +# +[[config.i2c.controllers]] +controller = 1 +target = true + +# +# SMBUS_SPD_PROXY_SP3_TO_SP_SMCLK +# SMBUS_SPD_PROXY_SP3_TO_SP_SMDAT +# +[config.i2c.controllers.ports.B] +name = "spd" +description = "SPD proxy" +pins = [ { pins = [ 0, 1 ], af = 4 } ] + +# +# I2C2: Front/M.2 bus +# +[[config.i2c.controllers]] +controller = 2 + +# +# SMBUS_SP_TO_M2_SMCLK_A2_V3P3 +# SMBUS_SP_TO_M2_SMDAT_A2_V3P3 +# +[config.i2c.controllers.ports.B] +name = "m2" +description = "M.2 bus" +pins = [ { pins = [ 10, 11 ], af = 4 } ] +muxes = [ { driver = "pca9548", address = 0x73 } ] + +# +# SMBUS_SP_TO_LVL_FRONT_SMDAT +# SMBUS_SP_TO_LVL_FRONT_SMCLK +# +[config.i2c.controllers.ports.F] +name = "front" +description = "Front bus" +pins = [ { pins = [ 0, 1 ], af = 4 } ] + +# +# Shark fin muxes +# +[[config.i2c.controllers.ports.F.muxes]] +driver = "pca9548" +address = 0x70 + +[[config.i2c.controllers.ports.F.muxes]] +driver = "pca9548" +address = 0x71 + +[[config.i2c.controllers.ports.F.muxes]] +driver = "pca9548" +address = 0x72 + +# +# I2C3: Mid bus +# +[[config.i2c.controllers]] +controller = 3 + +# +# SMBUS_SP_TO_LVL_MID_SMCLK +# SMBUS_SP_TO_LVL_MID_SMDAT +# +[config.i2c.controllers.ports.H] +name = "mid" +description = "Mid bus" +pins = [ { pins = [ 7, 8 ], af = 4 } ] + +# +# I2C4: Rear bus +# +[[config.i2c.controllers]] +controller = 4 + +# +# SMBUS_SP_TO_LVL_REAR_SMCLK +# SMBUS_SP_TO_LVL_REAR_SMDAT +# +[config.i2c.controllers.ports.F] +name = "rear" +description = "Rear bus" +pins = [ { pins = [ 14, 15 ], af = 4 } ] + +[[config.i2c.devices]] +bus = "front" +address = 0x48 +device = "tmp117" +name = "zone1" +description = "Front temperature sensor (zone 1)" +removable = true + +[[config.i2c.devices]] +bus = "front" +address = 0x49 +device = "tmp117" +name = "zone2" +description = "Front temperature sensor (zone 2)" +removable = true + +[[config.i2c.devices]] +bus = "front" +address = 0x4a +device = "tmp117" +name = "zone3" +description = "Front temperature sensor (zone 3)" +removable = true + +[[config.i2c.devices]] +bus = "front" +address = 0x70 +device = "pca9545" +description = "U.2 ABCD mux" + +[[config.i2c.devices]] +bus = "front" +address = 0x71 +device = "pca9545" +description = "U.2 EFGH mux" + +[[config.i2c.devices]] +bus = "front" +address = 0x72 +device = "pca9545" +description = "U.2 IJ mux" + +[[config.i2c.devices]] +bus = "m2" +address = 0x73 +device = "pca9545" +description = "M.2 mux" + +[[config.i2c.devices]] +bus = "mid" +address = 0x24 +device = "tps546b24a" +description = "A2 3.3V rail" +pmbus = { rails = [ "V3P3_SP_A2" ] } +refdes = "U522" + +[[config.i2c.devices]] +bus = "mid" +address = 0x27 +device = "tps546b24a" +description = "A2 1.8V rail" +pmbus = { rails = [ "V1P8_SP3" ] } +refdes = "U523" + +[[config.i2c.devices]] +bus = "mid" +address = 0x29 +device = "tps546b24a" +description = "A2 5V rail" +pmbus = { rails = [ "V5_SYS_A2" ] } +refdes = "U524" + +[[config.i2c.devices]] +bus = "mid" +address = 0x3a +device = "max5970" +description = "M.2 hot plug controller" + +[[config.i2c.devices]] +bus = "mid" +address = 0x58 +device = "idt8a34004" +description = "Clock generator" + +[[config.i2c.devices]] +bus = "mid" +address = 0x5a +device = "raa229618" +description = "CPU power controller" +pmbus = { rails = [ "VDD_MEM_ABCD", "VDD_VCORE" ] } +refdes = "U350" + +[[config.i2c.devices]] +bus = "mid" +address = 0x5b +device = "raa229618" +description = "SoC power controller" +pmbus = { rails = [ "VDD_MEM_EFGH", "VDDCR_SOC" ] } +refdes = "U351" + +[[config.i2c.devices]] +bus = "mid" +address = 0x5c +device = "isl68224" +description = "DIMM ABCD power controller" +pmbus = { rails = [ "VPP_ABCD", "V3P3_SYS", "" ] } +refdes = "U352" + +[[config.i2c.devices]] +bus = "mid" +address = 0x5d +device = "isl68224" +description = "DIMM EFGH power controller" +pmbus = { rails = [ "VPP_EFGH", "", "" ] } +refdes = "U418" + +[[config.i2c.devices]] +bus = "rear" +address = 0x10 +device = "adm1272" +description = "Fan hot swap controller" +pmbus = { rails = [ "V54_FAN" ] } +refdes = "U419" + +[[config.i2c.devices]] +bus = "rear" +address = 0x14 +device = "adm1272" +description = "Sled hot swap controller" +pmbus = { rails = [ "V54_HS_OUTPUT" ] } +refdes = "U452" + +[[config.i2c.devices]] +bus = "rear" +address = 0x20 +device = "max31790" +description = "Fan controller" + +[[config.i2c.devices]] +bus = "rear" +address = 0x48 +device = "tmp117" +name = "zone1" +description = "Rear temperature sensor (zone 1)" +removable = true + +[[config.i2c.devices]] +bus = "rear" +address = 0x49 +device = "tmp117" +name = "zone2" +description = "Rear temperature sensor (zone 2)" +removable = true + +[[config.i2c.devices]] +bus = "rear" +address = 0x4a +device = "tmp117" +name = "zone3" +description = "Rear temperature sensor (zone 3)" +removable = true + +[[config.i2c.devices]] +bus = "rear" +address = 0x4c +device = "tmp451" +description = "T6 temperature sensor" + +[[config.i2c.devices]] +bus = "rear" +address = 0x5f +device = "isl68224" +description = "T6 power controller" +pmbus = { rails = [ "V0P96_NIC_VDD" ] } +refdes = "U357" + +[[config.i2c.devices]] +bus = "rear" +address = 0x67 +device = "bmr491" +description = "IBC" +pmbus = { rails = [ "V12_SYS_A2" ] } +refdes = "U431" + diff --git a/gimlet/openocd.cfg b/gimlet/openocd.cfg index d33563379..bdfc0e06d 100644 --- a/gimlet/openocd.cfg +++ b/gimlet/openocd.cfg @@ -18,7 +18,6 @@ source [find target/stm32h7x_dual_bank.cfg] # implementation. # $_CHIPNAME.cpu0 configure -event examine-end { - echo "Info : executing patched examine-end for $_CHIPNAME.cpu0" # Enable D3 and D1 DBG clocks # DBGMCU_CR |= D3DBGCKEN | D1DBGCKEN stm32h7x_dbgmcu_mmw 0x004 0x00600000 0 diff --git a/gimlet/src/apiwrap.rs b/gimlet/src/apiwrap.rs deleted file mode 100644 index aa0acf89a..000000000 --- a/gimlet/src/apiwrap.rs +++ /dev/null @@ -1,288 +0,0 @@ -//! Alternate register access sketch -//! -//! Principles: -//! -//! - Values must be read faithfully. The result of a read should contain all of -//! the bits read, even if they're invalid. -//! -//! - If a field is extracted into a limited-domain type, such as an enum, and -//! its value is invalid, we must notice. Return None or panic. -//! -//! - We'll try our best not to write invalid values to reserved bits. The type -//! used to write must maintain WZ/WO and "keep at reset value" fields in -//! their proper shape. -//! -//! - The "read and write it back with changes" pattern should be simple and -//! cheap. In the case of reset values that must be preserved, etc., we'll -//! need to replace the bits. - -use core::marker::PhantomData; - -pub struct PwrRegisters { - pub cr1: Reg, - pub csr1: Reg, - pub cr2: Reg, - pub cr3: Reg, -// pub cpucr: CPUCR, -// _reserved5: [u8; 4usize], -// pub d3cr: D3CR, -// _reserved6: [u8; 4usize], -// pub wkupcr: WKUPCR, -// pub wkupfr: WKUPFR, -// pub wkupepr: WKUPEPR, -} - -impl PwrRegisters { - pub unsafe fn get() -> &'static Self { - &*(0x5802_4800 as *const _) - } -} - -///////////////////////////////// -// CR1 definition - -pub enum CR1 {} - -impl RegPersonality for CR1 { - type Rep = u32; -} - -impl CanRead for CR1 { - type R = Cr1Value; -} - -#[derive(Copy, Clone)] -pub struct Cr1Value(u32); - -impl From for Cr1Value { - fn from(bits: u32) -> Self { - Cr1Value(bits) - } -} - -///////////////////////////////// -// CSR1 definition - -pub enum CSR1 {} - -impl RegPersonality for CSR1 { - type Rep = u32; -} - -impl CanRead for CSR1 { - type R = Csr1Value; -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Csr1Value(u32); - -impl From for Csr1Value { - fn from(bits: u32) -> Self { - Csr1Value(bits) - } -} - -impl Csr1Value { - pub fn mmcvdo(self) -> bool { - self.0 & (1 << 17) != 0 - } - pub fn avdo(self) -> bool { - self.0 & (1 << 16) != 0 - } - pub fn actvosrdy(self) -> bool { - self.0 & (1 << 13) != 0 - } - pub fn pvdo(self) -> bool { - self.0 & (1 << 4) != 0 - } -} - -///////////////////////////////// -// CR2 definition - -pub enum CR2 {} - -impl RegPersonality for CR2 { - type Rep = u32; -} - -impl CanRead for CR2 { - type R = Cr2Value; -} - -#[derive(Copy, Clone)] -pub struct Cr2Value(u32); - -impl From for Cr2Value { - fn from(bits: u32) -> Self { - Cr2Value(bits) - } -} - -///////////////////////////////// -// CR3 definition - -pub enum CR3 {} - -impl RegPersonality for CR3 { - type Rep = u32; -} - -impl CanRead for CR3 { - type R = Cr3Value; -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Cr3Value(u32); - -impl From for Cr3Value { - fn from(bits: u32) -> Self { - Cr3Value(bits) - } -} - -impl Cr3Value { - pub fn usb33rdy(self) -> bool { - self.0 & (1 << 26) != 0 - } - pub fn usbregen(self) -> bool { - self.0 & (1 << 25) != 0 - } - pub fn usb33den(self) -> bool { - self.0 & (1 << 24) != 0 - } - pub fn smpsextrdy(self) -> bool { - self.0 & (1 << 16) != 0 - } - pub fn vbrs(self) -> bool { - self.0 & (1 << 9) != 0 - } - pub fn vbe(self) -> bool { - self.0 & (1 << 8) != 0 - } - pub fn smpslevel(self) -> SmpsLevel { - match (self.0 >> 4) & 0b11 { - 0b00 => SmpsLevel::ResetValue, - 0b01 => SmpsLevel::V1_8, - 0b10 => SmpsLevel::V2_5, - _ => SmpsLevel::V2_5a, - } - } - pub fn smpsexthp(self) -> SmpsExtHP { - if self.0 & (1 << 3) != 0 { - SmpsExtHP::External - } else { - SmpsExtHP::Normal - } - } - pub fn smpsen(self) -> bool { - self.0 & (1 << 2) != 0 - } - pub fn ldoen(self) -> bool { - self.0 & (1 << 1) != 0 - } - pub fn bypass(self) -> bool { - self.0 & (1 << 0) != 0 - } -} - -impl CanWrite for CR3 { - type W = Cr3Update; -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Cr3Update(u32); - -impl From for u32 { - fn from(x: Cr3Update) -> Self { - x.0 - } -} - -impl Cr3Update { - pub fn with_usbregen(self, v: bool) -> Self { - Self(self.0 & !(1 << 25) | u32::from(v) << 25) - } - pub fn with_usb33den(self, v: bool) -> Self { - Self(self.0 & !(1 << 24) | u32::from(v) << 24) - } - pub fn with_vbrs(self, v: bool) -> Self { - Self(self.0 & !(1 << 9) | u32::from(v) << 9) - } - pub fn with_vbe(self, v: bool) -> Self { - Self(self.0 & !(1 << 8) | u32::from(v) << 8) - } - pub fn with_smpslevel(self, v: SmpsLevel) -> Self { - Self(self.0 & !(0b11 << 4) | (v as u32) << 4) - } - pub fn with_smpsexthp(self, v: SmpsExtHP) -> Self { - Self(self.0 & !(1 << 3) | (v as u32) << 3) - } - pub fn with_smpsen(self, v: bool) -> Self { - Self(self.0 & !(1 << 2) | u32::from(v) << 2) - } - pub fn with_ldoen(self, v: bool) -> Self { - Self(self.0 & !(1 << 1) | u32::from(v) << 1) - } - pub fn with_bypass(self, v: bool) -> Self { - Self(self.0 & !(1 << 0) | u32::from(v) << 0) - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum SmpsLevel { - ResetValue = 0b00, - V1_8 = 0b01, - V2_5 = 0b10, - V2_5a = 0b11, -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum SmpsExtHP { - Normal = 0, - External = 1, -} - -impl CanModify for CR3 { - fn write_from_read(r: Self::R) -> Self::W { - Cr3Update(r.0 & 0x0701_033f) - } -} - -///////////////////////////////// -// Generic register support code - -pub struct Reg { - inner: vcell::VolatileCell, -} - -pub trait RegPersonality { - type Rep: Copy; -} - -pub trait CanRead: RegPersonality { - type R: From + Copy; -} - -pub trait CanWrite: RegPersonality { - type W: Into; -} - -pub trait CanModify: CanRead + CanWrite { - fn write_from_read(_: Self::R) -> Self::W; -} - -impl Reg { - pub fn read(&self) -> Pers::R where Pers: CanRead { - Pers::R::from(self.inner.get()) - } - pub fn write(&self, value: Pers::W) where Pers: CanWrite { - self.inner.set(value.into()); - } - pub fn modify(&self, f: impl FnOnce(Pers::R, Pers::W) -> Pers::W) - where Pers: CanModify - { - let r = self.read(); - self.write(f(r, Pers::write_from_read(r))) - } -} diff --git a/gimletlet/app.toml b/gimletlet/app.toml index 9a0bbf7c7..77f2ceebc 100644 --- a/gimletlet/app.toml +++ b/gimletlet/app.toml @@ -276,3 +276,40 @@ size = 1024 [peripherals.quadspi] address = 0x52005000 size = 4096 + +[config] +[[config.i2c.controllers]] +controller = 2 +target = true + +[[config.i2c.controllers.ports.F.pins]] +pins = [ 0, 1 ] +af = 4 + +[[config.i2c.controllers]] +controller = 3 + +# +# Note that I2C3 on Gimletlet is a bit unusual in that its SCL and SDA are on +# two different ports (port A and port C, respectively); we therefore have two +# pin structures for I2C3, but for purposes of the abstraction that i2c +# exports to consumers, we adhere to the convention outlined in the +# definition of `PortIndex` and name the logical port C after the GPIO pin that +# corresponds to SDA. +# +[[config.i2c.controllers.ports.C.pins]] +gpio_port = "A" +pins = [ 8 ] +af = 4 + +[[config.i2c.controllers.ports.C.pins]] +gpio_port = "C" +pins = [ 9 ] +af = 4 + +[[config.i2c.controllers]] +controller = 4 + +[[config.i2c.controllers.ports.F.pins]] +pins = [ 14, 15 ] +af = 4 diff --git a/pmbus b/pmbus index fafbaa748..40fbd18f5 160000 --- a/pmbus +++ b/pmbus @@ -1 +1 @@ -Subproject commit fafbaa7486c975ae5a3652bc2d1c3521ebea33f3 +Subproject commit 40fbd18f541039a99f8dcd923fa4990017a12a04 diff --git a/sidecar/Cargo.toml b/sidecar/Cargo.toml new file mode 100644 index 000000000..c01cafefd --- /dev/null +++ b/sidecar/Cargo.toml @@ -0,0 +1,38 @@ +[package] +edition = "2018" +readme = "README.md" +name = "sidecar" +version = "0.1.0" + +[features] +default = ["standalone"] +standalone = ["itm"] +itm = ["panic-itm", "kern/klog-itm"] +semihosting = ["panic-semihosting", "kern/klog-semihosting"] + +[dependencies] +cortex-m = { version = "0.7", features = ["inline-asm"] } +cortex-m-rt = "0.6.12" +cortex-m-semihosting = "0.3.5" +panic-itm = { version = "0.4.1", optional = true } +panic-halt = { version = "0.2.0", optional = true } +panic-semihosting = { version = "0.5.3", optional = true } +cfg-if = "0.1.10" +stm32h7 = { version = "0.13.0", features = ["rt", "stm32h743"] } + +[dependencies.kern] +path = "../kern" +default-features = false + +[build-dependencies] +build-util = {path = "../build-util"} + +# a target for `cargo xtask check` +[package.metadata.build] +target = "thumbv7em-none-eabihf" + +# this lets you use `cargo fix`! +[[bin]] +name = "sidecar" +test = false +bench = false diff --git a/sidecar/README.md b/sidecar/README.md new file mode 100644 index 000000000..31e7524e7 --- /dev/null +++ b/sidecar/README.md @@ -0,0 +1,2 @@ +# Sidecar STM32H7 Firmware + diff --git a/sidecar/app.toml b/sidecar/app.toml new file mode 100644 index 000000000..840c8d7e0 --- /dev/null +++ b/sidecar/app.toml @@ -0,0 +1,465 @@ +name = "sidecar" +target = "thumbv7em-none-eabihf" +board = "sidecar-1" +stacksize = 1024 + +[kernel] +path = "." +name = "sidecar" +requires = {flash = 32768, ram = 4096} +# +# For the kernel (and for any task that logs), we are required to enable +# either "itm" (denoting logging/panicking via ARM's Instrumentation Trace +# Macrocell) or "semihosting" (denoting logging/panicking via ARM +# semihosting). We are biased to ITM because semihosting is excruciatingly +# slow (it is breakpoint based) and has an undesirable failure mode if logging +# output is generated and debugger is not attached (namely, the target stops). +# If one does choose to change this to semihosting for purposes of +# development, be sure to also change it in every task of interest. +# +features = ["itm"] + +[supervisor] +notification = 1 + +# Flash sections are mapped into flash bank 1 (of 2). +[outputs.flash] +address = 0x08000000 +size = 1048576 +read = true +execute = true + +# RAM sections are currently mapped into DTCM, a small but fast SRAM. +[outputs.ram] +address = 0x20000000 +size = 131072 +read = true +write = true +execute = false # let's assume XN until proven otherwise + +[tasks.jefe] +path = "../task-jefe" +name = "task-jefe" +priority = 0 +requires = {flash = 16384, ram = 2048} +start = true +features = ["itm"] +stacksize = 1536 + +[tasks.rcc_driver] +path = "../drv/stm32h7-rcc" +name = "drv-stm32h7-rcc" +priority = 1 +requires = {flash = 8192, ram = 1024} +uses = ["rcc"] +start = true + +[tasks.gpio_driver] +path = "../drv/stm32h7-gpio" +name = "drv-stm32h7-gpio" +priority = 2 +requires = {flash = 8192, ram = 1024} +uses = ["gpios1", "gpios2", "gpios3"] +start = true + +[tasks.gpio_driver.task-slots] +rcc_driver = "rcc_driver" + +[tasks.i2c_driver] +path = "../drv/stm32h7-i2c-server" +name = "drv-stm32h7-i2c-server" +features = ["h743", "itm"] +priority = 2 +requires = {flash = 16384, ram = 2048} +uses = ["i2c1", "i2c2", "i2c3", "i2c4"] +start = true + +[tasks.i2c_driver.interrupts] +31 = 0b0000_0001 # I2C1 event +32 = 0b0000_0001 # I2C1 error +33 = 0b0000_0010 # I2C2 event +34 = 0b0000_0010 # I2C2 error +72 = 0b0000_0100 # I2C3 event +73 = 0b0000_0100 # I2C3 error +95 = 0b0000_1000 # I2C4 event +96 = 0b0000_1000 # I2C4 error + +[tasks.i2c_driver.task-slots] +gpio_driver = "gpio_driver" +rcc_driver = "rcc_driver" + +[tasks.hiffy] +path = "../task-hiffy" +name = "task-hiffy" +features = ["stm32h7", "itm", "i2c", "gpio"] +priority = 3 +requires = {flash = 32768, ram = 32768 } +start = true + +[tasks.hiffy.task-slots] +gpio_driver = "gpio_driver" +i2c_driver = "i2c_driver" + +[tasks.idle] +path = "../task-idle" +name = "task-idle" +priority = 5 +requires = {flash = 256, ram = 256} +stacksize = 256 +start = true + +[peripherals.rcc] +address = 0x58024400 +size = 1024 + +[peripherals.gpios1] +address = 0x58020000 +size = 0x2000 + +[peripherals.gpios2] +address = 0x58022000 +size = 0x0800 + +[peripherals.gpios3] +address = 0x58022800 +size = 0x0400 + +[peripherals.spi2] +address = 0x40003800 +size = 1024 + +[peripherals.spi4] +address = 0x40013400 +size = 1024 + +[peripherals.usart3] +address = 0x40004800 +size = 1024 + +[peripherals.i2c1] +address = 0x40005400 +size = 1024 + +[peripherals.i2c2] +address = 0x40005800 +size = 1024 + +[peripherals.i2c3] +address = 0x40005c00 +size = 1024 + +[peripherals.i2c4] +address = 0x58001c00 +size = 1024 + +[peripherals.quadspi] +address = 0x52005000 +size = 4096 + +[config] + +# +# I2C1: Northeast corridors +# +[[config.i2c.controllers]] +controller = 1 + +# +# I2C_NORTH_EAST0_SCL +# I2C_NORTH_EAST0_SDA +# +[config.i2c.controllers.ports.B1] +name = "northeast0" +description = "Northeast Corridor 0" +pins = [ { gpio_port = "B", pins = [ 6, 7 ], af = 4 } ] + +# +# I2C_NORTH_EAST1_SCL +# I2C_NORTH_EAST1_SDA +# +[config.i2c.controllers.ports.B2] +name = "northeast1" +description = "Northeast Corridor 1" +pins = [ { gpio_port = "B", pins = [ 8, 9 ], af = 4 } ] + +# +# I2C2: Front I/O +# +[[config.i2c.controllers]] +controller = 2 + +# +# I2C_FRONT_IO0_SCL +# I2C_FRONT_IO0_SDA +# +[config.i2c.controllers.ports.F] +name = "frontio" +description = "Front I/O Board" +pins = [ { pins = [ 0, 1 ], af = 4 } ] + +# +# I2C_FRONT_IO1_SCL +# I2C_FRONT_IO1_SDA +# +[config.i2c.controllers.ports.H] +name = "frontgps" +description = "Front I/O GPS" +pins = [ { pins = [ 4, 5 ], af = 4 } ] + +# +# I2C3: Northwest corridors +# +[[config.i2c.controllers]] +controller = 3 + +# +# I2C_NORTH_WEST0_SCL +# I2C_NORTH_WEST0_SDL +# +[config.i2c.controllers.ports.C] +name = "northwest0" +description = "Northwest Corridor 0" + +[[config.i2c.controllers.ports.C.pins]] +gpio_port = "A" +pins = [ 8 ] +af = 4 + +[[config.i2c.controllers.ports.C.pins]] +gpio_port = "C" +pins = [ 9 ] +af = 4 + +# +# I2C_NORTH_WEST1_SCL +# I2C_NORTH_WEST1_SDL +# +[config.i2c.controllers.ports.H] +name = "northwest1" +description = "Northwest Corridor 1" +pins = [ { pins = [ 7, 8 ], af = 4 } ] + +# +# I2C4: South bend +# +[[config.i2c.controllers]] +controller = 4 + +# +# I2C_SOUTH0_SCL +# I2C_SOUTH0_SDA +# +[config.i2c.controllers.ports.F] +name = "south0" +description = "South Bend 0" +pins = [ { pins = [ 14, 15 ], af = 4 } ] + +# +# I2C_SOUTH1_SCL +# I2C_SOUTH1_SDA +# +[config.i2c.controllers.ports.H] +name = "south1" +description = "South Bend 1" +pins = [ { pins = [ 11, 12 ], af = 4 } ] + +# +# I2C_SOUTH2_SCL +# I2C_SOUTH2_SDA +# +[config.i2c.controllers.ports.D] +name = "south2" +description = "South Bend 2" +pins = [ { pins = [ 12, 13 ], af = 4 } ] + +[[config.i2c.devices]] +device = "bmr480" +bus = "northwest0" +address = 0b0010_111 +description = "IBC" + +[[config.i2c.devices]] +device = "bmr491" +bus = "northwest0" +address = 0b1100_111 +description = "IBC" + +[[config.i2c.devices]] +device = "adm1272" +bus = "northwest0" +address = 0b0010_110 +description = "54V hot swap controller" + +[[config.i2c.devices]] +device = "adm1272" +bus = "northeast1" +address = 0b0010_000 +description = "Fan 0 hot swap controller" + +[[config.i2c.devices]] +device = "adm1272" +bus = "northeast0" +address = 0b0010_011 +description = "Fan 1 hot swap controller" + +[[config.i2c.devices]] +device = "adm1272" +bus = "northwest1" +address = 0b0010_000 +description = "Fan 2 hot swap controller" + +[[config.i2c.devices]] +device = "adm1272" +bus = "northwest1" +address = 0b0010_011 +description = "Fan 3 hot swap controller" + +[[config.i2c.devices]] +device = "tps546b24a" +bus = "northwest0" +address = 0b0011_001 +description = "V5P0_SYS rail" + +[[config.i2c.devices]] +device = "tps546b24a" +bus = "northeast1" +address = 0b0011_010 +description = "V3P3_SYS rail" + +[[config.i2c.devices]] +device = "tps546b24a" +bus = "south1" +address = 0b0011_011 +description = "V1P0_SYS rail" + +[[config.i2c.devices]] +device = "tps546b24a" +bus = "south1" +address = 0b0011_100 +description = "V1P8_SYS rail" + +[[config.i2c.devices]] +device = "raa229618" +bus = "northeast0" +address = 0b1100_011 +description = "TF2 VDD rail" + +[[config.i2c.devices]] +device = "raa229618" +bus = "northwest0" +address = 0b1100_000 +description = "TF2 VDDA rail" + +[[config.i2c.devices]] +device = "isl68224" +bus = "south0" +address = 0b1100_010 +description = "VDD[A]18 rail" + +[[config.i2c.devices]] +device = "tmp451" +bus = "northwest0" +address = 0b1001_100 +description = "TF2 temperature sensor" + +[[config.i2c.devices]] +device = "tmp451" +bus = "south1" +address = 0b1001_100 +description = "VSC7448 temperature sensor" + +[[config.i2c.devices]] +device = "max31790" +bus = "northeast0" +address = 0b0100_011 +description = "Fan 0/1 controller" + +[[config.i2c.devices]] +device = "max31790" +bus = "northwest1" +address = 0b0100_000 +description = "Fan 2/3 controller" + +[[config.i2c.devices]] +device = "tmp117" +bus = "south0" +address = 0b1001_000 +description = "Front temperature sensor (east)" + +[[config.i2c.devices]] +device = "tmp117" +bus = "south0" +address = 0b1001_001 +description = "Front temperature sensor (central)" + +[[config.i2c.devices]] +device = "tmp117" +bus = "south0" +address = 0b1001_010 +description = "Front temperature sensor (west)" + +[[config.i2c.devices]] +device = "tmp117" +bus = "northeast1" +address = 0b1001_000 +description = "Rear temperature sensor (northeast 1)" + +[[config.i2c.devices]] +device = "tmp117" +bus = "northeast0" +address = 0b1001_001 +description = "Rear temperature sensor (northeast 0)" + +[[config.i2c.devices]] +device = "tmp117" +bus = "northwest0" +address = 0b1001_000 +description = "Rear temperature sensor (northwest 0)" + +[[config.i2c.devices]] +device = "tmp117" +bus = "northwest1" +address = 0b1001_001 +description = "Rear temperature sensor (northwest 1)" + +# [[config.i2c.devices]] +# device = "at24csw080" +# bus = "northeast0" +# address = 0b1010_0** +# description = "" + +# [[config.i2c.devices]] +# device = "at24csw080" +# bus = "northwest1" +# address = 0b1010_0** +# description = "" + +[[config.i2c.devices]] +device = "idt8a34001" +bus = "south0" +address = 0b1011_000 +description = "Clock generator" + +[[config.i2c.devices]] +device = "pca9545" +bus = "northwest1" +address = 0b1110_000 +description = "Northwest fan mux" + +[[config.i2c.devices]] +device = "pca9545" +bus = "northeast0" +address = 0b1110_000 +description = "Northeast fan mux" + +# [[config.i2c.devices]] +# device = "at24csw080" +# bus = "south2" +# address = 0b1010_0** +# description = "" + +# [[config.i2c.devices]] +# device = "tf2" +# bus = "ECP5->S?" +# address = 0b1011_011 +# description = "" diff --git a/sidecar/build.rs b/sidecar/build.rs new file mode 100644 index 000000000..941cd93f4 --- /dev/null +++ b/sidecar/build.rs @@ -0,0 +1,3 @@ +fn main() { + build_util::expose_target_board(); +} diff --git a/sidecar/openocd.cfg b/sidecar/openocd.cfg new file mode 100644 index 000000000..bdfc0e06d --- /dev/null +++ b/sidecar/openocd.cfg @@ -0,0 +1,43 @@ +gdb_port 3366 +telnet_port 4455 + +source [find interface/stlink.cfg] +source [find target/stm32h7x_dual_bank.cfg] + +# +# Unfortunately, there exists a bug in OpenOCD's stm32h7x.cfg file whereby it +# will set reserved bits in the STM32H7 DBGMCU CR: +# +# https://sourceforge.net/p/openocd/tickets/266/ +# +# The documentation is clear that these must be preserved at their reset +# value (namely: zero) -- and setting them causes SWO to not function at all. +# Making what feels like the reasonable assumption that this bug will never +# actually be fixed by OpenOCD, we instead workaround it by overriding the +# event in which the damage is done, replacing it with the correct +# implementation. +# +$_CHIPNAME.cpu0 configure -event examine-end { + # Enable D3 and D1 DBG clocks + # DBGMCU_CR |= D3DBGCKEN | D1DBGCKEN + stm32h7x_dbgmcu_mmw 0x004 0x00600000 0 + + # Enable debug during low power modes (uses more power) + # DBGMCU_CR |= DBG_STANDBY | DBG_STOP | DBG_SLEEP + # + # Note that we do this ONLY for D1; setting this for D3 (a.k.a. + # SmartRun, a.k.a. SRD) and/or the reserved bits that may have once + # corresponded to D2 is what causes SWO to malfunction. As an added + # precaution, we also take the step of explicitly clearing these bits, + # should a broken OpenOCD have already set them. + stm32h7x_dbgmcu_mmw 0x004 0x00000007 0x000001B8 + + # Stop watchdog counters during halt + # DBGMCU_APB3FZ1 |= WWDG1 + stm32h7x_dbgmcu_mmw 0x034 0x00000040 0 + # DBGMCU_APB1LFZ1 |= WWDG2 + stm32h7x_dbgmcu_mmw 0x03C 0x00000800 0 + # DBGMCU_APB4FZ1 |= WDGLSD1 | WDGLSD2 + stm32h7x_dbgmcu_mmw 0x054 0x000C0000 0 +} + diff --git a/sidecar/openocd.gdb b/sidecar/openocd.gdb new file mode 100644 index 000000000..f45eab4ea --- /dev/null +++ b/sidecar/openocd.gdb @@ -0,0 +1,30 @@ +target extended-remote :3366 + +# print demangled symbols +set print asm-demangle on + +# set backtrace limit to not have infinite backtrace loops +set backtrace limit 32 + +# detect unhandled exceptions, hard faults and panics +break HardFault + +monitor arm semihosting enable + +# # send captured ITM to the file itm.fifo +# # (the microcontroller SWO pin must be connected to the programmer SWO pin) +# # 8000000 must match the core clock frequency +# monitor tpiu config internal itm.txt uart off 8000000 + +# # OR: make the microcontroller SWO pin output compatible with UART (8N1) +# # 8000000 must match the core clock frequency +# # 2000000 is the frequency of the SWO pin +# monitor tpiu config external uart off 8000000 2000000 + +# # enable ITM port 0 +# monitor itm port 0 on + +load + +# start the process but immediately halt the processor +stepi diff --git a/sidecar/src/main.rs b/sidecar/src/main.rs new file mode 100644 index 000000000..0c65537f6 --- /dev/null +++ b/sidecar/src/main.rs @@ -0,0 +1,310 @@ +#![no_std] +#![no_main] + +#[cfg(not(any(feature = "panic-itm", feature = "panic-semihosting")))] +compile_error!( + "Must have either feature panic-itm or panic-semihosting enabled" +); + +// Panic behavior controlled by Cargo features: +#[cfg(feature = "panic-itm")] +extern crate panic_itm; // breakpoint on `rust_begin_unwind` to catch panics +#[cfg(feature = "panic-semihosting")] +extern crate panic_semihosting; // requires a debugger + +use cortex_m_rt::pre_init; + +// 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::stm32h743 as device; + +use cortex_m_rt::entry; +use kern::app::App; + +extern "C" { + static hubris_app_table: App; + static mut __sheap: u8; + static __eheap: u8; +} + +#[entry] +fn main() -> ! { + system_init(); + + const CYCLES_PER_MS: u32 = 400_000; + + unsafe { + let heap_size = + (&__eheap as *const _ as usize) - (&__sheap as *const _ as usize); + kern::startup::start_kernel( + &hubris_app_table, + (&mut __sheap) as *mut _, + heap_size, + CYCLES_PER_MS, + ) + } +} + +#[pre_init] +unsafe fn system_pre_init() { + // Configure the power supply to latch the LDO on and prevent further + // reconfiguration. + // + // Normally we would use Peripherals::take() to safely get a reference to + // the PWR block, but that function expects RAM to be initialized and + // writable. At this point, RAM is neither -- because the chip requires us + // to get the power supply configuration right _before it guarantees that + // RAM will work._ + // + // Another case of the cortex_m/stm32 crates being designed with simpler + // systems in mind. + + // Synthesize a pointer using a const fn (which won't hit RAM) and then + // convert it to a reference. We can have a reference to PWR because it's + // hardware, and is thus not uninitialized. + let pwr = &*device::PWR::ptr(); + // Poke CR3 to enable the LDO and prevent further writes. + pwr.cr3.modify(|_, w| w.ldoen().set_bit()); + + // Busy-wait until the ACTVOSRDY bit says that we've stabilized at VOS3. + while !pwr.csr1.read().actvosrdy().bit() { + // spin + } + + // Okay, yay, we can use some RAMs now. + + // We'll do the rest in system_init. +} + +const USE_EXTERNAL_CRYSTAL: bool = true; + +fn system_init() { + // Basic RAMs are working, power is stable, and the runtime has initialized + // static variables. + // + // We are running at 64MHz on the HSI oscillator at voltage scale VOS3. + + // Use the crate peripheral take mechanism to get peripherals. + let mut cp = cortex_m::Peripherals::take().unwrap(); + let p = device::Peripherals::take().unwrap(); + + // Workaround for erratum 2.2.9 "Reading from AXI SRAM may lead to data + // read corruption" - limits AXI SRAM read concurrency. + p.AXI + .targ7_fn_mod + .modify(|_, w| w.read_iss_override().set_bit()); + + // Turn on the SYSCFG so that we can fix the next thing. + p.RCC.apb4enr.modify(|_, w| w.syscfgen().set_bit()); + cortex_m::asm::dmb(); + // Gimlet uses PA0_C instead of PA0. Flip this. + p.SYSCFG.pmcr.modify(|_, w| { + w.pa0so() + .clear_bit() + .pa1so() + .set_bit() + .pc2so() + .set_bit() + .pc3so() + .set_bit() + }); + + // The H7 -- and perhaps the Cortex-M7 -- has the somewhat annoying + // property that any attempt to use ITM without having TRCENA set in + // DBGMCU results in the FIFO never being ready (that is, ITM writes + // spin). This is not consistent with previous generations (e.g., M3, + // M4), but it's also not inconsistent with the docs, which explicitly + // warn that stimulus ports are in an undefined state if TRCENA hasn't + // been set. So we enable tracing on ourselves as a first action, even + // though that isn't terribly meaningful if there is no debugger to + // consume the ITM output. It follows from the above, but just to be + // unequivocal: ANY use of ITM prior to this point will lock the system + // if/when an external debugger has not set TRCENA! + cp.DCB.enable_trace(); + + // Make sure debugging works in standby. + p.DBGMCU.cr.modify(|_, w| { + w.d3dbgcken() + .set_bit() + .d1dbgcken() + .set_bit() + .dbgstby_d1() + .set_bit() + .dbgstop_d1() + .set_bit() + .dbgsleep_d1() + .set_bit() + }); + + // Turn on CPU I/D caches to improve performance at the higher clock speeds + // we're about to enable. + cp.SCB.enable_icache(); + cp.SCB.enable_dcache(&mut cp.CPUID); + + // The Flash controller comes out of reset configured for 3 wait states. + // That's approximately correct for 64MHz at VOS3, which is fortunate, since + // we've been executing instructions out of flash _the whole time._ + + // Our goal is now to boost the CPU frequency to its final level. This means + // raising the core supply voltage from VOS3 -- to VOS1 on H753 -- and + // adding wait states or reduced divisors to a bunch of things, and then + // finally making the actual change. + + // We're allowed to hop directly from VOS3 to VOS1; the manual doesn't say + // this explicitly but the ST drivers do it. + // + // Bits are still unsafe in the API but name at least matches the manual. + p.PWR.d3cr.write(|w| unsafe { w.vos().bits(0b11) }); + // Busy-wait for the voltage to reach the right level. + while !p.PWR.d3cr.read().vosrdy().bit() { + // spin + } + // We are now at VOS1/0. + + if USE_EXTERNAL_CRYSTAL { + // There's an 8MHz crystal on our board. We'll use it as our clock + // source, to get higher accuracy than the internal oscillator. Turn + // on the High Speed External oscillator. + p.RCC.cr.modify(|_, w| w.hseon().set_bit()); + // Wait for it to stabilize. + while !p.RCC.cr.read().hserdy().bit() { + // spin + } + + // 8MHz HSE -> DIVM -> VCO input freq: the VCO's input must be in the + // range 2-16MHz, so we want to bypass the prescaler by setting DIVM to + // 1. + p.RCC + .pllckselr + .modify(|_, w| w.divm1().bits(1).pllsrc().hse()); + // The VCO itself needs to be configured to expect a 8MHz input + // ("wide input range" or, on the slightly later parts, "range 8") and + // at its normal (wide) output range. We will also want its P-output, + // which is the output that's tied to the system clock. + // + // The Q tap goes to a bunch of peripheral kernel clocks. The R clock + // goes to the trace unit. + p.RCC.pllcfgr.modify(|_, w| { + w.pll1vcosel() + .wide_vco() + .pll1rge() + .range8() + .divp1en() + .enabled() + .divq1en() + .enabled() + .divr1en() + .enabled() + }); + // Now, we configure the VCO for reals. + // + // The N value is the multiplication factor for the VCO internal + // frequency relative to its input. The resulting internal frequency + // must be in the range 192-836MHz. To avoid needing to configure + // the fractional divider, we configure the VCO to 2x our target + // frequency, 800MHz, which is in turn exactly 100x our (divided) + // input frequency. + // + // The P value is the divisor from VCO frequency to system + // frequency, so it needs to be 2 to get a 400MHz P-output. + // + // We set the R output to the same frequency because it's what Humility + // currently expects, and drop the Q output for kernel clock use. + p.RCC.pll1divr.modify(|_, w| unsafe { + w.divn1() + .bits(100 - 1) + .divp1() + .div2() + // Q and R fields aren't modeled correctly in the API, so: + .divq1() + .bits(4 - 1) + .divr1() + .bits(1) + }); + } else { + // This clock setup code is based on the H743 Nucleo code, which didn't + // include an external crystal -- so it uses HSI64. (TODO: fix for + // actual Gemini crystal on HSE.) + + // PLL1 configuration: + // CPU freq = VCO / DIVP = HSI / DIVM * DIVN / DIVP + // = 64 / 4 * 50 / 2 + // = 400 Mhz + // System clock = 400 Mhz + // HPRE = /2 => AHB/Timer clock = 200 Mhz + + // Configure PLL + let divm = 4; + let divn = 50; + let divp = 2; + + p.RCC + .pllckselr + .write(|w| w.pllsrc().hsi().divm1().bits(divm)); + p.RCC.pllcfgr.write(|w| { + w.pll1vcosel() + .wide_vco() + .pll1rge() + .range8() + .divp1en() + .enabled() + .divr1en() + .enabled() + }); + p.RCC.pll1divr.write(|w| unsafe { + w.divp1() + .bits(divp - 1) + .divn1() + .bits(divn - 1) + .divr1() + .bits(divp - 1) + }); + } + + // Turn on PLL1 and wait for it to lock. + p.RCC.cr.modify(|_, w| w.pll1on().on()); + while !p.RCC.cr.read().pll1rdy().bit() { + // spin + } + + // PLL1's frequency will become the system clock, which in turn goes through + // a series of dividers to produce clocks for each system bus. + // Configure peripheral clock dividers to make sure we stay within + // range when we change oscillators. + p.RCC.d1cfgr.write(|w| { + w.d1cpre() + .div1() // CPU at full rate + .hpre() + .div2() // AHB at half that (200mhz) + .d1ppre() + .div2() // D1 APB3 a further 1/2 down (100mhz) + }); + // Other APB buses at HCLK/2 = CPU/4 = 100MHz + p.RCC.d2cfgr.write(|w| w.d2ppre1().div2().d2ppre2().div2()); + p.RCC.d3cfgr.write(|w| w.d3ppre().div2()); + + // Configure Flash for 200MHz (AHB) at VOS1: 2WS, 2 programming + // delay. See ref man Table 13 + p.FLASH + .acr + .write(|w| unsafe { w.latency().bits(2).wrhighfreq().bits(2) }); + while { + let r = p.FLASH.acr.read(); + r.latency().bits() != 2 || r.wrhighfreq().bits() != 2 + } {} + // Not that reordering is likely here, since we polled, but: we + // really do need the Flash to be programmed with more wait states + // before switching the clock. + cortex_m::asm::dmb(); + + // Right! We're all set to change our clock without overclocking anything by + // accident. Perform the switch. + p.RCC.cfgr.write(|w| w.sw().pll1()); + while !p.RCC.cfgr.read().sws().is_pll1() { + // spin + } + + // Hello from 400MHz! +} diff --git a/task-hiffy/Cargo.toml b/task-hiffy/Cargo.toml index fa20bb335..56dcba8c9 100644 --- a/task-hiffy/Cargo.toml +++ b/task-hiffy/Cargo.toml @@ -13,6 +13,8 @@ ringbuf = {path = "../ringbuf" } drv-i2c-api = {path = "../drv/i2c-api"} drv-spi-api = {path = "../drv/spi-api"} drv-stm32h7-gpio-api = {path = "../drv/stm32h7-gpio-api", optional = true} +drv-stm32h7-i2c = {path = "../drv/stm32h7-i2c" } +drv-stm32h7-rcc-api = {path = "../drv/stm32h7-rcc-api"} drv-lpc55-gpio-api = {path = "../drv/lpc55-gpio-api", optional = true} drv-gimlet-hf-api = {path = "../drv/gimlet-hf-api", optional = true} cortex-m = { version = "0.7", features = ["inline-asm"] } @@ -26,6 +28,9 @@ byteorder = { version = "1.3.4", default-features = false } [build-dependencies] build-util = {path = "../build-util"} +build-i2c = {path = "../build-i2c"} +anyhow = "1.0.31" +cfg-if = "0.1.10" [features] default = ["standalone"] diff --git a/task-hiffy/src/common.rs b/task-hiffy/src/common.rs index 1049a27c3..ec7318a2f 100644 --- a/task-hiffy/src/common.rs +++ b/task-hiffy/src/common.rs @@ -1,6 +1,7 @@ +use hif::{Failure, Fault}; + cfg_if::cfg_if! { if #[cfg(feature = "spi")] { - use hif::{Fault, Failure}; use userlib::{sys_refresh_task_id, Generation, TaskId, NUM_TASKS}; } } @@ -17,6 +18,33 @@ where e.map_err(|e| hif::Failure::FunctionError(e.into())) } +/// +/// Function to sleep(), which takes a single parameter: the number of +/// milliseconds. This is expected to be short: if sleeping for any +/// serious length of time, it should be done on the initiator side, not +/// on the Hubris side. (The purpose of this function is to allow for +/// device-mandated sleeps to in turn for allow for bulk device operations.) +/// +pub(crate) fn sleep( + stack: &[Option], + _data: &[u8], + _rval: &mut [u8], +) -> Result { + if stack.len() < 1 { + return Err(Failure::Fault(Fault::MissingParameters)); + } + + let fp = stack.len() - 1; + let ms = match stack[fp] { + Some(ms) if ms > 0 && ms <= 100 => Ok(ms), + _ => Err(Failure::Fault(Fault::BadParameter(0))), + }?; + + userlib::hl::sleep_for(ms.into()); + + Ok(0) +} + #[cfg(feature = "spi")] fn spi_args(stack: &[Option]) -> Result<(TaskId, usize), Failure> { if stack.len() < 2 { @@ -221,6 +249,56 @@ pub(crate) fn qspi_read( Ok(len) } +#[cfg(feature = "qspi")] +pub(crate) fn qspi_verify( + stack: &[Option], + data: &[u8], + rval: &mut [u8], +) -> Result { + use drv_gimlet_hf_api as hf; + + if stack.len() < 3 { + return Err(Failure::Fault(Fault::MissingParameters)); + } + let frame = &stack[stack.len() - 3..]; + let addr = frame[0].ok_or(Failure::Fault(Fault::MissingParameters))?; + let offset = + frame[1].ok_or(Failure::Fault(Fault::MissingParameters))? as usize; + let len = + frame[2].ok_or(Failure::Fault(Fault::MissingParameters))? as usize; + + if offset + len > data.len() { + return Err(Failure::Fault(Fault::AccessOutOfBounds)); + } + + if len > rval.len() { + return Err(Failure::Fault(Fault::AccessOutOfBounds)); + } + + let data = &data[offset..offset + len]; + let out = &mut rval[..len]; + + let server = hf::HostFlash::from(HF.get_task_id()); + func_err(server.read(addr, out))?; + + let mut differ = false; + + for i in 0..len { + if data[i] != out[i] { + differ = true; + break; + } + } + + if differ { + rval[0] = 1; + } else { + rval[0] = 0; + } + + Ok(1) +} + #[cfg(feature = "qspi")] pub(crate) fn qspi_sector_erase( stack: &[Option], diff --git a/task-hiffy/src/generic.rs b/task-hiffy/src/generic.rs index 247e8f7ae..b32fc94c7 100644 --- a/task-hiffy/src/generic.rs +++ b/task-hiffy/src/generic.rs @@ -1,11 +1,13 @@ use hif::Function; -pub enum Functions {} +pub enum Functions { + Sleep(u16, u32), +} #[no_mangle] static HIFFY_FUNCTIONS: Option<&Functions> = None; -pub(crate) static HIFFY_FUNCS: &[Function] = &[]; +pub(crate) static HIFFY_FUNCS: &[Function] = &[crate::common::sleep]; pub(crate) fn trace_execute(_offset: usize, _op: hif::Op) {} diff --git a/task-hiffy/src/lpc55.rs b/task-hiffy/src/lpc55.rs index d5d466f1d..7e3be41fd 100644 --- a/task-hiffy/src/lpc55.rs +++ b/task-hiffy/src/lpc55.rs @@ -23,6 +23,7 @@ ringbuf!(Trace, 64, Trace::None); * The ordering here MUST match the ordering in the function table below! */ pub enum Functions { + Sleep(u16, u32), #[cfg(feature = "gpio")] GpioInput(drv_lpc55_gpio_api::Pin, drv_lpc55_gpio_api::GpioError), #[cfg(feature = "gpio")] @@ -250,6 +251,7 @@ fn gpio_reset( } pub(crate) static HIFFY_FUNCS: &[Function] = &[ + crate::common::sleep, #[cfg(feature = "gpio")] gpio_input, #[cfg(feature = "gpio")] diff --git a/task-hiffy/src/main.rs b/task-hiffy/src/main.rs index 0fd2a9d1a..a6f451bee 100644 --- a/task-hiffy/src/main.rs +++ b/task-hiffy/src/main.rs @@ -31,7 +31,11 @@ cfg_if::cfg_if! { } cfg_if::cfg_if! { - if #[cfg(any(target_board = "gimlet-1", target_board = "gimletlet-2"))] { + if #[cfg(any( + target_board = "gimlet-1", + target_board = "gimletlet-2", + target_board = "nucleo-h743zi2" + ))] { const HIFFY_DATA_SIZE: usize = 20_480; } else { const HIFFY_DATA_SIZE: usize = 2_048; diff --git a/task-hiffy/src/stm32h7.rs b/task-hiffy/src/stm32h7.rs index 040c256fd..da8a3fba6 100644 --- a/task-hiffy/src/stm32h7.rs +++ b/task-hiffy/src/stm32h7.rs @@ -6,7 +6,9 @@ use ringbuf::*; use userlib::*; #[cfg(feature = "i2c")] -use drv_i2c_api::{Controller, I2cDevice, Mux, Port, ResponseCode, Segment}; +use drv_i2c_api::{ + Controller, I2cDevice, Mux, PortIndex, ResponseCode, Segment, +}; #[cfg(feature = "i2c")] task_slot!(I2C, i2c_driver); @@ -42,14 +44,20 @@ pub struct Buffer(u8); // is passed to execute. // pub enum Functions { + Sleep(u16, u32), #[cfg(feature = "i2c")] I2cRead( - (Controller, Port, Mux, Segment, u8, u8, usize), + (Controller, PortIndex, Mux, Segment, u8, u8, usize), ResponseCode, ), #[cfg(feature = "i2c")] I2cWrite( - (Controller, Port, Mux, Segment, u8, u8, Buffer, usize), + (Controller, PortIndex, Mux, Segment, u8, u8, Buffer, usize), + ResponseCode, + ), + #[cfg(feature = "i2c")] + I2cBulkWrite( + (Controller, PortIndex, Mux, Segment, u8, u8, usize, usize), ResponseCode, ), #[cfg(feature = "gpio")] @@ -87,24 +95,34 @@ pub enum Functions { #[cfg(feature = "spi")] SpiWrite((Task, usize), drv_spi_api::SpiError), #[cfg(feature = "qspi")] - QspiReadId((), drv_spi_api::SpiError), + QspiReadId((), drv_gimlet_hf_api::HfError), #[cfg(feature = "qspi")] - QspiReadStatus((), drv_spi_api::SpiError), + QspiReadStatus((), drv_gimlet_hf_api::HfError), #[cfg(feature = "qspi")] - QspiBulkErase((), drv_spi_api::SpiError), + QspiBulkErase((), drv_gimlet_hf_api::HfError), #[cfg(feature = "qspi")] - QspiPageProgram((u32, usize, usize), drv_spi_api::SpiError), + QspiPageProgram((u32, usize, usize), drv_gimlet_hf_api::HfError), #[cfg(feature = "qspi")] - QspiRead((u32, usize), drv_spi_api::SpiError), + QspiRead((u32, usize), drv_gimlet_hf_api::HfError), #[cfg(feature = "qspi")] - QspiSectorErase(u32, drv_spi_api::SpiError), + QspiSectorErase(u32, drv_gimlet_hf_api::HfError), + #[cfg(feature = "qspi")] + QspiVerify((u32, usize, usize), drv_gimlet_hf_api::HfError), } #[cfg(feature = "i2c")] fn i2c_args( stack: &[Option], -) -> Result<(Controller, Port, Option<(Mux, Segment)>, u8, Option), Failure> -{ +) -> Result< + ( + Controller, + PortIndex, + Option<(Mux, Segment)>, + u8, + Option, + ), + Failure, +> { let controller = match stack[0] { Some(controller) => match Controller::from_u32(controller) { Some(controller) => controller, @@ -114,13 +132,22 @@ fn i2c_args( }; let port = match stack[1] { - Some(port) => match Port::from_u32(port) { - Some(port) => port, - None => { + Some(port) => { + if port > core::u8::MAX.into() { return Err(Failure::Fault(Fault::BadParameter(1))); } - }, - None => Port::Default, + + PortIndex(port as u8) + } + None => { + // + // While we once upon a time allowed HIF consumers to specify + // a default port, we now expect all HIF consumers to read the + // device configuration and correctly specify a port index: + // this is an error. + // + return Err(Failure::Fault(Fault::EmptyParameter(1))); + } }; let mux = match (stack[2], stack[3]) { @@ -264,6 +291,49 @@ fn i2c_write( } } +#[cfg(feature = "i2c")] +fn i2c_bulk_write( + stack: &[Option], + data: &[u8], + _rval: &mut [u8], +) -> Result { + // + // We need exactly 8 parameters: the normal i2c paramaters (controller, + // port, mux, segment, address, register) plus the offset and length. + // Note that the register must be None. + // + if stack.len() != 8 { + return Err(Failure::Fault(Fault::MissingParameters)); + } + + let offset = match stack[stack.len() - 2] { + Some(offset) if (offset as usize) < data.len() => Ok(offset as usize), + _ => Err(Failure::Fault(Fault::BadParameter(6))), + }?; + + let len = match stack[stack.len() - 1] { + Some(len) if len > 0 && offset + (len as usize) < data.len() => { + Ok(len as usize) + } + _ => Err(Failure::Fault(Fault::BadParameter(7))), + }?; + + let fp = stack.len() - 8; + let (controller, port, mux, addr, register) = i2c_args(&stack[fp..])?; + + if register.is_some() { + return Err(Failure::Fault(Fault::BadParameter(5))); + } + + let task = I2C.get_task_id(); + let device = I2cDevice::new(task, controller, port, mux, addr); + + match device.write(&data[offset..offset + len]) { + Ok(_) => Ok(0), + Err(err) => Err(Failure::FunctionError(err.into())), + } +} + #[cfg(feature = "gpio")] fn gpio_args( stack: &[Option], @@ -452,10 +522,13 @@ fn gpio_configure( } pub(crate) static HIFFY_FUNCS: &[Function] = &[ + crate::common::sleep, #[cfg(feature = "i2c")] i2c_read, #[cfg(feature = "i2c")] i2c_write, + #[cfg(feature = "i2c")] + i2c_bulk_write, #[cfg(feature = "gpio")] gpio_input, #[cfg(feature = "gpio")] @@ -482,6 +555,8 @@ pub(crate) static HIFFY_FUNCS: &[Function] = &[ crate::common::qspi_read, #[cfg(feature = "qspi")] crate::common::qspi_sector_erase, + #[cfg(feature = "qspi")] + crate::common::qspi_verify, ]; // diff --git a/task-power/Cargo.toml b/task-power/Cargo.toml index 924391bf1..d0be3ec76 100644 --- a/task-power/Cargo.toml +++ b/task-power/Cargo.toml @@ -19,6 +19,9 @@ drv-i2c-devices = { path = "../drv/i2c-devices" } [build-dependencies] build-util = {path = "../build-util"} +build-i2c = {path = "../build-i2c"} +anyhow = "1.0.31" +cfg-if = "0.1.10" [features] default = ["standalone"] diff --git a/task-power/build.rs b/task-power/build.rs index 941cd93f4..440386bdd 100644 --- a/task-power/build.rs +++ b/task-power/build.rs @@ -1,3 +1,16 @@ fn main() { build_util::expose_target_board(); + + let disposition = build_i2c::Disposition::Devices; + + #[cfg(feature = "standalone")] + let artifact = build_i2c::Artifact::Standalone; + + #[cfg(not(feature = "standalone"))] + let artifact = build_i2c::Artifact::Dist; + + if let Err(e) = build_i2c::codegen(disposition, artifact) { + println!("code generation failed: {}", e); + std::process::exit(1); + } } diff --git a/task-power/src/main.rs b/task-power/src/main.rs index fa53f1b46..bae8104b3 100644 --- a/task-power/src/main.rs +++ b/task-power/src/main.rs @@ -6,7 +6,6 @@ #![no_std] #![no_main] -use drv_i2c_api::*; use drv_i2c_devices::adm1272::*; use drv_i2c_devices::tps546b24a::*; use ringbuf::*; @@ -14,6 +13,7 @@ use userlib::units::*; use userlib::*; task_slot!(I2C, i2c_driver); +include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); #[derive(Copy, Clone, PartialEq)] enum Device { @@ -44,32 +44,20 @@ fn trace(dev: Device, cmd: Command) { #[export_name = "main"] fn main() -> ! { let task = I2C.get_task_id(); + use i2c_config::devices; cfg_if::cfg_if! { if #[cfg(target_board = "gemini-bu-1")] { - const ADM1272_ADDRESS: u8 = 0x10; - - let mut adm1272 = Adm1272::new(&I2cDevice::new( - task, - Controller::I2C4, - Port::F, - Some((Mux::M1, Segment::S3)), - ADM1272_ADDRESS - ), Ohms(0.001)); - - const TPS546B24A_ADDRESS: u8 = 0x24; - - let mut tps546 = Tps546b24a::new(&I2cDevice::new( - task, - Controller::I2C4, - Port::F, - Some((Mux::M1, Segment::S4)), - TPS546B24A_ADDRESS - )); + let mut adm1272 = Adm1272::new( + &devices::adm1272(task)[0], + Ohms(0.001) + ); + + let mut tps546 = Tps546b24a::new(&devices::tps546b24a(task)[0]); } else { cfg_if::cfg_if! { if #[cfg(feature = "standalone")] { - let device = I2cDevice::mock(task); + let device = &devices::mock(task); let mut adm1272 = Adm1272::new(&device, Ohms(0.0)); let mut tps546 = Tps546b24a::new(&device); } else { diff --git a/task-spd/Cargo.toml b/task-spd/Cargo.toml index 7c0ccce98..df212cb43 100644 --- a/task-spd/Cargo.toml +++ b/task-spd/Cargo.toml @@ -20,6 +20,9 @@ stm32h7 = { version = "0.13.0" } [build-dependencies] build-util = {path = "../build-util"} +build-i2c = {path = "../build-i2c"} +anyhow = "1.0.31" +cfg-if = "0.1.10" [features] default = ["standalone", "h743"] diff --git a/task-spd/build.rs b/task-spd/build.rs index 941cd93f4..99ab5af4d 100644 --- a/task-spd/build.rs +++ b/task-spd/build.rs @@ -1,3 +1,16 @@ fn main() { build_util::expose_target_board(); + + let disposition = build_i2c::Disposition::Target; + + #[cfg(feature = "standalone")] + let artifact = build_i2c::Artifact::Standalone; + + #[cfg(not(feature = "standalone"))] + let artifact = build_i2c::Artifact::Dist; + + if let Err(e) = build_i2c::codegen(disposition, artifact) { + println!("code generation failed: {}", e); + std::process::exit(1); + } } diff --git a/task-spd/src/main.rs b/task-spd/src/main.rs index 8ffc3907a..998b5dc90 100644 --- a/task-spd/src/main.rs +++ b/task-spd/src/main.rs @@ -17,19 +17,12 @@ #![no_std] #![no_main] -#[cfg(feature = "h7b3")] -use stm32h7::stm32h7b3 as device; - -#[cfg(feature = "h743")] -use stm32h7::stm32h743 as device; - use core::cell::Cell; use core::cell::RefCell; use drv_i2c_api::*; -use drv_i2c_api::{Controller, Port}; use drv_stm32h7_gpio_api::*; use drv_stm32h7_i2c::*; -use drv_stm32h7_rcc_api::{Peripheral, Rcc}; +use drv_stm32h7_rcc_api::Rcc; use ringbuf::*; use userlib::*; @@ -39,19 +32,21 @@ task_slot!(I2C, i2c_driver); mod ltc4306; -fn configure_pin(pin: &I2cPin) { +fn configure_pins(pins: &[I2cPin]) { let gpio_driver = GPIO.get_task_id(); let gpio_driver = Gpio::from(gpio_driver); - gpio_driver - .configure_alternate( - pin.gpio_pins, - OutputType::OpenDrain, - Speed::High, - Pull::None, - pin.function, - ) - .unwrap(); + for pin in pins { + gpio_driver + .configure_alternate( + pin.gpio_pins, + OutputType::OpenDrain, + Speed::High, + Pull::None, + pin.function, + ) + .unwrap(); + } } // @@ -60,7 +55,7 @@ fn configure_pin(pin: &I2cPin) { static mut SPD_DATA: [u8; 8192] = [0; 8192]; const LTC4306_ADDRESS: u8 = 0b1001_010; -type Bank = (Controller, drv_i2c_api::Port, Option<(Mux, Segment)>); +type Bank = (Controller, drv_i2c_api::PortIndex, Option<(Mux, Segment)>); #[derive(Copy, Clone, PartialEq)] enum Trace { @@ -81,66 +76,28 @@ enum Trace { ringbuf!(Trace, 16, Trace::None); +include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); + #[export_name = "main"] fn main() -> ! { + let controller = &i2c_config::controllers()[0]; + let pins = i2c_config::pins(); + use i2c_config::ports::*; + cfg_if::cfg_if! { if #[cfg(target_board = "gemini-bu-1")] { - let controller = I2cController { - controller: Controller::I2C2, - peripheral: Peripheral::I2c2, - registers: unsafe { &*device::I2C2::ptr() }, - notification: (1 << (2 - 1)), - }; - - let pin = I2cPin { - controller: Controller::I2C2, - port: Port::F, - gpio_pins: drv_stm32h7_gpio_api::Port::F.pin(0).and_pin(1), - function: Alternate::AF4, - }; - // These should be whatever ports the dimmlets are plugged into const BANKS: [Bank; 2] = [ - (Controller::I2C4, Port::D, None), - (Controller::I2C4, Port::F, Some((Mux::M1, Segment::S4))), + (Controller::I2C4, i2c4_d(), None), + (Controller::I2C4, i2c4_f(), Some((Mux::M1, Segment::S4))), ]; } else if #[cfg(target_board = "gimletlet-2")] { - let controller = I2cController { - controller: Controller::I2C2, - peripheral: Peripheral::I2c2, - registers: unsafe { &*device::I2C2::ptr() }, - notification: (1 << (2 - 1)), - }; - - let pin = I2cPin { - controller: Controller::I2C2, - port: Port::F, - gpio_pins: drv_stm32h7_gpio_api::Port::F.pin(0).and_pin(1), - function: Alternate::AF4, - }; - + // These should be whatever ports the dimmlets are plugged into const BANKS: [Bank; 2] = [ - (Controller::I2C3, Port::A, None), - (Controller::I2C4, Port::F, None), + (Controller::I2C3, i2c3_a(), None), + (Controller::I2C4, i2c4_f(), None), ]; } else if #[cfg(target_board = "gimlet-1")] { - // SP3 Proxy controller - let controller = I2cController { - controller: Controller::I2C1, - peripheral: Peripheral::I2c1, - registers: unsafe { &*device::I2C1::ptr() }, - notification: (1 << (1 - 1)), - }; - - // SMBUS_SPD_PROXY_SP3_TO_SP_SMCLK - // SMBUS_SPD_PROXY_SP3_TO_SP_SMDAT - let pin = I2cPin { - controller: Controller::I2C1, - port: Port::B, - gpio_pins: drv_stm32h7_gpio_api::Port::B.pin(0).and_pin(1), - function: Alternate::AF4, - }; - // // On Gimlet, we have two banks of up to 8 DIMMs apiece: // @@ -152,31 +109,15 @@ fn main() -> ! { // to the correct DIMM from the SoC's perspective. // const BANKS: [Bank; 2] = [ - (Controller::I2C3, Port::H, None), - (Controller::I2C4, Port::F, None), + (Controller::I2C3, i2c3_h(), None), + (Controller::I2C4, i2c4_f(), None), + ]; + } else if #[cfg(feature = "standalone")] { + const BANKS: [Bank; 1] = [ + (Controller::Mock, i2c_mock(), None), ]; } else { - cfg_if::cfg_if! { - if #[cfg(feature = "standalone")] { - let controller = I2cController { - controller: Controller::I2C1, - peripheral: Peripheral::I2c1, - registers: unsafe { &*device::I2C1::ptr() }, - notification: (1 << (1 - 1)), - }; - let pin = I2cPin { - controller: Controller::I2C2, - port: Port::F, - gpio_pins: drv_stm32h7_gpio_api::Port::F.pin(0).and_pin(1), - function: Alternate::AF4, - }; - const BANKS: [Bank; 1] = [ - (Controller::I2C4, Port::D, None), - ]; - } else { - compile_error!("I2C target unsupported for this board"); - } - } + compile_error!("I2C target unsupported for this board"); } } @@ -295,7 +236,7 @@ fn main() -> ! { controller.enable(&rcc_driver); // Configure our pins - configure_pin(&pin); + configure_pins(&pins); ringbuf_entry!(Trace::Ready); diff --git a/task-thermal/Cargo.toml b/task-thermal/Cargo.toml index f28c7584f..f2f7cba97 100644 --- a/task-thermal/Cargo.toml +++ b/task-thermal/Cargo.toml @@ -21,6 +21,9 @@ drv-onewire-devices = {path = "../drv/onewire-devices"} [build-dependencies] build-util = {path = "../build-util"} +build-i2c = {path = "../build-i2c"} +anyhow = "1.0.31" +cfg-if = "0.1.10" [features] default = ["standalone"] diff --git a/task-thermal/build.rs b/task-thermal/build.rs index 941cd93f4..440386bdd 100644 --- a/task-thermal/build.rs +++ b/task-thermal/build.rs @@ -1,3 +1,16 @@ fn main() { build_util::expose_target_board(); + + let disposition = build_i2c::Disposition::Devices; + + #[cfg(feature = "standalone")] + let artifact = build_i2c::Artifact::Standalone; + + #[cfg(not(feature = "standalone"))] + let artifact = build_i2c::Artifact::Dist; + + if let Err(e) = build_i2c::codegen(disposition, artifact) { + println!("code generation failed: {}", e); + std::process::exit(1); + } } diff --git a/task-thermal/src/main.rs b/task-thermal/src/main.rs index 99da00bae..96298ab45 100644 --- a/task-thermal/src/main.rs +++ b/task-thermal/src/main.rs @@ -8,7 +8,6 @@ #![no_std] #![no_main] -use drv_i2c_api::*; use drv_i2c_devices::max31790::*; use drv_i2c_devices::tmp116::*; use drv_i2c_devices::TempSensor; @@ -16,6 +15,7 @@ use userlib::units::*; use userlib::*; task_slot!(I2C, i2c_driver); +include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); fn convert_fahrenheit(temp: Celsius) -> f32 { temp.0 * (9.0 / 5.0) + 32.0 @@ -71,87 +71,28 @@ fn temp_read + core::fmt::Display>( #[export_name = "main"] fn main() -> ! { let task = I2C.get_task_id(); + use i2c_config::devices; cfg_if::cfg_if! { if #[cfg(target_board = "gemini-bu-1")] { - const MAX31790_ADDRESS: u8 = 0x20; - - let fctrl = Max31790::new(&I2cDevice::new( - task, - Controller::I2C1, - Port::Default, - None, - MAX31790_ADDRESS, - )); - + let fctrl = Max31790::new(&devices::max31790(task)[0]); let tmp116: [Tmp116; 0] = []; } else if #[cfg(target_board = "gimlet-1")] { - // Two sets of TMP117 sensors, Front and Rear - // These all have the same address but are on different - // controllers/ports - - const TMP116_ADDRESS: u8 = 0x48; - - // Front sensors (U.2) - let tmp116 = [ Tmp116::new(&I2cDevice::new( - task, - Controller::I2C2, - Port::F, - None, - TMP116_ADDRESS - )), Tmp116::new(&I2cDevice::new( - task, - Controller::I2C2, - Port::F, - None, - TMP116_ADDRESS + 1 - )), - Tmp116::new(&I2cDevice::new( - task, - Controller::I2C2, - Port::F, - None, - TMP116_ADDRESS + 2 - )), - - // Rear sensors (fans) - Tmp116::new(&I2cDevice::new( - task, - Controller::I2C4, - Port::F, - None, - TMP116_ADDRESS - )), Tmp116::new(&I2cDevice::new( - task, - Controller::I2C4, - Port::F, - None, - TMP116_ADDRESS + 1 - )), - Tmp116::new(&I2cDevice::new( - task, - Controller::I2C4, - Port::F, - None, - TMP116_ADDRESS + 2 - )), + let tmp116 = [ + Tmp116::new(&devices::tmp117_front_zone1(task)), + Tmp116::new(&devices::tmp117_front_zone2(task)), + Tmp116::new(&devices::tmp117_front_zone3(task)), + Tmp116::new(&devices::tmp117_rear_zone1(task)), + Tmp116::new(&devices::tmp117_rear_zone2(task)), + Tmp116::new(&devices::tmp117_rear_zone3(task)), ]; - const MAX31790_ADDRESS: u8 = 0x20; - - let fctrl = Max31790::new(&I2cDevice::new( - task, - Controller::I2C4, - Port::F, - None, - MAX31790_ADDRESS, - )); + let fctrl = Max31790::new(&devices::max31790(task)[0]); } else { cfg_if::cfg_if! { if #[cfg(feature = "standalone")] { - let device = I2cDevice::mock(task); - let fctrl = Max31790::new(&device); - let tmp116 = [ Tmp116::new(&device) ]; + let fctrl = Max31790::new(&devices::mock(task)); + let tmp116: [Tmp116; 0] = []; } else { compile_error!("unknown board"); } diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs index e9e59d1c8..4d826e69b 100644 --- a/xtask/src/dist.rs +++ b/xtask/src/dist.rs @@ -195,6 +195,8 @@ pub fn package(verbose: bool, cfg: &Path) -> Result<()> { &task_names, &None, &shared_syms, + &None, + &toml.config, )?; // Need a bootloader binary for signing @@ -269,6 +271,8 @@ pub fn package(verbose: bool, cfg: &Path) -> Result<()> { &task_names, &toml.secure, &shared_syms, + &task_toml.config, + &toml.config, ) .context(format!("failed to build {}", name))?; @@ -327,6 +331,8 @@ pub fn package(verbose: bool, cfg: &Path) -> Result<()> { "", &toml.secure, &None, + &None, + &toml.config, )?; let (kentry, _) = load_elf(&out.join("kernel"), &mut all_output_sections)?; @@ -836,6 +842,8 @@ fn build( task_names: &str, secure: &Option, shared_syms: &Option<&[String]>, + config: &Option, + app_config: &Option, ) -> Result<()> { println!("building path {}", path.display()); @@ -905,6 +913,21 @@ fn build( } } + // + // We allow for task- and app-specific configuration to be passed + // via environment variables to build.rs scripts that may choose to + // incorporate confiuration into compilation. + // + if let Some(config) = config { + let env = toml::to_string(&config).unwrap(); + cmd.env("HUBRIS_TASK_CONFIG", env); + } + + if let Some(app_config) = app_config { + let env = toml::to_string(&app_config).unwrap(); + cmd.env("HUBRIS_APP_CONFIG", env); + } + let status = cmd .status() .context(format!("failed to run rustc ({:?})", cmd))?; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 5ff85dd35..fbd88d4f2 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -130,6 +130,8 @@ struct Config { #[serde(default)] extratext: IndexMap, supervisor: Option, + #[serde(default)] + config: Option, } #[derive(Clone, Debug, Deserialize)] @@ -209,6 +211,8 @@ struct Task { sections: IndexMap, #[serde(default)] task_slots: IndexMap, + #[serde(default)] + config: Option, } #[derive(Clone, Debug, Deserialize)]