Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ git_override(
# Fix syscall_defs/syscall_user being gated on userspace_build_enabled,
# which breaks userspace=False kernel builds (pigweed regression).
"//third_party/pigweed:syscall_no_userspace_constraint.patch",
# Program the VeeR external-interrupt redirect table (MEIVT) in
# veer_pic early_init; without it the first external interrupt
# vectors through an unprogrammed table to address 0.
"//third_party/pigweed:veer_pic_meivt.patch",
],
remote = "https://pigweed.googlesource.com/pigweed/pigweed",
)
Expand Down
4 changes: 4 additions & 0 deletions target/veer/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ pub struct VeerPicConfig;
impl VeerPicConfigInterface for VeerPicConfig {
const PIC_BASE_ADDRESS: usize = PIC_BASE;
const MAX_IRQS: u32 = 256;
// The VeeR core requires the external-interrupt redirect table to live
// in DCCM; this image does not otherwise use DCCM. 256 IRQs * 4 bytes
// fits well within the 16KiB DCCM.
const MEIVT_BASE_ADDRESS: Option<usize> = Some(0x5000_0000);
}

pub struct TimerConfig;
Expand Down
19 changes: 19 additions & 0 deletions target/veer/peripherals/i3c/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

load("@rules_rust//rust:defs.bzl", "rust_library")
load("//target/veer:defs.bzl", "TARGET_COMPATIBLE_WITH")

package(default_visibility = ["//visibility:public"])

rust_library(
name = "i3c",
srcs = ["lib.rs"],
crate_name = "caliptra_i3c_target",
edition = "2024",
target_compatible_with = TARGET_COMPATIBLE_WITH,
deps = [
"//target/veer/registers",
"//third_party/caliptra:crate_tock-registers",
],
)
183 changes: 183 additions & 0 deletions target/veer/peripherals/i3c/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! Caliptra-SS I3C target peripheral driver.
//!
//! Drives the i3c-core HCI TTI (Target Transaction Interface) on behalf of
//! firmware running on the VeeR RISC-V core. The i3c-core is always a
//! *target* (secondary) from the VeeR perspective — a BMC controller on the
//! I3C bus initiates all transfers.
//!
//! The Caliptra ROM initializes the I3C core before handing off to application
//! firmware. This driver assumes the ROM has already run and only manages the
//! data path (interrupt enable/disable, RX drain, TX queue, IBI).
//!
//! # TTI data path
//!
//! - **Incoming write** (controller → target): hardware pushes a descriptor
//! into `tti_rx_desc_queue_port` then `data_length` words into
//! `tti_rx_data_port`. Poll `rx_pending()` or enable the RX interrupt.
//! - **Outgoing read** (target → controller): firmware writes a descriptor
//! to `tti_tx_desc_queue_port` then `data_length` words to
//! `tti_tx_data_port`.
//! - **IBI**: write the IBI descriptor (MDB + payload length) to
//! `tti_tti_ibi_port` then the payload words; hardware raises the IBI on
//! the bus.

#![no_std]

use caliptra_ss_registers::i3c;

use core::marker::PhantomData;
use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};

/// MIPI DCR value for an MCTP endpoint.
pub const MCTP_DCR: u8 = 0xCC;

/// Driver for the Caliptra-SS i3c-core in target (secondary) mode.
pub struct CaliptraI3cTarget {
regs: *const i3c::regs::I3c,
// !Send + !Sync: exclusive ownership of one physical peripheral
_not_send_sync: PhantomData<*mut ()>,
}

