zephyr/drivers/ethernet/oa_tc6.c
Lukasz Majewski 4903ec7478 drivers: ethernet: tc6: Check footer parity before updating struct oa_tc6
The parity of the received footer from data transfer (also including the
NORX) shall be checked before members of struct tc6 are updated.

This prevents from updating the driver's crucial metadata (i.e. struct
oa_tc6) with malformed values and informs the upper layers of the driver
that error has been detected.

Signed-off-by: Lukasz Majewski <lukma@denx.de>
2023-12-19 08:51:27 +01:00

429 lines
9.8 KiB
C

/*
* Copyright (c) 2023 DENX Software Engineering GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "oa_tc6.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(oa_tc6, CONFIG_ETHERNET_LOG_LEVEL);
int oa_tc6_reg_read(struct oa_tc6 *tc6, const uint32_t reg, uint32_t *val)
{
uint8_t buf[OA_TC6_HDR_SIZE + 12] = { 0 };
struct spi_buf tx_buf = { .buf = buf, .len = sizeof(buf) };
const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1 };
struct spi_buf rx_buf = { .buf = buf, .len = sizeof(buf) };
const struct spi_buf_set rx = { .buffers = &rx_buf, .count = 1 };
uint32_t rv, rvn, hdr_bkp, *hdr = (uint32_t *) &buf[0];
int ret = 0;
/*
* Buffers are allocated for protected (larger) case (by 4 bytes).
* When non-protected case - we need to decrase them
*/
if (!tc6->protected) {
tx_buf.len -= sizeof(rvn);
rx_buf.len -= sizeof(rvn);
}
*hdr = FIELD_PREP(OA_CTRL_HDR_DNC, 0) |
FIELD_PREP(OA_CTRL_HDR_WNR, 0) |
FIELD_PREP(OA_CTRL_HDR_AID, 0) |
FIELD_PREP(OA_CTRL_HDR_MMS, reg >> 16) |
FIELD_PREP(OA_CTRL_HDR_ADDR, reg) |
FIELD_PREP(OA_CTRL_HDR_LEN, 0); /* To read single register len = 0 */
*hdr |= FIELD_PREP(OA_CTRL_HDR_P, oa_tc6_get_parity(*hdr));
hdr_bkp = *hdr;
*hdr = sys_cpu_to_be32(*hdr);
ret = spi_transceive_dt(tc6->spi, &tx, &rx);
if (ret < 0) {
return ret;
}
/* Check if echoed control command header is correct */
rv = sys_be32_to_cpu(*(uint32_t *)&buf[4]);
if (hdr_bkp != rv) {
LOG_ERR("Header transmission error!");
return -1;
}
rv = sys_be32_to_cpu(*(uint32_t *)&buf[8]);
/* In protected mode read data is followed by its compliment value */
if (tc6->protected) {
rvn = sys_be32_to_cpu(*(uint32_t *)&buf[12]);
if (rv != ~rvn) {
LOG_ERR("Protected mode transmission error!");
return -1;
}
}
*val = rv;
return ret;
}
int oa_tc6_reg_write(struct oa_tc6 *tc6, const uint32_t reg, uint32_t val)
{
uint8_t buf_tx[OA_TC6_HDR_SIZE + 12] = { 0 };
uint8_t buf_rx[OA_TC6_HDR_SIZE + 12] = { 0 };
struct spi_buf tx_buf = { .buf = buf_tx, .len = sizeof(buf_tx) };
const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1 };
struct spi_buf rx_buf = { .buf = buf_rx, .len = sizeof(buf_rx) };
const struct spi_buf_set rx = { .buffers = &rx_buf, .count = 1 };
uint32_t rv, rvn, hdr_bkp, *hdr = (uint32_t *) &buf_tx[0];
int ret;
/*
* Buffers are allocated for protected (larger) case (by 4 bytes).
* When non-protected case - we need to decrase them
*/
if (!tc6->protected) {
tx_buf.len -= sizeof(rvn);
rx_buf.len -= sizeof(rvn);
}
*hdr = FIELD_PREP(OA_CTRL_HDR_DNC, 0) |
FIELD_PREP(OA_CTRL_HDR_WNR, 1) |
FIELD_PREP(OA_CTRL_HDR_AID, 0) |
FIELD_PREP(OA_CTRL_HDR_MMS, reg >> 16) |
FIELD_PREP(OA_CTRL_HDR_ADDR, reg) |
FIELD_PREP(OA_CTRL_HDR_LEN, 0); /* To read single register len = 0 */
*hdr |= FIELD_PREP(OA_CTRL_HDR_P, oa_tc6_get_parity(*hdr));
hdr_bkp = *hdr;
*hdr = sys_cpu_to_be32(*hdr);
*(uint32_t *)&buf_tx[4] = sys_cpu_to_be32(val);
if (tc6->protected) {
*(uint32_t *)&buf_tx[8] = sys_be32_to_cpu(~val);
}
ret = spi_transceive_dt(tc6->spi, &tx, &rx);
if (ret < 0) {
return ret;
}
/* Check if echoed control command header is correct */
rv = sys_be32_to_cpu(*(uint32_t *)&buf_rx[4]);
if (hdr_bkp != rv) {
LOG_ERR("Header transmission error!");
return -1;
}
/* Check if echoed value is correct */
rv = sys_be32_to_cpu(*(uint32_t *)&buf_rx[8]);
if (val != rv) {
LOG_ERR("Header transmission error!");
return -1;
}
/*
* In protected mode check if read value is followed by its
* compliment value
*/
if (tc6->protected) {
rvn = sys_be32_to_cpu(*(uint32_t *)&buf_rx[12]);
if (val != ~rvn) {
LOG_ERR("Protected mode transmission error!");
return -1;
}
}
return ret;
}
int oa_tc6_reg_rmw(struct oa_tc6 *tc6, const uint32_t reg,
uint32_t mask, uint32_t val)
{
uint32_t tmp;
int ret;
ret = oa_tc6_reg_read(tc6, reg, &tmp);
if (ret < 0) {
return ret;
}
tmp &= ~mask;
if (val) {
tmp |= val;
}
return oa_tc6_reg_write(tc6, reg, tmp);
}
int oa_tc6_set_protected_ctrl(struct oa_tc6 *tc6, bool prote)
{
int ret = oa_tc6_reg_rmw(tc6, OA_CONFIG0, OA_CONFIG0_PROTE,
prote ? OA_CONFIG0_PROTE : 0);
if (ret < 0) {
return ret;
}
tc6->protected = prote;
return 0;
}
int oa_tc6_send_chunks(struct oa_tc6 *tc6, struct net_pkt *pkt)
{
uint16_t len = net_pkt_get_len(pkt);
uint8_t oa_tx[tc6->cps];
uint32_t hdr, ftr;
uint8_t chunks, i;
int ret;
if (len == 0) {
return -ENODATA;
}
chunks = len / tc6->cps;
if (len % tc6->cps) {
chunks++;
}
/* Check if LAN865x has any free internal buffer space */
if (chunks > tc6->txc) {
return -EIO;
}
/* Transform struct net_pkt content into chunks */
for (i = 1; i <= chunks; i++) {
hdr = FIELD_PREP(OA_DATA_HDR_DNC, 1) |
FIELD_PREP(OA_DATA_HDR_DV, 1) |
FIELD_PREP(OA_DATA_HDR_NORX, 1) |
FIELD_PREP(OA_DATA_HDR_SWO, 0);
if (i == 1) {
hdr |= FIELD_PREP(OA_DATA_HDR_SV, 1);
}
if (i == chunks) {
hdr |= FIELD_PREP(OA_DATA_HDR_EBO, len - 1) |
FIELD_PREP(OA_DATA_HDR_EV, 1);
}
hdr |= FIELD_PREP(OA_DATA_HDR_P, oa_tc6_get_parity(hdr));
ret = net_pkt_read(pkt, oa_tx, len > tc6->cps ? tc6->cps : len);
if (ret < 0) {
return ret;
}
ret = oa_tc6_chunk_spi_transfer(tc6, NULL, oa_tx, hdr, &ftr);
if (ret < 0) {
return ret;
}
len -= tc6->cps;
}
return 0;
}
int oa_tc6_check_status(struct oa_tc6 *tc6)
{
uint32_t sts;
if (!tc6->sync) {
LOG_ERR("SYNC: Configuration lost, reset IC!");
return -EIO;
}
if (tc6->exst) {
/*
* Just clear any pending interrupts.
* The RESETC is handled separately as it requires per
* device configuration.
*/
oa_tc6_reg_read(tc6, OA_STATUS0, &sts);
if (sts != 0) {
oa_tc6_reg_write(tc6, OA_STATUS0, sts);
LOG_WRN("EXST: OA_STATUS0: 0x%x", sts);
}
oa_tc6_reg_read(tc6, OA_STATUS1, &sts);
if (sts != 0) {
oa_tc6_reg_write(tc6, OA_STATUS1, sts);
LOG_WRN("EXST: OA_STATUS1: 0x%x", sts);
}
}
return 0;
}
static int oa_tc6_update_status(struct oa_tc6 *tc6, uint32_t ftr)
{
if (oa_tc6_get_parity(ftr)) {
LOG_DBG("OA Status Update: Footer parity error!");
return -EIO;
}
tc6->exst = FIELD_GET(OA_DATA_FTR_EXST, ftr);
tc6->sync = FIELD_GET(OA_DATA_FTR_SYNC, ftr);
tc6->rca = FIELD_GET(OA_DATA_FTR_RCA, ftr);
tc6->txc = FIELD_GET(OA_DATA_FTR_TXC, ftr);
return 0;
}
int oa_tc6_chunk_spi_transfer(struct oa_tc6 *tc6, uint8_t *buf_rx, uint8_t *buf_tx,
uint32_t hdr, uint32_t *ftr)
{
struct spi_buf tx_buf[2];
struct spi_buf rx_buf[2];
struct spi_buf_set tx;
struct spi_buf_set rx;
int ret;
hdr = sys_cpu_to_be32(hdr);
tx_buf[0].buf = &hdr;
tx_buf[0].len = sizeof(hdr);
tx_buf[1].buf = buf_tx;
tx_buf[1].len = tc6->cps;
tx.buffers = tx_buf;
tx.count = ARRAY_SIZE(tx_buf);
rx_buf[0].buf = buf_rx;
rx_buf[0].len = tc6->cps;
rx_buf[1].buf = ftr;
rx_buf[1].len = sizeof(*ftr);
rx.buffers = rx_buf;
rx.count = ARRAY_SIZE(rx_buf);
ret = spi_transceive_dt(tc6->spi, &tx, &rx);
if (ret < 0) {
return ret;
}
*ftr = sys_be32_to_cpu(*ftr);
return oa_tc6_update_status(tc6, *ftr);
}
int oa_tc6_read_status(struct oa_tc6 *tc6, uint32_t *ftr)
{
uint32_t hdr;
hdr = FIELD_PREP(OA_DATA_HDR_DNC, 1) |
FIELD_PREP(OA_DATA_HDR_DV, 0) |
FIELD_PREP(OA_DATA_HDR_NORX, 1);
hdr |= FIELD_PREP(OA_DATA_HDR_P, oa_tc6_get_parity(hdr));
return oa_tc6_chunk_spi_transfer(tc6, NULL, NULL, hdr, ftr);
}
int oa_tc6_read_chunks(struct oa_tc6 *tc6, struct net_pkt *pkt)
{
struct net_buf *buf_rx = NULL;
uint32_t hdr, ftr;
uint8_t sbo, ebo;
int ret;
/*
* Special case - append already received data (extracted from previous
* chunk) to new packet.
*/
if (tc6->concat_buf) {
net_pkt_append_buffer(pkt, tc6->concat_buf);
tc6->concat_buf = NULL;
}
do {
buf_rx = net_pkt_get_frag(pkt, tc6->cps, OA_TC6_BUF_ALLOC_TIMEOUT);
if (!buf_rx) {
LOG_ERR("OA RX: Can't allocate RX buffer fordata!");
return -ENOMEM;
}
hdr = FIELD_PREP(OA_DATA_HDR_DNC, 1);
hdr |= FIELD_PREP(OA_DATA_HDR_P, oa_tc6_get_parity(hdr));
ret = oa_tc6_chunk_spi_transfer(tc6, buf_rx->data, NULL, hdr, &ftr);
if (ret < 0) {
LOG_ERR("OA RX: transmission error: %d!", ret);
goto unref_buf;
}
ret = -EIO;
if (oa_tc6_get_parity(ftr)) {
LOG_ERR("OA RX: Footer parity error!");
goto unref_buf;
}
if (!FIELD_GET(OA_DATA_FTR_SYNC, ftr)) {
LOG_ERR("OA RX: Configuration not SYNC'ed!");
goto unref_buf;
}
if (!FIELD_GET(OA_DATA_FTR_DV, ftr)) {
LOG_DBG("OA RX: Data chunk not valid, skip!");
goto unref_buf;
}
sbo = FIELD_GET(OA_DATA_FTR_SWO, ftr) * sizeof(uint32_t);
ebo = FIELD_GET(OA_DATA_FTR_EBO, ftr) + 1;
if (FIELD_GET(OA_DATA_FTR_SV, ftr)) {
/*
* Adjust beginning of the buffer with SWO only when
* we DO NOT have two frames concatenated together
* in one chunk.
*/
if (!(FIELD_GET(OA_DATA_FTR_EV, ftr) && (ebo <= sbo))) {
if (sbo) {
net_buf_pull(buf_rx, sbo);
}
}
}
net_pkt_append_buffer(pkt, buf_rx);
buf_rx->len = tc6->cps;
if (FIELD_GET(OA_DATA_FTR_EV, ftr)) {
/*
* Check if received frame shall be dropped - i.e. MAC has
* detected error condition, which shall result in frame drop
* by the SPI host.
*/
if (FIELD_GET(OA_DATA_FTR_FD, ftr)) {
ret = -EIO;
goto unref_buf;
}
/*
* Concatenation of frames in a single chunk - one frame ends
* and second one starts just afterwards (ebo == sbo).
*/
if (FIELD_GET(OA_DATA_FTR_SV, ftr) && (ebo <= sbo)) {
tc6->concat_buf = net_buf_clone(buf_rx, OA_TC6_BUF_ALLOC_TIMEOUT);
if (!tc6->concat_buf) {
LOG_ERR("OA RX: Can't allocate RX buffer for data!");
ret = -ENOMEM;
goto unref_buf;
}
net_buf_pull(tc6->concat_buf, sbo);
}
/* Set final size of the buffer */
buf_rx->len = ebo;
/*
* Exit when complete packet is read and added to
* struct net_pkt
*/
break;
}
} while (tc6->rca > 0);
return 0;
unref_buf:
net_buf_unref(buf_rx);
return ret;
}