diff --git a/Cargo.lock b/Cargo.lock index 2c00ec02fe..ce4c3ceb25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -518,6 +518,21 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76fbd10dce159c002b9c688ae8ab7cd531151e185e0ad360f4bfea3b0eede3a8" +[[package]] +name = "demo-pi-pico" +version = "0.1.0" +dependencies = [ + "build-util", + "cfg-if 0.1.10", + "cortex-m", + "cortex-m-rt", + "kern", + "panic-halt", + "panic-semihosting", + "rp2040-boot2", + "rp2040-pac", +] + [[package]] name = "demo-stm32f4-discovery" version = "0.1.0" @@ -937,6 +952,33 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "drv-rp2040-sys" +version = "0.1.0" +dependencies = [ + "cfg-if 1.0.0", + "drv-rp2040-sys-api", + "idol", + "idol-runtime", + "num-traits", + "rp2040-pac", + "userlib", + "zerocopy", +] + +[[package]] +name = "drv-rp2040-sys-api" +version = "0.1.0" +dependencies = [ + "bitflags", + "byteorder", + "cfg-if 1.0.0", + "idol", + "num-traits", + "userlib", + "zerocopy", +] + [[package]] name = "drv-sidecar-seq-api" version = "0.1.0" @@ -1235,6 +1277,7 @@ dependencies = [ "build-util", "cfg-if 0.1.10", "drv-lpc55-gpio-api", + "drv-rp2040-sys-api", "drv-stm32xx-sys-api", "drv-user-leds-api", "idol", @@ -2379,6 +2422,26 @@ dependencies = [ "serde", ] +[[package]] +name = "rp2040-boot2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b480fe63133f0d639f82ce5a027fee7cac7ac92f67ef1896ee036a6f9737128" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rp2040-pac" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a6106d5db01c7171a39c1f7696780912db9b42fe7ac722db60069c8904ea7c" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + [[package]] name = "rsa" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index 85cbf745d8..3d9bfaa74b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ members = [ "app/demo-stm32f4-discovery", "app/demo-stm32g0-nucleo", "app/demo-stm32h7-nucleo", + "app/demo-pi-pico", "app/gemini-bu", "app/gemini-bu-rot", "app/gimlet", @@ -93,6 +94,9 @@ members = [ "drv/lpc55-rng", "drv/lpc55-swd", + "drv/rp2040-sys-api", + "drv/rp2040-sys", + "drv/user-leds", "drv/user-leds-api", "drv/ice40-spi-program", diff --git a/app/demo-pi-pico/.gitignore b/app/demo-pi-pico/.gitignore new file mode 100644 index 0000000000..75430e99f6 --- /dev/null +++ b/app/demo-pi-pico/.gitignore @@ -0,0 +1,12 @@ +**/*.rs.bk +.#* +.gdb_history +target/ + +# editor files +.vscode/* +!.vscode/*.md +!.vscode/*.svd +!.vscode/launch.json +!.vscode/tasks.json +!.vscode/extensions.json diff --git a/app/demo-pi-pico/Cargo.toml b/app/demo-pi-pico/Cargo.toml new file mode 100644 index 0000000000..a38b5b3daf --- /dev/null +++ b/app/demo-pi-pico/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2018" +readme = "README.md" +name = "demo-pi-pico" +version = "0.1.0" + +[features] +semihosting = ["panic-semihosting", "klog-semihosting"] +klog-semihosting = ["kern/klog-semihosting"] + +[dependencies] +cortex-m = { version = "0.7", features = ["inline-asm"] } +cortex-m-rt = "0.6.12" +panic-halt = { version = "0.2.0", optional = true } +panic-semihosting = { version = "0.5.3", optional = true } +cfg-if = "0.1.10" +rp2040-pac = {version = "0.3", features = ["rt"]} +rp2040-boot2 = "0.2" + +[dependencies.kern] +path = "../../sys/kern" +default-features = false + +[build-dependencies] +build-util = {path = "../../build/util"} + +# this lets you use `cargo fix`! +[[bin]] +name = "demo-pi-pico" +test = false +bench = false diff --git a/app/demo-pi-pico/README.md b/app/demo-pi-pico/README.md new file mode 100644 index 0000000000..a5033c835f --- /dev/null +++ b/app/demo-pi-pico/README.md @@ -0,0 +1,52 @@ +# Rasperry Pi Pico demo application + +This will blink an LED, and not a whole lot else, on a Raspberry Pi Pico board +(based on the RP2040). + +Currently, our tools don't know how to flash this board. So, to use this, you'll +have to jump through some hoops. + +You will need: + +- [uf2l](https://github.com/cbiffle/uf2l) - you can install it, or run it from + the build directory, but the instructions below will just refer to it as + `uf2l`. +- [rp2040-rustboot](https://github.com/cbiffle/rp2040-rustboot) - run the + `build-all.sh` to produce ELF binaries. The instructions below will refer to + its location as `$RUSTBOOT`. + +To build and prepare the image: + +``` +cargo xtask dist app/demo-pi-pico/app.toml +uf2l pack -e 4096 \ + $RUSTBOOT/elf/rustboot-w25q080 \ + target/demo-pi-pico/dist/combined.elf \ + hubris-pico.uf2 +``` + +Then, hold BOOTSEL while plugging in your Pi Pico. It should show up as a USB +drive. Copy the `hubris-pico.uf2` file onto it. It should reboot and begin +blinking a light at 1 Hz. + +## Debugging + +General RP2040 support in OpenOCD hasn't been upstreamed, but amusingly there +_is_ upstream support for the [pico-debug] same-chip debugger. This will run on +core 1 while Hubris runs on core 0. + +To use this: + +1. Flash Hubris as described above. +2. Ensure that you've got a fairly recent OpenOCD git build. +3. Download the GIMMECACHE version of the debug image. +4. Hold BOOTSEL and reboot your RP2040 board into the bootloader. +5. Copy the GIMMECACHE UF2 image onto the board. It should reboot. It does not + appear to correctly start the Flash image by default, but that's easy to fix: +6. From this directory, run: `openocd -f openocd-pico-debug.cfg -c init -c reset + -c exit` + +The clock configuration under pico-debug is slightly different from the reset +configuration; there is code in `src/main.rs` to adjust for this. It is a hack. + +[pico-debug]: https://github.com/majbthrd/pico-debug diff --git a/app/demo-pi-pico/app.toml b/app/demo-pi-pico/app.toml new file mode 100644 index 0000000000..601ab99d50 --- /dev/null +++ b/app/demo-pi-pico/app.toml @@ -0,0 +1,89 @@ +name = "demo-pi-pico" +target = "thumbv6m-none-eabi" +chip = "../../chips/rp2040.toml" +board = "pi-pico" +stacksize = 944 + +[kernel] +path = "." +name = "demo-pi-pico" +requires = {flash = 12000, ram = 1696} +features = ["panic-halt"] +stacksize = 640 + +[supervisor] +notification = 1 + +[outputs.flash] +address = 0x10000100 # just past the bootloader +size = 0x1fff00 # 2MiB - 256 bytes +read = true +execute = true + +[outputs.ram] +address = 0x20000000 +size = 270336 +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 = 4096, ram = 512} +start = true +features = ["log-null"] +stacksize = 352 + +[tasks.sys] +path = "../../drv/rp2040-sys" +name = "drv-rp2040-sys" +priority = 1 +requires = {flash = 1024, ram = 256} +uses = ["resets", "sio"] +start = true +stacksize = 256 + +[tasks.user_leds] +path = "../../drv/user-leds" +name = "drv-user-leds" +features = ["rp2040"] +priority = 2 +requires = {flash = 1024, ram = 256} +start = true +task-slots = ["sys"] +stacksize = 256 + +[tasks.pong] +path = "../../task/pong" +name = "task-pong" +priority = 3 +requires = {flash = 1024, ram = 256} +start = true +task-slots = ["user_leds"] +stacksize = 256 + +[tasks.ping] +path = "../../task/ping" +name = "task-ping" +priority = 4 +requires = {flash = 4096, ram = 512} +stacksize = 256 +start = true +task-slots = [{peer = "pong"}] + +[tasks.hiffy] +path = "../../task/hiffy" +name = "task-hiffy" +priority = 3 +requires = {flash = 8192, ram = 8192 } +start = true + +[tasks.idle] +path = "../../task/idle" +name = "task-idle" +priority = 5 +requires = {flash = 128, ram = 64} +stacksize = 64 +start = true diff --git a/app/demo-pi-pico/build.rs b/app/demo-pi-pico/build.rs new file mode 100644 index 0000000000..2800895934 --- /dev/null +++ b/app/demo-pi-pico/build.rs @@ -0,0 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +fn main() { + build_util::expose_target_board(); +} diff --git a/app/demo-pi-pico/openocd-pico-debug.cfg b/app/demo-pi-pico/openocd-pico-debug.cfg new file mode 100644 index 0000000000..81bda1d0cf --- /dev/null +++ b/app/demo-pi-pico/openocd-pico-debug.cfg @@ -0,0 +1,6 @@ +source [find interface/cmsis-dap.cfg] +source [find target/rp2040-core0.cfg] + +transport select swd + +adapter_khz 500 diff --git a/app/demo-pi-pico/src/main.rs b/app/demo-pi-pico/src/main.rs new file mode 100644 index 0000000000..6233a0c610 --- /dev/null +++ b/app/demo-pi-pico/src/main.rs @@ -0,0 +1,57 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#![no_std] +#![no_main] + +#[cfg(not(any( + feature = "panic-itm", + feature = "panic-semihosting", + feature = "panic-halt" +)))] +compile_error!("Must have one of panic-{itm,semihosting,halt} enabled"); + +// Panic behavior controlled by Cargo features: +#[cfg(feature = "panic-halt")] +extern crate panic_halt; +#[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 + +// We have to do this if we don't otherwise use it to ensure its vector table +// gets linked in. +use rp2040_pac as _; + +#[link_section = ".boot_loader"] +#[used] +pub static BOOT_LOADER: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +use cortex_m_rt::entry; + +#[entry] +fn main() -> ! { + let p = unsafe { rp2040_pac::Peripherals::steal() }; + + p.RESETS.reset.modify(|_, w| w.io_bank0().clear_bit()); + while !p.RESETS.reset_done.read().io_bank0().bit() {} + + p.SIO.gpio_oe_set.write(|w| unsafe { w.bits(1 << 25) }); + p.SIO.gpio_out_set.write(|w| unsafe { w.bits(1 << 25) }); + + p.IO_BANK0.gpio[25].gpio_ctrl.write(|w| w.funcsel().sio()); + + let cycles_per_ms = if p.CLOCKS.clk_sys_ctrl.read().src().is_clk_ref() { + // This is the reset state, so we'll assume we launched directly from + // flash running on the ROSC. + 6_000 // ish + } else { + // This is _not_ the reset state, so we'll assume that the pico-debug + // resident debugger has reconfigured things to run off the 48 MHz USB + // clock. + 48_000 + }; + + unsafe { kern::startup::start_kernel(cycles_per_ms) } +} diff --git a/build/xtask/src/dist.rs b/build/xtask/src/dist.rs index 1a56b546a2..e17775c655 100644 --- a/build/xtask/src/dist.rs +++ b/build/xtask/src/dist.rs @@ -639,10 +639,10 @@ Did you mean to run `cargo xtask dist`?" // any external configuration files, serialize it, and add it to the // archive. // - let mut config = crate::flash::config(&toml.board.as_str())?; - config.flatten()?; - - archive.text(img_dir.join("flash.ron"), ron::to_string(&config)?)?; + if let Some(mut config) = crate::flash::config(&toml.board.as_str())? { + config.flatten()?; + archive.text(img_dir.join("flash.ron"), ron::to_string(&config)?)?; + } archive.finish()?; diff --git a/build/xtask/src/flash.rs b/build/xtask/src/flash.rs index 378434b9a1..1c887f7ef8 100644 --- a/build/xtask/src/flash.rs +++ b/build/xtask/src/flash.rs @@ -135,7 +135,7 @@ impl FlashConfig { } } -pub fn config(board: &str) -> anyhow::Result { +pub fn config(board: &str) -> anyhow::Result> { match board { "lpcxpresso55s69" | "gemini-bu-rot-1" | "gimlet-rot-1" => { let chip = if board == "lpcxpresso55s69" { @@ -160,7 +160,7 @@ pub fn config(board: &str) -> anyhow::Result { .arg("hex") .payload(); - Ok(flash) + Ok(Some(flash)) } "stm32f3-discovery" | "stm32f4-discovery" | "nucleo-h743zi2" | "nucleo-h753zi" | "stm32h7b3i-dk" | "gemini-bu-1" | "gimletlet-1" @@ -196,10 +196,11 @@ pub fn config(board: &str) -> anyhow::Result { .arg("-c") .arg("exit"); - Ok(flash) + Ok(Some(flash)) } _ => { - anyhow::bail!("unrecognized board {}", board); + eprintln!("Warning: unrecognized board, won't know how to flash."); + Ok(None) } } } diff --git a/chips/rp2040.toml b/chips/rp2040.toml new file mode 100644 index 0000000000..3057410b5b --- /dev/null +++ b/chips/rp2040.toml @@ -0,0 +1,7 @@ +[resets] +address = 0x4000c000 +size = 32 + +[sio] +address = 0xd0000000 +size = 512 diff --git a/drv/rp2040-sys-api/Cargo.toml b/drv/rp2040-sys-api/Cargo.toml new file mode 100644 index 0000000000..3169ac70f3 --- /dev/null +++ b/drv/rp2040-sys-api/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "drv-rp2040-sys-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +userlib = {path = "../../sys/userlib"} +zerocopy = "0.6.1" +byteorder = {version = "1.3", default-features = false} +num-traits = {version = "0.2", default-features = false} +cfg-if = "1" +bitflags = "1.3.2" + +[build-dependencies] +idol = {git = "https://github.com/oxidecomputer/idolatry.git"} + +# This section is here to discourage RLS/rust-analyzer from doing test builds, +# since test builds don't work for cross compilation. +[lib] +test = false +bench = false diff --git a/drv/rp2040-sys-api/build.rs b/drv/rp2040-sys-api/build.rs new file mode 100644 index 0000000000..d91e679c1c --- /dev/null +++ b/drv/rp2040-sys-api/build.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +fn main() -> Result<(), Box> { + idol::client::build_client_stub( + "../../idl/rp2040-sys.idol", + "client_stub.rs", + )?; + Ok(()) +} diff --git a/drv/rp2040-sys-api/src/g0.rs b/drv/rp2040-sys-api/src/g0.rs new file mode 100644 index 0000000000..25e348a291 --- /dev/null +++ b/drv/rp2040-sys-api/src/g0.rs @@ -0,0 +1,93 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! STM32G0 specifics + +use crate::periph; +use userlib::FromPrimitive; + +/// Peripherals appear in "groups." All peripherals in a group are controlled +/// from the same subset of registers in the RCC. +/// +/// The reference manual lacks a term for this, so we made this one up. It would +/// be tempting to refer to these as "buses," but in practice there are almost +/// always more groups than there are buses, particularly on M0. +/// +/// This is `pub` mostly for use inside driver-servers. +#[derive(Copy, Clone, Debug, FromPrimitive)] +#[repr(u8)] +pub enum Group { + Iop = 0, + Ahb, + Apb1, + Apb2, +} + +/// Peripheral numbering. +/// +/// Peripheral bit numbers per the STM32G0 documentation, starting at section: +/// +/// STM32G0 PART MANUAL SECTION +/// G0x0 RM0454 5.4.8 (RCC_IOPRSTR) +/// G0x1 RM0444 5.4.9 (RCC_IOPRSTR) +/// +/// These are in the order that they appear in the documentation. This is +/// the union of all STM32G0 peripherals; not all peripherals will exist on +/// all variants! +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[repr(u32)] +pub enum Peripheral { + GpioF = periph(Group::Iop, 5), + GpioE = periph(Group::Iop, 4), + GpioD = periph(Group::Iop, 3), + GpioC = periph(Group::Iop, 2), + GpioB = periph(Group::Iop, 1), + GpioA = periph(Group::Iop, 0), + + Rng = periph(Group::Ahb, 18), // G0x1 only + Aes = periph(Group::Ahb, 16), // G0x1 only + Crc = periph(Group::Ahb, 12), + Flash = periph(Group::Ahb, 8), + Dma2 = periph(Group::Ahb, 1), + Dma1 = periph(Group::Ahb, 0), + + LpTim1 = periph(Group::Apb1, 31), // G0x1 only + LpTim2 = periph(Group::Apb1, 30), // G0x1 only + Dac1 = periph(Group::Apb1, 29), // G0x1 only + Pwr = periph(Group::Apb1, 28), + Dbg = periph(Group::Apb1, 27), + Ucpd2 = periph(Group::Apb1, 26), // G0x1 only + Ucpd1 = periph(Group::Apb1, 25), // G0x1 only + Cec = periph(Group::Apb1, 24), // G0x1 only + I2c3 = periph(Group::Apb1, 23), + I2c2 = periph(Group::Apb1, 22), + I2c1 = periph(Group::Apb1, 21), + LpUart1 = periph(Group::Apb1, 20), // G0x1 only + Usart4 = periph(Group::Apb1, 19), + Usart3 = periph(Group::Apb1, 18), + Usart2 = periph(Group::Apb1, 17), + Crs = periph(Group::Apb1, 16), // G0x1 only + Spi3 = periph(Group::Apb1, 15), + Spi2 = periph(Group::Apb1, 14), + Usb = periph(Group::Apb1, 13), + Fdcan = periph(Group::Apb1, 12), // G0x1 only + Usart6 = periph(Group::Apb1, 9), + Usart5 = periph(Group::Apb1, 8), + LpUart2 = periph(Group::Apb1, 7), // G0x1 only + Tim7 = periph(Group::Apb1, 5), + Tim6 = periph(Group::Apb1, 4), + Tim4 = periph(Group::Apb1, 2), + Tim3 = periph(Group::Apb1, 1), + Tim2 = periph(Group::Apb1, 0), // G0x1 only + + Adc = periph(Group::Apb2, 20), + Tim17 = periph(Group::Apb2, 18), + Tim16 = periph(Group::Apb2, 17), + Tim15 = periph(Group::Apb2, 16), + Tim14 = periph(Group::Apb2, 15), + Usart1 = periph(Group::Apb2, 14), + Spi1 = periph(Group::Apb2, 12), + Tim1 = periph(Group::Apb2, 11), + Syscfg = periph(Group::Apb2, 0), +} diff --git a/drv/rp2040-sys-api/src/h7.rs b/drv/rp2040-sys-api/src/h7.rs new file mode 100644 index 0000000000..57559dd254 --- /dev/null +++ b/drv/rp2040-sys-api/src/h7.rs @@ -0,0 +1,200 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! STM32H7 specifics + +use crate::periph; +use userlib::FromPrimitive; + +/// Peripherals appear in "groups." All peripherals in a group are controlled +/// from the same subset of registers in the RCC. +/// +/// The reference manual lacks a term for this, so we made this one up. It would +/// be tempting to refer to these as "buses," but in practice there are almost +/// always more groups than there are buses, particularly on M0. +/// +/// This is `pub` mostly for use inside driver-servers. +#[derive(Copy, Clone, Debug, FromPrimitive)] +#[repr(u8)] +pub enum Group { + Ahb1, + Ahb2, + Ahb3, + Ahb4, + Apb1L, + Apb1H, + Apb2, + Apb3, + Apb4, +} + +/// Peripheral numbering. +/// +/// Peripheral bit numbers per the STM32H7 documentation, starting with the +/// following sections: +/// +/// STM32H7 PART SECTION +/// B3/A3,B0 8.7.38 +/// 43/53,42,50 8.7.40 +/// 47/57,45/55 9.7.39 +/// +/// These are in the order that they appear in the documentation -- which, +/// while thankfully uniform across the STM32H7 variants, is not necessarily +/// an order that is at all sensible! This is the union of all STM32H7 +/// peripherals; not all peripherals will exist on all variants! +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[repr(u32)] +pub enum Peripheral { + AxisRam = periph(Group::Ahb3, 31), // 47 only + Itcm = periph(Group::Ahb3, 30), // 47 only + Dtcm2 = periph(Group::Ahb3, 29), // 47 only + Dtcm1 = periph(Group::Ahb3, 28), // 47 only + Gfxmmu = periph(Group::Ahb3, 24), // B3 only + Otf2 = periph(Group::Ahb3, 23), // B3 only + Otf1 = periph(Group::Ahb3, 22), // B3 only + Iomngr = periph(Group::Ahb3, 21), // B3 only + OctoSpi2 = periph(Group::Ahb3, 19), // B3 only + Sdmmc1 = periph(Group::Ahb3, 16), + + #[cfg(feature = "h7b3")] + OctoSpi1 = periph(Group::Ahb3, 14), // B3 only + #[cfg(any(feature = "h743", feature = "h747", feature = "h753"))] + QuadSpi = periph(Group::Ahb3, 14), // 43/47 only + + Fmc = periph(Group::Ahb3, 12), + Flash = periph(Group::Ahb3, 8), // 47 only + JpgDec = periph(Group::Ahb3, 5), + Dma2d = periph(Group::Ahb3, 4), + Mdma = periph(Group::Ahb3, 0), + + Usb2Otg = periph(Group::Ahb1, 27), // 43/47 only + Usb1Phy = periph(Group::Ahb1, 26), + Usb1Otg = periph(Group::Ahb1, 25), + Usb2Phy = periph(Group::Ahb1, 18), // 43/47 only + Eth1Rx = periph(Group::Ahb1, 17), // 43/47 only + Eth1Tx = periph(Group::Ahb1, 16), // 43/47 only + Eth1Mac = periph(Group::Ahb1, 15), // 43/47 only + Art = periph(Group::Ahb1, 14), // 47 only + Crc = periph(Group::Ahb1, 9), // B3 only + Adc1 = periph(Group::Ahb1, 5), + Dma2 = periph(Group::Ahb1, 1), + Dma1 = periph(Group::Ahb1, 0), + + Sram3 = periph(Group::Ahb2, 31), // 43/47 only + Sram2 = periph(Group::Ahb2, 30), + Sram1 = periph(Group::Ahb2, 29), + DfsdmDma = periph(Group::Ahb2, 11), // B3 only + Sdmmc2 = periph(Group::Ahb2, 9), + + #[cfg(any(feature = "h753", feature = "h743"))] + Rng = periph(Group::Ahb2, 6), + #[cfg(any(feature = "h753"))] + Hash = periph(Group::Ahb2, 5), + #[cfg(any(feature = "h753"))] + Crypt = periph(Group::Ahb2, 4), + + #[cfg(feature = "h7b3")] + Hsem = periph(Group::Ahb2, 2), // B3 differs from 43/47 + + Dcmi = periph(Group::Ahb2, 0), + + SmartRunSram = periph(Group::Ahb4, 29), // B3 only + BackupRam = periph(Group::Ahb4, 28), + + #[cfg(any(feature = "h743", feature = "h747", feature = "h753"))] + Hsem = periph(Group::Ahb4, 25), // 43/47: differs from B3 + + #[cfg(feature = "h7b3")] + Bdma2 = periph(Group::Ahb4, 21), + #[cfg(any(feature = "h743", feature = "h747", feature = "h757"))] + Bdma = periph(Group::Ahb4, 21), + + GpioK = periph(Group::Ahb4, 10), + GpioJ = periph(Group::Ahb4, 9), + GpioI = periph(Group::Ahb4, 8), + GpioH = periph(Group::Ahb4, 7), + GpioG = periph(Group::Ahb4, 6), + GpioF = periph(Group::Ahb4, 5), + GpioE = periph(Group::Ahb4, 4), + GpioD = periph(Group::Ahb4, 3), + GpioC = periph(Group::Ahb4, 2), + GpioB = periph(Group::Ahb4, 1), + GpioA = periph(Group::Ahb4, 0), + + Wwdg = periph(Group::Apb3, 6), + Dsi = periph(Group::Apb3, 4), // 47 only + Ltdc = periph(Group::Apb3, 3), + + Uart8 = periph(Group::Apb1L, 31), + Uart7 = periph(Group::Apb1L, 30), + Dac1 = periph(Group::Apb1L, 29), + HdmiCec = periph(Group::Apb1L, 27), + I2c3 = periph(Group::Apb1L, 23), + I2c2 = periph(Group::Apb1L, 22), + I2c1 = periph(Group::Apb1L, 21), + Uart5 = periph(Group::Apb1L, 20), + Uart4 = periph(Group::Apb1L, 19), + Usart3 = periph(Group::Apb1L, 18), + Usart2 = periph(Group::Apb1L, 17), + Spdifrx = periph(Group::Apb1L, 16), + Spi3 = periph(Group::Apb1L, 15), + Spi2 = periph(Group::Apb1L, 14), + Wwdg2 = periph(Group::Apb1L, 11), // 47 only + LpTim1 = periph(Group::Apb1L, 9), + Tim14 = periph(Group::Apb1L, 8), + Tim13 = periph(Group::Apb1L, 7), + Tim12 = periph(Group::Apb1L, 6), + Tim7 = periph(Group::Apb1L, 5), + Tim6 = periph(Group::Apb1L, 4), + Tim5 = periph(Group::Apb1L, 3), + Tim4 = periph(Group::Apb1L, 2), + Tim3 = periph(Group::Apb1L, 1), + Tim2 = periph(Group::Apb1L, 0), + + Fdcan = periph(Group::Apb1H, 8), + Mdios = periph(Group::Apb1H, 5), + Opamp = periph(Group::Apb1H, 4), + Swp = periph(Group::Apb1H, 2), + Crsen = periph(Group::Apb1H, 1), + + #[cfg(feature = "h7b3")] + Dfsdm1 = periph(Group::Apb2, 30), // B3 differs from 43/47 + + Hrtim = periph(Group::Apb2, 29), // 43/47 only + + #[cfg(any(feature = "h743", feature = "h747", feature = "h757"))] + Dfsdm1 = periph(Group::Apb2, 28), // 43/47 differ from B3 + + Sai3 = periph(Group::Apb2, 24), // 43/47 only + Sai2 = periph(Group::Apb2, 23), + Sai1 = periph(Group::Apb2, 22), + Spi5 = periph(Group::Apb2, 20), + Tim17 = periph(Group::Apb2, 18), + Tim16 = periph(Group::Apb2, 17), + Tim15 = periph(Group::Apb2, 16), + Spi4 = periph(Group::Apb2, 13), + Spi1 = periph(Group::Apb2, 12), + Usart10 = periph(Group::Apb2, 7), // B3 only + Uart9 = periph(Group::Apb2, 6), // B3 only + Usart6 = periph(Group::Apb2, 5), + Usart1 = periph(Group::Apb2, 4), + Tim8 = periph(Group::Apb2, 1), + Tim1 = periph(Group::Apb2, 0), + + Dfsdm2 = periph(Group::Apb4, 27), // B3 only + Dts = periph(Group::Apb4, 26), // B3 only + Sai4 = periph(Group::Apb4, 21), // 43/47 only + RtcApb = periph(Group::Apb4, 16), + Vref = periph(Group::Apb4, 15), + Comp1 = periph(Group::Apb4, 14), + Dac2 = periph(Group::Apb4, 13), // B3 only + LpTim5 = periph(Group::Apb4, 12), // 43/47 only + LpTim4 = periph(Group::Apb4, 11), // 43/47 only + LpTim3 = periph(Group::Apb4, 10), + LpTim2 = periph(Group::Apb4, 9), + I2c4 = periph(Group::Apb4, 7), + Spi6 = periph(Group::Apb4, 5), + LpUart = periph(Group::Apb4, 3), + SysCfg = periph(Group::Apb4, 1), +} diff --git a/drv/rp2040-sys-api/src/lib.rs b/drv/rp2040-sys-api/src/lib.rs new file mode 100644 index 0000000000..9f5d6dfe9b --- /dev/null +++ b/drv/rp2040-sys-api/src/lib.rs @@ -0,0 +1,159 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Client API for the RP2040 SYS server. + +#![no_std] + +use userlib::*; + +bitflags::bitflags! { + /// Bitmask of peripherals controlled by the reset controller. + pub struct Resets: u32 { + const ADC = 1 << 0; + const BUSCTRL = 1 << 1; + const DMA = 1 << 2; + const I2C0 = 1 << 3; + const I2C1 = 1 << 4; + const IO_BANK0 = 1 << 5; + const IO_QSPI = 1 << 6; + const JTAG = 1 << 7; + const PADS_BANK0 = 1 << 8; + const PADS_QSPI = 1 << 9; + const PIO0 = 1 << 10; + const PIO1 = 1 << 11; + const PLL_SYS = 1 << 12; + const PLL_USB = 1 << 13; + const PWM = 1 << 14; + const RTC = 1 << 15; + const SPI0 = 1 << 16; + const SPI1 = 1 << 17; + const SYSCFG = 1 << 18; + const SYSINFO = 1 << 19; + const TBMAN = 1 << 20; + const TIMER = 1 << 21; + const UART0 = 1 << 22; + const UART1 = 1 << 23; + const USBCTRL = 1 << 24; + } +} + +/// Basically equivalent to Infallible, except that Infallible doesn't define +/// any `From for Infallible` impls and we need some. +/// +/// This should probably move into idol-runtime. +pub enum CantFail {} + +impl TryFrom for CantFail { + type Error = (); + + fn try_from(_: u32) -> Result { + Err(()) + } +} + +impl TryFrom for CantFail { + type Error = (); + + fn try_from(_: u16) -> Result { + Err(()) + } +} + +impl From for u16 { + fn from(x: CantFail) -> Self { + match x {} + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum BitControl { + Normal = 0, + Invert = 1, + ForceLow = 2, + ForceHigh = 3, +} + +pub enum FuncSel0 { + Spi = 1, + Uart = 2, + I2c = 3, + Pwm = 4, + Sio = 5, + Pio0 = 6, + Pio1 = 7, + Usb = 9, + Null = 0x1f, +} + +impl Sys { + /// Requests that a subset of peripherals be put into reset. + /// + /// This operation is idempotent and will be retried automatically should + /// the Sys server crash while processing it. + pub fn enter_reset(&self, resets: Resets) { + self.enter_reset_raw(resets.bits()); + } + + /// Requests that a subset of peripherals be taken out of reset. The server + /// will ensure that the corresponding bits in `RESET_DONE` go high before + /// returning. (TODO: it maybe shouldn't since that commits the Sys server + /// to a blocking operation...) + /// + /// This operation is idempotent and will be retried automatically should + /// the Sys server crash while processing it. + pub fn leave_reset(&self, resets: Resets) { + self.leave_reset_raw(resets.bits()); + } + + /// Changes the GPIO configuration (`GPIOx_CTRL` register) for any subset of + /// pins in IO BANK0. + /// + /// Pins with corresponding 1 bits in the `pins` mask will be changed, other + /// pins will be unaffected. + /// + /// For each of the arguments, `None` will leave the current GPIO setting + /// unchanged, and `Some` will overwrite it. + /// + /// Note that this IPC will generate a sequence of register writes, so the + /// changes across GPIOs will not be atomic. This is a property of the + /// RP2040; the IPC offers the ability to set multiple pins anyway to cut + /// down on round trips. + /// + /// This operation is idempotent and will be automatically retried if the + /// Sys server crashes. + pub fn gpio_configure( + &self, + pins: u32, + irqover: Option, + inover: Option, + oeover: Option, + outover: Option, + funcsel: Option, + ) { + // Pack all that stuff into a compact form that also happens to line up + // with the register bit layout. + let mut packed = 0u32; + + if let Some(fs) = funcsel { + packed |= 0b10_0000 | fs as u32; + } + if let Some(bc) = outover { + packed |= (0b100 | bc as u32) << 8; + } + if let Some(bc) = oeover { + packed |= (0b100 | bc as u32) << 12; + } + if let Some(bc) = inover { + packed |= (0b100 | bc as u32) << 16; + } + if let Some(bc) = irqover { + packed |= (0b100 | bc as u32) << 28; + } + + self.gpio_configure_raw(pins, packed) + } +} + +include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/drv/rp2040-sys/Cargo.toml b/drv/rp2040-sys/Cargo.toml new file mode 100644 index 0000000000..bb7ce2e291 --- /dev/null +++ b/drv/rp2040-sys/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "drv-rp2040-sys" +version = "0.1.0" +edition = "2021" + +[dependencies] +userlib = {path = "../../sys/userlib"} +zerocopy = "0.6.1" +num-traits = { version = "0.2.12", default-features = false } +rp2040-pac = "0.3" +idol-runtime = {git = "https://github.com/oxidecomputer/idolatry.git"} +drv-rp2040-sys-api = {path = "../rp2040-sys-api"} +cfg-if = "1" + +[build-dependencies] +idol = {git = "https://github.com/oxidecomputer/idolatry.git"} + +# This section is here to discourage RLS/rust-analyzer from doing test builds, +# since test builds don't work for cross compilation. +[[bin]] +name = "drv-rp2040-sys" +test = false +bench = false diff --git a/drv/rp2040-sys/build.rs b/drv/rp2040-sys/build.rs new file mode 100644 index 0000000000..668547ddac --- /dev/null +++ b/drv/rp2040-sys/build.rs @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +fn main() -> Result<(), Box> { + idol::server::build_server_support( + "../../idl/rp2040-sys.idol", + "server_stub.rs", + idol::server::ServerStyle::InOrder, + )?; + + Ok(()) +} diff --git a/drv/rp2040-sys/src/main.rs b/drv/rp2040-sys/src/main.rs new file mode 100644 index 0000000000..d70e35bd59 --- /dev/null +++ b/drv/rp2040-sys/src/main.rs @@ -0,0 +1,206 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! A driver for the RP2040 systemy bits + +#![no_std] +#![no_main] + +use rp2040_pac as device; + +use drv_rp2040_sys_api::{Resets, CantFail}; +use core::convert::Infallible; +use idol_runtime::{ClientError, RequestError}; +use userlib::*; + +#[export_name = "main"] +fn main() -> ! { + let resets = unsafe { &*device::RESETS::ptr() }; + + // Bring some things we use out of reset. + resets.reset.modify(|_, w| w + .io_bank0().clear_bit() + ); + + while !resets.reset_done.read().io_bank0().bit() {} + + let sio = unsafe { &*device::SIO::ptr() }; + + sio.gpio_oe_set.write(|w| unsafe { w.bits(1 << 25) }); + sio.gpio_out_set.write(|w| unsafe { w.bits(1 << 25) }); + + let io_bank0 = unsafe { &*device::IO_BANK0::ptr() }; + + // Field messages. + let mut buffer = [0u8; idl::INCOMING_SIZE]; + let mut server = ServerImpl { resets, sio, io_bank0 }; + loop { + idol_runtime::dispatch(&mut buffer, &mut server); + } +} + +struct ServerImpl<'a> { + resets: &'a device::resets::RegisterBlock, + sio: &'a device::sio::RegisterBlock, + io_bank0: &'a device::io_bank0::RegisterBlock, +} + +impl idl::InOrderSysImpl for ServerImpl<'_> { + fn enter_reset_raw( + &mut self, + _: &RecvMessage, + peripherals: u32, + ) -> Result<(), RequestError> { + // Refuse any reserved bits in the reset register. + // PAC/svd2rust is no help here; easiest thing is to use our bitfield. + Resets::from_bits(peripherals) + .ok_or(ClientError::BadMessageContents.fail())?; + + self.resets.reset.modify(|r, w| unsafe { + w.bits(r.bits() | peripherals) + }); + Ok(()) + } + + fn leave_reset_raw( + &mut self, + _: &RecvMessage, + peripherals: u32, + ) -> Result<(), RequestError> { + // Refuse any reserved bits in the reset register. + // PAC/svd2rust is no help here; easiest thing is to use our bitfield. + Resets::from_bits(peripherals) + .ok_or(ClientError::BadMessageContents.fail())?; + + self.resets.reset.modify(|r, w| unsafe { + w.bits(r.bits() & !peripherals) + }); + while self.resets.reset_done.read().bits() & peripherals != 0 { + // TODO: it's probably not great to spin forever in response to any + // client request. + } + + Ok(()) + } + + fn gpio_set_oe_raw( + &mut self, + _: &RecvMessage, + pins: u32, + enable: bool, + ) -> Result<(), RequestError> { + // Only pins 29:0 on this bank are implemented. + if pins & !((1 << 29) - 1) != 0 { + return Err(ClientError::BadMessageContents.fail()); + } + + if enable { + self.sio.gpio_oe_set.write(|w| unsafe { w.bits(pins) }); + } else { + self.sio.gpio_oe_clr.write(|w| unsafe { w.bits(pins) }); + } + + Ok(()) + } + + fn gpio_set_reset( + &mut self, + _: &RecvMessage, + set_pins: u32, + reset_pins: u32, + ) -> Result<(), RequestError> { + // Only pins 29:0 on this bank are implemented. + if set_pins & !((1 << 29) - 1) != 0 { + return Err(ClientError::BadMessageContents.fail()); + } + if reset_pins & !((1 << 29) - 1) != 0 { + return Err(ClientError::BadMessageContents.fail()); + } + + if set_pins != 0 { + self.sio.gpio_out_set.write(|w| unsafe { w.bits(set_pins) }); + } + if reset_pins != 0 { + self.sio.gpio_out_clr.write(|w| unsafe { w.bits(reset_pins) }); + } + + Ok(()) + } + + fn gpio_toggle( + &mut self, + _: &RecvMessage, + pins: u32, + ) -> Result<(), RequestError> { + // Only pins 29:0 on this bank are implemented. + if pins & !((1 << 29) - 1) != 0 { + return Err(ClientError::BadMessageContents.fail()); + } + + self.sio.gpio_out_xor.write(|w| unsafe { w.bits(pins) }); + + Ok(()) + } + + fn gpio_read_input( + &mut self, + _: &RecvMessage, + ) -> Result> { + Ok(self.sio.gpio_in.read().bits()) + } + + + fn gpio_configure_raw( + &mut self, + _: &RecvMessage, + pins: u32, + packed: u32, + ) -> Result<(), RequestError> { + // Each of the fields being written has an enable bit in the IPC + // interface, so the caller can write a subset of fields. We're going to + // turn that into a register bitmask. + + // Mask out the enables for the 2-bit fields. + let enables2_mask = 0b100 << 28 + | 0b100 << 16 + | 0b100 << 12 + | 0b100 << 8; + let enables2 = packed & enables2_mask; + // Turn it into a collection of two-bit masks, with the enable bits + // themselves missing (they are reserved in the register). + let mask2 = (enables2 >> 2) * 0b11; + // And the _one_ five bit field. Do the same. + let enables5 = packed & 0b10_0000; + let mask5 = (enables5 >> 5) * 0b1_1111; + + // Cool. + let mask = mask5 | mask2; + let writebits = packed & mask; + + // We're not being clever with trailing-bits or anything because the M0 + // doesn't have those instructions. + let mut pins = pins; + for pin_no in 0..32 { + let pin_mask = 1 << pin_no; + if pins & pin_mask != 0 { + pins &= !pin_mask; + + self.io_bank0.gpio[pin_no].gpio_ctrl.modify(|r, w| unsafe { + w.bits(r.bits() & !mask | writebits) + }); + + if pins == 0 { break; } + } + } + + Ok(()) + } + +} + +mod idl { + use drv_rp2040_sys_api::CantFail; + + include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); +} diff --git a/drv/user-leds/Cargo.toml b/drv/user-leds/Cargo.toml index ce41203528..d94b1b85ff 100644 --- a/drv/user-leds/Cargo.toml +++ b/drv/user-leds/Cargo.toml @@ -15,6 +15,7 @@ drv-stm32xx-sys-api = {path = "../stm32xx-sys-api", optional = true} drv-lpc55-gpio-api = {path = "../lpc55-gpio-api", optional = true} cfg-if = "0.1.10" idol-runtime = {git = "https://github.com/oxidecomputer/idolatry.git"} +drv-rp2040-sys-api = {path = "../rp2040-sys-api", optional = true} [build-dependencies] build-util = {path = "../../build/util"} @@ -25,6 +26,7 @@ stm32g0 = ["drv-stm32xx-sys-api/family-stm32g0"] stm32h7 = ["drv-stm32xx-sys-api/family-stm32h7"] lpc55 = ["lpc55-pac", "drv-lpc55-gpio-api"] panic-messages = ["userlib/panic-messages"] +rp2040 = ["drv-rp2040-sys-api"] # This section is here to discourage RLS/rust-analyzer from doing test builds, # since test builds don't work for cross compilation. diff --git a/drv/user-leds/src/main.rs b/drv/user-leds/src/main.rs index ab9454afdf..fae37b7d4e 100644 --- a/drv/user-leds/src/main.rs +++ b/drv/user-leds/src/main.rs @@ -59,7 +59,12 @@ cfg_if::cfg_if! { } } // Target boards with 1 led - else if #[cfg(any(target_board = "stm32g031", target_board = "stm32g070", target_board = "stm32g0b1"))] { + else if #[cfg(any( + target_board = "stm32g031", + target_board = "stm32g070", + target_board = "stm32g0b1", + target_board = "pi-pico", + ))] { #[derive(FromPrimitive)] enum Led { Zero = 0, @@ -576,6 +581,66 @@ fn led_toggle(led: Led) { gpio_driver.toggle(pin).unwrap(); } +/////////////////////////////////////////////////////////////////////////////// +// The RP2040 specific bits. +// + +cfg_if::cfg_if! { + if #[cfg(feature = "rp2040")] { + task_slot!(SYS, sys); + + cfg_if::cfg_if! { + if #[cfg(target_board = "pi-pico")] { + // This is really only valid for the Pi Pico. + const LED_PIN: u32 = 25; + } else { + compile_error!("unrecognized rp2040 board"); + } + } + } +} + + +#[cfg(feature = "rp2040")] +fn enable_led_pins() { + use drv_rp2040_sys_api::*; + + let sys = SYS.get_task_id(); + let sys = Sys::from(sys); + + sys.gpio_set_oe_raw(1 << LED_PIN, true); +} + +#[cfg(feature = "rp2040")] +fn led_on(Led::Zero: Led) { + use drv_rp2040_sys_api::*; + + let sys = SYS.get_task_id(); + let sys = Sys::from(sys); + + sys.gpio_set_reset(1 << LED_PIN, 0); +} + +#[cfg(feature = "rp2040")] +fn led_off(Led::Zero: Led) { + use drv_rp2040_sys_api::*; + + let sys = SYS.get_task_id(); + let sys = Sys::from(sys); + + sys.gpio_set_reset(0, 1 << LED_PIN); +} + +#[cfg(feature = "rp2040")] +fn led_toggle(Led::Zero: Led) { + use drv_rp2040_sys_api::*; + + let sys = SYS.get_task_id(); + let sys = Sys::from(sys); + + sys.gpio_toggle(1 << LED_PIN).ok(); +} + mod idl { use super::LedError; diff --git a/idl/rp2040-sys.idol b/idl/rp2040-sys.idol new file mode 100644 index 0000000000..38e1d63cfc --- /dev/null +++ b/idl/rp2040-sys.idol @@ -0,0 +1,59 @@ +// RP2040 "system" IPC API + +Interface( + name: "Sys", + ops: { + "enter_reset_raw": ( + args: { + "peripherals": "u32", + }, + reply: Simple("()"), + idempotent: true, + ), + "leave_reset_raw": ( + args: { + "peripherals": "u32", + }, + reply: Simple("()"), + idempotent: true, + ), + "gpio_set_oe_raw": ( + args: { + "pins": "u32", + "enable": "bool", + }, + reply: Simple("()"), + idempotent: true, + ), + "gpio_set_reset": ( + args: { + "set_pins": "u32", + "reset_pins": "u32", + }, + reply: Simple("()"), + idempotent: true, + ), + "gpio_configure_raw": ( + args: { + "pins": "u32", + "packed_attributes": "u32", + }, + reply: Simple("()"), + idempotent: true, + ), + "gpio_read_input": ( + args: {}, + reply: Simple("u32"), + idempotent: true, + ), + "gpio_toggle": ( + args: { + "pins": "u32", + }, + reply: Result ( + ok: "()", + err: CLike("CantFail"), + ), + ), + }, +)