Add support for RX VLAN ctag/stag strip which may be configured via ethtool. e.g. # ethtool -K $DEV rx-vlan-offload on # ethtool -K $DEV rx-vlan-stag-hw-parse on Ctag-stripped and stag-stripped cannot be enabled at the same time because currently the kernel supports only one layer of VLAN stripping. The NIC supplies VLAN strip information as packet metadata. The fields of this VLAN metadata are: * strip flag: 1 for stripped; 0 for unstripped * tci: VLAN TCI ID * tpid: 1 for ETH_P_8021AD; 0 for ETH_P_8021Q Configuration control bits NFP_NET_CFG_CTRL_RXVLAN_V2 and NFP_NET_CFG_CTRL_RXQINQ are to signal availability of ctag-strip and stag-strip features of the firmware. Signed-off-by: Diana Wang <na.wang@corigine.com> Reviewed-by: Louis Peens <louis.peens@corigine.com> Signed-off-by: Simon Horman <simon.horman@corigine.com> Signed-off-by: David S. Miller <davem@davemloft.net>
466 lines
12 KiB
C
466 lines
12 KiB
C
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
|
/* Copyright (C) 2015-2019 Netronome Systems, Inc. */
|
|
|
|
#include "nfp_app.h"
|
|
#include "nfp_net_dp.h"
|
|
#include "nfp_net_xsk.h"
|
|
|
|
/**
|
|
* nfp_net_rx_alloc_one() - Allocate and map page frag for RX
|
|
* @dp: NFP Net data path struct
|
|
* @dma_addr: Pointer to storage for DMA address (output param)
|
|
*
|
|
* This function will allcate a new page frag, map it for DMA.
|
|
*
|
|
* Return: allocated page frag or NULL on failure.
|
|
*/
|
|
void *nfp_net_rx_alloc_one(struct nfp_net_dp *dp, dma_addr_t *dma_addr)
|
|
{
|
|
void *frag;
|
|
|
|
if (!dp->xdp_prog) {
|
|
frag = netdev_alloc_frag(dp->fl_bufsz);
|
|
} else {
|
|
struct page *page;
|
|
|
|
page = alloc_page(GFP_KERNEL);
|
|
frag = page ? page_address(page) : NULL;
|
|
}
|
|
if (!frag) {
|
|
nn_dp_warn(dp, "Failed to alloc receive page frag\n");
|
|
return NULL;
|
|
}
|
|
|
|
*dma_addr = nfp_net_dma_map_rx(dp, frag);
|
|
if (dma_mapping_error(dp->dev, *dma_addr)) {
|
|
nfp_net_free_frag(frag, dp->xdp_prog);
|
|
nn_dp_warn(dp, "Failed to map DMA RX buffer\n");
|
|
return NULL;
|
|
}
|
|
|
|
return frag;
|
|
}
|
|
|
|
/**
|
|
* nfp_net_tx_ring_init() - Fill in the boilerplate for a TX ring
|
|
* @tx_ring: TX ring structure
|
|
* @dp: NFP Net data path struct
|
|
* @r_vec: IRQ vector servicing this ring
|
|
* @idx: Ring index
|
|
* @is_xdp: Is this an XDP TX ring?
|
|
*/
|
|
static void
|
|
nfp_net_tx_ring_init(struct nfp_net_tx_ring *tx_ring, struct nfp_net_dp *dp,
|
|
struct nfp_net_r_vector *r_vec, unsigned int idx,
|
|
bool is_xdp)
|
|
{
|
|
struct nfp_net *nn = r_vec->nfp_net;
|
|
|
|
tx_ring->idx = idx;
|
|
tx_ring->r_vec = r_vec;
|
|
tx_ring->is_xdp = is_xdp;
|
|
u64_stats_init(&tx_ring->r_vec->tx_sync);
|
|
|
|
tx_ring->qcidx = tx_ring->idx * nn->stride_tx;
|
|
tx_ring->txrwb = dp->txrwb ? &dp->txrwb[idx] : NULL;
|
|
tx_ring->qcp_q = nn->tx_bar + NFP_QCP_QUEUE_OFF(tx_ring->qcidx);
|
|
}
|
|
|
|
/**
|
|
* nfp_net_rx_ring_init() - Fill in the boilerplate for a RX ring
|
|
* @rx_ring: RX ring structure
|
|
* @r_vec: IRQ vector servicing this ring
|
|
* @idx: Ring index
|
|
*/
|
|
static void
|
|
nfp_net_rx_ring_init(struct nfp_net_rx_ring *rx_ring,
|
|
struct nfp_net_r_vector *r_vec, unsigned int idx)
|
|
{
|
|
struct nfp_net *nn = r_vec->nfp_net;
|
|
|
|
rx_ring->idx = idx;
|
|
rx_ring->r_vec = r_vec;
|
|
u64_stats_init(&rx_ring->r_vec->rx_sync);
|
|
|
|
rx_ring->fl_qcidx = rx_ring->idx * nn->stride_rx;
|
|
rx_ring->qcp_fl = nn->rx_bar + NFP_QCP_QUEUE_OFF(rx_ring->fl_qcidx);
|
|
}
|
|
|
|
/**
|
|
* nfp_net_rx_ring_reset() - Reflect in SW state of freelist after disable
|
|
* @rx_ring: RX ring structure
|
|
*
|
|
* Assumes that the device is stopped, must be idempotent.
|
|
*/
|
|
void nfp_net_rx_ring_reset(struct nfp_net_rx_ring *rx_ring)
|
|
{
|
|
unsigned int wr_idx, last_idx;
|
|
|
|
/* wr_p == rd_p means ring was never fed FL bufs. RX rings are always
|
|
* kept at cnt - 1 FL bufs.
|
|
*/
|
|
if (rx_ring->wr_p == 0 && rx_ring->rd_p == 0)
|
|
return;
|
|
|
|
/* Move the empty entry to the end of the list */
|
|
wr_idx = D_IDX(rx_ring, rx_ring->wr_p);
|
|
last_idx = rx_ring->cnt - 1;
|
|
if (rx_ring->r_vec->xsk_pool) {
|
|
rx_ring->xsk_rxbufs[wr_idx] = rx_ring->xsk_rxbufs[last_idx];
|
|
memset(&rx_ring->xsk_rxbufs[last_idx], 0,
|
|
sizeof(*rx_ring->xsk_rxbufs));
|
|
} else {
|
|
rx_ring->rxbufs[wr_idx] = rx_ring->rxbufs[last_idx];
|
|
memset(&rx_ring->rxbufs[last_idx], 0, sizeof(*rx_ring->rxbufs));
|
|
}
|
|
|
|
memset(rx_ring->rxds, 0, rx_ring->size);
|
|
rx_ring->wr_p = 0;
|
|
rx_ring->rd_p = 0;
|
|
}
|
|
|
|
/**
|
|
* nfp_net_rx_ring_bufs_free() - Free any buffers currently on the RX ring
|
|
* @dp: NFP Net data path struct
|
|
* @rx_ring: RX ring to remove buffers from
|
|
*
|
|
* Assumes that the device is stopped and buffers are in [0, ring->cnt - 1)
|
|
* entries. After device is disabled nfp_net_rx_ring_reset() must be called
|
|
* to restore required ring geometry.
|
|
*/
|
|
static void
|
|
nfp_net_rx_ring_bufs_free(struct nfp_net_dp *dp,
|
|
struct nfp_net_rx_ring *rx_ring)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (nfp_net_has_xsk_pool_slow(dp, rx_ring->idx))
|
|
return;
|
|
|
|
for (i = 0; i < rx_ring->cnt - 1; i++) {
|
|
/* NULL skb can only happen when initial filling of the ring
|
|
* fails to allocate enough buffers and calls here to free
|
|
* already allocated ones.
|
|
*/
|
|
if (!rx_ring->rxbufs[i].frag)
|
|
continue;
|
|
|
|
nfp_net_dma_unmap_rx(dp, rx_ring->rxbufs[i].dma_addr);
|
|
nfp_net_free_frag(rx_ring->rxbufs[i].frag, dp->xdp_prog);
|
|
rx_ring->rxbufs[i].dma_addr = 0;
|
|
rx_ring->rxbufs[i].frag = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* nfp_net_rx_ring_bufs_alloc() - Fill RX ring with buffers (don't give to FW)
|
|
* @dp: NFP Net data path struct
|
|
* @rx_ring: RX ring to remove buffers from
|
|
*/
|
|
static int
|
|
nfp_net_rx_ring_bufs_alloc(struct nfp_net_dp *dp,
|
|
struct nfp_net_rx_ring *rx_ring)
|
|
{
|
|
struct nfp_net_rx_buf *rxbufs;
|
|
unsigned int i;
|
|
|
|
if (nfp_net_has_xsk_pool_slow(dp, rx_ring->idx))
|
|
return 0;
|
|
|
|
rxbufs = rx_ring->rxbufs;
|
|
|
|
for (i = 0; i < rx_ring->cnt - 1; i++) {
|
|
rxbufs[i].frag = nfp_net_rx_alloc_one(dp, &rxbufs[i].dma_addr);
|
|
if (!rxbufs[i].frag) {
|
|
nfp_net_rx_ring_bufs_free(dp, rx_ring);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nfp_net_tx_rings_prepare(struct nfp_net *nn, struct nfp_net_dp *dp)
|
|
{
|
|
unsigned int r;
|
|
|
|
dp->tx_rings = kcalloc(dp->num_tx_rings, sizeof(*dp->tx_rings),
|
|
GFP_KERNEL);
|
|
if (!dp->tx_rings)
|
|
return -ENOMEM;
|
|
|
|
if (dp->ctrl & NFP_NET_CFG_CTRL_TXRWB) {
|
|
dp->txrwb = dma_alloc_coherent(dp->dev,
|
|
dp->num_tx_rings * sizeof(u64),
|
|
&dp->txrwb_dma, GFP_KERNEL);
|
|
if (!dp->txrwb)
|
|
goto err_free_rings;
|
|
}
|
|
|
|
for (r = 0; r < dp->num_tx_rings; r++) {
|
|
int bias = 0;
|
|
|
|
if (r >= dp->num_stack_tx_rings)
|
|
bias = dp->num_stack_tx_rings;
|
|
|
|
nfp_net_tx_ring_init(&dp->tx_rings[r], dp,
|
|
&nn->r_vecs[r - bias], r, bias);
|
|
|
|
if (nfp_net_tx_ring_alloc(dp, &dp->tx_rings[r]))
|
|
goto err_free_prev;
|
|
|
|
if (nfp_net_tx_ring_bufs_alloc(dp, &dp->tx_rings[r]))
|
|
goto err_free_ring;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_free_prev:
|
|
while (r--) {
|
|
nfp_net_tx_ring_bufs_free(dp, &dp->tx_rings[r]);
|
|
err_free_ring:
|
|
nfp_net_tx_ring_free(dp, &dp->tx_rings[r]);
|
|
}
|
|
if (dp->txrwb)
|
|
dma_free_coherent(dp->dev, dp->num_tx_rings * sizeof(u64),
|
|
dp->txrwb, dp->txrwb_dma);
|
|
err_free_rings:
|
|
kfree(dp->tx_rings);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
void nfp_net_tx_rings_free(struct nfp_net_dp *dp)
|
|
{
|
|
unsigned int r;
|
|
|
|
for (r = 0; r < dp->num_tx_rings; r++) {
|
|
nfp_net_tx_ring_bufs_free(dp, &dp->tx_rings[r]);
|
|
nfp_net_tx_ring_free(dp, &dp->tx_rings[r]);
|
|
}
|
|
|
|
if (dp->txrwb)
|
|
dma_free_coherent(dp->dev, dp->num_tx_rings * sizeof(u64),
|
|
dp->txrwb, dp->txrwb_dma);
|
|
kfree(dp->tx_rings);
|
|
}
|
|
|
|
/**
|
|
* nfp_net_rx_ring_free() - Free resources allocated to a RX ring
|
|
* @rx_ring: RX ring to free
|
|
*/
|
|
static void nfp_net_rx_ring_free(struct nfp_net_rx_ring *rx_ring)
|
|
{
|
|
struct nfp_net_r_vector *r_vec = rx_ring->r_vec;
|
|
struct nfp_net_dp *dp = &r_vec->nfp_net->dp;
|
|
|
|
if (dp->netdev)
|
|
xdp_rxq_info_unreg(&rx_ring->xdp_rxq);
|
|
|
|
if (nfp_net_has_xsk_pool_slow(dp, rx_ring->idx))
|
|
kvfree(rx_ring->xsk_rxbufs);
|
|
else
|
|
kvfree(rx_ring->rxbufs);
|
|
|
|
if (rx_ring->rxds)
|
|
dma_free_coherent(dp->dev, rx_ring->size,
|
|
rx_ring->rxds, rx_ring->dma);
|
|
|
|
rx_ring->cnt = 0;
|
|
rx_ring->rxbufs = NULL;
|
|
rx_ring->xsk_rxbufs = NULL;
|
|
rx_ring->rxds = NULL;
|
|
rx_ring->dma = 0;
|
|
rx_ring->size = 0;
|
|
}
|
|
|
|
/**
|
|
* nfp_net_rx_ring_alloc() - Allocate resource for a RX ring
|
|
* @dp: NFP Net data path struct
|
|
* @rx_ring: RX ring to allocate
|
|
*
|
|
* Return: 0 on success, negative errno otherwise.
|
|
*/
|
|
static int
|
|
nfp_net_rx_ring_alloc(struct nfp_net_dp *dp, struct nfp_net_rx_ring *rx_ring)
|
|
{
|
|
enum xdp_mem_type mem_type;
|
|
size_t rxbuf_sw_desc_sz;
|
|
int err;
|
|
|
|
if (nfp_net_has_xsk_pool_slow(dp, rx_ring->idx)) {
|
|
mem_type = MEM_TYPE_XSK_BUFF_POOL;
|
|
rxbuf_sw_desc_sz = sizeof(*rx_ring->xsk_rxbufs);
|
|
} else {
|
|
mem_type = MEM_TYPE_PAGE_ORDER0;
|
|
rxbuf_sw_desc_sz = sizeof(*rx_ring->rxbufs);
|
|
}
|
|
|
|
if (dp->netdev) {
|
|
err = xdp_rxq_info_reg(&rx_ring->xdp_rxq, dp->netdev,
|
|
rx_ring->idx, rx_ring->r_vec->napi.napi_id);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = xdp_rxq_info_reg_mem_model(&rx_ring->xdp_rxq, mem_type, NULL);
|
|
if (err)
|
|
goto err_alloc;
|
|
}
|
|
|
|
rx_ring->cnt = dp->rxd_cnt;
|
|
rx_ring->size = array_size(rx_ring->cnt, sizeof(*rx_ring->rxds));
|
|
rx_ring->rxds = dma_alloc_coherent(dp->dev, rx_ring->size,
|
|
&rx_ring->dma,
|
|
GFP_KERNEL | __GFP_NOWARN);
|
|
if (!rx_ring->rxds) {
|
|
netdev_warn(dp->netdev, "failed to allocate RX descriptor ring memory, requested descriptor count: %d, consider lowering descriptor count\n",
|
|
rx_ring->cnt);
|
|
goto err_alloc;
|
|
}
|
|
|
|
if (nfp_net_has_xsk_pool_slow(dp, rx_ring->idx)) {
|
|
rx_ring->xsk_rxbufs = kvcalloc(rx_ring->cnt, rxbuf_sw_desc_sz,
|
|
GFP_KERNEL);
|
|
if (!rx_ring->xsk_rxbufs)
|
|
goto err_alloc;
|
|
} else {
|
|
rx_ring->rxbufs = kvcalloc(rx_ring->cnt, rxbuf_sw_desc_sz,
|
|
GFP_KERNEL);
|
|
if (!rx_ring->rxbufs)
|
|
goto err_alloc;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_alloc:
|
|
nfp_net_rx_ring_free(rx_ring);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
int nfp_net_rx_rings_prepare(struct nfp_net *nn, struct nfp_net_dp *dp)
|
|
{
|
|
unsigned int r;
|
|
|
|
dp->rx_rings = kcalloc(dp->num_rx_rings, sizeof(*dp->rx_rings),
|
|
GFP_KERNEL);
|
|
if (!dp->rx_rings)
|
|
return -ENOMEM;
|
|
|
|
for (r = 0; r < dp->num_rx_rings; r++) {
|
|
nfp_net_rx_ring_init(&dp->rx_rings[r], &nn->r_vecs[r], r);
|
|
|
|
if (nfp_net_rx_ring_alloc(dp, &dp->rx_rings[r]))
|
|
goto err_free_prev;
|
|
|
|
if (nfp_net_rx_ring_bufs_alloc(dp, &dp->rx_rings[r]))
|
|
goto err_free_ring;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_free_prev:
|
|
while (r--) {
|
|
nfp_net_rx_ring_bufs_free(dp, &dp->rx_rings[r]);
|
|
err_free_ring:
|
|
nfp_net_rx_ring_free(&dp->rx_rings[r]);
|
|
}
|
|
kfree(dp->rx_rings);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
void nfp_net_rx_rings_free(struct nfp_net_dp *dp)
|
|
{
|
|
unsigned int r;
|
|
|
|
for (r = 0; r < dp->num_rx_rings; r++) {
|
|
nfp_net_rx_ring_bufs_free(dp, &dp->rx_rings[r]);
|
|
nfp_net_rx_ring_free(&dp->rx_rings[r]);
|
|
}
|
|
|
|
kfree(dp->rx_rings);
|
|
}
|
|
|
|
void
|
|
nfp_net_rx_ring_hw_cfg_write(struct nfp_net *nn,
|
|
struct nfp_net_rx_ring *rx_ring, unsigned int idx)
|
|
{
|
|
/* Write the DMA address, size and MSI-X info to the device */
|
|
nn_writeq(nn, NFP_NET_CFG_RXR_ADDR(idx), rx_ring->dma);
|
|
nn_writeb(nn, NFP_NET_CFG_RXR_SZ(idx), ilog2(rx_ring->cnt));
|
|
nn_writeb(nn, NFP_NET_CFG_RXR_VEC(idx), rx_ring->r_vec->irq_entry);
|
|
}
|
|
|
|
void
|
|
nfp_net_tx_ring_hw_cfg_write(struct nfp_net *nn,
|
|
struct nfp_net_tx_ring *tx_ring, unsigned int idx)
|
|
{
|
|
nn_writeq(nn, NFP_NET_CFG_TXR_ADDR(idx), tx_ring->dma);
|
|
if (tx_ring->txrwb) {
|
|
*tx_ring->txrwb = 0;
|
|
nn_writeq(nn, NFP_NET_CFG_TXR_WB_ADDR(idx),
|
|
nn->dp.txrwb_dma + idx * sizeof(u64));
|
|
}
|
|
nn_writeb(nn, NFP_NET_CFG_TXR_SZ(idx), ilog2(tx_ring->cnt));
|
|
nn_writeb(nn, NFP_NET_CFG_TXR_VEC(idx), tx_ring->r_vec->irq_entry);
|
|
}
|
|
|
|
void nfp_net_vec_clear_ring_data(struct nfp_net *nn, unsigned int idx)
|
|
{
|
|
nn_writeq(nn, NFP_NET_CFG_RXR_ADDR(idx), 0);
|
|
nn_writeb(nn, NFP_NET_CFG_RXR_SZ(idx), 0);
|
|
nn_writeb(nn, NFP_NET_CFG_RXR_VEC(idx), 0);
|
|
|
|
nn_writeq(nn, NFP_NET_CFG_TXR_ADDR(idx), 0);
|
|
nn_writeq(nn, NFP_NET_CFG_TXR_WB_ADDR(idx), 0);
|
|
nn_writeb(nn, NFP_NET_CFG_TXR_SZ(idx), 0);
|
|
nn_writeb(nn, NFP_NET_CFG_TXR_VEC(idx), 0);
|
|
}
|
|
|
|
netdev_tx_t nfp_net_tx(struct sk_buff *skb, struct net_device *netdev)
|
|
{
|
|
struct nfp_net *nn = netdev_priv(netdev);
|
|
|
|
return nn->dp.ops->xmit(skb, netdev);
|
|
}
|
|
|
|
bool __nfp_ctrl_tx(struct nfp_net *nn, struct sk_buff *skb)
|
|
{
|
|
struct nfp_net_r_vector *r_vec = &nn->r_vecs[0];
|
|
|
|
return nn->dp.ops->ctrl_tx_one(nn, r_vec, skb, false);
|
|
}
|
|
|
|
bool nfp_ctrl_tx(struct nfp_net *nn, struct sk_buff *skb)
|
|
{
|
|
struct nfp_net_r_vector *r_vec = &nn->r_vecs[0];
|
|
bool ret;
|
|
|
|
spin_lock_bh(&r_vec->lock);
|
|
ret = nn->dp.ops->ctrl_tx_one(nn, r_vec, skb, false);
|
|
spin_unlock_bh(&r_vec->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool nfp_net_vlan_strip(struct sk_buff *skb, const struct nfp_net_rx_desc *rxd,
|
|
const struct nfp_meta_parsed *meta)
|
|
{
|
|
u16 tpid = 0, tci = 0;
|
|
|
|
if (rxd->rxd.flags & PCIE_DESC_RX_VLAN) {
|
|
tpid = ETH_P_8021Q;
|
|
tci = le16_to_cpu(rxd->rxd.vlan);
|
|
} else if (meta->vlan.stripped) {
|
|
if (meta->vlan.tpid == NFP_NET_VLAN_CTAG)
|
|
tpid = ETH_P_8021Q;
|
|
else if (meta->vlan.tpid == NFP_NET_VLAN_STAG)
|
|
tpid = ETH_P_8021AD;
|
|
else
|
|
return false;
|
|
|
|
tci = meta->vlan.tci;
|
|
}
|
|
if (tpid)
|
|
__vlan_hwaccel_put_tag(skb, htons(tpid), tci);
|
|
|
|
return true;
|
|
}
|