impl CaliptraI3cTarget {
/// Construct the driver over the Caliptra-SS I3C peripheral.
///
/// # Safety
///
/// Caller must ensure exclusive access to the I3C peripheral for the
/// lifetime of this value.
pub const unsafe fn new() -> Self {
Self {
regs: i3c::I3C_CSR_ADDR as *const i3c::regs::I3c,
_not_send_sync: PhantomData,
}
}

#[inline]
fn regs(&self) -> &i3c::regs::I3c {
// SAFETY: `new` guarantees a valid address and exclusive access.
unsafe { &*self.regs }
}

// -------------------------------------------------------------------------
// Interrupts
// -------------------------------------------------------------------------

/// Enable the RX descriptor threshold interrupt.
pub fn enable_rx_interrupt(&mut self) {
self.regs()
.tti_interrupt_enable
.modify(i3c::bits::InterruptEnable::RxDescStatEn::SET);
}

/// Disable the RX descriptor threshold interrupt.
pub fn disable_rx_interrupt(&mut self) {
self.regs()
.tti_interrupt_enable
.modify(i3c::bits::InterruptEnable::RxDescStatEn::CLEAR);
}

// -------------------------------------------------------------------------
// TTI receive (controller → target private write)
// -------------------------------------------------------------------------

/// Return `true` if an RX descriptor is waiting in the TTI queue.
pub fn rx_pending(&self) -> bool {
self.regs()
.tti_interrupt_status
.is_set(i3c::bits::InterruptStatus::RxDescStat)
}

/// Drain one incoming write into `buf`. Returns the number of bytes read,
/// or `None` if no descriptor was present.
pub fn rx_read(&mut self, buf: &mut [u8]) -> Option<usize> {
let regs = self.regs();
if !regs
.tti_interrupt_status
.is_set(i3c::bits::InterruptStatus::RxDescStat)
{
return None;
}

let desc = regs.tti_rx_desc_queue_port.get();
// Lower 16 bits of descriptor carry the data length in bytes.
let len = (desc & 0xffff) as usize;
let nwords = (len + 3) / 4;

let mut i = 0usize;
for _ in 0..nwords {
let word = regs.tti_rx_data_port.get();
for &b in &word.to_le_bytes() {
if let Some(slot) = buf.get_mut(i) {
*slot = b;
}
i += 1;
}
}

// W1C — clear the status bit.
regs.tti_interrupt_status
.write(i3c::bits::InterruptStatus::RxDescStat::SET);

Some(len.min(buf.len()))
}

// -------------------------------------------------------------------------
// TTI transmit (target → controller private read response)
// -------------------------------------------------------------------------

/// Queue `data` as the response to the next private-read from the controller.
///
/// The descriptor must be written before the data words: the hardware
/// (and the emulator model) opens a new TX buffer on the descriptor
/// write and appends subsequent data-port writes to it, matching the
/// upstream caliptra-mcu-sw runtime driver.
pub fn tx_write(&mut self, data: &[u8]) {
let regs = self.regs();
// Descriptor: data_length in lower 16 bits; saturate rather than truncate.
regs.tti_tx_desc_queue_port
.set(u32::try_from(data.len()).unwrap_or(u16::MAX as u32));
let mut chunks = data.chunks_exact(4);
for chunk in &mut chunks {
let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
regs.tti_tx_data_port.set(word);
}
let rem = chunks.remainder();
if !rem.is_empty() {
let mut tmp = [0u8; 4];
tmp[..rem.len()].copy_from_slice(rem);
regs.tti_tx_data_port.set(u32::from_le_bytes(tmp));
}
}

// -------------------------------------------------------------------------
// IBI (In-Band Interrupt — target → controller unsolicited notification)
// -------------------------------------------------------------------------

/// Raise an IBI with the given Mandatory Data Byte and optional payload.
/// Payload must be ≤255 bytes; excess bytes are silently dropped.
///
/// The descriptor word must be written before the payload words: the
/// hardware (and the emulator model) parses the first word written to
/// the IBI port as the descriptor and takes the payload length from it.
pub fn ibi_raise(&mut self, mdb: u8, payload: &[u8]) {
let payload = &payload[..payload.len().min(255)];
let regs = self.regs();
// IBI descriptor: MDB in bits [31:24], payload length in bits [7:0].
let desc = ((mdb as u32) << 24) | (payload.len() as u32 & 0xff);
regs.tti_tti_ibi_port.set(desc);
let mut chunks = payload.chunks_exact(4);
for chunk in &mut chunks {
let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
regs.tti_tti_ibi_port.set(word);
}
let rem = chunks.remainder();
if !rem.is_empty() {
let mut tmp = [0u8; 4];
tmp[..rem.len()].copy_from_slice(rem);
regs.tti_tti_ibi_port.set(u32::from_le_bytes(tmp));
}
}
}
17 changes: 17 additions & 0 deletions target/veer/registers/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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"])

