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
1,182 changes: 742 additions & 440 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions services/pldm/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")

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

rust_library(
name = "pldm_service",
srcs = glob(["src/**/*.rs"]),
crate_name = "openprot_pldm_service",
edition = "2024",
deps = [
"//services/mctp/api:mctp_api",
"@rust_crates//:mctp",
"@rust_crates//:mctp-lib",
"@rust_crates//:pldm-common",
"@rust_crates//:pldm-interface",
],
)

rust_test(
name = "pldm_service_test",
crate = ":pldm_service",
)

rust_test(
name = "base_host_test",
srcs = ["tests/base_host.rs"],
crate_root = "tests/base_host.rs",
edition = "2024",
deps = [
":pldm_service",
"//services/mctp/api:mctp_api",
"//services/mctp/server:mctp_server_lib",
"@rust_crates//:mctp",
"@rust_crates//:mctp-lib",
"@rust_crates//:pldm-common",
"@rust_crates//:pldm-interface",
],
)

rust_test(
name = "firmware_update_host_test",
srcs = ["tests/firmware_update_host.rs"],
crate_root = "tests/firmware_update_host.rs",
edition = "2024",
deps = [
":pldm_service",
"//services/mctp/api:mctp_api",
"//services/mctp/server:mctp_server_lib",
"@rust_crates//:mctp",
"@rust_crates//:mctp-lib",
"@rust_crates//:pldm-common",
"@rust_crates//:pldm-interface",
],
)
102 changes: 102 additions & 0 deletions services/pldm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# openprot-pldm-service

Platform-independent PLDM-over-MCTP responder service.

## Overview

