Contributors: 4
Author Tokens Token Proportion Commits Commit Proportion
Vadym Kochan 3523 98.99% 2 40.00%
Oleksandr Mazur 18 0.51% 1 20.00%
Zhengchao Shao 17 0.48% 1 20.00%
Julia Lawall 1 0.03% 1 20.00%
Total 3559 5


// 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/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 concurrent 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);
	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;
	int err;

	rxtx = kzalloc(sizeof(*rxtx), GFP_KERNEL);
	if (!rxtx)
		return -ENOMEM;

	sw->rxtx = rxtx;

	err = prestera_sdma_switch_init(sw);
	if (err)
		kfree(rxtx);

	return err;
}

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);
}