Skip to content

Commit 21aeba0

Browse files
committed
pldm: Add pldm samples
Adds a matching pair of samples using PLDM doing initial PLDM discovery flow over a UART. Signed-off-by: Tom Burdick <[email protected]>
1 parent 3deb0a2 commit 21aeba0

File tree

15 files changed

+666
-0
lines changed

15 files changed

+666
-0
lines changed

include/zephyr/pmci/pldm/service.h

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) 2025 Intel Corporation
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_PLDM_SERVICE_H_
8+
#define ZEPHYR_PLDM_SERVICE_H_
9+
10+
#include <stdint.h>
11+
#include <libpldm/base.h>
12+
13+
const char *MESSAGE_TYPE_TO_STRING[] = {"Response", "Request", "Reserved", "Async Request Notify"};
14+
15+
const char *COMMAND_TO_STRING[] = {"UNDEFINED", "SetTID", "GetTID",
16+
"GetPLDMVersion", "GetPLDMCommands", "SelectPLDMVersion"};
17+
18+
/**
19+
* @brief Set of functions that are expected to be implemented by PLDM base specification
20+
21+
* Some of these functions are *mandatory* to comply with the PLDM specification, and common
22+
implementations
23+
* of these can use the common pldm_context struct to provide them.
24+
*/
25+
struct pldm_base_api {
26+
void (*get_tid_request)(uint8_t mctp_src_eid, struct pldm_header_info *hdr);
27+
void (*get_tid_response)(uint8_t mctp_src_eid, struct pldm_header_info *hdr,
28+
uint8_t completion_code, uint8_t tid);
29+
void (*get_version_request)(uint8_t mctp_src_eid, struct pldm_header_info *hdr,
30+
uint32_t data_transfer_handle, uint8_t flags,
31+
uint8_t pldm_type);
32+
void (*get_version_response)(uint8_t mctp_src_eid, struct pldm_header_info *hdr,
33+
uint8_t completion_code, uint32_t version);
34+
void (*get_types)(uint8_t mctp_src_eid, struct pldm_header_info *hdr);
35+
void (*get_commands)(uint8_t mctp_src_eid, struct pldm_header_info *hdr);
36+
};
37+
38+
/**
39+
* @brief PLDM handler handles PLDM requests/response messages
40+
*
41+
* Struct should be embedded into a containing struct which holds all the needed contextual
42+
* information.
43+
*/
44+
struct pldm_handler {
45+
const struct pldm_base_api *api;
46+
};
47+
48+
/**
49+
* @brief Handle a pldm message from mctp by routing to the appropriate function pointer
50+
*/
51+
void pldm_service_mctp_receive(struct pldm_handler *hndlr, uint8_t mctp_src_eid, uint8_t *msg, size_t msg_len);
52+
53+
/**
54+
* @brief Common base API implementations
55+
*/
56+
extern const struct pldm_base_api COMMON_BASE_API;
57+
58+
struct pldm_common_handler {
59+
struct pldm_handler handler;
60+
uint8_t tid;
61+
uint32_t base_version;
62+
uint8_t *commands;
63+
size_t num_commands;
64+
uint8_t *types;
65+
size_t num_types;
66+
};
67+
68+
#define PLDM_COMMON_HANDLER_INIT(_tid, _base_version, _commands, _types) \
69+
{ \
70+
.handler = \
71+
{ \
72+
.api = COMMON_BASE_API, \
73+
}, \
74+
.tid = _tid, \
75+
.version = _base_version, \
76+
.commands = _commands, \
77+
.num_commands = ARRAY_SIZE(_commands).types = _types, \
78+
.num_types = ARRAY_SIZE(_types), \
79+
}
80+
81+
#endif /* ZEPHYR_PLDM_SERVICE_H_ */
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
cmake_minimum_required(VERSION 3.20.0)
2+
3+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
4+
5+
project(mctp_endpoint)
6+
7+
target_sources(app PRIVATE src/main.c)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
.. zephyr:code-sample:: mctp_endpoint_sample
2+
:name: MCTP Endpoint Sample
3+
4+
Create an MCTP endpoint over UART.
5+
6+
Overview
7+
********
8+
Sets up an MCTP node that listens on a UART for messages targeting a particular
9+
MCTP endpoint id with the message "hello". Responds to this "hello" message with
10+
"world".
11+
12+
Requirements
13+
************
14+
A board and SoC that provide access to a UART and a driver that implements the
15+
UART async API.
16+
17+
Wiring
18+
******
19+
The listening UART pins should be wired to a board which will run the MCTP host
20+
sample such that this board's UART tx pin connects to the host board's rx pin,
21+
and this board's UART rx pin connects to the host board's tx pin. The boards'
22+
grounds should also be wired together.
23+
24+
Optionally a logic analyzer can be wired up and listening to the UART to inspect
25+
the data flowing.
26+
27+
Building and Running
28+
********************
29+
30+
31+
.. zephyr-app-commands::
32+
:zephyr-app: samples/modules/mctp/mctp_endpoint
33+
:host-os: unix
34+
:board: nrf52840_nrf52840dk
35+
:goals: run
36+
:compact:
37+
38+
References
39+
**********
40+
41+
`MCTP Base Specification 2019 <https://www.dmtf.org/sites/default/files/standards/documents/DSP0236_1.3.1.pdf>`_
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
&arduino_serial{
2+
status = "okay";
3+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CONFIG_SERIAL=y
2+
CONFIG_UART_ASYNC_API=y
3+
CONFIG_MCTP=y
4+
CONFIG_PLDM=y
5+
CONFIG_MCTP_UART=y
6+
CONFIG_LOG=y
7+
CONFIG_LOG_BUFFER_SIZE=4096
8+
CONFIG_MCTP_LOG_LEVEL_INF=y
9+
CONFIG_ISR_STACK_SIZE=4096
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* Copyright (c) 2025 Intel Corporation
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdint.h>
8+
#include <stdlib.h>
9+
#include <stdio.h>
10+
#include <assert.h>
11+
#include <unistd.h>
12+
#include <zephyr/types.h>
13+
#include <zephyr/kernel.h>
14+
#include <libpldm/base.h>
15+
#include <libmctp.h>
16+
#include <zephyr/pmci/mctp/mctp_uart.h>
17+
18+
#include <zephyr/logging/log.h>
19+
LOG_MODULE_REGISTER(pldm_endpoint);
20+
21+
/* PLDM MCTP Message Type */
22+
#define PLDM_MCTP_MESSAGE_TYPE 1
23+
24+
/* Local MCTP Endpoint ID that responds to requests */
25+
#define LOCAL_EID 10
26+
/* Local PLDM Terminus ID that responds to requests */
27+
#define LOCAL_TID 2
28+
29+
/* Remote MCTP Endpoint ID that we request from to */
30+
#define REMOTE_EID 20
31+
32+
#define MCTP_INTEGRITY_CHECK 0x80
33+
#define MCTP_MESSAGE_TYPE_MASK 0x7F
34+
35+
const char *MESSAGE_TYPE_TO_STRING[] = {"Response", "Request", "Reserved", "Async Request Notify"};
36+
37+
const char *COMMAND_TO_STRING[] = {"UNDEFINED", "SetTID", "GetTID",
38+
"GetPLDMVersion", "GetPLDMCommands", "SelectPLDMVersion"};
39+
40+
static struct mctp *mctp_ctx;
41+
42+
static void pldm_rx_handler(uint8_t src_eid, void *data, struct pldm_msg_hdr *msg_hdr, void *msg,
43+
size_t msg_len)
44+
{
45+
struct pldm_header_info hdr_info;
46+
const char *message_type_str = "";
47+
const char *command_str = "";
48+
int rc;
49+
50+
rc = unpack_pldm_header(msg_hdr, &hdr_info);
51+
if (rc != 0) {
52+
LOG_ERR("Failed unpacking pldm header");
53+
}
54+
55+
if (hdr_info.msg_type < ARRAY_SIZE(MESSAGE_TYPE_TO_STRING)) {
56+
message_type_str = MESSAGE_TYPE_TO_STRING[hdr_info.msg_type];
57+
}
58+
59+
if (hdr_info.command < ARRAY_SIZE(COMMAND_TO_STRING)) {
60+
command_str = COMMAND_TO_STRING[hdr_info.command];
61+
}
62+
63+
LOG_INF("received pldm message from mctp endpoint %d len %zu message type %d (%s) command "
64+
"%d (%s)",
65+
src_eid, msg_len, hdr_info.msg_type, message_type_str, hdr_info.command,
66+
command_str);
67+
68+
/* Handle the GetTID command */
69+
if (hdr_info.command == PLDM_GET_TID && hdr_info.msg_type == PLDM_REQUEST) {
70+
/* Response buffer for the GetTID command needs
71+
* pldm response header (4 bytes) + 1 bytes (the tid is 1 byte) + 1 byte (mctp
72+
* message type byte)
73+
*/
74+
uint8_t resp_msg_buf[PLDM_MSG_SIZE(sizeof(struct pldm_get_tid_resp)) + 1];
75+
76+
resp_msg_buf[0] = PLDM_MCTP_MESSAGE_TYPE;
77+
78+
rc = encode_get_tid_resp(hdr_info.instance, PLDM_SUCCESS, LOCAL_TID,
79+
(struct pldm_msg *)&resp_msg_buf[1]);
80+
__ASSERT(rc == PLDM_SUCCESS, "Encoding pldm response should succeed");
81+
82+
rc = mctp_message_tx(mctp_ctx, src_eid, false, 0, resp_msg_buf,
83+
sizeof(resp_msg_buf));
84+
__ASSERT(rc == 0, "Sending response to GetTID should succeed");
85+
} else if (hdr_info.command == PLDM_GET_PLDM_TYPES && hdr_info.msg_type == PLDM_REQUEST) {
86+
/* Response buffer for the GetTID command needs
87+
* pldm response header (4 bytes) + 1 bytes (the tid is 1 byte) + 1 byte (mctp
88+
* message type byte)
89+
*/
90+
uint8_t resp_msg_buf[PLDM_MSG_SIZE(PLDM_GET_TYPES_RESP_BYTES) + 1];
91+
uint8_t types[8];
92+
93+
resp_msg_buf[0] = PLDM_MCTP_MESSAGE_TYPE;
94+
95+
types[0] = PLDM_BASE;
96+
rc = encode_get_types_resp(hdr_info.instance, PLDM_SUCCESS, (const bitfield8_t *)types,
97+
(struct pldm_msg *)&resp_msg_buf[1]);
98+
__ASSERT(rc == PLDM_SUCCESS, "Encoding pldm response should succeed");
99+
100+
rc = mctp_message_tx(mctp_ctx, src_eid, false, 0, resp_msg_buf,
101+
sizeof(resp_msg_buf));
102+
__ASSERT(rc == 0, "Sending response to GetTypes should succeed"); } else if (hdr_info.command == PLDM_GET_PLDM_COMMANDS && hdr_info.msg_type == PLDM_REQUEST) {
103+
104+
} else if (hdr_info.command == PLDM_GET_PLDM_VERSION && hdr_info.msg_type == PLDM_REQUEST) {
105+
uint32_t transfer_handle;
106+
uint8_t transfer_opflag;
107+
uint8_t type;
108+
109+
rc = decode_get_version_req(msg, PLDM_GET_VERSION_REQ_BYTES, &transfer_handle, &transfer_opflag, &type);
110+
__ASSERT(rc == PLDM_SUCCESS, "Decoding GetVersion request should succeed");
111+
112+
113+
/* Response buffer for the GetTID command needs
114+
* pldm response header (4 bytes) + 1 bytes (the tid is 1 byte) + 1 byte (mctp
115+
* message type byte)
116+
*/
117+
uint8_t resp_msg_buf[PLDM_MSG_SIZE(PLDM_GET_VERSION_RESP_BYTES) + 1];
118+
ver32_t version = { .major = 1 };
119+
120+
resp_msg_buf[0] = PLDM_MCTP_MESSAGE_TYPE;
121+
122+
rc = encode_get_version_resp(hdr_info.instance, PLDM_SUCCESS, 0, 0, &version, sizeof(ver32_t),
123+
(struct pldm_msg *)&resp_msg_buf[1]);
124+
__ASSERT(rc == PLDM_SUCCESS, "Encoding pldm response should succeed");
125+
126+
rc = mctp_message_tx(mctp_ctx, src_eid, false, 0, resp_msg_buf,
127+
sizeof(resp_msg_buf));
128+
__ASSERT(rc == 0, "Sending response to GetVersion should succeed"); } else if (hdr_info.command == PLDM_GET_PLDM_COMMANDS && hdr_info.msg_type == PLDM_REQUEST) {
129+
} else if (hdr_info.command == PLDM_GET_PLDM_COMMANDS && hdr_info.msg_type == PLDM_REQUEST) {
130+
uint8_t type;
131+
ver32_t vers;
132+
133+
rc = decode_get_commands_req(msg, PLDM_GET_COMMANDS_REQ_BYTES, &type, &vers);
134+
135+
__ASSERT(rc == PLDM_SUCCESS, "Decoding GetCommands request should succeed");
136+
137+
uint8_t resp_msg_buf[PLDM_MSG_SIZE(PLDM_GET_COMMANDS_RESP_BYTES) + 1];
138+
uint8_t commands[32];
139+
140+
resp_msg_buf[0] = PLDM_MCTP_MESSAGE_TYPE;
141+
commands[0] = PLDM_GET_TID | PLDM_GET_PLDM_VERSION | PLDM_GET_PLDM_TYPES | PLDM_GET_PLDM_COMMANDS;
142+
rc = encode_get_commands_resp(hdr_info.instance, PLDM_SUCCESS, (const bitfield8_t *)commands,
143+
(struct pldm_msg *)&resp_msg_buf[1]);
144+
__ASSERT(rc == PLDM_SUCCESS, "Encoding pldm response should succeed");
145+
146+
rc = mctp_message_tx(mctp_ctx, src_eid, false, 0, resp_msg_buf,
147+
sizeof(resp_msg_buf));
148+
__ASSERT(rc == 0, "Sending response to GetCommands should succeed"); } else if (hdr_info.command == PLDM_GET_PLDM_COMMANDS && hdr_info.msg_type == PLDM_REQUEST) {
149+
} else {
150+
LOG_WRN("Unhandled pldm message, command %d, type %d", hdr_info.command, hdr_info.msg_type);
151+
}
152+
}
153+
154+
static void rx_message(uint8_t src_eid, bool tag_owner, uint8_t msg_tag, void *data, void *msg,
155+
size_t len)
156+
{
157+
LOG_INF("received message from mctp endpoint %d, msg_tag %d, len %zu", src_eid, msg_tag,
158+
len);
159+
LOG_HEXDUMP_INF(msg, len, "mctp rx message");
160+
if (len < 1) {
161+
LOG_ERR("MCTP Message should contain a message type and integrity check byte!");
162+
return;
163+
}
164+
165+
/* Treat data as a buffer for byte wise access */
166+
uint8_t *msg_buf = msg;
167+
168+
/* If the message endpoint ID matches our local endpoint ID, and its a pldm message, call
169+
* the pldm_rx_message call
170+
*/
171+
if ((msg_buf[0] & MCTP_MESSAGE_TYPE_MASK) == PLDM_MCTP_MESSAGE_TYPE) {
172+
/* HAZARD This is potentially error prone but libpldm provides little help here */
173+
struct pldm_msg_hdr *pldm_hdr = (struct pldm_msg_hdr *)&(msg_buf[1]);
174+
size_t pldm_msg_body_len = len - (1 + sizeof(struct pldm_msg_hdr));
175+
void *pldm_msg_body = &msg_buf[1 + sizeof(struct pldm_msg_hdr)];
176+
177+
pldm_rx_handler(src_eid, msg, pldm_hdr, pldm_msg_body, pldm_msg_body_len);
178+
}
179+
}
180+
181+
MCTP_UART_DT_DEFINE(mctp_host, DEVICE_DT_GET(DT_NODELABEL(arduino_serial)));
182+
183+
int main(void)
184+
{
185+
LOG_INF("PLDM Endpoint EID:%d TID:%d on %s\n", LOCAL_EID, LOCAL_TID, CONFIG_BOARD_TARGET);
186+
187+
mctp_set_alloc_ops(malloc, free, realloc);
188+
mctp_ctx = mctp_init();
189+
__ASSERT_NO_MSG(mctp_ctx != NULL);
190+
mctp_register_bus(mctp_ctx, &mctp_host.binding, LOCAL_EID);
191+
mctp_set_rx_all(mctp_ctx, rx_message, NULL);
192+
mctp_uart_start_rx(&mctp_host);
193+
194+
return 0;
195+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
cmake_minimum_required(VERSION 3.20.0)
2+
3+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
4+
project(mctp_host)
5+
6+
target_sources(app PRIVATE src/main.c)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
.. zephyr:code-sample:: mctp_host_sample
2+
:name: MCTP Host Sample
3+
4+
Create an MCTP host over UART.
5+
6+
Overview
7+
********
8+
Sets up an MCTP node that sends a request on a UART targeting a particular MCTP
9+
endpoint id with the message "hello". Expects and waits for a response to this
10+
"hello" message containing "world".
11+
12+
Requirements
13+
************
14+
A board and SoC that provide access to a UART and a driver that implements the
15+
UART async API.
16+
17+
Wiring
18+
******
19+
The UART pins should be wired to a board which will run the MCTP endpoint
20+
sample such that this board's UART tx pin connects to the endpoint board's rx
21+
pin, and this board's UART rx pin connects to the endpoint board's tx pin. The
22+
boards' grounds should also be wired together.
23+
24+
Optionally a logic analyzer can be wired up and listening to the UART to inspect
25+
the data flowing.
26+
27+
Building and Running
28+
********************
29+
30+
.. zephyr-app-commands::
31+
:zephyr-app: samples/modules/mctp/mctp_host
32+
:host-os: unix
33+
:board: nrf52840_nrf52840dk
34+
:goals: run
35+
:compact:
36+
37+
References
38+
**********
39+
40+
`MCTP Base Specification 2019 <https://www.dmtf.org/sites/default/files/standards/documents/DSP0236_1.3.1.pdf>`_
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
&arduino_serial{
2+
status = "okay";
3+
};

0 commit comments

Comments
 (0)