# Umbrella crate re-exporting all Caliptra-SS peripheral register modules.
rust_library(
name = "registers",
srcs = ["registers.rs"],
crate_name = "caliptra_ss_registers",
edition = "2024",
deps = [
"//third_party/caliptra/caliptra-mcu-sw:firmware_registers_generated",
],
)
28 changes: 28 additions & 0 deletions target/veer/registers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# target/veer/registers

Umbrella crate (`caliptra_ss_registers`) that re-exports the generated
register definitions for every Caliptra Subsystem peripheral.

## Source

Registers are generated from the Caliptra-SS SystemRDL sources by
`caliptra_mcu_registers_generator` and live in the pinned
`caliptra-mcu-sw` third-party dependency at
`registers/generated-firmware/src/`. Each peripheral module exposes a
`bits` sub-module of `tock_registers::register_bitfields!` types plus a
base address constant (e.g. `I3C_CSR_ADDR = 0x2000_4000`).

## Usage

Add `//target/veer/registers` to your `deps` and import the peripheral
module you need:

```rust
use caliptra_ss_registers::i3c;
// Bitfield types for register reads/writes
use i3c::bits::Control;

// Base address for MMIO pointer construction
const BASE: u32 = i3c::I3C_CSR_ADDR;

```
24 changes: 24 additions & 0 deletions target/veer/registers/registers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! Caliptra Subsystem register definitions.
//!
//! Re-exports the generated firmware register modules from caliptra-mcu-sw so
//! peripheral drivers in `target/veer/peripherals/` have a single import path.

#![no_std]

pub use caliptra_mcu_registers_generated::axicdma;
pub use caliptra_mcu_registers_generated::defines;
pub use caliptra_mcu_registers_generated::doe_mbox;
pub use caliptra_mcu_registers_generated::el2_pic_ctrl;
pub use caliptra_mcu_registers_generated::fuses;
pub use caliptra_mcu_registers_generated::i3c;
pub use caliptra_mcu_registers_generated::lc_ctrl;
pub use caliptra_mcu_registers_generated::mbox;
pub use caliptra_mcu_registers_generated::mci;
pub use caliptra_mcu_registers_generated::otp_ctrl;
pub use caliptra_mcu_registers_generated::primary_flash_ctrl;
pub use caliptra_mcu_registers_generated::secondary_flash_ctrl;
pub use caliptra_mcu_registers_generated::sha512_acc;
pub use caliptra_mcu_registers_generated::soc;
80 changes: 80 additions & 0 deletions target/veer/tests/i3c_echo/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image")
load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen")
load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script")
load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
load("//target/veer:defs.bzl", "TARGET_COMPATIBLE_WITH")
load("//target/veer/tooling:caliptra_runner.bzl", "caliptra_runner")

package(default_visibility = ["//visibility:public"])

system_image(
name = "i3c_echo",
kernel = ":target",
platform = "//target/veer",
)

target_linker_script(
name = "linker_script",
system_config = ":system_config",
tags = ["kernel"],
template = "//target/veer:linker_script_template",
)

rust_binary_no_panics_test(
name = "no_panics_test",
binary = ":i3c_echo",
)

filegroup(
name = "system_config",
srcs = ["system.json5"],
)

target_codegen(
name = "codegen",
arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv",
system_config = ":system_config",
)

rust_binary(
name = "target",
srcs = ["target.rs"],
edition = "2024",
target_compatible_with = TARGET_COMPATIBLE_WITH,
deps = [
":codegen",
":linker_script",
"//target/veer:entry",
"//target/veer/peripherals/i3c",
"@pigweed//pw_kernel/arch/riscv:arch_riscv",
"@pigweed//pw_kernel/kernel",
"@pigweed//pw_kernel/subsys/console:console_backend",
"@pigweed//pw_kernel/target:target_common",
"@pigweed//pw_log/rust:pw_log",
],
)

caliptra_runner(
name = "i3c_echo_runner",
interface = "emulator",
tags = ["manual"],
target = ":i3c_echo",
)

rust_test(
name = "i3c_echo_test",
srcs = ["i3c_echo_host_test.rs"],
crate_root = "i3c_echo_host_test.rs",
edition = "2024",
data = [":i3c_echo_runner"],
# caliptra_runner.py hardcodes --i3c-port=65534; must not run in parallel.
tags = [
"emulator",
"exclusive",
],
deps = ["//target/veer/tests/i3c_host"],
)
Loading