zephyr/drivers/ethernet/oa_tc6.c
Lukasz Majewski b0e0bbfe5d drivers: ethernet: tc6: Combine read chunks into continuous net buffer
Up till now the size of net buffer chunk was set to only 64B. This
approach was acceptable for IPv4 support as all headers would fit into
64B of allocated continuous memory.

With enabled support for IPv6 one would observe following errors when
Neighbor Discovery [ND] is performed:

net_pkt: Uncontiguous data cannot be linearized
net_ipv6_nd: DROP: NULL NA header
net_icmpv6: ICMPv6 handling failure (-5)

As some IPv6 headers span on multiple 64B net_pkt buffer instances.

To fix this error - the received chunks are stored to large enough single
net_pkt buffer fragment.

Signed-off-by: Stefan Bigler <linux@bigler.io>
Signed-off-by: Lukasz Majewski <lukma@denx.de>
2024-08-08 20:21:58 -04:00

452 lines
10 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);
/*
* When IPv6 support enabled - the minimal size of network buffer
* shall be at least 128 bytes (i.e. default value).
*/
#if defined(CONFIG_NET_IPV6) && (CONFIG_NET_BUF_DATA_SIZE < 128)
#error IPv6 requires at least 128 bytes of continuous data to handle headers!
#endif
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)
{
const uint16_t buf_rx_size = CONFIG_NET_BUF_DATA_SIZE;
struct net_buf *buf_rx = NULL;
uint32_t buf_rx_used = 0;
uint32_t hdr, ftr;
uint8_t sbo, ebo;
int ret;
/*
* Special case - append already received data (extracted from previous
* chunk) to new packet.
*
* This code is NOT used when OA_CONFIG0 RFA [13:12] is set to 01
* (ZAREFE) - so received ethernet frames will always start on the
* beginning of new chunks.
*/
if (tc6->concat_buf) {
net_pkt_append_buffer(pkt, tc6->concat_buf);
tc6->concat_buf = NULL;
}
do {
if (!buf_rx) {
buf_rx = net_pkt_get_frag(pkt, buf_rx_size, 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 + buf_rx_used, 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);
}
}
}
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_used += ebo;
buf_rx->len = buf_rx_used;
net_pkt_append_buffer(pkt, buf_rx);
/*
* Exit when complete packet is read and added to
* struct net_pkt
*/
break;
} else {
buf_rx_used += tc6->cps;
if ((buf_rx_size - buf_rx_used) < tc6->cps) {
net_pkt_append_buffer(pkt, buf_rx);
buf_rx->len = buf_rx_used;
buf_rx_used = 0;
buf_rx = NULL;
}
}
} while (tc6->rca > 0);
return 0;
unref_buf:
net_buf_unref(buf_rx);
return ret;
}