<div> </div><div> </div><div>Hi Feifei,</div><div> </div><div>few more comments from me, see below.</div><blockquote><p>Add 'rte_eth_recycle_rx_queue_info_get' and 'rte_eth_recycle_mbufs'<br />APIs to recycle used mbufs from a transmit queue of an Ethernet device,<br />and move these mbufs into a mbuf ring for a receive queue of an Ethernet<br />device. This can bypass mempool 'put/get' operations hence saving CPU<br />cycles.<br /><br />For each recycling mbufs, the rte_eth_recycle_mbufs() function performs<br />the following operations:<br />- Copy used *rte_mbuf* buffer pointers from Tx mbuf ring into Rx mbuf<br />ring.<br />- Replenish the Rx descriptors with the recycling *rte_mbuf* mbufs freed<br />from the Tx mbuf ring.<br /><br />Suggested-by: Honnappa Nagarahalli <<a href="mailto:honnappa.nagarahalli@arm.com" rel="noopener noreferrer">honnappa.nagarahalli@arm.com</a>><br />Suggested-by: Ruifeng Wang <<a href="mailto:ruifeng.wang@arm.com" rel="noopener noreferrer">ruifeng.wang@arm.com</a>><br />Signed-off-by: Feifei Wang <<a href="mailto:feifei.wang2@arm.com" rel="noopener noreferrer">feifei.wang2@arm.com</a>><br />Reviewed-by: Ruifeng Wang <<a href="mailto:ruifeng.wang@arm.com" rel="noopener noreferrer">ruifeng.wang@arm.com</a>><br />Reviewed-by: Honnappa Nagarahalli <<a href="mailto:honnappa.nagarahalli@arm.com" rel="noopener noreferrer">honnappa.nagarahalli@arm.com</a>><br />---<br /> doc/guides/rel_notes/release_23_07.rst | 7 +<br /> lib/ethdev/ethdev_driver.h | 10 ++<br /> lib/ethdev/ethdev_private.c | 2 +<br /> lib/ethdev/rte_ethdev.c | 31 +++++<br /> lib/ethdev/rte_ethdev.h | 182 +++++++++++++++++++++++++<br /> lib/ethdev/rte_ethdev_core.h | 15 +-<br /> lib/ethdev/version.map | 4 +<br /> 7 files changed, 249 insertions(+), 2 deletions(-)<br /><br />diff --git a/doc/guides/rel_notes/release_23_07.rst b/doc/guides/rel_notes/release_23_07.rst<br />index a9b1293689..f279036cb9 100644<br />--- a/doc/guides/rel_notes/release_23_07.rst<br />+++ b/doc/guides/rel_notes/release_23_07.rst<br />@@ -55,6 +55,13 @@ New Features<br />      Also, make sure to start the actual text at the margin.<br />      =======================================================<br /> <br />+* **Add mbufs recycling support. **<br />+ Added ``rte_eth_recycle_rx_queue_info_get`` and ``rte_eth_recycle_mbufs``<br />+ APIs which allow the user to copy used mbufs from the Tx mbuf ring<br />+ into the Rx mbuf ring. This feature supports the case that the Rx Ethernet<br />+ device is different from the Tx Ethernet device with respective driver<br />+ callback functions in ``rte_eth_recycle_mbufs``.<br />+<br /> <br /> Removed Items<br /> -------------<br />diff --git a/lib/ethdev/ethdev_driver.h b/lib/ethdev/ethdev_driver.h<br />index 2c9d615fb5..c6723d5277 100644<br />--- a/lib/ethdev/ethdev_driver.h<br />+++ b/lib/ethdev/ethdev_driver.h<br />@@ -59,6 +59,10 @@ struct rte_eth_dev {<!-- --><br />         eth_rx_descriptor_status_t rx_descriptor_status;<br />         /** Check the status of a Tx descriptor */<br />         eth_tx_descriptor_status_t tx_descriptor_status;<br />+ /** Pointer to PMD transmit mbufs reuse function */<br />+ eth_recycle_tx_mbufs_reuse_t recycle_tx_mbufs_reuse;<br />+ /** Pointer to PMD receive descriptors refill function */<br />+ eth_recycle_rx_descriptors_refill_t recycle_rx_descriptors_refill;<br /> <br />         /**<br />          * Device data that is shared between primary and secondary processes<br />@@ -504,6 +508,10 @@ typedef void (*eth_rxq_info_get_t)(struct rte_eth_dev *dev,<br /> typedef void (*eth_txq_info_get_t)(struct rte_eth_dev *dev,<br />         uint16_t tx_queue_id, struct rte_eth_txq_info *qinfo);<br /> <br />+typedef void (*eth_recycle_rxq_info_get_t)(struct rte_eth_dev *dev,<br />+ uint16_t rx_queue_id,<br />+ struct rte_eth_recycle_rxq_info *recycle_rxq_info);<br />+<br /> typedef int (*eth_burst_mode_get_t)(struct rte_eth_dev *dev,<br />         uint16_t queue_id, struct rte_eth_burst_mode *mode);<br /> <br />@@ -1247,6 +1255,8 @@ struct eth_dev_ops {<!-- --><br />         eth_rxq_info_get_t rxq_info_get;<br />         /** Retrieve Tx queue information */<br />         eth_txq_info_get_t txq_info_get;<br />+ /** Retrieve mbufs recycle Rx queue information */<br />+ eth_recycle_rxq_info_get_t recycle_rxq_info_get;<br />         eth_burst_mode_get_t rx_burst_mode_get; /**< Get Rx burst mode */<br />         eth_burst_mode_get_t tx_burst_mode_get; /**< Get Tx burst mode */<br />         eth_fw_version_get_t fw_version_get; /**< Get firmware version */<br />diff --git a/lib/ethdev/ethdev_private.c b/lib/ethdev/ethdev_private.c<br />index 14ec8c6ccf..f8ab64f195 100644<br />--- a/lib/ethdev/ethdev_private.c<br />+++ b/lib/ethdev/ethdev_private.c<br />@@ -277,6 +277,8 @@ eth_dev_fp_ops_setup(struct rte_eth_fp_ops *fpo,<br />         fpo->rx_queue_count = dev->rx_queue_count;<br />         fpo->rx_descriptor_status = dev->rx_descriptor_status;<br />         fpo->tx_descriptor_status = dev->tx_descriptor_status;<br />+ fpo->recycle_tx_mbufs_reuse = dev->recycle_tx_mbufs_reuse;<br />+ fpo->recycle_rx_descriptors_refill = dev->recycle_rx_descriptors_refill;<br /> <br />         fpo->rxq.data = dev->data->rx_queues;<br />         fpo->rxq.clbk = (void **)(uintptr_t)dev->post_rx_burst_cbs;<br />diff --git a/lib/ethdev/rte_ethdev.c b/lib/ethdev/rte_ethdev.c<br />index 4d03255683..7c27dcfea4 100644<br />--- a/lib/ethdev/rte_ethdev.c<br />+++ b/lib/ethdev/rte_ethdev.c<br />@@ -5784,6 +5784,37 @@ rte_eth_tx_queue_info_get(uint16_t port_id, uint16_t queue_id,<br />         return 0;<br /> }<br /> <br />+int<br />+rte_eth_recycle_rx_queue_info_get(uint16_t port_id, uint16_t queue_id,<br />+ struct rte_eth_recycle_rxq_info *recycle_rxq_info)<br />+{<!-- --><br />+ struct rte_eth_dev *dev;<br />+<br />+ RTE_ETH_VALID_PORTID_OR_ERR_RET(port_id, -ENODEV);<br />+ dev = &rte_eth_devices[port_id];<br />+<br />+ if (queue_id >= dev->data->nb_rx_queues) {<!-- --><br />+ RTE_ETHDEV_LOG(ERR, "Invalid Rx queue_id=%u\n", queue_id);<br />+ return -EINVAL;<br />+ }<br />+<br />+ if (dev->data->rx_queues == NULL ||<br />+ dev->data->rx_queues[queue_id] == NULL) {<!-- --><br />+ RTE_ETHDEV_LOG(ERR,<br />+ "Rx queue %"PRIu16" of device with port_id=%"<br />+ PRIu16" has not been setup\n",<br />+ queue_id, port_id);<br />+ return -EINVAL;<br />+ }<br />+<br />+ if (*dev->dev_ops->recycle_rxq_info_get == NULL)<br />+ return -ENOTSUP;<br />+<br />+ dev->dev_ops->recycle_rxq_info_get(dev, queue_id, recycle_rxq_info);<br />+<br />+ return 0;<br />+}<br />+<br /> int<br /> rte_eth_rx_burst_mode_get(uint16_t port_id, uint16_t queue_id,<br />                           struct rte_eth_burst_mode *mode)<br />diff --git a/lib/ethdev/rte_ethdev.h b/lib/ethdev/rte_ethdev.h<br />index 99fe9e238b..7434aa2483 100644<br />--- a/lib/ethdev/rte_ethdev.h<br />+++ b/lib/ethdev/rte_ethdev.h<br />@@ -1820,6 +1820,30 @@ struct rte_eth_txq_info {<!-- --><br />         uint8_t queue_state; /**< one of RTE_ETH_QUEUE_STATE_*. */<br /> } __rte_cache_min_aligned;<br /> <br />+/**<br />+ * @warning<br />+ * @b EXPERIMENTAL: this structure may change without prior notice.<br />+ *<br />+ * Ethernet device Rx queue information structure for recycling mbufs.<br />+ * Used to retrieve Rx queue information when Tx queue reusing mbufs and moving<br />+ * them into Rx mbuf ring.<br />+ */<br />+struct rte_eth_recycle_rxq_info {<!-- --><br />+ struct rte_mbuf **mbuf_ring; /**< mbuf ring of Rx queue. */<br />+ struct rte_mempool *mp; /**< mempool of Rx queue. */<br />+ uint16_t *refill_head; /**< head of Rx queue refilling mbufs. */<br />+ uint16_t *receive_tail; /**< tail of Rx queue receiving pkts. */<br />+ uint16_t mbuf_ring_size; /**< configured number of mbuf ring size. */<br />+ /**<br />+ * Requirement on mbuf refilling batch size of Rx mbuf ring.<br />+ * For some PMD drivers, the number of Rx mbuf ring refilling mbufs<br />+ * should be aligned with mbuf ring size, in order to simplify<br />+ * ring wrapping around.<br />+ * Value 0 means that PMD drivers have no requirement for this.<br />+ */<br />+ uint16_t refill_requirement;<br />+} __rte_cache_min_aligned;<br />+<br /> /* Generic Burst mode flag definition, values can be ORed. */<br /> <br /> /**<br />@@ -4809,6 +4833,31 @@ int rte_eth_rx_queue_info_get(uint16_t port_id, uint16_t queue_id,<br /> int rte_eth_tx_queue_info_get(uint16_t port_id, uint16_t queue_id,<br />         struct rte_eth_txq_info *qinfo);<br /> <br />+/**<br />+ * @warning<br />+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice<br />+ *<br />+ * Retrieve information about given ports's Rx queue for recycling mbufs.<br />+ *<br />+ * @param port_id<br />+ * The port identifier of the Ethernet device.<br />+ * @param queue_id<br />+ * The Rx queue on the Ethernet devicefor which information<br />+ * will be retrieved.<br />+ * @param recycle_rxq_info<br />+ * A pointer to a structure of type *rte_eth_recycle_rxq_info* to be filled.<br />+ *<br />+ * @return<br />+ * - 0: Success<br />+ * - -ENODEV: If *port_id* is invalid.<br />+ * - -ENOTSUP: routine is not supported by the device PMD.<br />+ * - -EINVAL: The queue_id is out of range.<br />+ */<br />+__rte_experimental<br />+int rte_eth_recycle_rx_queue_info_get(uint16_t port_id,<br />+ uint16_t queue_id,<br />+ struct rte_eth_recycle_rxq_info *recycle_rxq_info);<br />+<br /> /**<br />  * Retrieve information about the Rx packet burst mode.<br />  *<br />@@ -6483,6 +6532,139 @@ rte_eth_tx_buffer(uint16_t port_id, uint16_t queue_id,<br />         return rte_eth_tx_buffer_flush(port_id, queue_id, buffer);<br /> }<br /> <br />+/**<br />+ * @warning<br />+ * @b EXPERIMENTAL: this API may change, or be removed, without prior notice<br />+ *<br />+ * Recycle used mbufs from a transmit queue of an Ethernet device, and move<br />+ * these mbufs into a mbuf ring for a receive queue of an Ethernet device.<br />+ * This can bypass mempool path to save CPU cycles.<br />+ *<br />+ * The rte_eth_recycle_mbufs() function loops, with rte_eth_rx_burst() and<br />+ * rte_eth_tx_burst() functions, freeing Tx used mbufs and replenishing Rx<br />+ * descriptors. The number of recycling mbufs depends on the request of Rx mbuf<br />+ * ring, with the constraint of enough used mbufs from Tx mbuf ring.<br />+ *<br />+ * For each recycling mbufs, the rte_eth_recycle_mbufs() function performs the<br />+ * following operations:<br />+ *<br />+ * - Copy used *rte_mbuf* buffer pointers from Tx mbuf ring into Rx mbuf ring.<br />+ *<br />+ * - Replenish the Rx descriptors with the recycling *rte_mbuf* mbufs freed<br />+ * from the Tx mbuf ring.<br />+ *<br />+ * This function spilts Rx and Tx path with different callback functions. The<br />+ * callback function recycle_tx_mbufs_reuse is for Tx driver. The callback<br />+ * function recycle_rx_descriptors_refill is for Rx driver. rte_eth_recycle_mbufs()<br />+ * can support the case that Rx Ethernet device is different from Tx Ethernet device.<br />+ *<br />+ * It is the responsibility of users to select the Rx/Tx queue pair to recycle<br />+ * mbufs. Before call this function, users must call rte_eth_recycle_rxq_info_get<br />+ * function to retrieve selected Rx queue information.<br />+ * @see rte_eth_recycle_rxq_info_get, struct rte_eth_recycle_rxq_info<br />+ *<br />+ * Currently, the rte_eth_recycle_mbufs() function can only support one-time pairing<br />+ * between the receive queue and transmit queue. Do not pair one receive queue with<br />+ * multiple transmit queues or pair one transmit queue with multiple receive queues,<br />+ * in order to avoid memory error rewriting.</p></blockquote><div>Probably I am missing something, but why it is not possible to do something like that:</div><div> </div><div><div>rte_eth_recycle_mbufs(rx_port_id=X, rx_queue_id=Y, tx_port_id=N, tx_queue_id=M, ...);</div><div>....</div><div><div>rte_eth_recycle_mbufs(rx_port_id=X, rx_queue_id=Y, tx_port_id=N, tx_queue_id=K, ...);</div></div></div><div> </div><div>I.E. feed rx queue from 2 tx queues?</div><div> </div><blockquote><p>+ *<br />+ * @param rx_port_id<br />+ * Port identifying the receive side.<br />+ * @param rx_queue_id<br />+ * The index of the receive queue identifying the receive side.<br />+ * The value must be in the range [0, nb_rx_queue - 1] previously supplied<br />+ * to rte_eth_dev_configure().<br />+ * @param tx_port_id<br />+ * Port identifying the transmit side.<br />+ * @param tx_queue_id<br />+ * The index of the transmit queue identifying the transmit side.<br />+ * The value must be in the range [0, nb_tx_queue - 1] previously supplied<br />+ * to rte_eth_dev_configure().<br />+ * @param recycle_rxq_info<br />+ * A pointer to a structure of type *rte_eth_recycle_rxq_info* which contains<br />+ * the information of the Rx queue mbuf ring.<br />+ * @return<br />+ * The number of recycling mbufs.<br />+ */<br />+__rte_experimental<br />+static inline uint16_t<br />+rte_eth_recycle_mbufs(uint16_t rx_port_id, uint16_t rx_queue_id,<br />+ uint16_t tx_port_id, uint16_t tx_queue_id,<br />+ struct rte_eth_recycle_rxq_info *recycle_rxq_info)<br />+{<!-- --><br />+ struct rte_eth_fp_ops *p;<br />+ void *qd;<br />+ uint16_t nb_mbufs;<br />+<br />+#ifdef RTE_ETHDEV_DEBUG_TX<br />+ if (tx_port_id >= RTE_MAX_ETHPORTS ||<br />+ tx_queue_id >= RTE_MAX_QUEUES_PER_PORT) {<!-- --><br />+ RTE_ETHDEV_LOG(ERR,<br />+ "Invalid tx_port_id=%u or tx_queue_id=%u\n",<br />+ tx_port_id, tx_queue_id);<br />+ return 0;<br />+ }<br />+#endif<br />+<br />+ /* fetch pointer to queue data */<br />+ p = &rte_eth_fp_ops[tx_port_id];<br />+ qd = p->txq.data[tx_queue_id];<br />+<br />+#ifdef RTE_ETHDEV_DEBUG_TX<br />+ RTE_ETH_VALID_PORTID_OR_ERR_RET(tx_port_id, 0);<br />+<br />+ if (qd == NULL) {<!-- --><br />+ RTE_ETHDEV_LOG(ERR, "Invalid Tx queue_id=%u for port_id=%u\n",<br />+ tx_queue_id, tx_port_id);<br />+ return 0;<br />+ }<br />+#endif<br />+ if (p->recycle_tx_mbufs_reuse == NULL)<br />+ return 0;<br />+<br />+ /* Copy used *rte_mbuf* buffer pointers from Tx mbuf ring<br />+ * into Rx mbuf ring.<br />+ */<br />+ nb_mbufs = p->recycle_tx_mbufs_reuse(qd, recycle_rxq_info);<br />+<br />+ /* If no recycling mbufs, return 0. */<br />+ if (nb_mbufs == 0)<br />+ return 0;<br />+<br />+#ifdef RTE_ETHDEV_DEBUG_RX<br />+ if (rx_port_id >= RTE_MAX_ETHPORTS ||<br />+ rx_queue_id >= RTE_MAX_QUEUES_PER_PORT) {<!-- --><br />+ RTE_ETHDEV_LOG(ERR, "Invalid rx_port_id=%u or rx_queue_id=%u\n",<br />+ rx_port_id, rx_queue_id);<br />+ return 0;<br />+ }<br />+#endif<br />+<br />+ /* fetch pointer to queue data */<br />+ p = &rte_eth_fp_ops[rx_port_id];<br />+ qd = p->rxq.data[rx_queue_id];<br />+<br />+#ifdef RTE_ETHDEV_DEBUG_RX<br />+ RTE_ETH_VALID_PORTID_OR_ERR_RET(rx_port_id, 0);<br />+<br />+ if (qd == NULL) {<!-- --><br />+ RTE_ETHDEV_LOG(ERR, "Invalid Rx queue_id=%u for port_id=%u\n",<br />+ rx_queue_id, rx_port_id);<br />+ return 0;<br />+ }<br />+#endif<br />+<br />+ if (p->recycle_rx_descriptors_refill == NULL)<br />+ return 0;<br />+<br />+ /* Replenish the Rx descriptors with the recycling<br />+ * into Rx mbuf ring.<br />+ */<br />+ p->recycle_rx_descriptors_refill(qd, nb_mbufs);<br />+<br />+ return nb_mbufs;<br />+}<br />+<br /> /**<br />  * @warning<br />  * @b EXPERIMENTAL: this API may change without prior notice<br />diff --git a/lib/ethdev/rte_ethdev_core.h b/lib/ethdev/rte_ethdev_core.h<br />index dcf8adab92..a2e6ea6b6c 100644<br />--- a/lib/ethdev/rte_ethdev_core.h<br />+++ b/lib/ethdev/rte_ethdev_core.h<br />@@ -56,6 +56,13 @@ typedef int (*eth_rx_descriptor_status_t)(void *rxq, uint16_t offset);<br /> /** @internal Check the status of a Tx descriptor */<br /> typedef int (*eth_tx_descriptor_status_t)(void *txq, uint16_t offset);<br /> <br />+/** @internal Copy used mbufs from Tx mbuf ring into Rx mbuf ring */<br />+typedef uint16_t (*eth_recycle_tx_mbufs_reuse_t)(void *txq,<br />+ struct rte_eth_recycle_rxq_info *recycle_rxq_info);<br />+<br />+/** @internal Refill Rx descriptors with the recycling mbufs */<br />+typedef void (*eth_recycle_rx_descriptors_refill_t)(void *rxq, uint16_t nb);<br />+<br /> /**<br />  * @internal<br />  * Structure used to hold opaque pointers to internal ethdev Rx/Tx<br />@@ -90,9 +97,11 @@ struct rte_eth_fp_ops {<!-- --><br />         eth_rx_queue_count_t rx_queue_count;<br />         /** Check the status of a Rx descriptor. */<br />         eth_rx_descriptor_status_t rx_descriptor_status;<br />+ /** Refill Rx descriptors with the recycling mbufs. */<br />+ eth_recycle_rx_descriptors_refill_t recycle_rx_descriptors_refill;</p></blockquote><div>I am afraid we can't put new fields here without ABI breakage.</div><div>It has to be below rxq.</div><div>Now thinking about current layout probably not the best one,</div><div>and when introducing this struct, I should probably put rxq either</div><div>on the top of the struct, or on the next cache line.</div><div>But such change is not possible right now anyway.</div><div>Same story for txq.</div><div> </div><blockquote><p>         /** Rx queues data. */<br />         struct rte_ethdev_qdata rxq;<br />- uintptr_t reserved1[3];<br />+ uintptr_t reserved1[2];<br />         /**@}*/<br /> <br />         /**@{*/<br />@@ -106,9 +115,11 @@ struct rte_eth_fp_ops {<!-- --><br />         eth_tx_prep_t tx_pkt_prepare;<br />         /** Check the status of a Tx descriptor. */<br />         eth_tx_descriptor_status_t tx_descriptor_status;<br />+ /** Copy used mbufs from Tx mbuf ring into Rx. */<br />+ eth_recycle_tx_mbufs_reuse_t recycle_tx_mbufs_reuse;<br />         /** Tx queues data. */<br />         struct rte_ethdev_qdata txq;<br />- uintptr_t reserved2[3];<br />+ uintptr_t reserved2[2];<br />         /**@}*/<br /> <br /> } __rte_cache_aligned;<br />diff --git a/lib/ethdev/version.map b/lib/ethdev/version.map<br />index 357d1a88c0..45c417f6bd 100644<br />--- a/lib/ethdev/version.map<br />+++ b/lib/ethdev/version.map<br />@@ -299,6 +299,10 @@ EXPERIMENTAL {<!-- --><br />         rte_flow_action_handle_query_update;<br />         rte_flow_async_action_handle_query_update;<br />         rte_flow_async_create_by_index;<br />+<br />+ # added in 23.07<br />+ rte_eth_recycle_mbufs;<br />+ rte_eth_recycle_rx_queue_info_get;<br /> };<br /> <br /> INTERNAL {<!-- --></p>--<br />2.25.1<br /> </blockquote>