mctp: Add mctp subsystem with uart binding

libmctp provides interfaces for wiring up a MCTP bus it calls bus
bindings. The bindings provided in libmctp however are not directly
useful to Zephyr without some work. Provide an initial uart binding that
directly uses Zephyr's async uart interface.

Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
This commit is contained in:
Tom Burdick 2024-08-22 10:45:03 -05:00 committed by Henrik Brix Andersen
parent bb22e3e625
commit 4148512567
6 changed files with 437 additions and 0 deletions

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2024 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#ifndef ZEPHYR_MCTP_UART_H_
#define ZEPHYR_MCTP_UART_H_
#include <stdint.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <libmctp.h>
/**
* @brief An MCTP binding for Zephyr's asynchronous UART interface
*/
struct mctp_binding_uart {
/** @cond INTERNAL_HIDDEN */
struct mctp_binding binding;
const struct device *dev;
/* receive buffers and state */
uint8_t rx_buf[2][256];
bool rx_buf_used[2];
struct mctp_pktbuf *rx_pkt;
uint8_t rx_exp_len;
uint16_t rx_fcs;
uint16_t rx_fcs_calc;
enum {
STATE_WAIT_SYNC_START,
STATE_WAIT_REVISION,
STATE_WAIT_LEN,
STATE_DATA,
STATE_DATA_ESCAPED,
STATE_WAIT_FCS1,
STATE_WAIT_FCS2,
STATE_WAIT_SYNC_END,
} rx_state;
int rx_res;
/* staging buffer for tx */
uint8_t tx_buf[256];
int tx_res;
/** @endcond INTERNAL_HIDDEN */
};
/**
* @brief Start the receive of a single mctp message
*
* Will read a single mctp message from the uart.
*
* @param uart MCTP UART binding
*/
void mctp_uart_start_rx(struct mctp_binding_uart *uart);
/** @cond INTERNAL_HIDDEN */
int mctp_uart_start(struct mctp_binding *binding);
int mctp_uart_tx(struct mctp_binding *binding, struct mctp_pktbuf *pkt);
/** @endcond INTERNAL_HIDDEN */
/**
* @brief Statically define a MCTP bus binding for a UART
*
* @param _name Symbolic name of the bus binding variable
* @param _dev UART device
*/
#define MCTP_UART_DT_DEFINE(_name, _dev) \
struct mctp_binding_uart _name = { \
.binding = \
{ \
.name = STRINGIFY(_name), .version = 1, \
.pkt_size = MCTP_PACKET_SIZE(MCTP_BTU), \
.pkt_header = 0, .pkt_trailer = 0, \
.start = mctp_uart_start, .tx = mctp_uart_tx, \
}, \
.dev = _dev, \
.rx_state = STATE_WAIT_SYNC_START, \
.rx_pkt = NULL, \
.rx_res = 0, \
.tx_res = 0, \
};
#endif /* ZEPHYR_MCTP_UART_H_ */

View file

@ -46,6 +46,7 @@ add_subdirectory_ifdef(CONFIG_IMG_MANAGER dfu)
add_subdirectory_ifdef(CONFIG_INPUT input) add_subdirectory_ifdef(CONFIG_INPUT input)
add_subdirectory_ifdef(CONFIG_JWT jwt) add_subdirectory_ifdef(CONFIG_JWT jwt)
add_subdirectory_ifdef(CONFIG_LLEXT llext) add_subdirectory_ifdef(CONFIG_LLEXT llext)
add_subdirectory_ifdef(CONFIG_MCTP mctp)
add_subdirectory_ifdef(CONFIG_MODEM_MODULES modem) add_subdirectory_ifdef(CONFIG_MODEM_MODULES modem)
add_subdirectory_ifdef(CONFIG_NETWORKING net) add_subdirectory_ifdef(CONFIG_NETWORKING net)
add_subdirectory_ifdef(CONFIG_PROFILING profiling) add_subdirectory_ifdef(CONFIG_PROFILING profiling)

View file