This crate bridges [`openprot-mctp-api`](../mctp/api) and
[`pldm-interface`](https://github.com/OpenPRoT/pldm-lib/tree/main/pldm-interface)
so that firmware can receive and respond to PLDM messages transported over
MCTP without depending on any particular MCTP implementation or OS.

```text
┌──────────────────────────┐
│ Application / Firmware │ creates PldmResponder, calls run_once()
└───────────┬──────────────┘
┌──────────────────────────┐
│ openprot-pldm-service │ dispatches to CmdInterface (this crate)
└───────────┬──────────────┘
│ MctpListener / MctpRespChannel
┌──────────────────────────┐
│ openprot-mctp-api │ Stack<C: MctpClient>
└───────────┬──────────────┘
│ IPC / transport
┌──────────────────────────┐
│ MCTP Server │
└──────────────────────────┘
```

## Key types

| Type | Description |
|------|-------------|
| `PldmResponder<'a>` | Holds a `CmdInterface`; call `run_once()` in a loop |
| `PldmServiceError` | Union of MCTP transport errors, PLDM handler errors, and overflow |
| `PLDM_MSG_TYPE` | MCTP message-type constant for PLDM (`0x01`) |

## Usage

```rust,ignore
use openprot_pldm_service::PldmResponder;
use pldm-interface::control_context::ProtocolCapability;
use pldm-common::protocol::base::{PldmControlCmd, PldmSupportedType};

const CTRL_CMDS: [u8; 5] = [
PldmControlCmd::SetTid as u8,
PldmControlCmd::GetTid as u8,
PldmControlCmd::GetPldmCommands as u8,
PldmControlCmd::GetPldmVersion as u8,
PldmControlCmd::GetPldmTypes as u8,
];

let caps = [
ProtocolCapability::new(PldmSupportedType::Base, "1.1.0", &CTRL_CMDS).unwrap(),
];

let mut responder = PldmResponder::new(&caps);
let mut buf = [0u8; 1024];

// `stack` is a `Stack<impl MctpClient>` obtained from the platform MCTP client.
loop {
if let Err(e) = responder.run_once(&stack, &mut buf, 0) {
// handle or log error
}
}
```

## Buffer layout

`run_once` expects `buf` to be at least 2 bytes. Internally byte 0 is
reserved for the MCTP message-type prefix (`0x01`); the PLDM payload is
received into `buf[1..]` and the response is written back in-place:

```text
buf[0] : MCTP message-type byte (0x01) — managed by PldmResponder
buf[1..] : PLDM request / response bytes
```

Size the buffer to accommodate the largest PLDM message your application
expects (typically ≤ 4096 bytes; smaller for embedded targets).

## Build

```
bazel build //services/pldm:pldm_service
```

After adding `pldm-common` and `pldm-interface` to
`third_party/crates_io/Cargo.toml`, re-pin the lock file:

```
CARGO_BAZEL_REPIN=1 bazel sync
```

## Dependencies

- [`openprot-mctp-api`](../mctp/api) — MCTP stack facade and traits
- [`pldm-interface`](https://github.com/OpenPRoT/pldm-lib/tree/main/pldm-interface) — PLDM command dispatcher (`CmdInterface`)
- [`pldm-common`](https://github.com/OpenPRoT/pldm-lib/tree/main/pldm-common) — PLDM protocol types and MCTP transport helpers
19 changes: 19 additions & 0 deletions services/pldm/firmware-device-ipc/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")

rust_library(
name = "pldm_firmware_device_ipc",
srcs = ["src/lib.rs"],
crate_name = "openprot_pldm_firmware_device_ipc",
edition = "2024",
tags = ["kernel"],
target_compatible_with = ["@platforms//os:none"],
visibility = ["//visibility:public"],
deps = [
"//services/pldm:pldm_service",
"@pigweed//pw_kernel/userspace",
"@pigweed//pw_status/rust:pw_status",
],
)
200 changes: 200 additions & 0 deletions services/pldm/firmware-device-ipc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! Pigweed IPC channel implementations for [`FirmwareDevice`].
//!
//! Provides:
//! * [`IpcFdUaRspChannel`] – server-side channel that receives firmware-device
//! commands via `channel_read` and responds via `channel_respond`.
//! * [`IpcFdUaCmdChannel`] – client-side channel that performs a synchronous
//! firmware-update request/response round-trip via `channel_transact`.
//! * [`IpcUaFdRspChannel`] – server-side channel used by `PldmRequester` to
//! receive forwarded PLDM requests from `FirmwareDevice` and respond with
//! the MCTP result.
//!
//! ## Usage
//!
//! ```rust,ignore
//! use openprot_pldm_firmware_device_ipc::{IpcFdUaRspChannel, IpcFdUaCmdChannel};
//! use openprot_pldm_service::firmware_device::FirmwareDevice;
//!
//! let fd_channel = IpcFdUaRspChannel::new(handle::FD_CMD);
//! let fw_channel = IpcFdUaCmdChannel::new(handle::FW_REQ);
//! let mut fd = FirmwareDevice::new(&PROTOCOL_CAPS);
//! let mut buf = [0u8; openprot_pldm_service::firmware_device::FD_IPC_MAX_MSG];
//! loop {
//! let _ = fd.run_terminus(&fd_channel, &fw_channel, &mut buf, 0);
//! }
//! ```
//!
//! [`FirmwareDevice`]: openprot_pldm_service::firmware_device::FirmwareDevice

#![no_std]
#![warn(missing_docs)]

use openprot_pldm_service::error::PldmServiceError;
use openprot_pldm_service::firmware_device::{
FdUaCmdChannel, FdUaRspChannel, UaFdCmdChannel, UaFdRspChannel,
};
use userspace::syscall::Signals;
use userspace::time::Instant;

/// IPC server-side channel for receiving PLDM firmware-device commands.
/// Meant to be used by [`FirmwareDevice`] to receive requests from the UA
/// and respond with the MCTP result.
///
/// Wraps a Pigweed IPC channel handle. Each call to [`FdUaRspChannel::recv`]
/// reads one incoming request with `channel_read`; [`FdUaRspChannel::respond`]
/// sends the response with `channel_respond`.
///
/// The handle comes from the application's generated `handle` module
/// (e.g. `handle::FD_CMD`).
pub struct IpcFdUaRspChannel {
handle: u32,
}

impl IpcFdUaRspChannel {
/// Create a new channel bound to `handle`.
pub fn new(handle: u32) -> Self {
Self { handle }
}

/// Return the underlying IPC channel handle.
pub fn channel_handle(&self) -> u32 {
self.handle
}
}

impl FdUaRspChannel for IpcFdUaRspChannel {
fn recv(&self, buf: &mut [u8], _timeout_millis: u32) -> Result<usize, PldmServiceError> {
userspace::syscall::channel_read(self.handle, 0, buf).map_err(|_| PldmServiceError::Ipc)
}

fn try_recv(&self, buf: &mut [u8]) -> Result<Option<usize>, PldmServiceError> {
// `channel_read` is non-blocking: it returns `Error::Unavailable` when
// no transaction is pending, which we map to "no message".
match userspace::syscall::channel_read(self.handle, 0, buf) {
Ok(len) => Ok(Some(len)),
Err(pw_status::Error::Unavailable) => Ok(None),
Err(_) => Err(PldmServiceError::Ipc),
}
}

fn respond(&self, buf: &[u8]) -> Result<(), PldmServiceError> {
userspace::syscall::channel_respond(self.handle, buf).map_err(|_| PldmServiceError::Ipc)
}

fn wait_readable(&self, _timeout_millis: u32) -> Result<(), PldmServiceError> {
// Park the task until the channel becomes readable. This is the yield
// point that lets `run_terminus` avoid busy-polling when idle.
//
// TODO: honor a finite `timeout_millis`. The kernel takes an absolute
// deadline; all current call sites block indefinitely, so we mirror
// that with `Instant::MAX` for now.
userspace::syscall::object_wait(self.handle, Signals::READABLE, Instant::MAX)
.map(|_| ())
.map_err(|_| PldmServiceError::Ipc)
}
}

/// IPC client-side channel for sending PLDM firmware-update requests.
/// Meant to be used by [`FirmwareDevice`] to send requests to the UA and receive
/// the MCTP response.
///
/// Wraps a Pigweed IPC channel handle. Each call to [`FdUaCmdChannel::transact`]
/// performs one synchronous `channel_transact`, blocking until the response
/// arrives.
///
/// The handle comes from the application's generated `handle` module
/// (e.g. `handle::FW_REQ`).
pub struct IpcFdUaCmdChannel {
handle: u32,
}

impl IpcFdUaCmdChannel {
/// Create a new channel bound to `handle`.
pub fn new(handle: u32) -> Self {
Self { handle }
}

/// Return the underlying IPC channel handle.
pub fn channel_handle(&self) -> u32 {
self.handle
}
}

impl FdUaCmdChannel for IpcFdUaCmdChannel {
fn transact(&self, req: &[u8], resp: &mut [u8]) -> Result<usize, PldmServiceError> {
userspace::syscall::channel_transact(self.handle, req, resp, Instant::MAX)
.map_err(|_| PldmServiceError::Ipc)
}
}

/// IPC client-side channel for sending PLDM firmware-command requests.
///
/// Wraps a Pigweed IPC channel handle. Each call to [`IpcUaFdCmdChannel::transact`]
/// performs one synchronous `channel_transact`, blocking until the response
/// arrives.
///
/// The handle comes from the application's generated `handle` module
/// (e.g. `handle::FW_REQ`).
pub struct IpcUaFdCmdChannel {
handle: u32,
}

impl IpcUaFdCmdChannel {
/// Create a new channel bound to `handle`.
pub fn new(handle: u32) -> Self {
Self { handle }
}

/// Return the underlying IPC channel handle.
pub fn channel_handle(&self) -> u32 {
self.handle
}
}

impl UaFdCmdChannel for IpcUaFdCmdChannel {
fn transact(&self, req: &[u8], resp: &mut [u8]) -> Result<usize, PldmServiceError> {
userspace::syscall::channel_transact(self.handle, req, resp, Instant::MAX)
.map_err(|_| PldmServiceError::Ipc)
}
}

/// IPC server-side channel used by [`PldmRequester`] to receive forwarded
/// PLDM requests from [`FirmwareDevice`] and respond with the MCTP result.
///
/// Wraps a Pigweed IPC channel handle. Each call to [`UaFdRspChannel::recv`]
/// reads one incoming request with `channel_read`; [`UaFdRspChannel::respond`]
/// sends the response with `channel_respond`.
///
/// The handle comes from the application's generated `handle` module
/// (e.g. `handle::FW_REQ`).
///
/// [`PldmRequester`]: openprot_pldm_service::requester::PldmRequester
/// [`FirmwareDevice`]: openprot_pldm_service::firmware_device::FirmwareDevice
pub struct IpcUaFdRspChannel {
handle: u32,
}

impl IpcUaFdRspChannel {
/// Create a new channel bound to `handle`.
pub fn new(handle: u32) -> Self {
Self { handle }
}

/// Return the underlying IPC channel handle.
pub fn channel_handle(&self) -> u32 {
self.handle
}
}

impl UaFdRspChannel for IpcUaFdRspChannel {
fn recv(&self, buf: &mut [u8]) -> Result<usize, PldmServiceError> {
userspace::syscall::channel_read(self.handle, 0, buf).map_err(|_| PldmServiceError::Ipc)
}

fn respond(&self, buf: &[u8]) -> Result<(), PldmServiceError> {
userspace::syscall::channel_respond(self.handle, buf).map_err(|_| PldmServiceError::Ipc)
}
}
Loading