Commit c8186c80 authored by Daniel Borkmann's avatar Daniel Borkmann

Merge branch 'bpf-af-xdp-wakeup'

Magnus Karlsson says:

====================
This patch set adds support for a new flag called need_wakeup in the
AF_XDP Tx and fill rings. When this flag is set by the driver, it
means that the application has to explicitly wake up the kernel Rx
(for the bit in the fill ring) or kernel Tx (for bit in the Tx ring)
processing by issuing a syscall. Poll() can wake up both and sendto()
will wake up Tx processing only.

The main reason for introducing this new flag is to be able to
efficiently support the case when application and driver is executing
on the same core. Previously, the driver was just busy-spinning on the
fill ring if it ran out of buffers in the HW and there were none to
get from the fill ring. This approach works when the application and
driver is running on different cores as the application can replenish
the fill ring while the driver is busy-spinning. Though, this is a
lousy approach if both of them are running on the same core as the
probability of the fill ring getting more entries when the driver is
busy-spinning is zero. With this new feature the driver now sets the
need_wakeup flag and returns to the application. The application can
then replenish the fill queue and then explicitly wake up the Rx
processing in the kernel using the syscall poll(). For Tx, the flag is
only set to one if the driver has no outstanding Tx completion
interrupts. If it has some, the flag is zero as it will be woken up by
a completion interrupt anyway. This flag can also be used in other
situations where the driver needs to be woken up explicitly.

As a nice side effect, this new flag also improves the Tx performance
of the case where application and driver are running on two different
cores as it reduces the number of syscalls to the kernel. The kernel
tells user space if it needs to be woken up by a syscall, and this
eliminates many of the syscalls. The Rx performance of the 2-core case
is on the other hand slightly worse, since there is a need to use a
syscall now to wake up the driver, instead of the driver
busy-spinning. It does waste less CPU cycles though, which might lead
to better overall system performance.

This new flag needs some simple driver support. If the driver does not
support it, the Rx flag is always zero and the Tx flag is always
one. This makes any application relying on this feature default to the
old behavior of not requiring any syscalls in the Rx path and always
having to call sendto() in the Tx path.

For backwards compatibility reasons, this feature has to be explicitly
turned on using a new bind flag (XDP_USE_NEED_WAKEUP). I recommend
that you always turn it on as it has a large positive performance
impact for the one core case and does not degrade 2 core performance
and actually improves it for Tx heavy workloads.

Here are some performance numbers measured on my local,
non-performance optimized development system. That is why you are
seeing numbers lower than the ones from Björn and Jesper. 64 byte
packets at 40Gbit/s line rate. All results in Mpps. Cores == 1 means
that both application and driver is executing on the same core. Cores
== 2 that they are on different cores.

                              Applications
need_wakeup  cores    txpush    rxdrop      l2fwd
---------------------------------------------------------------
     n         1       0.07      0.06        0.03
     y         1       21.6      8.2         6.5
     n         2       32.3      11.7        8.7
     y         2       33.1      11.7        8.7

Overall, the need_wakeup flag provides the same or better performance
in all the micro-benchmarks. The reduction of sendto() calls in txpush
is large. Only a few per second is needed. For l2fwd, the drop is 50%
for the 1 core case and more than 99.9% for the 2 core case. Do not
know why I am not seeing the same drop for the 1 core case yet.

The name and inspiration of the flag has been taken from io_uring by
Jens Axboe. Details about this feature in io_uring can be found in
http://kernel.dk/io_uring.pdf, section 8.3. It also addresses most of
the denial of service and sendto() concerns raised by Maxim
Mikityanskiy in https://www.spinics.net/lists/netdev/msg554657.html.

The typical Tx part of an application will have to change from:

ret = sendto(fd,....)

to:

if (xsk_ring_prod__needs_wakeup(&xsk->tx))
       ret = sendto(fd,....)

and th Rx part from:

rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx);
if (!rcvd)
       return;

to:

rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx);
if (!rcvd) {
       if (xsk_ring_prod__needs_wakeup(&xsk->umem->fq))
              ret = poll(fd,.....);
       return;
}

v3 -> v4:
* Maxim found a possible race in the Tx part of the driver. The
  setting of the flag needs to happen before the sending, otherwise it
  might trigger this race. Fixed in ixgbe and i40e driver.
* Mellanox support contributed by Maxim
* Removed the XSK_DRV_CAN_SLEEP flag as it was not used
  anymore. Thanks to Sridhar for discovering this.
* For consistency the feature is now always called need_wakeup. There
  were some places where it was referred to as might_sleep, but they
  have been removed. Thanks to Sridhar for spotting.
* Fixed some typos in the commit messages

v2 -> v3:
* Converted the Mellanox driver to the new ndo in patch 1 as pointed
  out by Maxim
* Fixed the compatibility code of XDP_MMAP_OFFSETS so it now works.

v1 -> v2:
* Fixed bisectability problem pointed out by Jakub
* Added missing initiliztion of the Tx need_wakeup flag to 1

This patch has been applied against commit b753c5a7 ("Merge branch 'r8152-RX-improve'")

Structure of the patch set:

Patch 1: Replaces the ndo_xsk_async_xmit with ndo_xsk_wakeup to
         support waking up both Rx and Tx processing