@ -26,6 +26,7 @@ source "subsys/jwt/Kconfig"
source "subsys/llext/Kconfig" source "subsys/llext/Kconfig"
source "subsys/logging/Kconfig" source "subsys/logging/Kconfig"
source "subsys/lorawan/Kconfig" source "subsys/lorawan/Kconfig"
source "subsys/mctp/Kconfig"
source "subsys/mem_mgmt/Kconfig" source "subsys/mem_mgmt/Kconfig"
source "subsys/mgmt/Kconfig" source "subsys/mgmt/Kconfig"
source "subsys/modbus/Kconfig" source "subsys/modbus/Kconfig"

View file

@ -0,0 +1,2 @@
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_MCTP_UART mctp_uart.c)

22
subsys/mctp/Kconfig Normal file
View file

@ -0,0 +1,22 @@
# Copyright (c) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
menuconfig MCTP
bool "Management Component Transport Protocol"
help
Enable the MCTP Subsystem and Module Usage
if MCTP
config MCTP_UART
bool "MCTP UART Binding"
depends on UART_ASYNC_API
help
Build the MCTP UART binding to use MCTP over Zephyr's async UART
interface.
module = MCTP
module-str = MCTP
source "subsys/logging/Kconfig.template.log_config"
endif

325
subsys/mctp/mctp_uart.c Normal file
View file

