linux/drivers/net/ethernet/marvell/prestera/prestera_rxtx.c
Volodymyr Mytnyk bb5dbf2cc6 net: marvell: prestera: add firmware v4.0 support
Add firmware (FW) version 4.0 support for Marvell Prestera
driver.

Major changes have been made to new v4.0 FW ABI to add support
of new features, introduce the stability of the FW ABI and ensure
better forward compatibility for the future driver vesrions.

Current v4.0 FW feature set support does not expect any changes
to ABI, as it was defined and tested through long period of time.
The ABI may be extended in case of new features, but it will not
break the backward compatibility.

ABI major changes done in v4.0:
- L1 ABI, where MAC and PHY API configuration are split.
- ACL has been split to low-level TCAM and Counters ABI
  to provide more HW ACL capabilities for future driver
  versions.

To support backward support, the addition compatibility layer is
required in the driver which will have two different codebase under
"if FW-VER elif FW-VER else" conditions that will be removed
in the future anyway, So, the idea was to break backward support
and focus on more stable FW instead of supporting old version
with very minimal and limited set of features/capabilities.

Improve FW msg validation:
 * Use __le64, __le32, __le16 types in msg to/from FW to
   catch endian mismatch by sparse.
 * Use BUILD_BUG_ON for structures sent/recv to/from FW.

Co-developed-by: Vadym Kochan <vkochan@marvell.com>
Signed-off-by: Vadym Kochan <vkochan@marvell.com>
Signed-off-by: Yevhen Orlov <yevhen.orlov@plvision.eu>
Signed-off-by: Taras Chornyi <tchornyi@marvell.com>
Signed-off-by: Volodymyr Mytnyk <vmytnyk@marvell.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2021-10-29 13:55:29 +01:00

819 lines
18 KiB
C

// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved */
#include <linux/bitfield.h>
#include <linux/dmapool.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include "prestera_dsa.h"
#include "prestera.h"
#include "prestera_hw.h"
#include "prestera_rxtx.h"
#include "prestera_devlink.h"
#define PRESTERA_SDMA_WAIT_MUL 10
struct prestera_sdma_desc {
__le32 word1;
__le32 word2;
__le32 buff;
__le32 next;
} __packed __aligned(16);
#define PRESTERA_SDMA_BUFF_SIZE_MAX 1544
#define PRESTERA_SDMA_RX_DESC_PKT_LEN(desc) \
((le32_to_cpu((desc)->word2) >> 16) & GENMASK(13, 0))
#define PRESTERA_SDMA_RX_DESC_OWNER(desc) \
((le32_to_cpu((desc)->word1) & BIT(31)) >> 31)
#define PRESTERA_SDMA_RX_DESC_IS_RCVD(desc) \
(PRESTERA_SDMA_RX_DESC_OWNER(desc) == PRESTERA_SDMA_RX_DESC_CPU_OWN)
#define PRESTERA_SDMA_RX_DESC_CPU_OWN 0
#define PRESTERA_SDMA_RX_DESC_DMA_OWN 1
#define PRESTERA_SDMA_RX_QUEUE_NUM 8
#define PRESTERA_SDMA_RX_DESC_PER_Q 1000
#define PRESTERA_SDMA_TX_DESC_PER_Q 1000
#define PRESTERA_SDMA_TX_MAX_BURST 64
#define PRESTERA_SDMA_TX_DESC_OWNER(desc) \
((le32_to_cpu((desc)->word1) & BIT(31)) >> 31)
#define PRESTERA_SDMA_TX_DESC_CPU_OWN 0
#define PRESTERA_SDMA_TX_DESC_DMA_OWN 1U
#define PRESTERA_SDMA_TX_DESC_IS_SENT(desc) \
(PRESTERA_SDMA_TX_DESC_OWNER(desc) == PRESTERA_SDMA_TX_DESC_CPU_OWN)
#define PRESTERA_SDMA_TX_DESC_LAST BIT(20)
#define PRESTERA_SDMA_TX_DESC_FIRST BIT(21)
#define PRESTERA_SDMA_TX_DESC_CALC_CRC BIT(12)
#define PRESTERA_SDMA_TX_DESC_SINGLE \
(PRESTERA_SDMA_TX_DESC_FIRST | PRESTERA_SDMA_TX_DESC_LAST)
#define PRESTERA_SDMA_TX_DESC_INIT \
(PRESTERA_SDMA_TX_DESC_SINGLE | PRESTERA_SDMA_TX_DESC_CALC_CRC)
#define PRESTERA_SDMA_RX_INTR_MASK_REG 0x2814
#define PRESTERA_SDMA_RX_QUEUE_STATUS_REG 0x2680
#define PRESTERA_SDMA_RX_QUEUE_DESC_REG(n) (0x260C + (n) * 16)
#define PRESTERA_SDMA_TX_QUEUE_DESC_REG 0x26C0
#define PRESTERA_SDMA_TX_QUEUE_START_REG 0x2868
struct prestera_sdma_buf {
struct prestera_sdma_desc *desc;
dma_addr_t desc_dma;
struct sk_buff *skb;
dma_addr_t buf_dma;
bool is_used;
};
struct prestera_rx_ring {
struct prestera_sdma_buf *bufs;
int next_rx;
};
struct prestera_tx_ring {
struct prestera_sdma_buf *bufs;
int next_tx;
int max_burst;
int burst;
};
struct prestera_sdma {
struct prestera_rx_ring rx_ring[PRESTERA_SDMA_RX_QUEUE_NUM];
struct prestera_tx_ring tx_ring;
struct prestera_switch *sw;
struct dma_pool *desc_pool;
struct work_struct tx_work;
struct napi_struct rx_napi;
struct net_device napi_dev;
u32 map_addr;
u64 dma_mask;
/* protect SDMA with concurrrent access from multiple CPUs */
spinlock_t tx_lock;
};
struct prestera_rxtx {
struct prestera_sdma sdma;
};
static int prestera_sdma_buf_init(struct prestera_sdma *sdma,
struct prestera_sdma_buf *buf)
{
struct prestera_sdma_desc *desc;
dma_addr_t dma;
desc = dma_pool_alloc(sdma->desc_pool, GFP_DMA | GFP_KERNEL, &dma);
if (!desc)
return -ENOMEM;
buf->buf_dma = DMA_MAPPING_ERROR;
buf->desc_dma = dma;
buf->desc = desc;
buf->skb = NULL;
return 0;
}
static u32 prestera_sdma_map(struct prestera_sdma *sdma, dma_addr_t pa)
{
return sdma->map_addr + pa;
}
static void prestera_sdma_rx_desc_init(struct prestera_sdma *sdma,
struct prestera_sdma_desc *desc,
dma_addr_t buf)
{
u32 word = le32_to_cpu(desc->word2);
u32p_replace_bits(&word, PRESTERA_SDMA_BUFF_SIZE_MAX, GENMASK(15, 0));
desc->word2 = cpu_to_le32(word);
desc->buff = cpu_to_le32(prestera_sdma_map(sdma, buf));
/* make sure buffer is set before reset the descriptor */
wmb();
desc->word1 = cpu_to_le32(0xA0000000);
}
static void prestera_sdma_rx_desc_set_next(struct prestera_sdma *sdma,
struct prestera_sdma_desc *desc,
dma_addr_t next)
{
desc->next = cpu_to_le32(prestera_sdma_map(sdma, next));
}
static int prestera_sdma_rx_skb_alloc(struct prestera_sdma *sdma,
struct prestera_sdma_buf *buf)
{
struct device *dev = sdma->sw->dev->dev;
struct sk_buff *skb;
dma_addr_t dma;
skb = alloc_skb(PRESTERA_SDMA_BUFF_SIZE_MAX, GFP_DMA | GFP_ATOMIC);
if (!skb)
return -ENOMEM;
dma = dma_map_single(dev, skb->data, skb->len, DMA_FROM_DEVICE);
if (dma_mapping_error(dev, dma))
goto err_dma_map;
if (buf->skb)
dma_unmap_single(dev, buf->buf_dma, buf->skb->len,
DMA_FROM_DEVICE);
buf->buf_dma = dma;
buf->skb = skb;
return 0;
err_dma_map:
kfree_skb(skb);
return -ENOMEM;
}
static struct sk_buff *prestera_sdma_rx_skb_get(struct prestera_sdma *sdma,
struct prestera_sdma_buf *buf)
{
dma_addr_t buf_dma = buf->buf_dma;
struct sk_buff *skb = buf->skb;
u32 len = skb->len;
int err;
err = prestera_sdma_rx_skb_alloc(sdma, buf);
if (err) {
buf->buf_dma = buf_dma;
buf->skb = skb;
skb = alloc_skb(skb->len, GFP_ATOMIC);
if (skb) {
skb_put(skb, len);
skb_copy_from_linear_data(buf->skb, skb->data, len);
}
}
prestera_sdma_rx_desc_init(sdma, buf->desc, buf->buf_dma);
return skb;
}
static int prestera_rxtx_process_skb(struct prestera_sdma *sdma,
struct sk_buff *skb)
{
struct prestera_port *port;
struct prestera_dsa dsa;
u32 hw_port, dev_id;
u8 cpu_code;
int err;
skb_pull(skb, ETH_HLEN);
/* ethertype field is part of the dsa header */
err = prestera_dsa_parse(&dsa, skb->data - ETH_TLEN);
if (err)
return err;
dev_id = dsa.hw_dev_num;
hw_port = dsa.port_num;
port = prestera_port_find_by_hwid(sdma->sw, dev_id, hw_port);
if (unlikely(!port)) {
dev_warn_ratelimited(prestera_dev(sdma->sw), "received pkt for non-existent port(%u, %u)\n",
dev_id, hw_port);
return -ENOENT;
}
if (unlikely(!pskb_may_pull(skb, PRESTERA_DSA_HLEN)))
return -EINVAL;
/* remove DSA tag and update checksum */
skb_pull_rcsum(skb, PRESTERA_DSA_HLEN);
memmove(skb->data - ETH_HLEN, skb->data - ETH_HLEN - PRESTERA_DSA_HLEN,
ETH_ALEN * 2);
skb_push(skb, ETH_HLEN);
skb->protocol = eth_type_trans(skb, port->dev);
if (dsa.vlan.is_tagged) {
u16 tci = dsa.vlan.vid & VLAN_VID_MASK;
tci |= dsa.vlan.vpt << VLAN_PRIO_SHIFT;
if (dsa.vlan.cfi_bit)
tci |= VLAN_CFI_MASK;
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tci);
}
cpu_code = dsa.cpu_code;
prestera_devlink_trap_report(port, skb, cpu_code);
return 0;
}
static int prestera_sdma_next_rx_buf_idx(int buf_idx)
{
return (buf_idx + 1) % PRESTERA_SDMA_RX_DESC_PER_Q;
}
static int prestera_sdma_rx_poll(struct napi_struct *napi, int budget)
{
int qnum = PRESTERA_SDMA_RX_QUEUE_NUM;
unsigned int rxq_done_map = 0;
struct prestera_sdma *sdma;
struct list_head rx_list;
unsigned int qmask;
int pkts_done = 0;
int q;
qnum = PRESTERA_SDMA_RX_QUEUE_NUM;
qmask = GENMASK(qnum - 1, 0);
INIT_LIST_HEAD(&rx_list);
sdma = container_of(napi, struct prestera_sdma, rx_napi);
while (pkts_done < budget && rxq_done_map != qmask) {
for (q = 0; q < qnum && pkts_done < budget; q++) {
struct prestera_rx_ring *ring = &sdma->rx_ring[q];
struct prestera_sdma_desc *desc;
struct prestera_sdma_buf *buf;
int buf_idx = ring->next_rx;
struct sk_buff *skb;
buf = &ring->bufs[buf_idx];
desc = buf->desc;
if (PRESTERA_SDMA_RX_DESC_IS_RCVD(desc)) {
rxq_done_map &= ~BIT(q);
} else {
rxq_done_map |= BIT(q);
continue;
}
pkts_done++;
__skb_trim(buf->skb, PRESTERA_SDMA_RX_DESC_PKT_LEN(desc));
skb = prestera_sdma_rx_skb_get(sdma, buf);
if (!skb)
goto rx_next_buf;
if (unlikely(prestera_rxtx_process_skb(sdma, skb)))
goto rx_next_buf;
list_add_tail(&skb->list, &rx_list);
rx_next_buf:
ring->next_rx = prestera_sdma_next_rx_buf_idx(buf_idx);
}
}
if (pkts_done < budget && napi_complete_done(napi, pkts_done))
prestera_write(sdma->sw, PRESTERA_SDMA_RX_INTR_MASK_REG,
GENMASK(9, 2));
netif_receive_skb_list(&rx_list);
return pkts_done;
}
static void prestera_sdma_rx_fini(struct prestera_sdma *sdma)
{
int qnum = PRESTERA_SDMA_RX_QUEUE_NUM;
int q, b;
/* disable all rx queues */
prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_STATUS_REG,
GENMASK(15, 8));
for (q = 0; q < qnum; q++) {
struct prestera_rx_ring *ring = &sdma->rx_ring[q];
if (!ring->bufs)
break;
for (b = 0; b < PRESTERA_SDMA_RX_DESC_PER_Q; b++) {
struct prestera_sdma_buf *buf = &ring->bufs[b];
if (buf->desc_dma)
dma_pool_free(sdma->desc_pool, buf->desc,
buf->desc_dma);
if (!buf->skb)
continue;
if (buf->buf_dma != DMA_MAPPING_ERROR)
dma_unmap_single(sdma->sw->dev->dev,
buf->buf_dma, buf->skb->len,
DMA_FROM_DEVICE);
kfree_skb(buf->skb);
}
}
}
static int prestera_sdma_rx_init(struct prestera_sdma *sdma)
{
int bnum = PRESTERA_SDMA_RX_DESC_PER_Q;
int qnum = PRESTERA_SDMA_RX_QUEUE_NUM;
int err;
int q;
/* disable all rx queues */
prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_STATUS_REG,
GENMASK(15, 8));
for (q = 0; q < qnum; q++) {
struct prestera_sdma_buf *head, *tail, *next, *prev;
struct prestera_rx_ring *ring = &sdma->rx_ring[q];
ring->bufs = kmalloc_array(bnum, sizeof(*head), GFP_KERNEL);
if (!ring->bufs)
return -ENOMEM;
ring->next_rx = 0;
tail = &ring->bufs[bnum - 1];
head = &ring->bufs[0];
next = head;
prev = next;
do {
err = prestera_sdma_buf_init(sdma, next);
if (err)
return err;
err = prestera_sdma_rx_skb_alloc(sdma, next);
if (err)
return err;
prestera_sdma_rx_desc_init(sdma, next->desc,
next->buf_dma);
prestera_sdma_rx_desc_set_next(sdma, prev->desc,
next->desc_dma);
prev = next;
next++;
} while (prev != tail);
/* join tail with head to make a circular list */
prestera_sdma_rx_desc_set_next(sdma, tail->desc, head->desc_dma);
prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_DESC_REG(q),
prestera_sdma_map(sdma, head->desc_dma));
}
/* make sure all rx descs are filled before enabling all rx queues */
wmb();
prestera_write(sdma->sw, PRESTERA_SDMA_RX_QUEUE_STATUS_REG,
GENMASK(7, 0));
return 0;
}
static void prestera_sdma_tx_desc_init(struct prestera_sdma *sdma,
struct prestera_sdma_desc *desc)
{
desc->word1 = cpu_to_le32(PRESTERA_SDMA_TX_DESC_INIT);
desc->word2 = 0;
}
static void prestera_sdma_tx_desc_set_next(struct prestera_sdma *sdma,
struct prestera_sdma_desc *desc,
dma_addr_t next)
{
desc->next = cpu_to_le32(prestera_sdma_map(sdma, next));
}
static void prestera_sdma_tx_desc_set_buf(struct prestera_sdma *sdma,
struct prestera_sdma_desc *desc,
dma_addr_t buf, size_t len)
{
u32 word = le32_to_cpu(desc->word2);
u32p_replace_bits(&word, len + ETH_FCS_LEN, GENMASK(30, 16));
desc->buff = cpu_to_le32(prestera_sdma_map(sdma, buf));
desc->word2 = cpu_to_le32(word);
}
static void prestera_sdma_tx_desc_xmit(struct prestera_sdma_desc *desc)
{
u32 word = le32_to_cpu(desc->word1);
word |= PRESTERA_SDMA_TX_DESC_DMA_OWN << 31;
/* make sure everything is written before enable xmit */
wmb();
desc->word1 = cpu_to_le32(word);
}
static int prestera_sdma_tx_buf_map(struct prestera_sdma *sdma,
struct prestera_sdma_buf *buf,
struct sk_buff *skb)
{
struct device *dma_dev = sdma->sw->dev->dev;
dma_addr_t dma;
dma = dma_map_single(dma_dev, skb->data, skb->len, DMA_TO_DEVICE);
if (dma_mapping_error(dma_dev, dma))
return -ENOMEM;
buf->buf_dma = dma;
buf->skb = skb;
return 0;
}
static void prestera_sdma_tx_buf_unmap(struct prestera_sdma *sdma,
struct prestera_sdma_buf *buf)
{
struct device *dma_dev = sdma->sw->dev->dev;
dma_unmap_single(dma_dev, buf->buf_dma, buf->skb->len, DMA_TO_DEVICE);
}
static void prestera_sdma_tx_recycle_work_fn(struct work_struct *work)
{
int bnum = PRESTERA_SDMA_TX_DESC_PER_Q;
struct prestera_tx_ring *tx_ring;
struct prestera_sdma *sdma;
int b;
sdma = container_of(work, struct prestera_sdma, tx_work);
tx_ring = &sdma->tx_ring;
for (b = 0; b < bnum; b++) {
struct prestera_sdma_buf *buf = &tx_ring->bufs[b];
if (!buf->is_used)
continue;
if (!PRESTERA_SDMA_TX_DESC_IS_SENT(buf->desc))
continue;
prestera_sdma_tx_buf_unmap(sdma, buf);
dev_consume_skb_any(buf->skb);
buf->skb = NULL;
/* make sure everything is cleaned up */
wmb();
buf->is_used = false;
}
}
static int prestera_sdma_tx_init(struct prestera_sdma *sdma)
{
struct prestera_sdma_buf *head, *tail, *next, *prev;
struct prestera_tx_ring *tx_ring = &sdma->tx_ring;
int bnum = PRESTERA_SDMA_TX_DESC_PER_Q;
int err;
INIT_WORK(&sdma->tx_work, prestera_sdma_tx_recycle_work_fn);
spin_lock_init(&sdma->tx_lock);
tx_ring->bufs = kmalloc_array(bnum, sizeof(*head), GFP_KERNEL);
if (!tx_ring->bufs)
return -ENOMEM;
tail = &tx_ring->bufs[bnum - 1];
head = &tx_ring->bufs[0];
next = head;
prev = next;
tx_ring->max_burst = PRESTERA_SDMA_TX_MAX_BURST;
tx_ring->burst = tx_ring->max_burst;
tx_ring->next_tx = 0;
do {
err = prestera_sdma_buf_init(sdma, next);
if (err)
return err;
next->is_used = false;
prestera_sdma_tx_desc_init(sdma, next->desc);
prestera_sdma_tx_desc_set_next(sdma, prev->desc,
next->desc_dma);
prev = next;
next++;
} while (prev != tail);
/* join tail with head to make a circular list */
prestera_sdma_tx_desc_set_next(sdma, tail->desc, head->desc_dma);
/* make sure descriptors are written */
wmb();
prestera_write(sdma->sw, PRESTERA_SDMA_TX_QUEUE_DESC_REG,
prestera_sdma_map(sdma, head->desc_dma));
return 0;
}
static void prestera_sdma_tx_fini(struct prestera_sdma *sdma)
{
struct prestera_tx_ring *ring = &sdma->tx_ring;
int bnum = PRESTERA_SDMA_TX_DESC_PER_Q;
int b;
cancel_work_sync(&sdma->tx_work);
if (!ring->bufs)
return;
for (b = 0; b < bnum; b++) {
struct prestera_sdma_buf *buf = &ring->bufs[b];
if (buf->desc)
dma_pool_free(sdma->desc_pool, buf->desc,
buf->desc_dma);
if (!buf->skb)
continue;
dma_unmap_single(sdma->sw->dev->dev, buf->buf_dma,
buf->skb->len, DMA_TO_DEVICE);
dev_consume_skb_any(buf->skb);
}
}
static void prestera_rxtx_handle_event(struct prestera_switch *sw,
struct prestera_event *evt,
void *arg)
{
struct prestera_sdma *sdma = arg;
if (evt->id != PRESTERA_RXTX_EVENT_RCV_PKT)
return;
prestera_write(sdma->sw, PRESTERA_SDMA_RX_INTR_MASK_REG, 0);
napi_schedule(&sdma->rx_napi);
}
static int prestera_sdma_switch_init(struct prestera_switch *sw)
{
struct prestera_sdma *sdma = &sw->rxtx->sdma;
struct device *dev = sw->dev->dev;
struct prestera_rxtx_params p;
int err;
p.use_sdma = true;
err = prestera_hw_rxtx_init(sw, &p);
if (err) {
dev_err(dev, "failed to init rxtx by hw\n");
return err;
}
sdma->dma_mask = dma_get_mask(dev);
sdma->map_addr = p.map_addr;
sdma->sw = sw;
sdma->desc_pool = dma_pool_create("desc_pool", dev,
sizeof(struct prestera_sdma_desc),
16, 0);
if (!sdma->desc_pool)
return -ENOMEM;
err = prestera_sdma_rx_init(sdma);
if (err) {
dev_err(dev, "failed to init rx ring\n");
goto err_rx_init;
}
err = prestera_sdma_tx_init(sdma);
if (err) {
dev_err(dev, "failed to init tx ring\n");
goto err_tx_init;
}
err = prestera_hw_event_handler_register(sw, PRESTERA_EVENT_TYPE_RXTX,
prestera_rxtx_handle_event,
sdma);
if (err)
goto err_evt_register;
init_dummy_netdev(&sdma->napi_dev);
netif_napi_add(&sdma->napi_dev, &sdma->rx_napi, prestera_sdma_rx_poll, 64);
napi_enable(&sdma->rx_napi);
return 0;
err_evt_register:
err_tx_init:
prestera_sdma_tx_fini(sdma);
err_rx_init:
prestera_sdma_rx_fini(sdma);
dma_pool_destroy(sdma->desc_pool);
return err;
}
static void prestera_sdma_switch_fini(struct prestera_switch *sw)
{
struct prestera_sdma *sdma = &sw->rxtx->sdma;
napi_disable(&sdma->rx_napi);
netif_napi_del(&sdma->rx_napi);
prestera_hw_event_handler_unregister(sw, PRESTERA_EVENT_TYPE_RXTX,
prestera_rxtx_handle_event);
prestera_sdma_tx_fini(sdma);
prestera_sdma_rx_fini(sdma);
dma_pool_destroy(sdma->desc_pool);
}
static bool prestera_sdma_is_ready(struct prestera_sdma *sdma)
{
return !(prestera_read(sdma->sw, PRESTERA_SDMA_TX_QUEUE_START_REG) & 1);
}
static int prestera_sdma_tx_wait(struct prestera_sdma *sdma,
struct prestera_tx_ring *tx_ring)
{
int tx_wait_num = PRESTERA_SDMA_WAIT_MUL * tx_ring->max_burst;
do {
if (prestera_sdma_is_ready(sdma))
return 0;
udelay(1);
} while (--tx_wait_num);
return -EBUSY;
}
static void prestera_sdma_tx_start(struct prestera_sdma *sdma)
{
prestera_write(sdma->sw, PRESTERA_SDMA_TX_QUEUE_START_REG, 1);
schedule_work(&sdma->tx_work);
}
static netdev_tx_t prestera_sdma_xmit(struct prestera_sdma *sdma,
struct sk_buff *skb)
{
struct device *dma_dev = sdma->sw->dev->dev;
struct net_device *dev = skb->dev;
struct prestera_tx_ring *tx_ring;
struct prestera_sdma_buf *buf;
int err;
spin_lock(&sdma->tx_lock);
tx_ring = &sdma->tx_ring;
buf = &tx_ring->bufs[tx_ring->next_tx];
if (buf->is_used) {
schedule_work(&sdma->tx_work);
goto drop_skb;
}
if (unlikely(eth_skb_pad(skb)))
goto drop_skb_nofree;
err = prestera_sdma_tx_buf_map(sdma, buf, skb);
if (err)
goto drop_skb;
prestera_sdma_tx_desc_set_buf(sdma, buf->desc, buf->buf_dma, skb->len);
dma_sync_single_for_device(dma_dev, buf->buf_dma, skb->len,
DMA_TO_DEVICE);
if (tx_ring->burst) {
tx_ring->burst--;
} else {
tx_ring->burst = tx_ring->max_burst;
err = prestera_sdma_tx_wait(sdma, tx_ring);
if (err)
goto drop_skb_unmap;
}
tx_ring->next_tx = (tx_ring->next_tx + 1) % PRESTERA_SDMA_TX_DESC_PER_Q;
prestera_sdma_tx_desc_xmit(buf->desc);
buf->is_used = true;
prestera_sdma_tx_start(sdma);
goto tx_done;
drop_skb_unmap:
prestera_sdma_tx_buf_unmap(sdma, buf);
drop_skb:
dev_consume_skb_any(skb);
drop_skb_nofree:
dev->stats.tx_dropped++;
tx_done:
spin_unlock(&sdma->tx_lock);
return NETDEV_TX_OK;
}
int prestera_rxtx_switch_init(struct prestera_switch *sw)
{
struct prestera_rxtx *rxtx;
rxtx = kzalloc(sizeof(*rxtx), GFP_KERNEL);
if (!rxtx)
return -ENOMEM;
sw->rxtx = rxtx;
return prestera_sdma_switch_init(sw);
}
void prestera_rxtx_switch_fini(struct prestera_switch *sw)
{
prestera_sdma_switch_fini(sw);
kfree(sw->rxtx);
}
int prestera_rxtx_port_init(struct prestera_port *port)
{
port->dev->needed_headroom = PRESTERA_DSA_HLEN;
return 0;
}
netdev_tx_t prestera_rxtx_xmit(struct prestera_port *port, struct sk_buff *skb)
{
struct prestera_dsa dsa;
dsa.hw_dev_num = port->dev_id;
dsa.port_num = port->hw_id;
if (skb_cow_head(skb, PRESTERA_DSA_HLEN) < 0)
return NET_XMIT_DROP;
skb_push(skb, PRESTERA_DSA_HLEN);
memmove(skb->data, skb->data + PRESTERA_DSA_HLEN, 2 * ETH_ALEN);
if (prestera_dsa_build(&dsa, skb->data + 2 * ETH_ALEN) != 0)
return NET_XMIT_DROP;
return prestera_sdma_xmit(&port->sw->rxtx->sdma, skb);
}