earlgrey: Implement initial platform service Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/target/earlgrey/firmware/hwe/BUILD.bazel b/target/earlgrey/firmware/hwe/BUILD.bazel index 7238435..9006cb2 100644 --- a/target/earlgrey/firmware/hwe/BUILD.bazel +++ b/target/earlgrey/firmware/hwe/BUILD.bazel
@@ -54,7 +54,6 @@ "//protocol/usb/stack", "//services/flash:client", "//target/earlgrey/drivers:usb_driver", - "//target/earlgrey/registers:pinmux", "//target/earlgrey/registers:top_earlgrey", "//target/earlgrey/registers:usbdev", "//target/earlgrey/services/sysmgr:client", @@ -129,7 +128,12 @@ tags = ["kernel"], visibility = ["//visibility:public"], deps = [ + "//target/earlgrey/drivers:gpio", + "//target/earlgrey/pinout", + "//target/earlgrey/services/platform", + "//target/earlgrey/services/sysmgr:client", "//util/error", + "//util/ipc", "//util/zfmt", "@pigweed//pw_kernel/userspace", "@pigweed//pw_status/rust:pw_status", @@ -161,6 +165,7 @@ platform = "//target/earlgrey", system_config = ":system_config", tags = ["kernel"], + visibility = ["//visibility:public"], ) rust_binary_no_panics_test(
diff --git a/target/earlgrey/firmware/hwe/platform.rs b/target/earlgrey/firmware/hwe/platform.rs index 8389b49..894baa1 100644 --- a/target/earlgrey/firmware/hwe/platform.rs +++ b/target/earlgrey/firmware/hwe/platform.rs
@@ -5,19 +5,136 @@ #![no_main] use pw_status::Error; -use userspace::time::{sleep_until, Clock, Duration, SystemClock}; use userspace::{process_entry, syscall}; use util_error::{AsStatus, ErrorCode}; use util_zfmt::messages::{ProcessExit, ProcessStart}; -/* - * TODO: implement platform server. - */ - fn platform_server() -> Result<(), ErrorCode> { + use earlgrey_gpio::EarlGreyGpio; + use earlgrey_pinout::dualsbs::DualSideBySide; + use earlgrey_pinout::swstraps::SwStraps; + use earlgrey_pinout::Pinout; + use earlgrey_platform::reset::{ResetPolicy, TargetCpuReset}; + use earlgrey_platform::server::PlatformServer; + use earlgrey_platform::spimux::SpiMuxHandler; + use earlgrey_platform::usbmux::UsbMuxHandler; + use earlgrey_sysmgr_client::{ResetInfo, SysmgrClient}; + use platform_codegen::{handle, signals}; + use userspace::syscall::Signals; + use userspace::time::{Clock, Duration, SystemClock}; + use util_ipc::IpcHandle; + + // SAFETY: the platform process has exclusive access to the GPIO & Pinmux peripherals. + let mut gpio = unsafe { EarlGreyGpio::new() }; + let sysmgr = SysmgrClient::new(IpcHandle::new(handle::SYSMGR_PLATFORM)); + + // 1. Unconditionally configure SwStraps pinmux. + SwStraps::configure(&mut gpio)?; + + // 2. Read software straps. + let straps = SwStraps::read_straps(&mut gpio)?; + util_zfmt::debug!("SW_STRAPs read: {straps:02x}", straps = straps); + + // 3. Send the strap value to sysmgr. + sysmgr.set_software_straps(straps)?; + + // 4. Retrieve BootInfo from sysmgr. + let boot_info = sysmgr.get_boot_info()?; + let is_low_power = (boot_info.reset.reason & ResetInfo::REASON_LOW_POWER_EXIT) != 0; + + // 5. Examine straps, configure board pinmux if power-on-reset, and create handlers. + let (usb_mux, spi_mux, reset_policy, usb_sig, rst0_sig, rst1_sig) = match straps { + SwStraps::TEACUP_BOARD | SwStraps::BRINGUP_STRAPS1 | SwStraps::BRINGUP_STRAPS2 => { + if !is_low_power { + DualSideBySide::configure(&mut gpio)?; + } + ( + UsbMuxHandler::new(DualSideBySide::USB_PRESENCE_N, DualSideBySide::USB_MUX_CTRL), + SpiMuxHandler::new( + DualSideBySide::SPI_MUX_EN_N, + DualSideBySide::SPI_MUX_CTRL, + DualSideBySide::SPI_RESET_N, + DualSideBySide::SPI_HOST0_WP_N, + DualSideBySide::SPI_HOST1_WP_N, + ), + ResetPolicy::TargetCpu(TargetCpuReset::new( + DualSideBySide::RST_CTRL0_N, + DualSideBySide::RST_MON0_N, + DualSideBySide::RST_MON1_N, + )), + signals::GPIO_16, + signals::GPIO_17, + signals::GPIO_18, + ) + } + _ => { + // Fall back to DualSideBySide for undefined strapping values. + if !is_low_power { + DualSideBySide::configure(&mut gpio)?; + } + ( + UsbMuxHandler::new(DualSideBySide::USB_PRESENCE_N, DualSideBySide::USB_MUX_CTRL), + SpiMuxHandler::new( + DualSideBySide::SPI_MUX_EN_N, + DualSideBySide::SPI_MUX_CTRL, + DualSideBySide::SPI_RESET_N, + DualSideBySide::SPI_HOST0_WP_N, + DualSideBySide::SPI_HOST1_WP_N, + ), + ResetPolicy::TargetCpu(TargetCpuReset::new( + DualSideBySide::RST_CTRL0_N, + DualSideBySide::RST_MON0_N, + DualSideBySide::RST_MON1_N, + )), + signals::GPIO_16, + signals::GPIO_17, + signals::GPIO_18, + ) + } + }; + + let mut server = PlatformServer::new(gpio, usb_mux, spi_mux, reset_policy); + server.set_exit_deadline(SystemClock::now() + Duration::from_secs(10)); + server.start(is_low_power)?; + loop { - sleep_until(SystemClock::now() + Duration::from_secs(600)) - .map_err(ErrorCode::kernel_error)?; + if server.should_exit() { + return Ok(()); + } + let deadline = server.next_deadline(); + let wait_res = syscall::object_wait( + handle::PLATFORM_INTERRUPTS, + usb_sig | rst0_sig | rst1_sig, + deadline, + ); + + match wait_res { + Ok(wait_return) => { + let signals = wait_return.pending_signals; + + if (signals & usb_sig) != Signals::empty() { + server.handle_usb_presence_interrupt()?; + } + if (signals & rst0_sig) != Signals::empty() { + server.handle_rst_mon_interrupt(0)?; + } + if (signals & rst1_sig) != Signals::empty() { + server.handle_rst_mon_interrupt(1)?; + } + + syscall::interrupt_ack(handle::PLATFORM_INTERRUPTS, signals) + .map_err(ErrorCode::kernel_error)?; + } + Err(Error::DeadlineExceeded) => { + if server.should_exit() { + return Ok(()); + } + server.handle_timeout()?; + } + Err(e) => { + return Err(ErrorCode::kernel_error(e)); + } + } } }
diff --git a/target/earlgrey/firmware/hwe/system.json5 b/target/earlgrey/firmware/hwe/system.json5 index 4f8db6b..ba9a9c0 100644 --- a/target/earlgrey/firmware/hwe/system.json5 +++ b/target/earlgrey/firmware/hwe/system.json5
@@ -123,12 +123,39 @@ handler_object_name: "logger_platform" }, { + name: "sysmgr_platform", + type: "channel_initiator", + handler_process: "sysmgr", + handler_object_name: "sysmgr_service" + }, + { + name: "platform_interrupts", + type: "interrupt", + irqs: [ + { name: "gpio_16", number: 53 }, + { name: "gpio_17", number: 54 }, + { name: "gpio_18", number: 55 } + ] + }, + { name: "platform_thread", kernel_stack_size_bytes: 2048, type: "thread" } ], memory_mappings: [ + { + name: "gpio", + type: "device", + start_address: 0x40040000, + size_bytes: 0x1000 + }, + { + name: "pinmux", + type: "device", + start_address: 0x40460000, + size_bytes: 0x1000 + } ] }, { @@ -235,14 +262,6 @@ type: "device", start_address: 0x40320000, size_bytes: 0x1000 - }, - { - // TODO: eliminate pinmux from usbmgr after implementing pinmux - // configuration in the platform task. - name: "pinmux", - type: "device", - start_address: 0x40460000, - size_bytes: 0x1000 } ] }
diff --git a/target/earlgrey/firmware/hwe/usbmgr.rs b/target/earlgrey/firmware/hwe/usbmgr.rs index 9cb13f4..ef6b03e 100644 --- a/target/earlgrey/firmware/hwe/usbmgr.rs +++ b/target/earlgrey/firmware/hwe/usbmgr.rs
@@ -323,24 +323,8 @@ } } -/// Configures pinmux for the USB device. -/// -/// Currently configures USB sense (VBUS detect) to constant high. -fn usb_setup_pinmux() { - // TODO: move pinmux setup into the platform task. - use top_earlgrey::{PinmuxInsel, PinmuxPeripheralIn}; - let mut pinmux = unsafe { pinmux::PinmuxAon::new() }; - - pinmux - .regs_mut() - .mio_periph_insel() - .at(PinmuxPeripheralIn::UsbdevSense as usize) - .modify(|_| (PinmuxInsel::ConstantOne as u32).into()); -} - /// USB manager server entry point. fn usbmgr_server() -> Result<(), ErrorCode> { - usb_setup_pinmux(); handle_usb() }
diff --git a/target/earlgrey/pinout/swstraps.rs b/target/earlgrey/pinout/swstraps.rs index 6f95896..d589203 100644 --- a/target/earlgrey/pinout/swstraps.rs +++ b/target/earlgrey/pinout/swstraps.rs
@@ -14,6 +14,13 @@ pub const SW_STRAP0: GpioPin = GpioPin::Pin22; pub const SW_STRAP1: GpioPin = GpioPin::Pin23; pub const SW_STRAP2: GpioPin = GpioPin::Pin24; + + /// The teacup board normally operates with a strapping value of 0. + pub const TEACUP_BOARD: u32 = 0; + /// Strap-combo reserved for bringup. + pub const BRINGUP_STRAPS1: u32 = 1; + /// Strap-combo reserved for bringup. + pub const BRINGUP_STRAPS2: u32 = 2; } impl Pinout for SwStraps {
diff --git a/target/earlgrey/services/platform/BUILD.bazel b/target/earlgrey/services/platform/BUILD.bazel new file mode 100644 index 0000000..f1ee29b --- /dev/null +++ b/target/earlgrey/services/platform/BUILD.bazel
@@ -0,0 +1,32 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "platform", + srcs = [ + "lib.rs", + "reset.rs", + "server.rs", + "spimux.rs", + "usbmux.rs", + ], + crate_name = "earlgrey_platform", + edition = "2024", + deps = [ + "//hal/blocking", + "//target/earlgrey/drivers:gpio", + "//target/earlgrey/registers:top_earlgrey", + "//target/earlgrey/util", + "//util/error", + "//util/ipc", + "//util/zfmt", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:zerocopy", + "@zfmt//zfmt", + ], +)
diff --git a/target/earlgrey/services/platform/README.md b/target/earlgrey/services/platform/README.md new file mode 100644 index 0000000..aad9aa5 --- /dev/null +++ b/target/earlgrey/services/platform/README.md
@@ -0,0 +1,92 @@ +# Earlgrey Platform Service + +The Platform Service is a core service running in the Earlgrey Hardware Enablement (HWE) firmware. It is responsible for managing hardware strap pins, system reset monitoring and execution, and USB routing. + +## Overview + +The Platform Service performs the following key functions: +1. **Software Strap Configuration**: Reads the 3-bit hardware strap pins on boot using a robust dual-read procedure and reports the combined 6-bit value to the System Manager (`sysmgr`). +2. **Reset Execution**: Controls the system reset line `RST_CTRL0_N` to latch, measure (for 1 second), and release the target system reset. +3. **Reset Monitoring**: Monitors the reset monitor lines (`RST_MON0_N` and `RST_MON1_N`) for falling edges, triggering a new reset sequence if a reset request is detected. +4. **USB routing**: Monitors the USB presence line (`USB_PRESENCE_N`) and dynamically switches the USB Mux (`USB_MUX_CTRL`) to route USB signals appropriately. + +## Hardware Mappings + +The service interacts with the following pins (mapped via GPIO and Pinmux): +* **Reset Control**: `RST_CTRL0_N` (Pin 0 / Pad `IOA0`). `RST_CTRL1_N` (Pin 1 / Pad `IOA1`) is reserved as a backup. +* **Reset Monitors**: `RST_MON0_N` (Pin 17 / Pad `IOA2`) and `RST_MON1_N` (Pin 18 / Pad `IOA5`). +* **USB Presence**: `USB_PRESENCE_N` (Pin 16 / Pad `IOR11`). +* **USB Mux**: `USB_MUX_CTRL` (Pin 7 / Pad `IOC6`). +* **Software Straps**: `SW_STRAP0` (Pin 22 / Pad `IOC0`), `SW_STRAP1` (Pin 23 / Pad `IOC1`), `SW_STRAP2` (Pin 24 / Pad `IOC2`). +* **SPI Mux/Reset (Cold Boot only)**: + * `SPI_MUX_CTRL` (Pin 4 / Pad `IOB8`) + * `SPI_MUX_EN_N` (Pin 3 / Pad `IOB7`) + * `SPI_RESET_N` (Pin 2 / Pad `IOA7`) + * `SPI_HOST0_WP_N` (Pin 5 / Pad `IOA3`) + * `SPI_HOST1_WP_N` (Pin 6 / Pad `IOA6`) + +## Startup Sequence + +Upon starting, the Platform Service (`//target/earlgrey/firmware/hwe/platform.rs`) executes the following sequence: +1. Unconditionally configures the `SwStraps` pinmux configuration. +2. Performs the **Strap Reading Procedure** (see below) via `SwStraps::read_straps` to determine the software strap value. +3. Sends the strap value to `sysmgr` via the `set_software_straps` IPC. +4. Retrieves `BootInfo` from `sysmgr` and checks whether the boot reason is a **Low Power Exit**. +5. Examines the strap combination (`0`, `1`, `2`, or falling back to `DualSideBySide` for undefined combinations): + * If it is a **Cold Boot / Power-On Reset** (`!is_low_power_exit`), initializes the board pinmux configuration (`DualSideBySide`). + * Constructs the `UsbMuxHandler`, `SpiMuxHandler`, and `ResetPolicy` (`TargetCpuReset`) state machines with their relevant GPIO pins based on the selected pinmux configuration. + * Constructs `PlatformServer` and calls `server.start(is_low_power_exit)`. + +Inside `PlatformServer::start(is_low_power_exit)` (`//target/earlgrey/services/platform/server.rs`): +6. Configures interrupts on the reset monitors (`RST_MON0_N`, `RST_MON1_N`) and USB presence (`USB_PRESENCE_N`). +7. Dispatches initial startup events to the state machine handlers: + * If it is a **Cold Boot / Power-On Reset**, dispatches `SpiMuxEvent::ColdBoot` to `SpiMuxHandler` (driving `SPI_MUX_CTRL` low, `SPI_MUX_EN_N` low, and releasing resets) and dispatches `ResetEvent::Start { is_low_power_exit: false }` to `ResetPolicy` to enter the `LatchReset` state and perform a target reset. + * If it is a **Low Power Exit**, dispatches `ResetEvent::Start { is_low_power_exit: true }` to `ResetPolicy` to transition directly to the `Running` state. + +## Strap Reading Procedure + +Strap pins are read using a two-pass procedure to ensure stability and filter out noise: +For each strap pin $i \in \{0, 1, 2\}$: +1. Configure `SW_STRAPi` pin with **no pull**. +2. Delay for 50 microseconds (`PINMUX_PROP_DELAY`). +3. Read the pin value -> `val1` (0 or 1). +4. Configure the pin pull **opposite** to `val1` (if `val1 == 0` -> pull `Up`, if `val1 == 1` -> pull `Down`). +5. Delay for 50 microseconds. +6. Read the pin value again -> `val2` (0 or 1). +7. The 2-bit result for this pin is `(val1 << 1) | val2`. + +The final 6-bit software strap value is constructed by combining the results: +$$\text{strap\_value} = (\text{strap2} \ll 4) \mid (\text{strap1} \ll 2) \mid \text{strap0}$$ + +## State Machine + +The platform service state machine coordinates target reset execution and runtime event handling. + +```mermaid +stateDiagram-v2 + [*] --> ColdBoot + + ColdBoot --> LatchReset : start() [not Low Power] + ColdBoot --> Running : start() [Low Power Exit] + + LatchReset --> Measure : Drive RST_CTRL0_N Low + + Measure --> ReleaseReset : 1 second timeout + + ReleaseReset --> Running : Drive RST_CTRL0_N High + + state Running { + [*] --> WaitEvent + WaitEvent --> LatchReset : RST_MON0_N / RST_MON1_N Falling Edge + WaitEvent --> WaitEvent : USB_PRESENCE_N Edge (Toggle USB_MUX_CTRL) + } +``` + +### States +* **ColdBoot**: Initial state. Performs startup initialization, strap reading, and decides whether to perform a reset based on the boot reason. +* **LatchReset**: Asserts the target reset by driving `RST_CTRL0_N` Low. +* **Measure**: Waits for 1 second to ensure the reset is registered by the target system. +* **ReleaseReset**: De-asserts the target reset by driving `RST_CTRL0_N` High. +* **Running**: Main loop. Listens for interrupts: + * `RST_MON0_N` / `RST_MON1_N` falling edge: Transitions back to `LatchReset` to execute a reset. + * `USB_PRESENCE_N` edge: Updates the `USB_MUX_CTRL` output (High on unplug/rising edge, Low on plug-in/falling edge).
diff --git a/target/earlgrey/services/platform/lib.rs b/target/earlgrey/services/platform/lib.rs new file mode 100644 index 0000000..163d64a --- /dev/null +++ b/target/earlgrey/services/platform/lib.rs
@@ -0,0 +1,9 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +pub mod reset; +pub mod server; +pub mod spimux; +pub mod usbmux;
diff --git a/target/earlgrey/services/platform/reset.rs b/target/earlgrey/services/platform/reset.rs new file mode 100644 index 0000000..d1af486 --- /dev/null +++ b/target/earlgrey/services/platform/reset.rs
@@ -0,0 +1,194 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use earlgrey_gpio::{EarlGreyGpio, GpioMask, GpioPin}; +use openprot_hal_blocking::gpio_port::{ + EdgeSensitivity, GpioInterrupt, GpioPort, InterruptOperation, PinMask, +}; +use userspace::time::{Clock, Duration, Instant, SystemClock}; +use util_error::ErrorCode; +use zfmt::Zfmt; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TargetCpuState { + ColdBoot, + LatchReset, + Measure, + ReleaseReset, + Running, +} + +#[derive(Zfmt, Clone)] +#[zfmt(format = "Platform State: {state}")] +pub struct StateTransition { + pub state: &'static str, +} + +#[derive(Zfmt, Clone)] +#[zfmt(format = "Reset Monitor: {monitor}")] +pub struct ResetMonitor { + pub monitor: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ResetEvent { + Start { is_low_power_exit: bool }, + MonitorFallingEdge { monitor_index: usize }, + Timeout, +} + +pub struct TargetCpuReset { + pub rst_ctrl0_n: GpioPin, + pub rst_mon0_n: GpioPin, + pub rst_mon1_n: GpioPin, + pub state: TargetCpuState, + next_deadline: Instant, +} + +impl TargetCpuReset { + pub const fn new(rst_ctrl0_n: GpioPin, rst_mon0_n: GpioPin, rst_mon1_n: GpioPin) -> Self { + Self { + rst_ctrl0_n, + rst_mon0_n, + rst_mon1_n, + state: TargetCpuState::ColdBoot, + next_deadline: Instant::MAX, + } + } + + pub fn setup_interrupts(&self, gpio: &mut EarlGreyGpio) -> Result<(), ErrorCode> { + let rst_mon0 = GpioMask::from(self.rst_mon0_n); + gpio.irq_configure(rst_mon0, EdgeSensitivity::FallingEdge) + .map_err(ErrorCode::from)?; + gpio.irq_control(rst_mon0, InterruptOperation::Enable) + .map_err(ErrorCode::from)?; + + let rst_mon1 = GpioMask::from(self.rst_mon1_n); + gpio.irq_configure(rst_mon1, EdgeSensitivity::FallingEdge) + .map_err(ErrorCode::from)?; + gpio.irq_control(rst_mon1, InterruptOperation::Enable) + .map_err(ErrorCode::from)?; + + Ok(()) + } + + pub fn handle_event( + &mut self, + event: ResetEvent, + gpio: &mut EarlGreyGpio, + ) -> Result<(), ErrorCode> { + match event { + ResetEvent::Start { is_low_power_exit } => { + if !is_low_power_exit { + self.transition_to_latch_reset(gpio)?; + } else { + self.transition_to_running(); + } + } + ResetEvent::MonitorFallingEdge { monitor_index } => { + let pin = if monitor_index == 0 { + self.rst_mon0_n + } else { + self.rst_mon1_n + }; + let pin_mask = GpioMask::from(pin); + + gpio.irq_control(pin_mask, InterruptOperation::Clear) + .map_err(ErrorCode::from)?; + + util_zfmt::info!(ResetMonitor { + monitor: monitor_index as u32 + }); + + self.transition_to_latch_reset(gpio)?; + } + ResetEvent::Timeout => match self.state { + TargetCpuState::Measure => { + self.transition_to_release_reset(gpio)?; + } + _ => { + self.next_deadline = Instant::MAX; + } + }, + } + Ok(()) + } + + fn transition_to_latch_reset(&mut self, gpio: &mut EarlGreyGpio) -> Result<(), ErrorCode> { + self.state = TargetCpuState::LatchReset; + util_zfmt::info!(StateTransition { + state: "LatchReset" + }); + gpio.set_reset(GpioMask::empty(), GpioMask::from(self.rst_ctrl0_n)) + .map_err(ErrorCode::from)?; + + self.transition_to_measure(); + Ok(()) + } + + fn transition_to_measure(&mut self) { + self.state = TargetCpuState::Measure; + util_zfmt::info!(StateTransition { state: "Measure" }); + self.next_deadline = SystemClock::now() + Duration::from_secs(1); + } + + fn transition_to_release_reset(&mut self, gpio: &mut EarlGreyGpio) -> Result<(), ErrorCode> { + self.state = TargetCpuState::ReleaseReset; + util_zfmt::info!(StateTransition { + state: "ReleaseReset" + }); + gpio.set_reset(GpioMask::from(self.rst_ctrl0_n), GpioMask::empty()) + .map_err(ErrorCode::from)?; + + self.transition_to_running(); + Ok(()) + } + + fn transition_to_running(&mut self) { + self.state = TargetCpuState::Running; + util_zfmt::info!(StateTransition { state: "Running" }); + self.next_deadline = Instant::MAX; + } + + pub fn next_deadline(&self) -> Instant { + self.next_deadline + } + + pub fn state(&self) -> TargetCpuState { + self.state + } +} + +pub enum ResetPolicy { + TargetCpu(TargetCpuReset), +} + +impl ResetPolicy { + pub fn setup_interrupts(&self, gpio: &mut EarlGreyGpio) -> Result<(), ErrorCode> { + match self { + Self::TargetCpu(policy) => policy.setup_interrupts(gpio), + } + } + + pub fn handle_event( + &mut self, + event: ResetEvent, + gpio: &mut EarlGreyGpio, + ) -> Result<(), ErrorCode> { + match self { + Self::TargetCpu(policy) => policy.handle_event(event, gpio), + } + } + + pub fn next_deadline(&self) -> Instant { + match self { + Self::TargetCpu(policy) => policy.next_deadline(), + } + } + + pub fn state(&self) -> TargetCpuState { + match self { + Self::TargetCpu(policy) => policy.state(), + } + } +}
diff --git a/target/earlgrey/services/platform/server.rs b/target/earlgrey/services/platform/server.rs new file mode 100644 index 0000000..009ef24 --- /dev/null +++ b/target/earlgrey/services/platform/server.rs
@@ -0,0 +1,87 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use crate::reset::{ResetEvent, ResetPolicy, TargetCpuState}; +use crate::spimux::{SpiMuxEvent, SpiMuxHandler}; +use crate::usbmux::{UsbMuxEvent, UsbMuxHandler}; +use earlgrey_gpio::EarlGreyGpio; +use userspace::time::{Clock, Instant, SystemClock}; +use util_error::ErrorCode; + +pub use crate::reset::TargetCpuState as State; + +pub struct PlatformServer { + gpio: EarlGreyGpio, + usb_mux: UsbMuxHandler, + spi_mux: SpiMuxHandler, + reset_policy: ResetPolicy, + exit_deadline: Instant, +} + +impl PlatformServer { + pub fn new( + gpio: EarlGreyGpio, + usb_mux: UsbMuxHandler, + spi_mux: SpiMuxHandler, + reset_policy: ResetPolicy, + ) -> Self { + Self { + gpio, + usb_mux, + spi_mux, + reset_policy, + exit_deadline: Instant::MAX, + } + } + + pub fn state(&self) -> TargetCpuState { + self.reset_policy.state() + } + + pub fn next_deadline(&self) -> Instant { + self.reset_policy.next_deadline().min(self.exit_deadline) + } + + pub fn set_exit_deadline(&mut self, deadline: Instant) { + self.exit_deadline = deadline; + } + + pub fn should_exit(&self) -> bool { + SystemClock::now() >= self.exit_deadline + } + + pub fn start(&mut self, is_low_power_exit: bool) -> Result<(), ErrorCode> { + // 1. Configure interrupts on reset monitors and USB presence. + self.usb_mux.setup_interrupts(&mut self.gpio)?; + self.reset_policy.setup_interrupts(&mut self.gpio)?; + + // 2. Dispatch initial startup events. + if !is_low_power_exit { + self.spi_mux + .handle_event(SpiMuxEvent::ColdBoot, &mut self.gpio)?; + } + self.reset_policy + .handle_event(ResetEvent::Start { is_low_power_exit }, &mut self.gpio)?; + + Ok(()) + } + + pub fn handle_timeout(&mut self) -> Result<(), ErrorCode> { + self.reset_policy + .handle_event(ResetEvent::Timeout, &mut self.gpio) + } + + pub fn handle_usb_presence_interrupt(&mut self) -> Result<(), ErrorCode> { + self.usb_mux + .handle_event(UsbMuxEvent::PinChanged, &mut self.gpio) + } + + pub fn handle_rst_mon_interrupt(&mut self, index: usize) -> Result<(), ErrorCode> { + self.reset_policy.handle_event( + ResetEvent::MonitorFallingEdge { + monitor_index: index, + }, + &mut self.gpio, + ) + } +}
diff --git a/target/earlgrey/services/platform/spimux.rs b/target/earlgrey/services/platform/spimux.rs new file mode 100644 index 0000000..137d2e7 --- /dev/null +++ b/target/earlgrey/services/platform/spimux.rs
@@ -0,0 +1,57 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use earlgrey_gpio::{EarlGreyGpio, GpioMask, GpioPin}; +use openprot_hal_blocking::gpio_port::{GpioPort, PinMask}; +use util_error::ErrorCode; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SpiMuxEvent { + ColdBoot, +} + +pub struct SpiMuxHandler { + pub spi_mux_en_n: GpioPin, + pub spi_mux_ctrl: GpioPin, + pub spi_reset_n: GpioPin, + pub spi_host0_wp_n: GpioPin, + pub spi_host1_wp_n: GpioPin, +} + +impl SpiMuxHandler { + pub const fn new( + spi_mux_en_n: GpioPin, + spi_mux_ctrl: GpioPin, + spi_reset_n: GpioPin, + spi_host0_wp_n: GpioPin, + spi_host1_wp_n: GpioPin, + ) -> Self { + Self { + spi_mux_en_n, + spi_mux_ctrl, + spi_reset_n, + spi_host0_wp_n, + spi_host1_wp_n, + } + } + + pub fn handle_event( + &mut self, + event: SpiMuxEvent, + gpio: &mut EarlGreyGpio, + ) -> Result<(), ErrorCode> { + match event { + SpiMuxEvent::ColdBoot => { + let low_pins = + GpioMask::from(self.spi_mux_ctrl).union(GpioMask::from(self.spi_mux_en_n)); + let high_pins = GpioMask::from(self.spi_reset_n) + .union(GpioMask::from(self.spi_host0_wp_n)) + .union(GpioMask::from(self.spi_host1_wp_n)); + + gpio.set_reset(high_pins, low_pins) + .map_err(ErrorCode::from)?; + } + } + Ok(()) + } +}
diff --git a/target/earlgrey/services/platform/usbmux.rs b/target/earlgrey/services/platform/usbmux.rs new file mode 100644 index 0000000..06f03fa --- /dev/null +++ b/target/earlgrey/services/platform/usbmux.rs
@@ -0,0 +1,73 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use earlgrey_gpio::{EarlGreyGpio, GpioMask, GpioPin}; +use openprot_hal_blocking::gpio_port::{ + EdgeSensitivity, GpioInterrupt, GpioPort, InterruptOperation, PinMask, +}; +use util_error::ErrorCode; +use zfmt::Zfmt; + +#[derive(Zfmt, Clone)] +#[zfmt(format = "USB Presence: {present}")] +pub struct UsbPresence { + pub present: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UsbMuxEvent { + PinChanged, +} + +pub struct UsbMuxHandler { + pub usb_presence_n: GpioPin, + pub usb_mux_ctrl: GpioPin, +} + +impl UsbMuxHandler { + pub const fn new(usb_presence_n: GpioPin, usb_mux_ctrl: GpioPin) -> Self { + Self { + usb_presence_n, + usb_mux_ctrl, + } + } + + pub fn setup_interrupts(&self, gpio: &mut EarlGreyGpio) -> Result<(), ErrorCode> { + let usb_pres = GpioMask::from(self.usb_presence_n); + gpio.irq_configure(usb_pres, EdgeSensitivity::BothEdges) + .map_err(ErrorCode::from)?; + gpio.irq_control(usb_pres, InterruptOperation::Enable) + .map_err(ErrorCode::from)?; + Ok(()) + } + + pub fn handle_event( + &mut self, + event: UsbMuxEvent, + gpio: &mut EarlGreyGpio, + ) -> Result<(), ErrorCode> { + match event { + UsbMuxEvent::PinChanged => { + let pin_mask = GpioMask::from(self.usb_presence_n); + gpio.irq_control(pin_mask, InterruptOperation::Clear) + .map_err(ErrorCode::from)?; + + let is_high = gpio + .read_input() + .map_err(ErrorCode::from)? + .contains(pin_mask); + let usb_mux = GpioMask::from(self.usb_mux_ctrl); + if is_high { + gpio.set_reset(usb_mux, GpioMask::empty()) + .map_err(ErrorCode::from)?; + util_zfmt::info!(UsbPresence { present: false }); + } else { + gpio.set_reset(GpioMask::empty(), usb_mux) + .map_err(ErrorCode::from)?; + util_zfmt::info!(UsbPresence { present: true }); + } + } + } + Ok(()) + } +}
diff --git a/target/earlgrey/services/sysmgr/client.rs b/target/earlgrey/services/sysmgr/client.rs index f8e7884..3fd81c8 100644 --- a/target/earlgrey/services/sysmgr/client.rs +++ b/target/earlgrey/services/sysmgr/client.rs
@@ -15,6 +15,7 @@ pub const SYSMGR_OP_GET_BOOT_INFO: Opcode = Opcode::new(*b"MGBI"); pub const SYSMGR_OP_SET_BOOT_POLICY: Opcode = Opcode::new(*b"MGBP"); pub const SYSMGR_OP_REQ_REBOOT: Opcode = Opcode::new(*b"MGRB"); + pub const SYSMGR_OP_SET_SW_STRAPS: Opcode = Opcode::new(*b"MSWS"); } pub struct SysmgrClient<IPC: IpcChannel> { @@ -75,6 +76,17 @@ pub software_straps: u32, } +impl ResetInfo { + pub const REASON_POR: u32 = 1 << 0; + pub const REASON_LOW_POWER_EXIT: u32 = 1 << 1; + pub const REASON_SW_RESET: u32 = 1 << 2; + pub const REASON_HW_REQ_SYSRST_CTRL: u32 = 1 << 3; + pub const REASON_HW_REQ_AON_TIMER: u32 = 1 << 4; + pub const REASON_HW_REQ_PWRMGR: u32 = 1 << 5; + pub const REASON_HW_REQ_ALERT_HANDLER: u32 = 1 << 6; + pub const REASON_HW_REQ_RV_DM: u32 = 1 << 7; +} + #[derive(Clone, FromBytes, IntoBytes, Immutable, KnownLayout, Zfmt)] #[repr(C)] #[zfmt(format = "{chip} {rom_ext} {app} {ownership} {reset}")] @@ -139,4 +151,16 @@ .map_err(ErrorCode::kernel_error)?; ErrorCode::check_status(result) } + + pub fn set_software_straps(&self, straps: u32) -> Result<(), ErrorCode> { + let mut result = 0u32; + self.ipc + .transact( + &[op::SYSMGR_OP_SET_SW_STRAPS.as_bytes(), straps.as_bytes()], + &mut [result.as_mut_bytes()], + Instant::MAX, + ) + .map_err(ErrorCode::kernel_error)?; + ErrorCode::check_status(result) + } }
diff --git a/target/earlgrey/services/sysmgr/server.rs b/target/earlgrey/services/sysmgr/server.rs index c8b6d96..4bd561a 100644 --- a/target/earlgrey/services/sysmgr/server.rs +++ b/target/earlgrey/services/sysmgr/server.rs
@@ -79,7 +79,6 @@ }, }; - util_zfmt::info!(info.clone()); Ok(Self { info, retram }) } @@ -121,6 +120,19 @@ Ok(&data[0..0]) } + fn handle_set_software_straps<'a>( + &mut self, + data: &'a mut [u8], + reqsz: usize, + ) -> Result<&'a [u8], ErrorCode> { + let straps_bytes = data.get(..reqsz).ok_or(error::IPC_ERROR_BAD_REQ_LEN)?; + let straps = + u32::read_from_bytes(straps_bytes).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + self.info.reset.software_straps = straps; + util_zfmt::info!(self.info.clone()); + Ok(&data[0..0]) + } + fn handle_op<'a>( &mut self, opcode: Opcode, @@ -131,6 +143,7 @@ op::SYSMGR_OP_GET_BOOT_INFO => self.handle_get_boot_info(data, reqsz), op::SYSMGR_OP_REQ_REBOOT => self.handle_req_reboot(data, reqsz), op::SYSMGR_OP_SET_BOOT_POLICY => self.handle_set_boot_policy(data, reqsz), + op::SYSMGR_OP_SET_SW_STRAPS => self.handle_set_software_straps(data, reqsz), _ => Err(error::IPC_ERROR_UNKNOWN_OP), } }