@ -0,0 +1,325 @@
/*
* Copyright (c) 2024 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <zephyr/sys/__assert.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/mctp/mctp_uart.h>
#include <crc-16-ccitt.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(mctp_uart, CONFIG_MCTP_LOG_LEVEL);
#define MCTP_UART_REVISION 0x01
#define MCTP_UART_FRAMING_FLAG 0x7e
#define MCTP_UART_ESCAPE 0x7d
const char *UART_EVENT_STRING[] = {
"TX Done", "TX Aborted", "RX Ready", "RX Buffer Request", "RX Buffer Released",
"RX Disabled", "RX Stopped",
};
const char *MCTP_STATE_STRING[] = {
"Wait: Sync Start", "Wait: Revision", "Wait: Len", "Data",
"Data: Escaped", "Wait: FCS1", "Wait: FCS2", "Wait: Sync End",
};
struct mctp_serial_header {
uint8_t flag;
uint8_t revision;
uint8_t len;
};
struct mctp_serial_trailer {
uint8_t fcs_msb;
uint8_t fcs_lsb;
uint8_t flag;
};
static inline struct mctp_binding_uart *binding_to_uart(struct mctp_binding *b)
{
return (struct mctp_binding_uart *)b;
}
static void mctp_uart_finish_pkt(struct mctp_binding_uart *uart, bool valid)
{
struct mctp_pktbuf *pkt = uart->rx_pkt;
if (valid) {
__ASSERT_NO_MSG(pkt);
mctp_bus_rx(&uart->binding, pkt);
}
uart->rx_pkt = NULL;
}
static void mctp_uart_start_pkt(struct mctp_binding_uart *uart, uint8_t len)
{
__ASSERT_NO_MSG(uart->rx_pkt == NULL);
uart->rx_pkt = mctp_pktbuf_alloc(&uart->binding, len);
__ASSERT_NO_MSG(uart->rx_pkt);
}
static size_t mctp_uart_pkt_escape(struct mctp_pktbuf *pkt, uint8_t *buf)
{
uint8_t total_len;
uint8_t *p;
int i, j;
total_len = pkt->end - pkt->mctp_hdr_off;
p = (void *)mctp_pktbuf_hdr(pkt);
for (i = 0, j = 0; i < total_len; i++, j++) {
uint8_t c = p[i];
if (c == MCTP_UART_FRAMING_FLAG || c == MCTP_UART_ESCAPE) {
if (buf) {
buf[j] = MCTP_UART_ESCAPE;
}
j++;
c ^= 0x20;
}
if (buf) {
buf[j] = c;
}
}
return j;
}
/*
* Each byte coming from the uart is run through this state machine which
* does the MCTP packet decoding.
*
* The actual packet and buffer being read into is owned by the binding!
*/
static void mctp_uart_consume(struct mctp_binding_uart *uart, uint8_t c)
{
struct mctp_pktbuf *pkt = uart->rx_pkt;
bool valid = false;
LOG_DBG("uart consume start state: %d:%s, char 0x%02x", uart->rx_state,
MCTP_STATE_STRING[uart->rx_state], c);
__ASSERT_NO_MSG(!pkt == (uart->rx_state == STATE_WAIT_SYNC_START ||
uart->rx_state == STATE_WAIT_REVISION ||
uart->rx_state == STATE_WAIT_LEN));
switch (uart->rx_state) {
case STATE_WAIT_SYNC_START:
if (c != MCTP_UART_FRAMING_FLAG) {
LOG_DBG("lost sync, dropping packet");
if (pkt) {
mctp_uart_finish_pkt(uart, false);
}
} else {
uart->rx_state = STATE_WAIT_REVISION;
}
break;
case STATE_WAIT_REVISION:
if (c == MCTP_UART_REVISION) {
uart->rx_state = STATE_WAIT_LEN;
uart->rx_fcs_calc = crc_16_ccitt_byte(FCS_INIT_16, c);
} else if (c == MCTP_UART_FRAMING_FLAG) {
/* Handle the case where there are bytes dropped in request,
* and the state machine is out of sync. The failed request's
* trailing footer i.e. 0x7e would be interpreted as next
* request's framing footer. So if we are in STATE_WAIT_REVISION
* and receive 0x7e byte, then continue to stay in
* STATE_WAIT_REVISION
*/
LOG_DBG("Received serial framing flag 0x%02x while waiting"
" for serial revision 0x%02x.",
c, MCTP_UART_REVISION);
} else {
LOG_DBG("invalid revision 0x%02x", c);
uart->rx_state = STATE_WAIT_SYNC_START;
}
break;
case STATE_WAIT_LEN:
if (c > uart->binding.pkt_size || c < sizeof(struct mctp_hdr)) {
LOG_DBG("invalid size %d", c);
uart->rx_state = STATE_WAIT_SYNC_START;
} else {
mctp_uart_start_pkt(uart, 0);
pkt = uart->rx_pkt;
uart->rx_exp_len = c;
uart->rx_state = STATE_DATA;
uart->rx_fcs_calc = crc_16_ccitt_byte(uart->rx_fcs_calc, c);
}
break;
case STATE_DATA:
if (c == MCTP_UART_ESCAPE) {
uart->rx_state = STATE_DATA_ESCAPED;
} else {
mctp_pktbuf_push(pkt, &c, 1);
uart->rx_fcs_calc = crc_16_ccitt_byte(uart->rx_fcs_calc, c);
if (pkt->end - pkt->mctp_hdr_off == uart->rx_exp_len) {
uart->rx_state = STATE_WAIT_FCS1;
}
}
break;
case STATE_DATA_ESCAPED:
c ^= 0x20;
mctp_pktbuf_push(pkt, &c, 1);
uart->rx_fcs_calc = crc_16_ccitt_byte(uart->rx_fcs_calc, c);
if (pkt->end - pkt->mctp_hdr_off == uart->rx_exp_len) {
uart->rx_state = STATE_WAIT_FCS1;
} else {
uart->rx_state = STATE_DATA;
}
break;
case STATE_WAIT_FCS1:
uart->rx_fcs = c << 8;
uart->rx_state = STATE_WAIT_FCS2;
break;
case STATE_WAIT_FCS2:
uart->rx_fcs |= c;
uart->rx_state = STATE_WAIT_SYNC_END;
break;
case STATE_WAIT_SYNC_END:
if (uart->rx_fcs == uart->rx_fcs_calc) {
if (c == MCTP_UART_FRAMING_FLAG) {
valid = true;
} else {
valid = false;
LOG_DBG("missing end frame marker");
}
} else {
valid = false;
LOG_DBG("invalid fcs : 0x%04x, expect 0x%04x", uart->rx_fcs,
uart->rx_fcs_calc);
}
mctp_uart_finish_pkt(uart, valid);
uart->rx_state = STATE_WAIT_SYNC_START;
break;
}
LOG_DBG("uart consume end state: %d:%s, char 0x%02x", uart->rx_state,
MCTP_STATE_STRING[uart->rx_state], c);
}
static void mctp_uart_callback(const struct device *dev, struct uart_event *evt, void *userdata)
{
struct mctp_binding_uart *binding = userdata;
switch (evt->type) {
case UART_TX_DONE:
binding->tx_res = 0;
break;
case UART_TX_ABORTED:
binding->tx_res = -EIO;
break;
case UART_RX_RDY:
/* buffer being read into is ready */
binding->rx_res = evt->data.rx.len;
/* parse the buffer */
for (size_t i = 0; i < evt->data.rx.len; i++) {
mctp_uart_consume(binding, evt->data.rx.buf[evt->data.rx.offset + i]);
}
break;
case UART_RX_BUF_REQUEST:
for (int i = 0; i < sizeof(binding->rx_buf_used); i++) {
if (!binding->rx_buf_used[i]) {
binding->rx_buf_used[i] = true;
uart_rx_buf_rsp(dev, binding->rx_buf[i],
sizeof(binding->rx_buf[i]));
break;
}
}
break;
case UART_RX_BUF_RELEASED:
for (int i = 0; i < sizeof(binding->rx_buf_used); i++) {
if (binding->rx_buf[i] == evt->data.rx_buf.buf) {
binding->rx_buf_used[i] = false;
break;
}
}
break;
case UART_RX_STOPPED:
break;
case UART_RX_DISABLED:
break;
}
}
void mctp_uart_start_rx(struct mctp_binding_uart *uart)
{
int res = uart_callback_set(uart->dev, mctp_uart_callback, uart);
__ASSERT_NO_MSG(res == 0);
uart->rx_buf_used[0] = true;
res = uart_rx_enable(uart->dev, uart->rx_buf[0], sizeof(uart->rx_buf[0]), 1000);
__ASSERT_NO_MSG(res == 0);
}
int mctp_uart_tx(struct mctp_binding *b, struct mctp_pktbuf *pkt)
{
struct mctp_binding_uart *uart = binding_to_uart(b);
struct mctp_serial_header *hdr;
struct mctp_serial_trailer *tlr;
uint8_t *buf;
size_t len;
uint16_t fcs;
LOG_DBG("uart tx pkt %p", pkt);
/* the length field in the header excludes serial framing
* and escape sequences
*/
len = mctp_pktbuf_size(pkt);
hdr = (void *)uart->tx_buf;
hdr->flag = MCTP_UART_FRAMING_FLAG;
hdr->revision = MCTP_UART_REVISION;
hdr->len = len;
/* Calculate fcs */
fcs = crc_16_ccitt(FCS_INIT_16, (const uint8_t *)hdr + 1, 2);
fcs = crc_16_ccitt(fcs, (const uint8_t *)mctp_pktbuf_hdr(pkt), len);
LOG_DBG("calculated crc %d", fcs);
buf = (void *)(hdr + 1);
len = mctp_uart_pkt_escape(pkt, NULL);
if (len + sizeof(*hdr) + sizeof(*tlr) > sizeof(uart->tx_buf)) {
return -EMSGSIZE;
}
mctp_uart_pkt_escape(pkt, buf);
buf += len;
tlr = (void *)buf;
tlr->flag = MCTP_UART_FRAMING_FLAG;
tlr->fcs_msb = fcs >> 8;
tlr->fcs_lsb = fcs & 0xff;
len += sizeof(*hdr) + sizeof(*tlr);
int res = uart_tx(uart->dev, (const uint8_t *)uart->tx_buf, len, SYS_FOREVER_US);
if (res != 0) {
LOG_ERR("Failed sending data, %d", res);
return res;
}
return uart->tx_res;
}
int mctp_uart_start(struct mctp_binding *binding)
{
mctp_binding_set_tx_enabled(binding, true);
return 0;
}