You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
7.8 KiB
325 lines
7.8 KiB
/* |
|
* 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/pmci/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; |
|
}
|
|
|