Patch 2: Implements the need_wakeup functionality in common code
Patch 3-4: Add need_wakeup support to the i40e and ixgbe drivers
Patch 5: Add need_wakeup support to libbpf
Patch 6: Add need_wakeup support to the xdpsock sample application
Patch 7-8: Add need_wakeup support to the Mellanox mlx5 driver
====================
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
parents e0325006 a7bd4018
...@@ -12570,7 +12570,8 @@ static int i40e_xdp_setup(struct i40e_vsi *vsi, ...@@ -12570,7 +12570,8 @@ static int i40e_xdp_setup(struct i40e_vsi *vsi,
if (need_reset && prog) if (need_reset && prog)
for (i = 0; i < vsi->num_queue_pairs; i++) for (i = 0; i < vsi->num_queue_pairs; i++)
if (vsi->xdp_rings[i]->xsk_umem) if (vsi->xdp_rings[i]->xsk_umem)
(void)i40e_xsk_async_xmit(vsi->netdev, i); (void)i40e_xsk_wakeup(vsi->netdev, i,
XDP_WAKEUP_RX);
return 0; return 0;
} }
...@@ -12892,7 +12893,7 @@ static const struct net_device_ops i40e_netdev_ops = { ...@@ -12892,7 +12893,7 @@ static const struct net_device_ops i40e_netdev_ops = {
.ndo_bridge_setlink = i40e_ndo_bridge_setlink, .ndo_bridge_setlink = i40e_ndo_bridge_setlink,
.ndo_bpf = i40e_xdp, .ndo_bpf = i40e_xdp,
.ndo_xdp_xmit = i40e_xdp_xmit, .ndo_xdp_xmit = i40e_xdp_xmit,
.ndo_xsk_async_xmit = i40e_xsk_async_xmit, .ndo_xsk_wakeup = i40e_xsk_wakeup,
.ndo_dfwd_add_station = i40e_fwd_add, .ndo_dfwd_add_station = i40e_fwd_add,
.ndo_dfwd_del_station = i40e_fwd_del, .ndo_dfwd_del_station = i40e_fwd_del,
}; };
......
...@@ -116,7 +116,7 @@ static int i40e_xsk_umem_enable(struct i40e_vsi *vsi, struct xdp_umem *umem, ...@@ -116,7 +116,7 @@ static int i40e_xsk_umem_enable(struct i40e_vsi *vsi, struct xdp_umem *umem,
return err; return err;
/* Kick start the NAPI context so that receiving will start */ /* Kick start the NAPI context so that receiving will start */
err = i40e_xsk_async_xmit(vsi->netdev, qid); err = i40e_xsk_wakeup(vsi->netdev, qid, XDP_WAKEUP_RX);
if (err) if (err)
return err; return err;
} }
...@@ -626,6 +626,15 @@ int i40e_clean_rx_irq_zc(struct i40e_ring *rx_ring, int budget) ...@@ -626,6 +626,15 @@ int i40e_clean_rx_irq_zc(struct i40e_ring *rx_ring, int budget)
i40e_finalize_xdp_rx(rx_ring, xdp_xmit); i40e_finalize_xdp_rx(rx_ring, xdp_xmit);
i40e_update_rx_stats(rx_ring, total_rx_bytes, total_rx_packets); i40e_update_rx_stats(rx_ring, total_rx_bytes, total_rx_packets);
if (xsk_umem_uses_need_wakeup(rx_ring->xsk_umem)) {
if (failure || rx_ring->next_to_clean == rx_ring->next_to_use)
xsk_set_rx_need_wakeup(rx_ring->xsk_umem);
else
xsk_clear_rx_need_wakeup(rx_ring->xsk_umem);
return (int)total_rx_packets;
}
return failure ? budget : (int)total_rx_packets; return failure ? budget : (int)total_rx_packets;
} }
...@@ -681,6 +690,8 @@ static bool i40e_xmit_zc(struct i40e_ring *xdp_ring, unsigned int budget) ...@@ -681,6 +690,8 @@ static bool i40e_xmit_zc(struct i40e_ring *xdp_ring, unsigned int budget)
i40e_xdp_ring_update_tail(xdp_ring); i40e_xdp_ring_update_tail(xdp_ring);
xsk_umem_consume_tx_done(xdp_ring->xsk_umem); xsk_umem_consume_tx_done(xdp_ring->xsk_umem);
if (xsk_umem_uses_need_wakeup(xdp_ring->xsk_umem))
xsk_clear_tx_need_wakeup(xdp_ring->xsk_umem);
} }
return !!budget && work_done; return !!budget && work_done;
...@@ -759,19 +770,27 @@ bool i40e_clean_xdp_tx_irq(struct i40e_vsi *vsi, ...@@ -759,19 +770,27 @@ bool i40e_clean_xdp_tx_irq(struct i40e_vsi *vsi,
i40e_update_tx_stats(tx_ring, completed_frames, total_bytes); i40e_update_tx_stats(tx_ring, completed_frames, total_bytes);
out_xmit: out_xmit:
if (xsk_umem_uses_need_wakeup(tx_ring->xsk_umem)) {
if (tx_ring->next_to_clean == tx_ring->next_to_use)
xsk_set_tx_need_wakeup(tx_ring->xsk_umem);
else
xsk_clear_tx_need_wakeup(tx_ring->xsk_umem);
}
xmit_done = i40e_xmit_zc(tx_ring, budget); xmit_done = i40e_xmit_zc(tx_ring, budget);
return work_done && xmit_done; return work_done && xmit_done;
} }
/** /**
* i40e_xsk_async_xmit - Implements the ndo_xsk_async_xmit * i40e_xsk_wakeup - Implements the ndo_xsk_wakeup
* @dev: the netdevice * @dev: the netdevice
* @queue_id: queue id to wake up * @queue_id: queue id to wake up
* @flags: ignored in our case since we have Rx and Tx in the same NAPI.
* *
* Returns <0 for errors, 0 otherwise. * Returns <0 for errors, 0 otherwise.
**/ **/
int i40e_xsk_async_xmit(struct net_device *dev, u32 queue_id) int i40e_xsk_wakeup(struct net_device *dev, u32 queue_id, u32 flags)
{ {
struct i40e_netdev_priv *np = netdev_priv(dev); struct i40e_netdev_priv *np = netdev_priv(dev);
struct i40e_vsi *vsi = np->vsi; struct i40e_vsi *vsi = np->vsi;
......
...@@ -18,6 +18,6 @@ int i40e_clean_rx_irq_zc(struct i40e_ring *rx_ring, int budget); ...@@ -18,6 +18,6 @@ int i40e_clean_rx_irq_zc(struct i40e_ring *rx_ring, int budget);
bool i40e_clean_xdp_tx_irq(struct i40e_vsi *vsi, bool i40e_clean_xdp_tx_irq(struct i40e_vsi *vsi,
struct i40e_ring *tx_ring, int napi_budget); struct i40e_ring *tx_ring, int napi_budget);
int i40e_xsk_async_xmit(struct net_device *dev, u32 queue_id); int i40e_xsk_wakeup(struct net_device *dev, u32 queue_id, u32 flags);
#endif /* _I40E_XSK_H_ */ #endif /* _I40E_XSK_H_ */
...@@ -10263,7 +10263,8 @@ static int ixgbe_xdp_setup(struct net_device *dev, struct bpf_prog *prog) ...@@ -10263,7 +10263,8 @@ static int ixgbe_xdp_setup(struct net_device *dev, struct bpf_prog *prog)
if (need_reset && prog) if (need_reset && prog)
for (i = 0; i < adapter->num_rx_queues; i++) for (i = 0; i < adapter->num_rx_queues; i++)
if (adapter->xdp_ring[i]->xsk_umem) if (adapter->xdp_ring[i]->xsk_umem)
(void)ixgbe_xsk_async_xmit(adapter->netdev, i); (void)ixgbe_xsk_wakeup(adapter->netdev, i,
XDP_WAKEUP_RX);
return 0; return 0;
} }
...@@ -10382,7 +10383,7 @@ static const struct net_device_ops ixgbe_netdev_ops = { ...@@ -10382,7 +10383,7 @@ static const struct net_device_ops ixgbe_netdev_ops = {
.ndo_features_check = ixgbe_features_check, .ndo_features_check = ixgbe_features_check,
.ndo_bpf = ixgbe_xdp, .ndo_bpf = ixgbe_xdp,
.ndo_xdp_xmit = ixgbe_xdp_xmit, .ndo_xdp_xmit = ixgbe_xdp_xmit,
.ndo_xsk_async_xmit = ixgbe_xsk_async_xmit, .ndo_xsk_wakeup = ixgbe_xsk_wakeup,
}; };
static void ixgbe_disable_txr_hw(struct ixgbe_adapter *adapter, static void ixgbe_disable_txr_hw(struct ixgbe_adapter *adapter,
......
...@@ -42,7 +42,7 @@ int ixgbe_clean_rx_irq_zc(struct ixgbe_q_vector *q_vector, ...@@ -42,7 +42,7 @@ int ixgbe_clean_rx_irq_zc(struct ixgbe_q_vector *q_vector,
void ixgbe_xsk_clean_rx_ring(struct ixgbe_ring *rx_ring); void ixgbe_xsk_clean_rx_ring(struct ixgbe_ring *rx_ring);
bool ixgbe_clean_xdp_tx_irq(struct ixgbe_q_vector *q_vector, bool ixgbe_clean_xdp_tx_irq(struct ixgbe_q_vector *q_vector,
struct ixgbe_ring *tx_ring, int napi_budget); struct ixgbe_ring *tx_ring, int napi_budget);
int ixgbe_xsk_async_xmit(struct net_device *dev, u32 queue_id); int ixgbe_xsk_wakeup(struct net_device *dev, u32 queue_id, u32 flags);
void ixgbe_xsk_clean_tx_ring(struct ixgbe_ring *tx_ring); void ixgbe_xsk_clean_tx_ring(struct ixgbe_ring *tx_ring);
#endif /* #define _IXGBE_TXRX_COMMON_H_ */ #endif /* #define _IXGBE_TXRX_COMMON_H_ */
...@@ -100,7 +100,7 @@ static int ixgbe_xsk_umem_enable(struct ixgbe_adapter *adapter, ...@@ -100,7 +100,7 @@ static int ixgbe_xsk_umem_enable(struct ixgbe_adapter *adapter,
ixgbe_txrx_ring_enable(adapter, qid); ixgbe_txrx_ring_enable(adapter, qid);
/* Kick start the NAPI context so that receiving will start */ /* Kick start the NAPI context so that receiving will start */
err = ixgbe_xsk_async_xmit(adapter->netdev, qid); err = ixgbe_xsk_wakeup(adapter->netdev, qid, XDP_WAKEUP_RX);
if (err) if (err)
return err; return err;
} }
...@@ -547,6 +547,14 @@ int ixgbe_clean_rx_irq_zc(struct ixgbe_q_vector *q_vector, ...@@ -547,6 +547,14 @@ int ixgbe_clean_rx_irq_zc(struct ixgbe_q_vector *q_vector,
q_vector->rx.total_packets += total_rx_packets; q_vector->rx.total_packets += total_rx_packets;
q_vector->rx.total_bytes += total_rx_bytes; q_vector->rx.total_bytes += total_rx_bytes;
if (xsk_umem_uses_need_wakeup(rx_ring->xsk_umem)) {
if (failure || rx_ring->next_to_clean == rx_ring->next_to_use)
xsk_set_rx_need_wakeup(rx_ring->xsk_umem);
else
xsk_clear_rx_need_wakeup(rx_ring->xsk_umem);
return (int)total_rx_packets;
}
return failure ? budget : (int)total_rx_packets; return failure ? budget : (int)total_rx_packets;
} }
...@@ -615,6 +623,8 @@ static bool ixgbe_xmit_zc(struct ixgbe_ring *xdp_ring, unsigned int budget) ...@@ -615,6 +623,8 @@ static bool ixgbe_xmit_zc(struct ixgbe_ring *xdp_ring, unsigned int budget)
if (tx_desc) { if (tx_desc) {
ixgbe_xdp_ring_update_tail(xdp_ring); ixgbe_xdp_ring_update_tail(xdp_ring);
xsk_umem_consume_tx_done(xdp_ring->xsk_umem); xsk_umem_consume_tx_done(xdp_ring->xsk_umem);
if (xsk_umem_uses_need_wakeup(xdp_ring->xsk_umem))
xsk_clear_tx_need_wakeup(xdp_ring->xsk_umem);
} }
return !!budget && work_done; return !!budget && work_done;
...@@ -688,11 +698,19 @@ bool ixgbe_clean_xdp_tx_irq(struct ixgbe_q_vector *q_vector, ...@@ -688,11 +698,19 @@ bool ixgbe_clean_xdp_tx_irq(struct ixgbe_q_vector *q_vector,
if (xsk_frames) if (xsk_frames)
xsk_umem_complete_tx(umem, xsk_frames); xsk_umem_complete_tx(umem, xsk_frames);
if (xsk_umem_uses_need_wakeup(tx_ring->xsk_umem)) {
if (tx_ring->next_to_clean == tx_ring->next_to_use)
xsk_set_tx_need_wakeup(tx_ring->xsk_umem);
else
xsk_clear_tx_need_wakeup(tx_ring->xsk_umem);
}
xmit_done = ixgbe_xmit_zc(tx_ring, q_vector->tx.work_limit); xmit_done = ixgbe_xmit_zc(tx_ring, q_vector->tx.work_limit);
return budget > 0 && xmit_done; return budget > 0 && xmit_done;
} }
int ixgbe_xsk_async_xmit(struct net_device *dev, u32 qid) int ixgbe_xsk_wakeup(struct net_device *dev, u32 qid, u32 flags)
{ {
struct ixgbe_adapter *adapter = netdev_priv(dev); struct ixgbe_adapter *adapter = netdev_priv(dev);
struct ixgbe_ring *ring; struct ixgbe_ring *ring;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#define __MLX5_EN_XSK_RX_H__ #define __MLX5_EN_XSK_RX_H__
#include "en.h" #include "en.h"
#include <net/xdp_sock.h>
/* RX data path */ /* RX data path */
...@@ -24,4 +25,17 @@ struct sk_buff *mlx5e_xsk_skb_from_cqe_linear(struct mlx5e_rq *rq, ...@@ -24,4 +25,17 @@ struct sk_buff *mlx5e_xsk_skb_from_cqe_linear(struct mlx5e_rq *rq,
struct mlx5e_wqe_frag_info *wi, struct mlx5e_wqe_frag_info *wi,
u32 cqe_bcnt); u32 cqe_bcnt);
static inline bool mlx5e_xsk_update_rx_wakeup(struct mlx5e_rq *rq, bool alloc_err)
{
if (!xsk_umem_uses_need_wakeup(rq->umem))
return alloc_err;
if (unlikely(alloc_err))
xsk_set_rx_need_wakeup(rq->umem);
else
xsk_clear_rx_need_wakeup(rq->umem);
return false;
}
#endif /* __MLX5_EN_XSK_RX_H__ */ #endif /* __MLX5_EN_XSK_RX_H__ */
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
#include "en/params.h" #include "en/params.h"
#include <net/xdp_sock.h> #include <net/xdp_sock.h>
int mlx5e_xsk_async_xmit(struct net_device *dev, u32 qid) int mlx5e_xsk_wakeup(struct net_device *dev, u32 qid, u32 flags)
{ {
struct mlx5e_priv *priv = netdev_priv(dev); struct mlx5e_priv *priv = netdev_priv(dev);
struct mlx5e_params *params = &priv->channels.params; struct mlx5e_params *params = &priv->channels.params;
......
...@@ -5,11 +5,23 @@ ...@@ -5,11 +5,23 @@
#define __MLX5_EN_XSK_TX_H__ #define __MLX5_EN_XSK_TX_H__
#include "en.h" #include "en.h"
#include <net/xdp_sock.h>
/* TX data path */ /* TX data path */
int mlx5e_xsk_async_xmit(struct net_device *dev, u32 qid); int mlx5e_xsk_wakeup(struct net_device *dev, u32 qid, u32 flags);
bool mlx5e_xsk_tx(struct mlx5e_xdpsq *sq, unsigned int budget); bool mlx5e_xsk_tx(struct mlx5e_xdpsq *sq, unsigned int budget);
static inline void mlx5e_xsk_update_tx_wakeup(struct mlx5e_xdpsq *sq)
{
if (!xsk_umem_uses_need_wakeup(sq->umem))
return;
if (sq->pc != sq->cc)
xsk_clear_tx_need_wakeup(sq->umem);
else
xsk_set_tx_need_wakeup(sq->umem);
}
#endif /* __MLX5_EN_XSK_TX_H__ */ #endif /* __MLX5_EN_XSK_TX_H__ */
...@@ -4540,7 +4540,7 @@ const struct net_device_ops mlx5e_netdev_ops = { ...@@ -4540,7 +4540,7 @@ const struct net_device_ops mlx5e_netdev_ops = {
.ndo_tx_timeout = mlx5e_tx_timeout, .ndo_tx_timeout = mlx5e_tx_timeout,
.ndo_bpf = mlx5e_xdp, .ndo_bpf = mlx5e_xdp,
.ndo_xdp_xmit = mlx5e_xdp_xmit, .ndo_xdp_xmit = mlx5e_xdp_xmit,
.ndo_xsk_async_xmit = mlx5e_xsk_async_xmit, .ndo_xsk_wakeup = mlx5e_xsk_wakeup,
#ifdef CONFIG_MLX5_EN_ARFS #ifdef CONFIG_MLX5_EN_ARFS
.ndo_rx_flow_steer = mlx5e_rx_flow_steer, .ndo_rx_flow_steer = mlx5e_rx_flow_steer,
#endif #endif
......
...@@ -692,8 +692,11 @@ bool mlx5e_post_rx_mpwqes(struct mlx5e_rq *rq) ...@@ -692,8 +692,11 @@ bool mlx5e_post_rx_mpwqes(struct mlx5e_rq *rq)
rq->mpwqe.umr_in_progress += rq->mpwqe.umr_last_bulk; rq->mpwqe.umr_in_progress += rq->mpwqe.umr_last_bulk;
rq->mpwqe.actual_wq_head = head; rq->mpwqe.actual_wq_head = head;
/* If XSK Fill Ring doesn't have enough frames, busy poll by /* If XSK Fill Ring doesn't have enough frames, report the error, so
* rescheduling the NAPI poll. * that one of the actions can be performed:
* 1. If need_wakeup is used, signal that the application has to kick
* the driver when it refills the Fill Ring.
* 2. Otherwise, busy poll by rescheduling the NAPI poll.
*/ */
if (unlikely(alloc_err == -ENOMEM && rq->umem)) if (unlikely(alloc_err == -ENOMEM && rq->umem))
return true; return true;
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#include <linux/irq.h> #include <linux/irq.h>
#include "en.h" #include "en.h"
#include "en/xdp.h" #include "en/xdp.h"
#include "en/xsk/rx.h"
#include "en/xsk/tx.h" #include "en/xsk/tx.h"
static inline bool mlx5e_channel_no_affinity_change(struct mlx5e_channel *c) static inline bool mlx5e_channel_no_affinity_change(struct mlx5e_channel *c)
...@@ -81,6 +82,29 @@ void mlx5e_trigger_irq(struct mlx5e_icosq *sq) ...@@ -81,6 +82,29 @@ void mlx5e_trigger_irq(struct mlx5e_icosq *sq)
mlx5e_notify_hw(wq, sq->pc, sq->uar_map, &nopwqe->ctrl); mlx5e_notify_hw(wq, sq->pc, sq->uar_map, &nopwqe->ctrl);
} }
static bool mlx5e_napi_xsk_post(struct mlx5e_xdpsq *xsksq, struct mlx5e_rq *xskrq)
{
bool busy_xsk = false, xsk_rx_alloc_err;
/* Handle the race between the application querying need_wakeup and the
* driver setting it:
* 1. Update need_wakeup both before and after the TX. If it goes to
* "yes", it can only happen with the first update.
* 2. If the application queried need_wakeup before we set it, the
* packets will be transmitted anyway, even w/o a wakeup.
* 3. Give a chance to clear need_wakeup after new packets were queued
* for TX.
*/
mlx5e_xsk_update_tx_wakeup(xsksq);
busy_xsk |= mlx5e_xsk_tx(xsksq, MLX5E_TX_XSK_POLL_BUDGET);
mlx5e_xsk_update_tx_wakeup(xsksq);
xsk_rx_alloc_err = xskrq->post_wqes(xskrq);
busy_xsk |= mlx5e_xsk_update_rx_wakeup(xskrq, xsk_rx_alloc_err);
return busy_xsk;
}
int mlx5e_napi_poll(struct napi_struct *napi, int budget) int mlx5e_napi_poll(struct napi_struct *napi, int budget)
{ {
struct mlx5e_channel *c = container_of(napi, struct mlx5e_channel, struct mlx5e_channel *c = container_of(napi, struct mlx5e_channel,
...@@ -122,8 +146,7 @@ int mlx5e_napi_poll(struct napi_struct *napi, int budget) ...@@ -122,8 +146,7 @@ int mlx5e_napi_poll(struct napi_struct *napi, int budget)
if (xsk_open) { if (xsk_open) {
mlx5e_poll_ico_cq(&c->xskicosq.cq); mlx5e_poll_ico_cq(&c->xskicosq.cq);
busy |= mlx5e_poll_xdpsq_cq(&xsksq->cq); busy |= mlx5e_poll_xdpsq_cq(&xsksq->cq);
busy_xsk |= mlx5e_xsk_tx(xsksq, MLX5E_TX_XSK_POLL_BUDGET); busy_xsk |= mlx5e_napi_xsk_post(xsksq, xskrq);
busy_xsk |= xskrq->post_wqes(xskrq);
} }
busy |= busy_xsk; busy |= busy_xsk;
......
...@@ -901,6 +901,10 @@ struct netdev_bpf { ...@@ -901,6 +901,10 @@ struct netdev_bpf {
}; };
}; };
/* Flags for ndo_xsk_wakeup. */
#define XDP_WAKEUP_RX (1 << 0)
#define XDP_WAKEUP_TX (1 << 1)
#ifdef CONFIG_XFRM_OFFLOAD #ifdef CONFIG_XFRM_OFFLOAD
struct xfrmdev_ops { struct xfrmdev_ops {
int (*xdo_dev_state_add) (struct xfrm_state *x); int (*xdo_dev_state_add) (struct xfrm_state *x);
...@@ -1227,6 +1231,12 @@ struct tlsdev_ops; ...@@ -1227,6 +1231,12 @@ struct tlsdev_ops;
* that got dropped are freed/returned via xdp_return_frame(). * that got dropped are freed/returned via xdp_return_frame().
* Returns negative number, means general error invoking ndo, meaning * Returns negative number, means general error invoking ndo, meaning
* no frames were xmit'ed and core-caller will free all frames. * no frames were xmit'ed and core-caller will free all frames.
* int (*ndo_xsk_wakeup)(struct net_device *dev, u32 queue_id, u32 flags);
* This function is used to wake up the softirq, ksoftirqd or kthread
* responsible for sending and/or receiving packets on a specific
* queue id bound to an AF_XDP socket. The flags field specifies if
* only RX, only Tx, or both should be woken up using the flags
* XDP_WAKEUP_RX and XDP_WAKEUP_TX.
* struct devlink_port *(*ndo_get_devlink_port)(struct net_device *dev); * struct devlink_port *(*ndo_get_devlink_port)(struct net_device *dev);
* Get devlink port instance associated with a given netdev. * Get devlink port instance associated with a given netdev.
* Called with a reference on the netdevice and devlink locks only, * Called with a reference on the netdevice and devlink locks only,
...@@ -1426,8 +1436,8 @@ struct net_device_ops { ...@@ -1426,8 +1436,8 @@ struct net_device_ops {
int (*ndo_xdp_xmit)(struct net_device *dev, int n, int (*ndo_xdp_xmit)(struct net_device *dev, int n,
struct xdp_frame **xdp, struct xdp_frame **xdp,
u32 flags); u32 flags);
int (*ndo_xsk_async_xmit)(struct net_device *dev, int (*ndo_xsk_wakeup)(struct net_device *dev,
u32 queue_id); u32 queue_id, u32 flags);
struct devlink_port * (*ndo_get_devlink_port)(struct net_device *dev); struct devlink_port * (*ndo_get_devlink_port)(struct net_device *dev);
}; };
......
...@@ -27,6 +27,9 @@ struct xdp_umem_fq_reuse { ...@@ -27,6 +27,9 @@ struct xdp_umem_fq_reuse {
u64 handles[]; u64 handles[];
}; };
/* Flags for the umem flags field. */
#define XDP_UMEM_USES_NEED_WAKEUP (1 << 0)
struct xdp_umem { struct xdp_umem {
struct xsk_queue *fq; struct xsk_queue *fq;
struct xsk_queue *cq; struct xsk_queue *cq;
...@@ -41,10 +44,12 @@ struct xdp_umem { ...@@ -41,10 +44,12 @@ struct xdp_umem {
struct work_struct work; struct work_struct work;
struct page **pgs; struct page **pgs;
u32 npgs; u32 npgs;
u16 queue_id;
u8 need_wakeup;
u8 flags;
int id; int id;
struct net_device *dev; struct net_device *dev;
struct xdp_umem_fq_reuse *fq_reuse; struct xdp_umem_fq_reuse *fq_reuse;
u16 queue_id;
bool zc; bool zc;
spinlock_t xsk_list_lock; spinlock_t xsk_list_lock;
struct list_head xsk_list; struct list_head xsk_list;
...@@ -95,6 +100,11 @@ struct xdp_umem_fq_reuse *xsk_reuseq_swap(struct xdp_umem *umem, ...@@ -95,6 +100,11 @@ struct xdp_umem_fq_reuse *xsk_reuseq_swap(struct xdp_umem *umem,
struct xdp_umem_fq_reuse *newq); struct xdp_umem_fq_reuse *newq);
void xsk_reuseq_free(struct xdp_umem_fq_reuse *rq); void xsk_reuseq_free(struct xdp_umem_fq_reuse *rq);
struct xdp_umem *xdp_get_umem_from_qid(struct net_device *dev, u16 queue_id); struct xdp_umem *xdp_get_umem_from_qid(struct net_device *dev, u16 queue_id);
void xsk_set_rx_need_wakeup(struct xdp_umem *umem);
void xsk_set_tx_need_wakeup(struct xdp_umem *umem);
void xsk_clear_rx_need_wakeup(struct xdp_umem *umem);
void xsk_clear_tx_need_wakeup(struct xdp_umem *umem);
bool xsk_umem_uses_need_wakeup(struct xdp_umem *umem);
static inline char *xdp_umem_get_data(struct xdp_umem *umem, u64 addr) static inline char *xdp_umem_get_data(struct xdp_umem *umem, u64 addr)
{ {
...@@ -241,6 +251,27 @@ static inline void xsk_umem_fq_reuse(struct xdp_umem *umem, u64 addr) ...@@ -241,6 +251,27 @@ static inline void xsk_umem_fq_reuse(struct xdp_umem *umem, u64 addr)
{ {
} }
static inline void xsk_set_rx_need_wakeup(struct xdp_umem *umem)
{
}
static inline void xsk_set_tx_need_wakeup(struct xdp_umem *umem)
{
}
static inline void xsk_clear_rx_need_wakeup(struct xdp_umem *umem)
{
}
static inline void xsk_clear_tx_need_wakeup(struct xdp_umem *umem)
{
}
static inline bool xsk_umem_uses_need_wakeup(struct xdp_umem *umem)
{
return false;
}
#endif /* CONFIG_XDP_SOCKETS */ #endif /* CONFIG_XDP_SOCKETS */
#endif /* _LINUX_XDP_SOCK_H */ #endif /* _LINUX_XDP_SOCK_H */
...@@ -16,6 +16,15 @@ ...@@ -16,6 +16,15 @@
#define XDP_SHARED_UMEM (1 << 0) #define XDP_SHARED_UMEM (1 << 0)
#define XDP_COPY (1 << 1) /* Force copy-mode */ #define XDP_COPY (1 << 1) /* Force copy-mode */
#define XDP_ZEROCOPY (1 << 2) /* Force zero-copy mode */ #define XDP_ZEROCOPY (1 << 2) /* Force zero-copy mode */
/* If this option is set, the driver might go sleep and in that case
* the XDP_RING_NEED_WAKEUP flag in the fill and/or Tx rings will be
* set. If it is set, the application need to explicitly wake up the
* driver with a poll() (Rx and Tx) or sendto() (Tx only). If you are
* running the driver and the application on the same core, you should
* use this option so that the kernel will yield to the user space
* application.
*/
#define XDP_USE_NEED_WAKEUP (1 << 3)
struct sockaddr_xdp { struct sockaddr_xdp {
__u16 sxdp_family; __u16 sxdp_family;
...@@ -25,10 +34,14 @@ struct sockaddr_xdp { ...@@ -25,10 +34,14 @@ struct sockaddr_xdp {
__u32 sxdp_shared_umem_fd; __u32 sxdp_shared_umem_fd;
}; };
/* XDP_RING flags */
#define XDP_RING_NEED_WAKEUP (1 << 0)
struct xdp_ring_offset { struct xdp_ring_offset {
__u64 producer; __u64 producer;
__u64 consumer; __u64 consumer;
__u64 desc; __u64 desc;
__u64 flags;
}; };
struct xdp_mmap_offsets { struct xdp_mmap_offsets {
......
...@@ -106,14 +106,22 @@ int xdp_umem_assign_dev(struct xdp_umem *umem, struct net_device *dev, ...@@ -106,14 +106,22 @@ int xdp_umem_assign_dev(struct xdp_umem *umem, struct net_device *dev,
umem->dev = dev; umem->dev = dev;
umem->queue_id = queue_id; umem->queue_id = queue_id;
if (flags & XDP_USE_NEED_WAKEUP) {
umem->flags |= XDP_UMEM_USES_NEED_WAKEUP;
/* Tx needs to be explicitly woken up the first time.
* Also for supporting drivers that do not implement this
* feature. They will always have to call sendto().
*/
xsk_set_tx_need_wakeup(umem);
}
dev_hold(dev); dev_hold(dev);
if (force_copy) if (force_copy)
/* For copy-mode, we are done. */ /* For copy-mode, we are done. */
return 0; return 0;
if (!dev->netdev_ops->ndo_bpf || if (!dev->netdev_ops->ndo_bpf || !dev->netdev_ops->ndo_xsk_wakeup) {
!dev->netdev_ops->ndo_xsk_async_xmit) {
err = -EOPNOTSUPP; err = -EOPNOTSUPP;
goto err_unreg_umem; goto err_unreg_umem;
} }
......
...@@ -55,6 +55,66 @@ void xsk_umem_discard_addr(struct xdp_umem *umem) ...@@ -55,6 +55,66 @@ void xsk_umem_discard_addr(struct xdp_umem *umem)
} }
EXPORT_SYMBOL(xsk_umem_discard_addr); EXPORT_SYMBOL(xsk_umem_discard_addr);
void xsk_set_rx_need_wakeup(struct xdp_umem *umem)
{
if (umem->need_wakeup & XDP_WAKEUP_RX)
return;
umem->fq->ring->flags |= XDP_RING_NEED_WAKEUP;
umem->need_wakeup |= XDP_WAKEUP_RX;
}
EXPORT_SYMBOL(xsk_set_rx_need_wakeup);
void xsk_set_tx_need_wakeup(struct xdp_umem *umem)
{
struct xdp_sock *xs;
if (umem->need_wakeup & XDP_WAKEUP_TX)
return;
rcu_read_lock();
list_for_each_entry_rcu(xs, &umem->xsk_list, list) {
xs->tx->ring->flags |= XDP_RING_NEED_WAKEUP;
}
rcu_read_unlock();
umem->need_wakeup |= XDP_WAKEUP_TX;
}
EXPORT_SYMBOL(xsk_set_tx_need_wakeup);
void xsk_clear_rx_need_wakeup(struct xdp_umem *umem)
{
if (!(umem->need_wakeup & XDP_WAKEUP_RX))
return;
umem->fq->ring->flags &= ~XDP_RING_NEED_WAKEUP;
umem->need_wakeup &= ~XDP_WAKEUP_RX;
}
EXPORT_SYMBOL(xsk_clear_rx_need_wakeup);
void xsk_clear_tx_need_wakeup(struct xdp_umem *umem)
{
struct xdp_sock *xs;
if (!(umem->need_wakeup & XDP_WAKEUP_TX))
return;
rcu_read_lock();
list_for_each_entry_rcu(xs, &umem->xsk_list, list) {
xs->tx->ring->flags &= ~XDP_RING_NEED_WAKEUP;
}
rcu_read_unlock();
umem->need_wakeup &= ~XDP_WAKEUP_TX;
}
EXPORT_SYMBOL(xsk_clear_tx_need_wakeup);
bool xsk_umem_uses_need_wakeup(struct xdp_umem *umem)
{
return umem->flags & XDP_UMEM_USES_NEED_WAKEUP;
}
EXPORT_SYMBOL(xsk_umem_uses_need_wakeup);
static int __xsk_rcv(struct xdp_sock *xs, struct xdp_buff *xdp, u32 len) static int __xsk_rcv(struct xdp_sock *xs, struct xdp_buff *xdp, u32 len)
{ {
void *to_buf, *from_buf; void *to_buf, *from_buf;
...@@ -212,7 +272,8 @@ static int xsk_zc_xmit(struct sock *sk) ...@@ -212,7 +272,8 @@ static int xsk_zc_xmit(struct sock *sk)
struct xdp_sock *xs = xdp_sk(sk); struct xdp_sock *xs = xdp_sk(sk);
struct net_device *dev = xs->dev; struct net_device *dev = xs->dev;
return dev->netdev_ops->ndo_xsk_async_xmit(dev, xs->queue_id); return dev->netdev_ops->ndo_xsk_wakeup(dev, xs->queue_id,
XDP_WAKEUP_TX);
} }
static void xsk_destruct_skb(struct sk_buff *skb) static void xsk_destruct_skb(struct sk_buff *skb)
...@@ -319,6 +380,12 @@ static unsigned int xsk_poll(struct file *file, struct socket *sock, ...@@ -319,6 +380,12 @@ static unsigned int xsk_poll(struct file *file, struct socket *sock,
unsigned int mask = datagram_poll(file, sock, wait); unsigned int mask = datagram_poll(file, sock, wait);
struct sock *sk = sock->sk; struct sock *sk = sock->sk;
struct xdp_sock *xs = xdp_sk(sk); struct xdp_sock *xs = xdp_sk(sk);
struct net_device *dev = xs->dev;
struct xdp_umem *umem = xs->umem;
if (umem->need_wakeup)
dev->netdev_ops->ndo_xsk_wakeup(dev, xs->queue_id,
umem->need_wakeup);
if (xs->rx && !xskq_empty_desc(xs->rx)) if (xs->rx && !xskq_empty_desc(xs->rx))
mask |= POLLIN | POLLRDNORM; mask |= POLLIN | POLLRDNORM;
...@@ -427,7 +494,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr *addr, int addr_len) ...@@ -427,7 +494,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
return -EINVAL; return -EINVAL;
flags = sxdp->sxdp_flags; flags = sxdp->sxdp_flags;
if (flags & ~(XDP_SHARED_UMEM | XDP_COPY | XDP_ZEROCOPY)) if (flags & ~(XDP_SHARED_UMEM | XDP_COPY | XDP_ZEROCOPY |
XDP_USE_NEED_WAKEUP))
return -EINVAL; return -EINVAL;
rtnl_lock(); rtnl_lock();
...@@ -454,7 +522,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr *addr, int addr_len) ...@@ -454,7 +522,8 @@ static int xsk_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
struct xdp_sock *umem_xs; struct xdp_sock *umem_xs;
struct socket *sock; struct socket *sock;
if ((flags & XDP_COPY) || (flags & XDP_ZEROCOPY)) { if ((flags & XDP_COPY) || (flags & XDP_ZEROCOPY) ||
(flags & XDP_USE_NEED_WAKEUP)) {
/* Cannot specify flags for shared sockets. */ /* Cannot specify flags for shared sockets. */
err = -EINVAL; err = -EINVAL;
goto out_unlock; goto out_unlock;
...@@ -549,6 +618,9 @@ static int xsk_setsockopt(struct socket *sock, int level, int optname, ...@@ -549,6 +618,9 @@ static int xsk_setsockopt(struct socket *sock, int level, int optname,
} }
q = (optname == XDP_TX_RING) ? &xs->tx : &xs->rx; q = (optname == XDP_TX_RING) ? &xs->tx : &xs->rx;
err = xsk_init_queue(entries, q, false); err = xsk_init_queue(entries, q, false);
if (!err && optname == XDP_TX_RING)
/* Tx needs to be explicitly woken up the first time */
xs->tx->ring->flags |= XDP_RING_NEED_WAKEUP;
mutex_unlock(&xs->mutex); mutex_unlock(&xs->mutex);
return err; return err;
} }
...@@ -610,6 +682,20 @@ static int xsk_setsockopt(struct socket *sock, int level, int optname, ...@@ -610,6 +682,20 @@ static int xsk_setsockopt(struct socket *sock, int level, int optname,
return -ENOPROTOOPT; return -ENOPROTOOPT;
} }
static void xsk_enter_rxtx_offsets(struct xdp_ring_offset_v1 *ring)
{
ring->producer = offsetof(struct xdp_rxtx_ring, ptrs.producer);
ring->consumer = offsetof(struct xdp_rxtx_ring, ptrs.consumer);
ring->desc = offsetof(struct xdp_rxtx_ring, desc);
}
static void xsk_enter_umem_offsets(struct xdp_ring_offset_v1 *ring)
{
ring->producer = offsetof(struct xdp_umem_ring, ptrs.producer);
ring->consumer = offsetof(struct xdp_umem_ring, ptrs.consumer);
ring->desc = offsetof(struct xdp_umem_ring, desc);
}
static int xsk_getsockopt(struct socket *sock, int level, int optname, static int xsk_getsockopt(struct socket *sock, int level, int optname,
char __user *optval, int __user *optlen) char __user *optval, int __user *optlen)
{ {
...@@ -649,26 +735,49 @@ static int xsk_getsockopt(struct socket *sock, int level, int optname, ...@@ -649,26 +735,49 @@ static int xsk_getsockopt(struct socket *sock, int level, int optname,
case XDP_MMAP_OFFSETS: case XDP_MMAP_OFFSETS:
{ {
struct xdp_mmap_offsets off; struct xdp_mmap_offsets off;
struct xdp_mmap_offsets_v1 off_v1;
bool flags_supported = true;
void *to_copy;
if (len < sizeof(off)) if (len < sizeof(off_v1))
return -EINVAL; return -EINVAL;
else if (len < sizeof(off))
flags_supported = false;
if (flags_supported) {
/* xdp_ring_offset is identical to xdp_ring_offset_v1
* except for the flags field added to the end.
*/
xsk_enter_rxtx_offsets((struct xdp_ring_offset_v1 *)
&off.rx);
xsk_enter_rxtx_offsets((struct xdp_ring_offset_v1 *)
&off.tx);
xsk_enter_umem_offsets((struct xdp_ring_offset_v1 *)
&off.fr);
xsk_enter_umem_offsets((struct xdp_ring_offset_v1 *)
&off.cr);
off.rx.flags = offsetof(struct xdp_rxtx_ring,
ptrs.flags);
off.tx.flags = offsetof(struct xdp_rxtx_ring,
ptrs.flags);
off.fr.flags = offsetof(struct xdp_umem_ring,
ptrs.flags);
off.cr.flags = offsetof(struct xdp_umem_ring,
ptrs.flags);
len = sizeof(off);
to_copy = &off;
} else {
xsk_enter_rxtx_offsets(&off_v1.rx);
xsk_enter_rxtx_offsets(&off_v1.tx);
xsk_enter_umem_offsets(&off_v1.fr);
xsk_enter_umem_offsets(&off_v1.cr);
len = sizeof(off_v1);
to_copy = &off_v1;
}
off.rx.producer = offsetof(struct xdp_rxtx_ring, ptrs.producer); if (copy_to_user(optval, to_copy, len))
off.rx.consumer = offsetof(struct xdp_rxtx_ring, ptrs.consumer);
off.rx.desc = offsetof(struct xdp_rxtx_ring, desc);
off.tx.producer = offsetof(struct xdp_rxtx_ring, ptrs.producer);
off.tx.consumer = offsetof(struct xdp_rxtx_ring, ptrs.consumer);
off.tx.desc = offsetof(struct xdp_rxtx_ring, desc);
off.fr.producer = offsetof(struct xdp_umem_ring, ptrs.producer);
off.fr.consumer = offsetof(struct xdp_umem_ring, ptrs.consumer);
off.fr.desc = offsetof(struct xdp_umem_ring, desc);
off.cr.producer = offsetof(struct xdp_umem_ring, ptrs.producer);
off.cr.consumer = offsetof(struct xdp_umem_ring, ptrs.consumer);
off.cr.desc = offsetof(struct xdp_umem_ring, desc);
len = sizeof(off);
if (copy_to_user(optval, &off, len))
return -EFAULT; return -EFAULT;
if (put_user(len, optlen)) if (put_user(len, optlen))
return -EFAULT; return -EFAULT;
......
...@@ -4,6 +4,19 @@ ...@@ -4,6 +4,19 @@
#ifndef XSK_H_ #ifndef XSK_H_
#define XSK_H_ #define XSK_H_
struct xdp_ring_offset_v1 {
__u64 producer;
__u64 consumer;
__u64 desc;
};
struct xdp_mmap_offsets_v1 {
struct xdp_ring_offset_v1 rx;
struct xdp_ring_offset_v1 tx;
struct xdp_ring_offset_v1 fr;
struct xdp_ring_offset_v1 cr;
};
static inline struct xdp_sock *xdp_sk(struct sock *sk) static inline struct xdp_sock *xdp_sk(struct sock *sk)
{ {
return (struct xdp_sock *)sk; return (struct xdp_sock *)sk;
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
struct xdp_ring { struct xdp_ring {
u32 producer ____cacheline_aligned_in_smp; u32 producer ____cacheline_aligned_in_smp;
u32 consumer ____cacheline_aligned_in_smp; u32 consumer ____cacheline_aligned_in_smp;
u32 flags;
}; };
/* Used for the RX and TX queues for packets */ /* Used for the RX and TX queues for packets */
......
...@@ -67,8 +67,10 @@ static int opt_ifindex; ...@@ -67,8 +67,10 @@ static int opt_ifindex;
static int opt_queue; static int opt_queue;
static int opt_poll; static int opt_poll;
static int opt_interval = 1; static int opt_interval = 1;
static u32 opt_xdp_bind_flags; static u32 opt_xdp_bind_flags = XDP_USE_NEED_WAKEUP;
static int opt_xsk_frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE; static int opt_xsk_frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE;
static int opt_timeout = 1000;
static bool opt_need_wakeup = true;
static __u32 prog_id; static __u32 prog_id;
struct xsk_umem_info { struct xsk_umem_info {
...@@ -352,6 +354,7 @@ static struct option long_options[] = { ...@@ -352,6 +354,7 @@ static struct option long_options[] = {
{"zero-copy", no_argument, 0, 'z'}, {"zero-copy", no_argument, 0, 'z'},
{"copy", no_argument, 0, 'c'}, {"copy", no_argument, 0, 'c'},
{"frame-size", required_argument, 0, 'f'}, {"frame-size", required_argument, 0, 'f'},
{"no-need-wakeup", no_argument, 0, 'm'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
...@@ -372,6 +375,7 @@ static void usage(const char *prog) ...@@ -372,6 +375,7 @@ static void usage(const char *prog)
" -z, --zero-copy Force zero-copy mode.\n" " -z, --zero-copy Force zero-copy mode.\n"
" -c, --copy Force copy mode.\n" " -c, --copy Force copy mode.\n"
" -f, --frame-size=n Set the frame size (must be a power of two, default is %d).\n" " -f, --frame-size=n Set the frame size (must be a power of two, default is %d).\n"
" -m, --no-need-wakeup Turn off use of driver need wakeup flag.\n"
"\n"; "\n";
fprintf(stderr, str, prog, XSK_UMEM__DEFAULT_FRAME_SIZE); fprintf(stderr, str, prog, XSK_UMEM__DEFAULT_FRAME_SIZE);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
...@@ -384,8 +388,9 @@ static void parse_command_line(int argc, char **argv) ...@@ -384,8 +388,9 @@ static void parse_command_line(int argc, char **argv)
opterr = 0; opterr = 0;
for (;;) { for (;;) {
c = getopt_long(argc, argv, "Frtli:q:psSNn:czf:", long_options,
&option_index); c = getopt_long(argc, argv, "Frtli:q:psSNn:czf:m",
long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
...@@ -429,6 +434,9 @@ static void parse_command_line(int argc, char **argv) ...@@ -429,6 +434,9 @@ static void parse_command_line(int argc, char **argv)
break; break;
case 'f': case 'f':
opt_xsk_frame_size = atoi(optarg); opt_xsk_frame_size = atoi(optarg);
case 'm':
opt_need_wakeup = false;
opt_xdp_bind_flags &= ~XDP_USE_NEED_WAKEUP;
break; break;
default: default:
usage(basename(argv[0])); usage(basename(argv[0]));
...@@ -459,7 +467,8 @@ static void kick_tx(struct xsk_socket_info *xsk) ...@@ -459,7 +467,8 @@ static void kick_tx(struct xsk_socket_info *xsk)
exit_with_error(errno); exit_with_error(errno);
} }
static inline void complete_tx_l2fwd(struct xsk_socket_info *xsk) static inline void complete_tx_l2fwd(struct xsk_socket_info *xsk,
struct pollfd *fds)
{ {
u32 idx_cq = 0, idx_fq = 0; u32 idx_cq = 0, idx_fq = 0;
unsigned int rcvd; unsigned int rcvd;
...@@ -468,7 +477,9 @@ static inline void complete_tx_l2fwd(struct xsk_socket_info *xsk) ...@@ -468,7 +477,9 @@ static inline void complete_tx_l2fwd(struct xsk_socket_info *xsk)
if (!xsk->outstanding_tx) if (!xsk->outstanding_tx)
return; return;
kick_tx(xsk); if (!opt_need_wakeup || xsk_ring_prod__needs_wakeup(&xsk->tx))
kick_tx(xsk);
ndescs = (xsk->outstanding_tx > BATCH_SIZE) ? BATCH_SIZE : ndescs = (xsk->outstanding_tx > BATCH_SIZE) ? BATCH_SIZE :
xsk->outstanding_tx; xsk->outstanding_tx;
...@@ -482,6 +493,8 @@ static inline void complete_tx_l2fwd(struct xsk_socket_info *xsk) ...@@ -482,6 +493,8 @@ static inline void complete_tx_l2fwd(struct xsk_socket_info *xsk)
while (ret != rcvd) { while (ret != rcvd) {
if (ret < 0) if (ret < 0)
exit_with_error(-ret); exit_with_error(-ret);
if (xsk_ring_prod__needs_wakeup(&xsk->umem->fq))
ret = poll(fds, num_socks, opt_timeout);
ret = xsk_ring_prod__reserve(&xsk->umem->fq, rcvd, ret = xsk_ring_prod__reserve(&xsk->umem->fq, rcvd,
&idx_fq); &idx_fq);
} }
...@@ -505,7 +518,8 @@ static inline void complete_tx_only(struct xsk_socket_info *xsk) ...@@ -505,7 +518,8 @@ static inline void complete_tx_only(struct xsk_socket_info *xsk)
if (!xsk->outstanding_tx) if (!xsk->outstanding_tx)
return; return;
kick_tx(xsk); if (!opt_need_wakeup || xsk_ring_prod__needs_wakeup(&xsk->tx))
kick_tx(xsk);
rcvd = xsk_ring_cons__peek(&xsk->umem->cq, BATCH_SIZE, &idx); rcvd = xsk_ring_cons__peek(&xsk->umem->cq, BATCH_SIZE, &idx);
if (rcvd > 0) { if (rcvd > 0) {
...@@ -515,20 +529,25 @@ static inline void complete_tx_only(struct xsk_socket_info *xsk) ...@@ -515,20 +529,25 @@ static inline void complete_tx_only(struct xsk_socket_info *xsk)
} }
} }
static void rx_drop(struct xsk_socket_info *xsk) static void rx_drop(struct xsk_socket_info *xsk, struct pollfd *fds)
{ {
unsigned int rcvd, i; unsigned int rcvd, i;
u32 idx_rx = 0, idx_fq = 0; u32 idx_rx = 0, idx_fq = 0;
int ret; int ret;
rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx); rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx);
if (!rcvd) if (!rcvd) {
if (xsk_ring_prod__needs_wakeup(&xsk->umem->fq))
ret = poll(fds, num_socks, opt_timeout);
return; return;
}
ret = xsk_ring_prod__reserve(&xsk->umem->fq, rcvd, &idx_fq); ret = xsk_ring_prod__reserve(&xsk->umem->fq, rcvd, &idx_fq);
while (ret != rcvd) { while (ret != rcvd) {
if (ret < 0) if (ret < 0)
exit_with_error(-ret); exit_with_error(-ret);
if (xsk_ring_prod__needs_wakeup(&xsk->umem->fq))
ret = poll(fds, num_socks, opt_timeout);
ret = xsk_ring_prod__reserve(&xsk->umem->fq, rcvd, &idx_fq); ret = xsk_ring_prod__reserve(&xsk->umem->fq, rcvd, &idx_fq);
} }
...@@ -549,42 +568,65 @@ static void rx_drop(struct xsk_socket_info *xsk) ...@@ -549,42 +568,65 @@ static void rx_drop(struct xsk_socket_info *xsk)
static void rx_drop_all(void) static void rx_drop_all(void)
{ {
struct pollfd fds[MAX_SOCKS + 1]; struct pollfd fds[MAX_SOCKS + 1];
int i, ret, timeout, nfds = 1; int i, ret;
memset(fds, 0, sizeof(fds)); memset(fds, 0, sizeof(fds));
for (i = 0; i < num_socks; i++) { for (i = 0; i < num_socks; i++) {
fds[i].fd = xsk_socket__fd(xsks[i]->xsk); fds[i].fd = xsk_socket__fd(xsks[i]->xsk);
fds[i].events = POLLIN; fds[i].events = POLLIN;
timeout = 1000; /* 1sn */
} }
for (;;) { for (;;) {
if (opt_poll) { if (opt_poll) {
ret = poll(fds, nfds, timeout); ret = poll(fds, num_socks, opt_timeout);
if (ret <= 0) if (ret <= 0)
continue; continue;
} }
for (i = 0; i < num_socks; i++) for (i = 0; i < num_socks; i++)
rx_drop(xsks[i]); rx_drop(xsks[i], fds);
}
}
static void tx_only(struct xsk_socket_info *xsk, u32 frame_nb)
{
u32 idx;
if (xsk_ring_prod__reserve(&xsk->tx, BATCH_SIZE, &idx) == BATCH_SIZE) {
unsigned int i;
for (i = 0; i < BATCH_SIZE; i++) {
xsk_ring_prod__tx_desc(&xsk->tx, idx + i)->addr =
(frame_nb + i) << XSK_UMEM__DEFAULT_FRAME_SHIFT;
xsk_ring_prod__tx_desc(&xsk->tx, idx + i)->len =
sizeof(pkt_data) - 1;
}
xsk_ring_prod__submit(&xsk->tx, BATCH_SIZE);
xsk->outstanding_tx += BATCH_SIZE;
frame_nb += BATCH_SIZE;
frame_nb %= NUM_FRAMES;
} }
complete_tx_only(xsk);
} }
static void tx_only(struct xsk_socket_info *xsk) static void tx_only_all(void)
{ {
int timeout, ret, nfds = 1; struct pollfd fds[MAX_SOCKS];
struct pollfd fds[nfds + 1]; u32 frame_nb[MAX_SOCKS] = {};
u32 idx, frame_nb = 0; int i, ret;
memset(fds, 0, sizeof(fds)); memset(fds, 0, sizeof(fds));
fds[0].fd = xsk_socket__fd(xsk->xsk); for (i = 0; i < num_socks; i++) {
fds[0].events = POLLOUT; fds[0].fd = xsk_socket__fd(xsks[i]->xsk);
timeout = 1000; /* 1sn */ fds[0].events = POLLOUT;
}
for (;;) { for (;;) {
if (opt_poll) { if (opt_poll) {
ret = poll(fds, nfds, timeout); ret = poll(fds, num_socks, opt_timeout);
if (ret <= 0) if (ret <= 0)
continue; continue;
...@@ -592,69 +634,75 @@ static void tx_only(struct xsk_socket_info *xsk) ...@@ -592,69 +634,75 @@ static void tx_only(struct xsk_socket_info *xsk)
continue; continue;
} }
if (xsk_ring_prod__reserve(&xsk->tx, BATCH_SIZE, &idx) == for (i = 0; i < num_socks; i++)
BATCH_SIZE) { tx_only(xsks[i], frame_nb[i]);
unsigned int i;
for (i = 0; i < BATCH_SIZE; i++) {
xsk_ring_prod__tx_desc(&xsk->tx, idx + i)->addr
= (frame_nb + i) * opt_xsk_frame_size;
xsk_ring_prod__tx_desc(&xsk->tx, idx + i)->len =
sizeof(pkt_data) - 1;
}
xsk_ring_prod__submit(&xsk->tx, BATCH_SIZE);
xsk->outstanding_tx += BATCH_SIZE;
frame_nb += BATCH_SIZE;
frame_nb %= NUM_FRAMES;
}
complete_tx_only(xsk);
} }
} }
static void l2fwd(struct xsk_socket_info *xsk) static void l2fwd(struct xsk_socket_info *xsk, struct pollfd *fds)
{ {
for (;;) { unsigned int rcvd, i;
unsigned int rcvd, i; u32 idx_rx = 0, idx_tx = 0;
u32 idx_rx = 0, idx_tx = 0; int ret;
int ret;
for (;;) { complete_tx_l2fwd(xsk, fds);
complete_tx_l2fwd(xsk);
rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx);
&idx_rx); if (!rcvd) {
if (rcvd > 0) if (xsk_ring_prod__needs_wakeup(&xsk->umem->fq))
break; ret = poll(fds, num_socks, opt_timeout);
} return;
}
ret = xsk_ring_prod__reserve(&xsk->tx, rcvd, &idx_tx);
while (ret != rcvd) {
if (ret < 0)
exit_with_error(-ret);
if (xsk_ring_prod__needs_wakeup(&xsk->tx))
kick_tx(xsk);
ret = xsk_ring_prod__reserve(&xsk->tx, rcvd, &idx_tx); ret = xsk_ring_prod__reserve(&xsk->tx, rcvd, &idx_tx);
while (ret != rcvd) { }
if (ret < 0)
exit_with_error(-ret); for (i = 0; i < rcvd; i++) {
ret = xsk_ring_prod__reserve(&xsk->tx, rcvd, &idx_tx); u64 addr = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx)->addr;
} u32 len = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx++)->len;
char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr);
swap_mac_addresses(pkt);
for (i = 0; i < rcvd; i++) { hex_dump(pkt, len, addr);
u64 addr = xsk_ring_cons__rx_desc(&xsk->rx, xsk_ring_prod__tx_desc(&xsk->tx, idx_tx)->addr = addr;
idx_rx)->addr; xsk_ring_prod__tx_desc(&xsk->tx, idx_tx++)->len = len;
u32 len = xsk_ring_cons__rx_desc(&xsk->rx, }
idx_rx++)->len;
char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr);
swap_mac_addresses(pkt); xsk_ring_prod__submit(&xsk->tx, rcvd);
xsk_ring_cons__release(&xsk->rx, rcvd);
hex_dump(pkt, len, addr); xsk->rx_npkts += rcvd;
xsk_ring_prod__tx_desc(&xsk->tx, idx_tx)->addr = addr; xsk->outstanding_tx += rcvd;
xsk_ring_prod__tx_desc(&xsk->tx, idx_tx++)->len = len; }
}
static void l2fwd_all(void)
{
struct pollfd fds[MAX_SOCKS];
int i, ret;
memset(fds, 0, sizeof(fds));
for (i = 0; i < num_socks; i++) {
fds[i].fd = xsk_socket__fd(xsks[i]->xsk);
fds[i].events = POLLOUT | POLLIN;
}
xsk_ring_prod__submit(&xsk->tx, rcvd); for (;;) {
xsk_ring_cons__release(&xsk->rx, rcvd); if (opt_poll) {
ret = poll(fds, num_socks, opt_timeout);
if (ret <= 0)
continue;
}
xsk->rx_npkts += rcvd; for (i = 0; i < num_socks; i++)
xsk->outstanding_tx += rcvd; l2fwd(xsks[i], fds);
} }
} }
...@@ -705,9 +753,9 @@ int main(int argc, char **argv) ...@@ -705,9 +753,9 @@ int main(int argc, char **argv)
if (opt_bench == BENCH_RXDROP) if (opt_bench == BENCH_RXDROP)
rx_drop_all(); rx_drop_all();
else if (opt_bench == BENCH_TXONLY) else if (opt_bench == BENCH_TXONLY)
tx_only(xsks[0]); tx_only_all();
else else
l2fwd(xsks[0]); l2fwd_all();
return 0; return 0;
} }
...@@ -16,6 +16,15 @@ ...@@ -16,6 +16,15 @@
#define XDP_SHARED_UMEM (1 << 0) #define XDP_SHARED_UMEM (1 << 0)
#define XDP_COPY (1 << 1) /* Force copy-mode */ #define XDP_COPY (1 << 1) /* Force copy-mode */
#define XDP_ZEROCOPY (1 << 2) /* Force zero-copy mode */ #define XDP_ZEROCOPY (1 << 2) /* Force zero-copy mode */
/* If this option is set, the driver might go sleep and in that case
* the XDP_RING_NEED_WAKEUP flag in the fill and/or Tx rings will be
* set. If it is set, the application need to explicitly wake up the
* driver with a poll() (Rx and Tx) or sendto() (Tx only). If you are
* running the driver and the application on the same core, you should
* use this option so that the kernel will yield to the user space
* application.
*/
#define XDP_USE_NEED_WAKEUP (1 << 3)
struct sockaddr_xdp { struct sockaddr_xdp {
__u16 sxdp_family; __u16 sxdp_family;
...@@ -25,10 +34,14 @@ struct sockaddr_xdp { ...@@ -25,10 +34,14 @@ struct sockaddr_xdp {
__u32 sxdp_shared_umem_fd; __u32 sxdp_shared_umem_fd;
}; };
/* XDP_RING flags */
#define XDP_RING_NEED_WAKEUP (1 << 0)
struct xdp_ring_offset { struct xdp_ring_offset {
__u64 producer; __u64 producer;
__u64 consumer; __u64 consumer;
__u64 desc; __u64 desc;
__u64 flags;
}; };
struct xdp_mmap_offsets { struct xdp_mmap_offsets {
......
...@@ -224,6 +224,7 @@ int xsk_umem__create(struct xsk_umem **umem_ptr, void *umem_area, __u64 size, ...@@ -224,6 +224,7 @@ int xsk_umem__create(struct xsk_umem **umem_ptr, void *umem_area, __u64 size,
fill->size = umem->config.fill_size; fill->size = umem->config.fill_size;
fill->producer = map + off.fr.producer; fill->producer = map + off.fr.producer;
fill->consumer = map + off.fr.consumer; fill->consumer = map + off.fr.consumer;
fill->flags = map + off.fr.flags;
fill->ring = map + off.fr.desc; fill->ring = map + off.fr.desc;
fill->cached_cons = umem->config.fill_size; fill->cached_cons = umem->config.fill_size;
...@@ -241,6 +242,7 @@ int xsk_umem__create(struct xsk_umem **umem_ptr, void *umem_area, __u64 size, ...@@ -241,6 +242,7 @@ int xsk_umem__create(struct xsk_umem **umem_ptr, void *umem_area, __u64 size,
comp->size = umem->config.comp_size; comp->size = umem->config.comp_size;
comp->producer = map + off.cr.producer; comp->producer = map + off.cr.producer;
comp->consumer = map + off.cr.consumer; comp->consumer = map + off.cr.consumer;
comp->flags = map + off.cr.flags;
comp->ring = map + off.cr.desc; comp->ring = map + off.cr.desc;
*umem_ptr = umem; *umem_ptr = umem;
...@@ -564,6 +566,7 @@ int xsk_socket__create(struct xsk_socket **xsk_ptr, const char *ifname, ...@@ -564,6 +566,7 @@ int xsk_socket__create(struct xsk_socket **xsk_ptr, const char *ifname,
rx->size = xsk->config.rx_size; rx->size = xsk->config.rx_size;
rx->producer = rx_map + off.rx.producer; rx->producer = rx_map + off.rx.producer;
rx->consumer = rx_map + off.rx.consumer; rx->consumer = rx_map + off.rx.consumer;
rx->flags = rx_map + off.rx.flags;
rx->ring = rx_map + off.rx.desc; rx->ring = rx_map + off.rx.desc;
} }
xsk->rx = rx; xsk->rx = rx;
...@@ -583,6 +586,7 @@ int xsk_socket__create(struct xsk_socket **xsk_ptr, const char *ifname, ...@@ -583,6 +586,7 @@ int xsk_socket__create(struct xsk_socket **xsk_ptr, const char *ifname,
tx->size = xsk->config.tx_size; tx->size = xsk->config.tx_size;
tx->producer = tx_map + off.tx.producer; tx->producer = tx_map + off.tx.producer;
tx->consumer = tx_map + off.tx.consumer; tx->consumer = tx_map + off.tx.consumer;
tx->flags = tx_map + off.tx.flags;
tx->ring = tx_map + off.tx.desc; tx->ring = tx_map + off.tx.desc;
tx->cached_cons = xsk->config.tx_size; tx->cached_cons = xsk->config.tx_size;
} }
......
...@@ -32,6 +32,7 @@ struct name { \ ...@@ -32,6 +32,7 @@ struct name { \
__u32 *producer; \ __u32 *producer; \
__u32 *consumer; \ __u32 *consumer; \
void *ring; \ void *ring; \
__u32 *flags; \
} }
DEFINE_XSK_RING(xsk_ring_prod); DEFINE_XSK_RING(xsk_ring_prod);
...@@ -76,6 +77,11 @@ xsk_ring_cons__rx_desc(const struct xsk_ring_cons *rx, __u32 idx) ...@@ -76,6 +77,11 @@ xsk_ring_cons__rx_desc(const struct xsk_ring_cons *rx, __u32 idx)
return &descs[idx & rx->mask]; return &descs[idx & rx->mask];
} }
static inline int xsk_ring_prod__needs_wakeup(const struct xsk_ring_prod *r)
{
return *r->flags & XDP_RING_NEED_WAKEUP;
}
static inline __u32 xsk_prod_nb_free(struct xsk_ring_prod *r, __u32 nb) static inline __u32 xsk_prod_nb_free(struct xsk_ring_prod *r, __u32 nb)
{ {
__u32 free_entries = r->cached_cons - r->cached_prod; __u32 free_entries = r->cached_cons - r->cached_prod;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment