Large rx buffer support for zcrx
Subsystems (13)
Pavel Begunkov
maintainercover Large rx buffer support for zcrx
This series implements large rx buffer support for io_uring/zcrx on top of Jakub's queue configuration changes, but it can also be used by other memory providers. Large rx buffers can be drastically beneficial with high-end hw-gro enabled cards that can coalesce traffic into larger pages, reducing the number of frags traversing the network stack and resuling in larger contiguous chunks of data for the userspace. Benchamrks showed up to ~30% improvement in CPU util.
For example, for 200Gbit broadcom NIC, 4K vs 32K buffers, and napi and userspace pinned to the same CPU:
packets=23987040 (MB=2745098), rps=199559 (MB/s=22837)
CPU %usr %nice %sys %iowait %irq %soft %idle 0 1.53 0.00 27.78 2.72 1.31 66.45 0.22 packets=24078368 (MB=2755550), rps=200319 (MB/s=22924) CPU %usr %nice %sys %iowait %irq %soft %idle 0 0.69 0.00 8.26 31.65 1.83 57.00 0.57
And for napi and userspace on different CPUs:
packets=10725082 (MB=1227388), rps=198285 (MB/s=22692)
CPU %usr %nice %sys %iowait %irq %soft %idle 0 0.10 0.00 0.50 0.00 0.50 74.50 24.40 1 4.51 0.00 44.33 47.22 2.08 1.85 0.00 packets=14026235 (MB=1605175), rps=198388 (MB/s=22703) CPU %usr %nice %sys %iowait %irq %soft %idle 0 0.10 0.00 0.70 0.00 1.00 43.78 54.42 1 1.09 0.00 31.95 62.91 1.42 2.63 0.00
Patch 19 allows to pass queue config from a memory provider. The zcrx changes are contained in a single patch as I already queued most of work making it size agnostic into my zcrx branch. The uAPI is simple and imperative, it'll use the exact value (if) specified by the user. In the future we might extend it to "choose the best size in a given range".
The rest (first 20) patches are from Jakub's series implementing per queue configuration. Quoting Jakub:
"... The direct motivation for the series is that zero-copy Rx queues would like to use larger Rx buffers. Most modern high-speed NICs support HW-GRO, and can coalesce payloads into pages much larger than than the MTU. Enabling larger buffers globally is a bit precarious as it exposes us to potentially very inefficient memory use. Also allocating large buffers may not be easy or cheap under load. Zero-copy queues service only select traffic and have pre-allocated memory so the concerns don't apply as much.
The per-queue config has to address 3 problems:
- user API
- driver API
- memory provider API
For user API the main question is whether we expose the config via ethtool or netdev nl. I picked the latter - via queue GET/SET, rather than extending the ethtool RINGS_GET API. I worry slightly that queue GET/SET will turn in a monster like SETLINK. OTOH the only per-queue settings we have in ethtool which are not going via RINGS_SET is IRQ coalescing.
My goal for the driver API was to avoid complexity in the drivers. The queue management API has gained two ops, responsible for preparing configuration for a given queue, and validating whether the config is supported. The validating is used both for NIC-wide and per-queue changes. Queue alloc/start ops have a new "config" argument which contains the current config for a given queue (we use queue restart to apply per-queue settings). Outside of queue reset paths drivers can call netdev_queue_config() which returns the config for an arbitrary queue. Long story short I anticipate it to be used during ndo_open.
In the core I extended struct netdev_config with per queue settings. All in all this isn't too far from what was there in my "queue API prototype" a few years ago ..."
Kernel branch with all dependencies:
git: https://github.com/isilence/linux.git zcrx/large-buffers
url: https://github.com/isilence/linux/tree/zcrx/large-buffers
Jakub Kicinski (20):
docs: ethtool: document that rx_buf_len must control payload lengths
net: ethtool: report max value for rx-buf-len
net: use zero value to restore rx_buf_len to default
net: clarify the meaning of netdev_config members
net: add rx_buf_len to netdev config
eth: bnxt: read the page size from the adapter struct
eth: bnxt: set page pool page order based on rx_page_size
eth: bnxt: support setting size of agg buffers via ethtool
net: move netdev_config manipulation to dedicated helpers
net: reduce indent of struct netdev_queue_mgmt_ops members
net: allocate per-queue config structs and pass them thru the queue
API
net: pass extack to netdev_rx_queue_restart()
net: add queue config validation callback
eth: bnxt: always set the queue mgmt ops
eth: bnxt: store the rx buf size per queue
eth: bnxt: adjust the fill level of agg queues with larger buffers
netdev: add support for setting rx-buf-len per queue
net: wipe the setting of deactived queues
eth: bnxt: use queue op config validate
eth: bnxt: support per queue configuration of rx-buf-len
Pavel Begunkov (2):
net: parametrise mp open with a queue config io_uring/zcrx: implement large rx buffer support
Documentation/netlink/specs/ethtool.yaml | 4 + Documentation/netlink/specs/netdev.yaml | 15 ++ Documentation/networking/ethtool-netlink.rst | 7 +- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 135 ++++++++++++---- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 5 +- .../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 9 +- drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c | 6 +- drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.h | 2 +- drivers/net/ethernet/google/gve/gve_main.c | 9 +- .../marvell/octeontx2/nic/otx2_ethtool.c | 6 +- drivers/net/netdevsim/netdev.c | 8 +- include/linux/ethtool.h | 3 + include/net/netdev_queues.h | 83 ++++++++-- include/net/netdev_rx_queue.h | 3 +- include/net/netlink.h | 19 +++ include/net/page_pool/memory_provider.h | 4 +- .../uapi/linux/ethtool_netlink_generated.h | 1 + include/uapi/linux/io_uring.h | 2 +- include/uapi/linux/netdev.h | 2 + io_uring/zcrx.c | 39 ++++- net/core/Makefile | 2 +- net/core/dev.c | 12 +- net/core/dev.h | 12 ++ net/core/netdev-genl-gen.c | 15 ++ net/core/netdev-genl-gen.h | 1 + net/core/netdev-genl.c | 92 +++++++++++ net/core/netdev_config.c | 150 ++++++++++++++++++ net/core/netdev_rx_queue.c | 54 +++++-- net/ethtool/common.c | 4 +- net/ethtool/netlink.c | 14 +- net/ethtool/rings.c | 14 +- tools/include/uapi/linux/netdev.h | 2 + 32 files changed, 642 insertions(+), 92 deletions(-) create mode 100644 net/core/netdev_config.c
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 1/22 docs: ethtool: document that rx_buf_len must control payload lengths
From: Jakub Kicinski <[email protected]>
Document the semantics of the rx_buf_len ethtool ring param. Clarify its meaning in case of HDS, where driver may have two separate buffer pools.
The various zero-copy TCP Rx schemes we have suffer from memory management overhead. Specifically applications aren't too impressed with the number of 4kB buffers they have to juggle. Zero-copy TCP makes most sense with larger memory transfers so using 16kB or 32kB buffers (with the help of HW-GRO) feels more natural.
--- Documentation/networking/ethtool-netlink.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
Documentation/networking/ethtool-netlink.rst +5 -1
@@ -957,7 +957,6 @@ Kernel checks that requested ring sizes do not exceed limits reported by driver. Driver may impose additional constraints and may not support all attributes. - ``ETHTOOL_A_RINGS_CQE_SIZE`` specifies the completion queue event size. Completion queue events (CQE) are the events posted by NIC to indicate the completion status of a packet when the packet is sent (like send success or
@@ -971,6 +970,11 @@ completion queue size can be adjusted in the driver if CQE size is modified. header / data split feature. If a received packet size is larger than this threshold value, header and data will be split. +``ETHTOOL_A_RINGS_RX_BUF_LEN`` controls the size of the buffer chunks driver +uses to receive packets. If the device uses different memory polls for headers +and payload this setting may control the size of the header buffers but must +control the size of the payload buffers. + CHANNELS_GET ============
Signature · 1 line
2.49.0
Re: [RFC v1 01/22] docs: ethtool: document that rx_buf_len must control payload lengths
Quoted 33 lines · Pavel Begunkov
From: Jakub Kicinski <[email protected]>
Document the semantics of the
rx_buf_lenethtool ring param. Clarify its meaning in case of HDS, where driver may have two separate buffer pools.The various zero-copy TCP Rx schemes we have suffer from memory management overhead. Specifically applications aren't too impressed with the number of 4kB buffers they have to juggle. Zero-copy TCP makes most sense with larger memory transfers so using 16kB or 32kB buffers (with the help of HW-GRO) feels more natural.
Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- Documentation/networking/ethtool-netlink.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)Documentation/networking/ethtool-netlink.rst +0
-1@@ -957,7 +957,6 @@ Kernel checks that requested ring sizes do not exceed limits reported by driver. Driver may impose additional constraints and may not support all attributes. - ``ETHTOOL_A_RINGS_CQE_SIZE`` specifies the completion queue event size. Completion queue events (CQE) are the events posted by NIC to indicate the completion status of a packet when the packet is sent (like send success or
Documentation/networking/ethtool-netlink.rst completion queue size can be adjusted in the driver if CQE size is modified. · line 974
Quoted 5 lines
Documentation/networking/ethtool-netlink.rst +2
-0@@ -971,6 +970,11 @@ completion queue size can be adjusted in the driver if CQE size is modified. header / data split feature. If a received packet size is larger than this threshold value, header and data will be split. +``ETHTOOL_A_RINGS_RX_BUF_LEN`` controls the size of the buffer chunks driver +uses to receive packets. If the device uses different memory polls for headers
pools, not polls.
Quoted 3 lines
Documentation/networking/ethtool-netlink.rst +3
-0+and payload this setting may control the size of the header buffers but must +control the size of the payload buffers. +
Signature · 2 lines
Thanks, Mina
Re: [RFC v1 01/22] docs: ethtool: document that rx_buf_len must control payload lengths
In reply to Pavel BegunkovQuoted 33 lines · Pavel Begunkov
From: Jakub Kicinski <[email protected]>
Document the semantics of the
rx_buf_lenethtool ring param. Clarify its meaning in case of HDS, where driver may have two separate buffer pools.The various zero-copy TCP Rx schemes we have suffer from memory management overhead. Specifically applications aren't too impressed with the number of 4kB buffers they have to juggle. Zero-copy TCP makes most sense with larger memory transfers so using 16kB or 32kB buffers (with the help of HW-GRO) feels more natural.
Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- Documentation/networking/ethtool-netlink.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)Documentation/networking/ethtool-netlink.rst +0
-1@@ -957,7 +957,6 @@ Kernel checks that requested ring sizes do not exceed limits reported by driver. Driver may impose additional constraints and may not support all attributes. - ``ETHTOOL_A_RINGS_CQE_SIZE`` specifies the completion queue event size. Completion queue events (CQE) are the events posted by NIC to indicate the completion status of a packet when the packet is sent (like send success or
Documentation/networking/ethtool-netlink.rst completion queue size can be adjusted in the driver if CQE size is modified. · line 977
Quoted 8 lines
Documentation/networking/ethtool-netlink.rst +5
-0@@ -971,6 +970,11 @@ completion queue size can be adjusted in the driver if CQE size is modified. header / data split feature. If a received packet size is larger than this threshold value, header and data will be split. +``ETHTOOL_A_RINGS_RX_BUF_LEN`` controls the size of the buffer chunks driver +uses to receive packets. If the device uses different memory polls for headers +and payload this setting may control the size of the header buffers but must +control the size of the payload buffers. +
To be honest I'm not a big fan of the ambiguity here? Could this configure just the payload buffer sizes? And a new one to configure the header buffer sizes eventually?
Also, IIUC in this patchset, actually the size applied will be the order that is larger than the size configured, no? So a setting of 9KB will actually result in 16KB, no? Should this be documented? Or do we expect non power of 2 sizes to be rejected by the driver and this API fail?
Signature · 2 lines
Thanks, Mina
Jakub Kicinski
maintainerRe: [RFC v1 01/22] docs: ethtool: document that rx_buf_len must control payload lengths
Quoted 10 lines · On Mon, 28 Jul 2025 14:36:37 -0700 Mina Almasry wrote:
Documentation/networking/ethtool-netlink.rst completion queue size can be adjusted in the driver if CQE size is modified. · line 977
Quoted 5 lines
+``ETHTOOL_A_RINGS_RX_BUF_LEN`` controls the size of the buffer chunks driver
Documentation/networking/ethtool-netlink.rst +4
-0+uses to receive packets. If the device uses different memory polls for headers +and payload this setting may control the size of the header buffers but must +control the size of the payload buffers. +To be honest I'm not a big fan of the ambiguity here? Could this configure just the payload buffer sizes? And a new one to configure the header buffer sizes eventually?
Quoted 5 lines
Also, IIUC in this patchset, actually the size applied will be the order that is larger than the size configured, no? So a setting of 9KB will actually result in 16KB, no? Should this be documented? Or do we expect non power of 2 sizes to be rejected by the driver and this API fail?
This is an existing parameter.
Pavel Begunkov
maintainerRFC 2/22 net: ethtool: report max value for rx-buf-len
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Unlike most of our APIs the rx-buf-len param does not have an associated max value. In theory user could set this value pretty high, but in practice most NICs have limits due to the width of the length fields in the descriptors.
--- Documentation/netlink/specs/ethtool.yaml | 4 ++++ Documentation/networking/ethtool-netlink.rst | 1 + drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c | 3 ++- include/linux/ethtool.h | 2 ++ include/uapi/linux/ethtool_netlink_generated.h | 1 + net/ethtool/rings.c | 5 +++++ 6 files changed, 15 insertions(+), 1 deletion(-)
Documentation/netlink/specs/ethtool.yaml +4 -0
@@ -361,6 +361,9 @@ attribute-sets: - name: hds-thresh-max type: u32 + - + name: rx-buf-len-max + type: u32 - name: mm-stat
@@ -1811,6 +1814,7 @@ operations: - rx-jumbo - tx - rx-buf-len + - rx-buf-len-max - tcp-data-split - cqe-size - tx-push
Documentation/networking/ethtool-netlink.rst +1 -0
@@ -893,6 +893,7 @@ Kernel response contents: ``ETHTOOL_A_RINGS_RX_JUMBO`` u32 size of RX jumbo ring ``ETHTOOL_A_RINGS_TX`` u32 size of TX ring ``ETHTOOL_A_RINGS_RX_BUF_LEN`` u32 size of buffers on the ring + ``ETHTOOL_A_RINGS_RX_BUF_LEN_MAX`` u32 max size of rx buffers ``ETHTOOL_A_RINGS_TCP_DATA_SPLIT`` u8 TCP header / data split ``ETHTOOL_A_RINGS_CQE_SIZE`` u32 Size of TX/RX CQE ``ETHTOOL_A_RINGS_TX_PUSH`` u8 flag of TX Push mode
drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c +2 -1
@@ -376,6 +376,7 @@ static void otx2_get_ringparam(struct net_device *netdev, ring->tx_max_pending =Q_COUNT (Q_SIZE_MAX ); ring->tx_pending = qs->sqe_cnt ? qs->sqe_cnt :Q_COUNT (Q_SIZE_4K ); kernel_ring->rx_buf_len = pfvf->hw.rbuf_len; + kernel_ring->rx_buf_len_max =32768 ; kernel_ring->cqe_size = pfvf->hw.xqe_size; }
@@ -398,7 +399,7 @@ static int otx2_set_ringparam(struct net_device *netdev,/* Hardware supports max size of 32k for a receive buffer * and 1536 is typical ethernet frame size. */ -if (rx_buf_len && (rx_buf_len <1536 || rx_buf_len >32768 )) { +if (rx_buf_len && (rx_buf_len <1536 )) {netdev_err (netdev,"Receive buffer range is 1536 - 32768" );return -EINVAL ;
include/linux/ethtool.h +2 -0
@@ -77,6 +77,7 @@ enum {/** * struct kernel_ethtool_ringparam - RX/TX ring configuration * @rx_buf_len: Current length of buffers on the rx ring. +* @rx_buf_len_max: Max length of buffers on the rx ring. * @tcp_data_split: Scatter packet headers and data to separate buffers * @tx_push: The flag of tx push mode * @rx_push: The flag of rx push mode
@@ -89,6 +90,7 @@ enum {*/ struct kernel_ethtool_ringparam {u32 rx_buf_len; +u32 rx_buf_len_max;u8 tcp_data_split;u8 tx_push;u8 rx_push;
include/uapi/linux/ethtool_netlink_generated.h +1 -0
@@ -164,6 +164,7 @@ enum {ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX ,ETHTOOL_A_RINGS_HDS_THRESH ,ETHTOOL_A_RINGS_HDS_THRESH_MAX , +ETHTOOL_A_RINGS_RX_BUF_LEN_MAX , __ETHTOOL_A_RINGS_CNT,ETHTOOL_A_RINGS_MAX = (__ETHTOOL_A_RINGS_CNT -1 )
net/ethtool/rings.c +5 -0
@@ -105,6 +105,9 @@ static int rings_fill_reply(struct sk_buff *skb, ringparam->tx_pending))) || (kr->rx_buf_len && (nla_put_u32 (skb,ETHTOOL_A_RINGS_RX_BUF_LEN , kr->rx_buf_len))) || + (kr->rx_buf_len_max && + (nla_put_u32 (skb,ETHTOOL_A_RINGS_RX_BUF_LEN_MAX , + kr->rx_buf_len_max))) || (kr->tcp_data_split && (nla_put_u8 (skb,ETHTOOL_A_RINGS_TCP_DATA_SPLIT , kr->tcp_data_split))) ||
@@ -281,6 +284,8 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info) err_attr = tb[ETHTOOL_A_RINGS_TX ]; else if (kernel_ringparam.hds_thresh > kernel_ringparam.hds_thresh_max) err_attr = tb[ETHTOOL_A_RINGS_HDS_THRESH ]; + else if (kernel_ringparam.rx_buf_len > kernel_ringparam.rx_buf_len_max) + err_attr = tb[ETHTOOL_A_RINGS_RX_BUF_LEN ]; else err_attr =NULL ;if (err_attr) {
Signature · 1 line
2.49.0
Subbaraya Sundeep
maintainerRe: [RFC v1 02/22] net: ethtool: report max value for rx-buf-len
LGTM.
Thanks,
Sundeep
Quoted 131 lines · Pavel Begunkov
From: Jakub Kicinski <[email protected]>
Unlike most of our APIs the rx-buf-len param does not have an associated max value. In theory user could set this value pretty high, but in practice most NICs have limits due to the width of the length fields in the descriptors.
Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- Documentation/netlink/specs/ethtool.yaml | 4 ++++ Documentation/networking/ethtool-netlink.rst | 1 + drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c | 3 ++- include/linux/ethtool.h | 2 ++ include/uapi/linux/ethtool_netlink_generated.h | 1 + net/ethtool/rings.c | 5 +++++ 6 files changed, 15 insertions(+), 1 deletion(-)Documentation/netlink/specs/ethtool.yaml +4
-0@@ -361,6 +361,9 @@ attribute-sets: - name: hds-thresh-max type: u32 + - + name: rx-buf-len-max + type: u32 - name: mm-stat@@ -1811,6 +1814,7 @@ operations: - rx-jumbo - tx - rx-buf-len + - rx-buf-len-max - tcp-data-split - cqe-size - tx-pushDocumentation/networking/ethtool-netlink.rst +1
-0@@ -893,6 +893,7 @@ Kernel response contents: ``ETHTOOL_A_RINGS_RX_JUMBO`` u32 size of RX jumbo ring ``ETHTOOL_A_RINGS_TX`` u32 size of TX ring ``ETHTOOL_A_RINGS_RX_BUF_LEN`` u32 size of buffers on the ring + ``ETHTOOL_A_RINGS_RX_BUF_LEN_MAX`` u32 max size of rx buffers ``ETHTOOL_A_RINGS_TCP_DATA_SPLIT`` u8 TCP header / data split ``ETHTOOL_A_RINGS_CQE_SIZE`` u32 Size of TX/RX CQE ``ETHTOOL_A_RINGS_TX_PUSH`` u8 flag of TX Push modedrivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c +2
-1@@ -376,6 +376,7 @@ static void otx2_get_ringparam(struct net_device *netdev, ring->tx_max_pending =Q_COUNT (Q_SIZE_MAX ); ring->tx_pending = qs->sqe_cnt ? qs->sqe_cnt :Q_COUNT (Q_SIZE_4K ); kernel_ring->rx_buf_len = pfvf->hw.rbuf_len; + kernel_ring->rx_buf_len_max =32768 ; kernel_ring->cqe_size = pfvf->hw.xqe_size; }@@ -398,7 +399,7 @@ static int otx2_set_ringparam(struct net_device *netdev,/* Hardware supports max size of 32k for a receive buffer * and 1536 is typical ethernet frame size. */ -if (rx_buf_len && (rx_buf_len <1536 || rx_buf_len >32768 )) { +if (rx_buf_len && (rx_buf_len <1536 )) {netdev_err (netdev,"Receive buffer range is 1536 - 32768" );return -EINVAL ;include/linux/ethtool.h +2
-0@@ -77,6 +77,7 @@ enum {/** * struct kernel_ethtool_ringparam - RX/TX ring configuration * @rx_buf_len: Current length of buffers on the rx ring. +* @rx_buf_len_max: Max length of buffers on the rx ring. * @tcp_data_split: Scatter packet headers and data to separate buffers * @tx_push: The flag of tx push mode * @rx_push: The flag of rx push mode @@ -89,6 +90,7 @@ enum {*/ struct kernel_ethtool_ringparam {u32 rx_buf_len; +u32 rx_buf_len_max;u8 tcp_data_split;u8 tx_push;u8 rx_push;include/uapi/linux/ethtool_netlink_generated.h +1
-0@@ -164,6 +164,7 @@ enum {ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX ,ETHTOOL_A_RINGS_HDS_THRESH ,ETHTOOL_A_RINGS_HDS_THRESH_MAX , +ETHTOOL_A_RINGS_RX_BUF_LEN_MAX , __ETHTOOL_A_RINGS_CNT,ETHTOOL_A_RINGS_MAX = (__ETHTOOL_A_RINGS_CNT -1 )net/ethtool/rings.c +5
-0@@ -105,6 +105,9 @@ static int rings_fill_reply(struct sk_buff *skb, ringparam->tx_pending))) || (kr->rx_buf_len && (nla_put_u32 (skb,ETHTOOL_A_RINGS_RX_BUF_LEN , kr->rx_buf_len))) || + (kr->rx_buf_len_max && + (nla_put_u32 (skb,ETHTOOL_A_RINGS_RX_BUF_LEN_MAX , + kr->rx_buf_len_max))) || (kr->tcp_data_split && (nla_put_u8 (skb,ETHTOOL_A_RINGS_TCP_DATA_SPLIT , kr->tcp_data_split))) ||@@ -281,6 +284,8 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info) err_attr = tb[ETHTOOL_A_RINGS_TX ]; else if (kernel_ringparam.hds_thresh > kernel_ringparam.hds_thresh_max) err_attr = tb[ETHTOOL_A_RINGS_HDS_THRESH ]; + else if (kernel_ringparam.rx_buf_len > kernel_ringparam.rx_buf_len_max) + err_attr = tb[ETHTOOL_A_RINGS_RX_BUF_LEN ]; else err_attr =NULL ;if (err_attr) {Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 3/22 net: use zero value to restore rx_buf_len to default
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Distinguish between rx_buf_len being driver default vs user config. Use 0 as a special value meaning "unset" or "restore driver default". This will be necessary later on to configure it per-queue, but the ability to restore defaults may be useful in itself.
--- Documentation/networking/ethtool-netlink.rst | 2 +- drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c | 3 +++ include/linux/ethtool.h | 1 + net/ethtool/rings.c | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-)
Documentation/networking/ethtool-netlink.rst +1 -1
@@ -974,7 +974,7 @@ threshold value, header and data will be split. ``ETHTOOL_A_RINGS_RX_BUF_LEN`` controls the size of the buffer chunks driver uses to receive packets. If the device uses different memory polls for headers and payload this setting may control the size of the header buffers but must -control the size of the payload buffers. +control the size of the payload buffers. Setting to 0 restores driver default. CHANNELS_GET ============
drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c +3 -0
@@ -396,6 +396,9 @@ static int otx2_set_ringparam(struct net_device *netdev,if (ring->rx_mini_pending || ring->rx_jumbo_pending)return -EINVAL ; +if (!rx_buf_len) + rx_buf_len =OTX2_DEFAULT_RBUF_LEN ; +/* Hardware supports max size of 32k for a receive buffer * and 1536 is typical ethernet frame size. */
include/linux/ethtool.h +1 -0
@@ -77,6 +77,7 @@ enum {/** * struct kernel_ethtool_ringparam - RX/TX ring configuration * @rx_buf_len: Current length of buffers on the rx ring. +* Setting to 0 means reset to driver default. * @rx_buf_len_max: Max length of buffers on the rx ring. * @tcp_data_split: Scatter packet headers and data to separate buffers * @tx_push: The flag of tx push mode
net/ethtool/rings.c +1 -1
@@ -139,7 +139,7 @@ const struct nla_policy ethnl_rings_set_policy[] = { [ETHTOOL_A_RINGS_RX_MINI] = { .type = NLA_U32 }, [ETHTOOL_A_RINGS_RX_JUMBO] = { .type = NLA_U32 }, [ETHTOOL_A_RINGS_TX] = { .type = NLA_U32 }, - [ETHTOOL_A_RINGS_RX_BUF_LEN] = NLA_POLICY_MIN(NLA_U32, 1), + [ETHTOOL_A_RINGS_RX_BUF_LEN] = { .type = NLA_U32 }, [ETHTOOL_A_RINGS_TCP_DATA_SPLIT ] = NLA_POLICY_MAX(NLA_U8, ETHTOOL_TCP_DATA_SPLIT_ENABLED), [ETHTOOL_A_RINGS_CQE_SIZE] = NLA_POLICY_MIN(NLA_U32, 1),
Signature · 1 line
2.49.0
Subbaraya Sundeep
maintainerRe: [RFC v1 03/22] net: use zero value to restore rx_buf_len to default
LGTM.
Thanks,
Sundeep
Quoted 71 lines · Pavel Begunkov
From: Jakub Kicinski <[email protected]>
Distinguish between
rx_buf_lenbeing driver default vs user config. Use 0 as a special value meaning "unset" or "restore driver default". This will be necessary later on to configure it per-queue, but the ability to restore defaults may be useful in itself.Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- Documentation/networking/ethtool-netlink.rst | 2 +- drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c | 3 +++ include/linux/ethtool.h | 1 + net/ethtool/rings.c | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-)Documentation/networking/ethtool-netlink.rst +1
-1@@ -974,7 +974,7 @@ threshold value, header and data will be split. ``ETHTOOL_A_RINGS_RX_BUF_LEN`` controls the size of the buffer chunks driver uses to receive packets. If the device uses different memory polls for headers and payload this setting may control the size of the header buffers but must -control the size of the payload buffers. +control the size of the payload buffers. Setting to 0 restores driver default. CHANNELS_GET ============drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c +3
-0@@ -396,6 +396,9 @@ static int otx2_set_ringparam(struct net_device *netdev,if (ring->rx_mini_pending || ring->rx_jumbo_pending)return -EINVAL ; +if (!rx_buf_len) + rx_buf_len =OTX2_DEFAULT_RBUF_LEN ; +/* Hardware supports max size of 32k for a receive buffer * and 1536 is typical ethernet frame size. */ include/linux/ethtool.h +1
-0@@ -77,6 +77,7 @@ enum {/** * struct kernel_ethtool_ringparam - RX/TX ring configuration * @rx_buf_len: Current length of buffers on the rx ring. +* Setting to 0 means reset to driver default. * @rx_buf_len_max: Max length of buffers on the rx ring. * @tcp_data_split: Scatter packet headers and data to separate buffers * @tx_push: The flag of tx push mode net/ethtool/rings.c +1
-1@@ -139,7 +139,7 @@ const struct nla_policy ethnl_rings_set_policy[] = { [ETHTOOL_A_RINGS_RX_MINI] = { .type = NLA_U32 }, [ETHTOOL_A_RINGS_RX_JUMBO] = { .type = NLA_U32 }, [ETHTOOL_A_RINGS_TX] = { .type = NLA_U32 }, - [ETHTOOL_A_RINGS_RX_BUF_LEN] = NLA_POLICY_MIN(NLA_U32, 1), + [ETHTOOL_A_RINGS_RX_BUF_LEN] = { .type = NLA_U32 }, [ETHTOOL_A_RINGS_TCP_DATA_SPLIT ] = NLA_POLICY_MAX(NLA_U8, ETHTOOL_TCP_DATA_SPLIT_ENABLED), [ETHTOOL_A_RINGS_CQE_SIZE] = NLA_POLICY_MIN(NLA_U32, 1),Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 4/22 net: clarify the meaning of netdev_config members
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
hds_thresh and hds_config are both inside struct netdev_config but have quite different semantics. hds_config is the user config with ternary semantics (on/off/unset). hds_thresh is a straight up value, populated by the driver at init and only modified by user space. We don't expect the drivers to have to pick a special hds_thresh value based on other configuration.
The two approaches have different advantages and downsides. hds_thresh ("direct value") gives core easy access to current device settings, but there's no way to express whether the value comes from the user. It also requires the initialization by the driver.
hds_config ("user config values") tells us what user wanted, but doesn't give us the current value in the core.
Try to explain this a bit in the comments, so at we make a conscious choice for new values which semantics we expect.
Move the init inside ethtool_ringparam_get_cfg() to reflect the semantics. Commit 216a61d33c07 ("net: ethtool: fix ethtool_ringparam_get_cfg() returns a hds_thresh value always as 0.") added the setting for the benefit of netdevsim which doesn't touch the value at all on get. Again, this is just to clarify the intention, shouldn't cause any functional change.
--- include/net/netdev_queues.h | 19 +++++++++++++++++-- net/ethtool/common.c | 3 ++- 2 files changed, 19 insertions(+), 3 deletions(-)
include/net/netdev_queues.h +17 -2
@@ -6,11 +6,26 @@/** * struct netdev_config - queue-related configuration for a netdev -* @hds_thresh: HDS Threshold value. -* @hds_config: HDS value from userspace. */ struct netdev_config { +/* Direct value +* +* Driver default is expected to be fixed, and set in this struct +* at init. From that point on user may change the value. There is +* no explicit way to "unset" / restore driver default. +*/ +/** @hds_thresh: HDS Threshold value (ETHTOOL_A_RINGS_HDS_THRESH). +*/ u32 hds_thresh; + +/* User config values +* +* Contain user configuration. If "set" driver must obey. +* If "unset" driver is free to decide, and may change its choice +* as other parameters change. +*/ +/** @hds_config: HDS enabled (ETHTOOL_A_RINGS_TCP_DATA_SPLIT). +*/ u8 hds_config; };
net/ethtool/common.c +2 -1
@@ -825,12 +825,13 @@ void ethtool_ringparam_get_cfg(struct net_device *dev,memset (param,0 ,sizeof (*param));memset (kparam,0 ,sizeof (*kparam)); + kparam->hds_thresh = dev->cfg->hds_thresh; + param->cmd =ETHTOOL_GRINGPARAM ; dev->ethtool_ops->get_ringparam (dev, param, kparam, extack);/* Driver gives us current state, we want to return current config */ kparam->tcp_data_split = dev->cfg->hds_config; - kparam->hds_thresh = dev->cfg->hds_thresh; }static void ethtool_init_tsinfo (struct kernel_ethtool_ts_info *info)
Signature · 1 line
2.49.0
Re: [RFC v1 04/22] net: clarify the meaning of netdev_config members
Quoted 44 lines · Pavel Begunkov
From: Jakub Kicinski <[email protected]>
hds_threshandhds_configare both inside structnetdev_configbut have quite different semantics.hds_configis the user config with ternary semantics (on/off/unset).hds_threshis a straight up value, populated by the driver at init and only modified by user space. We don't expect the drivers to have to pick a specialhds_threshvalue based on other configuration.The two approaches have different advantages and downsides.
hds_thresh("direct value") gives core easy access to current device settings, but there's no way to express whether the value comes from the user. It also requires the initialization by the driver.
hds_config("user config values") tells us what user wanted, but doesn't give us the current value in the core.Try to explain this a bit in the comments, so at we make a conscious choice for new values which semantics we expect.
Move the init inside
ethtool_ringparam_get_cfg()to reflect the semantics. Commit216a61d33c07("net: ethtool: fixethtool_ringparam_get_cfg()returns ahds_threshvalue always as 0.") added the setting for the benefit of netdevsim which doesn't touch the value at all on get. Again, this is just to clarify the intention, shouldn't cause any functional change.Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- include/net/netdev_queues.h | 19 +++++++++++++++++-- net/ethtool/common.c | 3 ++- 2 files changed, 19 insertions(+), 3 deletions(-)include/net/netdev_queues.h +0
-0@@ -6,11 +6,26 @@/** * struct netdev_config - queue-related configuration for a netdev
include/net/netdev_queues.h line 16
Quoted 10 lines
include/net/netdev_queues.h +6
-2-* @hds_thresh: HDS Threshold value. -* @hds_config: HDS value from userspace. */ struct netdev_config { +/* Direct value +* +* Driver default is expected to be fixed, and set in this struct +* at init. From that point on user may change the value. There is +* no explicit way to "unset" / restore driver default. +*/
Does the user setting hds_thres imply turning hds_config to "on"? Or is hds_thres only used when hds_config is actually on?
Signature · 2 lines
Thanks, Mina
Jakub Kicinski
maintainerRe: [RFC v1 04/22] net: clarify the meaning of netdev_config members
Quoted 10 lines · On Mon, 28 Jul 2025 14:44:19 -0700 Mina Almasry wrote:
include/net/netdev_queues.h line 16
Quoted 7 lines
struct
netdev_config{include/net/netdev_queues.h +6
-0+/* Direct value +* +* Driver default is expected to be fixed, and set in this struct +* at init. From that point on user may change the value. There is +* no explicit way to "unset" / restore driver default. +*/ Does the user setting
hds_thresimply turninghds_configto "on"? Or ishds_thresonly used whenhds_configis actually on?
The latter.
Pavel Begunkov
maintainerRFC 5/22 net: add rx_buf_len to netdev config
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Add rx_buf_len to configuration maintained by the core. Use "three-state" semantics where 0 means "driver default".
--- include/net/netdev_queues.h | 4 ++++ net/ethtool/common.c | 1 + net/ethtool/rings.c | 2 ++ 3 files changed, 7 insertions(+)
include/net/netdev_queues.h +4 -0
@@ -24,6 +24,10 @@ struct netdev_config {* If "unset" driver is free to decide, and may change its choice * as other parameters change. */ +/** @rx_buf_len: Size of buffers on the Rx ring +* (ETHTOOL_A_RINGS_RX_BUF_LEN). +*/ +u32 rx_buf_len;/** @hds_config: HDS enabled (ETHTOOL_A_RINGS_TCP_DATA_SPLIT). */ u8 hds_config;
net/ethtool/common.c +1 -0
@@ -832,6 +832,7 @@ void ethtool_ringparam_get_cfg(struct net_device *dev,/* Driver gives us current state, we want to return current config */ kparam->tcp_data_split = dev->cfg->hds_config; + kparam->rx_buf_len = dev->cfg->rx_buf_len; }static void ethtool_init_tsinfo (struct kernel_ethtool_ts_info *info)
net/ethtool/rings.c +2 -0
@@ -41,6 +41,7 @@ static int rings_prepare_data(const struct ethnl_req_info *req_base,return ret; data->kernel_ringparam.tcp_data_split = dev->cfg->hds_config; + data->kernel_ringparam.rx_buf_len = dev->cfg->rx_buf_len; data->kernel_ringparam.hds_thresh = dev->cfg->hds_thresh; dev->ethtool_ops->get_ringparam(dev, &data->ringparam,
@@ -302,6 +303,7 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info)return -EINVAL ; } + dev->cfg_pending->rx_buf_len = kernel_ringparam.rx_buf_len; dev->cfg_pending->hds_config = kernel_ringparam.tcp_data_split; dev->cfg_pending->hds_thresh = kernel_ringparam.hds_thresh;
Signature · 1 line
2.49.0
Re: [RFC v1 05/22] net: add rx_buf_len to netdev config
Quoted 6 lines · Pavel Begunkov
From: Jakub Kicinski <[email protected]>
Add
rx_buf_lento configuration maintained by the core. Use "three-state" semantics where 0 means "driver default".
What are three states in the semantics here?
- 0 = driver default.
- non-zero means value set by userspace
What is the 3rd state here?
Quoted 28 lines
Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- include/net/netdev_queues.h | 4 ++++ net/ethtool/common.c | 1 + net/ethtool/rings.c | 2 ++ 3 files changed, 7 insertions(+)include/net/netdev_queues.h +4
-0@@ -24,6 +24,10 @@ struct netdev_config {* If "unset" driver is free to decide, and may change its choice * as other parameters change. */ +/** @rx_buf_len: Size of buffers on the Rx ring +* (ETHTOOL_A_RINGS_RX_BUF_LEN). +*/ +u32 rx_buf_len;/** @hds_config: HDS enabled (ETHTOOL_A_RINGS_TCP_DATA_SPLIT). */ u8 hds_config;net/ethtool/common.c +0
-0
net/ethtool/common.c void ethtool_ringparam_get_cfg(struct net_device *dev, · line 835
Quoted 4 lines
net/ethtool/common.c +1
-0@@ -832,6 +832,7 @@ void ethtool_ringparam_get_cfg(struct net_device *dev,/* Driver gives us current state, we want to return current config */ kparam->tcp_data_split = dev->cfg->hds_config; + kparam->rx_buf_len = dev->cfg->rx_buf_len;
I'm confused that struct netdev_config is defined in netdev_queues.h, and is documented to be a queue-related configuration, but doesn't seem to be actually per queue? This line is grabbing the current config for this queue from dev->cfg which looks like a shared value.
I don't think rx_buf_len should be a shared value between all the queues. I strongly think it should a per-queue value. The devmem/io_uring queues will probably want large rx_buf_len, but normal queues will want 0 buf len, me thinks.
Signature · 2 lines
Thanks, Mina
Jakub Kicinski
maintainerRe: [RFC v1 05/22] net: add rx_buf_len to netdev config
Quoted 10 lines · On Mon, 28 Jul 2025 14:50:12 -0700 Mina Almasry wrote:
Quoted 2 lines · Pavel Begunkov
Add
rx_buf_lento configuration maintained by the core. Use "three-state" semantics where 0 means "driver default".What are three states in the semantics here?
- 0 = driver default.
- non-zero means value set by userspaceWhat is the 3rd state here?
I just mean a value with an explicit default / unset state. If you have a better name I'm all ears ..
Quoted 15 lines
net/ethtool/common.c void ethtool_ringparam_get_cfg(struct net_device *dev, · line 835
Quoted 9 lines
net/ethtool/common.c +1
-0@@ -832,6 +832,7 @@ void ethtool_ringparam_get_cfg(struct net_device *dev,/* Driver gives us current state, we want to return current config */ kparam->tcp_data_split = dev->cfg->hds_config; + kparam->rx_buf_len = dev->cfg->rx_buf_len;I'm confused that struct
netdev_configis defined innetdev_queues.h, and is documented to be a queue-related configuration, but doesn't seem to be actually per queue? This line is grabbing the current config for this queue fromdev->cfgwhich looks like a shared value.
Quoted 4 lines
I don't think
rx_buf_lenshould be a shared value between all the queues. I strongly think it should a per-queue value. The devmem/io_uring queues will probably want largerx_buf_len, but normal queues will want 0 buf len, me thinks.
I presume that question answered itself as you were reading the rest of the patches? :)
Pavel Begunkov
maintainerRFC 6/22 eth: bnxt: read the page size from the adapter struct
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Switch from using a constant to storing the BNXT_RX_PAGE_SIZE inside struct bnxt. This will allow configuring the page size at runtime in subsequent patches.
The MSS size calculation for older chip continues to use the constant. I'm intending to support the configuration only on more recent HW, looks like on older chips setting this per queue won't work, and that's the ultimate goal.
This patch should not change the current behavior as value read from the struct will always be BNXT_RX_PAGE_SIZE at this stage.
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 27 ++++++++++--------- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 + drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c | 4 +-- 3 files changed, 17 insertions(+), 15 deletions(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +14 -13
@@ -895,7 +895,7 @@ static void bnxt_tx_int(struct bnxt *bp, struct bnxt_napi *bnapi, int budget)static bool bnxt_separate_head_pool (struct bnxt_rx_ring_info *rxr) { -return rxr->need_head_pool ||PAGE_SIZE >BNXT_RX_PAGE_SIZE ; +return rxr->need_head_pool ||PAGE_SIZE > rxr->bnapi->bp->rx_page_size; } static struct page *__bnxt_alloc_rx_page(struct bnxt *bp, dma_addr_t *mapping,
@@ -905,9 +905,9 @@ static struct page *__bnxt_alloc_rx_page(struct bnxt *bp, dma_addr_t *mapping, {struct page *page; -if (PAGE_SIZE >BNXT_RX_PAGE_SIZE ) { +if (PAGE_SIZE > bp->rx_page_size) { page =page_pool_dev_alloc_frag (rxr->page_pool, offset, -BNXT_RX_PAGE_SIZE ); + bp->rx_page_size); }else { page =page_pool_dev_alloc_pages (rxr->page_pool); *offset =0 ;
@@ -1139,9 +1139,9 @@ static struct sk_buff *bnxt_rx_multi_page_skb(struct bnxt *bp,return NULL ; } dma_addr -= bp->rx_dma_offset; -dma_sync_single_for_cpu (&bp->pdev->dev, dma_addr,BNXT_RX_PAGE_SIZE , +dma_sync_single_for_cpu (&bp->pdev->dev, dma_addr, bp->rx_page_size, bp->rx_dir); - skb =napi_build_skb (data_ptr - bp->rx_offset,BNXT_RX_PAGE_SIZE ); + skb =napi_build_skb (data_ptr - bp->rx_offset, bp->rx_page_size);if (!skb) {page_pool_recycle_direct (rxr->page_pool, page);return NULL ;
@@ -1173,7 +1173,7 @@ static struct sk_buff *bnxt_rx_page_skb(struct bnxt *bp,return NULL ; } dma_addr -= bp->rx_dma_offset; -dma_sync_single_for_cpu (&bp->pdev->dev, dma_addr,BNXT_RX_PAGE_SIZE , +dma_sync_single_for_cpu (&bp->pdev->dev, dma_addr, bp->rx_page_size, bp->rx_dir);if (unlikely (!payload))
@@ -1187,7 +1187,7 @@ static struct sk_buff *bnxt_rx_page_skb(struct bnxt *bp,skb_mark_for_recycle (skb); off = (void *)data_ptr -page_address (page); -skb_add_rx_frag (skb,0 , page, off, len,BNXT_RX_PAGE_SIZE ); +skb_add_rx_frag (skb,0 , page, off, len, bp->rx_page_size);memcpy (skb->data -NET_IP_ALIGN , data_ptr -NET_IP_ALIGN , payload +NET_IP_ALIGN );
@@ -1272,7 +1272,7 @@ static u32 __bnxt_rx_agg_netmems(struct bnxt *bp,if (skb) {skb_add_rx_frag_netmem (skb, i, cons_rx_buf->netmem, cons_rx_buf->offset, - frag_len,BNXT_RX_PAGE_SIZE ); + frag_len, bp->rx_page_size); }else {skb_frag_t *frag = &shinfo->frags[i];
@@ -1297,7 +1297,7 @@ static u32 __bnxt_rx_agg_netmems(struct bnxt *bp,if (skb) { skb->len -= frag_len; skb->data_len -= frag_len; - skb->truesize -=BNXT_RX_PAGE_SIZE ; + skb->truesize -= bp->rx_page_size; } --shinfo->nr_frags;
@@ -1312,7 +1312,7 @@ static u32 __bnxt_rx_agg_netmems(struct bnxt *bp, }page_pool_dma_sync_netmem_for_cpu (rxr->page_pool, netmem,0 , -BNXT_RX_PAGE_SIZE ); + bp->rx_page_size); total_frag_len += frag_len; prod =NEXT_RX_AGG (prod);
@@ -4448,7 +4448,7 @@ static void bnxt_init_one_rx_agg_ring_rxbd(struct bnxt *bp, ring = &rxr->rx_agg_ring_struct; ring->fw_ring_id =INVALID_HW_RING_ID ;if ((bp->flags &BNXT_FLAG_AGG_RINGS )) { - type = ((u32 )BNXT_RX_PAGE_SIZE <<RX_BD_LEN_SHIFT ) | + type = ((u32 )bp->rx_page_size <<RX_BD_LEN_SHIFT ) |RX_BD_TYPE_RX_AGG_BD |RX_BD_FLAGS_SOP ;bnxt_init_rxbd_pages (ring, type);
@@ -4710,7 +4710,7 @@ void bnxt_set_ring_params(struct bnxt *bp) bp->rx_agg_nr_pages =0 ;if (bp->flags &BNXT_FLAG_TPA || bp->flags &BNXT_FLAG_HDS ) - agg_factor =min_t (u32,4 ,65536 /BNXT_RX_PAGE_SIZE ); + agg_factor =min_t (u32,4 ,65536 / bp->rx_page_size); bp->flags &= ~BNXT_FLAG_JUMBO ;if (rx_space >PAGE_SIZE && !(bp->flags &BNXT_FLAG_NO_AGG_RINGS )) {
@@ -7022,7 +7022,7 @@ static void bnxt_set_rx_ring_params_p5(struct bnxt *bp, u32 ring_type,if (ring_type ==HWRM_RING_ALLOC_AGG ) { req->ring_type =RING_ALLOC_REQ_RING_TYPE_RX_AGG ; req->rx_ring_id =cpu_to_le16 (grp_info->rx_fw_ring_id); - req->rx_buf_size =cpu_to_le16 (BNXT_RX_PAGE_SIZE ); + req->rx_buf_size =cpu_to_le16 (bp->rx_page_size); enables |=RING_ALLOC_REQ_ENABLES_RX_RING_ID_VALID ; }else { req->rx_buf_size =cpu_to_le16 (bp->rx_buf_use_size);
@@ -16573,6 +16573,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) bp =netdev_priv (dev); bp->board_idx = ent->driver_data; bp->msg_enable =BNXT_DEF_MSG_ENABLE ; + bp->rx_page_size =BNXT_RX_PAGE_SIZE ;bnxt_set_max_func_irqs (bp, max_irqs);if (bnxt_vf_pciid (bp->board_idx))
drivers/net/ethernet/broadcom/bnxt/bnxt.h +1 -0
@@ -2358,6 +2358,7 @@ struct bnxt {u16 max_tpa;u32 rx_buf_size;u32 rx_buf_use_size;/* useable size */ +u16 rx_page_size;u16 rx_offset;u16 rx_dma_offset;enum dma_data_direction rx_dir;
drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c +2 -2
@@ -183,7 +183,7 @@ void bnxt_xdp_buff_init(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, u16 cons, u8 *data_ptr, unsigned int len, struct xdp_buff *xdp) { - u32 buflen = BNXT_RX_PAGE_SIZE; + u32 buflen = bp->rx_page_size;struct bnxt_sw_rx_bd *rx_buf;struct pci_dev *pdev;dma_addr_t mapping;
@@ -470,7 +470,7 @@ bnxt_xdp_build_skb(struct bnxt *bp, struct sk_buff *skb, u8 num_frags,xdp_update_skb_shared_info (skb, num_frags, sinfo->xdp_frags_size, -BNXT_RX_PAGE_SIZE * num_frags, + bp->rx_page_size * num_frags,xdp_buff_is_frag_pfmemalloc (xdp));return skb; }
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 7/22 eth: bnxt: set page pool page order based on rx_page_size
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
If user decides to increase the buffer size for agg ring we need to ask the page pool for higher order pages. There is no need to use larger pages for header frags, if user increase the size of agg ring buffers switch to separate header page automatically.
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 3 +++ 1 file changed, 3 insertions(+)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +3 -0
@@ -3806,6 +3806,7 @@ static int bnxt_alloc_rx_page_pool(struct bnxt *bp, pp.pool_size = bp->rx_agg_ring_size;if (BNXT_RX_PAGE_MODE (bp)) pp.pool_size += bp->rx_ring_size; + pp.order =get_order (bp->rx_page_size); pp.nid = numa_node; pp.napi = &rxr->bnapi->napi; pp.netdev = bp->dev;
@@ -3822,7 +3823,9 @@ static int bnxt_alloc_rx_page_pool(struct bnxt *bp, rxr->page_pool = pool; rxr->need_head_pool =page_pool_is_unreadable (pool); + rxr->need_head_pool |= !!pp.order;if (bnxt_separate_head_pool (rxr)) { + pp.order =0 ; pp.pool_size =max (bp->rx_ring_size,1024 ); pp.flags =PP_FLAG_DMA_MAP |PP_FLAG_DMA_SYNC_DEV ; pool =page_pool_create (&pp);
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 8/22 eth: bnxt: support setting size of agg buffers via ethtool
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
bnxt seems to be able to aggregate data up to 32kB without any issue. The driver is already capable of doing this for systems with higher order pages. While for systems with 4k pages we historically preferred to stick to small buffers because they are easier to allocate, the zero-copy APIs remove the allocation problem. The ZC mem is pre-allocated and fixed size.
--- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 3 ++- .../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.h +2 -1
@@ -758,7 +758,8 @@ struct nqe_cn {#define BNXT_RX_PAGE_SHIFT PAGE_SHIFT#endif -#define BNXT_RX_PAGE_SIZE (1 << BNXT_RX_PAGE_SHIFT) +#define BNXT_MAX_RX_PAGE_SIZE (1 << 15) +#define BNXT_RX_PAGE_SIZE (1 << BNXT_RX_PAGE_SHIFT)#define BNXT_MAX_MTU 9500
drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +20 -1
@@ -835,6 +835,8 @@ static void bnxt_get_ringparam(struct net_device *dev, ering->rx_jumbo_pending = bp->rx_agg_ring_size; ering->tx_pending = bp->tx_ring_size; + kernel_ering->rx_buf_len_max =BNXT_MAX_RX_PAGE_SIZE ; + kernel_ering->rx_buf_len = bp->rx_page_size; kernel_ering->hds_thresh_max =BNXT_HDS_THRESHOLD_MAX ; }
@@ -862,6 +864,21 @@ static int bnxt_set_ringparam(struct net_device *dev,return -EINVAL ; } +if (!kernel_ering->rx_buf_len)/* Zero means restore default */ + kernel_ering->rx_buf_len =BNXT_RX_PAGE_SIZE ; + +if (kernel_ering->rx_buf_len != bp->rx_page_size && + !(bp->flags &BNXT_FLAG_CHIP_P5_PLUS )) { +NL_SET_ERR_MSG_MOD (extack,"changing rx-buf-len not supported" ); +return -EINVAL ; + } +if (!is_power_of_2 (kernel_ering->rx_buf_len) || + kernel_ering->rx_buf_len <BNXT_RX_PAGE_SIZE || + kernel_ering->rx_buf_len >BNXT_MAX_RX_PAGE_SIZE ) { +NL_SET_ERR_MSG_MOD (extack,"rx-buf-len out of range, or not power of 2" ); +return -ERANGE ; + } +if (netif_running (dev))bnxt_close_nic (bp, false, false);
@@ -874,6 +891,7 @@ static int bnxt_set_ringparam(struct net_device *dev, bp->rx_ring_size = ering->rx_pending; bp->tx_ring_size = ering->tx_pending; + bp->rx_page_size = kernel_ering->rx_buf_len;bnxt_set_ring_params (bp);if (netif_running (dev))
@@ -5489,7 +5507,8 @@ const struct ethtool_ops bnxt_ethtool_ops = {ETHTOOL_COALESCE_STATS_BLOCK_USECS |ETHTOOL_COALESCE_USE_ADAPTIVE_RX | ETHTOOL_COALESCE_USE_CQE, - .supported_ring_params =ETHTOOL_RING_USE_TCP_DATA_SPLIT | + .supported_ring_params =ETHTOOL_RING_USE_RX_BUF_LEN | +ETHTOOL_RING_USE_TCP_DATA_SPLIT | ETHTOOL_RING_USE_HDS_THRS, .get_link_ksettings = bnxt_get_link_ksettings, .set_link_ksettings = bnxt_set_link_ksettings,
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 9/22 net: move netdev_config manipulation to dedicated helpers
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
netdev_config manipulation will become slightly more complicated soon and we will need to call if from ethtool as well as queue API. Encapsulate the logic into helper functions.
--- net/core/Makefile | 2 +- net/core/dev.c | 7 ++----- net/core/dev.h | 5 +++++ net/core/netdev_config.c | 43 ++++++++++++++++++++++++++++++++++++++++ net/ethtool/netlink.c | 14 ++++++------- 5 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 net/core/netdev_config.c
net/core/Makefile +1 -1
@@ -19,7 +19,7 @@ obj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o obj-y += net-sysfs.o obj-y += hotdata.o -obj-y += netdev_rx_queue.o +obj-y += netdev_config.o netdev_rx_queue.o obj-$(CONFIG_PAGE_POOL) += page_pool.o page_pool_user.o obj-$(CONFIG_PROC_FS) += net-procfs.o obj-$(CONFIG_NET_PKTGEN) += pktgen.o
net/core/dev.c +2 -5
@@ -11784,10 +11784,8 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,if (!dev->ethtool) goto free_all; - dev->cfg =kzalloc (sizeof (*dev->cfg),GFP_KERNEL_ACCOUNT ); -if (!dev->cfg) +if (netdev_alloc_config (dev)) goto free_all; - dev->cfg_pending = dev->cfg; napi_config_sz =array_size (maxqs,sizeof (*dev->napi_config)); dev->napi_config =kvzalloc (napi_config_sz,GFP_KERNEL_ACCOUNT );
@@ -11857,8 +11855,7 @@ void free_netdev(struct net_device *dev)return ; } -WARN_ON (dev->cfg != dev->cfg_pending); -kfree (dev->cfg); +netdev_free_config (dev);kfree (dev->ethtool);netif_free_tx_queues (dev);netif_free_rx_queues (dev);
net/core/dev.h +5 -0
@@ -92,6 +92,11 @@ extern struct rw_semaphore dev_addr_sem;extern struct list_head net_todo_list;void netdev_run_todo (void ); +int netdev_alloc_config (struct net_device *dev); +void __netdev_free_config (struct netdev_config *cfg); +void netdev_free_config (struct net_device *dev); +int netdev_reconfig_start (struct net_device *dev); +/* netdev management, shared between various uAPI entry points */ struct netdev_name_node {struct hlist_node hlist;
net/core/netdev_config.c new file +43 -0
43 lines not on this page · raw message
net/ethtool/netlink.c +6 -8
@@ -6,6 +6,7 @@#include <linux/ethtool_netlink.h> #include <linux/phy_link_topology.h> #include <linux/pm_runtime.h> +#include "../core/dev.h" #include "netlink.h" #include "module_fw.h"
@@ -891,12 +892,9 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)rtnl_lock ();netdev_lock_ops (dev); - dev->cfg_pending =kmemdup (dev->cfg,sizeof (*dev->cfg), -GFP_KERNEL_ACCOUNT ); -if (!dev->cfg_pending) { - ret = -ENOMEM ; - goto out_tie_cfg; - } + ret =netdev_reconfig_start (dev); +if (ret) + goto out_unlock; ret =ethnl_ops_begin (dev);if (ret <0 )
@@ -915,9 +913,9 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info) out_ops:ethnl_ops_complete (dev); out_free_cfg: -kfree (dev->cfg_pending); -out_tie_cfg: +__netdev_free_config (dev->cfg_pending); dev->cfg_pending = dev->cfg; +out_unlock:netdev_unlock_ops (dev);rtnl_unlock (); out_dev:
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 10/22 net: reduce indent of struct netdev_queue_mgmt_ops members
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Trivial change, reduce the indent. I think the original is copied from real NDOs. It's unnecessarily deep, makes passing struct args problematic.
--- include/net/netdev_queues.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-)
include/net/netdev_queues.h +12 -12
@@ -151,18 +151,18 @@ void netdev_stat_queue_sum(struct net_device *netdev,* be called for an interface which is open. */ struct netdev_queue_mgmt_ops { -size_t ndo_queue_mem_size; -int (*ndo_queue_mem_alloc)(struct net_device *dev, -void *per_queue_mem, -int idx); -void (*ndo_queue_mem_free)(struct net_device *dev, -void *per_queue_mem); -int (*ndo_queue_start)(struct net_device *dev, -void *per_queue_mem, -int idx); -int (*ndo_queue_stop)(struct net_device *dev, -void *per_queue_mem, -int idx); +size_t ndo_queue_mem_size; +int (*ndo_queue_mem_alloc)(struct net_device *dev, +void *per_queue_mem, +int idx); +void (*ndo_queue_mem_free)(struct net_device *dev, +void *per_queue_mem); +int (*ndo_queue_start)(struct net_device *dev, +void *per_queue_mem, +int idx); +int (*ndo_queue_stop)(struct net_device *dev, +void *per_queue_mem, +int idx); };/**
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 11/22 net: allocate per-queue config structs and pass them thru the queue API
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Create an array of config structs to store per-queue config. Pass these structs in the queue API. Drivers can also retrieve the config for a single queue calling netdev_queue_config() directly.
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 8 ++- drivers/net/ethernet/google/gve/gve_main.c | 9 ++-- drivers/net/netdevsim/netdev.c | 6 ++- include/net/netdev_queues.h | 19 +++++++ net/core/dev.h | 3 ++ net/core/netdev_config.c | 58 ++++++++++++++++++++++ net/core/netdev_rx_queue.c | 11 ++-- 7 files changed, 104 insertions(+), 10 deletions(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +6 -2
@@ -15820,7 +15820,9 @@ static const struct netdev_stat_ops bnxt_stat_ops = { .get_base_stats = bnxt_get_base_stats, }; -static int bnxt_queue_mem_alloc (struct net_device *dev,void *qmem,int idx) +static int bnxt_queue_mem_alloc (struct net_device *dev, +struct netdev_queue_config *qcfg, +void *qmem,int idx) {struct bnxt_rx_ring_info *rxr, *clone;struct bnxt *bp =netdev_priv (dev);
@@ -15988,7 +15990,9 @@ static void bnxt_copy_rx_ring(struct bnxt *bp, dst->rx_agg_bmap = src->rx_agg_bmap; } -static int bnxt_queue_start (struct net_device *dev,void *qmem,int idx) +static int bnxt_queue_start (struct net_device *dev, +struct netdev_queue_config *qcfg, +void *qmem,int idx) {struct bnxt *bp =netdev_priv (dev);struct bnxt_rx_ring_info *rxr, *clone;
drivers/net/ethernet/google/gve/gve_main.c +6 -3
@@ -2429,8 +2429,9 @@ static void gve_rx_queue_mem_free(struct net_device *dev, void *per_q_mem)gve_rx_free_ring_dqo (priv, gve_per_q_mem, &cfg); } -static int gve_rx_queue_mem_alloc (struct net_device *dev,void *per_q_mem, -int idx) +static int gve_rx_queue_mem_alloc (struct net_device *dev, +struct netdev_queue_config *qcfg, +void *per_q_mem,int idx) {struct gve_priv *priv =netdev_priv (dev);struct gve_rx_alloc_rings_cfg cfg = {0 };
@@ -2451,7 +2452,9 @@ static int gve_rx_queue_mem_alloc(struct net_device *dev, void *per_q_mem,return err; } -static int gve_rx_queue_start (struct net_device *dev,void *per_q_mem,int idx) +static int gve_rx_queue_start (struct net_device *dev, +struct netdev_queue_config *qcfg, +void *per_q_mem,int idx) {struct gve_priv *priv =netdev_priv (dev);struct gve_rx_ring *gve_per_q_mem;
drivers/net/netdevsim/netdev.c +4 -2
@@ -664,7 +664,8 @@ struct nsim_queue_mem { };static int -nsim_queue_mem_alloc (struct net_device *dev,void *per_queue_mem,int idx) +nsim_queue_mem_alloc (struct net_device *dev,struct netdev_queue_config *qcfg, +void *per_queue_mem,int idx) {struct nsim_queue_mem *qmem = per_queue_mem;struct netdevsim *ns =netdev_priv (dev);
@@ -713,7 +714,8 @@ static void nsim_queue_mem_free(struct net_device *dev, void *per_queue_mem) }static int -nsim_queue_start (struct net_device *dev,void *per_queue_mem,int idx) +nsim_queue_start (struct net_device *dev,struct netdev_queue_config *qcfg, +void *per_queue_mem,int idx) {struct nsim_queue_mem *qmem = per_queue_mem;struct netdevsim *ns =netdev_priv (dev);
include/net/netdev_queues.h +19 -0
@@ -31,6 +31,13 @@ struct netdev_config {/** @hds_config: HDS enabled (ETHTOOL_A_RINGS_TCP_DATA_SPLIT). */ u8 hds_config; + +/** @qcfg: per-queue configuration */ +struct netdev_queue_config *qcfg; +}; + +/* Same semantics as fields in struct netdev_config */ +struct netdev_queue_config { };/* See the netdev.yaml spec for definition of each statistic */
@@ -135,6 +142,10 @@ void netdev_stat_queue_sum(struct net_device *netdev,* * @ndo_queue_mem_size: Size of the struct that describes a queue's memory. * +* @ndo_queue_cfg_defaults: (Optional) Populate queue config struct with +* defaults. Queue config structs are passed to this +* helper before the user-requested settings are applied. +* * @ndo_queue_mem_alloc: Allocate memory for an RX queue at the specified index. * The new memory is written at the specified address. *
@@ -152,12 +163,17 @@ void netdev_stat_queue_sum(struct net_device *netdev,*/ struct netdev_queue_mgmt_ops {size_t ndo_queue_mem_size; +void (*ndo_queue_cfg_defaults)(struct net_device *dev, +int idx, +struct netdev_queue_config *qcfg);int (*ndo_queue_mem_alloc)(struct net_device *dev, +struct netdev_queue_config *qcfg,void *per_queue_mem,int idx);void (*ndo_queue_mem_free)(struct net_device *dev,void *per_queue_mem);int (*ndo_queue_start)(struct net_device *dev, +struct netdev_queue_config *qcfg,void *per_queue_mem,int idx); int (*ndo_queue_stop)(struct net_device *dev,
@@ -165,6 +181,9 @@ struct netdev_queue_mgmt_ops { int idx); }; +void netdev_queue_config (struct net_device *dev,int rxq, +struct netdev_queue_config *qcfg); +/** * DOC: Lockless queue stopping / waking helpers. *
net/core/dev.h +3 -0
@@ -9,6 +9,7 @@#include <net/netdev_lock.h> struct net ; +struct netdev_queue_config ;struct netlink_ext_ack ;struct cpumask ;
@@ -96,6 +97,8 @@ int netdev_alloc_config(struct net_device *dev);void __netdev_free_config (struct netdev_config *cfg);void netdev_free_config (struct net_device *dev);int netdev_reconfig_start (struct net_device *dev); +void __netdev_queue_config (struct net_device *dev,int rxq, +struct netdev_queue_config *qcfg,bool pending);/* netdev management, shared between various uAPI entry points */ struct netdev_name_node {
net/core/netdev_config.c +58 -0
88 lines not on this page · raw message
net/core/netdev_rx_queue.c +8 -3
@@ -7,12 +7,14 @@#include <net/netdev_rx_queue.h> #include <net/page_pool/memory_provider.h> +#include "dev.h" #include "page_pool_priv.h" int netdev_rx_queue_restart (struct net_device *dev,unsigned int rxq_idx) {struct netdev_rx_queue *rxq =__netif_get_rx_queue (dev, rxq_idx);const struct netdev_queue_mgmt_ops *qops = dev->queue_mgmt_ops; +struct netdev_queue_config qcfg;void *new_mem, *old_mem;int err;
@@ -32,7 +34,9 @@ int netdev_rx_queue_restart(struct net_device *dev, unsigned int rxq_idx) goto err_free_new_mem; } - err = qops->ndo_queue_mem_alloc (dev, new_mem, rxq_idx); +netdev_queue_config (dev, rxq_idx, &qcfg); + + err = qops->ndo_queue_mem_alloc (dev, &qcfg, new_mem, rxq_idx);if (err) goto err_free_old_mem;
@@ -45,7 +49,7 @@ int netdev_rx_queue_restart(struct net_device *dev, unsigned int rxq_idx)if (err) goto err_free_new_queue_mem; - err = qops->ndo_queue_start (dev, new_mem, rxq_idx); + err = qops->ndo_queue_start (dev, &qcfg, new_mem, rxq_idx);if (err) goto err_start_queue; }else {
@@ -60,6 +64,7 @@ int netdev_rx_queue_restart(struct net_device *dev, unsigned int rxq_idx)return 0 ; err_start_queue: +__netdev_queue_config (dev, rxq_idx, &qcfg, false);/* Restarting the queue with old_mem should be successful as we haven't * changed any of the queue configuration, and there is not much we can * do to recover from a failure here.
@@ -67,7 +72,7 @@ int netdev_rx_queue_restart(struct net_device *dev, unsigned int rxq_idx)* WARN if we fail to recover the old rx queue, and at least free * old_mem so we don't also leak that. */ -if (qops->ndo_queue_start (dev, old_mem, rxq_idx)) { +if (qops->ndo_queue_start (dev, &qcfg, old_mem, rxq_idx)) {WARN (1 ,"Failed to restart old queue in error path. RX queue %d may be unhealthy." , rxq_idx);
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 12/22 net: pass extack to netdev_rx_queue_restart()
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Pass extack to netdev_rx_queue_restart(). Subsequent change will need it.
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 2 +- drivers/net/netdevsim/netdev.c | 2 +- include/net/netdev_rx_queue.h | 3 ++- net/core/netdev_rx_queue.c | 7 ++++--- 4 files changed, 8 insertions(+), 6 deletions(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +1 -1
@@ -11518,7 +11518,7 @@ static void bnxt_irq_affinity_notify(struct irq_affinity_notify *notify,netdev_lock (irq->bp->dev);if (netif_running (irq->bp->dev)) { - err =netdev_rx_queue_restart (irq->bp->dev, irq->ring_nr); + err =netdev_rx_queue_restart (irq->bp->dev, irq->ring_nr,NULL );if (err)netdev_err (irq->bp->dev,"RX queue restart failed: err=%d\n" , err);
drivers/net/netdevsim/netdev.c +1 -1
@@ -800,7 +800,7 @@ nsim_qreset_write(struct file *file, const char __user *data, } ns->rq_reset_mode = mode; - ret =netdev_rx_queue_restart (ns->netdev, queue); + ret =netdev_rx_queue_restart (ns->netdev, queue,NULL ); ns->rq_reset_mode =0 ;if (ret) goto exit_unlock;
include/net/netdev_rx_queue.h +2 -1
@@ -56,6 +56,7 @@ get_netdev_rx_queue_index(struct netdev_rx_queue *queue) return index; } -int netdev_rx_queue_restart (struct net_device *dev,unsigned int rxq); +int netdev_rx_queue_restart (struct net_device *dev,unsigned int rxq, +struct netlink_ext_ack *extack);#endif
net/core/netdev_rx_queue.c +4 -3
@@ -10,7 +10,8 @@#include "dev.h" #include "page_pool_priv.h" -int netdev_rx_queue_restart (struct net_device *dev,unsigned int rxq_idx) +int netdev_rx_queue_restart (struct net_device *dev,unsigned int rxq_idx, +struct netlink_ext_ack *extack) {struct netdev_rx_queue *rxq =__netif_get_rx_queue (dev, rxq_idx);const struct netdev_queue_mgmt_ops *qops = dev->queue_mgmt_ops;
@@ -136,7 +137,7 @@ int __net_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,#endif rxq->mp_params = *p; - ret =netdev_rx_queue_restart (dev, rxq_idx); + ret =netdev_rx_queue_restart (dev, rxq_idx, extack);if (ret) { rxq->mp_params.mp_ops =NULL ; rxq->mp_params.mp_priv =NULL ;
@@ -179,7 +180,7 @@ void __net_mp_close_rxq(struct net_device *dev, unsigned int ifq_idx, rxq->mp_params.mp_ops =NULL ; rxq->mp_params.mp_priv =NULL ; - err =netdev_rx_queue_restart (dev, ifq_idx); + err =netdev_rx_queue_restart (dev, ifq_idx,NULL );WARN_ON (err && err != -ENETDOWN ); }
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 13/22 net: add queue config validation callback
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
I imagine (tm) that as the number of per-queue configuration options grows some of them may conflict for certain drivers. While the drivers can obviously do all the validation locally doing so is fairly inconvenient as the config is fed to drivers piecemeal via different ops (for different params and NIC-wide vs per-queue).
Add a centralized callback for validating the queue config in queue ops. The callback gets invoked before each queue restart and when ring params are modified.
For NIC-wide changes the callback gets invoked for each active (or active to-be) queue, and additionally with a negative queue index for NIC-wide defaults. The NIC-wide check is needed in case all queues have an override active when NIC-wide setting is changed to an unsupported one. Alternatively we could check the settings when new queues are enabled (in the channel API), but accepting invalid config is a bad idea. Users may expect that resetting a queue override will always work.
The "trick" of passing a negative index is a bit ugly, we may want to revisit if it causes confusion and bugs. Existing drivers don't care about the index so it "just works".
--- include/net/netdev_queues.h | 12 ++++++++++++ net/core/dev.h | 2 ++ net/core/netdev_config.c | 20 ++++++++++++++++++++ net/core/netdev_rx_queue.c | 6 ++++++ net/ethtool/rings.c | 5 +++++ 5 files changed, 45 insertions(+)
include/net/netdev_queues.h +12 -0
@@ -146,6 +146,14 @@ void netdev_stat_queue_sum(struct net_device *netdev,* defaults. Queue config structs are passed to this * helper before the user-requested settings are applied. * +* @ndo_queue_cfg_validate: (Optional) Check if queue config is supported. +* Called when configuration affecting a queue may be +* changing, either due to NIC-wide config, or config +* scoped to the queue at a specified index. +* When NIC-wide config is changed the callback will +* be invoked for all queues, and in addition to that +* with a negative queue index for the base settings. +* * @ndo_queue_mem_alloc: Allocate memory for an RX queue at the specified index. * The new memory is written at the specified address. *
@@ -166,6 +174,10 @@ struct netdev_queue_mgmt_ops {void (*ndo_queue_cfg_defaults)(struct net_device *dev,int idx,struct netdev_queue_config *qcfg); +int (*ndo_queue_cfg_validate)(struct net_device *dev, +int idx, +struct netdev_queue_config *qcfg, +struct netlink_ext_ack *extack); int (*ndo_queue_mem_alloc)(struct net_device *dev, struct netdev_queue_config *qcfg, void *per_queue_mem,
net/core/dev.h +2 -0
@@ -99,6 +99,8 @@ void netdev_free_config(struct net_device *dev);int netdev_reconfig_start (struct net_device *dev);void __netdev_queue_config (struct net_device *dev,int rxq,struct netdev_queue_config *qcfg,bool pending); +int netdev_queue_config_revalidate (struct net_device *dev, +struct netlink_ext_ack *extack);/* netdev management, shared between various uAPI entry points */ struct netdev_name_node {
net/core/netdev_config.c +20 -0
@@ -99,3 +99,23 @@ void netdev_queue_config(struct net_device *dev, int rxq,__netdev_queue_config (dev, rxq, qcfg, true); }EXPORT_SYMBOL (netdev_queue_config); + +int netdev_queue_config_revalidate (struct net_device *dev, +struct netlink_ext_ack *extack) +{ +const struct netdev_queue_mgmt_ops *qops = dev->queue_mgmt_ops; +struct netdev_queue_config qcfg; +int i, err; + +if (!qops || !qops->ndo_queue_cfg_validate) +return 0 ; + +for (i =-1 ; i < (int )dev->real_num_rx_queues; i++) { +netdev_queue_config (dev, i, &qcfg); + err = qops->ndo_queue_cfg_validate (dev, i, &qcfg, extack); +if (err) +return err; + } + +return 0 ; +}
net/core/netdev_rx_queue.c +6 -0
@@ -37,6 +37,12 @@ int netdev_rx_queue_restart(struct net_device *dev, unsigned int rxq_idx,netdev_queue_config (dev, rxq_idx, &qcfg); +if (qops->ndo_queue_cfg_validate) { + err = qops->ndo_queue_cfg_validate (dev, rxq_idx, &qcfg, extack); +if (err) + goto err_free_old_mem; + } + err = qops->ndo_queue_mem_alloc (dev, &qcfg, new_mem, rxq_idx);if (err) goto err_free_old_mem;
net/ethtool/rings.c +5 -0
@@ -4,6 +4,7 @@#include "netlink.h" #include "common.h" +#include "../core/dev.h" struct rings_req_info {struct ethnl_req_info base;
@@ -307,6 +308,10 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info) dev->cfg_pending->hds_config = kernel_ringparam.tcp_data_split; dev->cfg_pending->hds_thresh = kernel_ringparam.hds_thresh; + ret =netdev_queue_config_revalidate (dev, info->extack); +if (ret) +return ret; + ret = dev->ethtool_ops->set_ringparam (dev, &ringparam, &kernel_ringparam, info->extack);return ret <0 ? ret :1 ;
Signature · 1 line
2.49.0
Re: [RFC v1 13/22] net: add queue config validation callback
Quoted 89 lines · Pavel Begunkov
From: Jakub Kicinski <[email protected]>
I imagine (tm) that as the number of per-queue configuration options grows some of them may conflict for certain drivers. While the drivers can obviously do all the validation locally doing so is fairly inconvenient as the config is fed to drivers piecemeal via different ops (for different params and NIC-wide vs per-queue).
Add a centralized callback for validating the queue config in queue ops. The callback gets invoked before each queue restart and when ring params are modified.
For NIC-wide changes the callback gets invoked for each active (or active to-be) queue, and additionally with a negative queue index for NIC-wide defaults. The NIC-wide check is needed in case all queues have an override active when NIC-wide setting is changed to an unsupported one. Alternatively we could check the settings when new queues are enabled (in the channel API), but accepting invalid config is a bad idea. Users may expect that resetting a queue override will always work.
The "trick" of passing a negative index is a bit ugly, we may want to revisit if it causes confusion and bugs. Existing drivers don't care about the index so it "just works".
Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- include/net/netdev_queues.h | 12 ++++++++++++ net/core/dev.h | 2 ++ net/core/netdev_config.c | 20 ++++++++++++++++++++ net/core/netdev_rx_queue.c | 6 ++++++ net/ethtool/rings.c | 5 +++++ 5 files changed, 45 insertions(+)include/net/netdev_queues.h +12
-0@@ -146,6 +146,14 @@ void netdev_stat_queue_sum(struct net_device *netdev,* defaults. Queue config structs are passed to this * helper before the user-requested settings are applied. * +* @ndo_queue_cfg_validate: (Optional) Check if queue config is supported. +* Called when configuration affecting a queue may be +* changing, either due to NIC-wide config, or config +* scoped to the queue at a specified index. +* When NIC-wide config is changed the callback will +* be invoked for all queues, and in addition to that +* with a negative queue index for the base settings. +* * @ndo_queue_mem_alloc: Allocate memory for an RX queue at the specified index. * The new memory is written at the specified address. * @@ -166,6 +174,10 @@ struct netdev_queue_mgmt_ops {void (*ndo_queue_cfg_defaults)(struct net_device *dev,int idx,struct netdev_queue_config *qcfg); +int (*ndo_queue_cfg_validate)(struct net_device *dev, +int idx, +struct netdev_queue_config *qcfg, +struct netlink_ext_ack *extack); int (*ndo_queue_mem_alloc)(struct net_device *dev, struct netdev_queue_config *qcfg, void *per_queue_mem,net/core/dev.h +2
-0@@ -99,6 +99,8 @@ void netdev_free_config(struct net_device *dev);int netdev_reconfig_start (struct net_device *dev);void __netdev_queue_config (struct net_device *dev,int rxq,struct netdev_queue_config *qcfg,bool pending); +int netdev_queue_config_revalidate (struct net_device *dev, +struct netlink_ext_ack *extack);/* netdev management, shared between various uAPI entry points */ struct netdev_name_node {net/core/netdev_config.c +1
-0@@ -99,3 +99,23 @@ void netdev_queue_config(struct net_device *dev, int rxq,__netdev_queue_config (dev, rxq, qcfg, true); }EXPORT_SYMBOL (netdev_queue_config); +
net/core/netdev_config.c void netdev_queue_config(struct net_device *dev, int rxq, · line 112
Quoted 10 lines
net/core/netdev_config.c +10
-0+int netdev_queue_config_revalidate (struct net_device *dev, +struct netlink_ext_ack *extack) +{ +const struct netdev_queue_mgmt_ops *qops = dev->queue_mgmt_ops; +struct netdev_queue_config qcfg; +int i, err; + +if (!qops || !qops->ndo_queue_cfg_validate) +return 0 ; +
Shouldn't this be like return -EOPNOSUPP; or something? Otherwise how do you protect drivers (GVE) that support queue API but don't support a configuring a particular netdev_queue_config from core assuming that the configuration took place?
Quoted 52 lines
net/core/netdev_config.c +9
-0+for (i =-1 ; i < (int )dev->real_num_rx_queues; i++) { +netdev_queue_config (dev, i, &qcfg); + err = qops->ndo_queue_cfg_validate (dev, i, &qcfg, extack); +if (err) +return err; + } + +return 0 ; +}net/core/netdev_rx_queue.c +6
-0@@ -37,6 +37,12 @@ int netdev_rx_queue_restart(struct net_device *dev, unsigned int rxq_idx,netdev_queue_config (dev, rxq_idx, &qcfg); +if (qops->ndo_queue_cfg_validate) { + err = qops->ndo_queue_cfg_validate (dev, rxq_idx, &qcfg, extack); +if (err) + goto err_free_old_mem; + } + err = qops->ndo_queue_mem_alloc (dev, &qcfg, new_mem, rxq_idx);if (err) goto err_free_old_mem;net/ethtool/rings.c +5
-1@@ -4,6 +4,7 @@#include "netlink.h" #include "common.h" +#include "../core/dev.h" struct rings_req_info {struct ethnl_req_info base;@@ -307,6 +308,10 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info) dev->cfg_pending->hds_config = kernel_ringparam.tcp_data_split; dev->cfg_pending->hds_thresh = kernel_ringparam.hds_thresh; + ret =netdev_queue_config_revalidate (dev, info->extack); +if (ret) +return ret; + ret = dev->ethtool_ops->set_ringparam (dev, &ringparam, &kernel_ringparam, info->extack);return ret <0 ? ret :1 ; --2.49.0
Signature · 2 lines
Thanks, Mina
Pavel Begunkov
maintainerRFC 14/22 eth: bnxt: always set the queue mgmt ops
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Core provides a centralized callback for validating per-queue settings but the callback is part of the queue management ops. Having the ops conditionally set complicates the parts of the driver which could otherwise lean on the core to feel it the correct settings.
Always set the queue ops, but provide no restart-related callbacks if queue ops are not supported by the device. This should maintain current behavior, the check in netdev_rx_queue_restart() looks both at op struct and individual ops.
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +5 -1
@@ -16126,6 +16126,9 @@ static const struct netdev_queue_mgmt_ops bnxt_queue_mgmt_ops = { .ndo_queue_stop = bnxt_queue_stop, }; +static const struct netdev_queue_mgmt_ops bnxt_queue_mgmt_ops_unsupp = { +}; +static void bnxt_remove_one (struct pci_dev *pdev) {struct net_device *dev =pci_get_drvdata (pdev);
@@ -16781,7 +16784,8 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) bp->rss_cap |=BNXT_RSS_CAP_MULTI_RSS_CTX ;if (BNXT_SUPPORTS_QUEUE_API (bp)) dev->queue_mgmt_ops = &bnxt_queue_mgmt_ops; - dev->request_ops_lock = true; +else + dev->queue_mgmt_ops = &bnxt_queue_mgmt_ops_unsupp; rc =register_netdev (dev);if (rc)
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 15/22 eth: bnxt: store the rx buf size per queue
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
In normal operation only a subset of queues is configured for zero-copy. Since zero-copy is the main use for larger buffer sizes we need to configure the sizes per queue.
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 46 ++++++++++--------- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 + drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c | 6 +-- drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.h | 2 +- 4 files changed, 30 insertions(+), 25 deletions(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +25 -21
@@ -895,7 +895,7 @@ static void bnxt_tx_int(struct bnxt *bp, struct bnxt_napi *bnapi, int budget)static bool bnxt_separate_head_pool (struct bnxt_rx_ring_info *rxr) { -return rxr->need_head_pool ||PAGE_SIZE > rxr->bnapi->bp->rx_page_size; +return rxr->need_head_pool ||PAGE_SIZE > rxr->rx_page_size; } static struct page *__bnxt_alloc_rx_page(struct bnxt *bp, dma_addr_t *mapping,
@@ -905,9 +905,9 @@ static struct page *__bnxt_alloc_rx_page(struct bnxt *bp, dma_addr_t *mapping, {struct page *page; -if (PAGE_SIZE > bp->rx_page_size) { +if (PAGE_SIZE > rxr->rx_page_size) { page =page_pool_dev_alloc_frag (rxr->page_pool, offset, - bp->rx_page_size); + rxr->rx_page_size); }else { page =page_pool_dev_alloc_pages (rxr->page_pool); *offset =0 ;
@@ -1139,9 +1139,9 @@ static struct sk_buff *bnxt_rx_multi_page_skb(struct bnxt *bp,return NULL ; } dma_addr -= bp->rx_dma_offset; -dma_sync_single_for_cpu (&bp->pdev->dev, dma_addr, bp->rx_page_size, +dma_sync_single_for_cpu (&bp->pdev->dev, dma_addr, rxr->rx_page_size, bp->rx_dir); - skb =napi_build_skb (data_ptr - bp->rx_offset, bp->rx_page_size); + skb =napi_build_skb (data_ptr - bp->rx_offset, rxr->rx_page_size);if (!skb) {page_pool_recycle_direct (rxr->page_pool, page);return NULL ;
@@ -1173,7 +1173,7 @@ static struct sk_buff *bnxt_rx_page_skb(struct bnxt *bp,return NULL ; } dma_addr -= bp->rx_dma_offset; -dma_sync_single_for_cpu (&bp->pdev->dev, dma_addr, bp->rx_page_size, +dma_sync_single_for_cpu (&bp->pdev->dev, dma_addr, rxr->rx_page_size, bp->rx_dir);if (unlikely (!payload))
@@ -1187,7 +1187,7 @@ static struct sk_buff *bnxt_rx_page_skb(struct bnxt *bp,skb_mark_for_recycle (skb); off = (void *)data_ptr -page_address (page); -skb_add_rx_frag (skb,0 , page, off, len, bp->rx_page_size); +skb_add_rx_frag (skb,0 , page, off, len, rxr->rx_page_size);memcpy (skb->data -NET_IP_ALIGN , data_ptr -NET_IP_ALIGN , payload +NET_IP_ALIGN );
@@ -1272,7 +1272,7 @@ static u32 __bnxt_rx_agg_netmems(struct bnxt *bp,if (skb) {skb_add_rx_frag_netmem (skb, i, cons_rx_buf->netmem, cons_rx_buf->offset, - frag_len, bp->rx_page_size); + frag_len, rxr->rx_page_size); }else {skb_frag_t *frag = &shinfo->frags[i];
@@ -1297,7 +1297,7 @@ static u32 __bnxt_rx_agg_netmems(struct bnxt *bp,if (skb) { skb->len -= frag_len; skb->data_len -= frag_len; - skb->truesize -= bp->rx_page_size; + skb->truesize -= rxr->rx_page_size; } --shinfo->nr_frags;
@@ -1312,7 +1312,7 @@ static u32 __bnxt_rx_agg_netmems(struct bnxt *bp, }page_pool_dma_sync_netmem_for_cpu (rxr->page_pool, netmem,0 , - bp->rx_page_size); + rxr->rx_page_size); total_frag_len += frag_len; prod =NEXT_RX_AGG (prod);
@@ -2265,8 +2265,7 @@ 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->page_pool, &xdp); + skb =bnxt_xdp_build_skb (bp, skb, agg_bufs, rxr, &xdp);if (!skb) {/* we should be able to free the old skb here */ bnxt_xdp_buff_frags_free (rxr, &xdp);
@@ -3806,7 +3805,7 @@ static int bnxt_alloc_rx_page_pool(struct bnxt *bp, pp.pool_size = bp->rx_agg_ring_size;if (BNXT_RX_PAGE_MODE (bp)) pp.pool_size += bp->rx_ring_size; - pp.order =get_order (bp->rx_page_size); + pp.order =get_order (rxr->rx_page_size); pp.nid = numa_node; pp.napi = &rxr->bnapi->napi; pp.netdev = bp->dev;
@@ -4292,6 +4291,8 @@ static void bnxt_init_ring_struct(struct bnxt *bp)if (!rxr) goto skip_rx; + rxr->rx_page_size = bp->rx_page_size; + ring = &rxr->rx_ring_struct; rmem = &ring->ring_mem; rmem->nr_pages = bp->rx_nr_pages;
@@ -4451,7 +4452,7 @@ static void bnxt_init_one_rx_agg_ring_rxbd(struct bnxt *bp, ring = &rxr->rx_agg_ring_struct; ring->fw_ring_id =INVALID_HW_RING_ID ;if ((bp->flags &BNXT_FLAG_AGG_RINGS )) { - type = ((u32 )bp->rx_page_size <<RX_BD_LEN_SHIFT ) | + type = ((u32 )rxr->rx_page_size <<RX_BD_LEN_SHIFT ) |RX_BD_TYPE_RX_AGG_BD |RX_BD_FLAGS_SOP ;bnxt_init_rxbd_pages (ring, type);
@@ -7016,6 +7017,7 @@ static void bnxt_hwrm_ring_grp_free(struct bnxt *bp)static void bnxt_set_rx_ring_params_p5 (struct bnxt *bp,u32 ring_type,struct hwrm_ring_alloc_input *req, +struct bnxt_rx_ring_info *rxr,struct bnxt_ring_struct *ring) {struct bnxt_ring_grp_info *grp_info = &bp->grp_info[ring->grp_idx];
@@ -7025,7 +7027,7 @@ static void bnxt_set_rx_ring_params_p5(struct bnxt *bp, u32 ring_type,if (ring_type ==HWRM_RING_ALLOC_AGG ) { req->ring_type =RING_ALLOC_REQ_RING_TYPE_RX_AGG ; req->rx_ring_id =cpu_to_le16 (grp_info->rx_fw_ring_id); - req->rx_buf_size =cpu_to_le16 (bp->rx_page_size); + req->rx_buf_size =cpu_to_le16 (rxr->rx_page_size); enables |=RING_ALLOC_REQ_ENABLES_RX_RING_ID_VALID ; }else { req->rx_buf_size =cpu_to_le16 (bp->rx_buf_use_size);
@@ -7039,6 +7041,7 @@ static void bnxt_set_rx_ring_params_p5(struct bnxt *bp, u32 ring_type, }static int hwrm_ring_alloc_send_msg (struct bnxt *bp, +struct bnxt_rx_ring_info *rxr,struct bnxt_ring_struct *ring,u32 ring_type,u32 map_index) {
@@ -7095,7 +7098,8 @@ static int hwrm_ring_alloc_send_msg(struct bnxt *bp, cpu_to_le32(bp->rx_ring_mask + 1) :cpu_to_le32 (bp->rx_agg_ring_mask +1 );if (bp->flags &BNXT_FLAG_CHIP_P5_PLUS ) -bnxt_set_rx_ring_params_p5 (bp, ring_type, req, ring); +bnxt_set_rx_ring_params_p5 (bp, ring_type, req, + rxr, ring);break ;case HWRM_RING_ALLOC_CMPL : req->ring_type =RING_ALLOC_REQ_RING_TYPE_L2_CMPL ;
@@ -7243,7 +7247,7 @@ static int bnxt_hwrm_rx_ring_alloc(struct bnxt *bp,u32 map_idx = bnapi->index;int rc; - rc =hwrm_ring_alloc_send_msg (bp, ring, type, map_idx); + rc =hwrm_ring_alloc_send_msg (bp, rxr, ring, type, map_idx);if (rc)return rc;
@@ -7263,7 +7267,7 @@ static int bnxt_hwrm_rx_agg_ring_alloc(struct bnxt *bp,int rc; map_idx = grp_idx + bp->rx_nr_rings; - rc =hwrm_ring_alloc_send_msg (bp, ring, type, map_idx); + rc =hwrm_ring_alloc_send_msg (bp, rxr, ring, type, map_idx);if (rc)return rc;
@@ -7287,7 +7291,7 @@ static int bnxt_hwrm_cp_ring_alloc_p5(struct bnxt *bp, ring = &cpr->cp_ring_struct; ring->handle =BNXT_SET_NQ_HDL (cpr); - rc =hwrm_ring_alloc_send_msg (bp, ring, type, map_idx); + rc =hwrm_ring_alloc_send_msg (bp,NULL , ring, type, map_idx);if (rc)return rc;bnxt_set_db (bp, &cpr->cp_db, type, map_idx, ring->fw_ring_id);
@@ -7302,7 +7306,7 @@ static int bnxt_hwrm_tx_ring_alloc(struct bnxt *bp,const u32 type =HWRM_RING_ALLOC_TX ;int rc; - rc =hwrm_ring_alloc_send_msg (bp, ring, type, tx_idx); + rc =hwrm_ring_alloc_send_msg (bp,NULL , ring, type, tx_idx);if (rc)return rc;bnxt_set_db (bp, &txr->tx_db, type, tx_idx, ring->fw_ring_id);
@@ -7328,7 +7332,7 @@ static int bnxt_hwrm_ring_alloc(struct bnxt *bp) vector = bp->irq_tbl[map_idx].vector;disable_irq_nosync (vector); - rc =hwrm_ring_alloc_send_msg (bp, ring, type, map_idx); + rc =hwrm_ring_alloc_send_msg (bp,NULL , ring, type, map_idx);if (rc) {enable_irq (vector); goto err_out;
drivers/net/ethernet/broadcom/bnxt/bnxt.h +1 -0
@@ -1107,6 +1107,7 @@ struct bnxt_rx_ring_info {unsigned long *rx_agg_bmap;u16 rx_agg_bmap_size; +u16 rx_page_size;bool need_head_pool;dma_addr_t rx_desc_mapping[MAX_RX_PAGES ];
drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c +3 -3
@@ -183,7 +183,7 @@ void bnxt_xdp_buff_init(struct bnxt *bp, struct bnxt_rx_ring_info *rxr, u16 cons, u8 *data_ptr, unsigned int len, struct xdp_buff *xdp) { - u32 buflen = bp->rx_page_size; + u32 buflen = rxr->rx_page_size;struct bnxt_sw_rx_bd *rx_buf;struct pci_dev *pdev;dma_addr_t mapping;
@@ -461,7 +461,7 @@ int bnxt_xdp(struct net_device *dev, struct netdev_bpf *xdp)struct sk_buff *bnxt_xdp_build_skb (struct bnxt *bp,struct sk_buff *skb,u8 num_frags, -struct page_pool *pool,struct xdp_buff *xdp) +struct bnxt_rx_ring_info *rxr,struct xdp_buff *xdp) {struct skb_shared_info *sinfo =xdp_get_shared_info_from_buff (xdp);
@@ -470,7 +470,7 @@ bnxt_xdp_build_skb(struct bnxt *bp, struct sk_buff *skb, u8 num_frags,xdp_update_skb_shared_info (skb, num_frags, sinfo->xdp_frags_size, - bp->rx_page_size * num_frags, + rxr->rx_page_size * num_frags,xdp_buff_is_frag_pfmemalloc (xdp));return skb; }
drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.h +1 -1
@@ -32,6 +32,6 @@ void bnxt_xdp_buff_init(struct bnxt *bp, struct bnxt_rx_ring_info *rxr,void bnxt_xdp_buff_frags_free (struct bnxt_rx_ring_info *rxr,struct xdp_buff *xdp);struct sk_buff *bnxt_xdp_build_skb (struct bnxt *bp,struct sk_buff *skb, -u8 num_frags,struct page_pool *pool, +u8 num_frags,struct bnxt_rx_ring_info *rxr,struct xdp_buff *xdp);#endif
Signature · 1 line
2.49.0
Re: [RFC v1 15/22] eth: bnxt: store the rx buf size per queue
Quoted 9 lines · Pavel Begunkov
From: Jakub Kicinski <[email protected]>
In normal operation only a subset of queues is configured for zero-copy. Since zero-copy is the main use for larger buffer sizes we need to configure the sizes per queue.
Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>
I wonder if this is necessary for some reason, or is it better to expect the driver to refer to the netdev->qcfgs directly?
By my count the configs can now live in 4 places: the core netdev config, the core per-queue config, the driver netdev config, and the driver per-queue config.
I honestly I'm not sure about duplicating settings between the netdev configs and the per-queue configs in the first place (seems like configs should be either driver wide or per-queue to me, and not both), and I'm less sure about again duplicating the settings between core structs and in-driver structs. Seems like the same information duplicated in many places and a nightmare to keep it all in sync.
Signature · 2 lines
Thanks, Mina
Jakub Kicinski
maintainerRe: [RFC v1 15/22] eth: bnxt: store the rx buf size per queue
Quoted 16 lines · On Mon, 28 Jul 2025 15:33:55 -0700 Mina Almasry wrote:
Quoted 8 lines
From: Jakub Kicinski <[email protected]>
In normal operation only a subset of queues is configured for zero-copy. Since zero-copy is the main use for larger buffer sizes we need to configure the sizes per queue.
Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>I wonder if this is necessary for some reason, or is it better to expect the driver to refer to the
netdev->qcfgsdirectly?By my count the configs can now live in 4 places: the core netdev config, the core per-queue config, the driver netdev config, and the driver per-queue config.
Quoted 6 lines
I honestly I'm not sure about duplicating settings between the netdev configs and the per-queue configs in the first place (seems like configs should be either driver wide or per-queue to me, and not both), and I'm less sure about again duplicating the settings between core structs and in-driver structs. Seems like the same information duplicated in many places and a nightmare to keep it all in sync.
Does patch 20 answer this question?
Pavel Begunkov
maintainerRFC 16/22 eth: bnxt: adjust the fill level of agg queues with larger buffers
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
The driver tries to provision more agg buffers than header buffers since multiple agg segments can reuse the same header. The calculation / heuristic tries to provide enough pages for 65k of data for each header (or 4 frags per header if the result is too big). This calculation is currently global to the adapter. If we increase the buffer sizes 8x we don't want 8x the amount of memory sitting on the rings. Luckily we don't have to fill the rings completely, adjust the fill level dynamically in case particular queue has buffers larger than the global size.
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +20 -3
@@ -3795,6 +3795,21 @@ static void bnxt_free_rx_rings(struct bnxt *bp) } } +static int bnxt_rx_agg_ring_fill_level (struct bnxt *bp, +struct bnxt_rx_ring_info *rxr) +{ +/* User may have chosen larger than default rx_page_size, +* we keep the ring sizes uniform and also want uniform amount +* of bytes consumed per ring, so cap how much of the rings we fill. +*/ +int fill_level = bp->rx_agg_ring_size; + +if (rxr->rx_page_size > bp->rx_page_size) + fill_level /= rxr->rx_page_size / bp->rx_page_size; + +return fill_level; +} +static int bnxt_alloc_rx_page_pool (struct bnxt *bp,struct bnxt_rx_ring_info *rxr,int numa_node)
@@ -3802,7 +3817,7 @@ static int bnxt_alloc_rx_page_pool(struct bnxt *bp,struct page_pool_params pp = {0 };struct page_pool *pool; - pp.pool_size = bp->rx_agg_ring_size; + pp.pool_size =bnxt_rx_agg_ring_fill_level (bp, rxr);if (BNXT_RX_PAGE_MODE (bp)) pp.pool_size += bp->rx_ring_size; pp.order =get_order (rxr->rx_page_size);
@@ -4370,11 +4385,13 @@ static void bnxt_alloc_one_rx_ring_netmem(struct bnxt *bp,struct bnxt_rx_ring_info *rxr, int ring_nr) { + int fill_level, i;u32 prod; -int i; + + fill_level =bnxt_rx_agg_ring_fill_level (bp, rxr); prod = rxr->rx_agg_prod; -for (i =0 ; i < bp->rx_agg_ring_size; i++) { +for (i =0 ; i < fill_level; i++) {if (bnxt_alloc_rx_netmem (bp, rxr, prod,GFP_KERNEL )) {netdev_warn (bp->dev,"init'ed rx ring %d with %d/%d pages only\n" , ring_nr, i, bp->rx_ring_size);
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 17/22 netdev: add support for setting rx-buf-len per queue
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Zero-copy APIs increase the cost of buffer management. They also extend this cost to user space applications which may be used to dealing with much larger buffers. Allow setting rx-buf-len per queue, devices with HW-GRO support can commonly fill buffers up to 32k (or rather 64k - 1 but that's not a power of 2..)
The implementation adds a new option to the netdev netlink, rather than ethtool. The NIC-wide setting lives in ethtool ringparams so one could argue that we should be extending the ethtool API. OTOH netdev API is where we already have queue-get, and it's how zero-copy applications bind memory providers.
--- Documentation/netlink/specs/netdev.yaml | 15 ++++ include/net/netdev_queues.h | 5 ++ include/net/netlink.h | 19 +++++ include/uapi/linux/netdev.h | 2 + net/core/netdev-genl-gen.c | 15 ++++ net/core/netdev-genl-gen.h | 1 + net/core/netdev-genl.c | 92 +++++++++++++++++++++++++ net/core/netdev_config.c | 16 +++++ tools/include/uapi/linux/netdev.h | 2 + 9 files changed, 167 insertions(+)
Documentation/netlink/specs/netdev.yaml +15 -0
@@ -324,6 +324,10 @@ attribute-sets: doc: XSK information for this queue, if any. type: nest nested-attributes: xsk-info + - + name: rx-buf-len + doc: Per-queue configuration of ETHTOOL_A_RINGS_RX_BUF_LEN. + type: u32 - name: qstats doc: |
@@ -755,6 +759,17 @@ operations: reply: attributes: - id + - + name: queue-set + doc: Set per-queue configurable options. + attribute-set: queue + do: + request: + attributes: + - ifindex + - type + - id + - rx-buf-len kernel-family: headers: [ "net/netdev_netlink.h"]
include/net/netdev_queues.h +5 -0
@@ -38,6 +38,7 @@ struct netdev_config {/* Same semantics as fields in struct netdev_config */ struct netdev_queue_config { +u32 rx_buf_len; };/* See the netdev.yaml spec for definition of each statistic */
@@ -140,6 +141,8 @@ void netdev_stat_queue_sum(struct net_device *netdev,/** * struct netdev_queue_mgmt_ops - netdev ops for queue management * +* @supported_ring_params: ring params supported per queue (ETHTOOL_RING_USE_*). +* * @ndo_queue_mem_size: Size of the struct that describes a queue's memory. * * @ndo_queue_cfg_defaults: (Optional) Populate queue config struct with
@@ -170,6 +173,8 @@ void netdev_stat_queue_sum(struct net_device *netdev,* be called for an interface which is open. */ struct netdev_queue_mgmt_ops { +u32 supported_ring_params; +size_t ndo_queue_mem_size; void (*ndo_queue_cfg_defaults)(struct net_device *dev, int idx,
include/net/netlink.h +19 -0
@@ -2186,6 +2186,25 @@ static inline struct nla_bitfield32 nla_get_bitfield32(const struct nlattr *nla)return tmp; } +/** +* nla_update_u32() - update u32 value from NLA_U32 attribute +* @dst: value to update +* @attr: netlink attribute with new value or null +* +* Copy the u32 value from NLA_U32 netlink attribute @attr into variable +* pointed to by @dst; do nothing if @attr is null. +* +* Return: true if this function changed the value of @dst, otherwise false. +*/ +static inline bool nla_update_u32 (u32 *dst,const struct nlattr *attr) +{ +u32 old_val = *dst; + +if (attr) + *dst =nla_get_u32 (attr); +return *dst != old_val; +} +/** * nla_memdup - duplicate attribute memory (kmemdup) * @src: netlink attribute to duplicate from
include/uapi/linux/netdev.h +2 -0
@@ -152,6 +152,7 @@ enum {NETDEV_A_QUEUE_DMABUF ,NETDEV_A_QUEUE_IO_URING ,NETDEV_A_QUEUE_XSK , +NETDEV_A_QUEUE_RX_BUF_LEN , __NETDEV_A_QUEUE_MAX,NETDEV_A_QUEUE_MAX = (__NETDEV_A_QUEUE_MAX -1 )
@@ -220,6 +221,7 @@ enum {NETDEV_CMD_BIND_RX ,NETDEV_CMD_NAPI_SET ,NETDEV_CMD_BIND_TX , +NETDEV_CMD_QUEUE_SET , __NETDEV_CMD_MAX,NETDEV_CMD_MAX = (__NETDEV_CMD_MAX -1 )
net/core/netdev-genl-gen.c +15 -0
@@ -105,6 +105,14 @@ static const struct nla_policy netdev_bind_tx_nl_policy[NETDEV_A_DMABUF_FD + 1] [NETDEV_A_DMABUF_FD] = { .type = NLA_U32, }, }; +/* NETDEV_CMD_QUEUE_SET - do */ +static const struct nla_policy netdev_queue_set_nl_policy[NETDEV_A_QUEUE_RX_BUF_LEN +1 ] = { + [NETDEV_A_QUEUE_IFINDEX ] =NLA_POLICY_MIN (NLA_U32 ,1 ), + [NETDEV_A_QUEUE_TYPE ] =NLA_POLICY_MAX (NLA_U32 ,1 ), + [NETDEV_A_QUEUE_ID ] = { .type =NLA_U32 , }, + [NETDEV_A_QUEUE_RX_BUF_LEN ] = { .type =NLA_U32 , }, +}; +/* Ops table for netdev */ static const struct genl_split_ops netdev_nl_ops[] = { {
@@ -203,6 +211,13 @@ static const struct genl_split_ops netdev_nl_ops[] = { .maxattr = NETDEV_A_DMABUF_FD, .flags = GENL_CMD_CAP_DO, }, + { + .cmd = NETDEV_CMD_QUEUE_SET, + .doit = netdev_nl_queue_set_doit, + .policy = netdev_queue_set_nl_policy, + .maxattr = NETDEV_A_QUEUE_RX_BUF_LEN, + .flags = GENL_CMD_CAP_DO, + }, };static const struct genl_multicast_group netdev_nl_mcgrps[] = {
net/core/netdev-genl-gen.h +1 -0
@@ -35,6 +35,7 @@ int netdev_nl_qstats_get_dumpit(struct sk_buff *skb,int netdev_nl_bind_rx_doit (struct sk_buff *skb,struct genl_info *info);int netdev_nl_napi_set_doit (struct sk_buff *skb,struct genl_info *info);int netdev_nl_bind_tx_doit (struct sk_buff *skb,struct genl_info *info); +int netdev_nl_queue_set_doit (struct sk_buff *skb,struct genl_info *info);enum {NETDEV_NLGRP_MGMT ,
net/core/netdev-genl.c +92 -0
110 lines not on this page · raw message
net/core/netdev_config.c +16 -0
@@ -67,11 +67,27 @@ int netdev_reconfig_start(struct net_device *dev)void __netdev_queue_config (struct net_device *dev,int rxq,struct netdev_queue_config *qcfg,bool pending) { +const struct netdev_config *cfg; + + cfg = pending ? dev->cfg_pending : dev->cfg; +memset (qcfg,0 ,sizeof (*qcfg));/* Get defaults from the driver, in case user config not set */ if (dev->queue_mgmt_ops->ndo_queue_cfg_defaults) dev->queue_mgmt_ops->ndo_queue_cfg_defaults (dev, rxq, qcfg); + +/* Set config based on device-level settings */ +if (cfg->rx_buf_len) + qcfg->rx_buf_len = cfg->rx_buf_len; + +/* Set config dedicated to this queue */ +if (rxq >=0 ) { +const struct netdev_queue_config *user_cfg = &cfg->qcfg[rxq]; + +if (user_cfg->rx_buf_len) + qcfg->rx_buf_len = user_cfg->rx_buf_len; + } }/**
tools/include/uapi/linux/netdev.h +2 -0
@@ -152,6 +152,7 @@ enum {NETDEV_A_QUEUE_DMABUF ,NETDEV_A_QUEUE_IO_URING ,NETDEV_A_QUEUE_XSK , +NETDEV_A_QUEUE_RX_BUF_LEN , __NETDEV_A_QUEUE_MAX,NETDEV_A_QUEUE_MAX = (__NETDEV_A_QUEUE_MAX -1 )
@@ -220,6 +221,7 @@ enum {NETDEV_CMD_BIND_RX ,NETDEV_CMD_NAPI_SET ,NETDEV_CMD_BIND_TX , +NETDEV_CMD_QUEUE_SET , __NETDEV_CMD_MAX,NETDEV_CMD_MAX = (__NETDEV_CMD_MAX -1 )
Signature · 1 line
2.49.0
Re: [RFC v1 17/22] netdev: add support for setting rx-buf-len per queue
Quoted 75 lines · Pavel Begunkov
From: Jakub Kicinski <[email protected]>
Zero-copy APIs increase the cost of buffer management. They also extend this cost to user space applications which may be used to dealing with much larger buffers. Allow setting rx-buf-len per queue, devices with HW-GRO support can commonly fill buffers up to 32k (or rather 64k - 1 but that's not a power of 2..)
The implementation adds a new option to the netdev netlink, rather than ethtool. The NIC-wide setting lives in ethtool ringparams so one could argue that we should be extending the ethtool API. OTOH netdev API is where we already have queue-get, and it's how zero-copy applications bind memory providers.
Signed-off-by Jakub Kicinski <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- Documentation/netlink/specs/netdev.yaml | 15 ++++ include/net/netdev_queues.h | 5 ++ include/net/netlink.h | 19 +++++ include/uapi/linux/netdev.h | 2 + net/core/netdev-genl-gen.c | 15 ++++ net/core/netdev-genl-gen.h | 1 + net/core/netdev-genl.c | 92 +++++++++++++++++++++++++ net/core/netdev_config.c | 16 +++++ tools/include/uapi/linux/netdev.h | 2 + 9 files changed, 167 insertions(+)Documentation/netlink/specs/netdev.yaml +15
-0@@ -324,6 +324,10 @@ attribute-sets: doc: XSK information for this queue, if any. type: nest nested-attributes: xsk-info + - + name: rx-buf-len + doc: Per-queue configuration of ETHTOOL_A_RINGS_RX_BUF_LEN. + type: u32 - name: qstats doc: |@@ -755,6 +759,17 @@ operations: reply: attributes: - id + - + name: queue-set + doc: Set per-queue configurable options. + attribute-set: queue + do: + request: + attributes: + - ifindex + - type + - id + - rx-buf-len kernel-family: headers: [ "net/netdev_netlink.h"]include/net/netdev_queues.h +1
-0@@ -38,6 +38,7 @@ struct netdev_config {/* Same semantics as fields in struct netdev_config */ struct netdev_queue_config { +u32 rx_buf_len; };/* See the netdev.yaml spec for definition of each statistic */
include/net/netdev_queues.h void netdev_stat_queue_sum(struct net_device *netdev, · line 145
Quoted 5 lines
include/net/netdev_queues.h +2
-0@@ -140,6 +141,8 @@ void netdev_stat_queue_sum(struct net_device *netdev,/** * struct netdev_queue_mgmt_ops - netdev ops for queue management * +* @supported_ring_params: ring params supported per queue (ETHTOOL_RING_USE_*). +*
I don't see this used anywhere.
But more generally, I'm a bit concerned about protecting drivers that don't support configuring one particular queue config. I think likely supported_ring_params needs to be moved earlier to the patch which adds per queue netdev_configs to the queue API, and probably as part of that patch core needs to make sure it's never asking a driver that doesn't support changing a netdev_queue_config to do so?
Some thought may be given to moving the entire configuration story outside of queue_mem_alloc/free queue_start/stop altogether to new ndos where core can easily check if the ndo is supported otherwise per-queue config is not supported. Otherwise core needs to be careful never to attempt a config that is not supported?
Signature · 2 lines
Thanks, Mina
Jakub Kicinski
maintainerRe: [RFC v1 17/22] netdev: add support for setting rx-buf-len per queue
Quoted 1 line · On Mon, 28 Jul 2025 16:10:36 -0700 Mina Almasry wrote:
I don't see this used anywhere.
Good catch, I think I was planning to reuse the full structs from ethtool. And when I gave up I forgot to re-implement the checks.
Quoted 6 lines
But more generally, I'm a bit concerned about protecting drivers that don't support configuring one particular queue config. I think likely
supported_ring_paramsneeds to be moved earlier to the patch which adds per queuenetdev_configsto the queue API, and probably as part of that patch core needs to make sure it's never asking a driver that doesn't support changing anetdev_queue_configto do so?
I may be missing your point, but the "supported_params" flags are just an internal kernel thing. Based on our rich experience of drivers not validating inputs the "supported" flags just tell the core that a driver will pay attention to the member of a struct. We can add new members without having to go over all existing drivers to add input validation.
The flag doesn't actually say anything about particular configuration being... well.. supported. It's just that the driver promises to interpret it.
Quoted 5 lines
Some thought may be given to moving the entire configuration story outside of queue_mem_alloc/free queue_start/stop altogether to new ndos where core can easily check if the ndo is supported otherwise per-queue config is not supported. Otherwise core needs to be careful never to attempt a config that is not supported?
The configuration is of the queues. The queue configuration belongs in the queue APIs.
Pavel Begunkov
maintainerRFC 18/22 net: wipe the setting of deactived queues
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Clear out all settings of deactived queues when user changes the number of channels. We already perform similar cleanup for shapers.
--- net/core/dev.c | 5 +++++ net/core/dev.h | 2 ++ net/core/netdev_config.c | 13 +++++++++++++ 3 files changed, 20 insertions(+)
net/core/dev.c +5 -0
@@ -3190,6 +3190,8 @@ int netif_set_real_num_tx_queues(struct net_device *dev, unsigned int txq)if (dev->num_tc)netif_setup_tc (dev, txq); +netdev_queue_config_update_cnt (dev, txq, + dev->real_num_rx_queues);net_shaper_set_real_num_tx_queues (dev, txq);dev_qdisc_change_real_num_tx (dev, txq);
@@ -3236,6 +3238,9 @@ int netif_set_real_num_rx_queues(struct net_device *dev, unsigned int rxq) rxq);if (rc)return rc; + +netdev_queue_config_update_cnt (dev, dev->real_num_tx_queues, + rxq); } dev->real_num_rx_queues = rxq;
net/core/dev.h +2 -0
@@ -101,6 +101,8 @@ void __netdev_queue_config(struct net_device *dev, int rxq, struct netdev_queue_config *qcfg, bool pending);int netdev_queue_config_revalidate (struct net_device *dev,struct netlink_ext_ack *extack); +void netdev_queue_config_update_cnt (struct net_device *dev,unsigned int txq, +unsigned int rxq);/* netdev management, shared between various uAPI entry points */ struct netdev_name_node {
net/core/netdev_config.c +13 -0
@@ -64,6 +64,19 @@ int netdev_reconfig_start(struct net_device *dev)return -ENOMEM ; } +void netdev_queue_config_update_cnt (struct net_device *dev,unsigned int txq, +unsigned int rxq) +{ +size_t len; + +if (rxq < dev->real_num_rx_queues) { + len = (dev->real_num_rx_queues - rxq) *sizeof (*dev->cfg->qcfg); + +memset (&dev->cfg->qcfg[rxq],0 , len); +memset (&dev->cfg_pending->qcfg[rxq],0 , len); + } +} +void __netdev_queue_config (struct net_device *dev,int rxq,struct netdev_queue_config *qcfg,bool pending) {
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 19/22 eth: bnxt: use queue op config validate
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Move the rx-buf-len config validation to the queue ops.
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 40 +++++++++++++++++++ .../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 12 ------ 2 files changed, 40 insertions(+), 12 deletions(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +40 -0
@@ -16139,8 +16139,46 @@ static int bnxt_queue_stop(struct net_device *dev, void *qmem, int idx)return 0 ; } +static int +bnxt_queue_cfg_validate (struct net_device *dev,int idx, +struct netdev_queue_config *qcfg, +struct netlink_ext_ack *extack) +{ +struct bnxt *bp =netdev_priv (dev); + +/* Older chips need MSS calc so rx_buf_len is not supported, +* but we don't set queue ops for them so we should never get here. +*/ +if (qcfg->rx_buf_len != bp->rx_page_size && + !(bp->flags &BNXT_FLAG_CHIP_P5_PLUS )) { +NL_SET_ERR_MSG_MOD (extack,"changing rx-buf-len not supported" ); +return -EINVAL ; + } + +if (!is_power_of_2 (qcfg->rx_buf_len)) { +NL_SET_ERR_MSG_MOD (extack,"rx-buf-len is not power of 2" ); +return -ERANGE ; + } +if (qcfg->rx_buf_len <BNXT_RX_PAGE_SIZE || + qcfg->rx_buf_len >BNXT_MAX_RX_PAGE_SIZE ) { +NL_SET_ERR_MSG_MOD (extack,"rx-buf-len out of range" ); +return -ERANGE ; + } +return 0 ; +} + +static void +bnxt_queue_cfg_defaults (struct net_device *dev,int idx, +struct netdev_queue_config *qcfg) +{ + qcfg->rx_buf_len =BNXT_RX_PAGE_SIZE ; +} +static const struct netdev_queue_mgmt_ops bnxt_queue_mgmt_ops = { .ndo_queue_mem_size =sizeof (struct bnxt_rx_ring_info ), + + .ndo_queue_cfg_defaults = bnxt_queue_cfg_defaults, + .ndo_queue_cfg_validate = bnxt_queue_cfg_validate, .ndo_queue_mem_alloc = bnxt_queue_mem_alloc, .ndo_queue_mem_free = bnxt_queue_mem_free, .ndo_queue_start = bnxt_queue_start,
@@ -16148,6 +16186,8 @@ static const struct netdev_queue_mgmt_ops bnxt_queue_mgmt_ops = { };static const struct netdev_queue_mgmt_ops bnxt_queue_mgmt_ops_unsupp = { + .ndo_queue_cfg_defaults = bnxt_queue_cfg_defaults, + .ndo_queue_cfg_validate = bnxt_queue_cfg_validate, };static void bnxt_remove_one (struct pci_dev *pdev)
drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +0 -12
@@ -867,18 +867,6 @@ static int bnxt_set_ringparam(struct net_device *dev,if (!kernel_ering->rx_buf_len)/* Zero means restore default */ kernel_ering->rx_buf_len =BNXT_RX_PAGE_SIZE ; -if (kernel_ering->rx_buf_len != bp->rx_page_size && - !(bp->flags &BNXT_FLAG_CHIP_P5_PLUS )) { -NL_SET_ERR_MSG_MOD (extack,"changing rx-buf-len not supported" ); -return -EINVAL ; - } -if (!is_power_of_2 (kernel_ering->rx_buf_len) || - kernel_ering->rx_buf_len <BNXT_RX_PAGE_SIZE || - kernel_ering->rx_buf_len >BNXT_MAX_RX_PAGE_SIZE ) { -NL_SET_ERR_MSG_MOD (extack,"rx-buf-len out of range, or not power of 2" ); -return -ERANGE ; - } -if (netif_running (dev))bnxt_close_nic (bp, false, false);
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 20/22 eth: bnxt: support per queue configuration of rx-buf-len
In reply to Pavel BegunkovFrom: Jakub Kicinski <[email protected]>
Now that the rx_buf_len is stored and validated per queue allow it being set differently for different queues. Instead of copying the device setting for each queue ask the core for the config via netdev_queue_config().
--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
drivers/net/ethernet/broadcom/bnxt/bnxt.c +7 -1
@@ -4284,6 +4284,7 @@ static void bnxt_init_ring_struct(struct bnxt *bp)for (i =0 ; i < bp->cp_nr_rings; i++) {struct bnxt_napi *bnapi = bp->bnapi[i]; +struct netdev_queue_config qcfg;struct bnxt_ring_mem_info *rmem;struct bnxt_cp_ring_info *cpr;struct bnxt_rx_ring_info *rxr;
@@ -4306,7 +4307,8 @@ static void bnxt_init_ring_struct(struct bnxt *bp)if (!rxr) goto skip_rx; - rxr->rx_page_size = bp->rx_page_size; +netdev_queue_config (bp->dev, i, &qcfg); + rxr->rx_page_size = qcfg.rx_buf_len; ring = &rxr->rx_ring_struct; rmem = &ring->ring_mem;
@@ -15863,6 +15865,7 @@ static int bnxt_queue_mem_alloc(struct net_device *dev, clone->rx_agg_prod =0 ; clone->rx_sw_agg_prod =0 ; clone->rx_next_cons =0 ; + clone->rx_page_size = qcfg->rx_buf_len; clone->need_head_pool = false; rc =bnxt_alloc_rx_page_pool (bp, clone, rxr->page_pool->p.nid);
@@ -15969,6 +15972,8 @@ static void bnxt_copy_rx_ring(struct bnxt *bp, src_ring = &src->rx_ring_struct; src_rmem = &src_ring->ring_mem; + dst->rx_page_size = src->rx_page_size; +WARN_ON (dst_rmem->nr_pages != src_rmem->nr_pages);WARN_ON (dst_rmem->page_size != src_rmem->page_size);WARN_ON (dst_rmem->flags != src_rmem->flags);
@@ -16175,6 +16180,7 @@ bnxt_queue_cfg_defaults(struct net_device *dev, int idx, }static const struct netdev_queue_mgmt_ops bnxt_queue_mgmt_ops = { + .supported_ring_params =ETHTOOL_RING_USE_RX_BUF_LEN , .ndo_queue_mem_size =sizeof (struct bnxt_rx_ring_info ), .ndo_queue_cfg_defaults = bnxt_queue_cfg_defaults,
Signature · 1 line
2.49.0
Pavel Begunkov
maintainerRFC 21/22 net: parametrise mp open with a queue config
In reply to Pavel BegunkovThis patch allows memory providers to pass a queue config when opening a queue. It'll be used in the next patch to pass a custom rx buffer length from zcrx. As there are many users of netdev_rx_queue_restart(), it's allowed to pass a NULL qcfg, in which case the function will use the default configuration.
--- include/net/page_pool/memory_provider.h | 4 +- io_uring/zcrx.c | 2 +- net/core/netdev_rx_queue.c | 50 +++++++++++++++++-------- 3 files changed, 39 insertions(+), 17 deletions(-)
include/net/page_pool/memory_provider.h +3 -1
@@ -5,6 +5,7 @@#include <net/netmem.h> #include <net/page_pool/types.h> +struct netdev_queue_config ;struct netdev_rx_queue ;struct netlink_ext_ack ;struct sk_buff ;
@@ -24,7 +25,8 @@ void net_mp_niov_set_page_pool(struct page_pool *pool, struct net_iov *niov);void net_mp_niov_clear_page_pool (struct net_iov *niov);int net_mp_open_rxq (struct net_device *dev,unsigned ifq_idx, -struct pp_memory_provider_params *p); +struct pp_memory_provider_params *p, +struct netdev_queue_config *qcfg);int __net_mp_open_rxq (struct net_device *dev,unsigned int rxq_idx,const struct pp_memory_provider_params *p,struct netlink_ext_ack *extack);
io_uring/zcrx.c +1 -1
@@ -595,7 +595,7 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, mp_param.mp_ops = &io_uring_pp_zc_ops; mp_param.mp_priv = ifq; - ret =net_mp_open_rxq (ifq->netdev, reg.if_rxq, &mp_param); + ret =net_mp_open_rxq (ifq->netdev, reg.if_rxq, &mp_param,NULL );if (ret) goto err; ifq->if_rxq = reg.if_rxq;
net/core/netdev_rx_queue.c +35 -15
114 lines not on this page · raw message
Signature · 1 line
2.49.0
Jakub Kicinski
maintainerRe: [RFC v1 21/22] net: parametrise mp open with a queue config
Quoted 5 lines · On Mon, 28 Jul 2025 12:04:25 +0100 Pavel Begunkov wrote:
This patch allows memory providers to pass a queue config when opening a queue. It'll be used in the next patch to pass a custom rx buffer length from zcrx. As there are many users of
netdev_rx_queue_restart(), it's allowed to pass a NULL qcfg, in which case the function will use the default configuration.
This is not exactly what I anticipated, TBH, I was thinking of extending the config stuff with another layer.. Drivers will restart their queues for most random reasons, so we need to be able to reconstitute this config easily and serve it up via netdev_queue_config(). This was, IIUC, also Mina's first concern.
My thinking was that the config would be constructed like this:
qcfg = init_to_defaults()
drv_def = get_driver_defaults()
for each setting:
if drv_def.X.set:
qcfg.X = drv_def.X.value
if dev.config.X.set:
qcfg.X = dev.config.X.value
if dev.config.qcfg[qid].X.set:
qcfg.X = dev.config.qcfg[qid].X.value
if dev.config.mp[qid].X.set: << this was not in my
qcfg.X = dev.config.mp[qid].X.value << RFC series
Since we don't allow MP to be replaced atomically today, we don't actually have to place the mp overrides in the config struct and involve the whole netdev_reconfig_start() _swap() _free() machinery. We can just stash the config in the queue state, and "logically" do what I described above.
Pavel Begunkov
maintainerRe: [RFC v1 21/22] net: parametrise mp open with a queue config
Quoted 11 lines · Jakub Kicinski
Quoted 5 lines · On Mon, 28 Jul 2025 12:04:25 +0100 Pavel Begunkov wrote:
This patch allows memory providers to pass a queue config when opening a queue. It'll be used in the next patch to pass a custom rx buffer length from zcrx. As there are many users of
netdev_rx_queue_restart(), it's allowed to pass a NULL qcfg, in which case the function will use the default configuration.This is not exactly what I anticipated, TBH, I was thinking of extending the config stuff with another layer.. Drivers will restart their queues for most random reasons, so we need to be able to reconstitute this config easily and serve it up via
Yeah, also noticed the gap that while replying to Stan.
Quoted 16 lines
netdev_queue_config(). This was, IIUC, also Mina's first concern.My thinking was that the config would be constructed like this:
qcfg =
init_to_defaults()
drv_def=get_driver_defaults()
for each setting:
if drv_def.X.set:
qcfg.X = drv_def.X.value
if dev.config.X.set:
qcfg.X = dev.config.X.value
if dev.config.qcfg[qid].X.set:
qcfg.X = dev.config.qcfg[qid].X.value
if dev.config.mp[qid].X.set: << this was not in my
qcfg.X = dev.config.mp[qid].X.value << RFC series
Quoted 5 lines
Since we don't allow MP to be replaced atomically today, we don't actually have to place the mp overrides in the config struct and involve the whole
netdev_reconfig_start()_swap()_free()machinery. We can just stash the config in the queue state, and "logically" do what I described above.
I was thinking stashing it in struct pp_memory_provider_params and applying in netdev_rx_queue_restart(). Let me try to move it into __netdev_queue_config. Any preference between keeping just the size vs a qcfg pointer in pp_memory_provider_params?
struct struct pp_memory_provider_params {
const struct memory_provider_ops *mp_ops;
u32 rx_buf_len;
};vs
struct struct pp_memory_provider_params {
const struct memory_provider_ops *mp_ops;
// providers will need to allocate and keep the qcfg
// until it's completely detached from the queues.
struct netdev_queue_config *qcfg;
};The former one would be simpler for now.
Signature · 1 line
Pavel Begunkov
Jakub Kicinski
maintainerRe: [RFC v1 21/22] net: parametrise mp open with a queue config
Quoted 18 lines · On Mon, 4 Aug 2025 13:50:08 +0100 Pavel Begunkov wrote:
Quoted 5 lines
Since we don't allow MP to be replaced atomically today, we don't actually have to place the mp overrides in the config struct and involve the whole
netdev_reconfig_start()_swap()_free()machinery. We can just stash the config in the queue state, and "logically" do what I described above.I was thinking stashing it in struct
pp_memory_provider_paramsand applying innetdev_rx_queue_restart(). Let me try to move it into__netdev_queue_config. Any preference between keeping just the size vs a qcfg pointer inpp_memory_provider_params?struct struct pp_memory_provider_params { const struct memory_provider_ops *mp_ops; u32 rx_buf_len; };vs
Quoted 8 lines
struct struct pp_memory_provider_params { const struct memory_provider_ops *mp_ops; // providers will need to allocate and keep the qcfg // until it's completely detached from the queues. struct netdev_queue_config *qcfg; };The former one would be simpler for now.
+1, I'd stick to the former. We can adjust later if need be.
Jakub Kicinski
maintainerRe: [RFC v1 21/22] net: parametrise mp open with a queue config
In reply to Pavel BegunkovQuoted 2 lines · On Mon, 4 Aug 2025 13:50:08 +0100 Pavel Begunkov wrote:
I was thinking stashing it in struct
pp_memory_provider_paramsand applying innetdev_rx_queue_restart().
Tho, netdev_rx_queue_restart() may not be a great place, it's called from multiple points and more will come. net_mp_open_rxq() / net_mp_close_rxq() and friends are probably a better place to apply and clear MP related overrides.
Re: [RFC v1 21/22] net: parametrise mp open with a queue config
In reply to Pavel BegunkovQuoted 43 lines · Pavel Begunkov
Quoted 11 lines · Jakub Kicinski
Quoted 5 lines · On Mon, 28 Jul 2025 12:04:25 +0100 Pavel Begunkov wrote:
This patch allows memory providers to pass a queue config when opening a queue. It'll be used in the next patch to pass a custom rx buffer length from zcrx. As there are many users of
netdev_rx_queue_restart(), it's allowed to pass a NULL qcfg, in which case the function will use the default configuration.This is not exactly what I anticipated, TBH, I was thinking of extending the config stuff with another layer.. Drivers will restart their queues for most random reasons, so we need to be able to reconstitute this config easily and serve it up via
Yeah, also noticed the gap that while replying to Stan.
Quoted 16 lines
netdev_queue_config(). This was, IIUC, also Mina's first concern.My thinking was that the config would be constructed like this:
qcfg =
init_to_defaults()
drv_def=get_driver_defaults()
for each setting:
if drv_def.X.set:
qcfg.X = drv_def.X.value
if dev.config.X.set:
qcfg.X = dev.config.X.value
if dev.config.qcfg[qid].X.set:
qcfg.X = dev.config.qcfg[qid].X.value
if dev.config.mp[qid].X.set: << this was not in my
qcfg.X = dev.config.mp[qid].X.value << RFC seriesQuoted 5 lines
Since we don't allow MP to be replaced atomically today, we don't actually have to place the mp overrides in the config struct and involve the whole
netdev_reconfig_start()_swap()_free()machinery. We can just stash the config in the queue state, and "logically" do what I described above.I was thinking stashing it in struct
pp_memory_provider_paramsand applying innetdev_rx_queue_restart(). Let me try to move it into__netdev_queue_config. Any preference between keeping just the size vs a qcfg pointer inpp_memory_provider_params?
Quoted 5 lines
struct struct pp_memory_provider_params {
const struct memory_provider_ops *mp_ops;
u32 rx_buf_len;
};Is this suggesting one more place where we put rx_buf_len, so in addition to netdev_config?
Honestly I'm in favor of de-duplicating the info as much as possible, to reduce the headache of keeping all the copies in sync. pp_memory_provider_params is part of netdev_rx_queue. How about we add either all of netdev_config or just rx_buf_len there? And set the precedent that queue configs should be in netdev_rx_queue and all pieces that need it should grab it from there? Unless the driver needs a copy of the param I guess.
iouring zcrx and devmem can configure netdev_rx_queue->rx_buf_len in addition to netdev_rx_queue->mp_params in this scenario.
Signature · 2 lines
Thanks, Mina
Jakub Kicinski
maintainerRe: [RFC v1 21/22] net: parametrise mp open with a queue config
Quoted 2 lines · On Wed, 6 Aug 2025 09:48:56 -0700 Mina Almasry wrote:
iouring zcrx and devmem can configure
netdev_rx_queue->rx_buf_lenin addition tonetdev_rx_queue->mp_paramsin this scenario.
Did you not read my message or are you disagreeing that the setting should be separate and form a hierarchy?
Re: [RFC v1 21/22] net: parametrise mp open with a queue config
Quoted 7 lines · Jakub Kicinski
Quoted 2 lines · On Wed, 6 Aug 2025 09:48:56 -0700 Mina Almasry wrote:
iouring zcrx and devmem can configure
netdev_rx_queue->rx_buf_lenin addition tonetdev_rx_queue->mp_paramsin this scenario.Did you not read my message or are you disagreeing that the setting should be separate and form a hierarchy?
Sorry, I was disagreeing. The flow above seems complicated. I'm probably missing something that requires this complication. I was suggesting an approach I find more straightforward. Something like:
```
nedev_config = get_driver_defaults()
qcfg = get_driver_defaults()
for each setting:
if qcfg[i].X is set:
use qcfg[i].X
else
use netdev_config.X
```APIs that set netdev-global attributes could set netdev_config.X. APIs that set per-queue attributes would set qcfg[i].X (after validating that the driver supports setting this param on a queue granularity).
With this flow we don't need to duplicate each attribute like rx-buf-len in 3 different places and have a delicate hierarchy of serving the config. And we treat mp like any other 'X'. It's just a setting that exists per-queue but not per netdev.
Although I don't feel strongly here. If you feel the duplication is warranted please do go ahead1 :-D
Signature · 2 lines
Thanks, Mina
Jakub Kicinski
maintainerRe: [RFC v1 21/22] net: parametrise mp open with a queue config
Quoted 8 lines · On Wed, 6 Aug 2025 11:30:38 -0700 Mina Almasry wrote:
Sorry, I was disagreeing. The flow above seems complicated. I'm probably missing something that requires this complication. I was suggesting an approach I find more straightforward.
Something like:
nedev_config=get_driver_defaults()
qcfg =get_driver_defaults()
Quoted 5 lines
for each setting:
if qcfg[i].X is set:
use qcfg[i].X
else
use netdev_config.X
IMO the rules on when to override/update and reset qcfg[i].X will get much more complicated than the extra else if in this logic.
Plus I suspect at some point we may want to add another layer here for e.g. a group of queues delegated to the same container interface (netkit, veth, ipvlan etc.) So I want to establish a clear model rather than "optimize" for the number of u32 variables.
Most code (drivers) should never be exposed to any of this, they consume a flattened qcfg for a reason.
Pavel Begunkov
maintainerRFC 22/22 io_uring/zcrx: implement large rx buffer support
In reply to Pavel BegunkovThere are network cards that support receive buffers larger than 4K, and that can be vastly beneficial for performance, and benchmarks for this patch showed up to 30% CPU util improvement for 32K vs 4K buffers.
Allows zcrx users to specify the size in struct io_uring_zcrx_ifq_reg::rx_buf_len. If set to zero, zcrx will use a default value. zcrx will check and fail if the memory backing the area can't be split into physically contiguous chunks of the required size. It's more restrictive as it only needs dma addresses to be contig, but that's beyond this series.
--- include/uapi/linux/io_uring.h | 2 +- io_uring/zcrx.c | 39 +++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 7 deletions(-)
include/uapi/linux/io_uring.h +1 -1
@@ -1041,7 +1041,7 @@ struct io_uring_zcrx_ifq_reg {struct io_uring_zcrx_offsets offsets;__u32 zcrx_id; -__u32 __resv2; +__u32 rx_buf_len;__u64 __resv[3 ]; };
io_uring/zcrx.c +33 -6
@@ -13,6 +13,7 @@#include <net/page_pool/memory_provider.h> #include <net/netlink.h> #include <net/netdev_rx_queue.h> +#include <net/netdev_queues.h> #include <net/tcp.h> #include <net/rps.h>
@@ -53,6 +54,18 @@ static inline struct page *io_zcrx_iov_page(const struct net_iov *niov)return area->mem.pages[net_iov_idx (niov) << niov_pages_shift]; } +static int io_area_max_shift (struct io_zcrx_mem *mem) +{ +struct sg_table *sgt = mem->sgt; +struct scatterlist *sg; +unsigned order =-1U ; +unsigned i; + +for_each_sgtable_dma_sg (sgt, sg, i) + order =min (order,__ffs (sg->length)); +return order; +} +static int io_populate_area_dma (struct io_zcrx_ifq *ifq,struct io_zcrx_area *area) {
@@ -384,8 +397,10 @@ static int io_zcrx_append_area(struct io_zcrx_ifq *ifq, }static int io_zcrx_create_area (struct io_zcrx_ifq *ifq, -struct io_uring_zcrx_area_reg *area_reg) +struct io_uring_zcrx_area_reg *area_reg, +struct io_uring_zcrx_ifq_reg *reg) { +int buf_size_shift =PAGE_SHIFT ;struct io_zcrx_area *area;unsigned nr_iovs;int i, ret;
@@ -400,7 +415,16 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq,if (ret) goto err; - ifq->niov_shift =PAGE_SHIFT ; +if (reg->rx_buf_len) { +if (!is_power_of_2 (reg->rx_buf_len) || + reg->rx_buf_len <PAGE_SIZE ) +return -EINVAL ; + buf_size_shift =ilog2 (reg->rx_buf_len); + } +if (buf_size_shift >io_area_max_shift (&area->mem)) +return -EINVAL ; + + ifq->niov_shift = buf_size_shift; nr_iovs = area->mem.size >> ifq->niov_shift; area->nia.num_niovs = nr_iovs;
@@ -522,6 +546,7 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, struct io_uring_zcrx_ifq_reg __user *arg) {struct pp_memory_provider_params mp_param = {}; +struct netdev_queue_config qcfg = {};struct io_uring_zcrx_area_reg area;struct io_uring_zcrx_ifq_reg reg;struct io_uring_region_desc rd;
@@ -544,8 +569,7 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx,return -EFAULT ;if (copy_from_user (&rd,u64_to_user_ptr (reg.region_ptr),sizeof (rd)))return -EFAULT ; -if (!mem_is_zero (®.__resv,sizeof (reg.__resv)) || - reg.__resv2 || reg.zcrx_id) +if (!mem_is_zero (®.__resv,sizeof (reg.__resv)) || reg.zcrx_id)return -EINVAL ;if (reg.if_rxq ==-1 || !reg.rq_entries || reg.flags)return -EINVAL ;
@@ -589,13 +613,14 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, }get_device (ifq->dev); - ret =io_zcrx_create_area (ifq, &area); + ret =io_zcrx_create_area (ifq, &area, ®);if (ret) goto err; mp_param.mp_ops = &io_uring_pp_zc_ops; mp_param.mp_priv = ifq; - ret =net_mp_open_rxq (ifq->netdev, reg.if_rxq, &mp_param,NULL ); + qcfg.rx_buf_len =1U << ifq->niov_shift; + ret =net_mp_open_rxq (ifq->netdev, reg.if_rxq, &mp_param, &qcfg);if (ret) goto err; ifq->if_rxq = reg.if_rxq;
@@ -612,6 +637,8 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, goto err; } + reg.rx_buf_len =1U << ifq->niov_shift; +if (copy_to_user (arg, ®,sizeof (reg)) ||copy_to_user (u64_to_user_ptr (reg.region_ptr), &rd,sizeof (rd)) ||copy_to_user (u64_to_user_ptr (reg.area_ptr), &area,sizeof (area))) {
Signature · 1 line
2.49.0
Quoted 70 lines · Pavel Begunkov
This series implements large rx buffer support for
io_uring/zcrxon top of Jakub's queue configuration changes, but it can also be used by other memory providers. Large rx buffers can be drastically beneficial with high-end hw-gro enabled cards that can coalesce traffic into larger pages, reducing the number of frags traversing the network stack and resuling in larger contiguous chunks of data for the userspace. Benchamrks showed up to ~30% improvement in CPU util.For example, for 200Gbit broadcom NIC, 4K vs 32K buffers, and napi and userspace pinned to the same CPU:
packets=23987040 (MB=2745098), rps=199559 (MB/s=22837)
CPU %usr %nice %sys %iowait %irq %soft %idle 0 1.53 0.00 27.78 2.72 1.31 66.45 0.22 packets=24078368 (MB=2755550), rps=200319 (MB/s=22924) CPU %usr %nice %sys %iowait %irq %soft %idle 0 0.69 0.00 8.26 31.65 1.83 57.00 0.57And for napi and userspace on different CPUs:
packets=10725082 (MB=1227388), rps=198285 (MB/s=22692)
CPU %usr %nice %sys %iowait %irq %soft %idle 0 0.10 0.00 0.50 0.00 0.50 74.50 24.40 1 4.51 0.00 44.33 47.22 2.08 1.85 0.00 packets=14026235 (MB=1605175), rps=198388 (MB/s=22703) CPU %usr %nice %sys %iowait %irq %soft %idle 0 0.10 0.00 0.70 0.00 1.00 43.78 54.42 1 1.09 0.00 31.95 62.91 1.42 2.63 0.00Patch 19 allows to pass queue config from a memory provider. The zcrx changes are contained in a single patch as I already queued most of work making it size agnostic into my zcrx branch. The uAPI is simple and imperative, it'll use the exact value (if) specified by the user. In the future we might extend it to "choose the best size in a given range".
The rest (first 20) patches are from Jakub's series implementing per queue configuration. Quoting Jakub:
"... The direct motivation for the series is that zero-copy Rx queues would like to use larger Rx buffers. Most modern high-speed NICs support HW-GRO, and can coalesce payloads into pages much larger than than the MTU. Enabling larger buffers globally is a bit precarious as it exposes us to potentially very inefficient memory use. Also allocating large buffers may not be easy or cheap under load. Zero-copy queues service only select traffic and have pre-allocated memory so the concerns don't apply as much.
The per-queue config has to address 3 problems:
- user API
- driver API
- memory provider APIFor user API the main question is whether we expose the config via ethtool or netdev nl. I picked the latter - via queue GET/SET, rather than extending the ethtool
RINGS_GETAPI. I worry slightly that queue GET/SET will turn in a monster like SETLINK. OTOH the only per-queue settings we have in ethtool which are not going viaRINGS_SETis IRQ coalescing.My goal for the driver API was to avoid complexity in the drivers. The queue management API has gained two ops, responsible for preparing configuration for a given queue, and validating whether the config is supported. The validating is used both for NIC-wide and per-queue changes. Queue alloc/start ops have a new "config" argument which contains the current config for a given queue (we use queue restart to apply per-queue settings). Outside of queue reset paths drivers can call
netdev_queue_config()which returns the config for an arbitrary queue. Long story short I anticipate it to be used duringndo_open.
Quoted 3 lines
In the core I extended struct
netdev_configwith per queue settings. All in all this isn't too far from what was there in my "queue API prototype" a few years ago ..."
Supporting big buffers is the right direction, but I have the same
feedback: it would be nice to fit a cohesive story for the devmem as well. We should also aim for another use-case where we allocate page pool chunks from the huge page(s), this should push the perf even more.
We need some way to express these things from the UAPI point of view. Flipping the rx-buf-len value seems too fragile - there needs to be something to request 32K chunks only for devmem case, not for the (default) CPU memory. And the queues should go back to default 4K pages when the dmabuf is detached from the queue.
Pavel Begunkov
maintainerQuoted 71 lines · Stanislav Fomichev
Quoted 70 lines · Pavel Begunkov
This series implements large rx buffer support for
io_uring/zcrxon top of Jakub's queue configuration changes, but it can also be used by other memory providers. Large rx buffers can be drastically beneficial with high-end hw-gro enabled cards that can coalesce traffic into larger pages, reducing the number of frags traversing the network stack and resuling in larger contiguous chunks of data for the userspace. Benchamrks showed up to ~30% improvement in CPU util.For example, for 200Gbit broadcom NIC, 4K vs 32K buffers, and napi and userspace pinned to the same CPU:
packets=23987040 (MB=2745098), rps=199559 (MB/s=22837)
CPU %usr %nice %sys %iowait %irq %soft %idle
0 1.53 0.00 27.78 2.72 1.31 66.45 0.22
packets=24078368 (MB=2755550), rps=200319 (MB/s=22924)
CPU %usr %nice %sys %iowait %irq %soft %idle
0 0.69 0.00 8.26 31.65 1.83 57.00 0.57And for napi and userspace on different CPUs:
packets=10725082 (MB=1227388), rps=198285 (MB/s=22692)
CPU %usr %nice %sys %iowait %irq %soft %idle0 0.10 0.00 0.50 0.00 0.50 74.50 24.40 1 4.51 0.00 44.33 47.22 2.08 1.85 0.00 packets=14026235 (MB=1605175), rps=198388 (MB/s=22703) CPU %usr %nice %sys %iowait %irq %soft %idle 0 0.10 0.00 0.70 0.00 1.00 43.78 54.42 1 1.09 0.00 31.95 62.91 1.42 2.63 0.00Patch 19 allows to pass queue config from a memory provider. The zcrx changes are contained in a single patch as I already queued most of work making it size agnostic into my zcrx branch. The uAPI is simple and imperative, it'll use the exact value (if) specified by the user. In the future we might extend it to "choose the best size in a given range".
The rest (first 20) patches are from Jakub's series implementing per queue configuration. Quoting Jakub:
"... The direct motivation for the series is that zero-copy Rx queues would like to use larger Rx buffers. Most modern high-speed NICs support HW-GRO, and can coalesce payloads into pages much larger than than the MTU. Enabling larger buffers globally is a bit precarious as it exposes us to potentially very inefficient memory use. Also allocating large buffers may not be easy or cheap under load. Zero-copy queues service only select traffic and have pre-allocated memory so the concerns don't apply as much.
The per-queue config has to address 3 problems:
- user API
- driver API
- memory provider APIFor user API the main question is whether we expose the config via ethtool or netdev nl. I picked the latter - via queue GET/SET, rather than extending the ethtool
RINGS_GETAPI. I worry slightly that queue GET/SET will turn in a monster like SETLINK. OTOH the only per-queue settings we have in ethtool which are not going viaRINGS_SETis IRQ coalescing.My goal for the driver API was to avoid complexity in the drivers. The queue management API has gained two ops, responsible for preparing configuration for a given queue, and validating whether the config is supported. The validating is used both for NIC-wide and per-queue changes. Queue alloc/start ops have a new "config" argument which contains the current config for a given queue (we use queue restart to apply per-queue settings). Outside of queue reset paths drivers can call
netdev_queue_config()which returns the config for an arbitrary queue. Long story short I anticipate it to be used duringndo_open.
Quoted 6 lines
Quoted 3 lines
In the core I extended struct
netdev_configwith per queue settings. All in all this isn't too far from what was there in my "queue API prototype" a few years ago ..."Supporting big buffers is the right direction, but I have the same feedback:
Let me actually check the feedback for the queue config RFC...
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Quoted 2 lines
We should also aim for another use-case where we allocate page pool chunks from the huge page(s),
Separate huge page pool is a bit beyond the scope of this series.
this should push the perf even more.
And not sure about "even more" is from, you can already register a huge page with zcrx, and this will allow to chunk them to 32K or so for hardware. Is it in terms of applicability or you have some perf optimisation ideas?
Quoted 1 line
We need some way to express these things from the UAPI point of view.
Can you elaborate?
Quoted 4 lines
Flipping the rx-buf-len value seems too fragile - there needs to be something to request 32K chunks only for devmem case, not for the (default) CPU memory. And the queues should go back to default 4K pages when the dmabuf is detached from the queue.
That's what the per-queue config is solving. It's not default, zcrx configures it only for the specific queue it allocated, and the value is cleared on restart in netdev_rx_queue_restart(), if not even too aggressively. Maybe I should just stash it into mp_params to make sure it's not cleared if a provider is still attached on a spurious restart.
Signature · 1 line
Pavel Begunkov
Quoted 79 lines · Pavel Begunkov
Quoted 71 lines · Stanislav Fomichev
Quoted 70 lines · Pavel Begunkov
This series implements large rx buffer support for
io_uring/zcrxon top of Jakub's queue configuration changes, but it can also be used by other memory providers. Large rx buffers can be drastically beneficial with high-end hw-gro enabled cards that can coalesce traffic into larger pages, reducing the number of frags traversing the network stack and resuling in larger contiguous chunks of data for the userspace. Benchamrks showed up to ~30% improvement in CPU util.For example, for 200Gbit broadcom NIC, 4K vs 32K buffers, and napi and userspace pinned to the same CPU:
packets=23987040 (MB=2745098), rps=199559 (MB/s=22837)
CPU %usr %nice %sys %iowait %irq %soft %idle
0 1.53 0.00 27.78 2.72 1.31 66.45 0.22
packets=24078368 (MB=2755550), rps=200319 (MB/s=22924)
CPU %usr %nice %sys %iowait %irq %soft %idle
0 0.69 0.00 8.26 31.65 1.83 57.00 0.57And for napi and userspace on different CPUs:
packets=10725082 (MB=1227388), rps=198285 (MB/s=22692)
CPU %usr %nice %sys %iowait %irq %soft %idle0 0.10 0.00 0.50 0.00 0.50 74.50 24.40 1 4.51 0.00 44.33 47.22 2.08 1.85 0.00 packets=14026235 (MB=1605175), rps=198388 (MB/s=22703) CPU %usr %nice %sys %iowait %irq %soft %idle 0 0.10 0.00 0.70 0.00 1.00 43.78 54.42 1 1.09 0.00 31.95 62.91 1.42 2.63 0.00Patch 19 allows to pass queue config from a memory provider. The zcrx changes are contained in a single patch as I already queued most of work making it size agnostic into my zcrx branch. The uAPI is simple and imperative, it'll use the exact value (if) specified by the user. In the future we might extend it to "choose the best size in a given range".
The rest (first 20) patches are from Jakub's series implementing per queue configuration. Quoting Jakub:
"... The direct motivation for the series is that zero-copy Rx queues would like to use larger Rx buffers. Most modern high-speed NICs support HW-GRO, and can coalesce payloads into pages much larger than than the MTU. Enabling larger buffers globally is a bit precarious as it exposes us to potentially very inefficient memory use. Also allocating large buffers may not be easy or cheap under load. Zero-copy queues service only select traffic and have pre-allocated memory so the concerns don't apply as much.
The per-queue config has to address 3 problems:
- user API
- driver API
- memory provider APIFor user API the main question is whether we expose the config via ethtool or netdev nl. I picked the latter - via queue GET/SET, rather than extending the ethtool
RINGS_GETAPI. I worry slightly that queue GET/SET will turn in a monster like SETLINK. OTOH the only per-queue settings we have in ethtool which are not going viaRINGS_SETis IRQ coalescing.My goal for the driver API was to avoid complexity in the drivers. The queue management API has gained two ops, responsible for preparing configuration for a given queue, and validating whether the config is supported. The validating is used both for NIC-wide and per-queue changes. Queue alloc/start ops have a new "config" argument which contains the current config for a given queue (we use queue restart to apply per-queue settings). Outside of queue reset paths drivers can call
netdev_queue_config()which returns the config for an arbitrary queue. Long story short I anticipate it to be used duringndo_open.Quoted 6 lines
Quoted 3 lines
In the core I extended struct
netdev_configwith per queue settings. All in all this isn't too far from what was there in my "queue API prototype" a few years ago ..."Supporting big buffers is the right direction, but I have the same feedback:
Quoted 7 lines
Let me actually check the feedback for the queue config RFC...
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you care is exposing it via io_uring, maybe don't expose it from netlink for now? Although I'm not sure I understand why you're also passing this per-queue value via io_uring. Can you not inherit it from the queue config?
Quoted 11 lines
Quoted 2 lines
We should also aim for another use-case where we allocate page pool chunks from the huge page(s),
Separate huge page pool is a bit beyond the scope of this series.
this should push the perf even more.
And not sure about "even more" is from, you can already register a huge page with zcrx, and this will allow to chunk them to 32K or so for hardware. Is it in terms of applicability or you have some perf optimisation ideas?
What I'm looking for is a generic system-wide solution where we can set up the host to use huge pages to back all (even non-zc) networking queues. Not necessary needed, but might be an option to try.
Quoted 9 lines
Quoted 1 line
We need some way to express these things from the UAPI point of view.
Can you elaborate?
Quoted 4 lines
Flipping the rx-buf-len value seems too fragile - there needs to be something to request 32K chunks only for devmem case, not for the (default) CPU memory. And the queues should go back to default 4K pages when the dmabuf is detached from the queue.
Quoted 6 lines
That's what the per-queue config is solving. It's not default, zcrx configures it only for the specific queue it allocated, and the value is cleared on restart in
netdev_rx_queue_restart(), if not even too aggressively. Maybe I should just stash it intomp_paramsto make sure it's not cleared if a provider is still attached on a spurious restart.
If we assume that at some point niov can be backed up by chunks larger than PAGE_SIZE, the assumed workflow for devemem is:
1. change rx-buf-len to 32K
- this is needed only for devmem, but not for CPU RAM, but we'll have to refill the queues from the main memory anyway
- there is also a question on whether we need to do anything about MAX_PAGE_ORDER/PAGE_ALLOC_COSTLY_ORDER - do we just let the driver allocations fail?
2. attach dmabuf to the queue to refill from dmabuf sgt, essentially wasting all the effort on (1)
3. on detach, something needs to also not forget to reset the rx-buf-len back to PAGE_SIZE
I was hoping that maybe we can bind rx-buf-len to dmabuf for devmem, that should avoid all that useless refill from the main memory with large chunks. But I'm not sure it's the right way to go either.
Pavel Begunkov
maintainerQuoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink for
Sure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires io_uring to parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".
Quoted 7 lines
Quoted 7 lines
Quoted 2 lines
We should also aim for another use-case where we allocate page pool chunks from the huge page(s),
Separate huge page pool is a bit beyond the scope of this series.
this should push the perf even more.
Quoted 8 lines
Quoted 4 lines
And not sure about "even more" is from, you can already register a huge page with zcrx, and this will allow to chunk them to 32K or so for hardware. Is it in terms of applicability or you have some perf optimisation ideas?
What I'm looking for is a generic system-wide solution where we can set up the host to use huge pages to back all (even non-zc) networking queues. Not necessary needed, but might be an option to try.
Probably like what Jakub was once suggesting with the initial memory provider patch, got it.
Quoted 16 lines
Quoted 9 lines
Quoted 1 line
We need some way to express these things from the UAPI point of view.
Can you elaborate?
Quoted 4 lines
Flipping the rx-buf-len value seems too fragile - there needs to be something to request 32K chunks only for devmem case, not for the (default) CPU memory. And the queues should go back to default 4K pages when the dmabuf is detached from the queue.
Quoted 6 lines
That's what the per-queue config is solving. It's not default, zcrx configures it only for the specific queue it allocated, and the value is cleared on restart in
netdev_rx_queue_restart(), if not even too aggressively. Maybe I should just stash it intomp_paramsto make sure it's not cleared if a provider is still attached on a spurious restart.
Quoted 5 lines
If we assume that at some point niov can be backed up by chunks larger than
PAGE_SIZE, the assumed workflow for devemem is:
1. change rx-buf-len to 32K
- this is needed only for devmem, but not for CPU RAM, but we'll have to refill the queues from the main memory anyway
Urgh, that's another reason why I prefer to just pass it through zcrx and not netlink. So maybe you can just pass the len to devmem on creation, and internally it sets up its queues with it.
Quoted 7 lines
- there is also a question on whether we need to do anything about MAX_PAGE_ORDER/PAGE_ALLOC_COSTLY_ORDER - do we just let the driver allocations fail?
2. attach dmabuf to the queue to refill from dmabuf sgt, essentially wasting all the effort on (1)
3. on detach, something needs to also not forget to reset the rx-buf-len back toPAGE_SIZE
Sure
Quoted 3 lines
I was hoping that maybe we can bind rx-buf-len to dmabuf for devmem, that should avoid all that useless refill from the main memory with large chunks. But I'm not sure it's the right way to go either.
Signature · 1 line
Pavel Begunkov
Quoted 28 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?
Quoted 5 lines
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".
Don't you already need a bunch of netlink to setup rss and flow steering? And if we end up adding queue api, you'll have to call that one over netlink also.
Quoted 41 lines
Quoted 7 lines
Quoted 7 lines
Quoted 2 lines
We should also aim for another use-case where we allocate page pool chunks from the huge page(s),
Separate huge page pool is a bit beyond the scope of this series.
this should push the perf even more.
Quoted 8 lines
Quoted 4 lines
And not sure about "even more" is from, you can already register a huge page with zcrx, and this will allow to chunk them to 32K or so for hardware. Is it in terms of applicability or you have some perf optimisation ideas?
What I'm looking for is a generic system-wide solution where we can set up the host to use huge pages to back all (even non-zc) networking queues. Not necessary needed, but might be an option to try.
Probably like what Jakub was once suggesting with the initial memory provider patch, got it.
Quoted 16 lines
Quoted 9 lines
Quoted 1 line
We need some way to express these things from the UAPI point of view.
Can you elaborate?
Quoted 4 lines
Flipping the rx-buf-len value seems too fragile - there needs to be something to request 32K chunks only for devmem case, not for the (default) CPU memory. And the queues should go back to default 4K pages when the dmabuf is detached from the queue.
Quoted 6 lines
That's what the per-queue config is solving. It's not default, zcrx configures it only for the specific queue it allocated, and the value is cleared on restart in
netdev_rx_queue_restart(), if not even too aggressively. Maybe I should just stash it intomp_paramsto make sure it's not cleared if a provider is still attached on a spurious restart.Quoted 5 lines
If we assume that at some point niov can be backed up by chunks larger than
PAGE_SIZE, the assumed workflow for devemem is:
1. change rx-buf-len to 32K
- this is needed only for devmem, but not for CPU RAM, but we'll have to refill the queues from the main memory anyway
Quoted 3 lines
Urgh, that's another reason why I prefer to just pass it through zcrx and not netlink. So maybe you can just pass the len to devmem on creation, and internally it sets up its queues with it.
But you still need to solve MAX_PAGE_ORDER/PAGE_ALLOC_COSTLY_ORDER I think? We don't want the drivers to do PAGE_ALLOC_COSTLY_ORDER costly allocation presumably?
Pavel Begunkov
maintainerQuoted 29 lines · Stanislav Fomichev
Quoted 28 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?
Quoted 7 lines
Quoted 5 lines
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".Don't you already need a bunch of netlink to setup rss and flow
Could be needed, but there are cases where configuration and virtual queue selection is done outside the program. I'll need to ask which option we currently use.
Quoted 2 lines
steering? And if we end up adding queue api, you'll have to call that one over netlink also.
There is already a queue api, even though it's cropped IIUC. What kind of extra setup you have in mind?
Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
If we assume that at some point niov can be backed up by chunks larger than
PAGE_SIZE, the assumed workflow for devemem is:
1. change rx-buf-len to 32K
- this is needed only for devmem, but not for CPU RAM, but we'll have to refill the queues from the main memory anyway
Quoted 7 lines
Quoted 3 lines
Urgh, that's another reason why I prefer to just pass it through zcrx and not netlink. So maybe you can just pass the len to devmem on creation, and internally it sets up its queues with it.
But you still need to solve MAX_PAGE_ORDER/PAGE_ALLOC_COSTLY_ORDER I think? We don't want the drivers to do
PAGE_ALLOC_COSTLY_ORDERcostly allocation presumably?
#define PAGE_ALLOC_COSTLY_ORDER 3
It's "costly" for the page allocator and not a custom specially cooked memory providers. Nobody should care as long as the length applies to the given provider only. MAX_PAGE_ORDER also seems to be a page allocator thing.
Signature · 1 line
Pavel Begunkov
Quoted 36 lines · Pavel Begunkov
Quoted 35 lines · Stanislav Fomichev
Quoted 33 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".
Quoted 5 lines
Quoted 1 line
Don't you already need a bunch of netlink to setup rss and flow
Could be needed, but there are cases where configuration and virtual queue selection is done outside the program. I'll need to ask which option we currently use.
If the setup is done outside, you can also setup rx-buf-len outside, no?
Quoted 5 lines
Quoted 2 lines
steering? And if we end up adding queue api, you'll have to call that one over netlink also.
There is already a queue api, even though it's cropped IIUC. What kind of extra setup you have in mind?
I'm talking about allocating the queues. Currently the zc/devmem setup is a bit complicated, we need to partition the queues and rss+flow steer into a subset of zerocopy ones. In the future we might add some apis to request a new dedicated queue for the specific flow(s). That should hopefully simplify the design (and make the cleanup of the queues more robust if the application dies).
Quoted 14 lines
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
If we assume that at some point niov can be backed up by chunks larger than
PAGE_SIZE, the assumed workflow for devemem is:
1. change rx-buf-len to 32K
- this is needed only for devmem, but not for CPU RAM, but we'll have to refill the queues from the main memory anywayQuoted 7 lines
Quoted 3 lines
Urgh, that's another reason why I prefer to just pass it through zcrx and not netlink. So maybe you can just pass the len to devmem on creation, and internally it sets up its queues with it.
But you still need to solve MAX_PAGE_ORDER/PAGE_ALLOC_COSTLY_ORDER I think? We don't want the drivers to do
PAGE_ALLOC_COSTLY_ORDERcostly allocation presumably?
Quoted 6 lines
#define
PAGE_ALLOC_COSTLY_ORDER3It's "costly" for the page allocator and not a custom specially cooked memory providers. Nobody should care as long as the length applies to the given provider only.
MAX_PAGE_ORDERalso seems to be a page allocator thing.
By custom memory providers you mean page pool? Thinking about it more, maybe it's fine as is as long as we have ndo_queue_cfg_validate that enforces sensible ranges..
Pavel Begunkov
maintainerQuoted 37 lines · Stanislav Fomichev
Quoted 36 lines · Pavel Begunkov
Quoted 35 lines · Stanislav Fomichev
Quoted 33 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".
Quoted 7 lines
Quoted 5 lines
Quoted 1 line
Don't you already need a bunch of netlink to setup rss and flow
Could be needed, but there are cases where configuration and virtual queue selection is done outside the program. I'll need to ask which option we currently use.
If the setup is done outside, you can also setup rx-buf-len outside, no?
You can't do it without assuming the memory layout, and that's the application's role to allocate buffers. Not to mention that often the app won't know about all specifics either and it'd be resolved on zcrx registration.
Quoted 6 lines
Quoted 5 lines
Quoted 2 lines
steering? And if we end up adding queue api, you'll have to call that one over netlink also.
There is already a queue api, even though it's cropped IIUC. What kind of extra setup you have in mind?
Quoted 6 lines
I'm talking about allocating the queues. Currently the zc/devmem setup is a bit complicated, we need to partition the queues and rss+flow steer into a subset of zerocopy ones. In the future we might add some apis to request a new dedicated queue for the specific flow(s). That should hopefully simplify the design (and make the cleanup of the queues more robust if the application dies).
I see, would be useful indeed, but let's not over complicate things until we have to, especially since there are reasons not to. For the configuration, I was arguing for a while that it'd be great to have an allocated queue wrapped into an fd, so that all containerisation / queue passing / security / etc. questions just solved in a generic and ubiquitous way.
Quoted 14 lines
Quoted 14 lines
Quoted 13 lines
Quoted 9 lines
Quoted 5 lines
If we assume that at some point niov can be backed up by chunks larger than
PAGE_SIZE, the assumed workflow for devemem is:
1. change rx-buf-len to 32K
- this is needed only for devmem, but not for CPU RAM, but we'll have to refill the queues from the main memory anywayUrgh, that's another reason why I prefer to just pass it through zcrx and not netlink. So maybe you can just pass the len to devmem on creation, and internally it sets up its queues with it.
But you still need to solve MAX_PAGE_ORDER/PAGE_ALLOC_COSTLY_ORDER I think? We don't want the drivers to do
PAGE_ALLOC_COSTLY_ORDERcostly allocation presumably?
Quoted 8 lines
Quoted 6 lines
#define
PAGE_ALLOC_COSTLY_ORDER3It's "costly" for the page allocator and not a custom specially cooked memory providers. Nobody should care as long as the length applies to the given provider only.
MAX_PAGE_ORDERalso seems to be a page allocator thing.By custom memory providers you mean page pool? Thinking about it more,
zcrx / devmem. I'm just saying that in situations where zcrx sets the size for its queues and that only affects zcrx allocations and not normal page pools, PAGE_ALLOC_COSTLY_ORDER is irrelevant.
I agreed on dropping the netlink queue size set, which leaves the global size set, but that's a separate topic.
Quoted 2 lines
maybe it's fine as is as long as we have
ndo_queue_cfg_validatethat enforces sensible ranges..
Signature · 1 line
Pavel Begunkov
Quoted 44 lines · Pavel Begunkov
Quoted 43 lines · Stanislav Fomichev
Quoted 41 lines · Pavel Begunkov
Quoted 29 lines · Stanislav Fomichev
Quoted 28 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?Quoted 7 lines
Quoted 5 lines
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".Don't you already need a bunch of netlink to setup rss and flow
Could be needed, but there are cases where configuration and virtual queue selection is done outside the program. I'll need to ask which option we currently use.
Quoted 6 lines
Quoted 1 line
If the setup is done outside, you can also setup rx-buf-len outside, no?
You can't do it without assuming the memory layout, and that's the application's role to allocate buffers. Not to mention that often the app won't know about all specifics either and it'd be resolved on zcrx registration.
I think, fundamentally, we need to distinguish:
1. chunk size of the memory pool (page pool order, niov size)
2. chunk size of the rx queue entries (this is what this series calls rx-buf-len), mostly influenced by MTU?
For devmem (and same for iou?), we want an option to derive (2) from (1):
page pools with larger chunks need to generate larger rx entries.
Quoted 53 lines · Stanislav Fomichev
Quoted 44 lines · Pavel Begunkov
Quoted 43 lines · Stanislav Fomichev
Quoted 41 lines · Pavel Begunkov
Quoted 29 lines · Stanislav Fomichev
Quoted 28 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?Quoted 7 lines
Quoted 5 lines
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".Don't you already need a bunch of netlink to setup rss and flow
Could be needed, but there are cases where configuration and virtual queue selection is done outside the program. I'll need to ask which option we currently use.
Quoted 6 lines
Quoted 1 line
If the setup is done outside, you can also setup rx-buf-len outside, no?
You can't do it without assuming the memory layout, and that's the application's role to allocate buffers. Not to mention that often the app won't know about all specifics either and it'd be resolved on zcrx registration.
Quoted 8 lines
I think, fundamentally, we need to distinguish:
1. chunk size of the memory pool (page pool order, niov size)
2. chunk size of the rx queue entries (this is what this series calls rx-buf-len), mostly influenced by MTU?For devmem (and same for iou?), we want an option to derive (2) from (1):
page pools with larger chunks need to generate larger rx entries.
To be honest I'm not following. #1 and #2 seem the same to me. rx-buf-len is just the size of each rx buffer posted to the NIC.
With pp_params.order = 0 (most common configuration today), rx-buf-len == 4K. Regardless of MTU. With pp_params.order=1, I'm guessing 8K then, again regardless of MTU.
I think if the user has not configured rx-buf-len, the driver is probably free to pick whatever it wants and that can be a derivative of the MTU.
When the rx-buf-len is configured by the user, I assume the driver puts aside all MTU-related heuristics (if it has them) and uses whatever the userspace specified.
Note that the memory provider may reject the request. For example iouring and pages providers can only do page-order allocations. Devmem can in theory do any byte-aligned allocation, since gen_pool doesn't have a restriction AFAIR.
Signature · 2 lines
Thanks, Mina
Pavel Begunkov
maintainerQuoted 63 lines · Mina Almasry
Quoted 53 lines · Stanislav Fomichev
Quoted 44 lines · Pavel Begunkov
Quoted 43 lines · Stanislav Fomichev
Quoted 41 lines · Pavel Begunkov
Quoted 29 lines · Stanislav Fomichev
Quoted 28 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?Quoted 7 lines
Quoted 5 lines
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".Don't you already need a bunch of netlink to setup rss and flow
Could be needed, but there are cases where configuration and virtual queue selection is done outside the program. I'll need to ask which option we currently use.
Quoted 6 lines
Quoted 1 line
If the setup is done outside, you can also setup rx-buf-len outside, no?
You can't do it without assuming the memory layout, and that's the application's role to allocate buffers. Not to mention that often the app won't know about all specifics either and it'd be resolved on zcrx registration.
Quoted 8 lines
I think, fundamentally, we need to distinguish:
1. chunk size of the memory pool (page pool order, niov size)
2. chunk size of the rx queue entries (this is what this series calls rx-buf-len), mostly influenced by MTU?For devmem (and same for iou?), we want an option to derive (2) from (1):
page pools with larger chunks need to generate larger rx entries.
Quoted 6 lines
To be honest I'm not following. #1 and #2 seem the same to me. rx-buf-len is just the size of each rx buffer posted to the NIC.
With pp_params.order = 0 (most common configuration today), rx-buf-len == 4K. Regardless of MTU. With pp_params.order=1, I'm guessing 8K then, again regardless of MTU.
There are drivers that fragment the buffer they get from a page pool and give smaller chunks to the hw. It's surely a good idea to be more explicit on what's what, but from the whole setup and uapi perspective I'm not too concerned.
The parameter the user passes to zcrx must controls 1. As for 2. I'd expect the driver to use the passed size directly or fail validation, but even if that's not the case, zcrx / devmem would just continue to work without any change in uapi, so we have the freedom to patch up the nuances later on if anything sticks out.
Quoted 13 lines
I think if the user has not configured rx-buf-len, the driver is probably free to pick whatever it wants and that can be a derivative of the MTU.
When the rx-buf-len is configured by the user, I assume the driver puts aside all MTU-related heuristics (if it has them) and uses whatever the userspace specified.
Note that the memory provider may reject the request. For example iouring and pages providers can only do page-order allocations. Devmem can in theory do any byte-aligned allocation, since
gen_pooldoesn't have a restriction AFAIR.
Signature · 1 line
Pavel Begunkov
Quoted 77 lines · Pavel Begunkov
Quoted 63 lines · Mina Almasry
Quoted 53 lines · Stanislav Fomichev
Quoted 44 lines · Pavel Begunkov
Quoted 43 lines · Stanislav Fomichev
Quoted 41 lines · Pavel Begunkov
Quoted 29 lines · Stanislav Fomichev
Quoted 28 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?Quoted 7 lines
Quoted 5 lines
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".Don't you already need a bunch of netlink to setup rss and flow
Could be needed, but there are cases where configuration and virtual queue selection is done outside the program. I'll need to ask which option we currently use.
Quoted 6 lines
Quoted 1 line
If the setup is done outside, you can also setup rx-buf-len outside, no?
You can't do it without assuming the memory layout, and that's the application's role to allocate buffers. Not to mention that often the app won't know about all specifics either and it'd be resolved on zcrx registration.
Quoted 8 lines
I think, fundamentally, we need to distinguish:
1. chunk size of the memory pool (page pool order, niov size)
2. chunk size of the rx queue entries (this is what this series calls rx-buf-len), mostly influenced by MTU?For devmem (and same for iou?), we want an option to derive (2) from (1):
page pools with larger chunks need to generate larger rx entries.Quoted 6 lines
To be honest I'm not following. #1 and #2 seem the same to me. rx-buf-len is just the size of each rx buffer posted to the NIC.
With pp_params.order = 0 (most common configuration today), rx-buf-len == 4K. Regardless of MTU. With pp_params.order=1, I'm guessing 8K then, again regardless of MTU.
There are drivers that fragment the buffer they get from a page pool and give smaller chunks to the hw. It's surely a good idea to be more explicit on what's what, but from the whole setup and uapi perspective I'm not too concerned.
Quoted 7 lines
The parameter the user passes to zcrx must controls 1. As for 2. I'd expect the driver to use the passed size directly or fail validation, but even if that's not the case, zcrx / devmem would just continue to work without any change in uapi, so we have the freedom to patch up the nuances later on if anything sticks out.
I indeed forgot about driver-fragmenting. That does complicate things quite a bit.
So AFAIU the intended behavior is that rx-buf-len refers to the memory size allocated by the driver (and thun memory provider), but not necessarily the one posted by the driver if it's fragmenting that piece of memory? If so, that sounds good to me. Although I wonder if that could cause some unexpected behavior... Someone may configure rx-buf-len to 8K on one driver and get actual 8K packets, but then configure rx-buf-len on another driver and get 4K packets because the driver fragmented each buffer into 2...
I guess in the future there may be a knob that controls how much fragmentation the driver does?
Signature · 2 lines
Thanks, Mina
Pavel Begunkov
maintainerQuoted 2 lines · Mina Almasry
Quoted 1 line · Pavel Begunkov
...>>>>>> If the setup is done outside, you can also setup rx-buf-len outside, no?
Quoted 35 lines
Quoted 27 lines
Quoted 15 lines
Quoted 6 lines
Quoted 5 lines
You can't do it without assuming the memory layout, and that's the application's role to allocate buffers. Not to mention that often the app won't know about all specifics either and it'd be resolved on zcrx registration.
Quoted 8 lines
I think, fundamentally, we need to distinguish:
1. chunk size of the memory pool (page pool order, niov size)
2. chunk size of the rx queue entries (this is what this series calls rx-buf-len), mostly influenced by MTU?For devmem (and same for iou?), we want an option to derive (2) from (1):
page pools with larger chunks need to generate larger rx entries.Quoted 6 lines
To be honest I'm not following. #1 and #2 seem the same to me. rx-buf-len is just the size of each rx buffer posted to the NIC.
With pp_params.order = 0 (most common configuration today), rx-buf-len == 4K. Regardless of MTU. With pp_params.order=1, I'm guessing 8K then, again regardless of MTU.
There are drivers that fragment the buffer they get from a page pool and give smaller chunks to the hw. It's surely a good idea to be more explicit on what's what, but from the whole setup and uapi perspective I'm not too concerned.
Quoted 7 lines
The parameter the user passes to zcrx must controls 1. As for 2. I'd expect the driver to use the passed size directly or fail validation, but even if that's not the case, zcrx / devmem would just continue to work without any change in uapi, so we have the freedom to patch up the nuances later on if anything sticks out.
Quoted 7 lines
I indeed forgot about driver-fragmenting. That does complicate things quite a bit.
So AFAIU the intended behavior is that rx-buf-len refers to the memory size allocated by the driver (and thun memory provider), but not necessarily the one posted by the driver if it's fragmenting that piece of memory? If so, that sounds good to me. Although I wonder if
Yep
Quoted 4 lines
that could cause some unexpected behavior... Someone may configure rx-buf-len to 8K on one driver and get actual 8K packets, but then configure rx-buf-len on another driver and get 4K packets because the driver fragmented each buffer into 2...
That already can happen, the user can hope to get whole full buffers but shouldn't assume that it will. hw gro can't be 100% reliable in this sense for all circumstances. And I don't think it's sane for driver implementations to do that. Fragmenting PAGE_SIZE because the NIC needs smaller chunks or for some other compatibility reasons? Sure, but then I don't see a reason for validating even larger buffers.
Quoted 2 lines
I guess in the future there may be a knob that controls how much fragmentation the driver does?
Probably, but hopefully it'll not be needed
Signature · 1 line
Pavel Begunkov
Pavel Begunkov
maintainerQuoted 1 line · Mina Almasry
On Wed, Jul 30, 2025 at 8:50 AM Stanislav Fomichev <[email protected]> wrote:
...>> For devmem (and same for iou?), we want an option to derive (2) from (1):
Quoted 13 lines
Quoted 1 line
page pools with larger chunks need to generate larger rx entries.
To be honest I'm not following. #1 and #2 seem the same to me. rx-buf-len is just the size of each rx buffer posted to the NIC.
With pp_params.order = 0 (most common configuration today), rx-buf-len == 4K. Regardless of MTU. With pp_params.order=1, I'm guessing 8K then, again regardless of MTU.
I think if the user has not configured rx-buf-len, the driver is probably free to pick whatever it wants and that can be a derivative of the MTU.
Quoted 8 lines
When the rx-buf-len is configured by the user, I assume the driver puts aside all MTU-related heuristics (if it has them) and uses whatever the userspace specified.
Note that the memory provider may reject the request. For example iouring and pages providers can only do page-order allocations. Devmem can in theory do any byte-aligned allocation, since
gen_pooldoesn't have a restriction AFAIR.
It's trivial to add sub-page and/or non-pow2 allocations to zcrx, but the size needs to be uniform and decided on at registration.
Signature · 1 line
Pavel Begunkov
Quoted 36 lines · Stanislav Fomichev
Quoted 28 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?Quoted 5 lines
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".
Quoted 4 lines
Don't you already need a bunch of netlink to setup rss and flow steering? And if we end up adding queue api, you'll have to call that one over netlink also.
I'm thinking one thing that could work is extending bind-rx with an optional rx-buf-len arg, which in the code translates into devmem using the new net_mp_open_rxq variant which not only restarts the queue but also sets the size. From there the implementation should be fairly straightforward in devmem. devmem currently rejects any pp for which pp.order != 0. It would need to start accepting that and forwarding the order to the gen_pool doing the allocations, etc.
Signature · 2 lines
Thanks, Mina
Quoted 42 lines · Mina Almasry
Quoted 36 lines · Stanislav Fomichev
Quoted 28 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?Quoted 5 lines
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".Quoted 4 lines
Don't you already need a bunch of netlink to setup rss and flow steering? And if we end up adding queue api, you'll have to call that one over netlink also.
Quoted 7 lines
I'm thinking one thing that could work is extending bind-rx with an optional rx-buf-len arg, which in the code translates into devmem using the new
net_mp_open_rxqvariant which not only restarts the queue but also sets the size. From there the implementation should be fairly straightforward in devmem. devmem currently rejects any pp for which pp.order != 0. It would need to start accepting that and forwarding the order to thegen_pooldoing the allocations, etc.
Right, that's the logical alternative, to put that rx-buf-len on the binding to control the size of the niovs. But then what do we do with the queue's rx-buf-len? bnxt patch in the series does page_pool_dev_alloc_frag(..., bp->rx_page_size). bp->rx_page_size comes from netlink. Does it need to be inherited from the pp in the devmem case somehow?
Quoted 52 lines · Stanislav Fomichev
Quoted 42 lines · Mina Almasry
Quoted 36 lines · Stanislav Fomichev
Quoted 28 lines · Pavel Begunkov
Quoted 2 lines · Stanislav Fomichev
Quoted 1 line · Pavel Begunkov
On 7/28/25 18:13, Stanislav Fomichev wrote:
...>>> Supporting big buffers is the right direction, but I have the same
Quoted 4 lines
Quoted 4 lines
Quoted 1 line
feedback:
Let me actually check the feedback for the queue config RFC...
Quoted 8 lines
Quoted 5 lines
it would be nice to fit a cohesive story for the devmem as well.
Only the last patch is zcrx specific, the rest is agnostic, devmem can absolutely reuse that. I don't think there are any issues wiring up devmem?
Right, but the patch number 2 exposes per-queue rx-buf-len which I'm not sure is the right fit for devmem, see below. If all you
I guess you're talking about uapi setting it, because as an internal per queue parameter IMHO it does make sense for devmem.
Quoted 1 line
care is exposing it via
io_uring, maybe don't expose it from netlink forSure, I can remove the set operation.
Quoted 3 lines
now? Although I'm not sure I understand why you're also passing this per-queue value via
io_uring. Can you not inherit it from the queue config?Quoted 5 lines
It's not a great option. It complicates user space with netlink. And there are convenience configuration features in the future that requires
io_uringto parse memory first. E.g. instead of user specifying a particular size, it can say "choose the largest length under 32K that the backing memory allows".Quoted 4 lines
Don't you already need a bunch of netlink to setup rss and flow steering? And if we end up adding queue api, you'll have to call that one over netlink also.
Quoted 7 lines
I'm thinking one thing that could work is extending bind-rx with an optional rx-buf-len arg, which in the code translates into devmem using the new
net_mp_open_rxqvariant which not only restarts the queue but also sets the size. From there the implementation should be fairly straightforward in devmem. devmem currently rejects any pp for which pp.order != 0. It would need to start accepting that and forwarding the order to thegen_pooldoing the allocations, etc.
Quoted 6 lines
Right, that's the logical alternative, to put that rx-buf-len on the binding to control the size of the niovs. But then what do we do with the queue's rx-buf-len? bnxt patch in the series does
page_pool_dev_alloc_frag(..., bp->rx_page_size).bp->rx_page_sizecomes from netlink. Does it need to be inherited from the pp in the devmem case somehow?
I need to review the series closely, but the only thing that makes sense to me off the bat is that the rx-buf-len option sets the rx-buf-len of the queue as if you called the queue-set API in a separate call (and the unbind would reset the value to default).
Signature · 2 lines
Thanks, Mina
Quoted 9 lines · Pavel Begunkov
This series implements large rx buffer support for
io_uring/zcrxon top of Jakub's queue configuration changes, but it can also be used by other memory providers. Large rx buffers can be drastically beneficial with high-end hw-gro enabled cards that can coalesce traffic into larger pages, reducing the number of frags traversing the network stack and resuling in larger contiguous chunks of data for the userspace. Benchamrks showed up to ~30% improvement in CPU util.
Very exciting.
I have not yet had a chance to thoroughly look, but even still I have a few high level questions/concerns. Maybe you already have answers to them that can make my life a bit easier as I try to take a thorough look.
- I'm a bit confused that you're not making changes to the core net stack to support non-PAGE_SIZE netmems. From a quick glance, it seems that there are potentially a ton of places in the net stack that assume PAGE_SIZE:
cd net
ackc "PAGE_SIZE|PAGE_SHIFT" | wc -l
468
Are we sure none of these places assuming PAGE_SIZE or PAGE_SHIFT are concerning?
- You're not adding a field in the net_iov that tells us how big the net_iov is. It seems to me you're configuring the driver to set the rx buffer size, then assuming all the pp allocations are of that size, then assuming in the rxzc code that all the net_iov are of that size. I think a few problems may happen?
(a) what happens if the rx buffer size is re-configured? Does the io_uring rxrc instance get recreated as well?
(b) what happens with skb coalescing? skb coalescing is already a bit of a mess. We don't allow coalescing unreadable and readable skbs, but we do allow coalescing devmem and iozcrx skbs which could lead to some bugs I'm guessing already. AFAICT as of this patch series we may allow coalescing of skbs with netmems inside of them of different sizes, but AFAICT so far, the iozcrx assume the size is constant across all the netmems it gets, which I'm not sure is always true?
For all these reasons I had assumed that we'd need space in the net_iov that tells us its size: net_iov->size.
And then netmem_size(netmem) would replace all the PAGE_SIZE assumptions in the net stack, and then we'd disallow coalescing of skbs with different-sized netmems (else we need to handle them correctly per the netmem_size).
Signature · 2 lines
Thanks, Mina
Pavel Begunkov
maintainerQuoted 18 lines · Mina Almasry
Quoted 9 lines · Pavel Begunkov
This series implements large rx buffer support for
io_uring/zcrxon top of Jakub's queue configuration changes, but it can also be used by other memory providers. Large rx buffers can be drastically beneficial with high-end hw-gro enabled cards that can coalesce traffic into larger pages, reducing the number of frags traversing the network stack and resuling in larger contiguous chunks of data for the userspace. Benchamrks showed up to ~30% improvement in CPU util.Very exciting.
I have not yet had a chance to thoroughly look, but even still I have a few high level questions/concerns. Maybe you already have answers to them that can make my life a bit easier as I try to take a thorough look.
Quoted 4 lines
- I'm a bit confused that you're not making changes to the core net stack to support non-PAGE_SIZE netmems. From a quick glance, it seems that there are potentially a ton of places in the net stack that assume
PAGE_SIZE:
The stack already supports large frags and it's not new. Page pools has higher order allocations, see __page_pool_alloc_page_order. The tx path can allocate large pages / coalesce user pages. Any specific place that concerns you? There are many places legitimately usingPAGE_SIZE: kmap'ing folios, shifting it by order to get the size, linear allocations, etc.
Quoted 7 lines
cd net
ackc "PAGE_SIZE|PAGE_SHIFT" | wc -l
468Are we sure none of these places assuming
PAGE_SIZEorPAGE_SHIFTare concerning?
Quoted 8 lines
- You're not adding a field in the
net_iovthat tells us how big thenet_iovis. It seems to me you're configuring the driver to set the rx buffer size, then assuming all the pp allocations are of that size, then assuming in the rxzc code that all thenet_iovare of that size. I think a few problems may happen?(a) what happens if the rx buffer size is re-configured? Does the
io_uringrxrc instance get recreated as well?
Any reason you even want it to work? You can't and frankly shouldn't be allowed to, at least in case of io_uring. Unless it's rejected somewhere earlier, in this case it'll fail on the order check while trying to create a page pool with a zcrx provider.
Quoted 7 lines
(b) what happens with skb coalescing? skb coalescing is already a bit of a mess. We don't allow coalescing unreadable and readable skbs, but we do allow coalescing devmem and iozcrx skbs which could lead to some bugs I'm guessing already. AFAICT as of this patch series we may allow coalescing of skbs with netmems inside of them of different sizes, but AFAICT so far, the iozcrx assume the size is constant across all the netmems it gets, which I'm not sure is always true?
It rejects niovs from other providers incl. from any other io_uring instances, so it only assume a uniform size for its own niovs. The backing memory is verified that it can be chunked.
Quoted 2 lines
For all these reasons I had assumed that we'd need space in the
net_iovthat tells us its size:net_iov->size.
Nope, not in this case.
Quoted 4 lines
And then
netmem_size(netmem)would replace all thePAGE_SIZEassumptions in the net stack, and then we'd disallow coalescing of skbs with different-sized netmems (else we need to handle them correctly per thenetmem_size).
I'm not even sure what's the concern. What's the difference b/w tcp_recvmsg_dmabuf() getting one skb with differently sized frags or same frags in separate skbs? You still need to handle it somehow, even if by failing.
Also, we should never coalesce different niovs together regardless of sizes. And for coalescing two chunks of the same niov, it should work just fine even without knowing the length.
skb_can_coalesce_netmem {
...
return netmem == skb_frag_netmem(frag) &&
off == skb_frag_off(frag) + skb_frag_size(frag);
}Essentially, for devmem only tcp_recvmsg_dmabuf() and other devmem specific code would need to know about the niov size.
Signature · 1 line
Pavel Begunkov
Quoted 20 lines · Pavel Begunkov
Quoted 18 lines · Mina Almasry
Quoted 9 lines · Pavel Begunkov
This series implements large rx buffer support for
io_uring/zcrxon top of Jakub's queue configuration changes, but it can also be used by other memory providers. Large rx buffers can be drastically beneficial with high-end hw-gro enabled cards that can coalesce traffic into larger pages, reducing the number of frags traversing the network stack and resuling in larger contiguous chunks of data for the userspace. Benchamrks showed up to ~30% improvement in CPU util.Very exciting.
I have not yet had a chance to thoroughly look, but even still I have a few high level questions/concerns. Maybe you already have answers to them that can make my life a bit easier as I try to take a thorough look.
Quoted 8 lines
Quoted 4 lines
- I'm a bit confused that you're not making changes to the core net stack to support non-PAGE_SIZE netmems. From a quick glance, it seems that there are potentially a ton of places in the net stack that assume
PAGE_SIZE:The stack already supports large frags and it's not new. Page pools has higher order allocations, see
__page_pool_alloc_page_order. The tx path can allocate large pages / coalesce user pages.
Right, large order allocations are not new, but I'm not sure they actually work reliably. AFAICT most drivers set pp_params.order = 0; I'm not sure how well tested multi-order pages are.
It may be reasonable to assume multi order pages just work and see what blows up, though.
Quoted 5 lines
Any specific
place that concerns you? There are many places legitimately usingPAGE_SIZE: kmap'ing folios, shifting it by order to get the size, linear allocations, etc.
From a 5-min look:
- skb_splice_from_iter, this line: size_t part = min_t(size_t, PAGE_SIZE - off, len);
- skb_pp_cow_data, this line: max_head_size = SKB_WITH_OVERHEAD(PAGE_SIZE - headroom);
- skb_seq_read, this line: pg_sz = min_t(unsigned int, pg_sz - st->frag_off, PAGE_SIZE - pg_off
- zerocopy_fill_skb_from_iter, this line: int size = min_t(int, copied, PAGE_SIZE - start);
I think the PAGE_SIZE - logic in general assumes the memory is PAGE_SIZEd. Although in these cases it seems page specifics, i.e. net_iovs wouldn't be exposed to these particular call sites.
I spent a few weeks acking the net stack for all page-access to prune all of them to add unreadable netmem... are you somewhat confident there are no PAGE_SIZE assumptions in the net stack that affect net_iovs that require a deep look? Or is the approach here to merge this and see what/if breaks?
Quoted 13 lines
Quoted 13 lines
cd net
ackc "PAGE_SIZE|PAGE_SHIFT" | wc -l
468Are we sure none of these places assuming
PAGE_SIZEorPAGE_SHIFTare concerning?- You're not adding a field in the
net_iovthat tells us how big thenet_iovis. It seems to me you're configuring the driver to set the rx buffer size, then assuming all the pp allocations are of that size, then assuming in the rxzc code that all thenet_iovare of that size. I think a few problems may happen?
Quoted 8 lines
Quoted 2 lines
(a) what happens if the rx buffer size is re-configured? Does the
io_uringrxrc instance get recreated as well?Any reason you even want it to work? You can't and frankly shouldn't be allowed to, at least in case of
io_uring. Unless it's rejected somewhere earlier, in this case it'll fail on the order check while trying to create a page pool with a zcrx provider.
I think it's reasonable to disallow rx-buffer-size reconfiguration when the queue is memory-config bound. I can check to see what this code is doing.
Quoted 10 lines
Quoted 7 lines
(b) what happens with skb coalescing? skb coalescing is already a bit of a mess. We don't allow coalescing unreadable and readable skbs, but we do allow coalescing devmem and iozcrx skbs which could lead to some bugs I'm guessing already. AFAICT as of this patch series we may allow coalescing of skbs with netmems inside of them of different sizes, but AFAICT so far, the iozcrx assume the size is constant across all the netmems it gets, which I'm not sure is always true?
It rejects niovs from other providers incl. from any other
io_uringinstances, so it only assume a uniform size for its own niovs.
Thanks. What is 'it' and where is the code that does the rejection?
Quoted 7 lines
The
backing memory is verified that it can be chunked.Quoted 2 lines
For all these reasons I had assumed that we'd need space in the
net_iovthat tells us its size:net_iov->size.Nope, not in this case.
Quoted 9 lines
Quoted 4 lines
And then
netmem_size(netmem)would replace all thePAGE_SIZEassumptions in the net stack, and then we'd disallow coalescing of skbs with different-sized netmems (else we need to handle them correctly per thenetmem_size).I'm not even sure what's the concern. What's the difference b/w
tcp_recvmsg_dmabuf()getting one skb with differently sized frags or same frags in separate skbs? You still need to handle it somehow, even if by failing.
Right, I just wanted to understand what the design is. I guess the design is allowing the netmems in the same skb to have different max frag lens, yes?
I am guessing that it works, even in tcp_recvmsg_dmabuf. I guess the frag len is actually in frag->len, so already it may vary from frag to frag. Even if coalescing happens, some frags would have a frag->len = PAGE_SIZE and some > PAGE_SIZE. Seems fine to me off the bat.
Quoted 4 lines
Also, we should never coalesce different niovs together regardless of sizes. And for coalescing two chunks of the same niov, it should work just fine even without knowing the length.
Yeah, we should probably not coalesce 2 netmems together, although I vaguely remember reading code in a net stack hepler that does that somewhere already. Whatever.
Signature · 2 lines
Thanks, Mina
Pavel Begunkov
maintainerOn 7/28/25 21:23, Mina Almasry wrote:
...>>>
Quoted 22 lines
Quoted 8 lines
Quoted 4 lines
- I'm a bit confused that you're not making changes to the core net stack to support non-PAGE_SIZE netmems. From a quick glance, it seems that there are potentially a ton of places in the net stack that assume
PAGE_SIZE:The stack already supports large frags and it's not new. Page pools has higher order allocations, see
__page_pool_alloc_page_order. The tx path can allocate large pages / coalesce user pages.Right, large order allocations are not new, but I'm not sure they actually work reliably. AFAICT most drivers set pp_params.order = 0; I'm not sure how well tested multi-order pages are.
It may be reasonable to assume multi order pages just work and see what blows up, though.
Quoted 5 lines
Any specific
place that concerns you? There are many places legitimately usingPAGE_SIZE: kmap'ing folios, shifting it by order to get the size, linear allocations, etc.
Quoted 4 lines
From a 5-min look:
-
skb_splice_from_iter, this line:size_tpart =min_t(size_t, PAGE_SIZE - off, len);
It does it for pages that it got from iov_iter_extract_pages() a few lines above, those are PAGE_SIZE'd.
Quoted 2 lines
-
skb_pp_cow_data, this line:max_head_size=SKB_WITH_OVERHEAD(PAGE_SIZE - headroom);
This one should be about the linear part, not frags
Quoted 2 lines
-
skb_seq_read, this line:pg_sz= min_t(unsigned int,pg_sz-st->frag_off,PAGE_SIZE-pg_off
That's kmap handling, it can iterate a frag multiple times in PAGE_SIZE chunks for high mem archs.
Quoted 2 lines
-
zerocopy_fill_skb_from_iter, this line: int size =min_t(int, copied, PAGE_SIZE - start);
Pages from iov_iter_get_pages2(), same as withskb_splice_from_iter()
Quoted 8 lines
I think the
PAGE_SIZE -logic in general assumes the memory isPAGE_SIZEd. Although in these cases it seems page specifics, i.e.net_iovswouldn't be exposed to these particular call sites.I spent a few weeks acking the net stack for all page-access to prune all of them to add unreadable netmem... are you somewhat confident there are no
PAGE_SIZEassumptions in the net stack that affectnet_iovsthat require a deep look? Or is the approach here to merge
The difference is that this one is already supported and the stack is large page aware, while unreadable frags was a new concept.
Quoted 1 line
this and see what/if breaks?
No reason for it not to work. Even if breaks somewhere on that, it should be a pre-existent problem, which needs to be fixed either way.
Quoted 22 lines
Quoted 13 lines
Quoted 13 lines
cd net
ackc "PAGE_SIZE|PAGE_SHIFT" | wc -l
468Are we sure none of these places assuming
PAGE_SIZEorPAGE_SHIFTare concerning?- You're not adding a field in the
net_iovthat tells us how big thenet_iovis. It seems to me you're configuring the driver to set the rx buffer size, then assuming all the pp allocations are of that size, then assuming in the rxzc code that all thenet_iovare of that size. I think a few problems may happen?Quoted 8 lines
Quoted 2 lines
(a) what happens if the rx buffer size is re-configured? Does the
io_uringrxrc instance get recreated as well?Any reason you even want it to work? You can't and frankly shouldn't be allowed to, at least in case of
io_uring. Unless it's rejected somewhere earlier, in this case it'll fail on the order check while trying to create a page pool with a zcrx provider.
Quoted 3 lines
I think it's reasonable to disallow rx-buffer-size reconfiguration when the queue is memory-config bound. I can check to see what this code is doing.
Right, it doesn't make sense to reconfigure zcrx, and we can only fail the operation one way or another.
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
(b) what happens with skb coalescing? skb coalescing is already a bit of a mess. We don't allow coalescing unreadable and readable skbs, but we do allow coalescing devmem and iozcrx skbs which could lead to some bugs I'm guessing already. AFAICT as of this patch series we may allow coalescing of skbs with netmems inside of them of different sizes, but AFAICT so far, the iozcrx assume the size is constant across all the netmems it gets, which I'm not sure is always true?
Quoted 4 lines
Quoted 2 lines
It rejects niovs from other providers incl. from any other
io_uringinstances, so it only assume a uniform size for its own niovs.Thanks. What is 'it' and where is the code that does the rejection?
zcrx does, you're familiar with this chunk:
io_uring/zcrx.c:
io_zcrx_recv_frag() {
if (niov->pp->mp_ops != &io_uring_pp_zc_ops ||
io_pp_to_ifq(niov->pp) != ifq)
return -EFAULT;
}Quoted 17 lines
Quoted 7 lines
The
backing memory is verified that it can be chunked.Quoted 2 lines
For all these reasons I had assumed that we'd need space in the
net_iovthat tells us its size:net_iov->size.Nope, not in this case.
Quoted 9 lines
Quoted 4 lines
And then
netmem_size(netmem)would replace all thePAGE_SIZEassumptions in the net stack, and then we'd disallow coalescing of skbs with different-sized netmems (else we need to handle them correctly per thenetmem_size).I'm not even sure what's the concern. What's the difference b/w
tcp_recvmsg_dmabuf()getting one skb with differently sized frags or same frags in separate skbs? You still need to handle it somehow, even if by failing.
Quoted 3 lines
Right, I just wanted to understand what the design is. I guess the design is allowing the netmems in the same skb to have different max frag lens, yes?
Yeah, and it's already allowed for higher order pages.
Quoted 1 line
I am guessing that it works, even in
tcp_recvmsg_dmabuf. I guess the
And you won't see it unless adds support for that, that's why I added this:
if (!net_is_devmem_iov(niov)) {
err = -ENODEV;
goto out;
}Quoted 4 lines
frag len is actually in
frag->len, so already it may vary from frag to frag. Even if coalescing happens, some frags would have afrag->len=PAGE_SIZEand some >PAGE_SIZE. Seems fine to me off the bat.
Quoted 8 lines
Quoted 4 lines
Also, we should never coalesce different niovs together regardless of sizes. And for coalescing two chunks of the same niov, it should work just fine even without knowing the length.
Yeah, we should probably not coalesce 2 netmems together, although I vaguely remember reading code in a net stack hepler that does that somewhere already. Whatever.
Let know if that turns out to be true, because it should already be broken. You shouldn't coalesce pages from different folios, and to check that you need to get the head page / etc., which niovs obviously don't have.
Signature · 1 line
Pavel Begunkov