bnxt_en: Add XDP RSS hash metadata support
Subsystems (6)
v5 cover bnxt_en: Add XDP RSS hash metadata support
This series adds XDP RSS hash metadata extraction support for the bnxt_en driver and includes selftests to validate the functionality. I was able to test this on a BCM57414 NIC.
Patches 1-4 implement the driver support:
- Patch 1 adds bnxt_xdp_buff to wrap xdp_buff with
pointers to the hardware RX completion descriptors and the completion type. This is similar to other driver implementations.
- Patch 2 adds xmo_rx_hash callback for CMP_TYPE_RX_L2_CMP completions.
- Patch 3 moves bnxt_rss_ext_op to bnxt.h
- Patch 4 adds handling for CMP_TYPE_RX_L2_V3_CMP completions
Patches 5-6 add selftest coverage:
- Patch 5 refactors common BPF map helper functions out of the existing xdp.py selftest into a shared net/lib/py/bpf.py module.
- Patch 6 adds an XDP metadata selftest that loads an XDP program calling bpf_xdp_metadata_rx_hash. The test skips on devices without xdp-rx-metadata-features.
Changes from v4: https://lore.kernel.org/all/[email protected]/
- address RCT again
- make _format_hex_bytes private
- remove redundant bpf_map_set
- rebased on net-next
Changes from v3: https://lore.kernel.org/netdev/[email protected]/
- split out bnxt_rss_ext_op into header
- address RCT issue
- rebased on net-next
Changes from v2: https://lore.kernel.org/netdev/[email protected]/
- bnxt xdp: actually fixed RCT issues
- bnxt xdp: Added patch 3 to handle CMP_TYPE_RX_L2_V3_CMP completions.
Changes from v1: https://lore.kernel.org/netdev/[email protected]/
- selftests: moved tests into net/hw
- selftests: fix pylinter errors
- selftests: add check for no error count
- bnxt_xdp: followed RCT for variable declaration
- rebased on net-next
Chris J Arges (6):
bnxt_en: use bnxt_xdp_buff for xdp context
bnxt_en: Implement XDP RSS hash metadata extraction
bnxt_en: Move bnxt_rss_ext_op into header
bnxt_en: Implement XDP RSS hash metadata extraction for V3_CMP
selftests: net: move common xdp.py functions into lib
selftests: drv-net: xdp: Add rss_hash metadata tests
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 51 +++--- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 19 ++ drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c | 57 ++++++ drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.h | 10 ++ .../testing/selftests/drivers/net/hw/Makefile | 1 + .../drivers/net/hw/lib/py/__init__.py | 2 + .../selftests/drivers/net/hw/xdp_metadata.py | 146 ++++++++++++++++ .../selftests/drivers/net/lib/py/__init__.py | 2 + tools/testing/selftests/drivers/net/xdp.py | 96 +++-------- .../testing/selftests/net/lib/py/__init__.py | 2 + tools/testing/selftests/net/lib/py/bpf.py | 68 ++++++++ .../selftests/net/lib/xdp_metadata.bpf.c | 163 ++++++++++++++++++ 12 files changed, 516 insertions(+), 101 deletions(-) create mode 100644 tools/testing/selftests/drivers/net/hw/xdp_metadata.py create mode 100644 tools/testing/selftests/net/lib/py/bpf.py create mode 100644 tools/testing/selftests/net/lib/xdp_metadata.bpf.c
Signature · 1 line
2.43.0
PATCH v5 1/6 bnxt_en: use bnxt_xdp_buff for xdp context
This adds bnxt_xdp_buff which embeds the xdp_buff struct and stores pointers to hardware RX completion descriptors (rx_cmp and rx_cmp_ext) along with the completion type.
---
v4: moved bnxt_xdp declaration to follow RCT style
v5: moved bnxt_xdp again to follow RCT, try better at 80-col wrapping
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 29 ++++++++++++------- drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.h | 7 +++++ 2 files changed, 26 insertions(+), 10 deletions(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +19 -10
@@ -2112,13 +2112,13 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,u32 tmp_raw_cons = *raw_cons;u16 cons, prod, cp_cons =RING_CMP (tmp_raw_cons);struct skb_shared_info *sinfo; +struct bnxt_xdp_buff bnxt_xdp;struct bnxt_sw_rx_bd *rx_buf;unsigned int len;u8 *data_ptr, agg_bufs, cmp_type;bool xdp_active = false;dma_addr_t dma_addr;struct sk_buff *skb; -struct xdp_buff xdp;u32 flags, misc;u32 cmpl_ts;void *data;
@@ -2231,9 +2231,14 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr, dma_addr = rx_buf->mapping;if (bnxt_xdp_attached (bp, rxr)) { -bnxt_xdp_buff_init (bp, rxr, cons, data_ptr, len, &xdp); + bnxt_xdp.rxcmp = rxcmp; + bnxt_xdp.rxcmp1 = rxcmp1; + bnxt_xdp.cmp_type = cmp_type; + +bnxt_xdp_buff_init (bp, rxr, cons, data_ptr, len, &bnxt_xdp.xdp);if (agg_bufs) { -u32 frag_len =bnxt_rx_agg_netmems_xdp (bp, cpr, &xdp, +u32 frag_len =bnxt_rx_agg_netmems_xdp (bp, cpr, + &bnxt_xdp.xdp, cp_cons, agg_bufs, false);
@@ -2245,12 +2250,13 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr, }if (xdp_active) { -if (bnxt_rx_xdp (bp, rxr, cons, &xdp, data, &data_ptr, &len, event)) { +if (bnxt_rx_xdp (bp, rxr, cons, &bnxt_xdp.xdp, data, &data_ptr, + &len, event)) { rc =1 ; goto next_rx; } -if (xdp_buff_has_frags (&xdp)) { - sinfo =xdp_get_shared_info_from_buff (&xdp); +if (xdp_buff_has_frags (&bnxt_xdp.xdp)) { + sinfo =xdp_get_shared_info_from_buff (&bnxt_xdp.xdp); agg_bufs = sinfo->nr_frags; }else { agg_bufs =0 ;
@@ -2261,7 +2267,8 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,if (!xdp_active) skb =bnxt_copy_skb (bnapi, data_ptr, len, dma_addr);else - skb =bnxt_copy_xdp (bnapi, &xdp, len, dma_addr); + skb =bnxt_copy_xdp (bnapi, &bnxt_xdp.xdp, len, + dma_addr);bnxt_reuse_rx_data (rxr, cons, data);if (!skb) {if (agg_bufs) {
@@ -2269,7 +2276,8 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,bnxt_reuse_rx_agg_bufs (cpr, cp_cons,0 , agg_bufs, false); else - bnxt_xdp_buff_frags_free(rxr, &xdp); + bnxt_xdp_buff_frags_free(rxr, + &bnxt_xdp.xdp); } goto oom_next_rx; }
@@ -2293,10 +2301,11 @@ static int bnxt_rx_pkt(struct bnxt *bp, struct bnxt_cp_ring_info *cpr,if (!skb) goto oom_next_rx; }else { - skb =bnxt_xdp_build_skb (bp, skb, agg_bufs, rxr, &xdp); + skb =bnxt_xdp_build_skb (bp, skb, agg_bufs, + rxr, &bnxt_xdp.xdp);if (!skb) {/* we should be able to free the old skb here */ -bnxt_xdp_buff_frags_free (rxr, &xdp); +bnxt_xdp_buff_frags_free (rxr, &bnxt_xdp.xdp); goto oom_next_rx; } }
drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.h +7 -0
@@ -12,6 +12,13 @@DECLARE_STATIC_KEY_FALSE (bnxt_xdp_locking_key); +struct bnxt_xdp_buff { +struct xdp_buff xdp; +struct rx_cmp *rxcmp; +struct rx_cmp_ext *rxcmp1; +u8 cmp_type; +}; + struct bnxt_sw_tx_bd *bnxt_xmit_bd(struct bnxt *bp, struct bnxt_tx_ring_info *txr, dma_addr_t mapping, u32 len,
Signature · 1 line
2.43.0
PATCH v5 2/6 bnxt_en: Implement XDP RSS hash metadata extraction
In reply to Chris J ArgesAdd support for extracting RSS hash values and hash types from hardware completion descriptors in XDP programs for bnxt_en.
Add IP_TYPE definition for determining if completion is ipv4 or ipv6. In addition add ITYPE_ICMP flag for identifying ICMP completions.
---
v4: no changes
v5: no changes
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 5 ++ drivers/net/ethernet/broadcom/bnxt/bnxt.h | 2 + drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c | 47 +++++++++++++++++++ drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.h | 3 ++ 4 files changed, 57 insertions(+)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +5 -0
@@ -15911,6 +15911,10 @@ static const struct net_device_ops bnxt_netdev_ops = { .ndo_hwtstamp_set = bnxt_hwtstamp_set, }; +static const struct xdp_metadata_ops bnxt_xdp_metadata_ops = { + .xmo_rx_hash = bnxt_xdp_rx_hash, +}; +static void bnxt_get_queue_stats_rx (struct net_device *dev,int i,struct netdev_queue_stats_rx *stats) {
@@ -16795,6 +16799,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) goto init_err_free; dev->netdev_ops = &bnxt_netdev_ops; + dev->xdp_metadata_ops = &bnxt_xdp_metadata_ops; dev->stat_ops = &bnxt_stat_ops; dev->watchdog_timeo =BNXT_TX_TIMEOUT ; dev->ethtool_ops = &bnxt_ethtool_ops;
drivers/net/ethernet/broadcom/bnxt/bnxt.h +2 -0
@@ -232,6 +232,7 @@ struct rx_cmp {#define RX_CMP_FLAGS_ITYPE_UDP (3 << 12)#define RX_CMP_FLAGS_ITYPE_FCOE (4 << 12)#define RX_CMP_FLAGS_ITYPE_ROCE (5 << 12) +#define RX_CMP_FLAGS_ITYPE_ICMP (7 << 12)#define RX_CMP_FLAGS_ITYPE_PTP_WO_TS (8 << 12)#define RX_CMP_FLAGS_ITYPE_PTP_W_TS (9 << 12)#define RX_CMP_LEN (0xffff << 16)
@@ -311,6 +312,7 @@ struct rx_cmp_ext {#define RX_CMP_FLAGS2_T_IP_CS_CALC (0x1 << 2)#define RX_CMP_FLAGS2_T_L4_CS_CALC (0x1 << 3)#define RX_CMP_FLAGS2_META_FORMAT_VLAN (0x1 << 4) +#define RX_CMP_FLAGS2_IP_TYPE (0x1 << 8)__le32 rx_cmp_meta_data;#define RX_CMP_FLAGS2_METADATA_TCI_MASK 0xffff#define RX_CMP_FLAGS2_METADATA_VID_MASK 0xfff
drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c +47 -0
50 lines not on this page · raw message
drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.h +3 -0
@@ -41,4 +41,7 @@ void bnxt_xdp_buff_frags_free(struct bnxt_rx_ring_info *rxr,struct sk_buff *bnxt_xdp_build_skb (struct bnxt *bp,struct sk_buff *skb,u8 num_frags,struct bnxt_rx_ring_info *rxr,struct xdp_buff *xdp); +int bnxt_xdp_rx_hash (const struct xdp_md *ctx,u32 *hash, +enum xdp_rss_hash_type *rss_type); +#endif
Signature · 1 line
2.43.0
PATCH v5 3/6 bnxt_en: Move bnxt_rss_ext_op into header
In reply to Chris J ArgesThis allows bnxt_rss_ext_op to be used by other functions. In addition this modifies the rxcmp argument to be const since the function only reads from this structure.
---
v5: no changes
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 17 ----------------- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +0 -17
@@ -2077,23 +2077,6 @@ static struct sk_buff *bnxt_rx_vlan(struct sk_buff *skb, u8 cmp_type,return NULL ; } -static enum pkt_hash_types bnxt_rss_ext_op (struct bnxt *bp, -struct rx_cmp *rxcmp) -{ -u8 ext_op; - - ext_op =RX_CMP_V3_HASH_TYPE (bp, rxcmp); -switch (ext_op) { -case EXT_OP_INNER_4 : -case EXT_OP_OUTER_4 : -case EXT_OP_INNFL_3 : -case EXT_OP_OUTFL_3 : -return PKT_HASH_TYPE_L4 ; -default : -return PKT_HASH_TYPE_L3 ; - } -} -/* returns the following: * 1 - 1 packet successfully received * 0 - successful TPA_START, packet not completed yet
drivers/net/ethernet/broadcom/bnxt/bnxt.h +17 -0
@@ -2902,6 +2902,23 @@ static inline bool bnxt_sriov_cfg(struct bnxt *bp)#endif } +static inline enum pkt_hash_types bnxt_rss_ext_op (struct bnxt *bp, +const struct rx_cmp *rxcmp) +{ +u8 ext_op; + + ext_op =RX_CMP_V3_HASH_TYPE (bp, rxcmp); +switch (ext_op) { +case EXT_OP_INNER_4 : +case EXT_OP_OUTER_4 : +case EXT_OP_INNFL_3 : +case EXT_OP_OUTFL_3 : +return PKT_HASH_TYPE_L4 ; +default : +return PKT_HASH_TYPE_L3 ; + } +} +extern const u16 bnxt_bstore_to_trace[];extern const u16 bnxt_lhint_arr[];
Signature · 1 line
2.43.0
PATCH v5 4/6 bnxt_en: Implement XDP RSS hash metadata extraction for V3_CMP
In reply to Chris J ArgesThis adds another conditional when cmp_type is CMP_TYPE_RX_L2_V3_CMP for drivers that support this completion format.
This re-uses bnxt_rss_ext_op to provide similar functionality. One limitation is for L4 hash-types, protocol-specific bits can't be determined.
---
v5: no changes
--- drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c +10 -0
@@ -514,6 +514,16 @@ int bnxt_xdp_rx_hash(const struct xdp_md *ctx, u32 *hash,default :break ; } + }else if (xdp->cmp_type ==CMP_TYPE_RX_L2_V3_CMP ) { +struct bnxt *bp =netdev_priv (xdp->xdp.rxq->dev); + +if (rxcmp1->rx_cmp_flags2 &cpu_to_le32 (RX_CMP_FLAGS2_IP_TYPE )) + hash_type |=XDP_RSS_TYPE_L3_IPV6 ; +else + hash_type |=XDP_RSS_TYPE_L3_IPV4 ; + +if (bnxt_rss_ext_op (bp, rxcmp) ==PKT_HASH_TYPE_L4 ) + hash_type |=XDP_RSS_L4 ; } *rss_type = hash_type;
Signature · 1 line
2.43.0
PATCH v5 5/6 selftests: net: move common xdp.py functions into lib
In reply to Chris J ArgesThis moves a few functions which can be useful to other python programs that manipulate XDP programs. This also refactors xdp.py to use the refactored functions.
---
v4: rebase only
v5: make format_hex_bytes private, remove redundant bpf_map_set
--- .../selftests/drivers/net/lib/py/__init__.py | 2 + tools/testing/selftests/drivers/net/xdp.py | 96 +++++-------------- .../testing/selftests/net/lib/py/__init__.py | 2 + tools/testing/selftests/net/lib/py/bpf.py | 68 +++++++++++++ 4 files changed, 94 insertions(+), 74 deletions(-) create mode 100644 tools/testing/selftests/net/lib/py/bpf.py
tools/testing/selftests/drivers/net/lib/py/__init__.py +2 -0
@@ -24,6 +24,7 @@ try: from net.lib.py import CmdExitFailure from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \ fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, wait_file + from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \ ksft_setup, ksft_variants, KsftNamedVariant
@@ -37,6 +38,7 @@ try: "bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool", "fd_read_timeout", "ip", "rand_port", "rand_ports", "wait_port_listen", "wait_file", + "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run", "ksft_setup", "ksft_variants", "KsftNamedVariant",
tools/testing/selftests/drivers/net/xdp.py +22 -74
167 lines not on this page · raw message
tools/testing/selftests/net/lib/py/__init__.py +2 -0
@@ -15,6 +15,7 @@ from .nsim import NetdevSim, NetdevSimDev from .utils import CmdExitFailure, fd_read_timeout, cmd, bkg, defer, \ bpftool, ip, ethtool, bpftrace, rand_port, rand_ports, wait_port_listen, \ wait_file, tool +from .bpf import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from .ynl import NlError, NlctrlFamily, YnlFamily, \ EthtoolFamily, NetdevFamily, RtnlFamily, RtnlAddrFamily from .ynl import NetshaperFamily, DevlinkFamily, PSPFamily, Netlink
@@ -29,6 +30,7 @@ __all__ = ["KSRC", "CmdExitFailure", "fd_read_timeout", "cmd", "bkg", "defer", "bpftool", "ip", "ethtool", "bpftrace", "rand_port", "rand_ports", "wait_port_listen", "wait_file", "tool", + "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "NetdevSim", "NetdevSimDev", "NetshaperFamily", "DevlinkFamily", "PSPFamily", "NlError", "YnlFamily", "EthtoolFamily", "NetdevFamily", "RtnlFamily",
tools/testing/selftests/net/lib/py/bpf.py new file +68 -0
68 lines not on this page · raw message
Signature · 1 line
2.43.0
PATCH v5 6/6 selftests: drv-net: xdp: Add rss_hash metadata tests
In reply to Chris J ArgesThis test loads xdp_metadata.bpf which calls bpf_xdp_metadata_rx_hash() on incoming packets. The metadata from that packet is then sent to a BPF map for validation. It borrows structure from xdp.py, reusing common functions.
The test checks the device's xdp-rx-metadata-features via netlink before running and skips on devices that do not advertise hash support. This can be run on veth devices as well as real hardware.
The test is fairly simple and just verifies that a TCP or UDP packet can be identified as an L4 flow. This minimal test also passes if run on a veth device.
---
v5: no changes
--- .../testing/selftests/drivers/net/hw/Makefile | 1 + .../drivers/net/hw/lib/py/__init__.py | 2 + .../selftests/drivers/net/hw/xdp_metadata.py | 146 ++++++++++++++++ .../selftests/net/lib/xdp_metadata.bpf.c | 163 ++++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 tools/testing/selftests/drivers/net/hw/xdp_metadata.py create mode 100644 tools/testing/selftests/net/lib/xdp_metadata.bpf.c
tools/testing/selftests/drivers/net/hw/Makefile +1 -0
@@ -42,6 +42,7 @@ TEST_PROGS = \ rss_input_xfrm.py \ toeplitz.py \ tso.py \ + xdp_metadata.py \ xsk_reconfig.py \ #
tools/testing/selftests/drivers/net/hw/lib/py/__init__.py +2 -0
@@ -25,6 +25,7 @@ try: from net.lib.py import bkg, cmd, bpftool, bpftrace, defer, ethtool, \ fd_read_timeout, ip, rand_port, rand_ports, wait_port_listen, \ wait_file, tool + from net.lib.py import bpf_map_set, bpf_map_dump, bpf_prog_map_ids from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx from net.lib.py import ksft_disruptive, ksft_exit, ksft_pr, ksft_run, \ ksft_setup, ksft_variants, KsftNamedVariant
@@ -40,6 +41,7 @@ try: "bkg", "cmd", "bpftool", "bpftrace", "defer", "ethtool", "fd_read_timeout", "ip", "rand_port", "rand_ports", "wait_port_listen", "wait_file", "tool", + "bpf_map_set", "bpf_map_dump", "bpf_prog_map_ids", "KsftSkipEx", "KsftFailEx", "KsftXfailEx", "ksft_disruptive", "ksft_exit", "ksft_pr", "ksft_run", "ksft_setup", "ksft_variants", "KsftNamedVariant",
tools/testing/selftests/drivers/net/hw/xdp_metadata.py new file +146 -0
146 lines not on this page · raw message
tools/testing/selftests/net/lib/xdp_metadata.bpf.c new file +163 -0
163 lines not on this page · raw message
Signature · 1 line
2.43.0
This series was applied to netdev/net-next.git (main) by Jakub Kicinski <[email protected]>:
Hello:
This series was applied to netdev/net-next.git (main) by Jakub Kicinski <[email protected]>:
Quoted 12 lines · On Wed, 25 Mar 2026 15:09:46 -0500 you wrote:
This series adds XDP RSS hash metadata extraction support for the
bnxt_endriver and includes selftests to validate the functionality. I was able to test this on a BCM57414 NIC.Patches 1-4 implement the driver support:
- Patch 1 addsbnxt_xdp_buffto wrapxdp_buffwith
pointers to the hardware RX completion descriptors and the completion type. This is similar to other driver implementations.
- Patch 2 addsxmo_rx_hashcallback forCMP_TYPE_RX_L2_CMPcompletions.
- Patch 3 movesbnxt_rss_ext_optobnxt.h
- Patch 4 adds handling forCMP_TYPE_RX_L2_V3_CMPcompletions
Quoted 1 line
[...]
Here is the summary with links:
- [net-next,v5,1/6] bnxt_en: use bnxt_xdp_buff for xdp context
https://git.kernel.org/netdev/net-next/c/542d3ec4508f - [net-next,v5,2/6] bnxt_en: Implement XDP RSS hash metadata extraction https://git.kernel.org/netdev/net-next/c/e5648b08cbfc - [net-next,v5,3/6] bnxt_en: Move bnxt_rss_ext_op into header https://git.kernel.org/netdev/net-next/c/4a037aeb6bc8 - [net-next,v5,4/6] bnxt_en: Implement XDP RSS hash metadata extraction for V3_CMP https://git.kernel.org/netdev/net-next/c/5ec22d5f2afc - [net-next,v5,5/6] selftests: net: move common xdp.py functions into lib https://git.kernel.org/netdev/net-next/c/4ce06406958b - [net-next,v5,6/6] selftests: drv-net: xdp: Add rss_hash metadata tests https://git.kernel.org/netdev/net-next/c/e3cdf6cf5fc6
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html