* [PATCH] net/ice: support link flow control
@ 2026-09-01 15:12 Vladimir Medvedkin
2026-09-10 11:25 ` [PATCH v2] " Vladimir Medvedkin
0 siblings, 1 reply; 8+ messages in thread
From: Vladimir Medvedkin @ 2026-09-01 15:12 UTC (permalink / raw)
To: dev
Add get and set functions for link flow control (802.3x).
Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
doc/guides/nics/features/ice.ini | 1 +
doc/guides/rel_notes/release_26_11.rst | 4 +
drivers/net/intel/ice/ice_ethdev.c | 286 +++++++++++++++++++------
drivers/net/intel/ice/ice_ethdev.h | 1 +
4 files changed, 232 insertions(+), 60 deletions(-)
diff --git a/doc/guides/nics/features/ice.ini b/doc/guides/nics/features/ice.ini
index 893d09e9ec..309f691f32 100644
--- a/doc/guides/nics/features/ice.ini
+++ b/doc/guides/nics/features/ice.ini
@@ -30,6 +30,7 @@ RSS hash = Y
RSS key update = Y
RSS reta update = Y
VLAN filter = Y
+Flow control = Y
Traffic manager = Y
CRC offload = Y
VLAN offload = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..5c4cd8a6df 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,10 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Updated Intel ice driver.**
+
+ * Added support for getting and setting link (802.3x) flow control.
+
Removed Items
-------------
diff --git a/drivers/net/intel/ice/ice_ethdev.c b/drivers/net/intel/ice/ice_ethdev.c
index 76b8ff0a72..11902df8b0 100644
--- a/drivers/net/intel/ice/ice_ethdev.c
+++ b/drivers/net/intel/ice/ice_ethdev.c
@@ -211,7 +211,12 @@ static int ice_fec_set(struct rte_eth_dev *dev, uint32_t fec_capa);
static const uint32_t *ice_buffer_split_supported_hdr_ptypes_get(struct rte_eth_dev *dev,
size_t *no_of_elements);
static int ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info);
+static void ice_set_dflt_mib(struct rte_eth_dev *dev);
+static int ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
+static int ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
static int ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf);
+static int ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc, uint8_t tc_bitmap,
+ const struct rte_eth_fc_conf *fc_conf);
static const struct rte_pci_id pci_id_ice_map[] = {
{ RTE_PCI_DEVICE(ICE_INTEL_VENDOR_ID, ICE_DEV_ID_E823L_BACKPLANE) },
@@ -352,6 +357,8 @@ static const struct eth_dev_ops ice_eth_dev_ops = {
.fec_set = ice_fec_set,
.buffer_split_supported_hdr_ptypes_get = ice_buffer_split_supported_hdr_ptypes_get,
.get_dcb_info = ice_get_dcb_info,
+ .flow_ctrl_get = ice_flow_ctrl_get,
+ .flow_ctrl_set = ice_flow_ctrl_set,
.priority_flow_ctrl_set = ice_priority_flow_ctrl_set,
};
@@ -1468,9 +1475,23 @@ ice_handle_aq_msg(struct rte_eth_dev *dev)
switch (opcode) {
case ice_aqc_opc_get_link_status:
ret = ice_link_update(dev, 0);
- if (!ret)
+ if (!ret) {
+ struct ice_pf *pf =
+ ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+
+ /* On link-up, reapply the default single-TC configuration. */
+ if ((hw->port_info->phy.link_info.link_info & ICE_AQ_LINK_UP) &&
+ !(dev->data->dev_conf.rxmode.mq_mode &
+ RTE_ETH_MQ_RX_DCB_FLAG) &&
+ hw->port_info->qos_cfg.local_dcbx_cfg.pfc.pfcena == 0) {
+ ice_set_dflt_mib(dev);
+ if (pf->fc_conf.mode != RTE_ETH_FC_NONE)
+ ice_cfg_fc_params(dev, 0, 0, &pf->fc_conf);
+ }
+
rte_eth_dev_callback_process
(dev, RTE_ETH_EVENT_INTR_LSC, NULL);
+ }
break;
default:
PMD_DRV_LOG(DEBUG, "Request %u is not supported yet",
@@ -4073,6 +4094,22 @@ ice_dev_configure(struct rte_eth_dev *dev)
return 0;
}
+static int
+ice_get_link_info_safe(struct ice_pf *pf, bool ena_lse,
+ struct ice_link_status *link)
+{
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ int ret;
+
+ rte_spinlock_lock(&pf->link_lock);
+
+ ret = ice_aq_get_link_info(hw->port_info, ena_lse, link, NULL);
+
+ rte_spinlock_unlock(&pf->link_lock);
+
+ return ret;
+}
+
static int
ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info)
{
@@ -4105,6 +4142,190 @@ ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info)
return 0;
}
+/*
+ * Program a default single-TC local LLDP MIB configuration. All user priorities
+ * map to TC0 at 100% bandwidth.
+ */
+static void
+ice_set_dflt_mib(struct rte_eth_dev *dev)
+{
+ struct ice_hw *hw = ICE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+ struct ice_dcbx_cfg dcbcfg = { 0 };
+ uint8_t *lldpmib;
+ uint16_t miblen;
+
+ dcbcfg.etscfg.willing = 1;
+ dcbcfg.etscfg.maxtcs = hw->func_caps.common_cap.maxtc;
+ dcbcfg.etscfg.tcbwtable[0] = 100;
+ dcbcfg.etscfg.tsatable[0] = ICE_IEEE_TSA_ETS;
+ dcbcfg.etsrec = dcbcfg.etscfg;
+ dcbcfg.etsrec.willing = 0;
+ dcbcfg.pfc.willing = 1;
+ dcbcfg.pfc.pfccap = hw->func_caps.common_cap.maxtc;
+
+ lldpmib = ice_malloc(hw, ICE_LLDPDU_SIZE);
+ if (lldpmib == NULL) {
+ PMD_DRV_LOG(ERR, "Failed to allocate MIB memory");
+ return;
+ }
+
+ ice_dcb_cfg_to_lldp(lldpmib, &miblen, &dcbcfg, ICE_SET_PFC_SYM);
+
+ if (ice_aq_set_lldp_mib(hw, SET_LOCAL_MIB_TYPE_LOCAL_MIB, lldpmib, miblen, NULL))
+ PMD_DRV_LOG(ERR, "Failed to set default LLDP MIB");
+
+ ice_free(hw, lldpmib);
+}
+
+static int
+ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
+{
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ struct ice_port_info *pi = hw->port_info;
+ bool enable_lse = dev->data->dev_conf.intr_conf.lsc ? true : false;
+ struct ice_link_status link_status;
+ int ret;
+
+ /* Refresh the negotiated flow control mode from the link status */
+ ret = ice_get_link_info_safe(pf, enable_lse, &link_status);
+ if (ret != ICE_SUCCESS) {
+ PMD_DRV_LOG(ERR, "Failed to get link info");
+ return -EIO;
+ }
+
+ *fc_conf = pf->fc_conf;
+
+ /* report the link flow control mode currently in effect */
+ switch (pi->fc.current_mode) {
+ case ICE_FC_FULL:
+ fc_conf->mode = RTE_ETH_FC_FULL;
+ break;
+ case ICE_FC_TX_PAUSE:
+ fc_conf->mode = RTE_ETH_FC_TX_PAUSE;
+ break;
+ case ICE_FC_RX_PAUSE:
+ fc_conf->mode = RTE_ETH_FC_RX_PAUSE;
+ break;
+ default:
+ fc_conf->mode = RTE_ETH_FC_NONE;
+ }
+
+ return 0;
+}
+
+/*
+ * Program the Rx buffer watermarks, pause quanta and MAC control frame
+ * forwarding shared by link flow control and priority flow control.
+ * A tc_bitmap of 0 selects link flow control; a single bit selects a PFC
+ * traffic class. Watermark or quanta values of 0 leave the current setting.
+ */
+static int
+ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc, uint8_t tc_bitmap,
+ const struct rte_eth_fc_conf *fc_conf)
+{
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ struct ice_port_info *port_info = hw->port_info;
+ uint32_t high_water = fc_conf->high_water;
+ uint32_t low_water = fc_conf->low_water;
+ uint16_t max_frame_size;
+ int cgd_idx;
+ int ret;
+
+ if (high_water > ICE_MAC_TC_MAX_WATERMARK)
+ high_water = ICE_MAC_TC_MAX_WATERMARK;
+ if (low_water > ICE_MAC_TC_MAX_WATERMARK)
+ low_water = ICE_MAC_TC_MAX_WATERMARK;
+
+ cgd_idx = ice_get_cgd_idx(hw, tc);
+ if (high_water)
+ wr32(hw, GLRPB_TCHW(cgd_idx), high_water);
+ if (low_water)
+ wr32(hw, GLRPB_TCLW(cgd_idx), low_water);
+
+ /* Update pause quanta and refresh threshold */
+ max_frame_size = pf->dev_data->mtu ?
+ pf->dev_data->mtu + ICE_ETH_OVERHEAD :
+ ICE_FRAME_SIZE_MAX;
+ ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, tc_bitmap,
+ fc_conf->pause_time, ((u32)fc_conf->pause_time + 1) / 2,
+ false, NULL);
+ if (ret) {
+ PMD_DRV_LOG(ERR, "Can not update MAC configuration");
+ return ret;
+ }
+
+ /* Update forwarding of the non FC MAC control frames settings */
+ if (hw->mac_type == ICE_MAC_E830) {
+#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
+ E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
+
+ u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
+
+ if (fc_conf->mac_ctrl_frame_fwd)
+ mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
+ else
+ mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
+
+ wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
+ }
+
+ return 0;
+}
+
+static int
+ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
+{
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ struct ice_port_info *pi = hw->port_info;
+ struct ice_dcbx_cfg *dcbx_cfg = &pi->qos_cfg.local_dcbx_cfg;
+ bool link_up;
+ u8 aq_failures;
+ int ret;
+
+ /* Link flow control and priority flow control are mutually exclusive */
+ if (dcbx_cfg->pfc.pfcena != 0) {
+ PMD_DRV_LOG(ERR, "Priority flow control is enabled, cannot set link flow control");
+ return -ENOTSUP;
+ }
+
+ switch (fc_conf->mode) {
+ case RTE_ETH_FC_FULL:
+ pi->fc.req_mode = ICE_FC_FULL;
+ break;
+ case RTE_ETH_FC_TX_PAUSE:
+ pi->fc.req_mode = ICE_FC_TX_PAUSE;
+ break;
+ case RTE_ETH_FC_RX_PAUSE:
+ pi->fc.req_mode = ICE_FC_RX_PAUSE;
+ break;
+ default:
+ pi->fc.req_mode = ICE_FC_NONE;
+ }
+
+ /* ice_set_fc() reprograms the PHY pause config and, if auto link update
+ * is enabled, restarts the link so the new mode is renegotiated. The
+ * default MIB that arms the MAC for LFC is reapplied from the link-up
+ * event handler.
+ */
+ link_up = pi->phy.link_info.link_info & ICE_AQ_LINK_UP;
+ ret = ice_set_fc(pi, &aq_failures, link_up);
+ if (ret != 0 && aq_failures != ICE_SET_FC_AQ_FAIL_UPDATE) {
+ PMD_DRV_LOG(ERR, "Failed to set flow control mode, ret %d aq_failures 0x%x",
+ ret, aq_failures);
+ return -EIO;
+ }
+
+ pf->fc_conf = *fc_conf;
+
+ /* Apply the Rx buffer watermarks and pause quanta for the link flow
+ * control traffic class (LFC rides user priority 0).
+ */
+ return ice_cfg_fc_params(dev, 0, 0, fc_conf);
+}
+
static int
ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf)
{
@@ -4160,49 +4381,10 @@ ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc
return ret;
}
- /* Update high and low watermarks */
- u32 high_watermark = pfc_conf->fc.high_water;
- if (high_watermark > ICE_MAC_TC_MAX_WATERMARK)
- high_watermark = ICE_MAC_TC_MAX_WATERMARK;
-
- u32 low_watermark = pfc_conf->fc.low_water;
- if (low_watermark > ICE_MAC_TC_MAX_WATERMARK)
- low_watermark = ICE_MAC_TC_MAX_WATERMARK;
-
- int cgd_idx = ice_get_cgd_idx(hw, tc);
-
- if (high_watermark)
- wr32(hw, GLRPB_TCHW(cgd_idx), high_watermark);
- if (low_watermark)
- wr32(hw, GLRPB_TCLW(cgd_idx), low_watermark);
-
- /* Update pause quanta */
- uint16_t max_frame_size = pf->dev_data->mtu ?
- pf->dev_data->mtu + ICE_ETH_OVERHEAD :
- ICE_FRAME_SIZE_MAX;
- ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, 1 << tc, pfc_conf->fc.pause_time,
- ((u32)pfc_conf->fc.pause_time + 1) / 2, false, NULL);
- if (ret) {
- PMD_DRV_LOG(ERR, "Can not update MAC configuration");
- return ret;
- }
-
- /* Update forwarding of the non FC MAC control frames settings */
- if ((hw)->mac_type == ICE_MAC_E830) {
-#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
- E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
-
- u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
-
- if (pfc_conf->fc.mac_ctrl_frame_fwd)
- mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
- else
- mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
-
- wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
- }
-
- return 0;
+ /* Apply the Rx buffer watermarks and pause quanta for the PFC
+ * traffic class selected by the user priority.
+ */
+ return ice_cfg_fc_params(dev, tc, 1 << tc, &pfc_conf->fc);
}
static void
@@ -4373,22 +4555,6 @@ ice_rxq_intr_setup(struct rte_eth_dev *dev)
return 0;
}
-static int
-ice_get_link_info_safe(struct ice_pf *pf, bool ena_lse,
- struct ice_link_status *link)
-{
- struct ice_hw *hw = ICE_PF_TO_HW(pf);
- int ret;
-
- rte_spinlock_lock(&pf->link_lock);
-
- ret = ice_aq_get_link_info(hw->port_info, ena_lse, link, NULL);
-
- rte_spinlock_unlock(&pf->link_lock);
-
- return ret;
-}
-
static void
ice_get_init_link_status(struct rte_eth_dev *dev)
{
diff --git a/drivers/net/intel/ice/ice_ethdev.h b/drivers/net/intel/ice/ice_ethdev.h
index 7ee3ea8a70..3143aca74a 100644
--- a/drivers/net/intel/ice/ice_ethdev.h
+++ b/drivers/net/intel/ice/ice_ethdev.h
@@ -613,6 +613,7 @@ struct ice_pf {
* and link status update during dev_start.
*/
rte_spinlock_t link_lock;
+ struct rte_eth_fc_conf fc_conf; /* cached link flow control config */
};
#define ICE_MAX_QUEUE_NUM 2048
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v2] net/ice: support link flow control
2026-09-01 15:12 [PATCH] net/ice: support link flow control Vladimir Medvedkin
@ 2026-09-10 11:25 ` Vladimir Medvedkin
2026-09-16 16:38 ` Bruce Richardson
2026-09-22 18:23 ` [PATCH v3] " Vladimir Medvedkin
0 siblings, 2 replies; 8+ messages in thread
From: Vladimir Medvedkin @ 2026-09-10 11:25 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, anatoly.burakov
Add get and set functions for link flow control (802.3x).
Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
doc/guides/nics/features/ice.ini | 1 +
doc/guides/rel_notes/release_26_11.rst | 4 +
drivers/net/intel/ice/ice_ethdev.c | 271 +++++++++++++++++++------
drivers/net/intel/ice/ice_ethdev.h | 1 +
4 files changed, 217 insertions(+), 60 deletions(-)
diff --git a/doc/guides/nics/features/ice.ini b/doc/guides/nics/features/ice.ini
index 893d09e9ec..309f691f32 100644
--- a/doc/guides/nics/features/ice.ini
+++ b/doc/guides/nics/features/ice.ini
@@ -30,6 +30,7 @@ RSS hash = Y
RSS key update = Y
RSS reta update = Y
VLAN filter = Y
+Flow control = Y
Traffic manager = Y
CRC offload = Y
VLAN offload = Y
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..5c4cd8a6df 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,10 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Updated Intel ice driver.**
+
+ * Added support for getting and setting link (802.3x) flow control.
+
Removed Items
-------------
diff --git a/drivers/net/intel/ice/ice_ethdev.c b/drivers/net/intel/ice/ice_ethdev.c
index 76b8ff0a72..909b3ef963 100644
--- a/drivers/net/intel/ice/ice_ethdev.c
+++ b/drivers/net/intel/ice/ice_ethdev.c
@@ -211,7 +211,12 @@ static int ice_fec_set(struct rte_eth_dev *dev, uint32_t fec_capa);
static const uint32_t *ice_buffer_split_supported_hdr_ptypes_get(struct rte_eth_dev *dev,
size_t *no_of_elements);
static int ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info);
+static void ice_set_dflt_mib(struct rte_eth_dev *dev);
+static int ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
+static int ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
static int ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf);
+static int ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc, uint8_t tc_bitmap,
+ const struct rte_eth_fc_conf *fc_conf);
static const struct rte_pci_id pci_id_ice_map[] = {
{ RTE_PCI_DEVICE(ICE_INTEL_VENDOR_ID, ICE_DEV_ID_E823L_BACKPLANE) },
@@ -352,6 +357,8 @@ static const struct eth_dev_ops ice_eth_dev_ops = {
.fec_set = ice_fec_set,
.buffer_split_supported_hdr_ptypes_get = ice_buffer_split_supported_hdr_ptypes_get,
.get_dcb_info = ice_get_dcb_info,
+ .flow_ctrl_get = ice_flow_ctrl_get,
+ .flow_ctrl_set = ice_flow_ctrl_set,
.priority_flow_ctrl_set = ice_priority_flow_ctrl_set,
};
@@ -1468,9 +1475,16 @@ ice_handle_aq_msg(struct rte_eth_dev *dev)
switch (opcode) {
case ice_aqc_opc_get_link_status:
ret = ice_link_update(dev, 0);
- if (!ret)
+ if (!ret) {
+ /* On link-up, reapply the default single-TC configuration. */
+ if ((hw->port_info->phy.link_info.link_info & ICE_AQ_LINK_UP) &&
+ !(dev->data->dev_conf.rxmode.mq_mode &
+ RTE_ETH_MQ_RX_DCB_FLAG))
+ ice_set_dflt_mib(dev);
+
rte_eth_dev_callback_process
(dev, RTE_ETH_EVENT_INTR_LSC, NULL);
+ }
break;
default:
PMD_DRV_LOG(DEBUG, "Request %u is not supported yet",
@@ -4073,6 +4087,22 @@ ice_dev_configure(struct rte_eth_dev *dev)
return 0;
}
+static int
+ice_get_link_info_safe(struct ice_pf *pf, bool ena_lse,
+ struct ice_link_status *link)
+{
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ int ret;
+
+ rte_spinlock_lock(&pf->link_lock);
+
+ ret = ice_aq_get_link_info(hw->port_info, ena_lse, link, NULL);
+
+ rte_spinlock_unlock(&pf->link_lock);
+
+ return ret;
+}
+
static int
ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info)
{
@@ -4105,6 +4135,182 @@ ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info)
return 0;
}
+/*
+ * Program a default single-TC local LLDP MIB configuration. All user priorities
+ * map to TC0 at 100% bandwidth.
+ */
+static void
+ice_set_dflt_mib(struct rte_eth_dev *dev)
+{
+ struct ice_hw *hw = ICE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+ struct ice_dcbx_cfg dcbcfg = { 0 };
+ uint8_t lldpmib[ICE_LLDPDU_SIZE] = { 0 };
+ uint16_t miblen;
+
+ dcbcfg.etscfg.willing = 1;
+ dcbcfg.etscfg.maxtcs = hw->func_caps.common_cap.maxtc;
+ dcbcfg.etscfg.tcbwtable[0] = 100;
+ dcbcfg.etscfg.tsatable[0] = ICE_IEEE_TSA_ETS;
+ dcbcfg.etsrec = dcbcfg.etscfg;
+ dcbcfg.etsrec.willing = 0;
+ dcbcfg.pfc.willing = 1;
+ dcbcfg.pfc.pfccap = hw->func_caps.common_cap.maxtc;
+
+ ice_dcb_cfg_to_lldp(lldpmib, &miblen, &dcbcfg, ICE_SET_PFC_SYM);
+
+ if (ice_aq_set_lldp_mib(hw, SET_LOCAL_MIB_TYPE_LOCAL_MIB, lldpmib, miblen, NULL))
+ PMD_DRV_LOG(ERR, "Failed to set default LLDP MIB");
+}
+
+static int
+ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
+{
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ struct ice_port_info *pi = hw->port_info;
+ bool enable_lse = dev->data->dev_conf.intr_conf.lsc ? true : false;
+ struct ice_link_status link_status;
+ int ret;
+
+ /* Refresh the negotiated flow control mode from the link status */
+ ret = ice_get_link_info_safe(pf, enable_lse, &link_status);
+ if (ret != ICE_SUCCESS) {
+ PMD_DRV_LOG(ERR, "Failed to get link info");
+ return -EIO;
+ }
+
+ *fc_conf = pf->fc_conf;
+
+ /* report the link flow control mode currently in effect */
+ switch (pi->fc.current_mode) {
+ case ICE_FC_FULL:
+ fc_conf->mode = RTE_ETH_FC_FULL;
+ break;
+ case ICE_FC_TX_PAUSE:
+ fc_conf->mode = RTE_ETH_FC_TX_PAUSE;
+ break;
+ case ICE_FC_RX_PAUSE:
+ fc_conf->mode = RTE_ETH_FC_RX_PAUSE;
+ break;
+ default:
+ fc_conf->mode = RTE_ETH_FC_NONE;
+ }
+
+ return 0;
+}
+
+/*
+ * Program the Rx buffer watermarks, pause quanta and MAC control frame
+ * forwarding shared by link flow control and priority flow control.
+ * A tc_bitmap of 0 selects link flow control; a single bit selects a PFC
+ * traffic class. Watermark or quanta values of 0 leave the current setting.
+ */
+static int
+ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc, uint8_t tc_bitmap,
+ const struct rte_eth_fc_conf *fc_conf)
+{
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ struct ice_port_info *port_info = hw->port_info;
+ uint32_t high_water = fc_conf->high_water;
+ uint32_t low_water = fc_conf->low_water;
+ uint16_t max_frame_size;
+ int cgd_idx;
+ int ret;
+
+ if (high_water > ICE_MAC_TC_MAX_WATERMARK)
+ high_water = ICE_MAC_TC_MAX_WATERMARK;
+ if (low_water > ICE_MAC_TC_MAX_WATERMARK)
+ low_water = ICE_MAC_TC_MAX_WATERMARK;
+
+ cgd_idx = ice_get_cgd_idx(hw, tc);
+ if (high_water)
+ wr32(hw, GLRPB_TCHW(cgd_idx), high_water);
+ if (low_water)
+ wr32(hw, GLRPB_TCLW(cgd_idx), low_water);
+
+ /* Update pause quanta and refresh threshold */
+ max_frame_size = pf->dev_data->mtu ?
+ pf->dev_data->mtu + ICE_ETH_OVERHEAD :
+ ICE_FRAME_SIZE_MAX;
+ ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, tc_bitmap,
+ fc_conf->pause_time, ((u32)fc_conf->pause_time + 1) / 2,
+ false, NULL);
+ if (ret) {
+ PMD_DRV_LOG(ERR, "Can not update MAC configuration");
+ return ret;
+ }
+
+ /* Update forwarding of the non FC MAC control frames settings */
+ if (hw->mac_type == ICE_MAC_E830) {
+#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
+ E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
+
+ u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
+
+ if (fc_conf->mac_ctrl_frame_fwd)
+ mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
+ else
+ mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
+
+ wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
+ }
+
+ return 0;
+}
+
+static int
+ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
+{
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ struct ice_port_info *pi = hw->port_info;
+ bool link_up;
+ u8 aq_failures;
+ int ret;
+
+ /* Link flow control and priority flow control are mutually exclusive */
+ if (dev->data->dev_conf.rxmode.mq_mode & RTE_ETH_MQ_RX_DCB_FLAG) {
+ PMD_DRV_LOG(ERR, "Priority flow control is enabled, cannot set link flow control");
+ return -ENOTSUP;
+ }
+
+ switch (fc_conf->mode) {
+ case RTE_ETH_FC_FULL:
+ pi->fc.req_mode = ICE_FC_FULL;
+ break;
+ case RTE_ETH_FC_TX_PAUSE:
+ pi->fc.req_mode = ICE_FC_TX_PAUSE;
+ break;
+ case RTE_ETH_FC_RX_PAUSE:
+ pi->fc.req_mode = ICE_FC_RX_PAUSE;
+ break;
+ default:
+ pi->fc.req_mode = ICE_FC_NONE;
+ }
+
+ /* ice_set_fc() reprograms the PHY pause config and, if auto link update
+ * is enabled, restarts the link so the new mode is renegotiated. The
+ * default MIB that arms the MAC for LFC is reapplied from the link-up
+ * event handler.
+ */
+ link_up = pi->phy.link_info.link_info & ICE_AQ_LINK_UP;
+
+ ret = ice_set_fc(pi, &aq_failures, link_up);
+ if (ret != 0 && aq_failures != ICE_SET_FC_AQ_FAIL_UPDATE) {
+ PMD_DRV_LOG(ERR, "Failed to set flow control mode, ret %d aq_failures 0x%x",
+ ret, aq_failures);
+ return -EIO;
+ }
+
+ pf->fc_conf = *fc_conf;
+
+ /* Apply the Rx buffer watermarks and pause quanta for the link flow
+ * control traffic class (LFC rides user priority 0).
+ */
+ return ice_cfg_fc_params(dev, 0, 0, fc_conf);
+}
+
static int
ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf)
{
@@ -4160,49 +4366,10 @@ ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc
return ret;
}
- /* Update high and low watermarks */
- u32 high_watermark = pfc_conf->fc.high_water;
- if (high_watermark > ICE_MAC_TC_MAX_WATERMARK)
- high_watermark = ICE_MAC_TC_MAX_WATERMARK;
-
- u32 low_watermark = pfc_conf->fc.low_water;
- if (low_watermark > ICE_MAC_TC_MAX_WATERMARK)
- low_watermark = ICE_MAC_TC_MAX_WATERMARK;
-
- int cgd_idx = ice_get_cgd_idx(hw, tc);
-
- if (high_watermark)
- wr32(hw, GLRPB_TCHW(cgd_idx), high_watermark);
- if (low_watermark)
- wr32(hw, GLRPB_TCLW(cgd_idx), low_watermark);
-
- /* Update pause quanta */
- uint16_t max_frame_size = pf->dev_data->mtu ?
- pf->dev_data->mtu + ICE_ETH_OVERHEAD :
- ICE_FRAME_SIZE_MAX;
- ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, 1 << tc, pfc_conf->fc.pause_time,
- ((u32)pfc_conf->fc.pause_time + 1) / 2, false, NULL);
- if (ret) {
- PMD_DRV_LOG(ERR, "Can not update MAC configuration");
- return ret;
- }
-
- /* Update forwarding of the non FC MAC control frames settings */
- if ((hw)->mac_type == ICE_MAC_E830) {
-#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
- E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
-
- u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
-
- if (pfc_conf->fc.mac_ctrl_frame_fwd)
- mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
- else
- mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
-
- wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
- }
-
- return 0;
+ /* Apply the Rx buffer watermarks and pause quanta for the PFC
+ * traffic class selected by the user priority.
+ */
+ return ice_cfg_fc_params(dev, tc, 1 << tc, &pfc_conf->fc);
}
static void
@@ -4373,22 +4540,6 @@ ice_rxq_intr_setup(struct rte_eth_dev *dev)
return 0;
}
-static int
-ice_get_link_info_safe(struct ice_pf *pf, bool ena_lse,
- struct ice_link_status *link)
-{
- struct ice_hw *hw = ICE_PF_TO_HW(pf);
- int ret;
-
- rte_spinlock_lock(&pf->link_lock);
-
- ret = ice_aq_get_link_info(hw->port_info, ena_lse, link, NULL);
-
- rte_spinlock_unlock(&pf->link_lock);
-
- return ret;
-}
-
static void
ice_get_init_link_status(struct rte_eth_dev *dev)
{
diff --git a/drivers/net/intel/ice/ice_ethdev.h b/drivers/net/intel/ice/ice_ethdev.h
index 7ee3ea8a70..3143aca74a 100644
--- a/drivers/net/intel/ice/ice_ethdev.h
+++ b/drivers/net/intel/ice/ice_ethdev.h
@@ -613,6 +613,7 @@ struct ice_pf {
* and link status update during dev_start.
*/
rte_spinlock_t link_lock;
+ struct rte_eth_fc_conf fc_conf; /* cached link flow control config */
};
#define ICE_MAX_QUEUE_NUM 2048
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2] net/ice: support link flow control
2026-09-10 11:25 ` [PATCH v2] " Vladimir Medvedkin
@ 2026-09-16 16:38 ` Bruce Richardson
2026-09-22 19:41 ` Medvedkin, Vladimir
2026-09-22 18:23 ` [PATCH v3] " Vladimir Medvedkin
1 sibling, 1 reply; 8+ messages in thread
From: Bruce Richardson @ 2026-09-16 16:38 UTC (permalink / raw)
To: Vladimir Medvedkin; +Cc: dev, anatoly.burakov
On Thu, Sep 10, 2026 at 11:25:24AM +0000, Vladimir Medvedkin wrote:
> Add get and set functions for link flow control (802.3x).
>
> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> ---
A few minor comments inline below.
/Bruce
> doc/guides/nics/features/ice.ini | 1 +
> doc/guides/rel_notes/release_26_11.rst | 4 +
> drivers/net/intel/ice/ice_ethdev.c | 271 +++++++++++++++++++------
> drivers/net/intel/ice/ice_ethdev.h | 1 +
> 4 files changed, 217 insertions(+), 60 deletions(-)
>
> diff --git a/doc/guides/nics/features/ice.ini b/doc/guides/nics/features/ice.ini
> index 893d09e9ec..309f691f32 100644
> --- a/doc/guides/nics/features/ice.ini
> +++ b/doc/guides/nics/features/ice.ini
> @@ -30,6 +30,7 @@ RSS hash = Y
> RSS key update = Y
> RSS reta update = Y
> VLAN filter = Y
> +Flow control = Y
> Traffic manager = Y
> CRC offload = Y
> VLAN offload = Y
> diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
> index c8cc86295d..5c4cd8a6df 100644
> --- a/doc/guides/rel_notes/release_26_11.rst
> +++ b/doc/guides/rel_notes/release_26_11.rst
> @@ -55,6 +55,10 @@ New Features
> Also, make sure to start the actual text at the margin.
> =======================================================
>
> +* **Updated Intel ice driver.**
> +
> + * Added support for getting and setting link (802.3x) flow control.
> +
>
> Removed Items
> -------------
> diff --git a/drivers/net/intel/ice/ice_ethdev.c b/drivers/net/intel/ice/ice_ethdev.c
> index 76b8ff0a72..909b3ef963 100644
> --- a/drivers/net/intel/ice/ice_ethdev.c
> +++ b/drivers/net/intel/ice/ice_ethdev.c
> @@ -211,7 +211,12 @@ static int ice_fec_set(struct rte_eth_dev *dev, uint32_t fec_capa);
> static const uint32_t *ice_buffer_split_supported_hdr_ptypes_get(struct rte_eth_dev *dev,
> size_t *no_of_elements);
> static int ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info);
> +static void ice_set_dflt_mib(struct rte_eth_dev *dev);
> +static int ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
> +static int ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
> static int ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf);
> +static int ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc, uint8_t tc_bitmap,
> + const struct rte_eth_fc_conf *fc_conf);
>
> static const struct rte_pci_id pci_id_ice_map[] = {
> { RTE_PCI_DEVICE(ICE_INTEL_VENDOR_ID, ICE_DEV_ID_E823L_BACKPLANE) },
> @@ -352,6 +357,8 @@ static const struct eth_dev_ops ice_eth_dev_ops = {
> .fec_set = ice_fec_set,
> .buffer_split_supported_hdr_ptypes_get = ice_buffer_split_supported_hdr_ptypes_get,
> .get_dcb_info = ice_get_dcb_info,
> + .flow_ctrl_get = ice_flow_ctrl_get,
> + .flow_ctrl_set = ice_flow_ctrl_set,
> .priority_flow_ctrl_set = ice_priority_flow_ctrl_set,
> };
>
> @@ -1468,9 +1475,16 @@ ice_handle_aq_msg(struct rte_eth_dev *dev)
> switch (opcode) {
> case ice_aqc_opc_get_link_status:
> ret = ice_link_update(dev, 0);
> - if (!ret)
> + if (!ret) {
> + /* On link-up, reapply the default single-TC configuration. */
> + if ((hw->port_info->phy.link_info.link_info & ICE_AQ_LINK_UP) &&
> + !(dev->data->dev_conf.rxmode.mq_mode &
> + RTE_ETH_MQ_RX_DCB_FLAG))
> + ice_set_dflt_mib(dev);
> +
> rte_eth_dev_callback_process
> (dev, RTE_ETH_EVENT_INTR_LSC, NULL);
> + }
> break;
> default:
> PMD_DRV_LOG(DEBUG, "Request %u is not supported yet",
> @@ -4073,6 +4087,22 @@ ice_dev_configure(struct rte_eth_dev *dev)
> return 0;
> }
>
> +static int
> +ice_get_link_info_safe(struct ice_pf *pf, bool ena_lse,
> + struct ice_link_status *link)
> +{
> + struct ice_hw *hw = ICE_PF_TO_HW(pf);
> + int ret;
> +
> + rte_spinlock_lock(&pf->link_lock);
> +
> + ret = ice_aq_get_link_info(hw->port_info, ena_lse, link, NULL);
> +
> + rte_spinlock_unlock(&pf->link_lock);
> +
> + return ret;
> +}
> +
Rather than moving this function up, why not just put a prototype
declaration for it at the top of the file with all the other static
functions? It would make the diff smaller.
> static int
> ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info)
> {
> @@ -4105,6 +4135,182 @@ ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info)
> return 0;
> }
>
> +/*
> + * Program a default single-TC local LLDP MIB configuration. All user priorities
> + * map to TC0 at 100% bandwidth.
> + */
> +static void
> +ice_set_dflt_mib(struct rte_eth_dev *dev)
> +{
> + struct ice_hw *hw = ICE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
> + struct ice_dcbx_cfg dcbcfg = { 0 };
> + uint8_t lldpmib[ICE_LLDPDU_SIZE] = { 0 };
> + uint16_t miblen;
> +
> + dcbcfg.etscfg.willing = 1;
> + dcbcfg.etscfg.maxtcs = hw->func_caps.common_cap.maxtc;
> + dcbcfg.etscfg.tcbwtable[0] = 100;
> + dcbcfg.etscfg.tsatable[0] = ICE_IEEE_TSA_ETS;
> + dcbcfg.etsrec = dcbcfg.etscfg;
> + dcbcfg.etsrec.willing = 0;
> + dcbcfg.pfc.willing = 1;
> + dcbcfg.pfc.pfccap = hw->func_caps.common_cap.maxtc;
> +
> + ice_dcb_cfg_to_lldp(lldpmib, &miblen, &dcbcfg, ICE_SET_PFC_SYM);
> +
> + if (ice_aq_set_lldp_mib(hw, SET_LOCAL_MIB_TYPE_LOCAL_MIB, lldpmib, miblen, NULL))
> + PMD_DRV_LOG(ERR, "Failed to set default LLDP MIB");
> +}
> +
> +static int
> +ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
> +{
> + struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
> + struct ice_hw *hw = ICE_PF_TO_HW(pf);
> + struct ice_port_info *pi = hw->port_info;
> + bool enable_lse = dev->data->dev_conf.intr_conf.lsc ? true : false;
> + struct ice_link_status link_status;
> + int ret;
> +
> + /* Refresh the negotiated flow control mode from the link status */
> + ret = ice_get_link_info_safe(pf, enable_lse, &link_status);
> + if (ret != ICE_SUCCESS) {
> + PMD_DRV_LOG(ERR, "Failed to get link info");
> + return -EIO;
> + }
> +
> + *fc_conf = pf->fc_conf;
> +
> + /* report the link flow control mode currently in effect */
> + switch (pi->fc.current_mode) {
> + case ICE_FC_FULL:
> + fc_conf->mode = RTE_ETH_FC_FULL;
> + break;
> + case ICE_FC_TX_PAUSE:
> + fc_conf->mode = RTE_ETH_FC_TX_PAUSE;
> + break;
> + case ICE_FC_RX_PAUSE:
> + fc_conf->mode = RTE_ETH_FC_RX_PAUSE;
> + break;
> + default:
> + fc_conf->mode = RTE_ETH_FC_NONE;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Program the Rx buffer watermarks, pause quanta and MAC control frame
> + * forwarding shared by link flow control and priority flow control.
> + * A tc_bitmap of 0 selects link flow control; a single bit selects a PFC
> + * traffic class. Watermark or quanta values of 0 leave the current setting.
> + */
> +static int
> +ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc, uint8_t tc_bitmap,
> + const struct rte_eth_fc_conf *fc_conf)
Minor nit:
Looking at how this is used, I don't think you need both a tc_bitmap and a
tc parameter, since tc_bitmap is passed either as 0, or 1 << tc, so
therefore the tc is computable from the bitmap.
tc = tc_bitmap == 0 ? 0 : rte_ctz32(tc_bitmap);
> +{
> + struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
> + struct ice_hw *hw = ICE_PF_TO_HW(pf);
> + struct ice_port_info *port_info = hw->port_info;
> + uint32_t high_water = fc_conf->high_water;
> + uint32_t low_water = fc_conf->low_water;
> + uint16_t max_frame_size;
> + int cgd_idx;
> + int ret;
> +
> + if (high_water > ICE_MAC_TC_MAX_WATERMARK)
> + high_water = ICE_MAC_TC_MAX_WATERMARK;
> + if (low_water > ICE_MAC_TC_MAX_WATERMARK)
> + low_water = ICE_MAC_TC_MAX_WATERMARK;
> +
> + cgd_idx = ice_get_cgd_idx(hw, tc);
> + if (high_water)
> + wr32(hw, GLRPB_TCHW(cgd_idx), high_water);
> + if (low_water)
> + wr32(hw, GLRPB_TCLW(cgd_idx), low_water);
> +
> + /* Update pause quanta and refresh threshold */
> + max_frame_size = pf->dev_data->mtu ?
> + pf->dev_data->mtu + ICE_ETH_OVERHEAD :
> + ICE_FRAME_SIZE_MAX;
> + ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, tc_bitmap,
> + fc_conf->pause_time, ((u32)fc_conf->pause_time + 1) / 2,
> + false, NULL);
> + if (ret) {
> + PMD_DRV_LOG(ERR, "Can not update MAC configuration");
> + return ret;
> + }
> +
> + /* Update forwarding of the non FC MAC control frames settings */
> + if (hw->mac_type == ICE_MAC_E830) {
> +#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
> + E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
> +
> + u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
> +
> + if (fc_conf->mac_ctrl_frame_fwd)
> + mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
> + else
> + mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
> +
> + wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
> + }
> +
> + return 0;
> +}
> +
> +static int
> +ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
> +{
> + struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
> + struct ice_hw *hw = ICE_PF_TO_HW(pf);
> + struct ice_port_info *pi = hw->port_info;
> + bool link_up;
> + u8 aq_failures;
> + int ret;
> +
> + /* Link flow control and priority flow control are mutually exclusive */
> + if (dev->data->dev_conf.rxmode.mq_mode & RTE_ETH_MQ_RX_DCB_FLAG) {
> + PMD_DRV_LOG(ERR, "Priority flow control is enabled, cannot set link flow control");
> + return -ENOTSUP;
> + }
> +
> + switch (fc_conf->mode) {
> + case RTE_ETH_FC_FULL:
> + pi->fc.req_mode = ICE_FC_FULL;
> + break;
> + case RTE_ETH_FC_TX_PAUSE:
> + pi->fc.req_mode = ICE_FC_TX_PAUSE;
> + break;
> + case RTE_ETH_FC_RX_PAUSE:
> + pi->fc.req_mode = ICE_FC_RX_PAUSE;
> + break;
> + default:
> + pi->fc.req_mode = ICE_FC_NONE;
> + }
> +
> + /* ice_set_fc() reprograms the PHY pause config and, if auto link update
> + * is enabled, restarts the link so the new mode is renegotiated. The
> + * default MIB that arms the MAC for LFC is reapplied from the link-up
> + * event handler.
> + */
> + link_up = pi->phy.link_info.link_info & ICE_AQ_LINK_UP;
Is it safe to read this outside the lock? For safety should this be a call
to ice_get_link_info_safe? AI flags that all other instances in this file
use the function.
> +
> + ret = ice_set_fc(pi, &aq_failures, link_up);
> + if (ret != 0 && aq_failures != ICE_SET_FC_AQ_FAIL_UPDATE) {
> + PMD_DRV_LOG(ERR, "Failed to set flow control mode, ret %d aq_failures 0x%x",
> + ret, aq_failures);
> + return -EIO;
> + }
> +
> + pf->fc_conf = *fc_conf;
> +
> + /* Apply the Rx buffer watermarks and pause quanta for the link flow
> + * control traffic class (LFC rides user priority 0).
> + */
> + return ice_cfg_fc_params(dev, 0, 0, fc_conf);
> +}
AI review flags a potential issue here. What happens to pf->fc_conf if the
application fails? The applied values will not match the stored ones.
> +
> static int
> ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf)
> {
> @@ -4160,49 +4366,10 @@ ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc
> return ret;
> }
>
> - /* Update high and low watermarks */
> - u32 high_watermark = pfc_conf->fc.high_water;
> - if (high_watermark > ICE_MAC_TC_MAX_WATERMARK)
> - high_watermark = ICE_MAC_TC_MAX_WATERMARK;
> -
> - u32 low_watermark = pfc_conf->fc.low_water;
> - if (low_watermark > ICE_MAC_TC_MAX_WATERMARK)
> - low_watermark = ICE_MAC_TC_MAX_WATERMARK;
> -
> - int cgd_idx = ice_get_cgd_idx(hw, tc);
> -
> - if (high_watermark)
> - wr32(hw, GLRPB_TCHW(cgd_idx), high_watermark);
> - if (low_watermark)
> - wr32(hw, GLRPB_TCLW(cgd_idx), low_watermark);
> -
> - /* Update pause quanta */
> - uint16_t max_frame_size = pf->dev_data->mtu ?
> - pf->dev_data->mtu + ICE_ETH_OVERHEAD :
> - ICE_FRAME_SIZE_MAX;
> - ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, 1 << tc, pfc_conf->fc.pause_time,
> - ((u32)pfc_conf->fc.pause_time + 1) / 2, false, NULL);
> - if (ret) {
> - PMD_DRV_LOG(ERR, "Can not update MAC configuration");
> - return ret;
> - }
> -
> - /* Update forwarding of the non FC MAC control frames settings */
> - if ((hw)->mac_type == ICE_MAC_E830) {
> -#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
> - E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
> -
> - u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
> -
> - if (pfc_conf->fc.mac_ctrl_frame_fwd)
> - mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
> - else
> - mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
> -
> - wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
> - }
> -
> - return 0;
> + /* Apply the Rx buffer watermarks and pause quanta for the PFC
> + * traffic class selected by the user priority.
> + */
> + return ice_cfg_fc_params(dev, tc, 1 << tc, &pfc_conf->fc);
> }
>
> static void
> @@ -4373,22 +4540,6 @@ ice_rxq_intr_setup(struct rte_eth_dev *dev)
> return 0;
> }
>
> -static int
> -ice_get_link_info_safe(struct ice_pf *pf, bool ena_lse,
> - struct ice_link_status *link)
> -{
> - struct ice_hw *hw = ICE_PF_TO_HW(pf);
> - int ret;
> -
> - rte_spinlock_lock(&pf->link_lock);
> -
> - ret = ice_aq_get_link_info(hw->port_info, ena_lse, link, NULL);
> -
> - rte_spinlock_unlock(&pf->link_lock);
> -
> - return ret;
> -}
> -
> static void
> ice_get_init_link_status(struct rte_eth_dev *dev)
> {
> diff --git a/drivers/net/intel/ice/ice_ethdev.h b/drivers/net/intel/ice/ice_ethdev.h
> index 7ee3ea8a70..3143aca74a 100644
> --- a/drivers/net/intel/ice/ice_ethdev.h
> +++ b/drivers/net/intel/ice/ice_ethdev.h
> @@ -613,6 +613,7 @@ struct ice_pf {
> * and link status update during dev_start.
> */
> rte_spinlock_t link_lock;
> + struct rte_eth_fc_conf fc_conf; /* cached link flow control config */
> };
>
> #define ICE_MAX_QUEUE_NUM 2048
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v3] net/ice: support link flow control
2026-09-10 11:25 ` [PATCH v2] " Vladimir Medvedkin
2026-09-16 16:38 ` Bruce Richardson
@ 2026-09-22 18:23 ` Vladimir Medvedkin
2026-09-25 15:02 ` Bruce Richardson
1 sibling, 1 reply; 8+ messages in thread
From: Vladimir Medvedkin @ 2026-09-22 18:23 UTC (permalink / raw)
To: dev; +Cc: bruce.richardson, anatoly.burakov
Add get and set operations for IEEE 802.3x link flow control.
Link pause is advertised only when requested, retained across port
stop/start and re-armed on link-up. The watermark and pause quanta
programming is shared with priority flow control.
Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
v3:
- Address Bruce's comments.
- Add documentation.
- Retain LFC configuration across port stop/start.
- Fix various issues.
doc/guides/nics/features/ice.ini | 1 +
doc/guides/nics/ice.rst | 50 +++++
doc/guides/rel_notes/release_26_11.rst | 4 +
drivers/net/intel/ice/ice_ethdev.c | 296 +++++++++++++++++++++----
drivers/net/intel/ice/ice_ethdev.h | 1 +
5 files changed, 310 insertions(+), 42 deletions(-)
diff --git a/doc/guides/nics/features/ice.ini b/doc/guides/nics/features/ice.ini
index 893d09e9ec..309f691f32 100644
--- a/doc/guides/nics/features/ice.ini
+++ b/doc/guides/nics/features/ice.ini
@@ -30,6 +30,7 @@ RSS hash = Y
RSS key update = Y
RSS reta update = Y
VLAN filter = Y
+Flow control = Y
Traffic manager = Y
CRC offload = Y
VLAN offload = Y
diff --git a/doc/guides/nics/ice.rst b/doc/guides/nics/ice.rst
index b671422ad7..065038a0d1 100644
--- a/doc/guides/nics/ice.rst
+++ b/doc/guides/nics/ice.rst
@@ -516,6 +516,56 @@ Example PFC configuration using DPDK API:
ret = rte_eth_dev_priority_flow_ctrl_set(port_id, &pfc_conf);
+Link Flow Control (LFC)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The ice PMD supports IEEE 802.3x link flow control through
+``rte_eth_dev_flow_ctrl_set()`` and ``rte_eth_dev_flow_ctrl_get()``.
+
+The requested mode (``RTE_ETH_FC_NONE``, ``RTE_ETH_FC_RX_PAUSE``,
+``RTE_ETH_FC_TX_PAUSE`` or ``RTE_ETH_FC_FULL``) is advertised to the link
+partner and takes effect once the link has been renegotiated.
+Pause frames are only exchanged when the link partner agrees to them,
+so ``rte_eth_dev_flow_ctrl_get()`` reports the negotiated mode,
+which may differ from the requested one.
+Link flow control is disabled (``RTE_ETH_FC_NONE`` advertised) until a mode is requested.
+The request is retained across ``rte_eth_dev_stop()`` and ``rte_eth_dev_start()``.
+
+Link flow control and priority flow control are mutually exclusive:
+``rte_eth_dev_flow_ctrl_set()`` fails with ``-ENOTSUP`` on a port configured
+with ``RTE_ETH_MQ_RX_DCB_FLAG``.
+
+The single traffic class configuration that arms the MAC for link flow control
+is programmed when the link comes up, from the link status change interrupt.
+Link status change interrupts (``intr_conf.lsc``) must therefore be enabled
+for link flow control to operate.
+
+Fields of ``struct rte_eth_fc_conf``:
+
+high_water, low_water
+ Rx buffer watermarks, in bytes, of the traffic class carrying link flow control.
+ A value of 0 keeps the current setting.
+
+pause_time
+ Pause quanta (in 512 bit-time units) carried in the transmitted pause frames.
+ A value of 0 keeps the current setting.
+
+mac_ctrl_frame_fwd
+ Forwarding of MAC control frames other than pause frames.
+
+autoneg, send_xon
+ Not used.
+
+Example configuration in testpmd:
+
+.. code-block:: console
+
+ set flow_ctrl rx on tx on 100000 50000 65535 0 mac_ctrl_frame_fwd off autoneg off 0
+
+This requests symmetric link flow control on port 0 with a high watermark of 100000 bytes,
+a low watermark of 50000 bytes and a pause time of 65535.
+
+
Forward Error Correction (FEC)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..5c4cd8a6df 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,10 @@ New Features
Also, make sure to start the actual text at the margin.
=======================================================
+* **Updated Intel ice driver.**
+
+ * Added support for getting and setting link (802.3x) flow control.
+
Removed Items
-------------
diff --git a/drivers/net/intel/ice/ice_ethdev.c b/drivers/net/intel/ice/ice_ethdev.c
index 76b8ff0a72..2847b5269a 100644
--- a/drivers/net/intel/ice/ice_ethdev.c
+++ b/drivers/net/intel/ice/ice_ethdev.c
@@ -211,7 +211,12 @@ static int ice_fec_set(struct rte_eth_dev *dev, uint32_t fec_capa);
static const uint32_t *ice_buffer_split_supported_hdr_ptypes_get(struct rte_eth_dev *dev,
size_t *no_of_elements);
static int ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info);
+static void ice_set_dflt_mib(struct rte_eth_dev *dev);
+static int ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
+static int ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
static int ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf);
+static int ice_get_link_info_safe(struct ice_pf *pf, bool ena_lse,
+ struct ice_link_status *link);
static const struct rte_pci_id pci_id_ice_map[] = {
{ RTE_PCI_DEVICE(ICE_INTEL_VENDOR_ID, ICE_DEV_ID_E823L_BACKPLANE) },
@@ -352,6 +357,8 @@ static const struct eth_dev_ops ice_eth_dev_ops = {
.fec_set = ice_fec_set,
.buffer_split_supported_hdr_ptypes_get = ice_buffer_split_supported_hdr_ptypes_get,
.get_dcb_info = ice_get_dcb_info,
+ .flow_ctrl_get = ice_flow_ctrl_get,
+ .flow_ctrl_set = ice_flow_ctrl_set,
.priority_flow_ctrl_set = ice_priority_flow_ctrl_set,
};
@@ -1468,9 +1475,16 @@ ice_handle_aq_msg(struct rte_eth_dev *dev)
switch (opcode) {
case ice_aqc_opc_get_link_status:
ret = ice_link_update(dev, 0);
- if (!ret)
+ if (!ret) {
+ /* On link-up, reapply the default single-TC configuration. */
+ if ((hw->port_info->phy.link_info.link_info & ICE_AQ_LINK_UP) &&
+ !(dev->data->dev_conf.rxmode.mq_mode &
+ RTE_ETH_MQ_RX_DCB_FLAG))
+ ice_set_dflt_mib(dev);
+
rte_eth_dev_callback_process
(dev, RTE_ETH_EVENT_INTR_LSC, NULL);
+ }
break;
default:
PMD_DRV_LOG(DEBUG, "Request %u is not supported yet",
@@ -4105,6 +4119,233 @@ ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info)
return 0;
}
+/*
+ * Program a default single-TC local LLDP MIB configuration. All user priorities
+ * map to TC0 at 100% bandwidth.
+ */
+static void
+ice_set_dflt_mib(struct rte_eth_dev *dev)
+{
+ struct ice_hw *hw = ICE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
+ struct ice_dcbx_cfg dcbcfg = { 0 };
+ uint8_t lldpmib[ICE_LLDPDU_SIZE] = { 0 };
+ uint16_t miblen;
+
+ dcbcfg.etscfg.willing = 1;
+ dcbcfg.etscfg.maxtcs = hw->func_caps.common_cap.maxtc;
+ dcbcfg.etscfg.tcbwtable[0] = 100;
+ dcbcfg.etscfg.tsatable[0] = ICE_IEEE_TSA_ETS;
+ dcbcfg.etsrec = dcbcfg.etscfg;
+ dcbcfg.etsrec.willing = 0;
+ dcbcfg.pfc.willing = 1;
+ dcbcfg.pfc.pfccap = hw->func_caps.common_cap.maxtc;
+
+ ice_dcb_cfg_to_lldp(lldpmib, &miblen, &dcbcfg, ICE_SET_PFC_SYM);
+
+ if (ice_aq_set_lldp_mib(hw, SET_LOCAL_MIB_TYPE_LOCAL_MIB, lldpmib, miblen, NULL))
+ PMD_DRV_LOG(ERR, "Failed to set default LLDP MIB");
+}
+
+/* PHY pause advertisement bits for a link flow control mode */
+static uint8_t
+ice_fc_mode_to_pause_caps(enum rte_eth_fc_mode mode)
+{
+ switch (mode) {
+ case RTE_ETH_FC_FULL:
+ return ICE_AQC_PHY_EN_TX_LINK_PAUSE | ICE_AQC_PHY_EN_RX_LINK_PAUSE;
+ case RTE_ETH_FC_TX_PAUSE:
+ return ICE_AQC_PHY_EN_TX_LINK_PAUSE;
+ case RTE_ETH_FC_RX_PAUSE:
+ return ICE_AQC_PHY_EN_RX_LINK_PAUSE;
+ default:
+ return 0;
+ }
+}
+
+static int
+ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
+{
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+ bool enable_lse = dev->data->dev_conf.intr_conf.lsc ? true : false;
+ struct ice_link_status link_status;
+ bool tx_pause, rx_pause;
+ int ret;
+
+ ret = ice_get_link_info_safe(pf, enable_lse, &link_status);
+ if (ret != ICE_SUCCESS) {
+ PMD_DRV_LOG(ERR, "Failed to get link info");
+ return -EIO;
+ }
+
+ /* Last applied request; autoneg and send_xon are never set as they are
+ * not programmed. The mode is the one negotiated with the link partner,
+ * taken from the snapshot rather than the shared port state which the
+ * link event handler updates concurrently.
+ */
+ *fc_conf = pf->fc_conf;
+
+ tx_pause = (link_status.an_info & ICE_AQ_LINK_PAUSE_TX) != 0;
+ rx_pause = (link_status.an_info & ICE_AQ_LINK_PAUSE_RX) != 0;
+ if (tx_pause && rx_pause)
+ fc_conf->mode = RTE_ETH_FC_FULL;
+ else if (tx_pause)
+ fc_conf->mode = RTE_ETH_FC_TX_PAUSE;
+ else if (rx_pause)
+ fc_conf->mode = RTE_ETH_FC_RX_PAUSE;
+ else
+ fc_conf->mode = RTE_ETH_FC_NONE;
+
+ return 0;
+}
+
+/*
+ * Program the Rx buffer watermarks, pause quanta and MAC control frame
+ * forwarding shared by link flow control and priority flow control.
+ * A tc_bitmap of 0 selects link flow control; a single bit selects a PFC
+ * traffic class. Watermark or quanta values of 0 leave the current setting.
+ * The only step that can fail is issued first so that on error nothing
+ * has been changed.
+ */
+static int
+ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc_bitmap,
+ struct rte_eth_fc_conf *fc_conf)
+{
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ struct ice_port_info *port_info = hw->port_info;
+ uint8_t tc = tc_bitmap == 0 ? 0 : rte_ctz32(tc_bitmap);
+ uint32_t high_water = fc_conf->high_water;
+ uint32_t low_water = fc_conf->low_water;
+ uint16_t max_frame_size;
+ int cgd_idx;
+ int ret;
+
+ if (high_water > ICE_MAC_TC_MAX_WATERMARK)
+ high_water = ICE_MAC_TC_MAX_WATERMARK;
+ if (low_water > ICE_MAC_TC_MAX_WATERMARK)
+ low_water = ICE_MAC_TC_MAX_WATERMARK;
+
+ fc_conf->high_water = high_water;
+ fc_conf->low_water = low_water;
+
+ cgd_idx = ice_get_cgd_idx(hw, tc);
+ if (high_water)
+ wr32(hw, GLRPB_TCHW(cgd_idx), high_water);
+ if (low_water)
+ wr32(hw, GLRPB_TCLW(cgd_idx), low_water);
+
+ /* Update forwarding of the non FC MAC control frames settings */
+ if (hw->mac_type == ICE_MAC_E830) {
+#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
+ E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
+
+ u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
+
+ if (fc_conf->mac_ctrl_frame_fwd)
+ mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
+ else
+ mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
+
+ wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
+ } else {
+ fc_conf->mac_ctrl_frame_fwd = false;
+ }
+
+ /* Update pause quanta and refresh threshold */
+ max_frame_size = pf->dev_data->mtu ?
+ pf->dev_data->mtu + ICE_ETH_OVERHEAD :
+ ICE_FRAME_SIZE_MAX;
+ ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, tc_bitmap,
+ fc_conf->pause_time, ((u32)fc_conf->pause_time + 1) / 2,
+ false, NULL);
+ if (ret)
+ PMD_DRV_LOG(ERR, "Can not update MAC configuration");
+
+ return ret;
+}
+
+static int
+ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
+{
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
+ struct ice_hw *hw = ICE_PF_TO_HW(pf);
+ struct ice_port_info *pi = hw->port_info;
+ bool enable_lse = dev->data->dev_conf.intr_conf.lsc ? true : false;
+ struct ice_link_status link_status;
+ enum ice_fc_mode req_mode;
+ struct rte_eth_fc_conf tmp_fc_conf = { 0 };
+ bool link_up;
+ u8 aq_failures;
+ int ret;
+
+ /* Link flow control and priority flow control are mutually exclusive */
+ if (dev->data->dev_conf.rxmode.mq_mode & RTE_ETH_MQ_RX_DCB_FLAG) {
+ PMD_DRV_LOG(ERR, "Priority flow control is enabled, cannot set link flow control");
+ return -ENOTSUP;
+ }
+
+ ret = ice_get_link_info_safe(pf, enable_lse, &link_status);
+ if (ret != ICE_SUCCESS) {
+ PMD_DRV_LOG(ERR, "Failed to get link info");
+ return -EIO;
+ }
+ link_up = link_status.link_info & ICE_AQ_LINK_UP;
+
+ switch (fc_conf->mode) {
+ case RTE_ETH_FC_NONE:
+ req_mode = ICE_FC_NONE;
+ break;
+ case RTE_ETH_FC_RX_PAUSE:
+ req_mode = ICE_FC_RX_PAUSE;
+ break;
+ case RTE_ETH_FC_TX_PAUSE:
+ req_mode = ICE_FC_TX_PAUSE;
+ break;
+ case RTE_ETH_FC_FULL:
+ req_mode = ICE_FC_FULL;
+ break;
+ default:
+ PMD_DRV_LOG(ERR, "Invalid flow control mode %d", fc_conf->mode);
+ return -EINVAL;
+ }
+
+ /* ice_set_fc() reprograms pause config and, if auto link update is
+ * enabled, restarts the link so the new mode is renegotiated. The
+ * default MIB that configures the pipeline for LFC is reapplied from
+ * the link-up event handler.
+ */
+ pi->fc.req_mode = req_mode;
+ ret = ice_set_fc(pi, &aq_failures, link_up);
+ if (ret != 0 && aq_failures != ICE_SET_FC_AQ_FAIL_UPDATE) {
+ PMD_DRV_LOG(ERR, "Failed to set flow control mode, ret %d aq_failures 0x%x",
+ ret, aq_failures);
+ return -EIO;
+ }
+
+ pf->fc_conf.mode = fc_conf->mode;
+
+ /* Apply the Rx buffer watermarks and pause quanta. If ice_cfg_fc_params
+ * fails, leaves pause quanta at 0 (keep the current value), since this
+ * is the only place where it may fail. Update watermarks
+ * unconditionally.
+ */
+ tmp_fc_conf = *fc_conf;
+ ret = ice_cfg_fc_params(dev, 0, &tmp_fc_conf);
+ if (ret != 0) {
+ PMD_DRV_LOG(WARNING, "Flow control mode applied, pause parameters left unchanged");
+ } else {
+ if (tmp_fc_conf.pause_time)
+ pf->fc_conf.pause_time = tmp_fc_conf.pause_time;
+ }
+ if (tmp_fc_conf.high_water)
+ pf->fc_conf.high_water = tmp_fc_conf.high_water;
+ if (tmp_fc_conf.low_water)
+ pf->fc_conf.low_water = tmp_fc_conf.low_water;
+ pf->fc_conf.mac_ctrl_frame_fwd = tmp_fc_conf.mac_ctrl_frame_fwd;
+
+ return 0;
+}
+
static int
ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf)
{
@@ -4113,6 +4354,7 @@ ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc
struct ice_port_info *port_info = hw->port_info;
struct ice_qos_cfg *qos_cfg = &port_info->qos_cfg;
struct ice_dcbx_cfg *dcb_conf = &qos_cfg->local_dcbx_cfg;
+ struct rte_eth_fc_conf tmp_fc_conf = { 0 };
int ret;
dcb_conf->pfc_mode = ICE_QOS_MODE_VLAN;
@@ -4160,47 +4402,13 @@ ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc
return ret;
}
- /* Update high and low watermarks */
- u32 high_watermark = pfc_conf->fc.high_water;
- if (high_watermark > ICE_MAC_TC_MAX_WATERMARK)
- high_watermark = ICE_MAC_TC_MAX_WATERMARK;
-
- u32 low_watermark = pfc_conf->fc.low_water;
- if (low_watermark > ICE_MAC_TC_MAX_WATERMARK)
- low_watermark = ICE_MAC_TC_MAX_WATERMARK;
-
- int cgd_idx = ice_get_cgd_idx(hw, tc);
-
- if (high_watermark)
- wr32(hw, GLRPB_TCHW(cgd_idx), high_watermark);
- if (low_watermark)
- wr32(hw, GLRPB_TCLW(cgd_idx), low_watermark);
-
- /* Update pause quanta */
- uint16_t max_frame_size = pf->dev_data->mtu ?
- pf->dev_data->mtu + ICE_ETH_OVERHEAD :
- ICE_FRAME_SIZE_MAX;
- ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, 1 << tc, pfc_conf->fc.pause_time,
- ((u32)pfc_conf->fc.pause_time + 1) / 2, false, NULL);
- if (ret) {
- PMD_DRV_LOG(ERR, "Can not update MAC configuration");
- return ret;
- }
-
- /* Update forwarding of the non FC MAC control frames settings */
- if ((hw)->mac_type == ICE_MAC_E830) {
-#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
- E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
-
- u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
-
- if (pfc_conf->fc.mac_ctrl_frame_fwd)
- mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
- else
- mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
-
- wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
- }
+ /* Apply the Rx buffer watermarks and pause quanta for the PFC
+ * traffic class selected by the user priority.
+ */
+ tmp_fc_conf = pfc_conf->fc;
+ ret = ice_cfg_fc_params(dev, 1 << tc, &tmp_fc_conf);
+ if (ret)
+ PMD_DRV_LOG(WARNING, "Flow control mode applied, pause parameters left unchanged");
return 0;
}
@@ -4946,6 +5154,7 @@ ice_phy_conf_link(struct ice_hw *hw,
bool link_up)
{
struct ice_aqc_set_phy_cfg_data cfg = { 0 };
+ struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(hw->back);
struct ice_port_info *pi = hw->port_info;
struct ice_aqc_get_phy_caps_data *phy_caps;
int err;
@@ -4986,6 +5195,9 @@ ice_phy_conf_link(struct ice_hw *hw,
}
cfg.caps = phy_caps->caps | ICE_AQ_PHY_ENA_AUTO_LINK_UPDT;
+
+ cfg.caps &= ~(ICE_AQC_PHY_EN_TX_LINK_PAUSE | ICE_AQC_PHY_EN_RX_LINK_PAUSE);
+ cfg.caps |= ice_fc_mode_to_pause_caps(pf->fc_conf.mode);
cfg.low_power_ctrl_an = phy_caps->low_power_ctrl_an;
cfg.eee_cap = phy_caps->eee_cap;
cfg.eeer_value = phy_caps->eeer_value;
diff --git a/drivers/net/intel/ice/ice_ethdev.h b/drivers/net/intel/ice/ice_ethdev.h
index 7ee3ea8a70..3143aca74a 100644
--- a/drivers/net/intel/ice/ice_ethdev.h
+++ b/drivers/net/intel/ice/ice_ethdev.h
@@ -613,6 +613,7 @@ struct ice_pf {
* and link status update during dev_start.
*/
rte_spinlock_t link_lock;
+ struct rte_eth_fc_conf fc_conf; /* cached link flow control config */
};
#define ICE_MAX_QUEUE_NUM 2048
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v2] net/ice: support link flow control
2026-09-16 16:38 ` Bruce Richardson
@ 2026-09-22 19:41 ` Medvedkin, Vladimir
0 siblings, 0 replies; 8+ messages in thread
From: Medvedkin, Vladimir @ 2026-09-22 19:41 UTC (permalink / raw)
To: Bruce Richardson; +Cc: dev, anatoly.burakov
Hi Bruce,
On 9/16/2026 5:38 PM, Bruce Richardson wrote:
> On Thu, Sep 10, 2026 at 11:25:24AM +0000, Vladimir Medvedkin wrote:
>> Add get and set functions for link flow control (802.3x).
>>
>> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
>> ---
> A few minor comments inline below.
>
> /Bruce
>
>> doc/guides/nics/features/ice.ini | 1 +
>> doc/guides/rel_notes/release_26_11.rst | 4 +
>> drivers/net/intel/ice/ice_ethdev.c | 271 +++++++++++++++++++------
>> drivers/net/intel/ice/ice_ethdev.h | 1 +
>> 4 files changed, 217 insertions(+), 60 deletions(-)
>>
>> diff --git a/doc/guides/nics/features/ice.ini b/doc/guides/nics/features/ice.ini
>> index 893d09e9ec..309f691f32 100644
>> --- a/doc/guides/nics/features/ice.ini
>> +++ b/doc/guides/nics/features/ice.ini
>> @@ -30,6 +30,7 @@ RSS hash = Y
>> RSS key update = Y
>> RSS reta update = Y
>> VLAN filter = Y
>> +Flow control = Y
>> Traffic manager = Y
>> CRC offload = Y
>> VLAN offload = Y
>> diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
>> index c8cc86295d..5c4cd8a6df 100644
>> --- a/doc/guides/rel_notes/release_26_11.rst
>> +++ b/doc/guides/rel_notes/release_26_11.rst
>> @@ -55,6 +55,10 @@ New Features
>> Also, make sure to start the actual text at the margin.
>> =======================================================
>>
>> +* **Updated Intel ice driver.**
>> +
>> + * Added support for getting and setting link (802.3x) flow control.
>> +
>>
>> Removed Items
>> -------------
>> diff --git a/drivers/net/intel/ice/ice_ethdev.c b/drivers/net/intel/ice/ice_ethdev.c
>> index 76b8ff0a72..909b3ef963 100644
>> --- a/drivers/net/intel/ice/ice_ethdev.c
>> +++ b/drivers/net/intel/ice/ice_ethdev.c
>> @@ -211,7 +211,12 @@ static int ice_fec_set(struct rte_eth_dev *dev, uint32_t fec_capa);
>> static const uint32_t *ice_buffer_split_supported_hdr_ptypes_get(struct rte_eth_dev *dev,
>> size_t *no_of_elements);
>> static int ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info);
>> +static void ice_set_dflt_mib(struct rte_eth_dev *dev);
>> +static int ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
>> +static int ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf);
>> static int ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf);
>> +static int ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc, uint8_t tc_bitmap,
>> + const struct rte_eth_fc_conf *fc_conf);
>>
>> static const struct rte_pci_id pci_id_ice_map[] = {
>> { RTE_PCI_DEVICE(ICE_INTEL_VENDOR_ID, ICE_DEV_ID_E823L_BACKPLANE) },
>> @@ -352,6 +357,8 @@ static const struct eth_dev_ops ice_eth_dev_ops = {
>> .fec_set = ice_fec_set,
>> .buffer_split_supported_hdr_ptypes_get = ice_buffer_split_supported_hdr_ptypes_get,
>> .get_dcb_info = ice_get_dcb_info,
>> + .flow_ctrl_get = ice_flow_ctrl_get,
>> + .flow_ctrl_set = ice_flow_ctrl_set,
>> .priority_flow_ctrl_set = ice_priority_flow_ctrl_set,
>> };
>>
>> @@ -1468,9 +1475,16 @@ ice_handle_aq_msg(struct rte_eth_dev *dev)
>> switch (opcode) {
>> case ice_aqc_opc_get_link_status:
>> ret = ice_link_update(dev, 0);
>> - if (!ret)
>> + if (!ret) {
>> + /* On link-up, reapply the default single-TC configuration. */
>> + if ((hw->port_info->phy.link_info.link_info & ICE_AQ_LINK_UP) &&
>> + !(dev->data->dev_conf.rxmode.mq_mode &
>> + RTE_ETH_MQ_RX_DCB_FLAG))
>> + ice_set_dflt_mib(dev);
>> +
>> rte_eth_dev_callback_process
>> (dev, RTE_ETH_EVENT_INTR_LSC, NULL);
>> + }
>> break;
>> default:
>> PMD_DRV_LOG(DEBUG, "Request %u is not supported yet",
>> @@ -4073,6 +4087,22 @@ ice_dev_configure(struct rte_eth_dev *dev)
>> return 0;
>> }
>>
>> +static int
>> +ice_get_link_info_safe(struct ice_pf *pf, bool ena_lse,
>> + struct ice_link_status *link)
>> +{
>> + struct ice_hw *hw = ICE_PF_TO_HW(pf);
>> + int ret;
>> +
>> + rte_spinlock_lock(&pf->link_lock);
>> +
>> + ret = ice_aq_get_link_info(hw->port_info, ena_lse, link, NULL);
>> +
>> + rte_spinlock_unlock(&pf->link_lock);
>> +
>> + return ret;
>> +}
>> +
> Rather than moving this function up, why not just put a prototype
> declaration for it at the top of the file with all the other static
> functions? It would make the diff smaller.
>
>> static int
>> ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info)
>> {
>> @@ -4105,6 +4135,182 @@ ice_get_dcb_info(struct rte_eth_dev *dev, struct rte_eth_dcb_info *dcb_info)
>> return 0;
>> }
>>
>> +/*
>> + * Program a default single-TC local LLDP MIB configuration. All user priorities
>> + * map to TC0 at 100% bandwidth.
>> + */
>> +static void
>> +ice_set_dflt_mib(struct rte_eth_dev *dev)
>> +{
>> + struct ice_hw *hw = ICE_DEV_PRIVATE_TO_HW(dev->data->dev_private);
>> + struct ice_dcbx_cfg dcbcfg = { 0 };
>> + uint8_t lldpmib[ICE_LLDPDU_SIZE] = { 0 };
>> + uint16_t miblen;
>> +
>> + dcbcfg.etscfg.willing = 1;
>> + dcbcfg.etscfg.maxtcs = hw->func_caps.common_cap.maxtc;
>> + dcbcfg.etscfg.tcbwtable[0] = 100;
>> + dcbcfg.etscfg.tsatable[0] = ICE_IEEE_TSA_ETS;
>> + dcbcfg.etsrec = dcbcfg.etscfg;
>> + dcbcfg.etsrec.willing = 0;
>> + dcbcfg.pfc.willing = 1;
>> + dcbcfg.pfc.pfccap = hw->func_caps.common_cap.maxtc;
>> +
>> + ice_dcb_cfg_to_lldp(lldpmib, &miblen, &dcbcfg, ICE_SET_PFC_SYM);
>> +
>> + if (ice_aq_set_lldp_mib(hw, SET_LOCAL_MIB_TYPE_LOCAL_MIB, lldpmib, miblen, NULL))
>> + PMD_DRV_LOG(ERR, "Failed to set default LLDP MIB");
>> +}
>> +
>> +static int
>> +ice_flow_ctrl_get(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
>> +{
>> + struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
>> + struct ice_hw *hw = ICE_PF_TO_HW(pf);
>> + struct ice_port_info *pi = hw->port_info;
>> + bool enable_lse = dev->data->dev_conf.intr_conf.lsc ? true : false;
>> + struct ice_link_status link_status;
>> + int ret;
>> +
>> + /* Refresh the negotiated flow control mode from the link status */
>> + ret = ice_get_link_info_safe(pf, enable_lse, &link_status);
>> + if (ret != ICE_SUCCESS) {
>> + PMD_DRV_LOG(ERR, "Failed to get link info");
>> + return -EIO;
>> + }
>> +
>> + *fc_conf = pf->fc_conf;
>> +
>> + /* report the link flow control mode currently in effect */
>> + switch (pi->fc.current_mode) {
>> + case ICE_FC_FULL:
>> + fc_conf->mode = RTE_ETH_FC_FULL;
>> + break;
>> + case ICE_FC_TX_PAUSE:
>> + fc_conf->mode = RTE_ETH_FC_TX_PAUSE;
>> + break;
>> + case ICE_FC_RX_PAUSE:
>> + fc_conf->mode = RTE_ETH_FC_RX_PAUSE;
>> + break;
>> + default:
>> + fc_conf->mode = RTE_ETH_FC_NONE;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/*
>> + * Program the Rx buffer watermarks, pause quanta and MAC control frame
>> + * forwarding shared by link flow control and priority flow control.
>> + * A tc_bitmap of 0 selects link flow control; a single bit selects a PFC
>> + * traffic class. Watermark or quanta values of 0 leave the current setting.
>> + */
>> +static int
>> +ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc, uint8_t tc_bitmap,
>> + const struct rte_eth_fc_conf *fc_conf)
> Minor nit:
> Looking at how this is used, I don't think you need both a tc_bitmap and a
> tc parameter, since tc_bitmap is passed either as 0, or 1 << tc, so
> therefore the tc is computable from the bitmap.
>
> tc = tc_bitmap == 0 ? 0 : rte_ctz32(tc_bitmap);
>
>> +{
>> + struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
>> + struct ice_hw *hw = ICE_PF_TO_HW(pf);
>> + struct ice_port_info *port_info = hw->port_info;
>> + uint32_t high_water = fc_conf->high_water;
>> + uint32_t low_water = fc_conf->low_water;
>> + uint16_t max_frame_size;
>> + int cgd_idx;
>> + int ret;
>> +
>> + if (high_water > ICE_MAC_TC_MAX_WATERMARK)
>> + high_water = ICE_MAC_TC_MAX_WATERMARK;
>> + if (low_water > ICE_MAC_TC_MAX_WATERMARK)
>> + low_water = ICE_MAC_TC_MAX_WATERMARK;
>> +
>> + cgd_idx = ice_get_cgd_idx(hw, tc);
>> + if (high_water)
>> + wr32(hw, GLRPB_TCHW(cgd_idx), high_water);
>> + if (low_water)
>> + wr32(hw, GLRPB_TCLW(cgd_idx), low_water);
>> +
>> + /* Update pause quanta and refresh threshold */
>> + max_frame_size = pf->dev_data->mtu ?
>> + pf->dev_data->mtu + ICE_ETH_OVERHEAD :
>> + ICE_FRAME_SIZE_MAX;
>> + ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, tc_bitmap,
>> + fc_conf->pause_time, ((u32)fc_conf->pause_time + 1) / 2,
>> + false, NULL);
>> + if (ret) {
>> + PMD_DRV_LOG(ERR, "Can not update MAC configuration");
>> + return ret;
>> + }
>> +
>> + /* Update forwarding of the non FC MAC control frames settings */
>> + if (hw->mac_type == ICE_MAC_E830) {
>> +#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
>> + E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
>> +
>> + u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
>> +
>> + if (fc_conf->mac_ctrl_frame_fwd)
>> + mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
>> + else
>> + mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
>> +
>> + wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int
>> +ice_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_fc_conf *fc_conf)
>> +{
>> + struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
>> + struct ice_hw *hw = ICE_PF_TO_HW(pf);
>> + struct ice_port_info *pi = hw->port_info;
>> + bool link_up;
>> + u8 aq_failures;
>> + int ret;
>> +
>> + /* Link flow control and priority flow control are mutually exclusive */
>> + if (dev->data->dev_conf.rxmode.mq_mode & RTE_ETH_MQ_RX_DCB_FLAG) {
>> + PMD_DRV_LOG(ERR, "Priority flow control is enabled, cannot set link flow control");
>> + return -ENOTSUP;
>> + }
>> +
>> + switch (fc_conf->mode) {
>> + case RTE_ETH_FC_FULL:
>> + pi->fc.req_mode = ICE_FC_FULL;
>> + break;
>> + case RTE_ETH_FC_TX_PAUSE:
>> + pi->fc.req_mode = ICE_FC_TX_PAUSE;
>> + break;
>> + case RTE_ETH_FC_RX_PAUSE:
>> + pi->fc.req_mode = ICE_FC_RX_PAUSE;
>> + break;
>> + default:
>> + pi->fc.req_mode = ICE_FC_NONE;
>> + }
>> +
>> + /* ice_set_fc() reprograms the PHY pause config and, if auto link update
>> + * is enabled, restarts the link so the new mode is renegotiated. The
>> + * default MIB that arms the MAC for LFC is reapplied from the link-up
>> + * event handler.
>> + */
>> + link_up = pi->phy.link_info.link_info & ICE_AQ_LINK_UP;
> Is it safe to read this outside the lock? For safety should this be a call
> to ice_get_link_info_safe? AI flags that all other instances in this file
> use the function.
>
>> +
>> + ret = ice_set_fc(pi, &aq_failures, link_up);
>> + if (ret != 0 && aq_failures != ICE_SET_FC_AQ_FAIL_UPDATE) {
>> + PMD_DRV_LOG(ERR, "Failed to set flow control mode, ret %d aq_failures 0x%x",
>> + ret, aq_failures);
>> + return -EIO;
>> + }
>> +
>> + pf->fc_conf = *fc_conf;
>> +
>> + /* Apply the Rx buffer watermarks and pause quanta for the link flow
>> + * control traffic class (LFC rides user priority 0).
>> + */
>> + return ice_cfg_fc_params(dev, 0, 0, fc_conf);
>> +}
> AI review flags a potential issue here. What happens to pf->fc_conf if the
> application fails? The applied values will not match the stored ones.
agree, but I think we could do even better. Don't fail if setting FC
parameters returns an error. Instead don't update pause quanta (the only
place where ice_cfg_fc_params may fail) value in the fc_conf cache.
>
>> +
>> static int
>> ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc_conf)
>> {
>> @@ -4160,49 +4366,10 @@ ice_priority_flow_ctrl_set(struct rte_eth_dev *dev, struct rte_eth_pfc_conf *pfc
>> return ret;
>> }
>>
>> - /* Update high and low watermarks */
>> - u32 high_watermark = pfc_conf->fc.high_water;
>> - if (high_watermark > ICE_MAC_TC_MAX_WATERMARK)
>> - high_watermark = ICE_MAC_TC_MAX_WATERMARK;
>> -
>> - u32 low_watermark = pfc_conf->fc.low_water;
>> - if (low_watermark > ICE_MAC_TC_MAX_WATERMARK)
>> - low_watermark = ICE_MAC_TC_MAX_WATERMARK;
>> -
>> - int cgd_idx = ice_get_cgd_idx(hw, tc);
>> -
>> - if (high_watermark)
>> - wr32(hw, GLRPB_TCHW(cgd_idx), high_watermark);
>> - if (low_watermark)
>> - wr32(hw, GLRPB_TCLW(cgd_idx), low_watermark);
>> -
>> - /* Update pause quanta */
>> - uint16_t max_frame_size = pf->dev_data->mtu ?
>> - pf->dev_data->mtu + ICE_ETH_OVERHEAD :
>> - ICE_FRAME_SIZE_MAX;
>> - ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, 1 << tc, pfc_conf->fc.pause_time,
>> - ((u32)pfc_conf->fc.pause_time + 1) / 2, false, NULL);
>> - if (ret) {
>> - PMD_DRV_LOG(ERR, "Can not update MAC configuration");
>> - return ret;
>> - }
>> -
>> - /* Update forwarding of the non FC MAC control frames settings */
>> - if ((hw)->mac_type == ICE_MAC_E830) {
>> -#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
>> - E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
>> -
>> - u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
>> -
>> - if (pfc_conf->fc.mac_ctrl_frame_fwd)
>> - mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
>> - else
>> - mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
>> -
>> - wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
>> - }
>> -
>> - return 0;
>> + /* Apply the Rx buffer watermarks and pause quanta for the PFC
>> + * traffic class selected by the user priority.
>> + */
>> + return ice_cfg_fc_params(dev, tc, 1 << tc, &pfc_conf->fc);
>> }
>>
>> static void
>> @@ -4373,22 +4540,6 @@ ice_rxq_intr_setup(struct rte_eth_dev *dev)
>> return 0;
>> }
>>
>> -static int
>> -ice_get_link_info_safe(struct ice_pf *pf, bool ena_lse,
>> - struct ice_link_status *link)
>> -{
>> - struct ice_hw *hw = ICE_PF_TO_HW(pf);
>> - int ret;
>> -
>> - rte_spinlock_lock(&pf->link_lock);
>> -
>> - ret = ice_aq_get_link_info(hw->port_info, ena_lse, link, NULL);
>> -
>> - rte_spinlock_unlock(&pf->link_lock);
>> -
>> - return ret;
>> -}
>> -
>> static void
>> ice_get_init_link_status(struct rte_eth_dev *dev)
>> {
>> diff --git a/drivers/net/intel/ice/ice_ethdev.h b/drivers/net/intel/ice/ice_ethdev.h
>> index 7ee3ea8a70..3143aca74a 100644
>> --- a/drivers/net/intel/ice/ice_ethdev.h
>> +++ b/drivers/net/intel/ice/ice_ethdev.h
>> @@ -613,6 +613,7 @@ struct ice_pf {
>> * and link status update during dev_start.
>> */
>> rte_spinlock_t link_lock;
>> + struct rte_eth_fc_conf fc_conf; /* cached link flow control config */
>> };
>>
>> #define ICE_MAX_QUEUE_NUM 2048
>> --
>> 2.43.0
>>
--
Regards,
Vladimir
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] net/ice: support link flow control
2026-09-22 18:23 ` [PATCH v3] " Vladimir Medvedkin
@ 2026-09-25 15:02 ` Bruce Richardson
2026-09-25 15:40 ` Medvedkin, Vladimir
0 siblings, 1 reply; 8+ messages in thread
From: Bruce Richardson @ 2026-09-25 15:02 UTC (permalink / raw)
To: Vladimir Medvedkin; +Cc: dev, anatoly.burakov
On Tue, Sep 22, 2026 at 06:23:33PM +0000, Vladimir Medvedkin wrote:
> Add get and set operations for IEEE 802.3x link flow control.
> Link pause is advertised only when requested, retained across port
> stop/start and re-armed on link-up. The watermark and pause quanta
> programming is shared with priority flow control.
>
> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> ---
> v3:
> - Address Bruce's comments.
> - Add documentation.
> - Retain LFC configuration across port stop/start.
> - Fix various issues.
>
> doc/guides/nics/features/ice.ini | 1 +
> doc/guides/nics/ice.rst | 50 +++++
> doc/guides/rel_notes/release_26_11.rst | 4 +
> drivers/net/intel/ice/ice_ethdev.c | 296 +++++++++++++++++++++----
> drivers/net/intel/ice/ice_ethdev.h | 1 +
> 5 files changed, 310 insertions(+), 42 deletions(-)
>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
One comment inline below. I can fix on apply if you agree with it.
> diff --git a/doc/guides/nics/features/ice.ini b/doc/guides/nics/features/ice.ini
> index 893d09e9ec..309f691f32 100644
> --- a/doc/guides/nics/features/ice.ini
> +++ b/doc/guides/nics/features/ice.ini
> @@ -30,6 +30,7 @@ RSS hash = Y
> RSS key update = Y
> RSS reta update = Y
> VLAN filter = Y
> +Flow control = Y
> Traffic manager = Y
> CRC offload = Y
> VLAN offload = Y
> diff --git a/doc/guides/nics/ice.rst b/doc/guides/nics/ice.rst
> index b671422ad7..065038a0d1 100644
> --- a/doc/guides/nics/ice.rst
> +++ b/doc/guides/nics/ice.rst
> @@ -516,6 +516,56 @@ Example PFC configuration using DPDK API:
> ret = rte_eth_dev_priority_flow_ctrl_set(port_id, &pfc_conf);
>
>
<snip>
> +/*
> + * Program the Rx buffer watermarks, pause quanta and MAC control frame
> + * forwarding shared by link flow control and priority flow control.
> + * A tc_bitmap of 0 selects link flow control; a single bit selects a PFC
> + * traffic class. Watermark or quanta values of 0 leave the current setting.
> + * The only step that can fail is issued first so that on error nothing
> + * has been changed.
> + */
> +static int
> +ice_cfg_fc_params(struct rte_eth_dev *dev, uint8_t tc_bitmap,
> + struct rte_eth_fc_conf *fc_conf)
> +{
> + struct ice_pf *pf = ICE_DEV_PRIVATE_TO_PF(dev->data->dev_private);
> + struct ice_hw *hw = ICE_PF_TO_HW(pf);
> + struct ice_port_info *port_info = hw->port_info;
> + uint8_t tc = tc_bitmap == 0 ? 0 : rte_ctz32(tc_bitmap);
> + uint32_t high_water = fc_conf->high_water;
> + uint32_t low_water = fc_conf->low_water;
> + uint16_t max_frame_size;
> + int cgd_idx;
> + int ret;
> +
> + if (high_water > ICE_MAC_TC_MAX_WATERMARK)
> + high_water = ICE_MAC_TC_MAX_WATERMARK;
> + if (low_water > ICE_MAC_TC_MAX_WATERMARK)
> + low_water = ICE_MAC_TC_MAX_WATERMARK;
> +
> + fc_conf->high_water = high_water;
> + fc_conf->low_water = low_water;
> +
> + cgd_idx = ice_get_cgd_idx(hw, tc);
> + if (high_water)
> + wr32(hw, GLRPB_TCHW(cgd_idx), high_water);
> + if (low_water)
> + wr32(hw, GLRPB_TCLW(cgd_idx), low_water);
> +
> + /* Update forwarding of the non FC MAC control frames settings */
> + if (hw->mac_type == ICE_MAC_E830) {
> +#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
> + E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
> +
I don't like this definition right in the middle of the function. I think
it should either be defined right before the function or else, at latest,
just inside the function opening brace at the top.
If you are ok with it, I'll move it on apply of the patch.
> + u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
> +
> + if (fc_conf->mac_ctrl_frame_fwd)
> + mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
> + else
> + mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
> +
> + wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
> + } else {
> + fc_conf->mac_ctrl_frame_fwd = false;
> + }
> +
> + /* Update pause quanta and refresh threshold */
> + max_frame_size = pf->dev_data->mtu ?
> + pf->dev_data->mtu + ICE_ETH_OVERHEAD :
> + ICE_FRAME_SIZE_MAX;
> + ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, tc_bitmap,
> + fc_conf->pause_time, ((u32)fc_conf->pause_time + 1) / 2,
> + false, NULL);
> + if (ret)
> + PMD_DRV_LOG(ERR, "Can not update MAC configuration");
> +
> + return ret;
> +}
<snip>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] net/ice: support link flow control
2026-09-25 15:02 ` Bruce Richardson
@ 2026-09-25 15:40 ` Medvedkin, Vladimir
2026-09-28 10:00 ` Bruce Richardson
0 siblings, 1 reply; 8+ messages in thread
From: Medvedkin, Vladimir @ 2026-09-25 15:40 UTC (permalink / raw)
To: Bruce Richardson; +Cc: dev, anatoly.burakov
Hi Bruce,
On 9/25/2026 4:02 PM, Bruce Richardson wrote:
> On Tue, Sep 22, 2026 at 06:23:33PM +0000, Vladimir Medvedkin wrote:
>> Add get and set operations for IEEE 802.3x link flow control.
>> Link pause is advertised only when requested, retained across port
>> stop/start and re-armed on link-up. The watermark and pause quanta
>> programming is shared with priority flow control.
>>
>> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
>> ---
>> v3:
>> - Address Bruce's comments.
>> - Add documentation.
>> - Retain LFC configuration across port stop/start.
>> - Fix various issues.
>>
>> doc/guides/nics/features/ice.ini | 1 +
>> doc/guides/nics/ice.rst | 50 +++++
>> doc/guides/rel_notes/release_26_11.rst | 4 +
>> drivers/net/intel/ice/ice_ethdev.c | 296 +++++++++++++++++++++----
>> drivers/net/intel/ice/ice_ethdev.h | 1 +
>> 5 files changed, 310 insertions(+), 42 deletions(-)
>>
> Acked-by: Bruce Richardson <bruce.richardson@intel.com>
>
> One comment inline below. I can fix on apply if you agree with it.
>
<snip>
>> + /* Update forwarding of the non FC MAC control frames settings */
>> + if (hw->mac_type == ICE_MAC_E830) {
>> +#define E830_MAC_COMMAND_CONFIG(pi) (((pi)->phy.link_info.link_speed == ICE_AQ_LINK_SPEED_200GB) ? \
>> + E830_PRTMAC_200G_COMMAND_CONFIG : E830_PRTMAC_COMMAND_CONFIG)
>> +
> I don't like this definition right in the middle of the function. I think
> it should either be defined right before the function or else, at latest,
> just inside the function opening brace at the top.
> If you are ok with it, I'll move it on apply of the patch.
Sure, no objections
>> + u32 mac_config = rd32(hw, E830_MAC_COMMAND_CONFIG(port_info));
>> +
>> + if (fc_conf->mac_ctrl_frame_fwd)
>> + mac_config |= E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
>> + else
>> + mac_config &= ~E830_PRTMAC_COMMAND_CONFIG_CNTL_FRM_ENA_M;
>> +
>> + wr32(hw, E830_MAC_COMMAND_CONFIG(port_info), mac_config);
>> + } else {
>> + fc_conf->mac_ctrl_frame_fwd = false;
>> + }
>> +
>> + /* Update pause quanta and refresh threshold */
>> + max_frame_size = pf->dev_data->mtu ?
>> + pf->dev_data->mtu + ICE_ETH_OVERHEAD :
>> + ICE_FRAME_SIZE_MAX;
>> + ret = ice_aq_set_mac_pfc_cfg(hw, max_frame_size, tc_bitmap,
>> + fc_conf->pause_time, ((u32)fc_conf->pause_time + 1) / 2,
>> + false, NULL);
>> + if (ret)
>> + PMD_DRV_LOG(ERR, "Can not update MAC configuration");
>> +
>> + return ret;
>> +}
> <snip>
--
Regards,
Vladimir
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] net/ice: support link flow control
2026-09-25 15:40 ` Medvedkin, Vladimir
@ 2026-09-28 10:00 ` Bruce Richardson
0 siblings, 0 replies; 8+ messages in thread
From: Bruce Richardson @ 2026-09-28 10:00 UTC (permalink / raw)
To: Medvedkin, Vladimir; +Cc: dev, anatoly.burakov
On Fri, Sep 25, 2026 at 04:40:39PM +0100, Medvedkin, Vladimir wrote:
> Hi Bruce,
>
> On 9/25/2026 4:02 PM, Bruce Richardson wrote:
> > On Tue, Sep 22, 2026 at 06:23:33PM +0000, Vladimir Medvedkin wrote:
> > > Add get and set operations for IEEE 802.3x link flow control.
> > > Link pause is advertised only when requested, retained across port
> > > stop/start and re-armed on link-up. The watermark and pause quanta
> > > programming is shared with priority flow control.
> > >
> > > Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> > > ---
> > > v3:
> > > - Address Bruce's comments.
> > > - Add documentation.
> > > - Retain LFC configuration across port stop/start.
> > > - Fix various issues.
> > >
> > > doc/guides/nics/features/ice.ini | 1 +
> > > doc/guides/nics/ice.rst | 50 +++++
> > > doc/guides/rel_notes/release_26_11.rst | 4 +
> > > drivers/net/intel/ice/ice_ethdev.c | 296 +++++++++++++++++++++----
> > > drivers/net/intel/ice/ice_ethdev.h | 1 +
> > > 5 files changed, 310 insertions(+), 42 deletions(-)
> > >
> > Acked-by: Bruce Richardson <bruce.richardson@intel.com>
> >
Applied to dpdk-next-net-intel.
Thanks,
/Bruce
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-28 10:01 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-01 15:12 [PATCH] net/ice: support link flow control Vladimir Medvedkin
2026-09-10 11:25 ` [PATCH v2] " Vladimir Medvedkin
2026-09-16 16:38 ` Bruce Richardson
2026-09-22 19:41 ` Medvedkin, Vladimir
2026-09-22 18:23 ` [PATCH v3] " Vladimir Medvedkin
2026-09-25 15:02 ` Bruce Richardson
2026-09-25 15:40 ` Medvedkin, Vladimir
2026-09-28 10:00 ` Bruce Richardson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox