* [PATCH net-next v2 01/12] ice: rename shared Flow Director functions and structs
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-21 19:57 ` netdev-bot+sashiko
2026-09-17 18:39 ` [PATCH net-next v2 02/12] ice: remove unused ICE_FD_FLUSH_REQ from PF state Tony Nguyen
` (12 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Tony Nguyen, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Rinitha S
Rename shared Flow Director functions and structs. These entities are
currently used to add Flow Director filters, however, they will be
expanded to also add ACL filters. Rename the functions and struct,
replacing 'fdir' to 'ntuple', to reflect that they are being used for
ntuple filters and are not solely used for Flow Director.
Rename the file to also reflect this change.
Co-developed-by: Paul M Stillwell Jr <paul.m.stillwell.jr@intel.com>
Signed-off-by: Paul M Stillwell Jr <paul.m.stillwell.jr@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Co-developed-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/Makefile | 2 +-
drivers/net/ethernet/intel/ice/ice.h | 6 +-
drivers/net/ethernet/intel/ice/ice_arfs.c | 8 +-
drivers/net/ethernet/intel/ice/ice_arfs.h | 2 +-
drivers/net/ethernet/intel/ice/ice_ethtool.c | 9 +-
...ce_ethtool_fdir.c => ice_ethtool_ntuple.c} | 85 ++++++++++++-------
drivers/net/ethernet/intel/ice/ice_fdir.c | 38 ++-------
drivers/net/ethernet/intel/ice/ice_fdir.h | 14 ++-
drivers/net/ethernet/intel/ice/ice_type.h | 4 +-
drivers/net/ethernet/intel/ice/virt/fdir.c | 28 +++---
10 files changed, 98 insertions(+), 98 deletions(-)
rename drivers/net/ethernet/intel/ice/{ice_ethtool_fdir.c => ice_ethtool_ntuple.c} (96%)
diff --git a/drivers/net/ethernet/intel/ice/Makefile b/drivers/net/ethernet/intel/ice/Makefile
index 95fd0c49800f..a952bacb7ec7 100644
--- a/drivers/net/ethernet/intel/ice/Makefile
+++ b/drivers/net/ethernet/intel/ice/Makefile
@@ -24,7 +24,7 @@ ice-y := ice_main.o \
ice_vsi_vlan_ops.o \
ice_vsi_vlan_lib.o \
ice_fdir.o \
- ice_ethtool_fdir.o \
+ ice_ethtool_ntuple.o \
ice_vlan_mode.o \
ice_flex_pipe.o \
ice_flow.o \
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index e5f940d324dc..cb7a73bbcd71 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -1018,11 +1018,11 @@ void ice_deinit_rdma(struct ice_pf *pf);
bool ice_is_wol_supported(struct ice_hw *hw);
void ice_fdir_del_all_fltrs(struct ice_vsi *vsi);
int
-ice_fdir_write_fltr(struct ice_pf *pf, struct ice_fdir_fltr *input, bool add,
+ice_fdir_write_fltr(struct ice_pf *pf, struct ice_ntuple_fltr *input, bool add,
bool is_tun);
void ice_vsi_manage_fdir(struct ice_vsi *vsi, bool ena);
-int ice_add_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
-int ice_del_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
+int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
+int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
int ice_get_ethtool_fdir_entry(struct ice_hw *hw, struct ethtool_rxnfc *cmd);
int
ice_get_fdir_fltr_ids(struct ice_hw *hw, struct ethtool_rxnfc *cmd,
diff --git a/drivers/net/ethernet/intel/ice/ice_arfs.c b/drivers/net/ethernet/intel/ice/ice_arfs.c
index 53b6e2b09eb9..e0335f5e18fe 100644
--- a/drivers/net/ethernet/intel/ice/ice_arfs.c
+++ b/drivers/net/ethernet/intel/ice/ice_arfs.c
@@ -302,7 +302,7 @@ ice_arfs_build_entry(struct ice_vsi *vsi, const struct flow_keys *fk,
u16 rxq_idx, u32 flow_id)
{
struct ice_arfs_entry *arfs_entry;
- struct ice_fdir_fltr *fltr_info;
+ struct ice_ntuple_fltr *fltr_info;
u8 ip_proto;
arfs_entry = devm_kzalloc(ice_pf_to_dev(vsi->back),
@@ -392,8 +392,8 @@ ice_arfs_is_perfect_flow_set(struct ice_hw *hw, __be16 l3_proto, u8 l4_proto)
* * false - fltr_info and fk refer to different flows.
*/
static bool
-ice_arfs_cmp(const struct ice_fdir_fltr *fltr_info, const struct flow_keys *fk,
- __be16 n_proto, u8 ip_proto)
+ice_arfs_cmp(const struct ice_ntuple_fltr *fltr_info,
+ const struct flow_keys *fk, __be16 n_proto, u8 ip_proto)
{
/* Determine if the filter is for IPv4 or IPv6 based on flow_type,
* which is one of ICE_FLTR_PTYPE_NONF_IPV{4,6}_{TCP,UDP}.
@@ -485,7 +485,7 @@ ice_rx_flow_steer(struct net_device *netdev, const struct sk_buff *skb,
spin_lock_bh(&vsi->arfs_lock);
hlist_for_each_entry(arfs_entry, &vsi->arfs_fltr_list[idx],
list_entry) {
- struct ice_fdir_fltr *fltr_info;
+ struct ice_ntuple_fltr *fltr_info;
/* keep searching for the already existing arfs_entry flow */
if (arfs_entry->flow_id != flow_id)
diff --git a/drivers/net/ethernet/intel/ice/ice_arfs.h b/drivers/net/ethernet/intel/ice/ice_arfs.h
index 9706293128c3..7393254b7e0a 100644
--- a/drivers/net/ethernet/intel/ice/ice_arfs.h
+++ b/drivers/net/ethernet/intel/ice/ice_arfs.h
@@ -13,7 +13,7 @@ enum ice_arfs_fltr_state {
};
struct ice_arfs_entry {
- struct ice_fdir_fltr fltr_info;
+ struct ice_ntuple_fltr fltr_info;
struct hlist_node list_entry;
u64 time_activated; /* only valid for UDP flows */
u32 flow_id;
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool.c b/drivers/net/ethernet/intel/ice/ice_ethtool.c
index bf9a821c543b..ef1c66855723 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool.c
@@ -3088,9 +3088,9 @@ static int ice_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
switch (cmd->cmd) {
case ETHTOOL_SRXCLSRLINS:
- return ice_add_fdir_ethtool(vsi, cmd);
+ return ice_add_ntuple_ethtool(vsi, cmd);
case ETHTOOL_SRXCLSRLDEL:
- return ice_del_fdir_ethtool(vsi, cmd);
+ return ice_del_ntuple_ethtool(vsi, cmd);
default:
break;
}
@@ -3132,7 +3132,7 @@ ice_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd,
switch (cmd->cmd) {
case ETHTOOL_GRXCLSRLCNT:
- cmd->rule_cnt = hw->fdir_active_fltr;
+ cmd->rule_cnt = hw->ntuple_active_fltr_cnt;
/* report total rule count */
cmd->data = ice_get_fdir_cnt_all(hw);
ret = 0;
@@ -3908,7 +3908,8 @@ static int ice_set_channels(struct net_device *dev, struct ethtool_channels *ch)
return -EOPNOTSUPP;
}
- if (test_bit(ICE_FLAG_FD_ENA, pf->flags) && pf->hw.fdir_active_fltr) {
+ if (test_bit(ICE_FLAG_FD_ENA, pf->flags) &&
+ pf->hw.ntuple_active_fltr_cnt) {
netdev_err(dev, "Cannot set channels when Flow Director filters are active\n");
return -EOPNOTSUPP;
}
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_fdir.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
similarity index 96%
rename from drivers/net/ethernet/intel/ice/ice_ethtool_fdir.c
rename to drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
index aceec184e89b..744cd0b82470 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool_fdir.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
@@ -120,7 +120,7 @@ static bool ice_is_mask_valid(u64 mask, u64 field)
int ice_get_ethtool_fdir_entry(struct ice_hw *hw, struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp;
- struct ice_fdir_fltr *rule;
+ struct ice_ntuple_fltr *rule;
int ret = 0;
u16 idx;
@@ -240,7 +240,7 @@ int
ice_get_fdir_fltr_ids(struct ice_hw *hw, struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{
- struct ice_fdir_fltr *f_rule;
+ struct ice_ntuple_fltr *f_rule;
unsigned int cnt = 0;
int val = 0;
@@ -1487,10 +1487,10 @@ static void ice_update_per_q_fltr(struct ice_vsi *vsi, u32 q_index, bool inc)
* @add: true adds filter and false removed filter
* @is_tun: true adds inner filter on tunnel and false outer headers
*
- * returns 0 on success and negative value on error
+ * Return: 0 on success and negative value on error
*/
int
-ice_fdir_write_fltr(struct ice_pf *pf, struct ice_fdir_fltr *input, bool add,
+ice_fdir_write_fltr(struct ice_pf *pf, struct ice_ntuple_fltr *input, bool add,
bool is_tun)
{
struct device *dev = ice_pf_to_dev(pf);
@@ -1557,10 +1557,10 @@ ice_fdir_write_fltr(struct ice_pf *pf, struct ice_fdir_fltr *input, bool add,
* @input: filter structure
* @add: true adds filter and false removed filter
*
- * returns 0 on success and negative value on error
+ * Return: 0 on success and negative value on error
*/
static int
-ice_fdir_write_all_fltr(struct ice_pf *pf, struct ice_fdir_fltr *input,
+ice_fdir_write_all_fltr(struct ice_pf *pf, struct ice_ntuple_fltr *input,
bool add)
{
u16 port_num;
@@ -1585,7 +1585,7 @@ ice_fdir_write_all_fltr(struct ice_pf *pf, struct ice_fdir_fltr *input,
*/
void ice_fdir_replay_fltrs(struct ice_pf *pf)
{
- struct ice_fdir_fltr *f_rule;
+ struct ice_ntuple_fltr *f_rule;
struct ice_hw *hw = &pf->hw;
list_for_each_entry(f_rule, &hw->fdir_list_head, fltr_node) {
@@ -1622,6 +1622,25 @@ int ice_fdir_create_dflt_rules(struct ice_pf *pf)
return err;
}
+/**
+ * ice_ntuple_update_cntrs - increment or decrement filter counter
+ * @hw: pointer to hardware structure
+ * @flow: filter flow type
+ * @add: true to increment, false to decrement
+ */
+static void ice_ntuple_update_cntrs(struct ice_hw *hw,
+ enum ice_fltr_ptype flow, bool add)
+{
+ int incr = add ? 1 : -1;
+
+ hw->ntuple_active_fltr_cnt += incr;
+
+ if (flow == ICE_FLTR_PTYPE_NONF_NONE || flow >= ICE_FLTR_PTYPE_MAX)
+ ice_debug(hw, ICE_DBG_SW, "Unknown filter type %d\n", flow);
+ else
+ hw->fdir_fltr_cnt[flow] += incr;
+}
+
/**
* ice_fdir_del_all_fltrs - Delete all flow director filters
* @vsi: the VSI being changed
@@ -1630,13 +1649,13 @@ int ice_fdir_create_dflt_rules(struct ice_pf *pf)
*/
void ice_fdir_del_all_fltrs(struct ice_vsi *vsi)
{
- struct ice_fdir_fltr *f_rule, *tmp;
+ struct ice_ntuple_fltr *f_rule, *tmp;
struct ice_pf *pf = vsi->back;
struct ice_hw *hw = &pf->hw;
list_for_each_entry_safe(f_rule, tmp, &hw->fdir_list_head, fltr_node) {
ice_fdir_write_all_fltr(pf, f_rule, false);
- ice_fdir_update_cntrs(hw, f_rule->flow_type, false);
+ ice_ntuple_update_cntrs(hw, f_rule->flow_type, false);
list_del(&f_rule->fltr_node);
devm_kfree(ice_pf_to_dev(pf), f_rule);
}
@@ -1701,18 +1720,18 @@ ice_fdir_do_rem_flow(struct ice_pf *pf, enum ice_fltr_ptype flow_type)
}
/**
- * ice_fdir_update_list_entry - add or delete a filter from the filter list
+ * ice_ntuple_update_list_entry - add or delete a filter from the filter list
* @pf: PF structure
* @input: filter structure
* @fltr_idx: ethtool index of filter to modify
*
- * returns 0 on success and negative on errors
+ * Return: 0 on success and negative on errors
*/
static int
-ice_fdir_update_list_entry(struct ice_pf *pf, struct ice_fdir_fltr *input,
- int fltr_idx)
+ice_ntuple_update_list_entry(struct ice_pf *pf, struct ice_ntuple_fltr *input,
+ int fltr_idx)
{
- struct ice_fdir_fltr *old_fltr;
+ struct ice_ntuple_fltr *old_fltr;
struct ice_hw *hw = &pf->hw;
struct ice_vsi *vsi;
int err = -ENOENT;
@@ -1730,7 +1749,7 @@ ice_fdir_update_list_entry(struct ice_pf *pf, struct ice_fdir_fltr *input,
err = ice_fdir_write_all_fltr(pf, old_fltr, false);
if (err)
return err;
- ice_fdir_update_cntrs(hw, old_fltr->flow_type, false);
+ ice_ntuple_update_cntrs(hw, old_fltr->flow_type, false);
/* update sb-filters count, specific to ring->channel */
ice_update_per_q_fltr(vsi, old_fltr->orig_q_index, false);
if (!input && !hw->fdir_fltr_cnt[old_fltr->flow_type])
@@ -1746,18 +1765,18 @@ ice_fdir_update_list_entry(struct ice_pf *pf, struct ice_fdir_fltr *input,
ice_fdir_list_add_fltr(hw, input);
/* update sb-filters count, specific to ring->channel */
ice_update_per_q_fltr(vsi, input->orig_q_index, true);
- ice_fdir_update_cntrs(hw, input->flow_type, true);
+ ice_ntuple_update_cntrs(hw, input->flow_type, true);
return 0;
}
/**
- * ice_del_fdir_ethtool - delete Flow Director filter
+ * ice_del_ntuple_ethtool - delete Flow Director or ACL filter
* @vsi: pointer to target VSI
- * @cmd: command to add or delete Flow Director filter
+ * @cmd: command to add or delete the filter
*
- * Returns 0 on success and negative values for failure
+ * Return: 0 on success and negative values for failure
*/
-int ice_del_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
+int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
@@ -1778,7 +1797,7 @@ int ice_del_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
return -EBUSY;
mutex_lock(&hw->fdir_fltr_lock);
- val = ice_fdir_update_list_entry(pf, NULL, fsp->location);
+ val = ice_ntuple_update_list_entry(pf, NULL, fsp->location);
mutex_unlock(&hw->fdir_fltr_lock);
return val;
@@ -1818,14 +1837,16 @@ ice_update_ring_dest_vsi(struct ice_vsi *vsi, u16 *dest_vsi, u32 *ring)
}
/**
- * ice_set_fdir_input_set - Set the input set for Flow Director
+ * ice_ntuple_set_input_set - Set the input set for Flow Director
* @vsi: pointer to target VSI
* @fsp: pointer to ethtool Rx flow specification
* @input: filter structure
+ *
+ * Return: 0 on success, negative on failure
*/
static int
-ice_set_fdir_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp,
- struct ice_fdir_fltr *input)
+ice_ntuple_set_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp,
+ struct ice_ntuple_fltr *input)
{
s16 q_index = ICE_FDIR_NO_QUEUE_IDX;
u16 orig_q_index = 0;
@@ -1968,17 +1989,17 @@ ice_set_fdir_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp,
}
/**
- * ice_add_fdir_ethtool - Add/Remove Flow Director filter
+ * ice_add_ntuple_ethtool - Add/Remove Flow Director or ACL filter
* @vsi: pointer to target VSI
- * @cmd: command to add or delete Flow Director filter
+ * @cmd: command to add or delete the filter
*
- * Returns 0 on success and negative values for failure
+ * Return: 0 on success and negative values for failure
*/
-int ice_add_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
+int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
{
struct ice_rx_flow_userdef userdata;
struct ethtool_rx_flow_spec *fsp;
- struct ice_fdir_fltr *input;
+ struct ice_ntuple_fltr *input;
struct device *dev;
struct ice_pf *pf;
struct ice_hw *hw;
@@ -2034,7 +2055,7 @@ int ice_add_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
if (!input)
return -ENOMEM;
- ret = ice_set_fdir_input_set(vsi, fsp, input);
+ ret = ice_ntuple_set_input_set(vsi, fsp, input);
if (ret)
goto free_input;
@@ -2055,7 +2076,7 @@ int ice_add_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
input->comp_report = ICE_FXD_FLTR_QW0_COMP_REPORT_SW_FAIL;
/* input struct is added to the HW filter list */
- ret = ice_fdir_update_list_entry(pf, input, fsp->location);
+ ret = ice_ntuple_update_list_entry(pf, input, fsp->location);
if (ret)
goto release_lock;
@@ -2066,7 +2087,7 @@ int ice_add_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
goto release_lock;
remove_sw_rule:
- ice_fdir_update_cntrs(hw, input->flow_type, false);
+ ice_ntuple_update_cntrs(hw, input->flow_type, false);
/* update sb-filters count, specific to ring->channel */
ice_update_per_q_fltr(vsi, input->orig_q_index, false);
list_del(&input->fltr_node);
diff --git a/drivers/net/ethernet/intel/ice/ice_fdir.c b/drivers/net/ethernet/intel/ice/ice_fdir.c
index b29fbdec9442..1bc91cb41769 100644
--- a/drivers/net/ethernet/intel/ice/ice_fdir.c
+++ b/drivers/net/ethernet/intel/ice/ice_fdir.c
@@ -648,7 +648,7 @@ ice_set_fd_desc_val(struct ice_fd_fltr_desc_ctx *ctx,
* @add: if add is true, this is an add operation, false implies delete
*/
void
-ice_fdir_get_prgm_desc(struct ice_hw *hw, struct ice_fdir_fltr *input,
+ice_fdir_get_prgm_desc(struct ice_hw *hw, struct ice_ntuple_fltr *input,
struct ice_fltr_desc *fdesc, bool add)
{
struct ice_fd_fltr_desc_ctx fdir_fltr_ctx = { 0 };
@@ -855,7 +855,7 @@ static void ice_pkt_insert_mac_addr(u8 *pkt, u8 *addr)
* @tun: true implies generate a tunnel packet
*/
int
-ice_fdir_get_gen_prgm_pkt(struct ice_hw *hw, struct ice_fdir_fltr *input,
+ice_fdir_get_gen_prgm_pkt(struct ice_hw *hw, struct ice_ntuple_fltr *input,
u8 *pkt, bool frag, bool tun)
{
enum ice_fltr_ptype flow;
@@ -1138,10 +1138,10 @@ bool ice_fdir_has_frag(enum ice_fltr_ptype flow)
*
* Returns pointer to filter if found or null
*/
-struct ice_fdir_fltr *
+struct ice_ntuple_fltr *
ice_fdir_find_fltr_by_idx(struct ice_hw *hw, u32 fltr_idx)
{
- struct ice_fdir_fltr *rule;
+ struct ice_ntuple_fltr *rule;
list_for_each_entry(rule, &hw->fdir_list_head, fltr_node) {
/* rule ID found in the list */
@@ -1158,9 +1158,9 @@ ice_fdir_find_fltr_by_idx(struct ice_hw *hw, u32 fltr_idx)
* @hw: hardware structure
* @fltr: filter node to add to structure
*/
-void ice_fdir_list_add_fltr(struct ice_hw *hw, struct ice_fdir_fltr *fltr)
+void ice_fdir_list_add_fltr(struct ice_hw *hw, struct ice_ntuple_fltr *fltr)
{
- struct ice_fdir_fltr *rule, *parent = NULL;
+ struct ice_ntuple_fltr *rule, *parent = NULL;
list_for_each_entry(rule, &hw->fdir_list_head, fltr_node) {
/* rule ID found or pass its spot in the list */
@@ -1175,26 +1175,6 @@ void ice_fdir_list_add_fltr(struct ice_hw *hw, struct ice_fdir_fltr *fltr)
list_add(&fltr->fltr_node, &hw->fdir_list_head);
}
-/**
- * ice_fdir_update_cntrs - increment / decrement filter counter
- * @hw: pointer to hardware structure
- * @flow: filter flow type
- * @add: true implies filters added
- */
-void
-ice_fdir_update_cntrs(struct ice_hw *hw, enum ice_fltr_ptype flow, bool add)
-{
- int incr;
-
- incr = add ? 1 : -1;
- hw->fdir_active_fltr += incr;
-
- if (flow == ICE_FLTR_PTYPE_NONF_NONE || flow >= ICE_FLTR_PTYPE_MAX)
- ice_debug(hw, ICE_DBG_SW, "Unknown filter type %d\n", flow);
- else
- hw->fdir_fltr_cnt[flow] += incr;
-}
-
/**
* ice_cmp_ipv6_addr - compare 2 IP v6 addresses
* @a: IP v6 address
@@ -1215,7 +1195,7 @@ static int ice_cmp_ipv6_addr(__be32 *a, __be32 *b)
* Returns true if the filters match
*/
static bool
-ice_fdir_comp_rules(struct ice_fdir_fltr *a, struct ice_fdir_fltr *b)
+ice_fdir_comp_rules(struct ice_ntuple_fltr *a, struct ice_ntuple_fltr *b)
{
enum ice_fltr_ptype flow_type = a->flow_type;
@@ -1275,9 +1255,9 @@ ice_fdir_comp_rules(struct ice_fdir_fltr *a, struct ice_fdir_fltr *b)
*
* Returns true if the filter is found in the list
*/
-bool ice_fdir_is_dup_fltr(struct ice_hw *hw, struct ice_fdir_fltr *input)
+bool ice_fdir_is_dup_fltr(struct ice_hw *hw, struct ice_ntuple_fltr *input)
{
- struct ice_fdir_fltr *rule;
+ struct ice_ntuple_fltr *rule;
bool ret = false;
list_for_each_entry(rule, &hw->fdir_list_head, fltr_node) {
diff --git a/drivers/net/ethernet/intel/ice/ice_fdir.h b/drivers/net/ethernet/intel/ice/ice_fdir.h
index 820023c0271f..54f51ae31b40 100644
--- a/drivers/net/ethernet/intel/ice/ice_fdir.h
+++ b/drivers/net/ethernet/intel/ice/ice_fdir.h
@@ -160,7 +160,7 @@ struct ice_fdir_extra {
__be16 vlan_tag; /* VLAN tag info */
};
-struct ice_fdir_fltr {
+struct ice_ntuple_fltr {
struct list_head fltr_node;
enum ice_fltr_ptype flow_type;
@@ -216,18 +216,16 @@ int ice_free_fd_res_cntr(struct ice_hw *hw, u16 cntr_id);
int ice_alloc_fd_guar_item(struct ice_hw *hw, u16 *cntr_id, u16 num_fltr);
int ice_alloc_fd_shrd_item(struct ice_hw *hw, u16 *cntr_id, u16 num_fltr);
void
-ice_fdir_get_prgm_desc(struct ice_hw *hw, struct ice_fdir_fltr *input,
+ice_fdir_get_prgm_desc(struct ice_hw *hw, struct ice_ntuple_fltr *input,
struct ice_fltr_desc *fdesc, bool add);
int
-ice_fdir_get_gen_prgm_pkt(struct ice_hw *hw, struct ice_fdir_fltr *input,
+ice_fdir_get_gen_prgm_pkt(struct ice_hw *hw, struct ice_ntuple_fltr *input,
u8 *pkt, bool frag, bool tun);
int ice_get_fdir_cnt_all(struct ice_hw *hw);
int ice_fdir_num_avail_fltr(struct ice_hw *hw, struct ice_vsi *vsi);
-bool ice_fdir_is_dup_fltr(struct ice_hw *hw, struct ice_fdir_fltr *input);
+bool ice_fdir_is_dup_fltr(struct ice_hw *hw, struct ice_ntuple_fltr *input);
bool ice_fdir_has_frag(enum ice_fltr_ptype flow);
-struct ice_fdir_fltr *
+struct ice_ntuple_fltr *
ice_fdir_find_fltr_by_idx(struct ice_hw *hw, u32 fltr_idx);
-void
-ice_fdir_update_cntrs(struct ice_hw *hw, enum ice_fltr_ptype flow, bool add);
-void ice_fdir_list_add_fltr(struct ice_hw *hw, struct ice_fdir_fltr *input);
+void ice_fdir_list_add_fltr(struct ice_hw *hw, struct ice_ntuple_fltr *input);
#endif /* _ICE_FDIR_H_ */
diff --git a/drivers/net/ethernet/intel/ice/ice_type.h b/drivers/net/ethernet/intel/ice/ice_type.h
index cf147a212707..f1a80da6239e 100644
--- a/drivers/net/ethernet/intel/ice/ice_type.h
+++ b/drivers/net/ethernet/intel/ice/ice_type.h
@@ -1019,8 +1019,8 @@ struct ice_hw {
struct mutex fl_profs_locks[ICE_BLK_COUNT]; /* lock fltr profiles */
struct list_head fl_profs[ICE_BLK_COUNT];
- /* Flow Director filter info */
- int fdir_active_fltr;
+ /* ntuple filter info */
+ int ntuple_active_fltr_cnt;
struct mutex fdir_fltr_lock; /* protect Flow Director */
struct list_head fdir_list_head;
diff --git a/drivers/net/ethernet/intel/ice/virt/fdir.c b/drivers/net/ethernet/intel/ice/virt/fdir.c
index 4f1f3442e52c..eca9eda04f31 100644
--- a/drivers/net/ethernet/intel/ice/virt/fdir.c
+++ b/drivers/net/ethernet/intel/ice/virt/fdir.c
@@ -38,7 +38,7 @@ enum ice_fdir_tunnel_type {
};
struct virtchnl_fdir_fltr_conf {
- struct ice_fdir_fltr input;
+ struct ice_ntuple_fltr input;
enum ice_fdir_tunnel_type ttype;
u64 inset_flag;
u32 flow_id;
@@ -567,12 +567,12 @@ static bool
ice_vc_fdir_has_prof_conflict(struct ice_vf *vf,
struct virtchnl_fdir_fltr_conf *conf)
{
- struct ice_fdir_fltr *desc;
+ struct ice_ntuple_fltr *desc;
list_for_each_entry(desc, &vf->fdir.fdir_rule_list, fltr_node) {
struct virtchnl_fdir_fltr_conf *existing_conf;
enum ice_fltr_ptype flow_type_a, flow_type_b;
- struct ice_fdir_fltr *a, *b;
+ struct ice_ntuple_fltr *a, *b;
existing_conf = to_fltr_conf_from_desc(desc);
a = &existing_conf->input;
@@ -748,7 +748,7 @@ static int
ice_vc_fdir_config_input_set(struct ice_vf *vf, struct virtchnl_fdir_add *fltr,
struct virtchnl_fdir_fltr_conf *conf, int tun)
{
- struct ice_fdir_fltr *input = &conf->input;
+ struct ice_ntuple_fltr *input = &conf->input;
struct device *dev = ice_pf_to_dev(vf->pf);
struct ice_flow_seg_info *seg;
enum ice_fltr_ptype flow;
@@ -924,8 +924,8 @@ ice_vc_fdir_parse_pattern(struct ice_vf *vf, struct virtchnl_fdir_add *fltr,
struct virtchnl_proto_hdrs *proto = &fltr->rule_cfg.proto_hdrs;
enum virtchnl_proto_hdr_type l3 = VIRTCHNL_PROTO_HDR_NONE;
enum virtchnl_proto_hdr_type l4 = VIRTCHNL_PROTO_HDR_NONE;
+ struct ice_ntuple_fltr *input = &conf->input;
struct device *dev = ice_pf_to_dev(vf->pf);
- struct ice_fdir_fltr *input = &conf->input;
int i;
if (proto->count > VIRTCHNL_MAX_NUM_PROTO_HDRS) {
@@ -1150,8 +1150,8 @@ ice_vc_fdir_parse_action(struct ice_vf *vf, struct virtchnl_fdir_add *fltr,
struct virtchnl_fdir_fltr_conf *conf)
{
struct virtchnl_filter_action_set *as = &fltr->rule_cfg.action_set;
+ struct ice_ntuple_fltr *input = &conf->input;
struct device *dev = ice_pf_to_dev(vf->pf);
- struct ice_fdir_fltr *input = &conf->input;
u32 dest_num = 0;
u32 mark_num = 0;
int i;
@@ -1249,8 +1249,8 @@ static bool
ice_vc_fdir_comp_rules(struct virtchnl_fdir_fltr_conf *conf_a,
struct virtchnl_fdir_fltr_conf *conf_b)
{
- struct ice_fdir_fltr *a = &conf_a->input;
- struct ice_fdir_fltr *b = &conf_b->input;
+ struct ice_ntuple_fltr *a = &conf_a->input;
+ struct ice_ntuple_fltr *b = &conf_b->input;
if (conf_a->ttype != conf_b->ttype)
return false;
@@ -1288,7 +1288,7 @@ ice_vc_fdir_comp_rules(struct virtchnl_fdir_fltr_conf *conf_a,
static bool
ice_vc_fdir_is_dup_fltr(struct ice_vf *vf, struct virtchnl_fdir_fltr_conf *conf)
{
- struct ice_fdir_fltr *desc;
+ struct ice_ntuple_fltr *desc;
bool ret;
list_for_each_entry(desc, &vf->fdir.fdir_rule_list, fltr_node) {
@@ -1317,7 +1317,7 @@ static int
ice_vc_fdir_insert_entry(struct ice_vf *vf,
struct virtchnl_fdir_fltr_conf *conf, u32 *id)
{
- struct ice_fdir_fltr *input = &conf->input;
+ struct ice_ntuple_fltr *input = &conf->input;
int i;
/* alloc ID corresponding with conf */
@@ -1341,7 +1341,7 @@ static void
ice_vc_fdir_remove_entry(struct ice_vf *vf,
struct virtchnl_fdir_fltr_conf *conf, u32 id)
{
- struct ice_fdir_fltr *input = &conf->input;
+ struct ice_ntuple_fltr *input = &conf->input;
idr_remove(&vf->fdir.fdir_rule_idr, id);
list_del(&input->fltr_node);
@@ -1367,7 +1367,7 @@ ice_vc_fdir_lookup_entry(struct ice_vf *vf, u32 id)
static void ice_vc_fdir_flush_entry(struct ice_vf *vf)
{
struct virtchnl_fdir_fltr_conf *conf;
- struct ice_fdir_fltr *desc, *temp;
+ struct ice_ntuple_fltr *desc, *temp;
list_for_each_entry_safe(desc, temp,
&vf->fdir.fdir_rule_list, fltr_node) {
@@ -1390,7 +1390,7 @@ static int ice_vc_fdir_write_fltr(struct ice_vf *vf,
struct virtchnl_fdir_fltr_conf *conf,
bool add, bool is_tun)
{
- struct ice_fdir_fltr *input = &conf->input;
+ struct ice_ntuple_fltr *input = &conf->input;
struct ice_vsi *vsi, *ctrl_vsi;
struct ice_fltr_desc desc;
struct device *dev;
@@ -2315,7 +2315,7 @@ int ice_vc_del_fdir_fltr(struct ice_vf *vf, u8 *msg)
struct virtchnl_fdir_fltr_conf *conf;
struct ice_vf_fdir *fdir = &vf->fdir;
enum virtchnl_status_code v_ret;
- struct ice_fdir_fltr *input;
+ struct ice_ntuple_fltr *input;
enum ice_fltr_ptype flow;
struct device *dev;
struct ice_pf *pf;
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 01/12] ice: rename shared Flow Director functions and structs
2026-09-17 18:39 ` [PATCH net-next v2 01/12] ice: rename shared Flow Director functions and structs Tony Nguyen
@ 2026-09-21 19:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:57 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
lukasz.czapnik, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, sx.rinitha
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Kernel-doc for the renamed ethtool entry points advertises
functionality this patch does not implement: ice_add_ntuple_ethtool()…
--
ice: rename shared Flow Director functions and structs
This renames the shared Flow Director helpers and struct ice_fdir_fltr to
use an 'ntuple' prefix, moves ice_fdir_update_cntrs() into the ethtool
file as a static ice_ntuple_update_cntrs(), and renames
ice_ethtool_fdir.c to ice_ethtool_ntuple.c. The stated goal is to prepare
these entities for also handling ACL filters.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_fdir.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> similarity index 96%
> rename from drivers/net/ethernet/intel/ice/ice_ethtool_fdir.c
> rename to drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index aceec184e89b2..744cd0b824703 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_fdir.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
[ ... ]
> @@ -1746,18 +1765,18 @@ ice_fdir_update_list_entry(struct ice_pf *pf, struct ice_fdir_fltr *input,
> ice_fdir_list_add_fltr(hw, input);
> /* update sb-filters count, specific to ring->channel */
> ice_update_per_q_fltr(vsi, input->orig_q_index, true);
> - ice_fdir_update_cntrs(hw, input->flow_type, true);
> + ice_ntuple_update_cntrs(hw, input->flow_type, true);
> return 0;
> }
>
> /**
> - * ice_del_fdir_ethtool - delete Flow Director filter
> + * ice_del_ntuple_ethtool - delete Flow Director or ACL filter
> * @vsi: pointer to target VSI
> - * @cmd: command to add or delete Flow Director filter
> + * @cmd: command to add or delete the filter
> *
> - * Returns 0 on success and negative values for failure
> + * Return: 0 on success and negative values for failure
> */
> -int ice_del_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
> +int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
[Severity: Low]
This isn't a bug, but should the kernel-doc here describe only what this
commit implements?
At this commit ice_del_ntuple_ethtool() returns -EOPNOTSUPP unless
ICE_FLAG_FD_ENA is set, and the only programming path is
ice_ntuple_update_list_entry() -> ice_fdir_write_all_fltr(), which
unconditionally does Flow Director programming and maintains
hw->fdir_fltr_cnt[]:
if (old_fltr) {
err = ice_fdir_write_all_fltr(pf, old_fltr, false);
if (err)
return err;
ice_ntuple_update_cntrs(hw, old_fltr->flow_type, false);
There is also no discriminator in struct ice_ntuple_fltr at this commit for
telling an ACL-backed rule from a Flow Director rule; the bool acl_fltr
field and the ice_del_acl_ethtool() dispatch only appear in later patches of
the series (ending with "ice: use ACL for ntuple rules that conflict with
FDir"). Would it be clearer to keep the wording Flow Director here and
update it in the patch that actually adds ACL handling?
[ ... ]
> @@ -1968,17 +1989,17 @@ ice_set_fdir_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp,
> }
>
> /**
> - * ice_add_fdir_ethtool - Add/Remove Flow Director filter
> + * ice_add_ntuple_ethtool - Add/Remove Flow Director or ACL filter
> * @vsi: pointer to target VSI
> - * @cmd: command to add or delete Flow Director filter
> + * @cmd: command to add or delete the filter
> *
> - * Returns 0 on success and negative values for failure
> + * Return: 0 on success and negative values for failure
> */
> -int ice_add_fdir_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
> +int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
[Severity: Low]
Same question for ice_add_ntuple_ethtool(). The body at this commit only
builds a Flow Director extraction sequence via ice_cfg_fdir_xtrct_seq(),
checks Flow Director capacity with ice_fdir_num_avail_fltr(), and programs
via ice_fdir_write_all_fltr(), so the "or ACL filter" part of the summary
line has no corresponding code yet.
The ACL dispatch (ice_acl_add_rule_ethtool()) arrives in a later patch of
this series, so by the end of the series the wording is accurate; is the
forward-looking wording here intentional?
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_fdir.h b/drivers/net/ethernet/intel/ice/ice_fdir.h
> index 820023c0271fd..54f51ae31b402 100644
> --- a/drivers/net/ethernet/intel/ice/ice_fdir.h
> +++ b/drivers/net/ethernet/intel/ice/ice_fdir.h
> @@ -160,7 +160,7 @@ struct ice_fdir_extra {
> __be16 vlan_tag; /* VLAN tag info */
> };
>
> -struct ice_fdir_fltr {
> +struct ice_ntuple_fltr {
> struct list_head fltr_node;
> enum ice_fltr_ptype flow_type;
>
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v2 02/12] ice: remove unused ICE_FD_FLUSH_REQ from PF state
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
2026-09-17 18:39 ` [PATCH net-next v2 01/12] ice: rename shared Flow Director functions and structs Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-18 15:43 ` Loktionov, Aleksandr
2026-09-17 18:39 ` [PATCH net-next v2 03/12] ice: initialize ACL table Tony Nguyen
` (11 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Marcin Szycik, anthony.l.nguyen, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Rinitha S
From: Marcin Szycik <marcin.szycik@linux.intel.com>
It's hard to tell what this flag was intended to represent. The commit
adding it only added a check that was always false, because the flag is
never set. There is no relevant comment and commit message doesn't
mention it. Therefore, it looks like it has always been unused. Remove
it.
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 1 -
drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c | 3 ---
2 files changed, 4 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index cb7a73bbcd71..a56e9b7c4dad 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -302,7 +302,6 @@ enum ice_pf_state {
ICE_CFG_BUSY,
ICE_SERVICE_SCHED,
ICE_SERVICE_DIS,
- ICE_FD_FLUSH_REQ,
ICE_OICR_INTR_DIS, /* Global OICR interrupt disabled */
ICE_MDD_VF_PRINT_PENDING, /* set when MDD event handle */
ICE_VF_RESETS_DISABLED, /* disable resets during ice_remove */
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
index 744cd0b82470..516b57ff3b1c 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
@@ -1793,9 +1793,6 @@ int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
return -EBUSY;
}
- if (test_bit(ICE_FD_FLUSH_REQ, pf->state))
- return -EBUSY;
-
mutex_lock(&hw->fdir_fltr_lock);
val = ice_ntuple_update_list_entry(pf, NULL, fsp->location);
mutex_unlock(&hw->fdir_fltr_lock);
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* RE: [PATCH net-next v2 02/12] ice: remove unused ICE_FD_FLUSH_REQ from PF state
2026-09-17 18:39 ` [PATCH net-next v2 02/12] ice: remove unused ICE_FD_FLUSH_REQ from PF state Tony Nguyen
@ 2026-09-18 15:43 ` Loktionov, Aleksandr
0 siblings, 0 replies; 27+ messages in thread
From: Loktionov, Aleksandr @ 2026-09-18 15:43 UTC (permalink / raw)
To: Nguyen, Anthony L, davem@davemloft.net, kuba@kernel.org,
pabeni@redhat.com, edumazet@google.com, andrew+netdev@lunn.ch,
netdev@vger.kernel.org
Cc: Marcin Szycik, Penigalapati, Sandeep, S, Ananth,
alexander.duyck@gmail.com, Rinitha, SX
> -----Original Message-----
> From: Nguyen, Anthony L <anthony.l.nguyen@intel.com>
> Sent: Thursday, September 17, 2026 8:39 PM
> To: davem@davemloft.net; kuba@kernel.org; pabeni@redhat.com;
> edumazet@google.com; andrew+netdev@lunn.ch; netdev@vger.kernel.org
> Cc: Marcin Szycik <marcin.szycik@linux.intel.com>; Nguyen, Anthony L
> <anthony.l.nguyen@intel.com>; Loktionov, Aleksandr
> <aleksandr.loktionov@intel.com>; Penigalapati, Sandeep
> <sandeep.penigalapati@intel.com>; S, Ananth <ananth.s@intel.com>;
> alexander.duyck@gmail.com; Rinitha, SX <sx.rinitha@intel.com>
> Subject: [PATCH net-next v2 02/12] ice: remove unused ICE_FD_FLUSH_REQ
> from PF state
>
> From: Marcin Szycik <marcin.szycik@linux.intel.com>
>
> It's hard to tell what this flag was intended to represent. The commit
> adding it only added a check that was always false, because the flag
> is never set. There is no relevant comment and commit message doesn't
> mention it. Therefore, it looks like it has always been unused. Remove
> it.
>
> Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
> Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at
> Intel)
> Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
> ---
> drivers/net/ethernet/intel/ice/ice.h | 1 -
> drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c | 3 ---
> 2 files changed, 4 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice.h
> b/drivers/net/ethernet/intel/ice/ice.h
> index cb7a73bbcd71..a56e9b7c4dad 100644
> --- a/drivers/net/ethernet/intel/ice/ice.h
> +++ b/drivers/net/ethernet/intel/ice/ice.h
> @@ -302,7 +302,6 @@ enum ice_pf_state {
> ICE_CFG_BUSY,
> ICE_SERVICE_SCHED,
> ICE_SERVICE_DIS,
> - ICE_FD_FLUSH_REQ,
> ICE_OICR_INTR_DIS, /* Global OICR interrupt disabled */
> ICE_MDD_VF_PRINT_PENDING, /* set when MDD event handle */
> ICE_VF_RESETS_DISABLED, /* disable resets during ice_remove
> */
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index 744cd0b82470..516b57ff3b1c 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> @@ -1793,9 +1793,6 @@ int ice_del_ntuple_ethtool(struct ice_vsi *vsi,
> struct ethtool_rxnfc *cmd)
> return -EBUSY;
> }
>
> - if (test_bit(ICE_FD_FLUSH_REQ, pf->state))
> - return -EBUSY;
> -
> mutex_lock(&hw->fdir_fltr_lock);
> val = ice_ntuple_update_list_entry(pf, NULL, fsp->location);
> mutex_unlock(&hw->fdir_fltr_lock);
> --
> 2.47.1
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v2 03/12] ice: initialize ACL table
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
2026-09-17 18:39 ` [PATCH net-next v2 01/12] ice: rename shared Flow Director functions and structs Tony Nguyen
2026-09-17 18:39 ` [PATCH net-next v2 02/12] ice: remove unused ICE_FD_FLUSH_REQ from PF state Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-21 19:57 ` netdev-bot+sashiko
2026-09-17 18:39 ` [PATCH net-next v2 04/12] ice: initialize ACL scenario Tony Nguyen
` (10 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Real Valiquette, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Chinh Cao,
Rinitha S
From: Real Valiquette <real.valiquette@intel.com>
E8xx hardware provides a Ternary Classifier block for implementing
functions such as ACL (Access Control List). In this series it's simply
referred to as "ACL".
ACL filtering can be utilized to expand support of ntuple rules by allowing
mask values to be specified for redirect to queue or drop.
Begin implementation of ACL filters by setting up structures, AdminQ
commands, and allocation of the ACL table in the hardware.
Co-developed-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Real Valiquette <real.valiquette@intel.com>
Co-developed-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/Makefile | 2 +
drivers/net/ethernet/intel/ice/ice.h | 3 +
drivers/net/ethernet/intel/ice/ice_acl.c | 136 ++++++++
drivers/net/ethernet/intel/ice/ice_acl.h | 118 +++++++
drivers/net/ethernet/intel/ice/ice_acl_ctrl.c | 316 ++++++++++++++++++
.../net/ethernet/intel/ice/ice_adminq_cmd.h | 208 +++++++++++-
drivers/net/ethernet/intel/ice/ice_main.c | 59 ++++
drivers/net/ethernet/intel/ice/ice_type.h | 3 +
8 files changed, 843 insertions(+), 2 deletions(-)
create mode 100644 drivers/net/ethernet/intel/ice/ice_acl.c
create mode 100644 drivers/net/ethernet/intel/ice/ice_acl.h
create mode 100644 drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
diff --git a/drivers/net/ethernet/intel/ice/Makefile b/drivers/net/ethernet/intel/ice/Makefile
index a952bacb7ec7..8f0a91296969 100644
--- a/drivers/net/ethernet/intel/ice/Makefile
+++ b/drivers/net/ethernet/intel/ice/Makefile
@@ -25,6 +25,8 @@ ice-y := ice_main.o \
ice_vsi_vlan_lib.o \
ice_fdir.o \
ice_ethtool_ntuple.o \
+ ice_acl.o \
+ ice_acl_ctrl.o \
ice_vlan_mode.o \
ice_flex_pipe.o \
ice_flow.o \
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index a56e9b7c4dad..e5ef7bf549df 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -157,6 +157,9 @@
#define ICE_SWITCH_FLTR_PRIO_VSI 5
#define ICE_SWITCH_FLTR_PRIO_QGRP ICE_SWITCH_FLTR_PRIO_VSI
+#define ICE_ACL_ENTIRE_SLICE 1
+#define ICE_ACL_HALF_SLICE 2
+
/* Macro for each VSI in a PF */
#define ice_for_each_vsi(pf, i) \
for ((i) = 0; (i) < (pf)->num_alloc_vsi; (i)++)
diff --git a/drivers/net/ethernet/intel/ice/ice_acl.c b/drivers/net/ethernet/intel/ice/ice_acl.c
new file mode 100644
index 000000000000..1c9ae47d9efc
--- /dev/null
+++ b/drivers/net/ethernet/intel/ice/ice_acl.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2018-2026, Intel Corporation. */
+
+#include "ice_acl.h"
+
+/**
+ * ice_aq_alloc_acl_tbl - allocate ACL table
+ * @hw: pointer to the HW struct
+ * @tbl: pointer to ice_acl_alloc_tbl struct
+ * @cd: pointer to command details structure or NULL
+ *
+ * Allocate ACL table (indirect 0x0C10)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_alloc_acl_tbl(struct ice_hw *hw, struct ice_acl_alloc_tbl *tbl,
+ struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_alloc_table *cmd;
+ struct libie_aq_desc desc;
+
+ if (!tbl->act_pairs_per_entry)
+ return -EINVAL;
+
+ if (tbl->act_pairs_per_entry > ICE_AQC_MAX_ACTION_MEMORIES)
+ return -ENOSPC;
+
+ /* If this is concurrent table, then alloc_ids buffer shall be valid and
+ * contain AllocIDs of dependent tables. 'num_dependent_alloc_ids'
+ * should be non-zero and within limit.
+ */
+ if (tbl->concurr) {
+ if (!tbl->num_dependent_alloc_ids)
+ return -EINVAL;
+ if (tbl->num_dependent_alloc_ids >
+ ICE_AQC_MAX_CONCURRENT_ACL_TBL)
+ return -ERANGE;
+ }
+
+ ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_alloc_acl_tbl);
+ desc.flags |= cpu_to_le16(LIBIE_AQ_FLAG_RD);
+
+ cmd = libie_aq_raw(&desc);
+ cmd->table_width = cpu_to_le16(tbl->width * BITS_PER_BYTE);
+ cmd->table_depth = cpu_to_le16(tbl->depth);
+ cmd->act_pairs_per_entry = tbl->act_pairs_per_entry;
+ if (tbl->concurr)
+ cmd->table_type = tbl->num_dependent_alloc_ids;
+
+ return ice_aq_send_cmd(hw, &desc, &tbl->buf, sizeof(tbl->buf), cd);
+}
+
+/**
+ * ice_aq_dealloc_acl_tbl - deallocate ACL table
+ * @hw: pointer to the HW struct
+ * @alloc_id: allocation ID of the table being released
+ * @buf: address of indirect data buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Deallocate ACL table (indirect 0x0C11)
+ *
+ * NOTE: This command has no buffer format for command itself but response
+ * format is 'struct ice_aqc_acl_generic', pass ptr to that struct
+ * as 'buf' and its size as 'buf_size'
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_dealloc_acl_tbl(struct ice_hw *hw, u16 alloc_id,
+ struct ice_aqc_acl_generic *buf,
+ struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_tbl_actpair *cmd;
+ struct libie_aq_desc desc;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_dealloc_acl_tbl);
+ cmd = libie_aq_raw(&desc);
+ cmd->alloc_id = cpu_to_le16(alloc_id);
+
+ return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
+}
+
+/**
+ * ice_aq_program_acl_entry - program ACL entry
+ * @hw: pointer to the HW struct
+ * @tcam_idx: Updated TCAM block index
+ * @entry_idx: updated entry index
+ * @buf: address of indirect data buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Program ACL entry (indirect 0x0C20)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_program_acl_entry(struct ice_hw *hw, u8 tcam_idx, u16 entry_idx,
+ struct ice_aqc_acl_data *buf, struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_entry *cmd;
+ struct libie_aq_desc desc;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_program_acl_entry);
+ desc.flags |= cpu_to_le16(LIBIE_AQ_FLAG_RD);
+
+ cmd = libie_aq_raw(&desc);
+ cmd->tcam_index = tcam_idx;
+ cmd->entry_index = cpu_to_le16(entry_idx);
+
+ return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
+}
+
+/**
+ * ice_aq_program_actpair - program ACL action pair
+ * @hw: pointer to the HW struct
+ * @act_mem_idx: action memory index to program/update/query
+ * @act_entry_idx: the entry index in action memory to be programmed/updated
+ * @buf: address of indirect data buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Program action entries (indirect 0x0C1C)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_program_actpair(struct ice_hw *hw, u8 act_mem_idx, u16 act_entry_idx,
+ struct ice_aqc_actpair *buf, struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_actpair *cmd;
+ struct libie_aq_desc desc;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_program_acl_actpair);
+ desc.flags |= cpu_to_le16(LIBIE_AQ_FLAG_RD);
+
+ cmd = libie_aq_raw(&desc);
+ cmd->act_mem_index = act_mem_idx;
+ cmd->act_entry_index = cpu_to_le16(act_entry_idx);
+
+ return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
+}
diff --git a/drivers/net/ethernet/intel/ice/ice_acl.h b/drivers/net/ethernet/intel/ice/ice_acl.h
new file mode 100644
index 000000000000..7446be5d1edb
--- /dev/null
+++ b/drivers/net/ethernet/intel/ice/ice_acl.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2018-2026, Intel Corporation. */
+
+#ifndef _ICE_ACL_H_
+#define _ICE_ACL_H_
+
+#include "ice_common.h"
+
+#define ICE_ACL_TBL_PARAMS_DEP_TBLS_MAX 15
+struct ice_acl_tbl_params {
+ u16 width; /* Select/match bytes */
+ u16 depth; /* Number of entries */
+ u16 dep_tbls[ICE_ACL_TBL_PARAMS_DEP_TBLS_MAX];
+ u8 num_dep_tbls; /* Number of valid entries in dep_tbls */
+ u8 entry_act_pairs; /* Action pairs per entry */
+ u8 concurr; /* Concurrent table lookup enable */
+};
+
+#define ICE_ACL_ACT_MEM_ACT_MEM_INVAL 0xff
+struct ice_acl_act_mem {
+ u8 act_mem;
+ u8 member_of_tcam;
+};
+
+struct ice_acl_tbl {
+ /* TCAM configuration */
+ u8 first_tcam;
+ u8 last_tcam;
+ u16 first_entry; /* Index of the first entry in the first TCAM */
+ u16 last_entry; /* Index of the last entry in the last TCAM */
+ u16 id;
+
+ /* List of active scenarios */
+ struct list_head scens;
+
+ struct ice_acl_tbl_params info;
+ struct ice_acl_act_mem act_mems[ICE_AQC_MAX_ACTION_MEMORIES];
+
+ /* Keep track of available 64-entry chunks in TCAMs */
+ DECLARE_BITMAP(avail, ICE_AQC_ACL_ALLOC_UNITS);
+};
+
+enum ice_acl_entry_prio {
+ ICE_ACL_PRIO_LOW = 0,
+ ICE_ACL_PRIO_NORMAL,
+ ICE_ACL_PRIO_HIGH,
+ ICE_ACL_MAX_PRIO
+};
+
+#define ICE_ACL_SCEN_MIN_WIDTH 0x3
+#define ICE_ACL_SCEN_PKT_DIR_IDX_IN_TCAM 0x2
+#define ICE_ACL_SCEN_PID_IDX_IN_TCAM 0x3
+#define ICE_ACL_SCEN_RNG_CHK_IDX_IN_TCAM 0x4
+/* Scenario structure
+ * A scenario is a logical partition within an ACL table. It can span more
+ * than one TCAM in cascade mode to support select/mask key widths larger
+ * than the width of a TCAM. It can also span more than one TCAM in stacked
+ * mode to support larger number of entries than what a TCAM can hold. It is
+ * used to select values from selection bases (field vectors holding extract
+ * protocol header fields) to form lookup keys, and to associate action memory
+ * banks to the TCAMs used.
+ */
+struct ice_acl_scen {
+ struct list_head list_entry;
+ /* If nth bit of act_mem_bitmap is set, then nth action memory will
+ * participate in this scenario
+ */
+ DECLARE_BITMAP(act_mem_bitmap, ICE_AQC_MAX_ACTION_MEMORIES);
+ u16 first_idx[ICE_ACL_MAX_PRIO];
+ u16 last_idx[ICE_ACL_MAX_PRIO];
+
+ u16 id;
+ u16 start; /* Number of entry from the start of the parent table */
+ u16 width; /* Number of select/mask bytes */
+ u16 num_entry; /* Number of scenario entry */
+ u16 end; /* Last addressable entry from start of table */
+ u8 eff_width; /* Available width in bytes to match */
+ u8 pid_idx; /* Byte index used to match profile ID */
+ u8 rng_chk_idx; /* Byte index used to match range checkers result */
+ u8 pkt_dir_idx; /* Byte index used to match packet direction */
+};
+
+/* Input fields needed to allocate ACL table */
+struct ice_acl_alloc_tbl {
+ /* Table's width in number of bytes matched */
+ u16 width;
+ /* Table's depth in number of entries. */
+ u16 depth;
+ u8 num_dependent_alloc_ids;
+ /* true for concurrent table type */
+ u8 concurr;
+
+ /* Amount of action pairs per table entry. Minimal valid
+ * value for this field is 1 (e.g. single pair of actions)
+ */
+ u8 act_pairs_per_entry;
+ union {
+ struct ice_aqc_acl_alloc_table_data data_buf;
+ struct ice_aqc_acl_generic resp_buf;
+ } buf;
+};
+
+int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params);
+int ice_acl_destroy_tbl(struct ice_hw *hw);
+int ice_aq_alloc_acl_tbl(struct ice_hw *hw, struct ice_acl_alloc_tbl *tbl,
+ struct ice_sq_cd *cd);
+int ice_aq_dealloc_acl_tbl(struct ice_hw *hw, u16 alloc_id,
+ struct ice_aqc_acl_generic *buf,
+ struct ice_sq_cd *cd);
+int ice_aq_program_acl_entry(struct ice_hw *hw, u8 tcam_idx, u16 entry_idx,
+ struct ice_aqc_acl_data *buf,
+ struct ice_sq_cd *cd);
+int ice_aq_program_actpair(struct ice_hw *hw, u8 act_mem_idx, u16 act_entry_idx,
+ struct ice_aqc_actpair *buf, struct ice_sq_cd *cd);
+int ice_aq_alloc_acl_scen(struct ice_hw *hw, u16 *scen_id,
+ struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd);
+
+#endif /* _ICE_ACL_H_ */
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
new file mode 100644
index 000000000000..9984c07b53f0
--- /dev/null
+++ b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2018-2026, Intel Corporation. */
+
+#include "ice_acl.h"
+
+/* Determine the TCAM index of entry 'e' within the ACL table */
+#define ICE_ACL_TBL_TCAM_IDX(e) ((e) / ICE_AQC_ACL_TCAM_DEPTH)
+
+/**
+ * ice_acl_init_tbl - initialize ACL table
+ * @hw: pointer to the hardware structure
+ *
+ * Invalidate TCAM entries and action pairs.
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_acl_init_tbl(struct ice_hw *hw)
+{
+ struct ice_aqc_actpair act_buf = {};
+ struct ice_aqc_acl_data buf = {};
+ struct ice_acl_tbl *tbl;
+ u8 tcam_idx;
+ int err = 0;
+ u16 idx;
+
+ tbl = hw->acl_tbl;
+
+ tcam_idx = tbl->first_tcam;
+ idx = tbl->first_entry;
+ while (tcam_idx < tbl->last_tcam ||
+ (tcam_idx == tbl->last_tcam && idx <= tbl->last_entry)) {
+ /* Use the same value for entry_key and entry_key_inv since
+ * we are initializing the fields to 0
+ */
+ err = ice_aq_program_acl_entry(hw, tcam_idx, idx, &buf, NULL);
+ if (err)
+ return err;
+
+ if (++idx > tbl->last_entry) {
+ tcam_idx++;
+ idx = tbl->first_entry;
+ }
+ }
+
+ for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++) {
+ u16 act_entry_idx;
+
+ if (tbl->act_mems[i].act_mem == ICE_ACL_ACT_MEM_ACT_MEM_INVAL)
+ continue;
+
+ for (act_entry_idx = tbl->first_entry;
+ act_entry_idx <= tbl->last_entry; act_entry_idx++) {
+ /* Invalidate all allocated action pairs */
+ err = ice_aq_program_actpair(hw, i, act_entry_idx,
+ &act_buf, NULL);
+ if (err)
+ return err;
+ }
+ }
+
+ return err;
+}
+
+/**
+ * ice_acl_assign_act_mems_to_tcam - assign number of action memories to TCAM
+ * @tbl: pointer to ACL table structure
+ * @cur_tcam: Index of current TCAM. Value = 0 to (ICE_AQC_ACL_SLICES - 1)
+ * @cur_mem_idx: Index of current action memory bank. Value = 0 to
+ * (ICE_AQC_MAX_ACTION_MEMORIES - 1)
+ * @num_mem: Number of action memory banks for this TCAM
+ *
+ * Assign "num_mem" valid action memory banks from "curr_mem_idx" to
+ * "curr_tcam" TCAM.
+ */
+static void
+ice_acl_assign_act_mems_to_tcam(struct ice_acl_tbl *tbl, u8 cur_tcam,
+ u8 *cur_mem_idx, u8 num_mem)
+{
+ u8 mem_cnt;
+
+ for (mem_cnt = 0;
+ *cur_mem_idx < ICE_AQC_MAX_ACTION_MEMORIES && mem_cnt < num_mem;
+ (*cur_mem_idx)++) {
+ struct ice_acl_act_mem *p_mem = &tbl->act_mems[*cur_mem_idx];
+
+ if (p_mem->act_mem == ICE_ACL_ACT_MEM_ACT_MEM_INVAL)
+ continue;
+
+ p_mem->member_of_tcam = cur_tcam;
+
+ mem_cnt++;
+ }
+}
+
+/**
+ * ice_acl_divide_act_mems_to_tcams - assign action memory banks to TCAMs
+ * @tbl: pointer to ACL table structure
+ *
+ * Figure out how to divide given action memory banks to given TCAMs. This
+ * division is for SW book keeping. In the time when scenario is created,
+ * an action memory bank can be used for different TCAM.
+ *
+ * For example, given that we have 2x2 ACL table with each table entry has
+ * 2 action memory pairs. As the result, we will have 4 TCAMs (T1,T2,T3,T4)
+ * and 4 action memory banks (A1,A2,A3,A4)
+ * [T1 - T2] { A1 - A2 }
+ * [T3 - T4] { A3 - A4 }
+ * In the time when we need to create a scenario, for example, 2x1 scenario,
+ * we will use [T3,T4] in a cascaded layout. As it is a requirement that all
+ * action memory banks in a cascaded TCAM's row will need to associate with
+ * the last TCAM. Thus, we will associate action memory banks [A3] and [A4]
+ * for TCAM [T4].
+ * For SW book-keeping purpose, we will keep theoretical maps between TCAM
+ * [Tn] to action memory bank [An].
+ */
+static void ice_acl_divide_act_mems_to_tcams(struct ice_acl_tbl *tbl)
+{
+ u16 num_cscd, stack_level, stack_idx, min_act_mem;
+ u8 tcam_idx = tbl->first_tcam;
+ u16 max_idx_to_get_extra;
+ u8 mem_idx = 0;
+
+ /* Determine number of stacked TCAMs */
+ stack_level = DIV_ROUND_UP(tbl->info.depth, ICE_AQC_ACL_TCAM_DEPTH);
+
+ /* Determine number of cascaded TCAMs */
+ num_cscd = DIV_ROUND_UP(tbl->info.width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
+
+ /* In a line of cascaded TCAM, given the number of action memory
+ * banks per ACL table entry, we want to fairly divide these action
+ * memory banks between these TCAMs.
+ *
+ * For example, there are 3 TCAMs (TCAM 3,4,5) in a line of
+ * cascaded TCAM, and there are 7 act_mems for each ACL table entry.
+ * The result is:
+ * [TCAM_3 will have 3 act_mems]
+ * [TCAM_4 will have 2 act_mems]
+ * [TCAM_5 will have 2 act_mems]
+ */
+ min_act_mem = tbl->info.entry_act_pairs / num_cscd;
+ max_idx_to_get_extra = tbl->info.entry_act_pairs % num_cscd;
+
+ for (stack_idx = 0; stack_idx < stack_level; stack_idx++) {
+ u16 i;
+
+ for (i = 0; i < num_cscd; i++) {
+ u8 total_act_mem = min_act_mem;
+
+ if (i < max_idx_to_get_extra)
+ total_act_mem++;
+
+ ice_acl_assign_act_mems_to_tcam(tbl, tcam_idx,
+ &mem_idx,
+ total_act_mem);
+
+ tcam_idx++;
+ }
+ }
+}
+
+/**
+ * ice_acl_create_tbl - create ACL table
+ * @hw: pointer to the HW struct
+ * @params: parameters for the table to be created
+ *
+ * Create a LEM table for ACL usage. We are currently starting with some fixed
+ * values for the size of the table, but this will need to grow as more flow
+ * entries are added by the user level.
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params)
+{
+ struct ice_acl_alloc_tbl tbl_alloc = {};
+ struct ice_aqc_acl_generic *resp_buf;
+ u16 width, depth, first_e, last_e;
+ struct ice_acl_tbl *tbl;
+ u16 alloc_id;
+ int err;
+
+ if (hw->acl_tbl)
+ return -EEXIST;
+
+ /* round up the width to the next TCAM width boundary. */
+ width = roundup(params->width, (u16)ICE_AQC_ACL_KEY_WIDTH_BYTES);
+ /* depth should be provided in chunk (64 entry) increments */
+ depth = ALIGN(params->depth, ICE_ACL_ENTRY_ALLOC_UNIT);
+
+ if (params->entry_act_pairs < width / ICE_AQC_ACL_KEY_WIDTH_BYTES) {
+ params->entry_act_pairs = width / ICE_AQC_ACL_KEY_WIDTH_BYTES;
+
+ if (params->entry_act_pairs > ICE_AQC_TBL_MAX_ACTION_PAIRS)
+ params->entry_act_pairs = ICE_AQC_TBL_MAX_ACTION_PAIRS;
+ }
+
+ /* Validate that width*depth will not exceed the TCAM limit */
+ if ((DIV_ROUND_UP(depth, ICE_AQC_ACL_TCAM_DEPTH) *
+ (width / ICE_AQC_ACL_KEY_WIDTH_BYTES)) > ICE_AQC_ACL_SLICES)
+ return -ENOSPC;
+
+ tbl_alloc.width = width;
+ tbl_alloc.depth = depth;
+ tbl_alloc.act_pairs_per_entry = params->entry_act_pairs;
+ tbl_alloc.concurr = params->concurr;
+
+ if (params->concurr) {
+ int i;
+
+ tbl_alloc.num_dependent_alloc_ids = params->num_dep_tbls;
+
+ for (i = 0; i < params->num_dep_tbls; i++)
+ tbl_alloc.buf.data_buf.alloc_ids[i] =
+ cpu_to_le16(params->dep_tbls[i]);
+
+ for (; i < ICE_AQC_MAX_CONCURRENT_ACL_TBL; i++)
+ tbl_alloc.buf.data_buf.alloc_ids[i] =
+ cpu_to_le16(ICE_AQC_CONCURR_ID_INVALID);
+ }
+
+ err = ice_aq_alloc_acl_tbl(hw, &tbl_alloc, NULL);
+ if (err) {
+ dev_err(ice_hw_to_dev(hw), "ACL table allocation failed with error %d\n",
+ err);
+ return err;
+ }
+
+ alloc_id = le16_to_cpu(tbl_alloc.buf.resp_buf.alloc_id);
+ if (alloc_id < ICE_AQC_ALLOC_ID_4K) {
+ dev_err(ice_hw_to_dev(hw), "ACL table allocation failed due to unavailable resources.\n");
+ return -ENOMEM;
+ }
+
+ resp_buf = &tbl_alloc.buf.resp_buf;
+
+ tbl = kzalloc_obj(*tbl);
+ if (!tbl) {
+ err = -ENOMEM;
+ goto err_dealloc_tbl;
+ }
+
+ /* Retrieve information of the allocated table */
+ tbl->id = alloc_id;
+ tbl->first_tcam = resp_buf->ops.table.first_tcam;
+ tbl->last_tcam = resp_buf->ops.table.last_tcam;
+ tbl->first_entry = le16_to_cpu(resp_buf->first_entry);
+ tbl->last_entry = le16_to_cpu(resp_buf->last_entry);
+
+ tbl->info = *params;
+ tbl->info.width = width;
+ tbl->info.depth = depth;
+ hw->acl_tbl = tbl;
+
+ for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++)
+ tbl->act_mems[i].act_mem = resp_buf->act_mem[i];
+
+ /* Figure out which TCAMs that these newly allocated action memories
+ * belong to.
+ */
+ ice_acl_divide_act_mems_to_tcams(tbl);
+
+ /* Initialize the resources allocated by invalidating all TCAM entries
+ * and all the action pairs
+ */
+ err = ice_acl_init_tbl(hw);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "Initialization of TCAM entries failed. status: %d\n",
+ err);
+ goto err_free_tbl;
+ }
+
+ first_e = (tbl->first_tcam * ICE_AQC_MAX_TCAM_ALLOC_UNITS) +
+ (tbl->first_entry / ICE_ACL_ENTRY_ALLOC_UNIT);
+ last_e = (tbl->last_tcam * ICE_AQC_MAX_TCAM_ALLOC_UNITS) +
+ (tbl->last_entry / ICE_ACL_ENTRY_ALLOC_UNIT);
+
+ /* Indicate available entries in the table */
+ bitmap_set(tbl->avail, first_e, last_e - first_e + 1);
+
+ INIT_LIST_HEAD(&tbl->scens);
+
+ return 0;
+
+err_free_tbl:
+ hw->acl_tbl = NULL;
+ kfree(tbl);
+err_dealloc_tbl:
+ ice_aq_dealloc_acl_tbl(hw, alloc_id, resp_buf, NULL);
+ return err;
+}
+
+/**
+ * ice_acl_destroy_tbl - Destroy a previously created LEM table for ACL
+ * @hw: pointer to the HW struct
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_acl_destroy_tbl(struct ice_hw *hw)
+{
+ struct ice_aqc_acl_generic resp_buf;
+ int err;
+
+ if (!hw->acl_tbl)
+ return -ENOENT;
+
+ err = ice_aq_dealloc_acl_tbl(hw, hw->acl_tbl->id, &resp_buf, NULL);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL failed. status: %d\n",
+ err);
+ return err;
+ }
+
+ kfree(hw->acl_tbl);
+ hw->acl_tbl = NULL;
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
index 42878abac9eb..f98780afa139 100644
--- a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
+++ b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
@@ -303,6 +303,7 @@ struct ice_aqc_vsi_props {
#define ICE_AQ_VSI_PROP_RXQ_MAP_VALID BIT(6)
#define ICE_AQ_VSI_PROP_Q_OPT_VALID BIT(7)
#define ICE_AQ_VSI_PROP_OUTER_UP_VALID BIT(8)
+#define ICE_AQ_VSI_PROP_ACL_VALID BIT(10)
#define ICE_AQ_VSI_PROP_FLOW_DIR_VALID BIT(11)
#define ICE_AQ_VSI_PROP_PASID_VALID BIT(12)
/* switch section */
@@ -423,8 +424,10 @@ struct ice_aqc_vsi_props {
u8 q_opt_reserved[3];
/* outer up section */
__le32 outer_up_table; /* same structure and defines as ingress tbl */
- /* section 10 */
- __le16 sect_10_reserved;
+ /* ACL section */
+ __le16 acl_def_act;
+#define ICE_AQ_VSI_ACL_DEF_RX_PROF_M GENMASK(3, 0)
+#define ICE_AQ_VSI_ACL_DEF_RX_TABLE_M GENMASK(7, 4)
/* flow director section */
__le16 fd_options;
#define ICE_AQ_VSI_FD_ENABLE BIT(0)
@@ -1977,6 +1980,199 @@ struct ice_aqc_neigh_dev_req {
__le32 addr_low;
};
+/* Allocate ACL table (indirect 0x0C10) */
+#define ICE_AQC_ACL_KEY_WIDTH_BYTES 5
+#define ICE_AQC_ACL_TCAM_DEPTH 512
+#define ICE_ACL_ENTRY_ALLOC_UNIT 64
+#define ICE_AQC_MAX_CONCURRENT_ACL_TBL 15
+#define ICE_AQC_MAX_ACTION_MEMORIES 20
+#define ICE_AQC_ACL_SLICES 16
+#define ICE_AQC_ALLOC_ID_4K 0x1000
+/* The ACL block supports up to 8 actions per a single output. */
+#define ICE_AQC_TBL_MAX_ACTION_PAIRS 4
+
+#define ICE_AQC_MAX_TCAM_ALLOC_UNITS (ICE_AQC_ACL_TCAM_DEPTH / \
+ ICE_ACL_ENTRY_ALLOC_UNIT)
+#define ICE_AQC_ACL_ALLOC_UNITS (ICE_AQC_ACL_SLICES * \
+ ICE_AQC_MAX_TCAM_ALLOC_UNITS)
+
+struct ice_aqc_acl_alloc_table {
+ __le16 table_width;
+ __le16 table_depth;
+ u8 act_pairs_per_entry;
+ u8 table_type;
+ __le16 reserved;
+ __le32 addr_high;
+ __le32 addr_low;
+};
+
+#define ICE_AQC_CONCURR_ID_INVALID 0xffff
+/* Allocate ACL table command buffer format */
+struct ice_aqc_acl_alloc_table_data {
+ /* Dependent table AllocIDs. Each word in this 15 word array specifies
+ * a dependent table AllocID according to the amount specified in the
+ * "table_type" field. All unused words shall be set to
+ * ICE_AQC_CONCURR_ID_INVALID
+ */
+ __le16 alloc_ids[ICE_AQC_MAX_CONCURRENT_ACL_TBL];
+};
+
+/* Deallocate ACL table (indirect 0x0C11) */
+
+/* Following structure is common and used in case of deallocation
+ * of ACL table and action-pair
+ */
+struct ice_aqc_acl_tbl_actpair {
+ __le16 alloc_id;
+ u8 reserved[6];
+ __le32 addr_high;
+ __le32 addr_low;
+};
+
+/* This response structure is same in case of alloc/dealloc table,
+ * alloc/dealloc action-pair
+ */
+struct ice_aqc_acl_generic {
+ /* if alloc_id is below 0x1000 then allocation failed due to
+ * unavailable resources, else this is set by FW to identify
+ * table allocation
+ */
+ __le16 alloc_id;
+
+ union {
+ /* to be used only in case of alloc/dealloc table */
+ struct {
+ /* Set to 0xFF for a failed allocation */
+ u8 first_tcam;
+ /* This index shall be set to the value of first_tcam
+ * for single TCAM block allocation, otherwise set to
+ * 0xFF for a failed allocation.
+ */
+ u8 last_tcam;
+ } table;
+ /* reserved in case of alloc/dealloc action-pair */
+ struct {
+ __le16 reserved;
+ } act_pair;
+ } ops;
+
+ /* index of first entry (in both TCAM and action memories),
+ * otherwise set to 0xFF for a failed allocation
+ */
+ __le16 first_entry;
+ /* index of last entry (in both TCAM and action memories),
+ * otherwise set to 0xFF for a failed allocation
+ */
+ __le16 last_entry;
+
+ /* Each act_mem element specifies the order of the memory
+ * otherwise 0xFF
+ */
+ u8 act_mem[ICE_AQC_MAX_ACTION_MEMORIES];
+};
+
+/* Update ACL scenario (direct 0x0C1B)
+ * Query ACL scenario (direct 0x0C23)
+ */
+struct ice_aqc_acl_update_query_scen {
+ __le16 scen_id;
+ u8 reserved[6];
+ __le32 addr_high;
+ __le32 addr_low;
+};
+
+#define ICE_AQC_ACL_BYTE_SEL_BASE 0x20
+#define ICE_AQC_ACL_BYTE_SEL_BASE_PID 0x3E
+#define ICE_AQC_ACL_BYTE_SEL_BASE_PKT_DIR ICE_AQC_ACL_BYTE_SEL_BASE
+#define ICE_AQC_ACL_BYTE_SEL_BASE_RNG_CHK 0x3F
+
+#define ICE_AQC_ACL_ALLOC_SCE_START_CMP BIT(0)
+#define ICE_AQC_ACL_ALLOC_SCE_START_SET BIT(1)
+
+#define ICE_AQC_ACL_SCE_ACT_MEM_EN BIT(7)
+
+/* Input buffer format in case allocate/update ACL scenario and same format
+ * is used for response buffer in case of query ACL scenario.
+ * NOTE: de-allocate ACL scenario is direct command and doesn't require
+ * "buffer", hence no buffer format.
+ */
+struct ice_aqc_acl_scen {
+ struct {
+ /* Byte [x] selection for the TCAM key. This value must be set
+ * to 0x0 for unused TCAM.
+ * Only Bit 6..0 is used in each byte and MSB is reserved
+ */
+ u8 tcam_select[5];
+ /* TCAM Block entry masking. This value should be set to 0x0 for
+ * unused TCAM
+ */
+ u8 chnk_msk;
+ /* Bit 0 : masks TCAM entries 0-63
+ * Bit 1 : masks TCAM entries 64-127
+ * Bit 2 to 7 : follow the pattern of bit 0 and 1
+ */
+ u8 start_cmp_set;
+ } tcam_cfg[ICE_AQC_ACL_SLICES];
+
+ /* Each byte, Bit 6..0: action memory association to a TCAM block,
+ * otherwise it shall be set to 0x0 for disabled memory action.
+ * Bit 7 (ICE_AQC_ACL_SCE_ACT_MEM_EN): action memory enable for this
+ * scenario
+ */
+ u8 act_mem_cfg[ICE_AQC_MAX_ACTION_MEMORIES];
+};
+
+/* Program ACL actionpair (indirect 0x0C1C) */
+struct ice_aqc_acl_actpair {
+ u8 act_mem_index;
+ u8 reserved;
+ /* Entry index in action memory */
+ __le16 act_entry_index;
+ __le32 reserved2;
+ __le32 addr_high;
+ __le32 addr_low;
+};
+
+/* Input buffer format for program/query action-pair admin command */
+struct ice_acl_act_entry {
+ /* Action priority, values must be between 0..7 */
+ u8 prio;
+ /* Action meta-data identifier. This field should be set to 0x0
+ * for a NOP action
+ */
+ u8 mdid;
+ __le16 value;
+};
+
+#define ICE_ACL_NUM_ACT_PER_ACT_PAIR 2
+struct ice_aqc_actpair {
+ struct ice_acl_act_entry act[ICE_ACL_NUM_ACT_PER_ACT_PAIR];
+};
+
+/* Program ACL entry (indirect 0x0C20) */
+struct ice_aqc_acl_entry {
+ u8 tcam_index;
+ u8 reserved;
+ __le16 entry_index;
+ __le32 reserved2;
+ __le32 addr_high;
+ __le32 addr_low;
+};
+
+/* Input buffer format in case of program ACL entry and response buffer format
+ * in case of query ACL entry
+ */
+struct ice_aqc_acl_data {
+ /* Entry key and entry key invert are 40 bits wide.
+ * Byte 0..4 : entry key and Byte 5..7 are reserved
+ * Byte 8..12: entry key invert and Byte 13..15 are reserved
+ */
+ struct {
+ u8 val[5];
+ u8 reserved[3];
+ } entry_key, entry_key_invert;
+};
+
/* Add Tx LAN Queues (indirect 0x0C30) */
struct ice_aqc_add_txqs {
u8 num_qgrps;
@@ -2642,6 +2838,14 @@ enum ice_adminq_opc {
/* Sideband Control Interface commands */
ice_aqc_opc_neighbour_device_request = 0x0C00,
+ /* ACL commands */
+ ice_aqc_opc_alloc_acl_tbl = 0x0C10,
+ ice_aqc_opc_dealloc_acl_tbl = 0x0C11,
+ ice_aqc_opc_update_acl_scen = 0x0C1B,
+ ice_aqc_opc_program_acl_actpair = 0x0C1C,
+ ice_aqc_opc_program_acl_entry = 0x0C20,
+ ice_aqc_opc_query_acl_scen = 0x0C23,
+
/* Tx queue handling commands/events */
ice_aqc_opc_add_txqs = 0x0C30,
ice_aqc_opc_dis_txqs = 0x0C31,
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index 707c7431b91a..981c105e1d3a 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -17,6 +17,7 @@
#include "devlink/port.h"
#include "ice_sf_eth.h"
#include "ice_hwmon.h"
+#include "ice_acl.h"
/* Including ice_trace.h with CREATE_TRACE_POINTS defined will generate the
* ice tracepoint functions. This must be done exactly once across the
* ice driver.
@@ -4328,6 +4329,59 @@ static int ice_send_version(struct ice_pf *pf)
return ice_aq_send_driver_ver(&pf->hw, &dv, NULL);
}
+/**
+ * ice_acl_create_hw - create ACL HW table and scenario
+ * @pf: ptr to PF device
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_acl_create_hw(struct ice_pf *pf)
+{
+ struct ice_acl_tbl_params params = {};
+ struct ice_hw *hw = &pf->hw;
+ int divider;
+
+ /* Create a single ACL table that consists of src_ip (4 bytes),
+ * dest_ip (4 bytes), src_port (2 bytes) and dst_port (2 bytes) for a
+ * total of 12 bytes (96 bits), hence 120 bit wide keys, i.e. 3 TCAM
+ * slices. If the NIC contains less than 8 PFs, then each PF will have
+ * its own TCAM slices. For 8 PFs, a given slice will be shared by 2
+ * different PFs.
+ */
+ if (hw->dev_caps.num_funcs < 8)
+ divider = ICE_ACL_ENTIRE_SLICE;
+ else
+ divider = ICE_ACL_HALF_SLICE;
+
+ params.width = ICE_AQC_ACL_KEY_WIDTH_BYTES * 3;
+ params.depth = ICE_AQC_ACL_TCAM_DEPTH / divider;
+ params.entry_act_pairs = 1;
+ params.concurr = false;
+ params.num_dep_tbls = 0;
+
+ return ice_acl_create_tbl(hw, ¶ms);
+}
+
+/**
+ * ice_init_acl - initialize the ACL block
+ * @pf: ptr to PF device
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_init_acl(struct ice_pf *pf)
+{
+ return ice_acl_create_hw(pf);
+}
+
+/**
+ * ice_deinit_acl - unroll the initialization of the ACL block
+ * @pf: ptr to PF device
+ */
+static void ice_deinit_acl(struct ice_pf *pf)
+{
+ ice_acl_destroy_tbl(&pf->hw);
+}
+
/**
* ice_init_fdir - Initialize flow director VSI and configuration
* @pf: pointer to the PF instance
@@ -4748,6 +4802,10 @@ static void ice_init_features(struct ice_pf *pf)
if (ice_init_fdir(pf))
dev_err(dev, "could not initialize flow director\n");
+ /* Note: ACL init failure is non-fatal to load */
+ if (ice_init_acl(pf))
+ dev_err(dev, "Failed to initialize ACL\n");
+
/* Note: DCB init failure is non-fatal to load */
if (ice_init_pf_dcb(pf, false)) {
clear_bit(ICE_FLAG_DCB_CAPABLE, pf->flags);
@@ -4770,6 +4828,7 @@ static void ice_deinit_features(struct ice_pf *pf)
ice_deinit_lag(pf);
if (test_bit(ICE_FLAG_DCB_CAPABLE, pf->flags))
ice_cfg_lldp_mib_change(&pf->hw, false);
+ ice_deinit_acl(pf);
ice_deinit_fdir(pf);
if (ice_is_feature_supported(pf, ICE_F_GNSS))
ice_gnss_exit(pf);
diff --git a/drivers/net/ethernet/intel/ice/ice_type.h b/drivers/net/ethernet/intel/ice/ice_type.h
index f1a80da6239e..34c381dac547 100644
--- a/drivers/net/ethernet/intel/ice/ice_type.h
+++ b/drivers/net/ethernet/intel/ice/ice_type.h
@@ -54,6 +54,7 @@ static inline u32 ice_round_to_num(u32 N, u32 R)
#define ICE_DBG_RDMA BIT_ULL(15)
#define ICE_DBG_PKG BIT_ULL(16)
#define ICE_DBG_RES BIT_ULL(17)
+#define ICE_DBG_ACL BIT_ULL(18)
#define ICE_DBG_PTP BIT_ULL(19)
#define ICE_DBG_AQ_MSG BIT_ULL(24)
#define ICE_DBG_AQ_DESC BIT_ULL(25)
@@ -1011,6 +1012,8 @@ struct ice_hw {
struct udp_tunnel_nic_shared udp_tunnel_shared;
struct udp_tunnel_nic_info udp_tunnel_nic;
+ struct ice_acl_tbl *acl_tbl;
+
/* dvm boost update information */
struct ice_dvm_table dvm_upd;
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 03/12] ice: initialize ACL table
2026-09-17 18:39 ` [PATCH net-next v2 03/12] ice: initialize ACL table Tony Nguyen
@ 2026-09-21 19:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:57 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
lukasz.czapnik, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, sx.rinitha
Thank you for your contribution! Sashiko AI review found 10 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 3 · Low: 7
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] ice_acl_create_tbl() validates only alloc_id from the 0x0C10
response and then uses resp_buf->ops.table.first_tcam/last_tcam and…
- [Medium] The ACL table is allocated and fully zero-initialized entry-
by-entry at probe for every PF, unconditionally and regardless of…
- [Medium] The available-chunk bitmap is filled with one contiguous
bitmap_set() from (first_tcam,first_entry) to (last_tcam,last_entry)…
- [Low] ACL table is created only in ice_init_features() and destroyed
only in ice_deinit_features(), so a PFR/CORER/GLOBR (or…
- [Low] ice_acl_create_tbl() mutates the caller-owned `params` struct
asymmetrically: params->entry_act_pairs is overwritten with…
- [Low] In ice_acl_create_tbl() the concurrent-table branch
(ice_acl_ctrl.c:206-218) copies params->num_dep_tbls entries into the…
- [Low] In struct ice_aqc_acl_scen the 64-entry chunk-mask bit
description ("Bit 0 : masks TCAM entries 0-63 / Bit 1 : masks TCAM…
- [Low] ICE_AQC_TBL_MAX_ACTION_PAIRS (4, documented as "The ACL block
supports up to 8 actions per a single output",…
- [Low] ice_acl_destroy_tbl() declares `struct ice_aqc_acl_generic
resp_buf;` (ice_acl_ctrl.c:299) without an initializer and passes it…
- [Low] ice_acl_destroy_tbl() returns early on AdminQ de-allocation
failure (`ice_debug(...); return err;`, ice_acl_ctrl.c:306-310)…
--
ice: initialize ACL table
This adds the structures, AdminQ command wrappers and table allocation for
the E8xx Ternary Classifier ("ACL") block, and creates the ACL table at
probe time from ice_init_features(). A few questions below about the
firmware response handling, the chunk bitmap geometry and the teardown
path.
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl.c b/drivers/net/ethernet/intel/ice/ice_acl.c
> new file mode 100644
> index 0000000000000..1c9ae47d9efc1
> --- /dev/null
> +++ b/drivers/net/ethernet/intel/ice/ice_acl.c
> @@ -0,0 +1,136 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2018-2026, Intel Corporation. */
> +
> +#include "ice_acl.h"
> +
> +/**
> + * ice_aq_alloc_acl_tbl - allocate ACL table
> + * @hw: pointer to the HW struct
> + * @tbl: pointer to ice_acl_alloc_tbl struct
> + * @cd: pointer to command details structure or NULL
> + *
> + * Allocate ACL table (indirect 0x0C10)
> + *
> + * Return: 0 on success, negative on error
> + */
> +int ice_aq_alloc_acl_tbl(struct ice_hw *hw, struct ice_acl_alloc_tbl *tbl,
> + struct ice_sq_cd *cd)
> +{
> + struct ice_aqc_acl_alloc_table *cmd;
> + struct libie_aq_desc desc;
> +
> + if (!tbl->act_pairs_per_entry)
> + return -EINVAL;
> +
> + if (tbl->act_pairs_per_entry > ICE_AQC_MAX_ACTION_MEMORIES)
> + return -ENOSPC;
[Severity: Low]
Should this bound be ICE_AQC_TBL_MAX_ACTION_PAIRS (4) rather than
ICE_AQC_MAX_ACTION_MEMORIES (20)?
ICE_AQC_TBL_MAX_ACTION_PAIRS is documented in ice_adminq_cmd.h as the
per-entry action-pair maximum, and ice_acl_create_tbl() only applies that
clamp inside the branch that raises the value:
if (params->entry_act_pairs < width / ICE_AQC_ACL_KEY_WIDTH_BYTES) {
params->entry_act_pairs = width / ICE_AQC_ACL_KEY_WIDTH_BYTES;
if (params->entry_act_pairs > ICE_AQC_TBL_MAX_ACTION_PAIRS)
params->entry_act_pairs = ICE_AQC_TBL_MAX_ACTION_PAIRS;
}
So a caller-supplied entry_act_pairs of 5..20 skips the clamp entirely and
is accepted here. The only in-tree caller passes 1, so nothing misbehaves
today, but the two limits contradict each other. An out-of-contract value
would also feed ice_acl_divide_act_mems_to_tcams(), which divides
tbl->info.entry_act_pairs across num_cscd TCAMs.
> +
> + /* If this is concurrent table, then alloc_ids buffer shall be valid and
> + * contain AllocIDs of dependent tables. 'num_dependent_alloc_ids'
> + * should be non-zero and within limit.
> + */
> + if (tbl->concurr) {
> + if (!tbl->num_dependent_alloc_ids)
> + return -EINVAL;
> + if (tbl->num_dependent_alloc_ids >
> + ICE_AQC_MAX_CONCURRENT_ACL_TBL)
> + return -ERANGE;
> + }
> +
[ ... ]
> +/**
> + * ice_aq_dealloc_acl_tbl - deallocate ACL table
> + * @hw: pointer to the HW struct
> + * @alloc_id: allocation ID of the table being released
> + * @buf: address of indirect data buffer
> + * @cd: pointer to command details structure or NULL
> + *
> + * Deallocate ACL table (indirect 0x0C11)
> + *
> + * NOTE: This command has no buffer format for command itself but response
> + * format is 'struct ice_aqc_acl_generic', pass ptr to that struct
> + * as 'buf' and its size as 'buf_size'
> + *
> + * Return: 0 on success, negative on error
> + */
> +int ice_aq_dealloc_acl_tbl(struct ice_hw *hw, u16 alloc_id,
> + struct ice_aqc_acl_generic *buf,
> + struct ice_sq_cd *cd)
> +{
> + struct ice_aqc_acl_tbl_actpair *cmd;
> + struct libie_aq_desc desc;
> +
> + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_dealloc_acl_tbl);
> + cmd = libie_aq_raw(&desc);
> + cmd->alloc_id = cpu_to_le16(alloc_id);
> +
> + return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
> +}
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl.h b/drivers/net/ethernet/intel/ice/ice_acl.h
> new file mode 100644
> index 0000000000000..7446be5d1edbe
> --- /dev/null
> +++ b/drivers/net/ethernet/intel/ice/ice_acl.h
> @@ -0,0 +1,118 @@
[ ... ]
> +struct ice_acl_tbl {
> + /* TCAM configuration */
> + u8 first_tcam;
> + u8 last_tcam;
> + u16 first_entry; /* Index of the first entry in the first TCAM */
> + u16 last_entry; /* Index of the last entry in the last TCAM */
> + u16 id;
> +
> + /* List of active scenarios */
> + struct list_head scens;
> +
> + struct ice_acl_tbl_params info;
> + struct ice_acl_act_mem act_mems[ICE_AQC_MAX_ACTION_MEMORIES];
> +
> + /* Keep track of available 64-entry chunks in TCAMs */
> + DECLARE_BITMAP(avail, ICE_AQC_ACL_ALLOC_UNITS);
> +};
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> new file mode 100644
> index 0000000000000..9984c07b53f0d
> --- /dev/null
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> @@ -0,0 +1,316 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2018-2026, Intel Corporation. */
> +
> +#include "ice_acl.h"
> +
[ ... ]
> +static int ice_acl_init_tbl(struct ice_hw *hw)
> +{
> + struct ice_aqc_actpair act_buf = {};
> + struct ice_aqc_acl_data buf = {};
> + struct ice_acl_tbl *tbl;
> + u8 tcam_idx;
> + int err = 0;
> + u16 idx;
> +
> + tbl = hw->acl_tbl;
> +
> + tcam_idx = tbl->first_tcam;
> + idx = tbl->first_entry;
> + while (tcam_idx < tbl->last_tcam ||
> + (tcam_idx == tbl->last_tcam && idx <= tbl->last_entry)) {
[Severity: Medium]
Can this loop terminate if firmware returns the documented failure values
for last_tcam and last_entry?
ice_acl_create_tbl() only validates alloc_id from the 0x0C10 response and
then stores the rest verbatim:
tbl->first_tcam = resp_buf->ops.table.first_tcam;
tbl->last_tcam = resp_buf->ops.table.last_tcam;
tbl->first_entry = le16_to_cpu(resp_buf->first_entry);
tbl->last_entry = le16_to_cpu(resp_buf->last_entry);
struct ice_aqc_acl_generic, added by this same patch, documents 0xFF as the
sentinel for exactly those four fields, and the hardware has 16 TCAM blocks
of 512 entries. With last_tcam = 0xFF, tcam_idx is a u8 and wraps at 255,
so the loop above keeps issuing 0x0C20 writes against TCAM blocks the PF
does not own.
The same unvalidated values also feed the chunk bitmap at the end of
ice_acl_create_tbl():
last_e = (tbl->last_tcam * ICE_AQC_MAX_TCAM_ALLOC_UNITS) +
(tbl->last_entry / ICE_ACL_ENTRY_ALLOC_UNIT);
bitmap_set(tbl->avail, first_e, last_e - first_e + 1);
Can that write past the 128-bit avail bitmap, which is the trailing member
of the kzalloc'ed struct ice_acl_tbl? Would it make sense to check
first_tcam <= last_tcam < ICE_AQC_ACL_SLICES and the entry indices against
ICE_AQC_ACL_TCAM_DEPTH before using them?
> + /* Use the same value for entry_key and entry_key_inv since
> + * we are initializing the fields to 0
> + */
> + err = ice_aq_program_acl_entry(hw, tcam_idx, idx, &buf, NULL);
> + if (err)
> + return err;
> +
> + if (++idx > tbl->last_entry) {
> + tcam_idx++;
> + idx = tbl->first_entry;
> + }
> + }
> +
> + for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++) {
> + u16 act_entry_idx;
> +
> + if (tbl->act_mems[i].act_mem == ICE_ACL_ACT_MEM_ACT_MEM_INVAL)
> + continue;
> +
> + for (act_entry_idx = tbl->first_entry;
> + act_entry_idx <= tbl->last_entry; act_entry_idx++) {
> + /* Invalidate all allocated action pairs */
> + err = ice_aq_program_actpair(hw, i, act_entry_idx,
> + &act_buf, NULL);
> + if (err)
> + return err;
> + }
> + }
> +
> + return err;
> +}
[Severity: Medium]
How much probe time do these two loops add?
For the configuration set up by ice_acl_create_hw() with num_funcs < 8
(3 slices, 512 entries) this is 3 * 512 = 1536 synchronous 0x0C20 commands,
plus one 0x0C1C per valid action memory per entry, i.e. roughly another
1536 with 3 valid act_mems. Each one is a separate firmware round trip
serialized inside ice_aq_send_cmd().
This runs unconditionally on every PF at probe, and ice_acl_create_hw()
also claims 3 of the device's 16 TCAM slices permanently, even when no
masked ntuple rule is ever configured. Would allocating the table lazily on
the first masked ntuple rule, or gating it behind a user knob, be
preferable?
[ ... ]
> +int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params)
> +{
> + struct ice_acl_alloc_tbl tbl_alloc = {};
> + struct ice_aqc_acl_generic *resp_buf;
> + u16 width, depth, first_e, last_e;
> + struct ice_acl_tbl *tbl;
> + u16 alloc_id;
> + int err;
> +
> + if (hw->acl_tbl)
> + return -EEXIST;
> +
> + /* round up the width to the next TCAM width boundary. */
> + width = roundup(params->width, (u16)ICE_AQC_ACL_KEY_WIDTH_BYTES);
> + /* depth should be provided in chunk (64 entry) increments */
> + depth = ALIGN(params->depth, ICE_ACL_ENTRY_ALLOC_UNIT);
> +
> + if (params->entry_act_pairs < width / ICE_AQC_ACL_KEY_WIDTH_BYTES) {
> + params->entry_act_pairs = width / ICE_AQC_ACL_KEY_WIDTH_BYTES;
> +
> + if (params->entry_act_pairs > ICE_AQC_TBL_MAX_ACTION_PAIRS)
> + params->entry_act_pairs = ICE_AQC_TBL_MAX_ACTION_PAIRS;
> + }
[Severity: Low]
This isn't a bug, but is the mutation of the caller-owned params intended to
be this asymmetric?
params->entry_act_pairs is written back here, while the rounded width and
the aligned depth are kept private to tbl->info:
tbl->info = *params;
tbl->info.width = width;
tbl->info.depth = depth;
The kernel-doc describes @params only as "parameters for the table to be
created", so after the call params describes neither the requested nor the
created table consistently. Later in the series ice_acl_create_hw() re-reads
params.width and params.depth after this function returns; with the current
values (width 15, depth 512/256) the stale values happen to match, but a
non-aligned request would not. Either normalizing all three fields or none
of them, and documenting it, would avoid that.
> +
> + /* Validate that width*depth will not exceed the TCAM limit */
> + if ((DIV_ROUND_UP(depth, ICE_AQC_ACL_TCAM_DEPTH) *
> + (width / ICE_AQC_ACL_KEY_WIDTH_BYTES)) > ICE_AQC_ACL_SLICES)
> + return -ENOSPC;
> +
> + tbl_alloc.width = width;
> + tbl_alloc.depth = depth;
> + tbl_alloc.act_pairs_per_entry = params->entry_act_pairs;
> + tbl_alloc.concurr = params->concurr;
> +
> + if (params->concurr) {
> + int i;
> +
> + tbl_alloc.num_dependent_alloc_ids = params->num_dep_tbls;
> +
> + for (i = 0; i < params->num_dep_tbls; i++)
> + tbl_alloc.buf.data_buf.alloc_ids[i] =
> + cpu_to_le16(params->dep_tbls[i]);
[Severity: Low]
Can this loop overrun tbl_alloc.buf.data_buf.alloc_ids[]?
num_dep_tbls is a u8, and both params->dep_tbls[] and alloc_ids[] hold 15
entries, but there is no bound check before the copy. The only range check
lives in ice_aq_alloc_acl_tbl(), which runs after the buffer has already
been filled:
if (tbl->num_dependent_alloc_ids > ICE_AQC_MAX_CONCURRENT_ACL_TBL)
return -ERANGE;
With num_dep_tbls > 15 that leaves an out-of-bounds read of the caller's
dep_tbls[] and an out-of-bounds write into the on-stack ice_acl_alloc_tbl
union. The only caller today sets concurr = false and num_dep_tbls = 0, so
the path is latent, but would it be better to validate num_dep_tbls here
before filling the buffer?
> +
> + for (; i < ICE_AQC_MAX_CONCURRENT_ACL_TBL; i++)
> + tbl_alloc.buf.data_buf.alloc_ids[i] =
> + cpu_to_le16(ICE_AQC_CONCURR_ID_INVALID);
> + }
> +
> + err = ice_aq_alloc_acl_tbl(hw, &tbl_alloc, NULL);
> + if (err) {
> + dev_err(ice_hw_to_dev(hw), "ACL table allocation failed with error %d\n",
> + err);
> + return err;
> + }
> +
> + alloc_id = le16_to_cpu(tbl_alloc.buf.resp_buf.alloc_id);
> + if (alloc_id < ICE_AQC_ALLOC_ID_4K) {
> + dev_err(ice_hw_to_dev(hw), "ACL table allocation failed due to unavailable resources.\n");
> + return -ENOMEM;
> + }
> +
> + resp_buf = &tbl_alloc.buf.resp_buf;
> +
> + tbl = kzalloc_obj(*tbl);
> + if (!tbl) {
> + err = -ENOMEM;
> + goto err_dealloc_tbl;
> + }
> +
> + /* Retrieve information of the allocated table */
> + tbl->id = alloc_id;
> + tbl->first_tcam = resp_buf->ops.table.first_tcam;
> + tbl->last_tcam = resp_buf->ops.table.last_tcam;
> + tbl->first_entry = le16_to_cpu(resp_buf->first_entry);
> + tbl->last_entry = le16_to_cpu(resp_buf->last_entry);
> +
[ ... ]
> + first_e = (tbl->first_tcam * ICE_AQC_MAX_TCAM_ALLOC_UNITS) +
> + (tbl->first_entry / ICE_ACL_ENTRY_ALLOC_UNIT);
> + last_e = (tbl->last_tcam * ICE_AQC_MAX_TCAM_ALLOC_UNITS) +
> + (tbl->last_entry / ICE_ACL_ENTRY_ALLOC_UNIT);
> +
> + /* Indicate available entries in the table */
> + bitmap_set(tbl->avail, first_e, last_e - first_e + 1);
[Severity: Medium]
Is a single contiguous bitmap_set() the right encoding when depth is less
than a full TCAM?
ice_acl_init_tbl() shows that the entry window [first_entry..last_entry]
repeats inside every TCAM of the table:
if (++idx > tbl->last_entry) {
tcam_idx++;
idx = tbl->first_entry;
}
but the bitmap index here is tcam * ICE_AQC_MAX_TCAM_ALLOC_UNITS(8) +
entry/64, so the owned chunk set is non-contiguous whenever depth < 512.
For the shared-slice case that ice_acl_create_hw() selects when
num_funcs >= 8 (depth 256, 3 cascaded slices), first_tcam = 0,
last_tcam = 2, first_entry = 0, last_entry = 255 gives first_e = 0 and
last_e = 19, so bitmap_set(avail, 0, 20) also marks chunks 4-7 (slice 0
entries 256-511) and 12-15 (slice 1 entries 256-511) as available. Those
halves belong to the peer PF according to the comment in
ice_acl_create_hw(). The PF whose first_entry is 256 gets the mirror-image
error.
Later in the series ice_acl_alloc_partition() converts a set bit back to a
hardware entry index, so should this be set per TCAM instead of as one
range?
> +
> + INIT_LIST_HEAD(&tbl->scens);
> +
> + return 0;
> +
> +err_free_tbl:
> + hw->acl_tbl = NULL;
> + kfree(tbl);
> +err_dealloc_tbl:
> + ice_aq_dealloc_acl_tbl(hw, alloc_id, resp_buf, NULL);
> + return err;
> +}
> +
> +/**
> + * ice_acl_destroy_tbl - Destroy a previously created LEM table for ACL
> + * @hw: pointer to the HW struct
> + *
> + * Return: 0 on success, negative on error
> + */
> +int ice_acl_destroy_tbl(struct ice_hw *hw)
> +{
> + struct ice_aqc_acl_generic resp_buf;
[Severity: Low]
Should resp_buf be initialized here, as the other AdminQ buffers in this
patch are?
ice_aq_dealloc_acl_tbl() passes it with a non-zero size, and
ice_sq_send_cmd() copies the caller buffer into the DMA-mapped send buffer
unconditionally, regardless of LIBIE_AQ_FLAG_RD:
drivers/net/ethernet/intel/ice/ice_controlq.c:ice_sq_send_cmd() {
if (buf) {
dma_buf = &cq->sq.r.sq_bi[cq->sq.next_to_use];
memcpy(dma_buf->va, buf, buf_size);
...
}
So 28 bytes of uninitialized stack are copied into device-visible memory on
every ACL teardown. Would "= {}" be enough here?
> + int err;
> +
> + if (!hw->acl_tbl)
> + return -ENOENT;
> +
> + err = ice_aq_dealloc_acl_tbl(hw, hw->acl_tbl->id, &resp_buf, NULL);
> + if (err) {
> + ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL failed. status: %d\n",
> + err);
> + return err;
> + }
> +
> + kfree(hw->acl_tbl);
> + hw->acl_tbl = NULL;
> +
> + return 0;
> +}
[Severity: Low]
Does this early return leak the kzalloc'ed hw->acl_tbl?
When the 0x0C11 command fails (surprise removal, dead device, firmware
error, reset in flight) the function returns before kfree(hw->acl_tbl) and
leaves the pointer set, so the object is never freed and any later
ice_acl_create_tbl() returns -EEXIST. The only caller discards the return
value:
static void ice_deinit_acl(struct ice_pf *pf)
{
ice_acl_destroy_tbl(&pf->hw);
}
Later in this series the commit "ice: add ACL reset recovery and NTUPLE
feature toggle" makes ice_acl_destroy_tbl() void, drops the early returns
and always reaches kfree(hw->acl_tbl) with the comment "Continue on AQ
errors so SW state is always cleaned up", so the final state of the series
is fine; would it be worth doing that here so the intermediate commits do
not carry the leak?
> diff --git a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
> index 42878abac9eb1..f98780afa1399 100644
> --- a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
> +++ b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
[ ... ]
> +#define ICE_AQC_ACL_ALLOC_SCE_START_CMP BIT(0)
> +#define ICE_AQC_ACL_ALLOC_SCE_START_SET BIT(1)
> +
> +#define ICE_AQC_ACL_SCE_ACT_MEM_EN BIT(7)
> +
> +/* Input buffer format in case allocate/update ACL scenario and same format
> + * is used for response buffer in case of query ACL scenario.
> + * NOTE: de-allocate ACL scenario is direct command and doesn't require
> + * "buffer", hence no buffer format.
> + */
> +struct ice_aqc_acl_scen {
> + struct {
> + /* Byte [x] selection for the TCAM key. This value must be set
> + * to 0x0 for unused TCAM.
> + * Only Bit 6..0 is used in each byte and MSB is reserved
> + */
> + u8 tcam_select[5];
> + /* TCAM Block entry masking. This value should be set to 0x0 for
> + * unused TCAM
> + */
> + u8 chnk_msk;
> + /* Bit 0 : masks TCAM entries 0-63
> + * Bit 1 : masks TCAM entries 64-127
> + * Bit 2 to 7 : follow the pattern of bit 0 and 1
> + */
> + u8 start_cmp_set;
[Severity: Low]
Is the 64-entry chunk-mask description attached to the wrong field? It
describes chnk_msk, but sits on start_cmp_set, whose bits are defined 20
lines above as ICE_AQC_ACL_ALLOC_SCE_START_CMP = BIT(0) and
ICE_AQC_ACL_ALLOC_SCE_START_SET = BIT(1). Later code in this series
confirms that, writing the per-64-entry bits into chnk_msk and the
START_CMP value into start_cmp_set.
While in the area, the kernel-doc of ice_aq_dealloc_acl_tbl() says to pass
"ptr to that struct as 'buf' and its size as 'buf_size'", but the function
has no buf_size parameter; it hardcodes sizeof(*buf).
> + } tcam_cfg[ICE_AQC_ACL_SLICES];
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
> index 707c7431b91a6..981c105e1d3a2 100644
> --- a/drivers/net/ethernet/intel/ice/ice_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_main.c
[ ... ]
> @@ -4748,6 +4802,10 @@ static void ice_init_features(struct ice_pf *pf)
> if (ice_init_fdir(pf))
> dev_err(dev, "could not initialize flow director\n");
>
> + /* Note: ACL init failure is non-fatal to load */
> + if (ice_init_acl(pf))
> + dev_err(dev, "Failed to initialize ACL\n");
> +
[Severity: Low]
What happens to hw->acl_tbl across a reset at this point in the series?
The table is created only from ice_init_features() and destroyed only from
ice_deinit_features(), so after a PFR/CORER/GLOBR (or after ice_resume(),
which schedules a PFR) hw->acl_tbl still describes a firmware allocation
that no longer exists, and:
if (hw->acl_tbl)
return -EEXIST;
in ice_acl_create_tbl() prevents re-creating it, while a later teardown
would deallocate an alloc_id firmware may have reassigned.
The later commit "ice: add ACL reset recovery and NTUPLE feature toggle"
wires ice_acl_destroy_tbl() plus ice_acl_create_hw() into ice_rebuild(),
which closes this for the end of the series; is it worth handling the
rebuild path from the start so the intermediate commits are not exposed?
> /* Note: DCB init failure is non-fatal to load */
> if (ice_init_pf_dcb(pf, false)) {
> clear_bit(ICE_FLAG_DCB_CAPABLE, pf->flags);
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v2 04/12] ice: initialize ACL scenario
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (2 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 03/12] ice: initialize ACL table Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-21 19:57 ` netdev-bot+sashiko
2026-09-17 18:39 ` [PATCH net-next v2 05/12] ice: create flow profile Tony Nguyen
` (9 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Real Valiquette, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Chinh Cao,
Rinitha S
From: Real Valiquette <real.valiquette@intel.com>
Complete initialization of the ACL table by programming the table with an
initial scenario. The scenario stores the data for the filtering rules.
Adjust reporting of ntuple filters to include ACL filters.
Co-developed-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Real Valiquette <real.valiquette@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 2 +
drivers/net/ethernet/intel/ice/ice_acl.c | 116 ++++
drivers/net/ethernet/intel/ice/ice_acl.h | 8 +
drivers/net/ethernet/intel/ice/ice_acl_ctrl.c | 573 ++++++++++++++++++
.../net/ethernet/intel/ice/ice_adminq_cmd.h | 31 +
drivers/net/ethernet/intel/ice/ice_ethtool.c | 4 +-
.../ethernet/intel/ice/ice_ethtool_ntuple.c | 86 ++-
drivers/net/ethernet/intel/ice/ice_fdir.h | 2 +
drivers/net/ethernet/intel/ice/ice_flow.h | 7 +
drivers/net/ethernet/intel/ice/ice_main.c | 20 +-
drivers/net/ethernet/intel/ice/ice_type.h | 7 +
11 files changed, 841 insertions(+), 15 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index e5ef7bf549df..1ee4e1aff94a 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -1026,10 +1026,12 @@ void ice_vsi_manage_fdir(struct ice_vsi *vsi, bool ena);
int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
int ice_get_ethtool_fdir_entry(struct ice_hw *hw, struct ethtool_rxnfc *cmd);
+u32 ice_ntuple_get_max_fltr_cnt(struct ice_hw *hw);
int
ice_get_fdir_fltr_ids(struct ice_hw *hw, struct ethtool_rxnfc *cmd,
u32 *rule_locs);
void ice_fdir_rem_adq_chnl(struct ice_hw *hw, u16 vsi_idx);
+void ice_acl_rem_flows(struct ice_hw *hw);
void ice_fdir_release_flows(struct ice_hw *hw);
void ice_fdir_replay_flows(struct ice_hw *hw);
void ice_fdir_replay_fltrs(struct ice_pf *pf);
diff --git a/drivers/net/ethernet/intel/ice/ice_acl.c b/drivers/net/ethernet/intel/ice/ice_acl.c
index 1c9ae47d9efc..7821ca247c82 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl.c
+++ b/drivers/net/ethernet/intel/ice/ice_acl.c
@@ -134,3 +134,119 @@ int ice_aq_program_actpair(struct ice_hw *hw, u8 act_mem_idx, u16 act_entry_idx,
return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
}
+
+/**
+ * ice_aq_alloc_acl_scen - allocate ACL scenario
+ * @hw: pointer to the HW struct
+ * @scen_id: memory location to receive allocated scenario ID
+ * @buf: address of indirect data buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Allocate ACL scenario (indirect 0x0C14)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_alloc_acl_scen(struct ice_hw *hw, u16 *scen_id,
+ struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_alloc_scen *cmd;
+ struct libie_aq_desc desc;
+ int err;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_alloc_acl_scen);
+ desc.flags |= cpu_to_le16(LIBIE_AQ_FLAG_RD);
+ cmd = libie_aq_raw(&desc);
+
+ err = ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
+ if (!err)
+ *scen_id = le16_to_cpu(cmd->ops.resp.scen_id);
+
+ return err;
+}
+
+/**
+ * ice_aq_dealloc_acl_scen - deallocate ACL scenario
+ * @hw: pointer to the HW struct
+ * @scen_id: scen_id to be deallocated (input and output field)
+ * @cd: pointer to command details structure or NULL
+ *
+ * Deallocate ACL scenario (direct 0x0C15)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_dealloc_acl_scen(struct ice_hw *hw, u16 scen_id,
+ struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_dealloc_scen *cmd;
+ struct libie_aq_desc desc;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_dealloc_acl_scen);
+ cmd = libie_aq_raw(&desc);
+ cmd->scen_id = cpu_to_le16(scen_id);
+
+ return ice_aq_send_cmd(hw, &desc, NULL, 0, cd);
+}
+
+/**
+ * ice_aq_update_query_scen - update or query ACL scenario
+ * @hw: pointer to the HW struct
+ * @opcode: AQ command opcode for either query or update scenario
+ * @scen_id: scen_id to be updated or queried
+ * @buf: address of indirect data buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Calls update or query ACL scenario
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_aq_update_query_scen(struct ice_hw *hw, u16 opcode, u16 scen_id,
+ struct ice_aqc_acl_scen *buf,
+ struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_update_query_scen *cmd;
+ struct libie_aq_desc desc;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, opcode);
+ if (opcode == ice_aqc_opc_update_acl_scen)
+ desc.flags |= cpu_to_le16(LIBIE_AQ_FLAG_RD);
+ cmd = libie_aq_raw(&desc);
+ cmd->scen_id = cpu_to_le16(scen_id);
+
+ return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
+}
+
+/**
+ * ice_aq_update_acl_scen - update ACL scenario
+ * @hw: pointer to the HW struct
+ * @scen_id: scen_id to be updated
+ * @buf: address of indirect data buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Update ACL scenario (indirect 0x0C1B)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_update_acl_scen(struct ice_hw *hw, u16 scen_id,
+ struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd)
+{
+ return ice_aq_update_query_scen(hw, ice_aqc_opc_update_acl_scen,
+ scen_id, buf, cd);
+}
+
+/**
+ * ice_aq_query_acl_scen - query ACL scenario
+ * @hw: pointer to the HW struct
+ * @scen_id: scen_id to be queried
+ * @buf: address of indirect data buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Query ACL scenario (indirect 0x0C23)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_query_acl_scen(struct ice_hw *hw, u16 scen_id,
+ struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd)
+{
+ return ice_aq_update_query_scen(hw, ice_aqc_opc_query_acl_scen,
+ scen_id, buf, cd);
+}
diff --git a/drivers/net/ethernet/intel/ice/ice_acl.h b/drivers/net/ethernet/intel/ice/ice_acl.h
index 7446be5d1edb..148e9b67a115 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl.h
+++ b/drivers/net/ethernet/intel/ice/ice_acl.h
@@ -102,6 +102,8 @@ struct ice_acl_alloc_tbl {
int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params);
int ice_acl_destroy_tbl(struct ice_hw *hw);
+int ice_acl_create_scen(struct ice_hw *hw, u16 match_width, u16 num_entries,
+ u16 *scen_id);
int ice_aq_alloc_acl_tbl(struct ice_hw *hw, struct ice_acl_alloc_tbl *tbl,
struct ice_sq_cd *cd);
int ice_aq_dealloc_acl_tbl(struct ice_hw *hw, u16 alloc_id,
@@ -114,5 +116,11 @@ int ice_aq_program_actpair(struct ice_hw *hw, u8 act_mem_idx, u16 act_entry_idx,
struct ice_aqc_actpair *buf, struct ice_sq_cd *cd);
int ice_aq_alloc_acl_scen(struct ice_hw *hw, u16 *scen_id,
struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd);
+int ice_aq_dealloc_acl_scen(struct ice_hw *hw, u16 scen_id,
+ struct ice_sq_cd *cd);
+int ice_aq_update_acl_scen(struct ice_hw *hw, u16 scen_id,
+ struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd);
+int ice_aq_query_acl_scen(struct ice_hw *hw, u16 scen_id,
+ struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd);
#endif /* _ICE_ACL_H_ */
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
index 9984c07b53f0..6a60da3034cf 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
+++ b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
@@ -6,6 +6,80 @@
/* Determine the TCAM index of entry 'e' within the ACL table */
#define ICE_ACL_TBL_TCAM_IDX(e) ((e) / ICE_AQC_ACL_TCAM_DEPTH)
+/**
+ * ice_acl_init_entry - initialize ACL entry
+ * @scen: pointer to the scenario struct
+ *
+ * Initialize the scenario control structure.
+ */
+static void ice_acl_init_entry(struct ice_acl_scen *scen)
+{
+ /* low priority: start from the highest index, 25% of total entries
+ * normal priority: start from the highest index, 50% of total entries
+ * high priority: start from the lowest index, 25% of total entries
+ */
+ scen->first_idx[ICE_ACL_PRIO_LOW] = scen->num_entry - 1;
+ scen->first_idx[ICE_ACL_PRIO_NORMAL] = scen->num_entry -
+ scen->num_entry / 4 - 1;
+ scen->first_idx[ICE_ACL_PRIO_HIGH] = 0;
+
+ scen->last_idx[ICE_ACL_PRIO_LOW] = scen->num_entry -
+ scen->num_entry / 4;
+ scen->last_idx[ICE_ACL_PRIO_NORMAL] = scen->num_entry / 4;
+ scen->last_idx[ICE_ACL_PRIO_HIGH] = scen->num_entry / 4 - 1;
+}
+
+/**
+ * ice_acl_tbl_calc_end_idx - get end ACL entry index
+ * @start: start index of the TCAM entry of this partition
+ * @num_entries: number of entries in this partition
+ * @width: width of a partition in number of TCAMs
+ *
+ * Calculate the end entry index for a partition with starting entry index
+ * 'start', entries 'num_entries', and width 'width'.
+ *
+ * Returns: end entry index
+ */
+static u16 ice_acl_tbl_calc_end_idx(u16 start, u16 num_entries, u16 width)
+{
+ u16 end_idx, add_entries = 0;
+
+ end_idx = start + (num_entries - 1);
+
+ /* In case that our ACL partition requires cascading TCAMs */
+ if (width > 1) {
+ u16 num_stack_level;
+
+ /* Figure out the TCAM stacked level in this ACL scenario */
+ num_stack_level = (start % ICE_AQC_ACL_TCAM_DEPTH) +
+ num_entries;
+ num_stack_level = DIV_ROUND_UP(num_stack_level,
+ ICE_AQC_ACL_TCAM_DEPTH);
+
+ /* In this case, each entries in our ACL partition span
+ * multiple TCAMs. Thus, we will need to add
+ * ((width - 1) * num_stack_level) TCAM's entries to
+ * end_idx.
+ *
+ * For example : In our case, our scenario is 2x2:
+ * [TCAM 0] [TCAM 1]
+ * [TCAM 2] [TCAM 3]
+ * Assuming that a TCAM will have 512 entries. If "start"
+ * is 500, "num_entries" is 3 and "width" = 2, then end_idx
+ * should be 1024 (belongs to TCAM 2).
+ * Before going to this if statement, end_idx will have the
+ * value of 512. If "width" is 1, then the final value of
+ * end_idx is 512. However, in our case, width is 2, then we
+ * will need add (2 - 1) * 1 * 512. As result, end_idx will
+ * have the value of 1024.
+ */
+ add_entries = (width - 1) * num_stack_level *
+ ICE_AQC_ACL_TCAM_DEPTH;
+ }
+
+ return end_idx + add_entries;
+}
+
/**
* ice_acl_init_tbl - initialize ACL table
* @hw: pointer to the hardware structure
@@ -288,6 +362,467 @@ int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params)
return err;
}
+/**
+ * ice_acl_alloc_partition - Allocate a partition from the ACL table
+ * @hw: pointer to the hardware structure
+ * @req: info of partition being allocated
+ *
+ * Returns: 0 on success, negative on error
+ */
+static int ice_acl_alloc_partition(struct ice_hw *hw, struct ice_acl_scen *req)
+{
+ u16 start = 0, cnt = 0, off = 0;
+ u16 width, r_entries;
+ bool done = false;
+ int row;
+ int dir;
+
+ /* Determine the number of TCAMs each entry overlaps */
+ width = DIV_ROUND_UP(req->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
+
+ /* Check if we have enough TCAMs to accommodate the width */
+ if (width > hw->acl_tbl->last_tcam - hw->acl_tbl->first_tcam + 1)
+ return -ENOSPC;
+
+ /* Number of entries must be multiple of ICE_ACL_ENTRY_ALLOC_UNIT's */
+ r_entries = ALIGN(req->num_entry, ICE_ACL_ENTRY_ALLOC_UNIT);
+
+ /* To look for an available partition that can accommodate the request,
+ * the process first logically arranges available TCAMs in rows such
+ * that each row produces entries with the requested width. It then
+ * scans the TCAMs' available bitmap, one bit at a time, and
+ * accumulates contiguous available 64-entry chunks until there are
+ * enough of them or when all TCAM configurations have been checked.
+ *
+ * For width of 1 TCAM, the scanning process starts from the top most
+ * TCAM, and goes downward. Available bitmaps are examined from LSB
+ * to MSB.
+ *
+ * For width of multiple TCAMs, the process starts from the bottom-most
+ * row of TCAMs, and goes upward. Available bitmaps are examined from
+ * the MSB to the LSB.
+ *
+ * To make sure that adjacent TCAMs can be logically arranged in the
+ * same row, the scanning process may have multiple passes. In each
+ * pass, the first TCAM of the bottom-most row is displaced by one
+ * additional TCAM. The width of the row and the number of the TCAMs
+ * available determine the number of passes. When the displacement is
+ * more than the size of width, the TCAM row configurations will
+ * repeat. The process will terminate when the configurations repeat.
+ *
+ * Available partitions can span more than one row of TCAMs.
+ */
+ if (width == 1) {
+ row = hw->acl_tbl->first_tcam;
+ dir = 1;
+ } else {
+ /* Start with the bottom-most row, and scan for available
+ * entries upward
+ */
+ row = hw->acl_tbl->last_tcam + 1 - width;
+ if (row < 0)
+ return -EINVAL;
+
+ dir = -1;
+ }
+
+ do {
+ /* Scan all 64-entry chunks, one chunk at a time, in the
+ * current TCAM row
+ */
+ for (u16 i = 0;
+ i < ICE_AQC_MAX_TCAM_ALLOC_UNITS && cnt < r_entries;
+ i++) {
+ bool avail = true;
+ u16 p;
+
+ /* Compute the cumulative available mask across the
+ * TCAM row to determine if the current 64-entry chunk
+ * is available.
+ */
+ p = dir > 0 ? i : ICE_AQC_MAX_TCAM_ALLOC_UNITS - i - 1;
+ for (u16 w = row; w < row + width && avail; w++) {
+ u16 b;
+
+ b = (w * ICE_AQC_MAX_TCAM_ALLOC_UNITS) + p;
+ avail &= test_bit(b, hw->acl_tbl->avail);
+ }
+
+ if (!avail) {
+ cnt = 0;
+ } else {
+ /* Compute the starting index of the newly
+ * found partition. When 'dir' is negative, the
+ * scan processes is going upward. If so, the
+ * starting index needs to be updated for every
+ * available 64-entry chunk found.
+ */
+ if (!cnt || dir < 0)
+ start = (row * ICE_AQC_ACL_TCAM_DEPTH) +
+ (p * ICE_ACL_ENTRY_ALLOC_UNIT);
+ cnt += ICE_ACL_ENTRY_ALLOC_UNIT;
+ }
+ }
+
+ if (cnt >= r_entries) {
+ req->start = start;
+ req->num_entry = r_entries;
+ req->end = ice_acl_tbl_calc_end_idx(start, r_entries,
+ width);
+ break;
+ }
+
+ row = dir > 0 ? row + width : row - width;
+ if (row < 0)
+ return -EINVAL;
+
+ if (row > hw->acl_tbl->last_tcam ||
+ row < hw->acl_tbl->first_tcam) {
+ /* All rows have been checked. Increment 'off' that
+ * will help yield a different TCAM configuration in
+ * which adjacent TCAMs can be alternatively in the
+ * same row.
+ */
+ off++;
+
+ /* However, if the new 'off' value yields previously
+ * checked configurations, then exit.
+ */
+ if (off >= width) {
+ done = true;
+ } else {
+ row = dir > 0 ? off :
+ hw->acl_tbl->last_tcam + 1 - off -
+ width;
+ if (row < 0)
+ return -EINVAL;
+ }
+ }
+ } while (!done);
+
+ return cnt >= r_entries ? 0 : -ENOSPC;
+}
+
+/**
+ * ice_acl_fill_tcam_select - fill key byte selection for scenario's TCAM
+ * @scen_buf: Pointer to the scenario buffer that needs to be populated
+ * @scen: Pointer to the available space for the scenario
+ * @tcam_idx: Index of the TCAM used for this scenario
+ * @tcam_idx_in_cascade: Local index of the TCAM in the cascade scenario
+ *
+ * For all TCAM that participate in this scenario, fill out the tcam_select
+ * value.
+ */
+static void ice_acl_fill_tcam_select(struct ice_aqc_acl_scen *scen_buf,
+ struct ice_acl_scen *scen, u16 tcam_idx,
+ u16 tcam_idx_in_cascade)
+{
+ u16 cascade_cnt, idx;
+
+ idx = tcam_idx_in_cascade * ICE_AQC_ACL_KEY_WIDTH_BYTES;
+ cascade_cnt = DIV_ROUND_UP(scen->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
+
+ /* For each scenario, we reserved last three bytes of scenario width for
+ * profile ID, range checker, and packet direction. Thus, the last three
+ * bytes of the last cascaded TCAMs will have value of 1st, 31st and
+ * 32nd byte location of BYTE selection base.
+ *
+ * For other bytes in the TCAMs:
+ * For non-cascade mode (1 TCAM wide) scenario, TCAM[x]'s Select {0-1}
+ * select indices 0-1 of the Byte Selection Base
+ * For cascade mode, the leftmost TCAM of the first cascade row selects
+ * indices 0-4 of the Byte Selection Base; the second TCAM in the
+ * cascade row selects indices starting with 5-n
+ */
+ for (int j = 0; j < ICE_AQC_ACL_KEY_WIDTH_BYTES; j++) {
+ /* PKT DIR uses the 1st location of Byte Selection Base: + 1 */
+ u8 val = ICE_AQC_ACL_BYTE_SEL_BASE + 1 + idx;
+
+ if (tcam_idx_in_cascade == cascade_cnt - 1) {
+ if (j == ICE_ACL_SCEN_RNG_CHK_IDX_IN_TCAM)
+ val = ICE_AQC_ACL_BYTE_SEL_BASE_RNG_CHK;
+ else if (j == ICE_ACL_SCEN_PID_IDX_IN_TCAM)
+ val = ICE_AQC_ACL_BYTE_SEL_BASE_PID;
+ else if (j == ICE_ACL_SCEN_PKT_DIR_IDX_IN_TCAM)
+ val = ICE_AQC_ACL_BYTE_SEL_BASE_PKT_DIR;
+ }
+
+ /* In case that scenario's width is greater than the width of
+ * the Byte selection base, we will not assign a value to the
+ * tcam_select[j]. As a result, the tcam_select[j] will have
+ * default value which is zero.
+ */
+ if (val > ICE_AQC_ACL_BYTE_SEL_BASE_RNG_CHK)
+ continue;
+
+ scen_buf->tcam_cfg[tcam_idx].tcam_select[j] = val;
+
+ idx++;
+ }
+}
+
+/**
+ * ice_acl_set_scen_chnk_msk - set entries chunk masks
+ * @scen_buf: Pointer to the scenario buffer that needs to be populated
+ * @scen: pointer to the available space for the scenario
+ *
+ * Set the chunk mask for the entries that will be used by this scenario
+ */
+static void ice_acl_set_scen_chnk_msk(struct ice_aqc_acl_scen *scen_buf,
+ struct ice_acl_scen *scen)
+{
+ u16 tcam_idx, num_cscd, units;
+ u8 chnk_offst;
+
+ /* Determine the starting TCAM index and offset of the start entry */
+ tcam_idx = ICE_ACL_TBL_TCAM_IDX(scen->start);
+ chnk_offst = (u8)((scen->start % ICE_AQC_ACL_TCAM_DEPTH) /
+ ICE_ACL_ENTRY_ALLOC_UNIT);
+
+ /* Entries are allocated and tracked in multiple of 64's */
+ units = scen->num_entry / ICE_ACL_ENTRY_ALLOC_UNIT;
+
+ /* Determine number of cascaded TCAMs */
+ num_cscd = scen->width / ICE_AQC_ACL_KEY_WIDTH_BYTES;
+
+ for (u16 cnt = 0; cnt < units; cnt++) {
+ /* Set the corresponding bitmap of individual 64-entry
+ * chunk spans across a cascade of 1 or more TCAMs
+ * For each TCAM, there will be (ICE_AQC_ACL_TCAM_DEPTH
+ * / ICE_ACL_ENTRY_ALLOC_UNIT) or 8 chunks.
+ */
+ for (u16 i = tcam_idx; i < tcam_idx + num_cscd; i++)
+ scen_buf->tcam_cfg[i].chnk_msk |= BIT(chnk_offst);
+
+ chnk_offst = (chnk_offst + 1) % ICE_AQC_MAX_TCAM_ALLOC_UNITS;
+ if (!chnk_offst)
+ tcam_idx += num_cscd;
+ }
+}
+
+/**
+ * ice_acl_assign_act_mem_for_scen - associate action memories to new TCAM
+ * @tbl: pointer to ACL table structure
+ * @scen: pointer to the scenario struct
+ * @scen_buf: pointer to the available space for the scenario
+ * @current_tcam_idx: theoretical index of the TCAM that we associated those
+ * action memory banks with, at the table creation time
+ * @target_tcam_idx: index of the TCAM that we want to associate those action
+ * memory banks with
+ */
+static void ice_acl_assign_act_mem_for_scen(struct ice_acl_tbl *tbl,
+ struct ice_acl_scen *scen,
+ struct ice_aqc_acl_scen *scen_buf,
+ u8 current_tcam_idx,
+ u8 target_tcam_idx)
+{
+ for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++) {
+ struct ice_acl_act_mem *p_mem = &tbl->act_mems[i];
+
+ if (p_mem->act_mem == ICE_ACL_ACT_MEM_ACT_MEM_INVAL ||
+ p_mem->member_of_tcam != current_tcam_idx)
+ continue;
+
+ scen_buf->act_mem_cfg[i] = target_tcam_idx;
+ scen_buf->act_mem_cfg[i] |= ICE_AQC_ACL_SCE_ACT_MEM_EN;
+ set_bit(i, scen->act_mem_bitmap);
+ }
+}
+
+/**
+ * ice_acl_commit_partition - Indicate if the specified partition is active
+ * @hw: pointer to the hardware structure
+ * @scen: pointer to the scenario struct
+ * @commit: true if the partition is being commit
+ */
+static void ice_acl_commit_partition(struct ice_hw *hw,
+ struct ice_acl_scen *scen, bool commit)
+{
+ u16 tcam_idx, off, num_cscd, units;
+
+ /* Determine the starting TCAM index and offset of the start entry */
+ tcam_idx = ICE_ACL_TBL_TCAM_IDX(scen->start);
+ off = (scen->start % ICE_AQC_ACL_TCAM_DEPTH) /
+ ICE_ACL_ENTRY_ALLOC_UNIT;
+
+ /* Entries are allocated and tracked in multiple of 64's */
+ units = scen->num_entry / ICE_ACL_ENTRY_ALLOC_UNIT;
+
+ /* Determine number of cascaded TCAM */
+ num_cscd = scen->width / ICE_AQC_ACL_KEY_WIDTH_BYTES;
+
+ for (u16 cnt = 0; cnt < units; cnt++) {
+ /* Set/clear the corresponding bitmap of individual 64-entry
+ * chunk spans across a row of 1 or more TCAMs
+ */
+ for (u16 w = 0; w < num_cscd; w++) {
+ u16 b;
+
+ b = ((tcam_idx + w) * ICE_AQC_MAX_TCAM_ALLOC_UNITS) +
+ off;
+ if (commit)
+ set_bit(b, hw->acl_tbl->avail);
+ else
+ clear_bit(b, hw->acl_tbl->avail);
+ }
+
+ off = (off + 1) % ICE_AQC_MAX_TCAM_ALLOC_UNITS;
+ if (!off)
+ tcam_idx += num_cscd;
+ }
+}
+
+/**
+ * ice_acl_create_scen - create ACL scenario
+ * @hw: pointer to the hardware structure
+ * @match_width: number of bytes to be matched in this scenario
+ * @num_entries: number of entries to be allocated for the scenario
+ * @scen_id: holds returned scenario ID if successful
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_acl_create_scen(struct ice_hw *hw, u16 match_width, u16 num_entries,
+ u16 *scen_id)
+{
+ u8 cascade_cnt, first_tcam, last_tcam, i, k;
+ struct ice_aqc_acl_scen scen_buf = {};
+ struct ice_acl_scen *scen;
+ int err;
+
+ if (match_width > ICE_ACL_MAX_WIDTH_BYTES)
+ return -EINVAL;
+
+ scen = devm_kzalloc(ice_hw_to_dev(hw), sizeof(*scen), GFP_KERNEL);
+ if (!scen)
+ return -ENOMEM;
+
+ scen->start = hw->acl_tbl->first_entry;
+ scen->width = ICE_AQC_ACL_KEY_WIDTH_BYTES *
+ DIV_ROUND_UP(match_width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
+ scen->num_entry = num_entries;
+
+ err = ice_acl_alloc_partition(hw, scen);
+ if (err)
+ goto out;
+
+ /* Determine the number of cascade TCAMs, given the scenario's width */
+ cascade_cnt = DIV_ROUND_UP(scen->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
+ first_tcam = ICE_ACL_TBL_TCAM_IDX(scen->start);
+ last_tcam = ICE_ACL_TBL_TCAM_IDX(scen->end);
+
+ /* For each scenario, we reserved last three bytes of scenario width for
+ * packet direction flag, profile ID and range checker. Thus, we want to
+ * return back to the caller the eff_width, pkt_dir_idx, rng_chk_idx and
+ * pid_idx.
+ */
+ scen->eff_width = cascade_cnt * ICE_AQC_ACL_KEY_WIDTH_BYTES -
+ ICE_ACL_SCEN_MIN_WIDTH;
+ scen->rng_chk_idx = (cascade_cnt - 1) * ICE_AQC_ACL_KEY_WIDTH_BYTES +
+ ICE_ACL_SCEN_RNG_CHK_IDX_IN_TCAM;
+ scen->pid_idx = (cascade_cnt - 1) * ICE_AQC_ACL_KEY_WIDTH_BYTES +
+ ICE_ACL_SCEN_PID_IDX_IN_TCAM;
+ scen->pkt_dir_idx = (cascade_cnt - 1) * ICE_AQC_ACL_KEY_WIDTH_BYTES +
+ ICE_ACL_SCEN_PKT_DIR_IDX_IN_TCAM;
+
+ /* set the chunk mask for the tcams */
+ ice_acl_set_scen_chnk_msk(&scen_buf, scen);
+
+ /* set the TCAM select and start_cmp and start_set bits */
+ k = first_tcam;
+ /* set the START_SET bit at the beginning of the stack */
+ scen_buf.tcam_cfg[k].start_cmp_set |= ICE_AQC_ACL_ALLOC_SCE_START_SET;
+ while (k <= last_tcam) {
+ u8 last_tcam_idx_cascade = cascade_cnt + k - 1;
+
+ /* set start_cmp for the first cascaded TCAM */
+ scen_buf.tcam_cfg[k].start_cmp_set |=
+ ICE_AQC_ACL_ALLOC_SCE_START_CMP;
+
+ /* cascade TCAMs up to the width of the scenario */
+ for (i = k; i < cascade_cnt + k; i++) {
+ ice_acl_fill_tcam_select(&scen_buf, scen, i, i - k);
+ ice_acl_assign_act_mem_for_scen(hw->acl_tbl, scen,
+ &scen_buf, i,
+ last_tcam_idx_cascade);
+ }
+
+ k = i;
+ }
+
+ /* We need to set the start_cmp bit for the unused TCAMs. */
+ i = 0;
+ while (i < first_tcam)
+ scen_buf.tcam_cfg[i++].start_cmp_set =
+ ICE_AQC_ACL_ALLOC_SCE_START_CMP;
+
+ i = last_tcam + 1;
+ while (i < ICE_AQC_ACL_SLICES)
+ scen_buf.tcam_cfg[i++].start_cmp_set =
+ ICE_AQC_ACL_ALLOC_SCE_START_CMP;
+
+ err = ice_aq_alloc_acl_scen(hw, scen_id, &scen_buf, NULL);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "AQ allocation of ACL scenario failed. status: %d\n",
+ err);
+ goto out;
+ }
+
+ scen->id = *scen_id;
+ ice_acl_commit_partition(hw, scen, false);
+ ice_acl_init_entry(scen);
+ list_add(&scen->list_entry, &hw->acl_tbl->scens);
+
+out:
+ if (err)
+ devm_kfree(ice_hw_to_dev(hw), scen);
+
+ return err;
+}
+
+/**
+ * ice_acl_destroy_scen - destroy an ACL scenario
+ * @hw: pointer to the HW struct
+ * @scen_id: ID of the remove scenario
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_acl_destroy_scen(struct ice_hw *hw, u16 scen_id)
+{
+ struct ice_acl_scen *scen, *tmp_scen;
+ struct ice_flow_prof *p, *tmp;
+ int err;
+
+ /* Remove profiles that use "scen_id" scenario */
+ list_for_each_entry_safe(p, tmp, &hw->fl_profs[ICE_BLK_ACL], l_entry)
+ if (p->cfg.scen && p->cfg.scen->id == scen_id) {
+ err = ice_flow_rem_prof(hw, ICE_BLK_ACL, p->id);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "ice_flow_rem_prof failed. status: %d\n",
+ err);
+ return err;
+ }
+ }
+
+ err = ice_aq_dealloc_acl_scen(hw, scen_id, NULL);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of scenario failed. status: %d\n",
+ err);
+ return err;
+ }
+
+ /* Remove scenario from hw->acl_tbl->scens */
+ list_for_each_entry_safe(scen, tmp_scen, &hw->acl_tbl->scens,
+ list_entry)
+ if (scen->id == scen_id) {
+ ice_acl_commit_partition(hw, scen, true);
+
+ list_del(&scen->list_entry);
+ devm_kfree(ice_hw_to_dev(hw), scen);
+ }
+
+ return 0;
+}
+
/**
* ice_acl_destroy_tbl - Destroy a previously created LEM table for ACL
* @hw: pointer to the HW struct
@@ -296,12 +831,50 @@ int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params)
*/
int ice_acl_destroy_tbl(struct ice_hw *hw)
{
+ struct ice_acl_scen *pos_scen, *tmp_scen;
struct ice_aqc_acl_generic resp_buf;
+ struct ice_aqc_acl_scen buf;
int err;
if (!hw->acl_tbl)
return -ENOENT;
+ /* Mark all the created scenario's TCAM to stop the packet lookup and
+ * delete them afterward
+ */
+ list_for_each_entry_safe(pos_scen, tmp_scen, &hw->acl_tbl->scens,
+ list_entry) {
+ err = ice_aq_query_acl_scen(hw, pos_scen->id, &buf, NULL);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "ice_aq_query_acl_scen() failed. status: %d\n",
+ err);
+ return err;
+ }
+
+ for (int i = 0; i < ICE_AQC_ACL_SLICES; i++) {
+ buf.tcam_cfg[i].chnk_msk = 0;
+ buf.tcam_cfg[i].start_cmp_set =
+ ICE_AQC_ACL_ALLOC_SCE_START_CMP;
+ }
+
+ for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++)
+ buf.act_mem_cfg[i] = 0;
+
+ err = ice_aq_update_acl_scen(hw, pos_scen->id, &buf, NULL);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "ice_aq_update_acl_scen() failed. status: %d\n",
+ err);
+ return err;
+ }
+
+ err = ice_acl_destroy_scen(hw, pos_scen->id);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "deletion of scenario failed. status: %d\n",
+ err);
+ return err;
+ }
+ }
+
err = ice_aq_dealloc_acl_tbl(hw, hw->acl_tbl->id, &resp_buf, NULL);
if (err) {
ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL failed. status: %d\n",
diff --git a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
index f98780afa139..d28594eda14f 100644
--- a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
+++ b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
@@ -1995,6 +1995,8 @@ struct ice_aqc_neigh_dev_req {
ICE_ACL_ENTRY_ALLOC_UNIT)
#define ICE_AQC_ACL_ALLOC_UNITS (ICE_AQC_ACL_SLICES * \
ICE_AQC_MAX_TCAM_ALLOC_UNITS)
+#define ICE_ACL_MAX_WIDTH_BYTES (ICE_AQC_ACL_SLICES * \
+ ICE_AQC_ACL_KEY_WIDTH_BYTES)
struct ice_aqc_acl_alloc_table {
__le16 table_width;
@@ -2071,6 +2073,33 @@ struct ice_aqc_acl_generic {
u8 act_mem[ICE_AQC_MAX_ACTION_MEMORIES];
};
+/* Allocate ACL scenario (indirect 0x0C14). This command doesn't have separate
+ * response buffer since original command buffer gets updated with
+ * 'scen_id' in case of success
+ */
+struct ice_aqc_acl_alloc_scen {
+ union {
+ struct {
+ u8 reserved[8];
+ } cmd;
+ struct {
+ __le16 scen_id;
+ u8 reserved[6];
+ } resp;
+ } ops;
+ __le32 addr_high;
+ __le32 addr_low;
+};
+
+/* De-allocate ACL scenario (direct 0x0C15). This command doesn't need
+ * separate response buffer since nothing to be returned as a response
+ * except status.
+ */
+struct ice_aqc_acl_dealloc_scen {
+ __le16 scen_id;
+ u8 reserved[14];
+};
+
/* Update ACL scenario (direct 0x0C1B)
* Query ACL scenario (direct 0x0C23)
*/
@@ -2841,6 +2870,8 @@ enum ice_adminq_opc {
/* ACL commands */
ice_aqc_opc_alloc_acl_tbl = 0x0C10,
ice_aqc_opc_dealloc_acl_tbl = 0x0C11,
+ ice_aqc_opc_alloc_acl_scen = 0x0C14,
+ ice_aqc_opc_dealloc_acl_scen = 0x0C15,
ice_aqc_opc_update_acl_scen = 0x0C1B,
ice_aqc_opc_program_acl_actpair = 0x0C1C,
ice_aqc_opc_program_acl_entry = 0x0C20,
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool.c b/drivers/net/ethernet/intel/ice/ice_ethtool.c
index ef1c66855723..bbc317e916a6 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool.c
@@ -3133,8 +3133,8 @@ ice_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd,
switch (cmd->cmd) {
case ETHTOOL_GRXCLSRLCNT:
cmd->rule_cnt = hw->ntuple_active_fltr_cnt;
- /* report total rule count */
- cmd->data = ice_get_fdir_cnt_all(hw);
+ /* report max rule count */
+ cmd->data = ice_ntuple_get_max_fltr_cnt(hw);
ret = 0;
break;
case ETHTOOL_GRXCLSRULE:
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
index 516b57ff3b1c..272b3d54f6e4 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
@@ -228,6 +228,24 @@ int ice_get_ethtool_fdir_entry(struct ice_hw *hw, struct ethtool_rxnfc *cmd)
return ret;
}
+/**
+ * ice_ntuple_get_max_fltr_cnt - get max number of allowed filters
+ * @hw: hardware structure containing filter information
+ *
+ * Return: maximum number of allowed filters
+ */
+u32 ice_ntuple_get_max_fltr_cnt(struct ice_hw *hw)
+{
+ int acl_cnt;
+
+ if (hw->dev_caps.num_funcs < 8)
+ acl_cnt = ICE_AQC_ACL_TCAM_DEPTH / ICE_ACL_ENTIRE_SLICE;
+ else
+ acl_cnt = ICE_AQC_ACL_TCAM_DEPTH / ICE_ACL_HALF_SLICE;
+
+ return ice_get_fdir_cnt_all(hw) + acl_cnt;
+}
+
/**
* ice_get_fdir_fltr_ids - fill buffer with filter IDs of active filters
* @hw: hardware structure containing the filter list
@@ -244,8 +262,8 @@ ice_get_fdir_fltr_ids(struct ice_hw *hw, struct ethtool_rxnfc *cmd,
unsigned int cnt = 0;
int val = 0;
- /* report total rule count */
- cmd->data = ice_get_fdir_cnt_all(hw);
+ /* report max rule count */
+ cmd->data = ice_ntuple_get_max_fltr_cnt(hw);
mutex_lock(&hw->fdir_fltr_lock);
@@ -415,6 +433,37 @@ ice_fdir_rem_flow(struct ice_hw *hw, enum ice_block blk,
prof->cnt = 0;
}
+/**
+ * ice_acl_rem_flows - remove ACL flow profiles and all their entries
+ * @hw: hardware structure containing the filter list
+ */
+void ice_acl_rem_flows(struct ice_hw *hw)
+{
+ if (!hw->acl_prof)
+ return;
+
+ for (enum ice_fltr_ptype flow = ICE_FLTR_PTYPE_NONF_NONE;
+ flow < ICE_FLTR_PTYPE_MAX; flow++) {
+ struct ice_acl_hw_prof *prof;
+ int err;
+
+ flow &= ~FLOW_EXT;
+ prof = hw->acl_prof[flow];
+ if (!prof || !prof->seg)
+ continue;
+
+ err = ice_flow_rem_prof(hw, ICE_BLK_ACL, prof->prof_id);
+ if (err) {
+ dev_err(ice_hw_to_dev(hw), "Could not remove ACL profile, flow type %d\n",
+ flow);
+ continue;
+ }
+
+ kfree(prof->seg);
+ prof->seg = NULL;
+ }
+}
+
/**
* ice_fdir_release_flows - release all flows in use for later replay
* @hw: pointer to HW instance
@@ -1589,8 +1638,12 @@ void ice_fdir_replay_fltrs(struct ice_pf *pf)
struct ice_hw *hw = &pf->hw;
list_for_each_entry(f_rule, &hw->fdir_list_head, fltr_node) {
- int err = ice_fdir_write_all_fltr(pf, f_rule, true);
+ int err;
+
+ if (f_rule->acl_fltr)
+ continue;
+ err = ice_fdir_write_all_fltr(pf, f_rule, true);
if (err)
dev_dbg(ice_pf_to_dev(pf), "Flow Director error %d, could not reprogram filter %d\n",
err, f_rule->fltr_id);
@@ -1623,20 +1676,26 @@ int ice_fdir_create_dflt_rules(struct ice_pf *pf)
}
/**
- * ice_ntuple_update_cntrs - increment or decrement filter counter
+ * ice_ntuple_update_cntrs - increment or decrement FDir/ACL filter counters
* @hw: pointer to hardware structure
- * @flow: filter flow type
+ * @fltr: filter node
* @add: true to increment, false to decrement
*/
static void ice_ntuple_update_cntrs(struct ice_hw *hw,
- enum ice_fltr_ptype flow, bool add)
+ struct ice_ntuple_fltr *fltr, bool add)
{
+ enum ice_fltr_ptype flow = fltr->flow_type;
int incr = add ? 1 : -1;
hw->ntuple_active_fltr_cnt += incr;
- if (flow == ICE_FLTR_PTYPE_NONF_NONE || flow >= ICE_FLTR_PTYPE_MAX)
+ if (flow == ICE_FLTR_PTYPE_NONF_NONE || flow >= ICE_FLTR_PTYPE_MAX) {
ice_debug(hw, ICE_DBG_SW, "Unknown filter type %d\n", flow);
+ return;
+ }
+
+ if (fltr->acl_fltr)
+ hw->acl_fltr_cnt[flow] += incr;
else
hw->fdir_fltr_cnt[flow] += incr;
}
@@ -1654,8 +1713,11 @@ void ice_fdir_del_all_fltrs(struct ice_vsi *vsi)
struct ice_hw *hw = &pf->hw;
list_for_each_entry_safe(f_rule, tmp, &hw->fdir_list_head, fltr_node) {
+ if (f_rule->acl_fltr)
+ continue;
+
ice_fdir_write_all_fltr(pf, f_rule, false);
- ice_ntuple_update_cntrs(hw, f_rule->flow_type, false);
+ ice_ntuple_update_cntrs(hw, f_rule, false);
list_del(&f_rule->fltr_node);
devm_kfree(ice_pf_to_dev(pf), f_rule);
}
@@ -1749,7 +1811,7 @@ ice_ntuple_update_list_entry(struct ice_pf *pf, struct ice_ntuple_fltr *input,
err = ice_fdir_write_all_fltr(pf, old_fltr, false);
if (err)
return err;
- ice_ntuple_update_cntrs(hw, old_fltr->flow_type, false);
+ ice_ntuple_update_cntrs(hw, old_fltr, false);
/* update sb-filters count, specific to ring->channel */
ice_update_per_q_fltr(vsi, old_fltr->orig_q_index, false);
if (!input && !hw->fdir_fltr_cnt[old_fltr->flow_type])
@@ -1765,7 +1827,7 @@ ice_ntuple_update_list_entry(struct ice_pf *pf, struct ice_ntuple_fltr *input,
ice_fdir_list_add_fltr(hw, input);
/* update sb-filters count, specific to ring->channel */
ice_update_per_q_fltr(vsi, input->orig_q_index, true);
- ice_ntuple_update_cntrs(hw, input->flow_type, true);
+ ice_ntuple_update_cntrs(hw, input, true);
return 0;
}
@@ -2033,7 +2095,7 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
if (ret)
return ret;
- max_location = ice_get_fdir_cnt_all(hw);
+ max_location = ice_ntuple_get_max_fltr_cnt(hw);
if (fsp->location >= max_location) {
dev_err(dev, "Failed to add filter. The number of ntuple filters or provided location exceed max %d.\n",
max_location);
@@ -2084,7 +2146,7 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
goto release_lock;
remove_sw_rule:
- ice_ntuple_update_cntrs(hw, input->flow_type, false);
+ ice_ntuple_update_cntrs(hw, input, false);
/* update sb-filters count, specific to ring->channel */
ice_update_per_q_fltr(vsi, input->orig_q_index, false);
list_del(&input->fltr_node);
diff --git a/drivers/net/ethernet/intel/ice/ice_fdir.h b/drivers/net/ethernet/intel/ice/ice_fdir.h
index 54f51ae31b40..ad3f841070f7 100644
--- a/drivers/net/ethernet/intel/ice/ice_fdir.h
+++ b/drivers/net/ethernet/intel/ice/ice_fdir.h
@@ -198,6 +198,8 @@ struct ice_ntuple_fltr {
u32 fltr_id;
u8 fdid_prio;
u8 comp_report;
+
+ bool acl_fltr;
};
/* Dummy packet filter definition structure */
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.h b/drivers/net/ethernet/intel/ice/ice_flow.h
index 6c6cdc8addb1..b9b42592b84a 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.h
+++ b/drivers/net/ethernet/intel/ice/ice_flow.h
@@ -482,6 +482,13 @@ struct ice_flow_prof {
DECLARE_BITMAP(vsis, ICE_MAX_VSI);
bool symm; /* Symmetric Hash for RSS */
+
+ union {
+ /* struct sw_recipe */
+ struct ice_acl_scen *scen;
+ /* struct fd */
+ u32 data;
+ } cfg;
};
struct ice_rss_raw_cfg {
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index 981c105e1d3a..952e6167517f 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -4340,6 +4340,8 @@ static int ice_acl_create_hw(struct ice_pf *pf)
struct ice_acl_tbl_params params = {};
struct ice_hw *hw = &pf->hw;
int divider;
+ u16 scen_id;
+ int err;
/* Create a single ACL table that consists of src_ip (4 bytes),
* dest_ip (4 bytes), src_port (2 bytes) and dst_port (2 bytes) for a
@@ -4359,7 +4361,20 @@ static int ice_acl_create_hw(struct ice_pf *pf)
params.concurr = false;
params.num_dep_tbls = 0;
- return ice_acl_create_tbl(hw, ¶ms);
+ err = ice_acl_create_tbl(hw, ¶ms);
+ if (err)
+ return err;
+
+ err = ice_acl_create_scen(hw, params.width, params.depth, &scen_id);
+ if (err)
+ goto destroy_table;
+
+ return 0;
+
+destroy_table:
+ ice_acl_destroy_tbl(hw);
+
+ return err;
}
/**
@@ -4379,6 +4394,9 @@ static int ice_init_acl(struct ice_pf *pf)
*/
static void ice_deinit_acl(struct ice_pf *pf)
{
+ struct ice_hw *hw = &pf->hw;
+
+ ice_acl_rem_flows(hw);
ice_acl_destroy_tbl(&pf->hw);
}
diff --git a/drivers/net/ethernet/intel/ice/ice_type.h b/drivers/net/ethernet/intel/ice/ice_type.h
index 34c381dac547..711c07536df9 100644
--- a/drivers/net/ethernet/intel/ice/ice_type.h
+++ b/drivers/net/ethernet/intel/ice/ice_type.h
@@ -264,6 +264,11 @@ struct ice_fd_hw_prof {
u64 prof_id[ICE_FD_HW_SEG_MAX];
};
+struct ice_acl_hw_prof {
+ struct ice_flow_seg_info *seg;
+ u64 prof_id;
+};
+
/* Common HW capabilities for SW use */
struct ice_hw_common_caps {
u32 valid_functions;
@@ -1013,6 +1018,8 @@ struct ice_hw {
struct udp_tunnel_nic_info udp_tunnel_nic;
struct ice_acl_tbl *acl_tbl;
+ struct ice_acl_hw_prof **acl_prof;
+ u16 acl_fltr_cnt[ICE_FLTR_PTYPE_MAX];
/* dvm boost update information */
struct ice_dvm_table dvm_upd;
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 04/12] ice: initialize ACL scenario
2026-09-17 18:39 ` [PATCH net-next v2 04/12] ice: initialize ACL scenario Tony Nguyen
@ 2026-09-21 19:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:57 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
lukasz.czapnik, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, sx.rinitha
Thank you for your contribution! Sashiko AI review found 14 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 3 · Low: 10
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] ice: unprotected list traversal in ice_acl_destroy_scen
- [Medium] In ice_acl_alloc_partition()
(drivers/net/ethernet/intel/ice/ice_acl_ctrl.c) the contiguous-chunk…
- [Medium] ice_fdir_del_all_fltrs() is changed here to skip every filter
with acl_fltr set ('if (f_rule->acl_fltr) continue;'), removing ACL…
- [Medium] ice_acl_destroy_tbl() declares 'struct ice_aqc_acl_scen buf;'
without initialization and hands it to ice_aq_query_acl_scen().
- [Low] ice_ntuple_get_max_fltr_cnt()
(drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c) unconditionally…
- [Low] The ACL table and its scenario are programmed into the device
only from ice_acl_create_hw() at probe (ice_init_features() ->…
- [Low] ice_adminq_cmd.h documents opcodes 0x0C1B and 0x0C23 as 'direct'
commands above struct ice_aqc_acl_update_query_scen, while the…
- [Low] The kernel-doc added for ice_aq_dealloc_acl_scen() describes
'@scen_id: scen_id to be deallocated (input and output field)', but…
- [Low] The worked example in the comment of ice_acl_tbl_calc_end_idx()
(drivers/net/ethernet/intel/ice/ice_acl_ctrl.c) contradicts the…
- [Low] ice_acl_commit_partition()'s kernel-doc reads '@commit: true if
the partition is being commit', but the true branch calls set_bit(b,…
- [Low] In ice_acl_alloc_partition(), after advancing to the next row
('row = dir > 0 ?
- [Low] ice_acl_create_scen() derives first_tcam/last_tcam from
scen->start/scen->end and uses them to index…
- [Low] ice_acl_destroy_tbl() as introduced by this patch gained three
early 'return err' paths (ice_aq_query_acl_scen(),…
- [Low] ice: dangerous modification of loop control variable in
ice_acl_rem_flows
--
ice: initialize ACL scenario
This completes ACL table initialization by allocating and programming an
initial scenario, adds the AQ wrappers for scenario alloc/dealloc/update/
query, and extends the ntuple filter accounting and reporting to cover ACL
filters.
A few questions below about the new scenario code, the teardown path and
some of the new comments.
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl.c b/drivers/net/ethernet/intel/ice/ice_acl.c
> index 1c9ae47d9efc1..7821ca247c829 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl.c
> @@ -134,3 +134,119 @@ int ice_aq_program_actpair(struct ice_hw *hw, u8 act_mem_idx, u16 act_entry_idx,
[ ... ]
> +/**
> + * ice_aq_dealloc_acl_scen - deallocate ACL scenario
> + * @hw: pointer to the HW struct
> + * @scen_id: scen_id to be deallocated (input and output field)
[Severity: Low]
Is scen_id really an output field here? It is passed by value as a u16 in
ice_aq_dealloc_acl_scen(), so nothing can be written back to the caller:
int ice_aq_dealloc_acl_scen(struct ice_hw *hw, u16 scen_id,
struct ice_sq_cd *cd)
The header comment added for struct ice_aqc_acl_dealloc_scen in the same
patch says the opposite, "nothing to be returned as a response except
status".
> + * @cd: pointer to command details structure or NULL
> + *
> + * Deallocate ACL scenario (direct 0x0C15)
> + *
> + * Return: 0 on success, negative on error
> + */
> +int ice_aq_dealloc_acl_scen(struct ice_hw *hw, u16 scen_id,
> + struct ice_sq_cd *cd)
> +{
> + struct ice_aqc_acl_dealloc_scen *cmd;
> + struct libie_aq_desc desc;
> +
> + ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_dealloc_acl_scen);
> + cmd = libie_aq_raw(&desc);
> + cmd->scen_id = cpu_to_le16(scen_id);
> +
> + return ice_aq_send_cmd(hw, &desc, NULL, 0, cd);
> +}
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> index 9984c07b53f0d..6a60da3034cf2 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> @@ -6,6 +6,80 @@
> /* Determine the TCAM index of entry 'e' within the ACL table */
> #define ICE_ACL_TBL_TCAM_IDX(e) ((e) / ICE_AQC_ACL_TCAM_DEPTH)
>
[ ... ]
> +static u16 ice_acl_tbl_calc_end_idx(u16 start, u16 num_entries, u16 width)
> +{
> + u16 end_idx, add_entries = 0;
> +
> + end_idx = start + (num_entries - 1);
> +
> + /* In case that our ACL partition requires cascading TCAMs */
> + if (width > 1) {
> + u16 num_stack_level;
> +
> + /* Figure out the TCAM stacked level in this ACL scenario */
> + num_stack_level = (start % ICE_AQC_ACL_TCAM_DEPTH) +
> + num_entries;
> + num_stack_level = DIV_ROUND_UP(num_stack_level,
> + ICE_AQC_ACL_TCAM_DEPTH);
> +
> + /* In this case, each entries in our ACL partition span
> + * multiple TCAMs. Thus, we will need to add
> + * ((width - 1) * num_stack_level) TCAM's entries to
> + * end_idx.
> + *
> + * For example : In our case, our scenario is 2x2:
> + * [TCAM 0] [TCAM 1]
> + * [TCAM 2] [TCAM 3]
> + * Assuming that a TCAM will have 512 entries. If "start"
> + * is 500, "num_entries" is 3 and "width" = 2, then end_idx
> + * should be 1024 (belongs to TCAM 2).
> + * Before going to this if statement, end_idx will have the
> + * value of 512. If "width" is 1, then the final value of
> + * end_idx is 512. However, in our case, width is 2, then we
> + * will need add (2 - 1) * 1 * 512. As result, end_idx will
> + * have the value of 1024.
> + */
[Severity: Low]
Do the numbers in this example match what the function computes?
For start = 500, num_entries = 3, width = 2:
end_idx = 500 + (3 - 1) = 502 (comment says 512)
num_stack_level = DIV_ROUND_UP(500 % 512 + 3, 512) = 1
add_entries = (2 - 1) * 1 * 512 = 512
return 502 + 512 = 1014 (comment says 1024)
Since ice_acl_create_scen() derives last_tcam = ICE_ACL_TBL_TCAM_IDX(scen->end),
1014 maps to TCAM 1, not TCAM 2 as the comment states. Could the example be
adjusted to the values the code actually produces?
> + add_entries = (width - 1) * num_stack_level *
> + ICE_AQC_ACL_TCAM_DEPTH;
> + }
> +
> + return end_idx + add_entries;
> +}
> +
> /**
> * ice_acl_init_tbl - initialize ACL table
> * @hw: pointer to the hardware structure
> @@ -288,6 +362,467 @@ int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params)
> return err;
> }
>
> +/**
> + * ice_acl_alloc_partition - Allocate a partition from the ACL table
> + * @hw: pointer to the hardware structure
> + * @req: info of partition being allocated
> + *
> + * Returns: 0 on success, negative on error
> + */
> +static int ice_acl_alloc_partition(struct ice_hw *hw, struct ice_acl_scen *req)
> +{
> + u16 start = 0, cnt = 0, off = 0;
> + u16 width, r_entries;
> + bool done = false;
> + int row;
> + int dir;
[ ... ]
> + if (cnt >= r_entries) {
> + req->start = start;
> + req->num_entry = r_entries;
> + req->end = ice_acl_tbl_calc_end_idx(start, r_entries,
> + width);
> + break;
> + }
> +
> + row = dir > 0 ? row + width : row - width;
> + if (row < 0)
> + return -EINVAL;
[Severity: Low]
Should a negative row here abort the search, or should it be treated the
same way as "all rows have been checked" and fall into the off++ branch
below?
For the upward scan (width > 1) with first_tcam = 0, last_tcam = 5 and
width = 3, the sequence is:
row = 3 scanned
row = 0 scanned
row = -3 return -EINVAL
while off = 1 would still have yielded the valid row 2, so the multi-pass
search described in the comment above is cut short. The caller chain
ice_acl_create_scen() -> ice_acl_create_hw() also gets -EINVAL for what is
an out-of-space condition. The same unconditional return appears again
inside the off++ branch.
> +
> + if (row > hw->acl_tbl->last_tcam ||
> + row < hw->acl_tbl->first_tcam) {
> + /* All rows have been checked. Increment 'off' that
> + * will help yield a different TCAM configuration in
> + * which adjacent TCAMs can be alternatively in the
> + * same row.
> + */
> + off++;
> +
> + /* However, if the new 'off' value yields previously
> + * checked configurations, then exit.
> + */
> + if (off >= width) {
> + done = true;
> + } else {
> + row = dir > 0 ? off :
> + hw->acl_tbl->last_tcam + 1 - off -
> + width;
> + if (row < 0)
> + return -EINVAL;
> + }
> + }
> + } while (!done);
[Severity: Medium]
Can cnt survive across passes here and describe a partition that is not
contiguous?
cnt is declared once outside the do/while and is only reset when an
unavailable chunk is found:
if (!avail) {
cnt = 0;
} else {
...
cnt += ICE_ACL_ENTRY_ALLOC_UNIT;
}
When the pass ends on a row that contributed chunks, the off++ branch
restarts the scan at a different, non-adjacent row without clearing cnt,
so chunks from the new row are added to the leftover count and the
if (cnt >= r_entries) {
req->start = start;
req->num_entry = r_entries;
path can accept a partition spanning chunks that are neither contiguous
nor all free. ice_acl_commit_partition() then clears 'avail' bits and
ice_acl_set_scen_chnk_msk() programs chnk_msk for chunks belonging to
another scenario.
The single in-tree caller (ice_acl_create_hw() with match_width 15 and a
table whose TCAM count equals the scenario width) always succeeds on the
first row, so this looks latent today, but ice_acl_create_scen() is
exported in ice_acl.h. Should cnt be reset when a new pass starts?
The same off++ branch also recomputes row without re-checking
row >= hw->acl_tbl->first_tcam.
> +
> + return cnt >= r_entries ? 0 : -ENOSPC;
> +}
[ ... ]
> +/**
> + * ice_acl_commit_partition - Indicate if the specified partition is active
> + * @hw: pointer to the hardware structure
> + * @scen: pointer to the scenario struct
> + * @commit: true if the partition is being commit
> + */
> +static void ice_acl_commit_partition(struct ice_hw *hw,
> + struct ice_acl_scen *scen, bool commit)
> +{
[Severity: Low]
Is the documented polarity of @commit inverted? The true branch does
if (commit)
set_bit(b, hw->acl_tbl->avail);
else
clear_bit(b, hw->acl_tbl->avail);
and struct ice_acl_tbl documents 'avail' as "Keep track of available
64-entry chunks in TCAMs", so commit == true marks the chunks free again.
That matches the call sites, where ice_acl_create_scen() passes false
after a successful allocation and ice_acl_destroy_scen() passes true while
tearing the scenario down, but not the kernel-doc.
> + u16 tcam_idx, off, num_cscd, units;
[ ... ]
> +int ice_acl_create_scen(struct ice_hw *hw, u16 match_width, u16 num_entries,
> + u16 *scen_id)
> +{
> + u8 cascade_cnt, first_tcam, last_tcam, i, k;
> + struct ice_aqc_acl_scen scen_buf = {};
> + struct ice_acl_scen *scen;
> + int err;
> +
> + if (match_width > ICE_ACL_MAX_WIDTH_BYTES)
> + return -EINVAL;
[ ... ]
> + /* Determine the number of cascade TCAMs, given the scenario's width */
> + cascade_cnt = DIV_ROUND_UP(scen->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
> + first_tcam = ICE_ACL_TBL_TCAM_IDX(scen->start);
> + last_tcam = ICE_ACL_TBL_TCAM_IDX(scen->end);
[Severity: Low]
Would it be worth bounding first_tcam/last_tcam before they are used to
index scen_buf.tcam_cfg[ICE_AQC_ACL_SLICES]? They are derived from
scen->start/scen->end, which in turn come from ice_acl_alloc_partition()
and from num_entries, and num_entries is not validated:
r_entries = ALIGN(req->num_entry, ICE_ACL_ENTRY_ALLOC_UNIT);
For num_entry == 0, ice_acl_tbl_calc_end_idx() evaluates
start + (0 - 1) in u16 and last_tcam becomes 127, which would index past
the 16-element tcam_cfg[] array on the stack. Similarly,
ice_acl_alloc_partition() and ice_acl_commit_partition() build
b = (w * ICE_AQC_MAX_TCAM_ALLOC_UNITS) + p;
from the firmware-supplied first_tcam/last_tcam and pass it to
test_bit()/set_bit()/clear_bit() on the 128-bit 'avail' bitmap without a
range check.
The only caller today is ice_acl_create_hw(), which always passes depth
512 or 256, so this is a hardening question rather than a reachable path.
> +
> + /* For each scenario, we reserved last three bytes of scenario width for
> + * packet direction flag, profile ID and range checker. Thus, we want to
> + * return back to the caller the eff_width, pkt_dir_idx, rng_chk_idx and
> + * pid_idx.
> + */
[ ... ]
> /**
> * ice_acl_destroy_tbl - Destroy a previously created LEM table for ACL
> * @hw: pointer to the HW struct
> @@ -296,12 +831,50 @@ int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params)
> */
> int ice_acl_destroy_tbl(struct ice_hw *hw)
> {
> + struct ice_acl_scen *pos_scen, *tmp_scen;
> struct ice_aqc_acl_generic resp_buf;
> + struct ice_aqc_acl_scen buf;
> int err;
[Severity: Medium]
Does this expose uninitialized stack bytes to the device? buf is not
initialized here, unlike scen_buf in ice_acl_create_scen() which uses
"= {}", and ice_sq_send_cmd() copies the caller buffer into the DMA-mapped
AQ buffer for every indirect command regardless of direction:
drivers/net/ethernet/intel/ice/ice_controlq.c:ice_sq_send_cmd() {
/* if buf is not NULL assume indirect command */
if (buf) {
dma_buf = &cq->sq.r.sq_bi[cq->sq.next_to_use];
/* copy the user buf into the respective DMA buf */
memcpy(dma_buf->va, buf, buf_size);
...
}
so roughly 132 bytes of stack become visible to firmware on the
ice_aq_query_acl_scen() call below.
On completion only the firmware-reported length is copied back:
u16 copy_size = le16_to_cpu(desc->datalen);
if (copy_size > buf_size) {
...
} else {
memcpy(buf, dma_buf->va, copy_size);
}
Since the disable loop below only rewrites chnk_msk, start_cmp_set and
act_mem_cfg, a short-but-successful response would leave stack residue in
tcam_cfg[].tcam_select[], which ice_aq_update_acl_scen() then programs
back into the scenario. Would "struct ice_aqc_acl_scen buf = {};" be
appropriate?
>
> if (!hw->acl_tbl)
> return -ENOENT;
>
> + /* Mark all the created scenario's TCAM to stop the packet lookup and
> + * delete them afterward
> + */
> + list_for_each_entry_safe(pos_scen, tmp_scen, &hw->acl_tbl->scens,
> + list_entry) {
> + err = ice_aq_query_acl_scen(hw, pos_scen->id, &buf, NULL);
> + if (err) {
> + ice_debug(hw, ICE_DBG_ACL, "ice_aq_query_acl_scen() failed. status: %d\n",
> + err);
> + return err;
> + }
[Severity: Low]
Do these three new early returns leak the struct ice_acl_tbl allocation?
They return before ice_aq_dealloc_acl_tbl() and before the
kfree(hw->acl_tbl) / hw->acl_tbl = NULL at the end of the function, and
both callers (ice_deinit_acl() and the destroy_table unwind in
ice_acl_create_hw()) discard the return value, so a single AQ error during
unload leaves hw->acl_tbl set and the firmware resources allocated with no
retry.
This is addressed later in the series by "ice: add ACL reset recovery and
NTUPLE feature toggle", where ice_acl_destroy_tbl() becomes void, only
logs each AQ failure and always ends with kfree(hw->acl_tbl) followed by
hw->acl_tbl = NULL. Could that cleanup ordering be used from the start?
> +
> + for (int i = 0; i < ICE_AQC_ACL_SLICES; i++) {
> + buf.tcam_cfg[i].chnk_msk = 0;
> + buf.tcam_cfg[i].start_cmp_set =
> + ICE_AQC_ACL_ALLOC_SCE_START_CMP;
> + }
> +
> + for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++)
> + buf.act_mem_cfg[i] = 0;
> +
> + err = ice_aq_update_acl_scen(hw, pos_scen->id, &buf, NULL);
> + if (err) {
> + ice_debug(hw, ICE_DBG_ACL, "ice_aq_update_acl_scen() failed. status: %d\n",
> + err);
> + return err;
> + }
> +
> + err = ice_acl_destroy_scen(hw, pos_scen->id);
> + if (err) {
> + ice_debug(hw, ICE_DBG_ACL, "deletion of scenario failed. status: %d\n",
> + err);
> + return err;
> + }
> + }
> +
> err = ice_aq_dealloc_acl_tbl(hw, hw->acl_tbl->id, &resp_buf, NULL);
> if (err) {
> ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL failed. status: %d\n",
> diff --git a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
> index f98780afa1399..d28594eda14f7 100644
> --- a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
> +++ b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
> @@ -2071,6 +2073,33 @@ struct ice_aqc_acl_generic {
> u8 act_mem[ICE_AQC_MAX_ACTION_MEMORIES];
> };
>
[ ... ]
> +/* De-allocate ACL scenario (direct 0x0C15). This command doesn't need
> + * separate response buffer since nothing to be returned as a response
> + * except status.
> + */
> +struct ice_aqc_acl_dealloc_scen {
> + __le16 scen_id;
> + u8 reserved[14];
> +};
> +
> /* Update ACL scenario (direct 0x0C1B)
> * Query ACL scenario (direct 0x0C23)
> */
[Severity: Low]
Are 0x0C1B and 0x0C23 direct commands? The new code in ice_acl.c treats
both as indirect, passing a struct ice_aqc_acl_scen buffer and setting
LIBIE_AQ_FLAG_RD for the update case:
return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
and the kernel-doc added for the wrappers says "Update ACL scenario
(indirect 0x0C1B)" and "Query ACL scenario (indirect 0x0C23)". The
struct's own addr_high/addr_low fields also suggest indirect, so should
this comment say indirect?
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index 516b57ff3b1ca..272b3d54f6e47 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> @@ -228,6 +228,24 @@ int ice_get_ethtool_fdir_entry(struct ice_hw *hw, struct ethtool_rxnfc *cmd)
> return ret;
> }
>
> +/**
> + * ice_ntuple_get_max_fltr_cnt - get max number of allowed filters
> + * @hw: hardware structure containing filter information
> + *
> + * Return: maximum number of allowed filters
> + */
> +u32 ice_ntuple_get_max_fltr_cnt(struct ice_hw *hw)
> +{
> + int acl_cnt;
> +
> + if (hw->dev_caps.num_funcs < 8)
> + acl_cnt = ICE_AQC_ACL_TCAM_DEPTH / ICE_ACL_ENTIRE_SLICE;
> + else
> + acl_cnt = ICE_AQC_ACL_TCAM_DEPTH / ICE_ACL_HALF_SLICE;
> +
> + return ice_get_fdir_cnt_all(hw) + acl_cnt;
> +}
[Severity: Low]
Should this check hw->acl_tbl before adding the ACL capacity? ACL init is
explicitly non-fatal in ice_init_features():
/* Note: ACL init failure is non-fatal to load */
if (ice_init_acl(pf))
dev_err(dev, "Failed to initialize ACL\n");
so when ice_acl_create_hw() fails, hw->acl_tbl stays NULL while this value
is still reported to user space as the rule table size for
ETHTOOL_GRXCLSRLCNT and ETHTOOL_GRXCLSRLALL (cmd->data) and used as the
only bound on fsp->location in ice_add_ntuple_ethtool(). User space then
sees 256 or 512 extra locations that can never be programmed.
The depth formula is also re-derived from ICE_ACL_ENTIRE_SLICE /
ICE_ACL_HALF_SLICE here rather than taken from the table/scenario geometry
that ice_acl_create_hw() obtained from firmware, so the two can drift.
Later patches in the series add an hw->acl_tbl test in
ice_add_ntuple_ethtool(), but this helper keeps the unconditional ACL term.
> +
> /**
> * ice_get_fdir_fltr_ids - fill buffer with filter IDs of active filters
> * @hw: hardware structure containing the filter list
[ ... ]
> @@ -1654,8 +1713,11 @@ void ice_fdir_del_all_fltrs(struct ice_vsi *vsi)
> struct ice_hw *hw = &pf->hw;
>
> list_for_each_entry_safe(f_rule, tmp, &hw->fdir_list_head, fltr_node) {
> + if (f_rule->acl_fltr)
> + continue;
> +
> ice_fdir_write_all_fltr(pf, f_rule, false);
> - ice_ntuple_update_cntrs(hw, f_rule->flow_type, false);
> + ice_ntuple_update_cntrs(hw, f_rule, false);
> list_del(&f_rule->fltr_node);
> devm_kfree(ice_pf_to_dev(pf), f_rule);
> }
[Severity: Medium]
Does skipping acl_fltr entries here leak the struct ice_ntuple_fltr
objects on driver unload?
This is the only walker of hw->fdir_list_head that unlinks and frees
filter nodes, and no ACL equivalent is added to the removal path. At the
end of the series, device removal runs:
ice_deinit_features()
ice_deinit_acl() /* ice_acl_rem_flows(),
ice_acl_destroy_tbl(), frees
hw->acl_prof - no filter walk */
ice_deinit_fdir()
ice_vsi_manage_fdir()
ice_fdir_del_all_fltrs() /* continues on acl_fltr */
while the new ice_acl_del_all_fltrs() is only reached from
ice_vsi_manage_acl() (ethtool -K ntuple off) and from the ice_rebuild()
failure branch. Since "ice: use plain alloc/dealloc for ice_ntuple_fltr"
switched these nodes to kzalloc/kfree, every ACL rule present at unload
would be leaked and hw->ntuple_active_fltr_cnt / hw->acl_fltr_cnt[] never
decremented.
> diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
> index 981c105e1d3a2..952e6167517f4 100644
> --- a/drivers/net/ethernet/intel/ice/ice_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_main.c
> @@ -4359,7 +4361,20 @@ static int ice_acl_create_hw(struct ice_pf *pf)
> params.concurr = false;
> params.num_dep_tbls = 0;
>
> - return ice_acl_create_tbl(hw, ¶ms);
> + err = ice_acl_create_tbl(hw, ¶ms);
> + if (err)
> + return err;
> +
> + err = ice_acl_create_scen(hw, params.width, params.depth, &scen_id);
> + if (err)
> + goto destroy_table;
[Severity: Low]
What happens to this table and scenario across a PF reset? At this commit
ice_acl_create_hw() is only reachable from probe via ice_init_features()
-> ice_init_acl(), and ice_rebuild() replays Flow Director profiles and
filters but has no ACL re-creation, while ice_fdir_replay_fltrs() is
changed here to skip acl_fltr entries. After a PFR/CORER/GLOBR the
software state (alloc ID, first/last TCAM, the 'avail' chunk bitmap, the
firmware scen_id) would describe hardware state that no longer exists.
This is closed later in the series by "ice: add ACL reset recovery and
NTUPLE feature toggle", which adds to ice_rebuild():
if (test_bit(ICE_FLAG_ACL_ENA, pf->flags)) {
ice_acl_destroy_tbl(hw);
if (!ice_acl_create_hw(pf)) {
so this is only about the intermediate state within the series.
> +
> + return 0;
> +
> +destroy_table:
> + ice_acl_destroy_tbl(hw);
> +
> + return err;
> }
[ ... ]
> @@ -288,6 +362,467 @@ int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params)
> return err;
> }
>
> +/**
> + * ice_acl_alloc_partition - Allocate a partition from the ACL table
> + * @hw: pointer to the hardware structure
> + * @req: info of partition being allocated
> + *
> + * Returns: 0 on success, negative on error
> + */
> +static int ice_acl_alloc_partition(struct ice_hw *hw, struct ice_acl_scen *req)
> +{
> + u16 start = 0, cnt = 0, off = 0;
> + u16 width, r_entries;
> + bool done = false;
> + int row;
> + int dir;
> +
> + /* Determine the number of TCAMs each entry overlaps */
> + width = DIV_ROUND_UP(req->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
> +
> + /* Check if we have enough TCAMs to accommodate the width */
> + if (width > hw->acl_tbl->last_tcam - hw->acl_tbl->first_tcam + 1)
> + return -ENOSPC;
> +
> + /* Number of entries must be multiple of ICE_ACL_ENTRY_ALLOC_UNIT's */
> + r_entries = ALIGN(req->num_entry, ICE_ACL_ENTRY_ALLOC_UNIT);
> +
> + /* To look for an available partition that can accommodate the request,
> + * the process first logically arranges available TCAMs in rows such
> + * that each row produces entries with the requested width. It then
> + * scans the TCAMs' available bitmap, one bit at a time, and
> + * accumulates contiguous available 64-entry chunks until there are
> + * enough of them or when all TCAM configurations have been checked.
> + *
> + * For width of 1 TCAM, the scanning process starts from the top most
> + * TCAM, and goes downward. Available bitmaps are examined from LSB
> + * to MSB.
> + *
> + * For width of multiple TCAMs, the process starts from the bottom-most
> + * row of TCAMs, and goes upward. Available bitmaps are examined from
> + * the MSB to the LSB.
> + *
> + * To make sure that adjacent TCAMs can be logically arranged in the
> + * same row, the scanning process may have multiple passes. In each
> + * pass, the first TCAM of the bottom-most row is displaced by one
> + * additional TCAM. The width of the row and the number of the TCAMs
> + * available determine the number of passes. When the displacement is
> + * more than the size of width, the TCAM row configurations will
> + * repeat. The process will terminate when the configurations repeat.
> + *
> + * Available partitions can span more than one row of TCAMs.
> + */
> + if (width == 1) {
> + row = hw->acl_tbl->first_tcam;
> + dir = 1;
> + } else {
> + /* Start with the bottom-most row, and scan for available
> + * entries upward
> + */
> + row = hw->acl_tbl->last_tcam + 1 - width;
> + if (row < 0)
> + return -EINVAL;
> +
> + dir = -1;
> + }
> +
> + do {
> + /* Scan all 64-entry chunks, one chunk at a time, in the
> + * current TCAM row
> + */
> + for (u16 i = 0;
> + i < ICE_AQC_MAX_TCAM_ALLOC_UNITS && cnt < r_entries;
> + i++) {
> + bool avail = true;
> + u16 p;
> +
> + /* Compute the cumulative available mask across the
> + * TCAM row to determine if the current 64-entry chunk
> + * is available.
> + */
> + p = dir > 0 ? i : ICE_AQC_MAX_TCAM_ALLOC_UNITS - i - 1;
> + for (u16 w = row; w < row + width && avail; w++) {
> + u16 b;
> +
> + b = (w * ICE_AQC_MAX_TCAM_ALLOC_UNITS) + p;
> + avail &= test_bit(b, hw->acl_tbl->avail);
> + }
> +
> + if (!avail) {
> + cnt = 0;
> + } else {
> + /* Compute the starting index of the newly
> + * found partition. When 'dir' is negative, the
> + * scan processes is going upward. If so, the
> + * starting index needs to be updated for every
> + * available 64-entry chunk found.
> + */
> + if (!cnt || dir < 0)
> + start = (row * ICE_AQC_ACL_TCAM_DEPTH) +
> + (p * ICE_ACL_ENTRY_ALLOC_UNIT);
> + cnt += ICE_ACL_ENTRY_ALLOC_UNIT;
> + }
> + }
> +
> + if (cnt >= r_entries) {
> + req->start = start;
> + req->num_entry = r_entries;
> + req->end = ice_acl_tbl_calc_end_idx(start, r_entries,
> + width);
> + break;
> + }
> +
> + row = dir > 0 ? row + width : row - width;
> + if (row < 0)
> + return -EINVAL;
> +
> + if (row > hw->acl_tbl->last_tcam ||
> + row < hw->acl_tbl->first_tcam) {
> + /* All rows have been checked. Increment 'off' that
> + * will help yield a different TCAM configuration in
> + * which adjacent TCAMs can be alternatively in the
> + * same row.
> + */
> + off++;
> +
> + /* However, if the new 'off' value yields previously
> + * checked configurations, then exit.
> + */
> + if (off >= width) {
> + done = true;
> + } else {
> + row = dir > 0 ? off :
> + hw->acl_tbl->last_tcam + 1 - off -
> + width;
> + if (row < 0)
> + return -EINVAL;
> + }
> + }
> + } while (!done);
> +
> + return cnt >= r_entries ? 0 : -ENOSPC;
> +}
> +
> +/**
> + * ice_acl_fill_tcam_select - fill key byte selection for scenario's TCAM
> + * @scen_buf: Pointer to the scenario buffer that needs to be populated
> + * @scen: Pointer to the available space for the scenario
> + * @tcam_idx: Index of the TCAM used for this scenario
> + * @tcam_idx_in_cascade: Local index of the TCAM in the cascade scenario
> + *
> + * For all TCAM that participate in this scenario, fill out the tcam_select
> + * value.
> + */
> +static void ice_acl_fill_tcam_select(struct ice_aqc_acl_scen *scen_buf,
> + struct ice_acl_scen *scen, u16 tcam_idx,
> + u16 tcam_idx_in_cascade)
> +{
> + u16 cascade_cnt, idx;
> +
> + idx = tcam_idx_in_cascade * ICE_AQC_ACL_KEY_WIDTH_BYTES;
> + cascade_cnt = DIV_ROUND_UP(scen->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
> +
> + /* For each scenario, we reserved last three bytes of scenario width for
> + * profile ID, range checker, and packet direction. Thus, the last three
> + * bytes of the last cascaded TCAMs will have value of 1st, 31st and
> + * 32nd byte location of BYTE selection base.
> + *
> + * For other bytes in the TCAMs:
> + * For non-cascade mode (1 TCAM wide) scenario, TCAM[x]'s Select {0-1}
> + * select indices 0-1 of the Byte Selection Base
> + * For cascade mode, the leftmost TCAM of the first cascade row selects
> + * indices 0-4 of the Byte Selection Base; the second TCAM in the
> + * cascade row selects indices starting with 5-n
> + */
> + for (int j = 0; j < ICE_AQC_ACL_KEY_WIDTH_BYTES; j++) {
> + /* PKT DIR uses the 1st location of Byte Selection Base: + 1 */
> + u8 val = ICE_AQC_ACL_BYTE_SEL_BASE + 1 + idx;
> +
> + if (tcam_idx_in_cascade == cascade_cnt - 1) {
> + if (j == ICE_ACL_SCEN_RNG_CHK_IDX_IN_TCAM)
> + val = ICE_AQC_ACL_BYTE_SEL_BASE_RNG_CHK;
> + else if (j == ICE_ACL_SCEN_PID_IDX_IN_TCAM)
> + val = ICE_AQC_ACL_BYTE_SEL_BASE_PID;
> + else if (j == ICE_ACL_SCEN_PKT_DIR_IDX_IN_TCAM)
> + val = ICE_AQC_ACL_BYTE_SEL_BASE_PKT_DIR;
> + }
> +
> + /* In case that scenario's width is greater than the width of
> + * the Byte selection base, we will not assign a value to the
> + * tcam_select[j]. As a result, the tcam_select[j] will have
> + * default value which is zero.
> + */
> + if (val > ICE_AQC_ACL_BYTE_SEL_BASE_RNG_CHK)
> + continue;
> +
> + scen_buf->tcam_cfg[tcam_idx].tcam_select[j] = val;
> +
> + idx++;
> + }
> +}
> +
> +/**
> + * ice_acl_set_scen_chnk_msk - set entries chunk masks
> + * @scen_buf: Pointer to the scenario buffer that needs to be populated
> + * @scen: pointer to the available space for the scenario
> + *
> + * Set the chunk mask for the entries that will be used by this scenario
> + */
> +static void ice_acl_set_scen_chnk_msk(struct ice_aqc_acl_scen *scen_buf,
> + struct ice_acl_scen *scen)
> +{
> + u16 tcam_idx, num_cscd, units;
> + u8 chnk_offst;
> +
> + /* Determine the starting TCAM index and offset of the start entry */
> + tcam_idx = ICE_ACL_TBL_TCAM_IDX(scen->start);
> + chnk_offst = (u8)((scen->start % ICE_AQC_ACL_TCAM_DEPTH) /
> + ICE_ACL_ENTRY_ALLOC_UNIT);
> +
> + /* Entries are allocated and tracked in multiple of 64's */
> + units = scen->num_entry / ICE_ACL_ENTRY_ALLOC_UNIT;
> +
> + /* Determine number of cascaded TCAMs */
> + num_cscd = scen->width / ICE_AQC_ACL_KEY_WIDTH_BYTES;
> +
> + for (u16 cnt = 0; cnt < units; cnt++) {
> + /* Set the corresponding bitmap of individual 64-entry
> + * chunk spans across a cascade of 1 or more TCAMs
> + * For each TCAM, there will be (ICE_AQC_ACL_TCAM_DEPTH
> + * / ICE_ACL_ENTRY_ALLOC_UNIT) or 8 chunks.
> + */
> + for (u16 i = tcam_idx; i < tcam_idx + num_cscd; i++)
> + scen_buf->tcam_cfg[i].chnk_msk |= BIT(chnk_offst);
> +
> + chnk_offst = (chnk_offst + 1) % ICE_AQC_MAX_TCAM_ALLOC_UNITS;
> + if (!chnk_offst)
> + tcam_idx += num_cscd;
> + }
> +}
> +
> +/**
> + * ice_acl_assign_act_mem_for_scen - associate action memories to new TCAM
> + * @tbl: pointer to ACL table structure
> + * @scen: pointer to the scenario struct
> + * @scen_buf: pointer to the available space for the scenario
> + * @current_tcam_idx: theoretical index of the TCAM that we associated those
> + * action memory banks with, at the table creation time
> + * @target_tcam_idx: index of the TCAM that we want to associate those action
> + * memory banks with
> + */
> +static void ice_acl_assign_act_mem_for_scen(struct ice_acl_tbl *tbl,
> + struct ice_acl_scen *scen,
> + struct ice_aqc_acl_scen *scen_buf,
> + u8 current_tcam_idx,
> + u8 target_tcam_idx)
> +{
> + for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++) {
> + struct ice_acl_act_mem *p_mem = &tbl->act_mems[i];
> +
> + if (p_mem->act_mem == ICE_ACL_ACT_MEM_ACT_MEM_INVAL ||
> + p_mem->member_of_tcam != current_tcam_idx)
> + continue;
> +
> + scen_buf->act_mem_cfg[i] = target_tcam_idx;
> + scen_buf->act_mem_cfg[i] |= ICE_AQC_ACL_SCE_ACT_MEM_EN;
> + set_bit(i, scen->act_mem_bitmap);
> + }
> +}
> +
> +/**
> + * ice_acl_commit_partition - Indicate if the specified partition is active
> + * @hw: pointer to the hardware structure
> + * @scen: pointer to the scenario struct
> + * @commit: true if the partition is being commit
> + */
> +static void ice_acl_commit_partition(struct ice_hw *hw,
> + struct ice_acl_scen *scen, bool commit)
> +{
> + u16 tcam_idx, off, num_cscd, units;
> +
> + /* Determine the starting TCAM index and offset of the start entry */
> + tcam_idx = ICE_ACL_TBL_TCAM_IDX(scen->start);
> + off = (scen->start % ICE_AQC_ACL_TCAM_DEPTH) /
> + ICE_ACL_ENTRY_ALLOC_UNIT;
> +
> + /* Entries are allocated and tracked in multiple of 64's */
> + units = scen->num_entry / ICE_ACL_ENTRY_ALLOC_UNIT;
> +
> + /* Determine number of cascaded TCAM */
> + num_cscd = scen->width / ICE_AQC_ACL_KEY_WIDTH_BYTES;
> +
> + for (u16 cnt = 0; cnt < units; cnt++) {
> + /* Set/clear the corresponding bitmap of individual 64-entry
> + * chunk spans across a row of 1 or more TCAMs
> + */
> + for (u16 w = 0; w < num_cscd; w++) {
> + u16 b;
> +
> + b = ((tcam_idx + w) * ICE_AQC_MAX_TCAM_ALLOC_UNITS) +
> + off;
> + if (commit)
> + set_bit(b, hw->acl_tbl->avail);
> + else
> + clear_bit(b, hw->acl_tbl->avail);
> + }
> +
> + off = (off + 1) % ICE_AQC_MAX_TCAM_ALLOC_UNITS;
> + if (!off)
> + tcam_idx += num_cscd;
> + }
> +}
> +
> +/**
> + * ice_acl_create_scen - create ACL scenario
> + * @hw: pointer to the hardware structure
> + * @match_width: number of bytes to be matched in this scenario
> + * @num_entries: number of entries to be allocated for the scenario
> + * @scen_id: holds returned scenario ID if successful
> + *
> + * Return: 0 on success, negative on error
> + */
> +int ice_acl_create_scen(struct ice_hw *hw, u16 match_width, u16 num_entries,
> + u16 *scen_id)
> +{
> + u8 cascade_cnt, first_tcam, last_tcam, i, k;
> + struct ice_aqc_acl_scen scen_buf = {};
> + struct ice_acl_scen *scen;
> + int err;
> +
> + if (match_width > ICE_ACL_MAX_WIDTH_BYTES)
> + return -EINVAL;
> +
> + scen = devm_kzalloc(ice_hw_to_dev(hw), sizeof(*scen), GFP_KERNEL);
> + if (!scen)
> + return -ENOMEM;
> +
> + scen->start = hw->acl_tbl->first_entry;
> + scen->width = ICE_AQC_ACL_KEY_WIDTH_BYTES *
> + DIV_ROUND_UP(match_width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
> + scen->num_entry = num_entries;
> +
> + err = ice_acl_alloc_partition(hw, scen);
> + if (err)
> + goto out;
> +
> + /* Determine the number of cascade TCAMs, given the scenario's width */
> + cascade_cnt = DIV_ROUND_UP(scen->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
> + first_tcam = ICE_ACL_TBL_TCAM_IDX(scen->start);
> + last_tcam = ICE_ACL_TBL_TCAM_IDX(scen->end);
> +
> + /* For each scenario, we reserved last three bytes of scenario width for
> + * packet direction flag, profile ID and range checker. Thus, we want to
> + * return back to the caller the eff_width, pkt_dir_idx, rng_chk_idx and
> + * pid_idx.
> + */
> + scen->eff_width = cascade_cnt * ICE_AQC_ACL_KEY_WIDTH_BYTES -
> + ICE_ACL_SCEN_MIN_WIDTH;
> + scen->rng_chk_idx = (cascade_cnt - 1) * ICE_AQC_ACL_KEY_WIDTH_BYTES +
> + ICE_ACL_SCEN_RNG_CHK_IDX_IN_TCAM;
> + scen->pid_idx = (cascade_cnt - 1) * ICE_AQC_ACL_KEY_WIDTH_BYTES +
> + ICE_ACL_SCEN_PID_IDX_IN_TCAM;
> + scen->pkt_dir_idx = (cascade_cnt - 1) * ICE_AQC_ACL_KEY_WIDTH_BYTES +
> + ICE_ACL_SCEN_PKT_DIR_IDX_IN_TCAM;
> +
> + /* set the chunk mask for the tcams */
> + ice_acl_set_scen_chnk_msk(&scen_buf, scen);
> +
> + /* set the TCAM select and start_cmp and start_set bits */
> + k = first_tcam;
> + /* set the START_SET bit at the beginning of the stack */
> + scen_buf.tcam_cfg[k].start_cmp_set |= ICE_AQC_ACL_ALLOC_SCE_START_SET;
> + while (k <= last_tcam) {
> + u8 last_tcam_idx_cascade = cascade_cnt + k - 1;
> +
> + /* set start_cmp for the first cascaded TCAM */
> + scen_buf.tcam_cfg[k].start_cmp_set |=
> + ICE_AQC_ACL_ALLOC_SCE_START_CMP;
> +
> + /* cascade TCAMs up to the width of the scenario */
> + for (i = k; i < cascade_cnt + k; i++) {
> + ice_acl_fill_tcam_select(&scen_buf, scen, i, i - k);
> + ice_acl_assign_act_mem_for_scen(hw->acl_tbl, scen,
> + &scen_buf, i,
> + last_tcam_idx_cascade);
> + }
> +
> + k = i;
> + }
> +
> + /* We need to set the start_cmp bit for the unused TCAMs. */
> + i = 0;
> + while (i < first_tcam)
> + scen_buf.tcam_cfg[i++].start_cmp_set =
> + ICE_AQC_ACL_ALLOC_SCE_START_CMP;
> +
> + i = last_tcam + 1;
> + while (i < ICE_AQC_ACL_SLICES)
> + scen_buf.tcam_cfg[i++].start_cmp_set =
> + ICE_AQC_ACL_ALLOC_SCE_START_CMP;
> +
> + err = ice_aq_alloc_acl_scen(hw, scen_id, &scen_buf, NULL);
> + if (err) {
> + ice_debug(hw, ICE_DBG_ACL, "AQ allocation of ACL scenario failed. status: %d\n",
> + err);
> + goto out;
> + }
> +
> + scen->id = *scen_id;
> + ice_acl_commit_partition(hw, scen, false);
> + ice_acl_init_entry(scen);
> + list_add(&scen->list_entry, &hw->acl_tbl->scens);
> +
> +out:
> + if (err)
> + devm_kfree(ice_hw_to_dev(hw), scen);
> +
> + return err;
> +}
> +
> +/**
> + * ice_acl_destroy_scen - destroy an ACL scenario
> + * @hw: pointer to the HW struct
> + * @scen_id: ID of the remove scenario
> + *
> + * Return: 0 on success, negative on error
> + */
> +static int ice_acl_destroy_scen(struct ice_hw *hw, u16 scen_id)
> +{
> + struct ice_acl_scen *scen, *tmp_scen;
> + struct ice_flow_prof *p, *tmp;
> + int err;
> +
> + /* Remove profiles that use "scen_id" scenario */
> + list_for_each_entry_safe(p, tmp, &hw->fl_profs[ICE_BLK_ACL], l_entry)
> + if (p->cfg.scen && p->cfg.scen->id == scen_id) {
> + err = ice_flow_rem_prof(hw, ICE_BLK_ACL, p->id);
> + if (err) {
> + ice_debug(hw, ICE_DBG_ACL, "ice_flow_rem_prof failed. status: %d\n",
> + err);
> + return err;
> + }
> + }
> +
> + err = ice_aq_dealloc_acl_scen(hw, scen_id, NULL);
> + if (err) {
> + ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of scenario failed. status: %d\n",
> + err);
> + return err;
> + }
> +
> + /* Remove scenario from hw->acl_tbl->scens */
> + list_for_each_entry_safe(scen, tmp_scen, &hw->acl_tbl->scens,
> + list_entry)
> + if (scen->id == scen_id) {
> + ice_acl_commit_partition(hw, scen, true);
> +
> + list_del(&scen->list_entry);
> + devm_kfree(ice_hw_to_dev(hw), scen);
> + }
> +
> + return 0;
> +}
> +
> /**
> * ice_acl_destroy_tbl - Destroy a previously created LEM table for ACL
> * @hw: pointer to the HW struct
[Severity: High]
Should this walk of hw->fl_profs[ICE_BLK_ACL] hold
hw->fl_profs_locks[ICE_BLK_ACL]? Every other accessor of that list
takes the mutex, including the function called from inside this very
loop:
ice_flow_rem_prof() {
mutex_lock(&hw->fl_profs_locks[blk]);
prof = ice_flow_find_prof_id(hw, blk, prof_id);
...
status = ice_flow_rem_prof_sync(hw, blk, prof);
out:
mutex_unlock(&hw->fl_profs_locks[blk]);
}
So the traversal in ice_acl_destroy_scen() reads and caches the 'tmp'
pointer with no serialization, while ice_flow_rem_prof_sync() unlinks
and frees profiles on the same list; an add/remove from another context
(the ethtool ntuple paths reach ice_flow_add_prof()/ice_flow_rem_prof())
can then make the cached 'tmp' stale.
Simply wrapping the loop in mutex_lock(&hw->fl_profs_locks[ICE_BLK_ACL])
would deadlock, since ice_flow_rem_prof() acquires it unconditionally,
which suggests the shape of this loop is wrong rather than just the
missing lock. Would it make sense to collect the matching profile IDs
under the lock and then call ice_flow_rem_prof() for each ID after
dropping it, or to add a helper that walks the list and calls
ice_flow_rem_prof_sync() while holding the mutex? What serializes this
path against concurrent ACL profile add/remove today?
[ ... ]
> @@ -415,6 +433,37 @@ ice_fdir_rem_flow(struct ice_hw *hw, enum ice_block blk,
> prof->cnt = 0;
> }
>
> +/**
> + * ice_acl_rem_flows - remove ACL flow profiles and all their entries
> + * @hw: hardware structure containing the filter list
> + */
> +void ice_acl_rem_flows(struct ice_hw *hw)
> +{
> + if (!hw->acl_prof)
> + return;
> +
> + for (enum ice_fltr_ptype flow = ICE_FLTR_PTYPE_NONF_NONE;
> + flow < ICE_FLTR_PTYPE_MAX; flow++) {
> + struct ice_acl_hw_prof *prof;
> + int err;
> +
> + flow &= ~FLOW_EXT;
> + prof = hw->acl_prof[flow];
> + if (!prof || !prof->seg)
> + continue;
> +
> + err = ice_flow_rem_prof(hw, ICE_BLK_ACL, prof->prof_id);
> + if (err) {
> + dev_err(ice_hw_to_dev(hw), "Could not remove ACL profile, flow type %d\n",
> + flow);
> + continue;
> + }
> +
> + kfree(prof->seg);
> + prof->seg = NULL;
> + }
> +}
> +
> /**
> * ice_fdir_release_flows - release all flows in use for later replay
> * @hw: pointer to HW instance
[Severity: Low]
Is masking the loop control variable here intentional? 'flow' is the
iteration variable of the enclosing for loop, and rewriting it in the
body means the loop counter itself can be moved backwards - here
FLOW_EXT (0x80000000, an ethtool uapi flow_type bit) is never set in the
enum ice_fltr_ptype values being iterated, which are bounded by
ICE_FLTR_PTYPE_MAX, so today the mask is a no-op and the loop still
terminates. It is still a fragile pattern: if the enum ever grows or
the bound changes, a body that clears a bit of the counter turns into a
non-terminating loop, and it obscures what the loop is actually walking.
ice_fdir_rem_flow() does the same masking but into a separate local:
int flow = (int)flow_type & ~FLOW_EXT;
Could the mask simply be dropped here, or applied to a separate local
used for the hw->acl_prof[] index so the loop counter is never touched?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v2 05/12] ice: create flow profile
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (3 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 04/12] ice: initialize ACL scenario Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-21 19:57 ` netdev-bot+sashiko
2026-09-17 18:39 ` [PATCH net-next v2 06/12] Revert "ice: remove unused ice_flow_entry fields" Tony Nguyen
` (8 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Real Valiquette, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Chinh Cao,
Rinitha S
From: Real Valiquette <real.valiquette@intel.com>
Implement the initial steps for creating an ACL filter to support ntuple
masks. Create a flow profile based on a given mask rule and program it to
the hardware. Though the profile is written to hardware, no actions are
associated with the profile yet.
Co-developed-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Real Valiquette <real.valiquette@intel.com>
Co-developed-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/Makefile | 1 +
drivers/net/ethernet/intel/ice/ice.h | 6 +
drivers/net/ethernet/intel/ice/ice_acl_main.c | 227 +++++++++++++
drivers/net/ethernet/intel/ice/ice_acl_main.h | 9 +
.../net/ethernet/intel/ice/ice_adminq_cmd.h | 39 +++
.../ethernet/intel/ice/ice_ethtool_ntuple.c | 307 +++++++++++++-----
.../net/ethernet/intel/ice/ice_flex_pipe.c | 6 +
drivers/net/ethernet/intel/ice/ice_flow.c | 173 ++++++++++
drivers/net/ethernet/intel/ice/ice_flow.h | 17 +
drivers/net/ethernet/intel/ice/ice_main.c | 42 ++-
10 files changed, 745 insertions(+), 82 deletions(-)
create mode 100644 drivers/net/ethernet/intel/ice/ice_acl_main.c
create mode 100644 drivers/net/ethernet/intel/ice/ice_acl_main.h
diff --git a/drivers/net/ethernet/intel/ice/Makefile b/drivers/net/ethernet/intel/ice/Makefile
index 8f0a91296969..904d22e57d53 100644
--- a/drivers/net/ethernet/intel/ice/Makefile
+++ b/drivers/net/ethernet/intel/ice/Makefile
@@ -25,6 +25,7 @@ ice-y := ice_main.o \
ice_vsi_vlan_lib.o \
ice_fdir.o \
ice_ethtool_ntuple.o \
+ ice_acl_main.o \
ice_acl.o \
ice_acl_ctrl.o \
ice_vlan_mode.o \
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index 1ee4e1aff94a..fe293d51ac8c 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -1027,6 +1027,11 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
int ice_get_ethtool_fdir_entry(struct ice_hw *hw, struct ethtool_rxnfc *cmd);
u32 ice_ntuple_get_max_fltr_cnt(struct ice_hw *hw);
+int ice_ntuple_l4_proto_to_port(enum ice_flow_seg_hdr l4_proto,
+ enum ice_flow_field *src_port,
+ enum ice_flow_field *dst_port);
+int ice_ntuple_check_ip4_seg(struct ethtool_tcpip4_spec *tcp_ip4_spec);
+int ice_ntuple_check_ip4_usr_seg(struct ethtool_usrip4_spec *usr_ip4_spec);
int
ice_get_fdir_fltr_ids(struct ice_hw *hw, struct ethtool_rxnfc *cmd,
u32 *rule_locs);
@@ -1036,6 +1041,7 @@ void ice_fdir_release_flows(struct ice_hw *hw);
void ice_fdir_replay_flows(struct ice_hw *hw);
void ice_fdir_replay_fltrs(struct ice_pf *pf);
int ice_fdir_create_dflt_rules(struct ice_pf *pf);
+enum ice_fltr_ptype ice_ethtool_flow_to_fltr(int eth);
enum ice_aq_task_state {
ICE_AQ_TASK_NOT_PREPARED,
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.c b/drivers/net/ethernet/intel/ice/ice_acl_main.c
new file mode 100644
index 000000000000..db8137e6f870
--- /dev/null
+++ b/drivers/net/ethernet/intel/ice/ice_acl_main.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2018-2026, Intel Corporation. */
+
+#include "ice.h"
+#include "ice_lib.h"
+#include "ice_acl_main.h"
+
+/* Number of action */
+#define ICE_ACL_NUM_ACT 1
+
+/**
+ * ice_acl_set_ip4_addr_seg - set flow segment IPv4 addresses masks
+ * @seg: flow segment for programming
+ */
+static void ice_acl_set_ip4_addr_seg(struct ice_flow_seg_info *seg)
+{
+ u16 val_loc, mask_loc;
+
+ /* IP source address */
+ val_loc = offsetof(struct ice_ntuple_fltr, ip.v4.src_ip);
+ mask_loc = offsetof(struct ice_ntuple_fltr, mask.v4.src_ip);
+
+ ice_flow_set_fld(seg, ICE_FLOW_FIELD_IDX_IPV4_SA, val_loc,
+ mask_loc, ICE_FLOW_FLD_OFF_INVAL, false);
+
+ /* IP destination address */
+ val_loc = offsetof(struct ice_ntuple_fltr, ip.v4.dst_ip);
+ mask_loc = offsetof(struct ice_ntuple_fltr, mask.v4.dst_ip);
+
+ ice_flow_set_fld(seg, ICE_FLOW_FIELD_IDX_IPV4_DA, val_loc,
+ mask_loc, ICE_FLOW_FLD_OFF_INVAL, false);
+}
+
+/**
+ * ice_acl_set_ip4_port_seg - set flow segment port masks based on L4 port
+ * @seg: flow segment for programming
+ * @l4_proto: Layer 4 protocol to program
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_acl_set_ip4_port_seg(struct ice_flow_seg_info *seg,
+ enum ice_flow_seg_hdr l4_proto)
+{
+ enum ice_flow_field src_port, dst_port;
+ u16 val_loc, mask_loc;
+ int err;
+
+ err = ice_ntuple_l4_proto_to_port(l4_proto, &src_port, &dst_port);
+ if (err)
+ return err;
+
+ /* Layer 4 source port */
+ val_loc = offsetof(struct ice_ntuple_fltr, ip.v4.src_port);
+ mask_loc = offsetof(struct ice_ntuple_fltr, mask.v4.src_port);
+
+ ice_flow_set_fld(seg, src_port, val_loc, mask_loc,
+ ICE_FLOW_FLD_OFF_INVAL, false);
+
+ /* Layer 4 destination port */
+ val_loc = offsetof(struct ice_ntuple_fltr, ip.v4.dst_port);
+ mask_loc = offsetof(struct ice_ntuple_fltr, mask.v4.dst_port);
+
+ ice_flow_set_fld(seg, dst_port, val_loc, mask_loc,
+ ICE_FLOW_FLD_OFF_INVAL, false);
+
+ return 0;
+}
+
+/**
+ * ice_acl_set_ip4_seg - set flow segment IPv4 and L4 masks
+ * @seg: flow segment for programming
+ * @tcp_ip4_spec: mask data from ethtool
+ * @l4_proto: Layer 4 protocol to program
+ *
+ * Set the mask data into the flow segment to be used to program HW
+ * table based on provided L4 protocol for IPv4
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_acl_set_ip4_seg(struct ice_flow_seg_info *seg,
+ struct ethtool_tcpip4_spec *tcp_ip4_spec,
+ enum ice_flow_seg_hdr l4_proto)
+{
+ int err;
+
+ err = ice_ntuple_check_ip4_seg(tcp_ip4_spec);
+ if (err)
+ return err;
+
+ ICE_FLOW_SET_HDRS(seg, ICE_FLOW_SEG_HDR_IPV4 | l4_proto);
+ ice_acl_set_ip4_addr_seg(seg);
+
+ return ice_acl_set_ip4_port_seg(seg, l4_proto);
+}
+
+/**
+ * ice_acl_set_ip4_usr_seg - set flow segment IPv4 masks
+ * @seg: flow segment for programming
+ * @usr_ip4_spec: ethtool userdef packet offset
+ *
+ * Set the offset data into the flow segment to be used to program HW
+ * table for IPv4
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_acl_set_ip4_usr_seg(struct ice_flow_seg_info *seg,
+ struct ethtool_usrip4_spec *usr_ip4_spec)
+{
+ int err;
+
+ err = ice_ntuple_check_ip4_usr_seg(usr_ip4_spec);
+ if (err)
+ return err;
+
+ ICE_FLOW_SET_HDRS(seg, ICE_FLOW_SEG_HDR_IPV4);
+ ice_acl_set_ip4_addr_seg(seg);
+
+ return 0;
+}
+
+/**
+ * ice_acl_prof_add_ethtool - Check ethtool input set and add ACL profile
+ * @pf: ice PF structure
+ * @fsp: pointer to ethtool Rx flow specification
+ *
+ * Return: 0 on success and negative values for failure
+ */
+static int ice_acl_prof_add_ethtool(struct ice_pf *pf,
+ struct ethtool_rx_flow_spec *fsp)
+{
+ struct ice_flow_prof *prof = NULL;
+ struct ice_acl_hw_prof *hw_prof;
+ struct ice_flow_seg_info *seg;
+ enum ice_fltr_ptype fltr_type;
+ struct ice_hw *hw = &pf->hw;
+ int err;
+
+ seg = kzalloc_obj(*seg);
+ if (!seg)
+ return -ENOMEM;
+
+ switch (fsp->flow_type & ~FLOW_EXT) {
+ case TCP_V4_FLOW:
+ err = ice_acl_set_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
+ ICE_FLOW_SEG_HDR_TCP);
+ break;
+ case UDP_V4_FLOW:
+ err = ice_acl_set_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
+ ICE_FLOW_SEG_HDR_UDP);
+ break;
+ case SCTP_V4_FLOW:
+ err = ice_acl_set_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
+ ICE_FLOW_SEG_HDR_SCTP);
+ break;
+ case IPV4_USER_FLOW:
+ err = ice_acl_set_ip4_usr_seg(seg, &fsp->m_u.usr_ip4_spec);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ }
+ if (err)
+ goto free_seg;
+
+ fltr_type = ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT);
+
+ hw_prof = hw->acl_prof[fltr_type];
+ if (!hw_prof) {
+ hw_prof = kzalloc_obj(**hw->acl_prof);
+ if (!hw_prof) {
+ err = -ENOMEM;
+ goto free_seg;
+ }
+ }
+
+ if (hw_prof->seg) {
+ /* This flow_type already has an input set.
+ * If it matches the requested input set then we are
+ * done. If it's different then it's an error.
+ */
+ if (!memcmp(hw_prof->seg, seg, sizeof(*seg))) {
+ kfree(seg);
+ return 0;
+ }
+
+ err = -EINVAL;
+ goto free_seg;
+ }
+
+ /* Adding a profile for the given flow specification with no
+ * actions (NULL) and zero actions 0.
+ */
+ err = ice_flow_add_prof(hw, ICE_BLK_ACL, ICE_FLOW_RX, seg, 1, false,
+ &prof);
+ if (err)
+ goto free_acl_prof;
+
+ hw_prof->seg = seg;
+ hw_prof->prof_id = prof->id;
+ hw->acl_prof[fltr_type] = hw_prof;
+ return 0;
+
+free_acl_prof:
+ kfree(hw_prof);
+free_seg:
+ kfree(seg);
+
+ return err;
+}
+
+/**
+ * ice_acl_add_rule_ethtool - add an ACL rule
+ * @vsi: pointer to target VSI
+ * @cmd: command to add or delete ACL rule
+ *
+ * Return: 0 on success and negative values for failure
+ */
+int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
+{
+ struct ethtool_rx_flow_spec *fsp;
+ struct ice_pf *pf;
+
+ pf = vsi->back;
+
+ fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
+
+ return ice_acl_prof_add_ethtool(pf, fsp);
+}
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.h b/drivers/net/ethernet/intel/ice/ice_acl_main.h
new file mode 100644
index 000000000000..6665af2e7053
--- /dev/null
+++ b/drivers/net/ethernet/intel/ice/ice_acl_main.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026, Intel Corporation. */
+
+#ifndef _ICE_ACL_MAIN_H_
+#define _ICE_ACL_MAIN_H_
+#include "ice.h"
+#include <linux/ethtool.h>
+int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
+#endif /* _ICE_ACL_MAIN_H_ */
diff --git a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
index d28594eda14f..43b2a67aa207 100644
--- a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
+++ b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
@@ -172,6 +172,8 @@ struct ice_aqc_set_port_params {
#define ICE_AQC_RES_TYPE_FDIR_COUNTER_BLOCK 0x21
#define ICE_AQC_RES_TYPE_FDIR_GUARANTEED_ENTRIES 0x22
#define ICE_AQC_RES_TYPE_FDIR_SHARED_ENTRIES 0x23
+#define ICE_AQC_RES_TYPE_ACL_PROF_BLDR_PROFID 0x50
+#define ICE_AQC_RES_TYPE_ACL_PROF_BLDR_TCAM 0x51
#define ICE_AQC_RES_TYPE_FD_PROF_BLDR_PROFID 0x58
#define ICE_AQC_RES_TYPE_FD_PROF_BLDR_TCAM 0x59
#define ICE_AQC_RES_TYPE_HASH_PROF_BLDR_PROFID 0x60
@@ -2178,6 +2180,43 @@ struct ice_aqc_actpair {
struct ice_acl_act_entry act[ICE_ACL_NUM_ACT_PER_ACT_PAIR];
};
+/* The first byte of the byte selection base is reserved to keep the
+ * first byte of the field vector where the packet direction info is
+ * available. Thus we should start at index 1 of the field vector to
+ * map its entries to the byte selection base.
+ */
+#define ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX 1
+#define ICE_AQC_ACL_PROF_BYTE_SEL_ELEMS 30
+
+/* Input buffer format for program profile extraction admin command and
+ * response buffer format for query profile admin command is as defined
+ * in struct ice_aqc_acl_prof_generic_frmt
+ */
+
+/* Input buffer format for program profile ranges and query profile ranges
+ * admin commands. Same format is used for response buffer in case of query
+ * profile ranges command
+ */
+struct ice_acl_rng_data {
+ /* The range checker output shall be sent when the value
+ * related to this range checker is lower than low boundary
+ */
+ __be16 low_boundary;
+ /* The range checker output shall be sent when the value
+ * related to this range checker is higher than high boundary
+ */
+ __be16 high_boundary;
+ /* A value of '0' in bit shall clear the relevant bit input
+ * to the range checker
+ */
+ __be16 mask;
+};
+
+#define ICE_AQC_ACL_PROF_RANGES_NUM_CFG 8
+struct ice_aqc_acl_profile_ranges {
+ struct ice_acl_rng_data checker_cfg[ICE_AQC_ACL_PROF_RANGES_NUM_CFG];
+};
+
/* Program ACL entry (indirect 0x0C20) */
struct ice_aqc_acl_entry {
u8 tcam_index;
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
index 272b3d54f6e4..afeda2110940 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
@@ -7,6 +7,7 @@
#include "ice_lib.h"
#include "ice_fdir.h"
#include "ice_flow.h"
+#include "ice_acl_main.h"
static struct in6_addr full_ipv6_addr_mask = {
.in6_u = {
@@ -26,6 +27,9 @@ static struct in6_addr zero_ipv6_addr_mask = {
}
};
+#define ICE_FULL_IPV4_ADDR_MASK 0xFFFFFFFF
+#define ICE_FULL_PORT_MASK 0xFFFF
+
/* calls to ice_flow_add_prof require the number of segments in the array
* for segs_cnt. In this code that is one more than the index.
*/
@@ -71,7 +75,7 @@ static int ice_fltr_to_ethtool_flow(enum ice_fltr_ptype flow)
*
* Returns flow enum
*/
-static enum ice_fltr_ptype ice_ethtool_flow_to_fltr(int eth)
+enum ice_fltr_ptype ice_ethtool_flow_to_fltr(int eth)
{
switch (eth) {
case ETHER_FLOW:
@@ -960,23 +964,13 @@ ice_create_init_fdir_rule(struct ice_pf *pf, enum ice_fltr_ptype flow)
}
/**
- * ice_set_fdir_ip4_seg
- * @seg: flow segment for programming
+ * ice_ntuple_check_ip4_seg - Check valid fields are provided for filter
* @tcp_ip4_spec: mask data from ethtool
- * @l4_proto: Layer 4 protocol to program
- * @perfect_fltr: only valid on success; returns true if perfect filter,
- * false if not
*
- * Set the mask data into the flow segment to be used to program HW
- * table based on provided L4 protocol for IPv4
+ * Return: 0 if fields valid, negative otherwise
*/
-static int
-ice_set_fdir_ip4_seg(struct ice_flow_seg_info *seg,
- struct ethtool_tcpip4_spec *tcp_ip4_spec,
- enum ice_flow_seg_hdr l4_proto, bool *perfect_fltr)
+int ice_ntuple_check_ip4_seg(struct ethtool_tcpip4_spec *tcp_ip4_spec)
{
- enum ice_flow_field src_port, dst_port;
-
/* make sure we don't have any empty rule */
if (!tcp_ip4_spec->psrc && !tcp_ip4_spec->ip4src &&
!tcp_ip4_spec->pdst && !tcp_ip4_spec->ip4dst)
@@ -986,24 +980,71 @@ ice_set_fdir_ip4_seg(struct ice_flow_seg_info *seg,
if (tcp_ip4_spec->tos)
return -EOPNOTSUPP;
+ return 0;
+}
+
+/**
+ * ice_ntuple_l4_proto_to_port - set src and dst port for given L4 protocol
+ * @l4_proto: Layer 4 protocol to program
+ * @src_port: source flow field value for provided l4 protocol
+ * @dst_port: destination flow field value for provided l4 protocol
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_ntuple_l4_proto_to_port(enum ice_flow_seg_hdr l4_proto,
+ enum ice_flow_field *src_port,
+ enum ice_flow_field *dst_port)
+{
if (l4_proto == ICE_FLOW_SEG_HDR_TCP) {
- src_port = ICE_FLOW_FIELD_IDX_TCP_SRC_PORT;
- dst_port = ICE_FLOW_FIELD_IDX_TCP_DST_PORT;
+ *src_port = ICE_FLOW_FIELD_IDX_TCP_SRC_PORT;
+ *dst_port = ICE_FLOW_FIELD_IDX_TCP_DST_PORT;
} else if (l4_proto == ICE_FLOW_SEG_HDR_UDP) {
- src_port = ICE_FLOW_FIELD_IDX_UDP_SRC_PORT;
- dst_port = ICE_FLOW_FIELD_IDX_UDP_DST_PORT;
+ *src_port = ICE_FLOW_FIELD_IDX_UDP_SRC_PORT;
+ *dst_port = ICE_FLOW_FIELD_IDX_UDP_DST_PORT;
} else if (l4_proto == ICE_FLOW_SEG_HDR_SCTP) {
- src_port = ICE_FLOW_FIELD_IDX_SCTP_SRC_PORT;
- dst_port = ICE_FLOW_FIELD_IDX_SCTP_DST_PORT;
+ *src_port = ICE_FLOW_FIELD_IDX_SCTP_SRC_PORT;
+ *dst_port = ICE_FLOW_FIELD_IDX_SCTP_DST_PORT;
} else {
return -EOPNOTSUPP;
}
+ return 0;
+}
+
+/**
+ * ice_set_fdir_ip4_seg - setup flow segment based on IPv4 and L4 proto
+ * @seg: flow segment for programming
+ * @tcp_ip4_spec: mask data from ethtool
+ * @l4_proto: Layer 4 protocol to program
+ * @perfect_fltr: only valid on success; returns true if perfect filter,
+ * false if not
+ *
+ * Set the mask data into the flow segment to be used to program HW
+ * table based on provided L4 protocol for IPv4
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_set_fdir_ip4_seg(struct ice_flow_seg_info *seg,
+ struct ethtool_tcpip4_spec *tcp_ip4_spec,
+ enum ice_flow_seg_hdr l4_proto,
+ bool *perfect_fltr)
+{
+ enum ice_flow_field src_port, dst_port;
+ int err;
+
+ err = ice_ntuple_check_ip4_seg(tcp_ip4_spec);
+ if (err)
+ return err;
+
+ err = ice_ntuple_l4_proto_to_port(l4_proto, &src_port, &dst_port);
+ if (err)
+ return err;
+
*perfect_fltr = true;
ICE_FLOW_SET_HDRS(seg, ICE_FLOW_SEG_HDR_IPV4 | l4_proto);
/* IP source address */
- if (tcp_ip4_spec->ip4src == htonl(0xFFFFFFFF))
+ if (tcp_ip4_spec->ip4src == htonl(ICE_FULL_IPV4_ADDR_MASK))
ice_flow_set_fld(seg, ICE_FLOW_FIELD_IDX_IPV4_SA,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, false);
@@ -1013,7 +1054,7 @@ ice_set_fdir_ip4_seg(struct ice_flow_seg_info *seg,
return -EOPNOTSUPP;
/* IP destination address */
- if (tcp_ip4_spec->ip4dst == htonl(0xFFFFFFFF))
+ if (tcp_ip4_spec->ip4dst == htonl(ICE_FULL_IPV4_ADDR_MASK))
ice_flow_set_fld(seg, ICE_FLOW_FIELD_IDX_IPV4_DA,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, false);
@@ -1023,7 +1064,7 @@ ice_set_fdir_ip4_seg(struct ice_flow_seg_info *seg,
return -EOPNOTSUPP;
/* Layer 4 source port */
- if (tcp_ip4_spec->psrc == htons(0xFFFF))
+ if (tcp_ip4_spec->psrc == htons(ICE_FULL_PORT_MASK))
ice_flow_set_fld(seg, src_port, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
false);
@@ -1033,7 +1074,7 @@ ice_set_fdir_ip4_seg(struct ice_flow_seg_info *seg,
return -EOPNOTSUPP;
/* Layer 4 destination port */
- if (tcp_ip4_spec->pdst == htons(0xFFFF))
+ if (tcp_ip4_spec->pdst == htons(ICE_FULL_PORT_MASK))
ice_flow_set_fld(seg, dst_port, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
false);
@@ -1046,19 +1087,12 @@ ice_set_fdir_ip4_seg(struct ice_flow_seg_info *seg,
}
/**
- * ice_set_fdir_ip4_usr_seg
- * @seg: flow segment for programming
+ * ice_ntuple_check_ip4_usr_seg - Check valid fields are provided for filter
* @usr_ip4_spec: ethtool userdef packet offset
- * @perfect_fltr: only valid on success; returns true if perfect filter,
- * false if not
*
- * Set the offset data into the flow segment to be used to program HW
- * table for IPv4
+ * Return: 0 if fields valid, negative otherwise
*/
-static int
-ice_set_fdir_ip4_usr_seg(struct ice_flow_seg_info *seg,
- struct ethtool_usrip4_spec *usr_ip4_spec,
- bool *perfect_fltr)
+int ice_ntuple_check_ip4_usr_seg(struct ethtool_usrip4_spec *usr_ip4_spec)
{
/* first 4 bytes of Layer 4 header */
if (usr_ip4_spec->l4_4_bytes)
@@ -1074,11 +1108,36 @@ ice_set_fdir_ip4_usr_seg(struct ice_flow_seg_info *seg,
if (!usr_ip4_spec->ip4src && !usr_ip4_spec->ip4dst)
return -EINVAL;
+ return 0;
+}
+
+/**
+ * ice_set_fdir_ip4_usr_seg - setup flow segment based on IPv4
+ * @seg: flow segment for programming
+ * @usr_ip4_spec: ethtool userdef packet offset
+ * @perfect_fltr: only set on success; returns true if perfect filter, false if
+ * not
+ *
+ * Set the offset data into the flow segment to be used to program HW
+ * table for IPv4
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_set_fdir_ip4_usr_seg(struct ice_flow_seg_info *seg,
+ struct ethtool_usrip4_spec *usr_ip4_spec,
+ bool *perfect_fltr)
+{
+ int err;
+
+ err = ice_ntuple_check_ip4_usr_seg(usr_ip4_spec);
+ if (err)
+ return err;
+
*perfect_fltr = true;
ICE_FLOW_SET_HDRS(seg, ICE_FLOW_SEG_HDR_IPV4);
/* IP source address */
- if (usr_ip4_spec->ip4src == htonl(0xFFFFFFFF))
+ if (usr_ip4_spec->ip4src == htonl(ICE_FULL_IPV4_ADDR_MASK))
ice_flow_set_fld(seg, ICE_FLOW_FIELD_IDX_IPV4_SA,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, false);
@@ -1088,7 +1147,7 @@ ice_set_fdir_ip4_usr_seg(struct ice_flow_seg_info *seg,
return -EOPNOTSUPP;
/* IP destination address */
- if (usr_ip4_spec->ip4dst == htonl(0xFFFFFFFF))
+ if (usr_ip4_spec->ip4dst == htonl(ICE_FULL_IPV4_ADDR_MASK))
ice_flow_set_fld(seg, ICE_FLOW_FIELD_IDX_IPV4_DA,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, false);
@@ -1101,23 +1160,13 @@ ice_set_fdir_ip4_usr_seg(struct ice_flow_seg_info *seg,
}
/**
- * ice_set_fdir_ip6_seg
- * @seg: flow segment for programming
+ * ice_ntuple_check_ip6_seg - Check valid fields are provided for filter
* @tcp_ip6_spec: mask data from ethtool
- * @l4_proto: Layer 4 protocol to program
- * @perfect_fltr: only valid on success; returns true if perfect filter,
- * false if not
*
- * Set the mask data into the flow segment to be used to program HW
- * table based on provided L4 protocol for IPv6
+ * Return: 0 if fields valid, negative otherwise
*/
-static int
-ice_set_fdir_ip6_seg(struct ice_flow_seg_info *seg,
- struct ethtool_tcpip6_spec *tcp_ip6_spec,
- enum ice_flow_seg_hdr l4_proto, bool *perfect_fltr)
+static int ice_ntuple_check_ip6_seg(struct ethtool_tcpip6_spec *tcp_ip6_spec)
{
- enum ice_flow_field src_port, dst_port;
-
/* make sure we don't have any empty rule */
if (!memcmp(tcp_ip6_spec->ip6src, &zero_ipv6_addr_mask,
sizeof(struct in6_addr)) &&
@@ -1130,18 +1179,37 @@ ice_set_fdir_ip6_seg(struct ice_flow_seg_info *seg,
if (tcp_ip6_spec->tclass)
return -EOPNOTSUPP;
- if (l4_proto == ICE_FLOW_SEG_HDR_TCP) {
- src_port = ICE_FLOW_FIELD_IDX_TCP_SRC_PORT;
- dst_port = ICE_FLOW_FIELD_IDX_TCP_DST_PORT;
- } else if (l4_proto == ICE_FLOW_SEG_HDR_UDP) {
- src_port = ICE_FLOW_FIELD_IDX_UDP_SRC_PORT;
- dst_port = ICE_FLOW_FIELD_IDX_UDP_DST_PORT;
- } else if (l4_proto == ICE_FLOW_SEG_HDR_SCTP) {
- src_port = ICE_FLOW_FIELD_IDX_SCTP_SRC_PORT;
- dst_port = ICE_FLOW_FIELD_IDX_SCTP_DST_PORT;
- } else {
- return -EINVAL;
- }
+ return 0;
+}
+
+/**
+ * ice_set_fdir_ip6_seg - setup flow segment based on IPv6 and L4 proto
+ * @seg: flow segment for programming
+ * @tcp_ip6_spec: mask data from ethtool
+ * @l4_proto: Layer 4 protocol to program
+ * @perfect_fltr: only valid on success; returns true if perfect filter,
+ * false if not
+ *
+ * Set the mask data into the flow segment to be used to program HW
+ * table based on provided L4 protocol for IPv6
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_set_fdir_ip6_seg(struct ice_flow_seg_info *seg,
+ struct ethtool_tcpip6_spec *tcp_ip6_spec,
+ enum ice_flow_seg_hdr l4_proto,
+ bool *perfect_fltr)
+{
+ enum ice_flow_field src_port, dst_port;
+ int err;
+
+ err = ice_ntuple_check_ip6_seg(tcp_ip6_spec);
+ if (err)
+ return err;
+
+ err = ice_ntuple_l4_proto_to_port(l4_proto, &src_port, &dst_port);
+ if (err)
+ return err;
*perfect_fltr = true;
ICE_FLOW_SET_HDRS(seg, ICE_FLOW_SEG_HDR_IPV6 | l4_proto);
@@ -1169,7 +1237,7 @@ ice_set_fdir_ip6_seg(struct ice_flow_seg_info *seg,
return -EOPNOTSUPP;
/* Layer 4 source port */
- if (tcp_ip6_spec->psrc == htons(0xFFFF))
+ if (tcp_ip6_spec->psrc == htons(ICE_FULL_PORT_MASK))
ice_flow_set_fld(seg, src_port, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
false);
@@ -1179,7 +1247,7 @@ ice_set_fdir_ip6_seg(struct ice_flow_seg_info *seg,
return -EOPNOTSUPP;
/* Layer 4 destination port */
- if (tcp_ip6_spec->pdst == htons(0xFFFF))
+ if (tcp_ip6_spec->pdst == htons(ICE_FULL_PORT_MASK))
ice_flow_set_fld(seg, dst_port, ICE_FLOW_FLD_OFF_INVAL,
ICE_FLOW_FLD_OFF_INVAL, ICE_FLOW_FLD_OFF_INVAL,
false);
@@ -1192,19 +1260,13 @@ ice_set_fdir_ip6_seg(struct ice_flow_seg_info *seg,
}
/**
- * ice_set_fdir_ip6_usr_seg
- * @seg: flow segment for programming
+ * ice_ntuple_check_ip6_usr_seg - Check valid fields are provided for filter
* @usr_ip6_spec: ethtool userdef packet offset
- * @perfect_fltr: only valid on success; returns true if perfect filter,
- * false if not
*
- * Set the offset data into the flow segment to be used to program HW
- * table for IPv6
+ * Return: 0 if fields valid, negative otherwise
*/
static int
-ice_set_fdir_ip6_usr_seg(struct ice_flow_seg_info *seg,
- struct ethtool_usrip6_spec *usr_ip6_spec,
- bool *perfect_fltr)
+ice_ntuple_check_ip6_usr_seg(struct ethtool_usrip6_spec *usr_ip6_spec)
{
/* filtering on Layer 4 bytes not supported */
if (usr_ip6_spec->l4_4_bytes)
@@ -1222,6 +1284,31 @@ ice_set_fdir_ip6_usr_seg(struct ice_flow_seg_info *seg,
sizeof(struct in6_addr)))
return -EINVAL;
+ return 0;
+}
+
+/**
+ * ice_set_fdir_ip6_usr_seg - setup flow segment based on IPv6
+ * @seg: flow segment for programming
+ * @usr_ip6_spec: ethtool userdef packet offset
+ * @perfect_fltr: only set on success; returns true if perfect filter, false if
+ * not
+ *
+ * Set the offset data into the flow segment to be used to program HW
+ * table for IPv6
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_set_fdir_ip6_usr_seg(struct ice_flow_seg_info *seg,
+ struct ethtool_usrip6_spec *usr_ip6_spec,
+ bool *perfect_fltr)
+{
+ int err;
+
+ err = ice_ntuple_check_ip6_usr_seg(usr_ip6_spec);
+ if (err)
+ return err;
+
*perfect_fltr = true;
ICE_FLOW_SET_HDRS(seg, ICE_FLOW_SEG_HDR_IPV6);
@@ -1846,7 +1933,7 @@ int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
struct ice_hw *hw = &pf->hw;
int val;
- if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
+ if (!test_bit(ICE_FLAG_FD_ENA, pf->flags) && !pf->hw.acl_tbl)
return -EOPNOTSUPP;
/* Do not delete filters during reset */
@@ -1862,6 +1949,60 @@ int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
return val;
}
+/**
+ * ice_is_acl_filter - Check if it's a FD or ACL filter
+ * @fsp: pointer to ethtool Rx flow specification
+ *
+ * If any field of the provided filter is using a partial mask then this is
+ * an ACL filter.
+ *
+ * Return: true if ACL filter, false otherwise
+ */
+static bool ice_is_acl_filter(struct ethtool_rx_flow_spec *fsp)
+{
+ struct ethtool_tcpip4_spec *tcp_ip4_spec;
+ struct ethtool_usrip4_spec *usr_ip4_spec;
+
+ switch (fsp->flow_type & ~FLOW_EXT) {
+ case TCP_V4_FLOW:
+ case UDP_V4_FLOW:
+ case SCTP_V4_FLOW:
+ tcp_ip4_spec = &fsp->m_u.tcp_ip4_spec;
+
+ if (tcp_ip4_spec->ip4src &&
+ tcp_ip4_spec->ip4src != htonl(ICE_FULL_IPV4_ADDR_MASK))
+ return true;
+
+ if (tcp_ip4_spec->ip4dst &&
+ tcp_ip4_spec->ip4dst != htonl(ICE_FULL_IPV4_ADDR_MASK))
+ return true;
+
+ if (tcp_ip4_spec->psrc &&
+ tcp_ip4_spec->psrc != htons(ICE_FULL_PORT_MASK))
+ return true;
+
+ if (tcp_ip4_spec->pdst &&
+ tcp_ip4_spec->pdst != htons(ICE_FULL_PORT_MASK))
+ return true;
+
+ break;
+ case IPV4_USER_FLOW:
+ usr_ip4_spec = &fsp->m_u.usr_ip4_spec;
+
+ if (usr_ip4_spec->ip4src &&
+ usr_ip4_spec->ip4src != htonl(ICE_FULL_IPV4_ADDR_MASK))
+ return true;
+
+ if (usr_ip4_spec->ip4dst &&
+ usr_ip4_spec->ip4dst != htonl(ICE_FULL_IPV4_ADDR_MASK))
+ return true;
+
+ break;
+ }
+
+ return false;
+}
+
/**
* ice_update_ring_dest_vsi - update dest ring and dest VSI
* @vsi: pointer to target VSI
@@ -2074,12 +2215,12 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
hw = &pf->hw;
dev = ice_pf_to_dev(pf);
- if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
+ if (!test_bit(ICE_FLAG_FD_ENA, pf->flags) && !pf->hw.acl_tbl)
return -EOPNOTSUPP;
/* Do not program filters during reset */
if (ice_is_reset_in_progress(pf->state)) {
- dev_err(dev, "Device is resetting - adding Flow Director filters not supported during reset\n");
+ dev_err(dev, "Device is resetting - adding ntuple filters not supported during reset\n");
return -EBUSY;
}
@@ -2091,10 +2232,6 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
if (fsp->flow_type & FLOW_MAC_EXT)
return -EINVAL;
- ret = ice_cfg_fdir_xtrct_seq(pf, fsp, &userdata);
- if (ret)
- return ret;
-
max_location = ice_ntuple_get_max_fltr_cnt(hw);
if (fsp->location >= max_location) {
dev_err(dev, "Failed to add filter. The number of ntuple filters or provided location exceed max %d.\n",
@@ -2102,6 +2239,18 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
return -ENOSPC;
}
+ /* ACL filter */
+ if (pf->hw.acl_tbl && ice_is_acl_filter(fsp))
+ return ice_acl_add_rule_ethtool(vsi, cmd);
+
+ /* Only fdir filters below */
+ if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
+ return -EOPNOTSUPP;
+
+ ret = ice_cfg_fdir_xtrct_seq(pf, fsp, &userdata);
+ if (ret)
+ return ret;
+
/* return error if not an update and no available filters */
fltrs_needed = ice_get_open_tunnel_port(hw, &tunnel_port, TNL_ALL) ? 2 : 1;
if (!ice_fdir_find_fltr_by_idx(hw, fsp->location) &&
diff --git a/drivers/net/ethernet/intel/ice/ice_flex_pipe.c b/drivers/net/ethernet/intel/ice/ice_flex_pipe.c
index bb1d12f952cf..d255ffcd5c86 100644
--- a/drivers/net/ethernet/intel/ice/ice_flex_pipe.c
+++ b/drivers/net/ethernet/intel/ice/ice_flex_pipe.c
@@ -1259,6 +1259,9 @@ ice_find_prof_id_with_mask(struct ice_hw *hw, enum ice_block blk,
static bool ice_prof_id_rsrc_type(enum ice_block blk, u16 *rsrc_type)
{
switch (blk) {
+ case ICE_BLK_ACL:
+ *rsrc_type = ICE_AQC_RES_TYPE_ACL_PROF_BLDR_PROFID;
+ break;
case ICE_BLK_FD:
*rsrc_type = ICE_AQC_RES_TYPE_FD_PROF_BLDR_PROFID;
break;
@@ -1279,6 +1282,9 @@ static bool ice_prof_id_rsrc_type(enum ice_block blk, u16 *rsrc_type)
static bool ice_tcam_ent_rsrc_type(enum ice_block blk, u16 *rsrc_type)
{
switch (blk) {
+ case ICE_BLK_ACL:
+ *rsrc_type = ICE_AQC_RES_TYPE_ACL_PROF_BLDR_TCAM;
+ break;
case ICE_BLK_FD:
*rsrc_type = ICE_AQC_RES_TYPE_FD_PROF_BLDR_TCAM;
break;
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.c b/drivers/net/ethernet/intel/ice/ice_flow.c
index 121552c644cd..855d296aeed8 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.c
+++ b/drivers/net/ethernet/intel/ice/ice_flow.c
@@ -3,6 +3,7 @@
#include "ice_common.h"
#include "ice_flow.h"
+#include "ice_acl.h"
#include <net/gre.h>
/* Size of known protocol header fields */
@@ -989,6 +990,43 @@ static int ice_flow_proc_seg_hdrs(struct ice_flow_prof_params *params)
return 0;
}
+/**
+ * ice_flow_xtract_pkt_flags - Create an extr sequence entry for packet flags
+ * @hw: pointer to the HW struct
+ * @params: information about the flow to be processed
+ * @flags: The value of pkt_flags[x:x] in Rx/Tx MDID metadata.
+ *
+ * Allocate an extraction sequence entries for a DWORD size chunk of the packet
+ * flags.
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_flow_xtract_pkt_flags(struct ice_hw *hw,
+ struct ice_flow_prof_params *params,
+ enum ice_flex_mdid_pkt_flags flags)
+{
+ u8 fv_words = hw->blk[params->blk].es.fvw;
+ u8 idx;
+
+ /* Make sure the number of extraction sequence entries required does not
+ * exceed the block's capacity.
+ */
+ if (params->es_cnt >= fv_words)
+ return -ENOSPC;
+
+ /* some blocks require a reversed field vector layout */
+ if (hw->blk[params->blk].es.reverse)
+ idx = fv_words - params->es_cnt - 1;
+ else
+ idx = params->es_cnt;
+
+ params->es[idx].prot_id = ICE_PROT_META_ID;
+ params->es[idx].off = flags;
+ params->es_cnt++;
+
+ return 0;
+}
+
/**
* ice_flow_xtract_fld - Create an extraction sequence entry for the given field
* @hw: pointer to the HW struct
@@ -1287,6 +1325,16 @@ ice_flow_create_xtrct_seq(struct ice_hw *hw,
int status = 0;
u8 i;
+ /* For ACL, we also need to extract the direction bit (Rx,Tx) data from
+ * packet flags
+ */
+ if (params->blk == ICE_BLK_ACL) {
+ status = ice_flow_xtract_pkt_flags(hw, params,
+ ICE_RX_MDID_PKT_FLAGS_15_0);
+ if (status)
+ return status;
+ }
+
for (i = 0; i < prof->segs_cnt; i++) {
u64 match = params->prof->segs[i].match;
enum ice_flow_field j;
@@ -1308,6 +1356,123 @@ ice_flow_create_xtrct_seq(struct ice_hw *hw,
return status;
}
+/**
+ * ice_flow_sel_acl_scen - return specific scenario
+ * @hw: pointer to the hardware structure
+ * @params: information about the flow to be processed
+ *
+ * Return (through @params) the specific scenario based on params.
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_flow_sel_acl_scen(struct ice_hw *hw,
+ struct ice_flow_prof_params *params)
+{
+ /* Find the best-fit scenario for the provided match width */
+ struct ice_acl_scen *cand_scen = NULL, *scen;
+
+ if (!hw->acl_tbl)
+ return -ENOENT;
+
+ /* Loop through each scenario and match against the scenario width
+ * to select the specific scenario
+ */
+ list_for_each_entry(scen, &hw->acl_tbl->scens, list_entry)
+ if (scen->eff_width >= params->entry_length &&
+ (!cand_scen || cand_scen->eff_width > scen->eff_width))
+ cand_scen = scen;
+ if (!cand_scen)
+ return -ENOENT;
+
+ params->prof->cfg.scen = cand_scen;
+
+ return 0;
+}
+
+/**
+ * ice_flow_acl_def_entry_frmt - Determine the layout of flow entries
+ * @params: information about the flow to be processed
+ *
+ * Return: 0 on success, negative on error
+ */
+static int
+ice_flow_acl_def_entry_frmt(struct ice_flow_prof_params *params)
+{
+ u16 index, range_idx = 0;
+
+ index = ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX;
+
+ for (int i = 0; i < params->prof->segs_cnt; i++) {
+ struct ice_flow_seg_info *seg = ¶ms->prof->segs[i];
+ unsigned long match = seg->match;
+ int j;
+
+ for_each_set_bit(j, &match, ICE_FLOW_FIELD_IDX_MAX) {
+ struct ice_flow_fld_info *fld = &seg->fields[j];
+
+ fld->entry.mask = ICE_FLOW_FLD_OFF_INVAL;
+
+ if (fld->type == ICE_FLOW_FLD_TYPE_RANGE) {
+ fld->entry.last = ICE_FLOW_FLD_OFF_INVAL;
+
+ /* Range checking only supported for single
+ * words
+ */
+ if (DIV_ROUND_UP(ice_flds_info[j].size +
+ fld->xtrct.disp,
+ BITS_PER_BYTE * 2) > 1)
+ return -EINVAL;
+
+ /* Ranges must define low and high values */
+ if (fld->src.val == ICE_FLOW_FLD_OFF_INVAL ||
+ fld->src.last == ICE_FLOW_FLD_OFF_INVAL)
+ return -EINVAL;
+
+ fld->entry.val = range_idx++;
+ } else {
+ /* Store adjusted byte-length of field for later
+ * use, taking into account potential
+ * non-byte-aligned displacement
+ */
+ fld->entry.last =
+ DIV_ROUND_UP(ice_flds_info[j].size +
+ (fld->xtrct.disp %
+ BITS_PER_BYTE),
+ BITS_PER_BYTE);
+ fld->entry.val = index;
+ index += fld->entry.last;
+ }
+ }
+
+ for (j = 0; j < seg->raws_cnt; j++) {
+ struct ice_flow_seg_fld_raw *raw = &seg->raws[j];
+
+ raw->info.entry.mask = ICE_FLOW_FLD_OFF_INVAL;
+ raw->info.entry.val = index;
+ raw->info.entry.last = raw->info.src.last;
+ index += raw->info.entry.last;
+ }
+ }
+
+ /* Currently only support using the byte selection base, which only
+ * allows for an effective entry size of 30 bytes. Reject anything
+ * larger.
+ */
+ if (index > ICE_AQC_ACL_PROF_BYTE_SEL_ELEMS)
+ return -EINVAL;
+
+ /* Only 8 range checkers per profile, reject anything trying to use
+ * more
+ */
+ if (range_idx > ICE_AQC_ACL_PROF_RANGES_NUM_CFG)
+ return -EINVAL;
+
+ /* Store # bytes required for entry for later use */
+ params->entry_length = index - ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX;
+
+ return 0;
+}
+
/**
* ice_flow_proc_segs - process all packet segments associated with a profile
* @hw: pointer to the HW struct
@@ -1331,6 +1496,14 @@ ice_flow_proc_segs(struct ice_hw *hw, struct ice_flow_prof_params *params)
case ICE_BLK_RSS:
status = 0;
break;
+ case ICE_BLK_ACL:
+ status = ice_flow_acl_def_entry_frmt(params);
+ if (status)
+ return status;
+ status = ice_flow_sel_acl_scen(hw, params);
+ if (status)
+ return status;
+ break;
default:
return -EOPNOTSUPP;
}
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.h b/drivers/net/ethernet/intel/ice/ice_flow.h
index b9b42592b84a..7357088091bd 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.h
+++ b/drivers/net/ethernet/intel/ice/ice_flow.h
@@ -504,6 +504,23 @@ struct ice_rss_cfg {
struct ice_rss_hash_cfg hash;
};
+enum ice_flow_action_type {
+ ICE_FLOW_ACT_NOP,
+ ICE_FLOW_ACT_DROP,
+ ICE_FLOW_ACT_CNTR_PKT,
+ ICE_FLOW_ACT_FWD_QUEUE,
+ ICE_FLOW_ACT_CNTR_BYTES,
+ ICE_FLOW_ACT_CNTR_PKT_BYTES,
+};
+
+struct ice_flow_action {
+ enum ice_flow_action_type type;
+ union {
+ struct ice_acl_act_entry acl_act;
+ u32 dummy;
+ } data;
+};
+
int
ice_flow_add_prof(struct ice_hw *hw, enum ice_block blk, enum ice_flow_dir dir,
struct ice_flow_seg_info *segs, u8 segs_cnt,
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index 952e6167517f..df58bd72301e 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -4378,14 +4378,33 @@ static int ice_acl_create_hw(struct ice_pf *pf)
}
/**
- * ice_init_acl - initialize the ACL block
+ * ice_init_acl - initialize the ACL block and allocate necessary structs
* @pf: ptr to PF device
*
* Return: 0 on success, negative on error
*/
static int ice_init_acl(struct ice_pf *pf)
{
- return ice_acl_create_hw(pf);
+ struct device *dev = ice_pf_to_dev(pf);
+ struct ice_hw *hw = &pf->hw;
+ int err;
+
+ hw->acl_prof = devm_kcalloc(dev, ICE_FLTR_PTYPE_MAX,
+ sizeof(*hw->acl_prof), GFP_KERNEL);
+ if (!hw->acl_prof)
+ return -ENOMEM;
+
+ err = ice_acl_create_hw(pf);
+ if (err)
+ goto free_acl_prof;
+
+ return 0;
+
+free_acl_prof:
+ devm_kfree(dev, hw->acl_prof);
+ hw->acl_prof = NULL;
+
+ return err;
}
/**
@@ -4394,10 +4413,27 @@ static int ice_init_acl(struct ice_pf *pf)
*/
static void ice_deinit_acl(struct ice_pf *pf)
{
+ struct device *dev = ice_pf_to_dev(pf);
struct ice_hw *hw = &pf->hw;
ice_acl_rem_flows(hw);
- ice_acl_destroy_tbl(&pf->hw);
+ ice_acl_destroy_tbl(hw);
+
+ if (!hw->acl_prof)
+ return;
+
+ for (int i = 0; i < ICE_FLTR_PTYPE_MAX; i++) {
+ struct ice_acl_hw_prof *hw_prof = hw->acl_prof[i];
+
+ if (!hw_prof)
+ continue;
+
+ kfree(hw_prof->seg);
+ kfree(hw_prof);
+ }
+
+ devm_kfree(dev, hw->acl_prof);
+ hw->acl_prof = NULL;
}
/**
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 05/12] ice: create flow profile
2026-09-17 18:39 ` [PATCH net-next v2 05/12] ice: create flow profile Tony Nguyen
@ 2026-09-21 19:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:57 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
lukasz.czapnik, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, sx.rinitha
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.
Critical: 0 · High: 2 · Medium: 2 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The new ethtool ACL path publishes and consumes the shared ACL
state hw->acl_prof[] and hw->acl_tbl with no lock, while the teardown…
- [High] (Validated as NOT a defect in the final series state.) Concern:
the ACL profile bookkeeping (hw->acl_prof[fltr_type]->seg/->prof_id)…
- [Medium] ice_flow_acl_def_entry_frmt()
(drivers/net/ethernet/intel/ice/ice_flow.c:1407) copies the 64-bit…
- [Medium] (Validated as NOT a defect in the final series state.)
Concern: at this commit ice_add_ntuple_ethtool() diverts partial-mask…
- [Low] The comment added immediately above the ice_flow_add_prof() call
in ice_acl_prof_add_ethtool()…
- [Low] The kernel-doc added for ice_flow_xtract_pkt_flags()
(drivers/net/ethernet/intel/ice/ice_flow.c:993-1003) misdescribes both…
--
ice: create flow profile
This adds an ACL path to the ethtool ntuple code: partial-mask rules are
diverted to a new ice_acl_main.c which builds an ice_flow_seg_info from the
ethtool mask and creates an ICE_BLK_ACL flow profile. Supporting bits are
added to ice_flow.c (packet-flags extraction, ACL entry format, scenario
selection) and hw->acl_prof is allocated in ice_init_acl().
A few questions below.
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.c b/drivers/net/ethernet/intel/ice/ice_acl_main.c
> new file mode 100644
> index 0000000000000..db8137e6f8706
> --- /dev/null
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_main.c
> @@ -0,0 +1,227 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2018-2026, Intel Corporation. */
> +
> +#include "ice.h"
> +#include "ice_lib.h"
> +#include "ice_acl_main.h"
[ ... ]
> +static int ice_acl_prof_add_ethtool(struct ice_pf *pf,
> + struct ethtool_rx_flow_spec *fsp)
> +{
> + struct ice_flow_prof *prof = NULL;
> + struct ice_acl_hw_prof *hw_prof;
> + struct ice_flow_seg_info *seg;
> + enum ice_fltr_ptype fltr_type;
> + struct ice_hw *hw = &pf->hw;
> + int err;
> +
> + seg = kzalloc_obj(*seg);
> + if (!seg)
> + return -ENOMEM;
[ ... ]
> + fltr_type = ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT);
> +
> + hw_prof = hw->acl_prof[fltr_type];
> + if (!hw_prof) {
> + hw_prof = kzalloc_obj(**hw->acl_prof);
> + if (!hw_prof) {
> + err = -ENOMEM;
> + goto free_seg;
> + }
> + }
> +
> + if (hw_prof->seg) {
> + /* This flow_type already has an input set.
> + * If it matches the requested input set then we are
> + * done. If it's different then it's an error.
> + */
> + if (!memcmp(hw_prof->seg, seg, sizeof(*seg))) {
> + kfree(seg);
> + return 0;
> + }
[Severity: High]
Does this short-circuit survive a PF reset? The bookkeeping in
hw->acl_prof[fltr_type] (seg and prof_id) is not invalidated on reset, but
ice_prepare_for_reset() drops the hardware state for this block:
ice_prepare_for_reset()
ice_clear_hw_tbls(hw); /* clears ICE_BLK_ACL profile map, ES and
profile TCAM shadow tables */
After that, a second identical request would match here and return 0 while
no ACL profile exists in hardware, and a later ice_acl_rem_flows() would
call ice_flow_rem_prof() with a stale profile ID.
For the record, this appears to be addressed later in the same series by
"ice: add ACL reset recovery and NTUPLE feature toggle", which makes
ice_rebuild() do:
ice_acl_destroy_tbl(hw);
if (!ice_acl_create_hw(pf)) {
ice_acl_replay_flows(hw);
ice_acl_replay_fltrs(pf);
}
so the concern is limited to the intermediate state of the series.
> +
> + err = -EINVAL;
> + goto free_seg;
> + }
> +
> + /* Adding a profile for the given flow specification with no
> + * actions (NULL) and zero actions 0.
> + */
[Severity: Low]
This isn't a bug, but does this comment still match the callee?
ice_flow_add_prof() takes no action arguments:
int ice_flow_add_prof(struct ice_hw *hw, enum ice_block blk,
enum ice_flow_dir dir, struct ice_flow_seg_info *segs,
u8 segs_cnt, bool symm, struct ice_flow_prof **prof);
The call below passes segs_cnt = 1 and symm = false, so the trailing false
is the symmetric-hash flag rather than an action count. The text looks
carried over from the older ice_fdir_set_hw_fltr_rule() comment.
> + err = ice_flow_add_prof(hw, ICE_BLK_ACL, ICE_FLOW_RX, seg, 1, false,
> + &prof);
> + if (err)
> + goto free_acl_prof;
> +
> + hw_prof->seg = seg;
> + hw_prof->prof_id = prof->id;
> + hw->acl_prof[fltr_type] = hw_prof;
> + return 0;
[Severity: High]
What serializes this read-check-allocate-publish of hw->acl_prof[] against
ACL teardown? ice_add_ntuple_ethtool() returns into
ice_acl_add_rule_ethtool() before mutex_lock(&hw->fdir_fltr_lock) is ever
taken, and ice_deinit_acl() frees the same state without any lock:
ice_remove()
devl_lock()
ice_unload()
ice_deinit_features()
ice_deinit_acl()
ice_acl_rem_flows(hw); /* kfree(prof->seg) */
ice_acl_destroy_tbl(hw); /* kfree(hw->acl_tbl) */
kfree(hw_prof->seg); kfree(hw_prof);
devm_kfree(dev, hw->acl_prof);
ice_unregister_netdev(vsi); /* only afterwards */
The ethtool path holds rtnl, ice_unload() holds devl_lock, so there is no
common lock and the netdev is still registered while the free happens. Can
an ethtool -N with a partial mask racing device removal or a devlink reload
store hw_prof into the already freed hw->acl_prof array, or walk a freed
hw->acl_tbl->scens list in ice_flow_sel_acl_scen()?
The equivalent FDIR state is protected by hw->fdir_fltr_lock (taken by
ice_vsi_manage_fdir() and ice_vsi_manage_acl()); should the ACL profile
path and ice_deinit_acl() take that mutex too?
> +
> +free_acl_prof:
> + kfree(hw_prof);
> +free_seg:
> + kfree(seg);
> +
> + return err;
> +}
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index 272b3d54f6e47..afeda21109409 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
[ ... ]
> @@ -1846,7 +1933,7 @@ int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
> struct ice_hw *hw = &pf->hw;
> int val;
>
> - if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
> + if (!test_bit(ICE_FLAG_FD_ENA, pf->flags) && !pf->hw.acl_tbl)
> return -EOPNOTSUPP;
[ ... ]
> @@ -2102,6 +2239,18 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
> return -ENOSPC;
> }
>
> + /* ACL filter */
> + if (pf->hw.acl_tbl && ice_is_acl_filter(fsp))
> + return ice_acl_add_rule_ethtool(vsi, cmd);
[Severity: Medium]
At this commit, does this report success for a rule that does not filter
anything? ice_acl_add_rule_ethtool() only reaches
ice_acl_prof_add_ethtool(), which creates the flow profile and returns 0
with no flow entry, no action, no VSI association and no insertion into
hw->fdir_list_head.
So ETHTOOL_SRXCLSRLINS would succeed while the rule is invisible to
ETHTOOL_GRXCLSRULE and ETHTOOL_GRXCLSRLALL and cannot be deleted, even
though the delete gate in ice_del_ntuple_ethtool() is relaxed for acl_tbl
devices in the same patch.
This is completed later in the series ("ice: program ACL entry"), where
ice_acl_add_rule_ethtool() builds the ice_ntuple_fltr, sets the action,
programs the TCAM entry with ice_flow_add_entry() and links the filter into
hw->fdir_list_head under hw->fdir_fltr_lock, so the gap is confined to the
intermediate patches. Would it be worth folding the dispatch into the
patch that actually programs the entry?
> +
> + /* Only fdir filters below */
> + if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
> + return -EOPNOTSUPP;
> +
> + ret = ice_cfg_fdir_xtrct_seq(pf, fsp, &userdata);
> + if (ret)
> + return ret;
> +
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_flow.c b/drivers/net/ethernet/intel/ice/ice_flow.c
> index 121552c644cdb..855d296aeed81 100644
> --- a/drivers/net/ethernet/intel/ice/ice_flow.c
> +++ b/drivers/net/ethernet/intel/ice/ice_flow.c
> @@ -989,6 +990,43 @@ static int ice_flow_proc_seg_hdrs(struct ice_flow_prof_params *params)
> return 0;
> }
>
> +/**
> + * ice_flow_xtract_pkt_flags - Create an extr sequence entry for packet flags
> + * @hw: pointer to the HW struct
> + * @params: information about the flow to be processed
> + * @flags: The value of pkt_flags[x:x] in Rx/Tx MDID metadata.
> + *
> + * Allocate an extraction sequence entries for a DWORD size chunk of the packet
> + * flags.
[Severity: Low]
This isn't a bug, but does the kernel-doc match the code? The parameter type
is enum ice_flex_mdid_pkt_flags, whose members select successive 16-bit
packet-flag ranges rather than carrying a value:
enum ice_flex_mdid_pkt_flags {
ICE_RX_MDID_PKT_FLAGS_15_0 = 20,
ICE_RX_MDID_PKT_FLAGS_31_16,
ICE_RX_MDID_PKT_FLAGS_47_32,
ICE_RX_MDID_PKT_FLAGS_63_48,
};
And the body fills exactly one field-vector word, not a DWORD chunk:
params->es[idx].prot_id = ICE_PROT_META_ID;
params->es[idx].off = flags;
params->es_cnt++;
> + *
> + * Return: 0 on success, negative on error
> + */
> +static int ice_flow_xtract_pkt_flags(struct ice_hw *hw,
> + struct ice_flow_prof_params *params,
> + enum ice_flex_mdid_pkt_flags flags)
> +{
[ ... ]
> @@ -1308,6 +1356,123 @@ ice_flow_create_xtrct_seq(struct ice_hw *hw,
> return status;
> }
>
[ ... ]
> +static int
> +ice_flow_acl_def_entry_frmt(struct ice_flow_prof_params *params)
> +{
> + u16 index, range_idx = 0;
> +
> + index = ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX;
> +
> + for (int i = 0; i < params->prof->segs_cnt; i++) {
> + struct ice_flow_seg_info *seg = ¶ms->prof->segs[i];
> + unsigned long match = seg->match;
> + int j;
> +
> + for_each_set_bit(j, &match, ICE_FLOW_FIELD_IDX_MAX) {
[Severity: Medium]
Can this read past the match local on 32-bit builds? seg->match is declared
u64 in struct ice_flow_seg_info, and ICE_FLOW_FIELD_IDX_MAX is 58, so with
BITS_PER_LONG == 32 the copy into unsigned long drops match bits 32 through
57, and find_next_bit() is told the bitmap is 58 bits wide while only 4
bytes of storage exist, so it dereferences (&match)[1].
The sibling loop in ice_flow_create_xtrct_seq() keeps the u64 and casts the
address instead:
u64 match = params->prof->segs[i].match;
...
for_each_set_bit(j, (unsigned long *)&match,
ICE_FLOW_FIELD_IDX_MAX) {
Besides the out-of-bounds read, would stale stack bits picked up above bit 31
be treated as real match fields here, inflating index and range_idx and so
producing a spurious -EINVAL or a wrong params->entry_length that
ice_flow_sel_acl_scen() then uses for scenario selection? ICE has no 64BIT
dependency in drivers/net/ethernet/intel/Kconfig, so i386 builds reach this.
> + struct ice_flow_fld_info *fld = &seg->fields[j];
> +
> + fld->entry.mask = ICE_FLOW_FLD_OFF_INVAL;
> +
[ ... ]
> + /* Store # bytes required for entry for later use */
> + params->entry_length = index - ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX;
> +
> + return 0;
> +}
> +
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
> index 952e6167517f4..df58bd72301ec 100644
> --- a/drivers/net/ethernet/intel/ice/ice_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_main.c
> @@ -4394,10 +4413,27 @@ static int ice_init_acl(struct ice_pf *pf)
> */
> static void ice_deinit_acl(struct ice_pf *pf)
> {
> + struct device *dev = ice_pf_to_dev(pf);
> struct ice_hw *hw = &pf->hw;
>
> ice_acl_rem_flows(hw);
> - ice_acl_destroy_tbl(&pf->hw);
> + ice_acl_destroy_tbl(hw);
> +
> + if (!hw->acl_prof)
> + return;
> +
> + for (int i = 0; i < ICE_FLTR_PTYPE_MAX; i++) {
> + struct ice_acl_hw_prof *hw_prof = hw->acl_prof[i];
> +
> + if (!hw_prof)
> + continue;
> +
> + kfree(hw_prof->seg);
> + kfree(hw_prof);
> + }
> +
> + devm_kfree(dev, hw->acl_prof);
> + hw->acl_prof = NULL;
> }
This is the teardown referenced in the locking question on
ice_acl_prof_add_ethtool() above.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v2 06/12] Revert "ice: remove unused ice_flow_entry fields"
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (4 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 05/12] ice: create flow profile Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-17 18:39 ` [PATCH net-next v2 07/12] ice: use plain alloc/dealloc for ice_ntuple_fltr Tony Nguyen
` (7 subsequent siblings)
13 siblings, 0 replies; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Marcin Szycik, anthony.l.nguyen, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Przemek Kitszel,
Rinitha S
From: Marcin Szycik <marcin.szycik@linux.intel.com>
This reverts commit 4cd7bc7144ec2c0bb27208c3bb1f153dfd44b1c7.
These fields will be needed in the following commits.
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_flow.c | 5 ++++-
drivers/net/ethernet/intel/ice/ice_flow.h | 3 +++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.c b/drivers/net/ethernet/intel/ice/ice_flow.c
index 855d296aeed8..e42367853253 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.c
+++ b/drivers/net/ethernet/intel/ice/ice_flow.c
@@ -1604,6 +1604,7 @@ ice_flow_rem_entry_sync(struct ice_hw *hw, enum ice_block __always_unused blk,
list_del(&entry->l_entry);
+ devm_kfree(ice_hw_to_dev(hw), entry->entry);
devm_kfree(ice_hw_to_dev(hw), entry);
return 0;
@@ -2024,8 +2025,10 @@ ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
*entry_h = ICE_FLOW_ENTRY_HNDL(e);
out:
- if (status)
+ if (status && e) {
+ devm_kfree(ice_hw_to_dev(hw), e->entry);
devm_kfree(ice_hw_to_dev(hw), e);
+ }
return status;
}
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.h b/drivers/net/ethernet/intel/ice/ice_flow.h
index 7357088091bd..23e366ee1f79 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.h
+++ b/drivers/net/ethernet/intel/ice/ice_flow.h
@@ -458,8 +458,11 @@ struct ice_flow_entry {
u64 id;
struct ice_flow_prof *prof;
+ /* Flow entry's content */
+ void *entry;
enum ice_flow_priority priority;
u16 vsi_handle;
+ u16 entry_sz;
};
#define ICE_FLOW_ENTRY_HNDL(e) ((u64)(uintptr_t)e)
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH net-next v2 07/12] ice: use plain alloc/dealloc for ice_ntuple_fltr
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (5 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 06/12] Revert "ice: remove unused ice_flow_entry fields" Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-21 19:57 ` netdev-bot+sashiko
2026-09-17 18:39 ` [PATCH net-next v2 08/12] ice: create ACL entry Tony Nguyen
` (6 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Marcin Szycik, anthony.l.nguyen, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Rinitha S
From: Marcin Szycik <marcin.szycik@linux.intel.com>
Change struct ice_ntuple_fltr allocation from devm_ to plain alloc,
since its lifetime is not tied to the device. All such objects are being
removed on device remove via ice_deinit_features() -> ice_deinit_fdir()
-> ice_vsi_manage_fdir() -> ice_fdir_del_all_fltrs()
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
index afeda2110940..0c088e626143 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
@@ -1806,7 +1806,7 @@ void ice_fdir_del_all_fltrs(struct ice_vsi *vsi)
ice_fdir_write_all_fltr(pf, f_rule, false);
ice_ntuple_update_cntrs(hw, f_rule, false);
list_del(&f_rule->fltr_node);
- devm_kfree(ice_pf_to_dev(pf), f_rule);
+ kfree(f_rule);
}
}
@@ -1907,7 +1907,7 @@ ice_ntuple_update_list_entry(struct ice_pf *pf, struct ice_ntuple_fltr *input,
*/
ice_fdir_do_rem_flow(pf, old_fltr->flow_type);
list_del(&old_fltr->fltr_node);
- devm_kfree(ice_hw_to_dev(hw), old_fltr);
+ kfree(old_fltr);
}
if (!input)
return err;
@@ -2259,7 +2259,7 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
return -ENOSPC;
}
- input = devm_kzalloc(dev, sizeof(*input), GFP_KERNEL);
+ input = kzalloc_obj(*input);
if (!input)
return -ENOMEM;
@@ -2303,7 +2303,7 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
mutex_unlock(&hw->fdir_fltr_lock);
free_input:
if (ret)
- devm_kfree(dev, input);
+ kfree(input);
return ret;
}
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 07/12] ice: use plain alloc/dealloc for ice_ntuple_fltr
2026-09-17 18:39 ` [PATCH net-next v2 07/12] ice: use plain alloc/dealloc for ice_ntuple_fltr Tony Nguyen
@ 2026-09-21 19:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:57 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
lukasz.czapnik, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, sx.rinitha
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 (1) · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] ice_add_ntuple_ethtool()
(drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c:2262) now…
Pre-existing issues:
- [High] hw->fdir_list_head is documented as being protected by
hw->fdir_fltr_lock (see the comment on ice_fdir_del_all_fltrs(),…
--
ice: use plain alloc/dealloc for ice_ntuple_fltr
This converts struct ice_ntuple_fltr from devm_kzalloc()/devm_kfree() to
kzalloc_obj()/kfree(), on the grounds that the filter objects are always
torn down explicitly on device remove. Four call sites in
ice_ethtool_ntuple.c are updated.
A couple of questions below about the teardown assumption and about the
readers of hw->fdir_list_head.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index afeda21109409..0c088e6261438 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> @@ -1806,7 +1806,7 @@ void ice_fdir_del_all_fltrs(struct ice_vsi *vsi)
> ice_fdir_write_all_fltr(pf, f_rule, false);
> ice_ntuple_update_cntrs(hw, f_rule, false);
> list_del(&f_rule->fltr_node);
> - devm_kfree(ice_pf_to_dev(pf), f_rule);
> + kfree(f_rule);
> }
> }
>
> @@ -1907,7 +1907,7 @@ ice_ntuple_update_list_entry(struct ice_pf *pf, struct ice_ntuple_fltr *input,
> */
> ice_fdir_do_rem_flow(pf, old_fltr->flow_type);
> list_del(&old_fltr->fltr_node);
> - devm_kfree(ice_hw_to_dev(hw), old_fltr);
> + kfree(old_fltr);
[Severity: High]
This isn't a bug introduced by this patch, but since this patch is now the
code doing the freeing, is the reader side of hw->fdir_list_head safe
against these kfree() calls?
The comment on ice_fdir_del_all_fltrs() states the list is protected by
hw->fdir_fltr_lock, and the writers do take it (ice_ntuple_update_list_entry()
via ice_add_ntuple_ethtool()/ice_del_ntuple_ethtool(), ice_vsi_manage_fdir(),
ice_remove_q_channels()). But ice_fdir_replay_fltrs() walks the same list with
no lock at all:
drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c:ice_fdir_replay_fltrs() {
list_for_each_entry(f_rule, &hw->fdir_list_head, fltr_node) {
int err;
if (f_rule->acl_fltr)
continue;
err = ice_fdir_write_all_fltr(pf, f_rule, true);
...
}
}
and it runs from the service task, ice_reset_subtask() -> ice_rebuild() ->
ice_fdir_replay_fltrs(), without rtnl_lock and without hw->fdir_fltr_lock.
The reset-in-progress test in ice_del_ntuple_ethtool() and at the top of
ice_ntuple_update_list_entry() is evaluated before the slow part of the
writer: ice_fdir_write_all_fltr() -> ice_fdir_write_fltr() does GFP_KERNEL
allocations and HW programming and can sleep for an unbounded time, and only
after that does the writer run:
list_del(&old_fltr->fltr_node);
kfree(old_fltr);
So can the replay walk end up dereferencing a node that was just kfree()d,
including reading fltr_node.next after list_del() has set it to
LIST_POISON1?
The mirror case exists too: ice_add_ntuple_ethtool() calls
ice_fdir_find_fltr_by_idx(), which walks the list, before
mutex_lock(&hw->fdir_fltr_lock), so it can traverse nodes that
ice_prepare_for_reset() -> ice_remove_q_channels() -> ice_fdir_del_all_fltrs()
is freeing in the service task.
This same shape is still present at the tip of the series, with
ice_fdir_replay_fltrs() still lockless.
> @@ -2259,7 +2259,7 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
> return -ENOSPC;
> }
>
> - input = devm_kzalloc(dev, sizeof(*input), GFP_KERNEL);
> + input = kzalloc_obj(*input);
> if (!input)
> return -ENOMEM;
>
[Severity: Medium]
The commit message says:
All such objects are being removed on device remove via
ice_deinit_features() -> ice_deinit_fdir() -> ice_vsi_manage_fdir() ->
ice_fdir_del_all_fltrs()
Is that always true? ice_deinit_features() bails out first thing in Safe
Mode:
drivers/net/ethernet/intel/ice/ice_main.c:ice_deinit_features() {
if (ice_is_safe_mode(pf))
return;
ice_deinit_lag(pf);
...
ice_deinit_fdir(pf);
...
}
A device that was fully featured when the filters were installed can later
move into Safe Mode at runtime: ice_rebuild() reloads the package after
CORER/GLOBR,
else
/* Reload DDP Package after CORER/GLOBR reset */
ice_load_pkg(NULL, pf);
and when ice_init_pkg() fails, ice_load_pkg() does:
if (!ice_is_init_pkg_successful(state)) {
/* Safe Mode */
clear_bit(ICE_FLAG_ADV_FEATURES, pf->flags);
return;
}
with ice_is_safe_mode() defined as !test_bit(ICE_FLAG_ADV_FEATURES,
pf->flags). ICE_FLAG_FD_ENA is not cleared there, so the entries stay on
hw->fdir_list_head (ice_fdir_replay_fltrs() keeps them, and
ice_fdir_del_all_fltrs() only runs from ice_prepare_for_reset() when ADQ
channels exist).
In that state, does unbind leak every installed struct ice_ntuple_fltr, since
ice_deinit_fdir() -> ice_vsi_manage_fdir() -> ice_fdir_del_all_fltrs() is
never reached and devres no longer reclaims the objects? The same structure
is present at the tip of the series, so nothing later frees them either.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v2 08/12] ice: create ACL entry
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (6 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 07/12] ice: use plain alloc/dealloc for ice_ntuple_fltr Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-21 19:57 ` netdev-bot+sashiko
2026-09-17 18:39 ` [PATCH net-next v2 09/12] ice: program " Tony Nguyen
` (5 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Real Valiquette, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Chinh Cao,
Rinitha S
From: Real Valiquette <real.valiquette@intel.com>
Create an ACL entry for the mask match data and set the desired action.
Generate and program the associated extraction sequence.
Co-developed-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Real Valiquette <real.valiquette@intel.com>
Co-developed-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
Signed-off-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
Co-developed-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 3 +
drivers/net/ethernet/intel/ice/ice_acl.c | 183 +++++
drivers/net/ethernet/intel/ice/ice_acl.h | 27 +
drivers/net/ethernet/intel/ice/ice_acl_main.c | 163 ++++-
.../net/ethernet/intel/ice/ice_adminq_cmd.h | 113 ++++
.../ethernet/intel/ice/ice_ethtool_ntuple.c | 37 +-
drivers/net/ethernet/intel/ice/ice_fdir.c | 3 +
.../net/ethernet/intel/ice/ice_flex_pipe.c | 5 +-
.../net/ethernet/intel/ice/ice_flex_pipe.h | 2 +
drivers/net/ethernet/intel/ice/ice_flow.c | 640 +++++++++++++++++-
drivers/net/ethernet/intel/ice/ice_flow.h | 9 +-
.../net/ethernet/intel/ice/ice_lan_tx_rx.h | 3 +
drivers/net/ethernet/intel/ice/ice_main.c | 2 +-
drivers/net/ethernet/intel/ice/virt/fdir.c | 4 +-
14 files changed, 1160 insertions(+), 34 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index fe293d51ac8c..7c72a61ffae5 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -1027,6 +1027,9 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
int ice_get_ethtool_fdir_entry(struct ice_hw *hw, struct ethtool_rxnfc *cmd);
u32 ice_ntuple_get_max_fltr_cnt(struct ice_hw *hw);
+int ice_ntuple_set_input_set(struct ice_vsi *vsi, enum ice_block blk,
+ struct ethtool_rx_flow_spec *fsp,
+ struct ice_ntuple_fltr *input);
int ice_ntuple_l4_proto_to_port(enum ice_flow_seg_hdr l4_proto,
enum ice_flow_field *src_port,
enum ice_flow_field *dst_port);
diff --git a/drivers/net/ethernet/intel/ice/ice_acl.c b/drivers/net/ethernet/intel/ice/ice_acl.c
index 7821ca247c82..3179ce33e365 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl.c
+++ b/drivers/net/ethernet/intel/ice/ice_acl.c
@@ -135,6 +135,189 @@ int ice_aq_program_actpair(struct ice_hw *hw, u8 act_mem_idx, u16 act_entry_idx,
return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
}
+/**
+ * ice_acl_prof_aq_send - send ACL profile AQ commands
+ * @hw: pointer to the HW struct
+ * @opc: command opcode
+ * @prof_id: profile ID
+ * @buf: ptr to buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_acl_prof_aq_send(struct ice_hw *hw, u16 opc, u8 prof_id,
+ struct ice_aqc_acl_prof_generic_frmt *buf,
+ struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_profile *cmd;
+ struct libie_aq_desc desc;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, opc);
+ cmd = libie_aq_raw(&desc);
+ cmd->profile_id = prof_id;
+
+ if (opc == ice_aqc_opc_program_acl_prof_extraction)
+ desc.flags |= cpu_to_le16(LIBIE_AQ_FLAG_RD);
+
+ return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
+}
+
+/**
+ * ice_prgm_acl_prof_xtrct - program ACL profile extraction sequence
+ * @hw: pointer to the HW struct
+ * @prof_id: profile ID
+ * @buf: ptr to buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Program ACL profile extraction (indirect 0x0C1D)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_prgm_acl_prof_xtrct(struct ice_hw *hw, u8 prof_id,
+ struct ice_aqc_acl_prof_generic_frmt *buf,
+ struct ice_sq_cd *cd)
+{
+ return ice_acl_prof_aq_send(hw, ice_aqc_opc_program_acl_prof_extraction,
+ prof_id, buf, cd);
+}
+
+/**
+ * ice_query_acl_prof - query ACL profile
+ * @hw: pointer to the HW struct
+ * @prof_id: profile ID
+ * @buf: ptr to buffer (which will contain response of this command)
+ * @cd: pointer to command details structure or NULL
+ *
+ * Query ACL profile (indirect 0x0C21)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_query_acl_prof(struct ice_hw *hw, u8 prof_id,
+ struct ice_aqc_acl_prof_generic_frmt *buf,
+ struct ice_sq_cd *cd)
+{
+ return ice_acl_prof_aq_send(hw, ice_aqc_opc_query_acl_prof, prof_id,
+ buf, cd);
+}
+
+/**
+ * ice_aq_acl_cntrs_chk_params - Checks ACL counter parameters
+ * @cntrs: ptr to buffer describing input and output params
+ *
+ * This function checks the counter bank range for counter type and returns
+ * success or failure.
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_aq_acl_cntrs_chk_params(struct ice_acl_cntrs *cntrs)
+{
+ int err = 0;
+
+ if (!cntrs->amount)
+ return -EINVAL;
+
+ switch (cntrs->type) {
+ case ICE_AQC_ACL_CNT_TYPE_SINGLE:
+ /* Single counter type - configured to count either bytes
+ * or packets, the valid values for byte or packet counters
+ * shall be 0-3.
+ */
+ if (cntrs->bank > ICE_AQC_ACL_MAX_CNT_SINGLE)
+ err = -EIO;
+ break;
+ case ICE_AQC_ACL_CNT_TYPE_DUAL:
+ /* Pair counter type - counts number of bytes and packets
+ * The valid values for byte/packet counter duals shall be 0-1
+ */
+ if (cntrs->bank > ICE_AQC_ACL_MAX_CNT_DUAL)
+ err = -EIO;
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+/**
+ * ice_aq_alloc_acl_cntrs - allocate ACL counters
+ * @hw: pointer to the HW struct
+ * @cntrs: ptr to buffer describing input and output params
+ * @cd: pointer to command details structure or NULL
+ *
+ * Allocate ACL counters (indirect 0x0C16). This function attempts to
+ * allocate a contiguous block of counters. In case of failures, caller can
+ * attempt to allocate a smaller chunk. The allocation is considered
+ * unsuccessful if returned counter value is invalid. In this case it returns
+ * an error otherwise success.
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_alloc_acl_cntrs(struct ice_hw *hw, struct ice_acl_cntrs *cntrs,
+ struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_alloc_counters *cmd;
+ u16 first_cntr, last_cntr;
+ struct libie_aq_desc desc;
+ int err;
+
+ err = ice_aq_acl_cntrs_chk_params(cntrs);
+ if (err)
+ return err;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_alloc_acl_counters);
+ cmd = libie_aq_raw(&desc);
+ cmd->counter_amount = cntrs->amount;
+ cmd->counters_type = cntrs->type;
+ cmd->bank_alloc = cntrs->bank;
+
+ err = ice_aq_send_cmd(hw, &desc, NULL, 0, cd);
+ if (err)
+ return err;
+
+ first_cntr = le16_to_cpu(cmd->ops.resp.first_counter);
+ last_cntr = le16_to_cpu(cmd->ops.resp.last_counter);
+
+ if (first_cntr == ICE_AQC_ACL_ALLOC_CNT_INVAL ||
+ last_cntr == ICE_AQC_ACL_ALLOC_CNT_INVAL)
+ return -EIO;
+
+ cntrs->first_cntr = first_cntr;
+ cntrs->last_cntr = last_cntr;
+
+ return 0;
+}
+
+/**
+ * ice_aq_dealloc_acl_cntrs - deallocate ACL counters
+ * @hw: pointer to the HW struct
+ * @cntrs: ptr to buffer describing input and output params
+ * @cd: pointer to command details structure or NULL
+ *
+ * De-allocate ACL counters (direct 0x0C17)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_aq_dealloc_acl_cntrs(struct ice_hw *hw, struct ice_acl_cntrs *cntrs,
+ struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_dealloc_counters *cmd;
+ struct libie_aq_desc desc;
+ int err;
+
+ err = ice_aq_acl_cntrs_chk_params(cntrs);
+ if (err)
+ return err;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_dealloc_acl_counters);
+ cmd = libie_aq_raw(&desc);
+ cmd->first_counter = cpu_to_le16(cntrs->first_cntr);
+ cmd->last_counter = cpu_to_le16(cntrs->last_cntr);
+ cmd->counters_type = cntrs->type;
+ cmd->bank_alloc = cntrs->bank;
+ return ice_aq_send_cmd(hw, &desc, NULL, 0, cd);
+}
+
/**
* ice_aq_alloc_acl_scen - allocate ACL scenario
* @hw: pointer to the HW struct
diff --git a/drivers/net/ethernet/intel/ice/ice_acl.h b/drivers/net/ethernet/intel/ice/ice_acl.h
index 148e9b67a115..066665c9d963 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl.h
+++ b/drivers/net/ethernet/intel/ice/ice_acl.h
@@ -6,6 +6,9 @@
#include "ice_common.h"
+/* Marks a PF scenario slot as unused in the ACL profile extraction table */
+#define ICE_ACL_INVALID_SCEN 0x3f
+
#define ICE_ACL_TBL_PARAMS_DEP_TBLS_MAX 15
struct ice_acl_tbl_params {
u16 width; /* Select/match bytes */
@@ -100,6 +103,20 @@ struct ice_acl_alloc_tbl {
} buf;
};
+/* Input and output params for [de]allocate_acl_counters */
+struct ice_acl_cntrs {
+ u8 amount;
+ u8 type;
+ u8 bank;
+
+ /* first/last:
+ * Output in case of alloc_acl_counters
+ * Input in case of deallocate_acl_counters
+ */
+ u16 first_cntr;
+ u16 last_cntr;
+};
+
int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params);
int ice_acl_destroy_tbl(struct ice_hw *hw);
int ice_acl_create_scen(struct ice_hw *hw, u16 match_width, u16 num_entries,
@@ -114,6 +131,16 @@ int ice_aq_program_acl_entry(struct ice_hw *hw, u8 tcam_idx, u16 entry_idx,
struct ice_sq_cd *cd);
int ice_aq_program_actpair(struct ice_hw *hw, u8 act_mem_idx, u16 act_entry_idx,
struct ice_aqc_actpair *buf, struct ice_sq_cd *cd);
+int ice_prgm_acl_prof_xtrct(struct ice_hw *hw, u8 prof_id,
+ struct ice_aqc_acl_prof_generic_frmt *buf,
+ struct ice_sq_cd *cd);
+int ice_query_acl_prof(struct ice_hw *hw, u8 prof_id,
+ struct ice_aqc_acl_prof_generic_frmt *buf,
+ struct ice_sq_cd *cd);
+int ice_aq_alloc_acl_cntrs(struct ice_hw *hw, struct ice_acl_cntrs *cntrs,
+ struct ice_sq_cd *cd);
+int ice_aq_dealloc_acl_cntrs(struct ice_hw *hw, struct ice_acl_cntrs *cntrs,
+ struct ice_sq_cd *cd);
int ice_aq_alloc_acl_scen(struct ice_hw *hw, u16 *scen_id,
struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd);
int ice_aq_dealloc_acl_scen(struct ice_hw *hw, u16 scen_id,
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.c b/drivers/net/ethernet/intel/ice/ice_acl_main.c
index db8137e6f870..6313d511398a 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_acl_main.c
@@ -5,6 +5,9 @@
#include "ice_lib.h"
#include "ice_acl_main.h"
+/* Default ACL Action priority */
+#define ICE_ACL_ACT_PRIO 3
+
/* Number of action */
#define ICE_ACL_NUM_ACT 1
@@ -207,6 +210,111 @@ static int ice_acl_prof_add_ethtool(struct ice_pf *pf,
return err;
}
+/**
+ * ice_acl_set_act_drop - setup drop action
+ * @action: pointer to action
+ */
+static void ice_acl_set_act_drop(struct ice_flow_action *action)
+{
+ action->type = ICE_FLOW_ACT_DROP;
+ action->data.acl_act.mdid = ICE_MDID_RX_PKT_DROP;
+ action->data.acl_act.prio = ICE_ACL_ACT_PRIO;
+ action->data.acl_act.value = cpu_to_le16(ICE_RX_PKT_DROP_DROP);
+}
+
+/**
+ * ice_acl_set_act_fwd_queue - setup forward to queue action
+ * @action: pointer to action
+ * @queue_index: queue index
+ */
+static void ice_acl_set_act_fwd_queue(struct ice_flow_action *action,
+ s16 queue_index)
+{
+ action->type = ICE_FLOW_ACT_FWD_QUEUE;
+ action->data.acl_act.mdid = ICE_MDID_RX_DST_Q;
+ action->data.acl_act.prio = ICE_ACL_ACT_PRIO;
+ action->data.acl_act.value = cpu_to_le16(queue_index);
+}
+
+/**
+ * ice_acl_comp_rules - compare two ACL filters
+ * @a: first ACL filter
+ * @b: second ACL filter
+ *
+ * Return: true if a and b values and masks are identical, false otherwise
+ */
+static bool
+ice_acl_comp_rules(struct ice_ntuple_fltr *a, struct ice_ntuple_fltr *b)
+{
+ bool base_equal;
+
+ if (a->flow_type != b->flow_type)
+ return false;
+
+ base_equal = a->ip.v4.dst_ip == b->ip.v4.dst_ip &&
+ a->ip.v4.src_ip == b->ip.v4.src_ip &&
+ a->mask.v4.dst_ip == b->mask.v4.dst_ip &&
+ a->mask.v4.src_ip == b->mask.v4.src_ip;
+
+ switch (a->flow_type) {
+ case ICE_FLTR_PTYPE_NONF_IPV4_TCP:
+ case ICE_FLTR_PTYPE_NONF_IPV4_UDP:
+ case ICE_FLTR_PTYPE_NONF_IPV4_SCTP:
+ return base_equal &&
+ a->ip.v4.dst_port == b->ip.v4.dst_port &&
+ a->ip.v4.src_port == b->ip.v4.src_port &&
+ a->mask.v4.dst_port == b->mask.v4.dst_port &&
+ a->mask.v4.src_port == b->mask.v4.src_port;
+ case ICE_FLTR_PTYPE_NONF_IPV4_OTHER:
+ return base_equal &&
+ a->ip.v4.l4_header == b->ip.v4.l4_header &&
+ a->ip.v4.proto == b->ip.v4.proto &&
+ a->ip.v4.ip_ver == b->ip.v4.ip_ver &&
+ a->ip.v4.tos == b->ip.v4.tos &&
+ a->mask.v4.l4_header == b->mask.v4.l4_header &&
+ a->mask.v4.proto == b->mask.v4.proto &&
+ a->mask.v4.ip_ver == b->mask.v4.ip_ver &&
+ a->mask.v4.tos == b->mask.v4.tos;
+ default:
+ return false;
+ }
+}
+
+/**
+ * ice_acl_is_dup_fltr - test if an ACL filter is already in the list
+ * @hw: hardware data structure
+ * @input: ACL filter to check
+ *
+ * Return: true if a filter with identical match criteria (same flow type,
+ * values, and masks) already exists, unless it is at the same location with a
+ * different queue (an update)
+ */
+static bool
+ice_acl_is_dup_fltr(struct ice_hw *hw, struct ice_ntuple_fltr *input)
+{
+ struct ice_ntuple_fltr *rule;
+
+ list_for_each_entry(rule, &hw->fdir_list_head, fltr_node) {
+ if (!rule->acl_fltr)
+ continue;
+
+ if (!ice_acl_comp_rules(rule, input))
+ continue;
+
+ /* At this point rule and input have same match criteria.
+ * Same location with a different queue is an update, not a
+ * duplicate - skip it. Everything else is a duplicate.
+ */
+ if (rule->fltr_id == input->fltr_id &&
+ rule->q_index != input->q_index)
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
/**
* ice_acl_add_rule_ethtool - add an ACL rule
* @vsi: pointer to target VSI
@@ -216,12 +324,65 @@ static int ice_acl_prof_add_ethtool(struct ice_pf *pf,
*/
int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
{
+ struct ice_flow_action acts[ICE_ACL_NUM_ACT];
struct ethtool_rx_flow_spec *fsp;
+ struct ice_acl_hw_prof *hw_prof;
+ struct ice_ntuple_fltr *input;
+ enum ice_fltr_ptype flow;
+ struct device *dev;
struct ice_pf *pf;
+ struct ice_hw *hw;
+ u64 entry_h = 0;
+ int err;
pf = vsi->back;
+ hw = &pf->hw;
+ dev = ice_pf_to_dev(pf);
fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
- return ice_acl_prof_add_ethtool(pf, fsp);
+ err = ice_acl_prof_add_ethtool(pf, fsp);
+ if (err)
+ return err;
+
+ /* Add new rule */
+ input = kzalloc_obj(*input);
+ if (!input)
+ return -ENOMEM;
+
+ err = ice_ntuple_set_input_set(vsi, ICE_BLK_ACL, fsp, input);
+ if (err)
+ goto free_input;
+
+ mutex_lock(&hw->fdir_fltr_lock);
+ if (ice_acl_is_dup_fltr(hw, input)) {
+ mutex_unlock(&hw->fdir_fltr_lock);
+ err = -EINVAL;
+ goto free_input;
+ }
+ mutex_unlock(&hw->fdir_fltr_lock);
+
+ memset(&acts, 0, sizeof(acts));
+ if (fsp->ring_cookie == RX_CLS_FLOW_DISC)
+ ice_acl_set_act_drop(&acts[0]);
+ else
+ ice_acl_set_act_fwd_queue(&acts[0], input->q_index);
+
+ flow = ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT);
+ hw_prof = hw->acl_prof[flow];
+
+ err = ice_flow_add_entry(hw, ICE_BLK_ACL, hw_prof->prof_id,
+ fsp->location, vsi->idx, ICE_FLOW_PRIO_NORMAL,
+ input, acts, ICE_ACL_NUM_ACT, &entry_h);
+ if (err) {
+ dev_err(dev, "Could not add flow entry %d\n", flow);
+ goto free_input;
+ }
+
+ return 0;
+
+free_input:
+ kfree(input);
+
+ return err;
}
diff --git a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
index 43b2a67aa207..44248df765fe 100644
--- a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
+++ b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
@@ -2153,6 +2153,67 @@ struct ice_aqc_acl_scen {
u8 act_mem_cfg[ICE_AQC_MAX_ACTION_MEMORIES];
};
+/* Allocate ACL counters (indirect 0x0C16) */
+struct ice_aqc_acl_alloc_counters {
+ /* Amount of contiguous counters requested. Min value is 1 and
+ * max value is 255
+ */
+ u8 counter_amount;
+
+ /* Counter type: 'single counter' which can be configured to count
+ * either bytes or packets
+ */
+#define ICE_AQC_ACL_CNT_TYPE_SINGLE 0x0
+
+ /* Counter type: 'counter pair' which counts number of bytes and number
+ * of packets.
+ */
+#define ICE_AQC_ACL_CNT_TYPE_DUAL 0x1
+ /* requested counter type, single/dual */
+ u8 counters_type;
+
+ /* counter bank allocation shall be 0-3 for 'byte or packet counter' */
+#define ICE_AQC_ACL_MAX_CNT_SINGLE 0x3
+ /* counter bank allocation shall be 0-1 for 'byte and packet counter
+ * dual'
+ */
+#define ICE_AQC_ACL_MAX_CNT_DUAL 0x1
+ /* requested counter bank allocation */
+ u8 bank_alloc;
+
+ u8 reserved;
+
+ union {
+ /* Applicable only in case of command */
+ struct {
+ u8 reserved[12];
+ } cmd;
+ /* Applicable only in case of response */
+#define ICE_AQC_ACL_ALLOC_CNT_INVAL 0xFFFF
+ struct {
+ /* Index of first allocated counter. 0xFFFF in case
+ * of unsuccessful allocation
+ */
+ __le16 first_counter;
+ /* Index of last allocated counter. 0xFFFF in case
+ * of unsuccessful allocation
+ */
+ __le16 last_counter;
+ u8 rsvd[8];
+ } resp;
+ } ops;
+};
+
+/* De-allocate ACL counters (direct 0x0C17) */
+struct ice_aqc_acl_dealloc_counters {
+ __le16 first_counter;
+ __le16 last_counter;
+ /* single/dual */
+ u8 counters_type;
+ u8 bank_alloc;
+ u8 reserved[10];
+};
+
/* Program ACL actionpair (indirect 0x0C1C) */
struct ice_aqc_acl_actpair {
u8 act_mem_index;
@@ -2164,6 +2225,8 @@ struct ice_aqc_acl_actpair {
__le32 addr_low;
};
+#define ICE_RX_PKT_DROP_DROP 0x1
+
/* Input buffer format for program/query action-pair admin command */
struct ice_acl_act_entry {
/* Action priority, values must be between 0..7 */
@@ -2186,7 +2249,53 @@ struct ice_aqc_actpair {
* map its entries to the byte selection base.
*/
#define ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX 1
+
#define ICE_AQC_ACL_PROF_BYTE_SEL_ELEMS 30
+#define ICE_AQC_ACL_PROF_WORD_SEL_ELEMS 32
+#define ICE_AQC_ACL_PROF_DWORD_SEL_ELEMS 15
+#define ICE_AQC_ACL_PROF_PF_SCEN_NUM_ELEMS 8
+
+/* Generic format used to describe either input or response buffer
+ * for admin commands related to ACL profile
+ */
+struct ice_aqc_acl_prof_generic_frmt {
+ /* In each byte:
+ * Bit 0..5 = Byte selection for the byte selection base from the
+ * extracted fields (expressed as byte offset in extracted fields).
+ * Applicable values are 0..63
+ * Bit 6..7 = Reserved
+ */
+ u8 byte_selection[ICE_AQC_ACL_PROF_BYTE_SEL_ELEMS];
+ /* In each byte:
+ * Bit 0..4 = Word selection for the word selection base from the
+ * extracted fields (expressed as word offset in extracted fields).
+ * Applicable values are 0..31
+ * Bit 5..7 = Reserved
+ */
+ u8 word_selection[ICE_AQC_ACL_PROF_WORD_SEL_ELEMS];
+ /* In each byte:
+ * Bit 0..3 = Double word selection for the double-word selection base
+ * from the extracted fields (expressed as double-word offset in
+ * extracted fields).
+ * Applicable values are 0..15
+ * Bit 4..7 = Reserved
+ */
+ u8 dword_selection[ICE_AQC_ACL_PROF_DWORD_SEL_ELEMS];
+ /* Scenario numbers for individual Physical Function's */
+ u8 pf_scenario_num[ICE_AQC_ACL_PROF_PF_SCEN_NUM_ELEMS];
+};
+
+/* Program ACL profile extraction (indirect 0x0C1D)
+ * Program ACL profile ranges (indirect 0x0C1E)
+ * Query ACL profile (indirect 0x0C21)
+ * Query ACL profile ranges (indirect 0x0C22)
+ */
+struct ice_aqc_acl_profile {
+ u8 profile_id; /* Programmed/Updated profile ID */
+ u8 reserved[7];
+ __le32 addr_high;
+ __le32 addr_low;
+};
/* Input buffer format for program profile extraction admin command and
* response buffer format for query profile admin command is as defined
@@ -2911,9 +3020,13 @@ enum ice_adminq_opc {
ice_aqc_opc_dealloc_acl_tbl = 0x0C11,
ice_aqc_opc_alloc_acl_scen = 0x0C14,
ice_aqc_opc_dealloc_acl_scen = 0x0C15,
+ ice_aqc_opc_alloc_acl_counters = 0x0C16,
+ ice_aqc_opc_dealloc_acl_counters = 0x0C17,
ice_aqc_opc_update_acl_scen = 0x0C1B,
ice_aqc_opc_program_acl_actpair = 0x0C1C,
+ ice_aqc_opc_program_acl_prof_extraction = 0x0C1D,
ice_aqc_opc_program_acl_entry = 0x0C20,
+ ice_aqc_opc_query_acl_prof = 0x0C21,
ice_aqc_opc_query_acl_scen = 0x0C23,
/* Tx queue handling commands/events */
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
index 0c088e626143..f67b6e1c0ffb 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
@@ -514,7 +514,7 @@ void ice_fdir_replay_flows(struct ice_hw *hw)
prof->vsi_h[0],
prof->vsi_h[j],
prio, prof->fdir_seg,
- &entry_h);
+ NULL, 0, &entry_h);
if (err) {
dev_err(ice_hw_to_dev(hw), "Could not replay Flow Director, flow type %d\n",
flow);
@@ -747,12 +747,12 @@ ice_fdir_set_hw_fltr_rule(struct ice_pf *pf, struct ice_flow_seg_info *seg,
return err;
err = ice_flow_add_entry(hw, ICE_BLK_FD, prof->id, main_vsi->idx,
main_vsi->idx, ICE_FLOW_PRIO_NORMAL,
- seg, &entry1_h);
+ seg, NULL, 0, &entry1_h);
if (err)
goto err_prof;
err = ice_flow_add_entry(hw, ICE_BLK_FD, prof->id, main_vsi->idx,
ctrl_vsi->idx, ICE_FLOW_PRIO_NORMAL,
- seg, &entry2_h);
+ seg, NULL, 0, &entry2_h);
if (err)
goto err_entry;
@@ -776,7 +776,7 @@ ice_fdir_set_hw_fltr_rule(struct ice_pf *pf, struct ice_flow_seg_info *seg,
vsi_h = main_vsi->tc_map_vsi[idx]->idx;
err = ice_flow_add_entry(hw, ICE_BLK_FD, prof->id,
main_vsi->idx, vsi_h,
- ICE_FLOW_PRIO_NORMAL, seg,
+ ICE_FLOW_PRIO_NORMAL, seg, NULL, 0,
&entry1_h);
if (err) {
dev_err(dev, "Could not add Channel VSI %d to flow group\n",
@@ -2037,28 +2037,36 @@ ice_update_ring_dest_vsi(struct ice_vsi *vsi, u16 *dest_vsi, u32 *ring)
}
/**
- * ice_ntuple_set_input_set - Set the input set for Flow Director
+ * ice_ntuple_set_input_set - Set the input set for specified block
* @vsi: pointer to target VSI
+ * @blk: filter block to configure
* @fsp: pointer to ethtool Rx flow specification
* @input: filter structure
*
* Return: 0 on success, negative on failure
*/
-static int
-ice_ntuple_set_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp,
- struct ice_ntuple_fltr *input)
+int ice_ntuple_set_input_set(struct ice_vsi *vsi, enum ice_block blk,
+ struct ethtool_rx_flow_spec *fsp,
+ struct ice_ntuple_fltr *input)
{
s16 q_index = ICE_FDIR_NO_QUEUE_IDX;
+ int flow_type, flow_mask;
u16 orig_q_index = 0;
struct ice_pf *pf;
struct ice_hw *hw;
- int flow_type;
u16 dest_vsi;
u8 dest_ctl;
if (!vsi || !fsp || !input)
return -EINVAL;
+ if (blk == ICE_BLK_FD)
+ flow_mask = FLOW_EXT;
+ else if (blk == ICE_BLK_ACL)
+ flow_mask = FLOW_MAC_EXT;
+ else
+ return -EINVAL;
+
pf = vsi->back;
hw = &pf->hw;
@@ -2070,7 +2078,8 @@ ice_ntuple_set_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp,
u8 vf = ethtool_get_flow_spec_ring_vf(fsp->ring_cookie);
if (vf) {
- dev_err(ice_pf_to_dev(pf), "Failed to add filter. Flow director filters are not supported on VF queues.\n");
+ dev_err(ice_pf_to_dev(pf), "Failed to add filter. %s filters are not supported on VF queues.\n",
+ blk == ICE_BLK_FD ? "Flow Director" : "ACL");
return -EINVAL;
}
@@ -2085,7 +2094,7 @@ ice_ntuple_set_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp,
input->fltr_id = fsp->location;
input->q_index = q_index;
- flow_type = fsp->flow_type & ~FLOW_EXT;
+ flow_type = fsp->flow_type & ~flow_mask;
/* Record the original queue index as specified by user.
* with channel configuration 'q_index' becomes relative
@@ -2139,9 +2148,9 @@ ice_ntuple_set_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp,
case TCP_V6_FLOW:
case UDP_V6_FLOW:
case SCTP_V6_FLOW:
- memcpy(input->ip.v6.dst_ip, fsp->h_u.usr_ip6_spec.ip6dst,
+ memcpy(input->ip.v6.dst_ip, fsp->h_u.tcp_ip6_spec.ip6dst,
sizeof(struct in6_addr));
- memcpy(input->ip.v6.src_ip, fsp->h_u.usr_ip6_spec.ip6src,
+ memcpy(input->ip.v6.src_ip, fsp->h_u.tcp_ip6_spec.ip6src,
sizeof(struct in6_addr));
input->ip.v6.dst_port = fsp->h_u.tcp_ip6_spec.pdst;
input->ip.v6.src_port = fsp->h_u.tcp_ip6_spec.psrc;
@@ -2263,7 +2272,7 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
if (!input)
return -ENOMEM;
- ret = ice_ntuple_set_input_set(vsi, fsp, input);
+ ret = ice_ntuple_set_input_set(vsi, ICE_BLK_FD, fsp, input);
if (ret)
goto free_input;
diff --git a/drivers/net/ethernet/intel/ice/ice_fdir.c b/drivers/net/ethernet/intel/ice/ice_fdir.c
index 1bc91cb41769..1d79138683b0 100644
--- a/drivers/net/ethernet/intel/ice/ice_fdir.c
+++ b/drivers/net/ethernet/intel/ice/ice_fdir.c
@@ -1261,6 +1261,9 @@ bool ice_fdir_is_dup_fltr(struct ice_hw *hw, struct ice_ntuple_fltr *input)
bool ret = false;
list_for_each_entry(rule, &hw->fdir_list_head, fltr_node) {
+ if (rule->acl_fltr)
+ continue;
+
if (rule->flow_type != input->flow_type)
continue;
diff --git a/drivers/net/ethernet/intel/ice/ice_flex_pipe.c b/drivers/net/ethernet/intel/ice/ice_flex_pipe.c
index d255ffcd5c86..92289b97117a 100644
--- a/drivers/net/ethernet/intel/ice/ice_flex_pipe.c
+++ b/drivers/net/ethernet/intel/ice/ice_flex_pipe.c
@@ -235,9 +235,8 @@ static bool ice_bits_max_set(const u8 *mask, u16 size, u16 max)
* dc == NULL --> dc mask is all 0's (no don't care bits)
* nm == NULL --> nm mask is all 0's (no never match bits)
*/
-static int
-ice_set_key(u8 *key, u16 size, u8 *val, u8 *upd, u8 *dc, u8 *nm, u16 off,
- u16 len)
+int ice_set_key(u8 *key, u16 size, u8 *val, u8 *upd, u8 *dc, u8 *nm, u16 off,
+ u16 len)
{
u16 half_size;
u16 i;
diff --git a/drivers/net/ethernet/intel/ice/ice_flex_pipe.h b/drivers/net/ethernet/intel/ice/ice_flex_pipe.h
index ee5d9f9c9d53..edb98afe200b 100644
--- a/drivers/net/ethernet/intel/ice/ice_flex_pipe.h
+++ b/drivers/net/ethernet/intel/ice/ice_flex_pipe.h
@@ -8,6 +8,8 @@
#define ICE_FDIR_REG_SET_SIZE 4
+int ice_set_key(u8 *key, u16 size, u8 *val, u8 *upd, u8 *dc, u8 *nm, u16 off,
+ u16 len);
int
ice_acquire_change_lock(struct ice_hw *hw, enum ice_aq_res_access_type access);
void ice_release_change_lock(struct ice_hw *hw);
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.c b/drivers/net/ethernet/intel/ice/ice_flow.c
index e42367853253..192569ff1d6c 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.c
+++ b/drivers/net/ethernet/intel/ice/ice_flow.c
@@ -1589,22 +1589,168 @@ ice_flow_find_prof_id(struct ice_hw *hw, enum ice_block blk, u64 prof_id)
return NULL;
}
+/**
+ * ice_flow_get_hw_prof - return the HW profile for a specific profile ID handle
+ * @hw: pointer to the HW struct
+ * @blk: classification stage
+ * @prof_id: the profile ID handle
+ * @hw_prof_id: pointer to variable to return the HW profile ID
+ *
+ * Return: 0 on success, negative on failure
+ */
+static int ice_flow_get_hw_prof(struct ice_hw *hw, enum ice_block blk,
+ u64 prof_id, u8 *hw_prof_id)
+{
+ struct ice_prof_map *map;
+ int err = -ENOENT;
+
+ mutex_lock(&hw->blk[blk].es.prof_map_lock);
+
+ map = ice_search_prof_id(hw, blk, prof_id);
+ if (map) {
+ *hw_prof_id = map->prof_id;
+ err = 0;
+ }
+
+ mutex_unlock(&hw->blk[blk].es.prof_map_lock);
+
+ return err;
+}
+
+/**
+ * ice_flow_acl_is_prof_in_use - verify if the profile is associated to any PF
+ * @buf: ACL profile buffer
+ *
+ * Return: true if at least one PF is associated to the given profile
+ */
+static bool
+ice_flow_acl_is_prof_in_use(const struct ice_aqc_acl_prof_generic_frmt *buf)
+{
+ u8 first = buf->pf_scenario_num[0];
+
+ /* If all PF's associated scenarios are all 0 or all
+ * ICE_ACL_INVALID_SCEN for the given profile, then the profile has not
+ * been configured yet.
+ */
+
+ if (first != 0 && first != ICE_ACL_INVALID_SCEN)
+ return true;
+
+ for (int i = 1; i < ICE_AQC_ACL_PROF_PF_SCEN_NUM_ELEMS; i++) {
+ if (buf->pf_scenario_num[i] != first)
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * ice_flow_acl_is_cntr_act - check if flow action is a counter action
+ * @type: action type
+ *
+ * Return: true if counter action, false otherwise
+ */
+static bool ice_flow_acl_is_cntr_act(enum ice_flow_action_type type)
+{
+ return type == ICE_FLOW_ACT_CNTR_PKT ||
+ type == ICE_FLOW_ACT_CNTR_BYTES ||
+ type == ICE_FLOW_ACT_CNTR_PKT_BYTES;
+}
+
+/**
+ * ice_flow_acl_free_act_cntr - Free the ACL rule's actions
+ * @hw: pointer to the hardware structure
+ * @acts: array of actions to be performed on a match
+ * @acts_cnt: number of actions
+ *
+ * Return: 0 on success, negative on failure
+ */
+static int ice_flow_acl_free_act_cntr(struct ice_hw *hw,
+ struct ice_flow_action *acts, u8 acts_cnt)
+{
+ for (int i = 0; i < acts_cnt; i++) {
+ struct ice_acl_cntrs cntrs = { 0 };
+ int err;
+
+ if (!ice_flow_acl_is_cntr_act(acts[i].type))
+ continue;
+
+ /* amount is unused in the dealloc path but the common
+ * parameter check routine wants a value set, as zero
+ * is invalid for the check. Just set it.
+ */
+ cntrs.amount = 1;
+ cntrs.bank = 0; /* Only bank0 for the moment */
+ cntrs.first_cntr = le16_to_cpu(acts[i].data.acl_act.value);
+ cntrs.last_cntr = le16_to_cpu(acts[i].data.acl_act.value);
+
+ if (acts[i].type == ICE_FLOW_ACT_CNTR_PKT_BYTES)
+ cntrs.type = ICE_AQC_ACL_CNT_TYPE_DUAL;
+ else
+ cntrs.type = ICE_AQC_ACL_CNT_TYPE_SINGLE;
+
+ err = ice_aq_dealloc_acl_cntrs(hw, &cntrs, NULL);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+/**
+ * ice_flow_acl_disassoc_scen - Disassociate the scenario from the profile
+ * @hw: pointer to the hardware structure
+ * @prof: pointer to flow profile
+ *
+ * Disassociate the scenario from the profile for the PF of the VSI.
+ *
+ * Return: 0 on success, negative on failure
+ */
+static int ice_flow_acl_disassoc_scen(struct ice_hw *hw,
+ struct ice_flow_prof *prof)
+{
+ struct ice_aqc_acl_prof_generic_frmt buf = {};
+ int err = 0;
+ u8 prof_id;
+
+ err = ice_flow_get_hw_prof(hw, ICE_BLK_ACL, prof->id, &prof_id);
+ if (err)
+ return err;
+
+ err = ice_query_acl_prof(hw, prof_id, &buf, NULL);
+ if (err)
+ return err;
+
+ /* Clear scenario for this PF */
+ buf.pf_scenario_num[hw->pf_id] = ICE_ACL_INVALID_SCEN;
+ return ice_prgm_acl_prof_xtrct(hw, prof_id, &buf, NULL);
+}
+
/**
* ice_flow_rem_entry_sync - Remove a flow entry
* @hw: pointer to the HW struct
* @blk: classification stage
* @entry: flow entry to be removed
+ *
+ * Return: 0 on success, negative on failure
*/
-static int
-ice_flow_rem_entry_sync(struct ice_hw *hw, enum ice_block __always_unused blk,
- struct ice_flow_entry *entry)
+static int ice_flow_rem_entry_sync(struct ice_hw *hw, enum ice_block blk,
+ struct ice_flow_entry *entry)
{
if (!entry)
return -EINVAL;
+ if (blk == ICE_BLK_ACL) {
+ if (entry->acts_cnt && entry->acts)
+ ice_flow_acl_free_act_cntr(hw, entry->acts,
+ entry->acts_cnt);
+ }
+
list_del(&entry->l_entry);
- devm_kfree(ice_hw_to_dev(hw), entry->entry);
+ kfree(entry->entry);
+ kfree(entry->range_buf);
+ kfree(entry->acts);
devm_kfree(ice_hw_to_dev(hw), entry);
return 0;
@@ -1729,6 +1875,13 @@ ice_flow_rem_prof_sync(struct ice_hw *hw, enum ice_block blk,
mutex_unlock(&prof->entries_lock);
}
+ if (blk == ICE_BLK_ACL) {
+ /* Disassociate the scenario from the profile for the PF */
+ status = ice_flow_acl_disassoc_scen(hw, prof);
+ if (status)
+ return status;
+ }
+
/* Remove all hardware profiles associated with this flow profile */
status = ice_rem_prof(hw, blk, prof->id);
if (!status) {
@@ -1741,6 +1894,101 @@ ice_flow_rem_prof_sync(struct ice_hw *hw, enum ice_block blk,
return status;
}
+/**
+ * ice_flow_acl_set_xtrct_seq_fld - Populate xtrct seq for single field
+ * @buf: Destination buffer function writes partial xtrct sequence to
+ * @info: Info about field
+ *
+ * Return: 0 on success, negative on failure
+ */
+static int
+ice_flow_acl_set_xtrct_seq_fld(struct ice_aqc_acl_prof_generic_frmt *buf,
+ struct ice_flow_fld_info *info)
+{
+ u16 src, dst;
+
+ src = info->xtrct.idx * ICE_FLOW_FV_EXTRACT_SZ +
+ info->xtrct.disp / BITS_PER_BYTE;
+ if (src > U8_MAX)
+ return -ERANGE;
+
+ dst = info->entry.val;
+ for (int i = 0; i < info->entry.last; i++)
+ /* HW stores field vector words in LE, convert words back to BE
+ * so constructed entries will end up in network order
+ */
+ buf->byte_selection[dst++] = src++ ^ 1;
+
+ return 0;
+}
+
+/**
+ * ice_flow_acl_set_xtrct_seq - Program ACL extraction sequence
+ * @hw: pointer to the hardware structure
+ * @prof: pointer to flow profile
+ *
+ * Return: 0 on success, negative on failure
+ */
+static int ice_flow_acl_set_xtrct_seq(struct ice_hw *hw,
+ struct ice_flow_prof *prof)
+{
+ struct ice_aqc_acl_prof_generic_frmt buf = {};
+ struct ice_flow_fld_info *info;
+ u8 prof_id = 0;
+ int err;
+
+ err = ice_flow_get_hw_prof(hw, ICE_BLK_ACL, prof->id, &prof_id);
+ if (err)
+ return err;
+
+ err = ice_query_acl_prof(hw, prof_id, &buf, NULL);
+ if (err)
+ return err;
+
+ if (!ice_flow_acl_is_prof_in_use(&buf)) {
+ /* Program the profile dependent configuration. This is done
+ * only once regardless of the number of PFs using that profile
+ */
+ memset(&buf, 0, sizeof(buf));
+
+ for (int i = 0; i < prof->segs_cnt; i++) {
+ struct ice_flow_seg_info *seg = &prof->segs[i];
+ u16 j;
+
+ for_each_set_bit(j, (unsigned long *)&seg->match,
+ ICE_FLOW_FIELD_IDX_MAX) {
+ info = &seg->fields[j];
+
+ if (info->type == ICE_FLOW_FLD_TYPE_RANGE) {
+ buf.word_selection[info->entry.val] =
+ info->xtrct.idx;
+ continue;
+ }
+
+ err = ice_flow_acl_set_xtrct_seq_fld(&buf,
+ info);
+ if (err)
+ return err;
+ }
+
+ for (j = 0; j < seg->raws_cnt; j++) {
+ info = &seg->raws[j].info;
+ err = ice_flow_acl_set_xtrct_seq_fld(&buf,
+ info);
+ if (err)
+ return err;
+ }
+ }
+
+ memset(&buf.pf_scenario_num[0], ICE_ACL_INVALID_SCEN,
+ ICE_AQC_ACL_PROF_PF_SCEN_NUM_ELEMS);
+ }
+
+ /* Update the current PF */
+ buf.pf_scenario_num[hw->pf_id] = (u8)prof->cfg.scen->id;
+ return ice_prgm_acl_prof_xtrct(hw, prof_id, &buf, NULL);
+}
+
/**
* ice_flow_assoc_prof - associate a VSI with a flow profile
* @hw: pointer to the hardware structure
@@ -1758,6 +2006,12 @@ ice_flow_assoc_prof(struct ice_hw *hw, enum ice_block blk,
int status = 0;
if (!test_bit(vsi_handle, prof->vsis)) {
+ if (blk == ICE_BLK_ACL) {
+ status = ice_flow_acl_set_xtrct_seq(hw, prof);
+ if (status)
+ return status;
+ }
+
status = ice_add_prof_id_flow(hw, blk,
ice_get_hw_vsi_num(hw,
vsi_handle),
@@ -1957,6 +2211,349 @@ int ice_flow_rem_prof(struct ice_hw *hw, enum ice_block blk, u64 prof_id)
return status;
}
+/**
+ * ice_flow_acl_check_actions - Checks the ACL rule's actions
+ * @hw: pointer to the hardware structure
+ * @acts: array of actions to be performed on a match
+ * @acts_cnt: number of actions
+ * @cnt_alloc: indicates if an ACL counter has been allocated.
+ *
+ * Return: 0 on success, negative on failure
+ */
+static int ice_flow_acl_check_actions(struct ice_hw *hw,
+ struct ice_flow_action *acts, u8 acts_cnt,
+ bool *cnt_alloc)
+{
+ DECLARE_BITMAP(dup_check, ICE_AQC_TBL_MAX_ACTION_PAIRS * 2);
+
+ bitmap_zero(dup_check, ICE_AQC_TBL_MAX_ACTION_PAIRS * 2);
+ *cnt_alloc = false;
+
+ if (acts_cnt > ICE_FLOW_ACL_MAX_NUM_ACT)
+ return -ERANGE;
+
+ for (int i = 0; i < acts_cnt; i++) {
+ if (acts[i].type != ICE_FLOW_ACT_NOP &&
+ acts[i].type != ICE_FLOW_ACT_DROP &&
+ acts[i].type != ICE_FLOW_ACT_FWD_QUEUE &&
+ !ice_flow_acl_is_cntr_act(acts[i].type))
+ return -EINVAL;
+
+ /* If the caller want to add two actions of the same type, then
+ * it is considered invalid configuration.
+ */
+ if (test_and_set_bit(acts[i].type, dup_check))
+ return -EINVAL;
+ }
+
+ /* Checks if ACL counters are needed. */
+ for (int i = 0; i < acts_cnt; i++) {
+ if (ice_flow_acl_is_cntr_act(acts[i].type)) {
+ struct ice_acl_cntrs cntrs = { 0 };
+ int err;
+
+ cntrs.amount = 1;
+ cntrs.bank = 0; /* Only bank0 for the moment */
+
+ if (acts[i].type == ICE_FLOW_ACT_CNTR_PKT_BYTES)
+ cntrs.type = ICE_AQC_ACL_CNT_TYPE_DUAL;
+ else
+ cntrs.type = ICE_AQC_ACL_CNT_TYPE_SINGLE;
+
+ err = ice_aq_alloc_acl_cntrs(hw, &cntrs, NULL);
+ if (err)
+ return err;
+ /* Counter index within the bank */
+ acts[i].data.acl_act.value =
+ cpu_to_le16(cntrs.first_cntr);
+ *cnt_alloc = true;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * ice_flow_acl_frmt_entry_range - Format an ACL range checker for a given field
+ * @fld: number of the given field
+ * @info: info about field
+ * @range_buf: range checker configuration buffer
+ * @data: pointer to a data buffer containing flow entry's match values/masks
+ * @range: Input/output param indicating which range checkers are being used
+ */
+static void
+ice_flow_acl_frmt_entry_range(u16 fld, struct ice_flow_fld_info *info,
+ struct ice_aqc_acl_profile_ranges *range_buf,
+ u8 *data, u8 *range)
+{
+ u16 new_mask, offset_val;
+
+ /* If not specified, default mask is all bits in field */
+ memcpy(&offset_val, data + info->src.mask, sizeof(offset_val));
+ new_mask = (info->src.mask == ICE_FLOW_FLD_OFF_INVAL ?
+ BIT(ice_flds_info[fld].size) - 1 : offset_val) <<
+ info->xtrct.disp;
+
+ /* If the mask is 0, then we don't need to worry about this input
+ * range checker value.
+ */
+ if (new_mask) {
+ u16 new_high, new_low;
+
+ memcpy(&offset_val, data + info->src.last, sizeof(offset_val));
+ new_high = offset_val << info->xtrct.disp;
+
+ memcpy(&offset_val, data + info->src.val, sizeof(offset_val));
+ new_low = offset_val << info->xtrct.disp;
+
+ u8 range_idx = info->entry.val;
+
+ range_buf->checker_cfg[range_idx].low_boundary =
+ cpu_to_be16(new_low);
+ range_buf->checker_cfg[range_idx].high_boundary =
+ cpu_to_be16(new_high);
+ range_buf->checker_cfg[range_idx].mask = cpu_to_be16(new_mask);
+
+ /* Indicate which range checker is being used */
+ *range |= BIT(range_idx);
+ }
+}
+
+/**
+ * ice_flow_acl_frmt_entry_fld - Partially format ACL entry for a given field
+ * @fld: number of the given field
+ * @info: info about the field
+ * @buf: buffer containing the entry
+ * @dontcare: buffer containing don't care mask for entry
+ * @data: pointer to a data buffer containing flow entry's match values/masks
+ */
+static void ice_flow_acl_frmt_entry_fld(u16 fld, struct ice_flow_fld_info *info,
+ u8 *buf, u8 *dontcare, u8 *data)
+{
+ u16 dst, src, mask, end_disp, tmp_s = 0, tmp_m = 0;
+ bool use_mask = false;
+ u8 disp;
+
+ src = info->src.val;
+ mask = info->src.mask;
+ dst = info->entry.val - ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX;
+ disp = info->xtrct.disp % BITS_PER_BYTE;
+
+ if (mask != ICE_FLOW_FLD_OFF_INVAL)
+ use_mask = true;
+
+ for (u16 i = 0; i < info->entry.last; i++, dst++) {
+ /* Add overflow bits from previous byte */
+ buf[dst] = (tmp_s & 0xff00) >> 8;
+
+ /* If mask is not valid, tmp_m is always zero, so just setting
+ * dontcare to 0 (no masked bits). If mask is valid, pulls in
+ * overflow bits of mask from prev byte
+ */
+ dontcare[dst] = (tmp_m & 0xff00) >> 8;
+
+ /* If there is displacement, last byte will only contain
+ * displaced data, but there is no more data to read from user
+ * buffer, so skip so as not to potentially read beyond end of
+ * user buffer
+ */
+ if (!disp || i < info->entry.last - 1) {
+ /* Store shifted data to use in next byte */
+ tmp_s = data[src++] << disp;
+
+ /* Add current (shifted) byte */
+ buf[dst] |= tmp_s & 0xff;
+
+ /* Handle mask if valid */
+ if (use_mask) {
+ tmp_m = (~data[mask++] & 0xff) << disp;
+ dontcare[dst] |= tmp_m & 0xff;
+ }
+ }
+ }
+
+ /* Fill in don't care bits at beginning of field */
+ if (disp) {
+ dst = info->entry.val - ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX;
+ for (int i = 0; i < disp; i++)
+ dontcare[dst] |= BIT(i);
+ }
+
+ end_disp = (disp + ice_flds_info[fld].size) % BITS_PER_BYTE;
+
+ /* Fill in don't care bits at end of field */
+ if (end_disp) {
+ dst = info->entry.val - ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX +
+ info->entry.last - 1;
+ for (int i = end_disp; i < BITS_PER_BYTE; i++)
+ dontcare[dst] |= BIT(i);
+ }
+}
+
+/**
+ * ice_flow_acl_frmt_entry - Format ACL entry
+ * @hw: pointer to the hardware structure
+ * @prof: pointer to flow profile
+ * @e: pointer to the flow entry
+ * @data: pointer to a data buffer containing flow entry's match values/masks
+ * @acts: array of actions to be performed on a match
+ * @acts_cnt: number of actions
+ *
+ * Formats the key (and key_inverse) to be matched from the data passed in,
+ * along with data from the flow profile. This key/key_inverse pair makes up
+ * the 'entry' for an ACL flow entry.
+ *
+ * Return: 0 on success, negative on failure
+ */
+static int ice_flow_acl_frmt_entry(struct ice_hw *hw,
+ struct ice_flow_prof *prof,
+ struct ice_flow_entry *e, u8 *data,
+ struct ice_flow_action *acts, u8 acts_cnt)
+{
+ u8 *buf = NULL, *dontcare = NULL, *key = NULL, range = 0, dir_flag_msk,
+ prof_id;
+ struct ice_aqc_acl_profile_ranges *range_buf = NULL;
+ bool cnt_alloc;
+ u16 buf_sz;
+ int err;
+
+ err = ice_flow_get_hw_prof(hw, ICE_BLK_ACL, prof->id, &prof_id);
+ if (err)
+ return err;
+
+ /* Format the result action */
+
+ err = ice_flow_acl_check_actions(hw, acts, acts_cnt, &cnt_alloc);
+ if (err)
+ return err;
+
+ e->acts = kmemdup(acts, acts_cnt * sizeof(*acts), GFP_KERNEL);
+ if (!e->acts) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ e->acts_cnt = acts_cnt;
+
+ /* Format the matching data */
+ buf_sz = prof->cfg.scen->width;
+ buf = kzalloc_objs(*buf, buf_sz);
+ if (!buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ dontcare = kzalloc_objs(*dontcare, buf_sz);
+ if (!dontcare) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* 'key' buffer will store both key and key_inverse, so must be twice
+ * size of buf
+ */
+ key = kzalloc_objs(*key, buf_sz * 2);
+ if (!key) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ range_buf = kzalloc_obj(*range_buf);
+ if (!range_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* Set don't care mask to all 1's to start, will zero out used bytes */
+ memset(dontcare, 0xff, buf_sz);
+
+ for (int i = 0; i < prof->segs_cnt; i++) {
+ struct ice_flow_seg_info *seg = &prof->segs[i];
+ u8 j;
+
+ for_each_set_bit(j, (unsigned long *)&seg->match,
+ ICE_FLOW_FIELD_IDX_MAX) {
+ struct ice_flow_fld_info *info = &seg->fields[j];
+
+ if (info->type == ICE_FLOW_FLD_TYPE_RANGE)
+ ice_flow_acl_frmt_entry_range(j, info,
+ range_buf, data,
+ &range);
+ else
+ ice_flow_acl_frmt_entry_fld(j, info, buf,
+ dontcare, data);
+ }
+
+ for (j = 0; j < seg->raws_cnt; j++) {
+ struct ice_flow_fld_info *info = &seg->raws[j].info;
+ u16 dst, src, mask, k;
+ bool use_mask = false;
+
+ src = info->src.val;
+ dst = info->entry.val -
+ ICE_AQC_ACL_PROF_BYTE_SEL_START_IDX;
+ mask = info->src.mask;
+
+ if (mask != ICE_FLOW_FLD_OFF_INVAL)
+ use_mask = true;
+
+ for (k = 0; k < info->entry.last; k++, dst++) {
+ buf[dst] = data[src++];
+ if (use_mask)
+ dontcare[dst] = ~data[mask++];
+ else
+ dontcare[dst] = 0;
+ }
+ }
+ }
+
+ buf[prof->cfg.scen->pid_idx] = (u8)prof_id;
+ dontcare[prof->cfg.scen->pid_idx] = 0;
+
+ /* Format the buffer for direction flags */
+ dir_flag_msk = BIT(ICE_FLG_PKT_DIR);
+
+ if (prof->dir == ICE_FLOW_RX)
+ buf[prof->cfg.scen->pkt_dir_idx] = dir_flag_msk;
+
+ if (range) {
+ buf[prof->cfg.scen->rng_chk_idx] = range;
+ /* Mark any unused range checkers as don't care */
+ dontcare[prof->cfg.scen->rng_chk_idx] = ~range;
+ e->range_buf = range_buf;
+ } else {
+ kfree(range_buf);
+ range_buf = NULL;
+ }
+
+ err = ice_set_key(key, buf_sz * 2, buf, NULL, dontcare, NULL, 0,
+ buf_sz);
+ if (err)
+ goto out;
+
+ e->entry = key;
+ e->entry_sz = buf_sz * 2;
+
+out:
+ kfree(buf);
+ kfree(dontcare);
+
+ if (err) {
+ kfree(key);
+
+ kfree(range_buf);
+ e->range_buf = NULL;
+
+ kfree(e->acts);
+ e->acts = NULL;
+ e->acts_cnt = 0;
+
+ if (cnt_alloc)
+ ice_flow_acl_free_act_cntr(hw, acts, acts_cnt);
+ }
+
+ return err;
+}
/**
* ice_flow_add_entry - Add a flow entry
* @hw: pointer to the HW struct
@@ -1966,17 +2563,23 @@ int ice_flow_rem_prof(struct ice_hw *hw, enum ice_block blk, u64 prof_id)
* @vsi_handle: software VSI handle for the flow entry
* @prio: priority of the flow entry
* @data: pointer to a data buffer containing flow entry's match values/masks
+ * @acts: array of actions to be performed on a match
+ * @acts_cnt: number of actions
* @entry_h: pointer to buffer that receives the new flow entry's handle
*/
-int
-ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
- u64 entry_id, u16 vsi_handle, enum ice_flow_priority prio,
- void *data, u64 *entry_h)
+int ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
+ u64 entry_id, u16 vsi_handle,
+ enum ice_flow_priority prio, void *data,
+ struct ice_flow_action *acts, u8 acts_cnt, u64 *entry_h)
{
struct ice_flow_entry *e = NULL;
struct ice_flow_prof *prof;
int status;
+ /* ACL entries must indicate an action */
+ if (blk == ICE_BLK_ACL && (!acts || !acts_cnt))
+ return -EINVAL;
+
/* No flow entry data is expected for RSS */
if (!entry_h || (!data && blk != ICE_BLK_RSS))
return -EINVAL;
@@ -2004,6 +2607,7 @@ ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
if (status)
goto out;
+ INIT_LIST_HEAD(&e->l_entry);
e->id = entry_id;
e->vsi_handle = vsi_handle;
e->prof = prof;
@@ -2013,20 +2617,32 @@ ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
case ICE_BLK_FD:
case ICE_BLK_RSS:
break;
+ case ICE_BLK_ACL:
+ /* ACL will handle the entry management */
+ status = ice_flow_acl_frmt_entry(hw, prof, e, (u8 *)data, acts,
+ acts_cnt);
+ if (status)
+ goto out;
+ break;
default:
status = -EOPNOTSUPP;
goto out;
}
- mutex_lock(&prof->entries_lock);
- list_add(&e->l_entry, &prof->entries);
- mutex_unlock(&prof->entries_lock);
+ if (blk != ICE_BLK_ACL) {
+ /* ACL will handle the entry management */
+ mutex_lock(&prof->entries_lock);
+ list_add(&e->l_entry, &prof->entries);
+ mutex_unlock(&prof->entries_lock);
+ }
*entry_h = ICE_FLOW_ENTRY_HNDL(e);
out:
if (status && e) {
- devm_kfree(ice_hw_to_dev(hw), e->entry);
+ kfree(e->entry);
+ kfree(e->range_buf);
+ kfree(e->acts);
devm_kfree(ice_hw_to_dev(hw), e);
}
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.h b/drivers/net/ethernet/intel/ice/ice_flow.h
index 23e366ee1f79..ddc2bf03af30 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.h
+++ b/drivers/net/ethernet/intel/ice/ice_flow.h
@@ -452,17 +452,23 @@ struct ice_flow_seg_info {
struct ice_flow_seg_fld_raw raws[ICE_FLOW_SEG_RAW_FLD_MAX];
};
+#define ICE_FLOW_ACL_MAX_NUM_ACT 2
/* This structure describes a flow entry, and is tracked only in this file */
struct ice_flow_entry {
struct list_head l_entry;
u64 id;
struct ice_flow_prof *prof;
+ /* Action list */
+ struct ice_flow_action *acts;
/* Flow entry's content */
void *entry;
+ /* Range buffer (For ACL only) */
+ struct ice_aqc_acl_profile_ranges *range_buf;
enum ice_flow_priority priority;
u16 vsi_handle;
u16 entry_sz;
+ u8 acts_cnt;
};
#define ICE_FLOW_ENTRY_HNDL(e) ((u64)(uintptr_t)e)
@@ -535,7 +541,8 @@ ice_flow_set_parser_prof(struct ice_hw *hw, u16 dest_vsi, u16 fdir_vsi,
int
ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
u64 entry_id, u16 vsi, enum ice_flow_priority prio,
- void *data, u64 *entry_h);
+ void *data, struct ice_flow_action *acts, u8 acts_cnt,
+ u64 *entry_h);
int ice_flow_rem_entry(struct ice_hw *hw, enum ice_block blk, u64 entry_h);
void
ice_flow_set_fld(struct ice_flow_seg_info *seg, enum ice_flow_field fld,
diff --git a/drivers/net/ethernet/intel/ice/ice_lan_tx_rx.h b/drivers/net/ethernet/intel/ice/ice_lan_tx_rx.h
index 185672c7e17d..7010afb787c3 100644
--- a/drivers/net/ethernet/intel/ice/ice_lan_tx_rx.h
+++ b/drivers/net/ethernet/intel/ice/ice_lan_tx_rx.h
@@ -312,6 +312,8 @@ enum ice_flex_mdid_pkt_flags {
enum ice_flex_rx_mdid {
ICE_RX_MDID_FLOW_ID_LOWER = 5,
ICE_RX_MDID_FLOW_ID_HIGH,
+ ICE_MDID_RX_PKT_DROP = 8,
+ ICE_MDID_RX_DST_Q = 12,
ICE_RX_MDID_SRC_VSI = 19,
ICE_RX_MDID_HASH_LOW = 56,
ICE_RX_MDID_HASH_HIGH,
@@ -320,6 +322,7 @@ enum ice_flex_rx_mdid {
/* Rx/Tx Flag64 packet flag bits */
enum ice_flg64_bits {
ICE_FLG_PKT_DSI = 0,
+ ICE_FLG_PKT_DIR = 4,
ICE_FLG_EVLAN_x8100 = 14,
ICE_FLG_EVLAN_x9100,
ICE_FLG_VLAN_x8100,
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index df58bd72301e..3a10b2c2a44a 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -8672,7 +8672,7 @@ static int ice_add_vsi_to_fdir(struct ice_pf *pf, struct ice_vsi *vsi)
prof->prof_id[tun],
prof->vsi_h[0], vsi->idx,
prio, prof->fdir_seg[tun],
- &entry_h);
+ NULL, 0, &entry_h);
if (status) {
dev_err(dev, "channel VSI idx %d, not able to add to group %d\n",
vsi->idx, flow);
diff --git a/drivers/net/ethernet/intel/ice/virt/fdir.c b/drivers/net/ethernet/intel/ice/virt/fdir.c
index eca9eda04f31..38e68d3d030c 100644
--- a/drivers/net/ethernet/intel/ice/virt/fdir.c
+++ b/drivers/net/ethernet/intel/ice/virt/fdir.c
@@ -688,7 +688,7 @@ ice_vc_fdir_write_flow_prof(struct ice_vf *vf, enum ice_fltr_ptype flow,
ret = ice_flow_add_entry(hw, ICE_BLK_FD, prof->id, vf_vsi->idx,
vf_vsi->idx, ICE_FLOW_PRIO_NORMAL,
- seg, &entry1_h);
+ seg, NULL, 0, &entry1_h);
if (ret) {
dev_dbg(dev, "Could not add flow 0x%x VSI entry for VF %d\n",
flow, vf->vf_id);
@@ -697,7 +697,7 @@ ice_vc_fdir_write_flow_prof(struct ice_vf *vf, enum ice_fltr_ptype flow,
ret = ice_flow_add_entry(hw, ICE_BLK_FD, prof->id, vf_vsi->idx,
ctrl_vsi->idx, ICE_FLOW_PRIO_NORMAL,
- seg, &entry2_h);
+ seg, NULL, 0, &entry2_h);
if (ret) {
dev_dbg(dev,
"Could not add flow 0x%x Ctrl VSI entry for VF %d\n",
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 08/12] ice: create ACL entry
2026-09-17 18:39 ` [PATCH net-next v2 08/12] ice: create ACL entry Tony Nguyen
@ 2026-09-21 19:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:57 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
lukasz.czapnik, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, sx.rinitha
Thank you for your contribution! Sashiko AI review found 11 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 8 · Low: 3
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] ice_ntuple_set_input_set() strips the wrong ethtool extension
bit for the ACL block: `else if (blk == ICE_BLK_ACL) flow_mask =…
- [Medium] ice_acl_comp_rules() requires the raw IP/port values to be
byte-identical in addition to the masks, so two ACL rules whose…
- [Medium] In ice_flow_acl_frmt_entry_range() the 2-byte read of the
field mask out of the caller-supplied match buffer is performed…
- [Medium] ice_flow_acl_check_actions() allocates one hardware ACL
counter per counter-type action in a loop and, on failure of a later…
- [Medium] ice_flow_acl_set_xtrct_seq() and ice_flow_acl_disassoc_scen()
perform a query-modify-program transaction on the ACL profile…
- [Medium] ice_flow_add_entry() releases hw->fl_profs_locks[blk]
immediately after ice_flow_assoc_prof() but keeps the raw `prof`…
- [Medium] ice_flow_acl_frmt_entry() writes the packet-direction flag
value into the ACL key (buf[prof->cfg.scen->pkt_dir_idx] =…
- [Medium] net/ice: TOCTOU race allows programming duplicate ACL rules
- [Low] At this commit ice_acl_add_rule_ethtool() formats an ACL entry
but never programs it into the ACL TCAM, discards the handle returned…
- [Low] At this commit the ACL flow entry created by ice_flow_add_entry()
is unreachable: for ICE_BLK_ACL it is deliberately not linked onto…
- [Low] ice_flow_rem_entry_sync() ignores the return value of
ice_flow_acl_free_act_cntr() and then frees entry->acts, discarding…
--
ice: create ACL entry
This adds the ACL entry creation path: the ethtool ACL add helper now
builds an action (drop or forward to queue), formats a TCAM key from the
ethtool flow spec, and programs the ACL profile extraction sequence.
ice_flow_add_entry() gains an action array and a new ICE_BLK_ACL case.
A few questions below.
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.c b/drivers/net/ethernet/intel/ice/ice_acl_main.c
> index db8137e6f8706..6313d511398a2 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_main.c
[ ... ]
> @@ -207,6 +210,111 @@ static int ice_acl_prof_add_ethtool(struct ice_pf *pf,
[ ... ]
> +/**
> + * ice_acl_comp_rules - compare two ACL filters
> + * @a: first ACL filter
> + * @b: second ACL filter
> + *
> + * Return: true if a and b values and masks are identical, false otherwise
> + */
> +static bool
> +ice_acl_comp_rules(struct ice_ntuple_fltr *a, struct ice_ntuple_fltr *b)
> +{
> + bool base_equal;
> +
> + if (a->flow_type != b->flow_type)
> + return false;
> +
> + base_equal = a->ip.v4.dst_ip == b->ip.v4.dst_ip &&
> + a->ip.v4.src_ip == b->ip.v4.src_ip &&
> + a->mask.v4.dst_ip == b->mask.v4.dst_ip &&
> + a->mask.v4.src_ip == b->mask.v4.src_ip;
[Severity: Medium]
Should the value comparison here be done after applying the mask?
ice_acl_comp_rules() requires the raw values to be byte-identical, but
ice_flow_acl_frmt_entry_fld() turns every bit cleared in the mask into a
don't-care bit:
if (use_mask) {
tmp_m = (~data[mask++] & 0xff) << disp;
dontcare[dst] |= tmp_m & 0xff;
}
So these two rules differ only in mask-cleared bits and produce a
bit-identical TCAM key, yet ice_acl_is_dup_fltr() does not consider them
duplicates:
ethtool -N ethX flow-type tcp4 dst-ip 10.0.0.1 m 0.0.0.255 action 1 loc 1
ethtool -N ethX flow-type tcp4 dst-ip 10.0.0.2 m 0.0.0.255 action 1 loc 2
Later in the series, ice_flow_acl_find_scen_entry_cond() then matches the
existing scenario entry with memcmp(p->entry, e->entry, p->entry_sz) and
re-labels it with exist->id = e->id, so location 1 no longer has an entry
of its own. A subsequent delete of location 1 goes through
ice_del_acl_ethtool() -> ice_flow_find_entry(hw, ICE_BLK_ACL, 1), which
finds nothing, so that filter can no longer be removed. Is that the
intended behaviour, and does the kerneldoc claim of "identical match
criteria" hold given the don't-care handling above?
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index 0c088e6261438..f67b6e1c0ffbf 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
[ ... ]
> @@ -2037,28 +2037,36 @@ ice_update_ring_dest_vsi(struct ice_vsi *vsi, u16 *dest_vsi, u32 *ring)
> if (!vsi || !fsp || !input)
> return -EINVAL;
>
> + if (blk == ICE_BLK_FD)
> + flow_mask = FLOW_EXT;
> + else if (blk == ICE_BLK_ACL)
> + flow_mask = FLOW_MAC_EXT;
> + else
> + return -EINVAL;
> +
[Severity: Medium]
Is FLOW_MAC_EXT the right bit to strip for the ACL block?
ice_add_ntuple_ethtool() rejects FLOW_MAC_EXT before the ACL path is ever
reached:
if (fsp->flow_type & FLOW_MAC_EXT)
return -EINVAL;
so for ICE_BLK_ACL the mask can never clear a bit that is set, while
FLOW_EXT stays in flow_type.
> @@ -2085,7 +2094,7 @@ ice_ntuple_set_input_set(struct ice_vsi *vsi, struct ethtool_rx_flow_spec *fsp,
>
> input->fltr_id = fsp->location;
> input->q_index = q_index;
> - flow_type = fsp->flow_type & ~FLOW_EXT;
> + flow_type = fsp->flow_type & ~flow_mask;
Can an ACL add with FLOW_EXT set still succeed after this change? With
something like:
ethtool -N ethX flow-type tcp4 dst-ip 10.0.0.0 m 0.0.0.255 vlan 5 action 1
ethtool sets FLOW_EXT, ice_is_acl_filter() (which uses & ~FLOW_EXT) returns
true because the IP mask is partial, ice_acl_prof_add_ethtool() succeeds
and records hw->acl_prof[fltr_type]->seg, and then flow_type here still
carries FLOW_EXT, so ice_ethtool_flow_to_fltr() and the closing switch in
this function hit:
default:
/* not doing un-parsed flow types */
return -EINVAL;
The request is rejected after the profile has already been created. Every
other ACL decode site (ice_is_acl_filter(), ice_acl_prof_add_ethtool(), and
the ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT) call in
ice_acl_add_rule_ethtool()) strips FLOW_EXT instead. Would masking both,
~(FLOW_EXT | FLOW_MAC_EXT), be what is wanted here? Note that simply
stripping FLOW_EXT would silently ignore the VLAN match the user asked
for, so the ACL path may need an explicit decision rather than a different
mask.
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_flow.c b/drivers/net/ethernet/intel/ice/ice_flow.c
> index e42367853253f..192569ff1d6c1 100644
> --- a/drivers/net/ethernet/intel/ice/ice_flow.c
> +++ b/drivers/net/ethernet/intel/ice/ice_flow.c
[ ... ]
> +/**
> + * ice_flow_acl_disassoc_scen - Disassociate the scenario from the profile
> + * @hw: pointer to the hardware structure
> + * @prof: pointer to flow profile
> + *
> + * Disassociate the scenario from the profile for the PF of the VSI.
> + *
> + * Return: 0 on success, negative on failure
> + */
> +static int ice_flow_acl_disassoc_scen(struct ice_hw *hw,
> + struct ice_flow_prof *prof)
> +{
> + struct ice_aqc_acl_prof_generic_frmt buf = {};
> + int err = 0;
> + u8 prof_id;
> +
> + err = ice_flow_get_hw_prof(hw, ICE_BLK_ACL, prof->id, &prof_id);
> + if (err)
> + return err;
> +
> + err = ice_query_acl_prof(hw, prof_id, &buf, NULL);
> + if (err)
> + return err;
> +
> + /* Clear scenario for this PF */
> + buf.pf_scenario_num[hw->pf_id] = ICE_ACL_INVALID_SCEN;
> + return ice_prgm_acl_prof_xtrct(hw, prof_id, &buf, NULL);
> +}
[Severity: Medium]
What serializes this query-modify-program sequence against the sibling PFs
of the same device?
struct ice_aqc_acl_prof_generic_frmt carries
pf_scenario_num[ICE_AQC_ACL_PROF_PF_SCEN_NUM_ELEMS], one byte per physical
function, so this profile entry is device global. Both this function and
ice_flow_acl_set_xtrct_seq() below read the whole entry, change only their
own byte (and, on first use, the byte/word/dword selections and the entire
pf_scenario_num array), and write the whole entry back.
The only lock held on the add side is hw->fl_profs_locks[blk], which is
private to one driver instance. rtnl_lock does not cover the other PF
either, since a reset/rebuild runs from the service task:
ice_reset_subtask()
ice_rebuild(pf, reset_type)
Can two PFs racing here lose an update, leaving another PF's profile
pointing at a wrong or invalid scenario, or resurrecting an association
that PF just cleared? The driver already has a cross-function primitive
for shared table updates in ice_acquire_change_lock() /
ice_acquire_res(ICE_CHANGE_LOCK_RES_ID); should these two transactions use
it?
[ ... ]
> +static int ice_flow_acl_set_xtrct_seq(struct ice_hw *hw,
> + struct ice_flow_prof *prof)
> +{
[ ... ]
> + /* Update the current PF */
> + buf.pf_scenario_num[hw->pf_id] = (u8)prof->cfg.scen->id;
> + return ice_prgm_acl_prof_xtrct(hw, prof_id, &buf, NULL);
> +}
[ ... ]
> +static int ice_flow_acl_check_actions(struct ice_hw *hw,
> + struct ice_flow_action *acts, u8 acts_cnt,
> + bool *cnt_alloc)
> +{
[ ... ]
> + /* Checks if ACL counters are needed. */
> + for (int i = 0; i < acts_cnt; i++) {
> + if (ice_flow_acl_is_cntr_act(acts[i].type)) {
> + struct ice_acl_cntrs cntrs = { 0 };
> + int err;
> +
> + cntrs.amount = 1;
> + cntrs.bank = 0; /* Only bank0 for the moment */
> +
> + if (acts[i].type == ICE_FLOW_ACT_CNTR_PKT_BYTES)
> + cntrs.type = ICE_AQC_ACL_CNT_TYPE_DUAL;
> + else
> + cntrs.type = ICE_AQC_ACL_CNT_TYPE_SINGLE;
> +
> + err = ice_aq_alloc_acl_cntrs(hw, &cntrs, NULL);
> + if (err)
> + return err;
[Severity: Medium]
Does this return leak the ACL counters allocated by earlier iterations?
ICE_FLOW_ACL_MAX_NUM_ACT is 2 and dup_check only rejects two actions of the
same type, so an array such as {ICE_FLOW_ACT_CNTR_PKT,
ICE_FLOW_ACT_CNTR_BYTES} performs two ice_aq_alloc_acl_cntrs() calls. If
the second one fails, the counter allocated for acts[0] is still owned by
hardware when the error is returned.
The caller cannot compensate, because the propagation happens before the
label that has the rollback:
err = ice_flow_acl_check_actions(hw, acts, acts_cnt, &cnt_alloc);
if (err)
return err;
...
out:
...
if (err) {
...
if (cnt_alloc)
ice_flow_acl_free_act_cntr(hw, acts, acts_cnt);
}
Bank 0 only has 4 single or 2 dual counters, and nothing reclaims them
short of a reset. No in-tree caller requests counter actions yet
(ice_acl_main.c builds only drop and forward-to-queue with
ICE_ACL_NUM_ACT == 1), so this is latent today.
> +/**
> + * ice_flow_acl_frmt_entry_range - Format an ACL range checker for a given field
[ ... ]
> +static void
> +ice_flow_acl_frmt_entry_range(u16 fld, struct ice_flow_fld_info *info,
> + struct ice_aqc_acl_profile_ranges *range_buf,
> + u8 *data, u8 *range)
> +{
> + u16 new_mask, offset_val;
> +
> + /* If not specified, default mask is all bits in field */
> + memcpy(&offset_val, data + info->src.mask, sizeof(offset_val));
> + new_mask = (info->src.mask == ICE_FLOW_FLD_OFF_INVAL ?
> + BIT(ice_flds_info[fld].size) - 1 : offset_val) <<
> + info->xtrct.disp;
[Severity: Medium]
Can this read past the end of the match buffer?
The memcpy() runs before the ICE_FLOW_FLD_OFF_INVAL test in the ternary
that follows, so in exactly the "mask not specified" case it performs:
memcpy(&offset_val, data + 0xffff, 2);
ICE_FLOW_FLD_OFF_INVAL is 0xffff, and data is the kzalloc'ed struct
ice_ntuple_fltr passed down from ice_acl_add_rule_ethtool() through
ice_flow_acl_frmt_entry(), so that is a 2-byte read 64 KB past the end of
a small slab object.
The sibling helper added in the same patch guards first:
if (mask != ICE_FLOW_FLD_OFF_INVAL)
use_mask = true;
Would hoisting the sentinel check above the memcpy() here be equivalent?
No in-tree ACL profile builder creates ICE_FLOW_FLD_TYPE_RANGE fields
today (ice_acl_set_ip4_addr_seg() and ice_acl_set_ip4_port_seg() pass
range == false), so this looks latent rather than reachable from userspace.
[ ... ]
> +static int ice_flow_acl_frmt_entry(struct ice_hw *hw,
> + struct ice_flow_prof *prof,
> + struct ice_flow_entry *e, u8 *data,
> + struct ice_flow_action *acts, u8 acts_cnt)
> +{
[ ... ]
> + /* Set don't care mask to all 1's to start, will zero out used bytes */
> + memset(dontcare, 0xff, buf_sz);
[ ... ]
> + buf[prof->cfg.scen->pid_idx] = (u8)prof_id;
> + dontcare[prof->cfg.scen->pid_idx] = 0;
> +
> + /* Format the buffer for direction flags */
> + dir_flag_msk = BIT(ICE_FLG_PKT_DIR);
> +
> + if (prof->dir == ICE_FLOW_RX)
> + buf[prof->cfg.scen->pkt_dir_idx] = dir_flag_msk;
[Severity: Medium]
Should dontcare[prof->cfg.scen->pkt_dir_idx] be cleared here too?
dontcare was pre-filled with 0xff, and pid_idx and rng_chk_idx get their
mask bytes assigned, but pkt_dir_idx does not. The dontcare buffer is
passed to ice_set_key() as the dc argument, and ice_gen_key_word()
wildcards every bit set there:
if (ice_gen_key_word(val[i], upd ? upd[i] : 0xff,
dc ? dc[i] : 0, nm ? nm[i] : 0,
key + off + i, key + half_size + off + i))
The byte at scen->pkt_dir_idx is a real match byte: ice_acl_create_scen()
wires the TCAM byte at ICE_ACL_SCEN_PKT_DIR_IDX_IN_TCAM to
ICE_AQC_ACL_BYTE_SEL_BASE_PKT_DIR and then computes scen->pkt_dir_idx from
it. So is the direction value written above simply discarded, and does
the entry then also match the other direction and arbitrary values of the
other flag bits in that byte? The dir_flag_msk name suggests the intent
was dontcare[pkt_dir_idx] = ~dir_flag_msk.
> @@ -2004,6 +2607,7 @@ ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
> if (status)
> goto out;
>
> + INIT_LIST_HEAD(&e->l_entry);
> e->id = entry_id;
> e->vsi_handle = vsi_handle;
> e->prof = prof;
> @@ -2013,20 +2617,32 @@ ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
> case ICE_BLK_FD:
> case ICE_BLK_RSS:
> break;
> + case ICE_BLK_ACL:
> + /* ACL will handle the entry management */
> + status = ice_flow_acl_frmt_entry(hw, prof, e, (u8 *)data, acts,
> + acts_cnt);
> + if (status)
> + goto out;
> + break;
[Severity: Medium]
What keeps prof alive across this call?
ice_flow_add_entry() drops hw->fl_profs_locks[blk] right after
ice_flow_assoc_prof() and then keeps using the raw prof pointer. The new
ACL branch does a lot of work with it: ice_flow_acl_frmt_entry()
dereferences prof->id, prof->cfg.scen->width, prof->cfg.scen->pid_idx,
prof->cfg.scen->pkt_dir_idx, prof->cfg.scen->rng_chk_idx, prof->segs[] and
prof->dir, plus several allocations and AdminQ commands.
Meanwhile ice_flow_rem_prof_sync() can take that same profile list lock and
free the profile:
clear_bit(prof->id, hw->blk[blk].prof_id.id);
list_del(&prof->l_entry);
mutex_destroy(&prof->entries_lock);
devm_kfree(ice_hw_to_dev(hw), prof);
ACL profile teardown also runs from the service task, without rtnl_lock,
via ice_reset_subtask() -> ice_rebuild(), and the new entry is not
registered anywhere yet, so removal cannot see it and cannot wait for it.
Using prof after the unlock predates this patch (the FD and RSS paths
re-take prof->entries_lock afterwards), but this widens the window and the
set of prof fields touched considerably. Would holding the profile list
lock until the entry is registered, or taking a reference on the profile,
be preferable?
> @@ -1957,6 +2211,349 @@ int ice_flow_rem_prof(struct ice_hw *hw, enum ice_block blk, u64 prof_id)
[ ... ]
> @@ -216,12 +324,65 @@ static int ice_acl_prof_add_ethtool(struct ice_pf *pf,
> */
> int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
> {
> + struct ice_flow_action acts[ICE_ACL_NUM_ACT];
> struct ethtool_rx_flow_spec *fsp;
> + struct ice_acl_hw_prof *hw_prof;
> + struct ice_ntuple_fltr *input;
> + enum ice_fltr_ptype flow;
> + struct device *dev;
> struct ice_pf *pf;
> + struct ice_hw *hw;
> + u64 entry_h = 0;
> + int err;
>
> pf = vsi->back;
> + hw = &pf->hw;
> + dev = ice_pf_to_dev(pf);
>
> fsp = (struct ethtool_rx_flow_spec *)&cmd->fs;
>
> - return ice_acl_prof_add_ethtool(pf, fsp);
> + err = ice_acl_prof_add_ethtool(pf, fsp);
> + if (err)
> + return err;
> +
> + /* Add new rule */
> + input = kzalloc_obj(*input);
> + if (!input)
> + return -ENOMEM;
> +
> + err = ice_ntuple_set_input_set(vsi, ICE_BLK_ACL, fsp, input);
> + if (err)
> + goto free_input;
> +
> + mutex_lock(&hw->fdir_fltr_lock);
> + if (ice_acl_is_dup_fltr(hw, input)) {
> + mutex_unlock(&hw->fdir_fltr_lock);
> + err = -EINVAL;
> + goto free_input;
> + }
> + mutex_unlock(&hw->fdir_fltr_lock);
> +
> + memset(&acts, 0, sizeof(acts));
> + if (fsp->ring_cookie == RX_CLS_FLOW_DISC)
> + ice_acl_set_act_drop(&acts[0]);
> + else
> + ice_acl_set_act_fwd_queue(&acts[0], input->q_index);
> +
> + flow = ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT);
> + hw_prof = hw->acl_prof[flow];
> +
> + err = ice_flow_add_entry(hw, ICE_BLK_ACL, hw_prof->prof_id,
> + fsp->location, vsi->idx, ICE_FLOW_PRIO_NORMAL,
> + input, acts, ICE_ACL_NUM_ACT, &entry_h);
> + if (err) {
> + dev_err(dev, "Could not add flow entry %d\n", flow);
> + goto free_input;
> + }
> +
> + return 0;
> +
> +free_input:
> + kfree(input);
> +
> + return err;
> }
[Severity: Medium]
Why is hw->fdir_fltr_lock dropped between the duplicate check and
the hardware programming?
ice_acl_is_dup_fltr() walks hw->fdir_list_head under the lock, but the
lock is released immediately afterwards and the decision is then acted
on without it: ice_flow_add_entry() formats the key, allocates the TCAM
entry and programs it, and only later (in the final form of this
function) is the lock re-taken for ice_ntuple_update_list_entry() to
publish the filter on hw->fdir_list_head. Between the check and that
insertion nothing prevents a second caller from observing the same
list state, concluding "not a duplicate", and programming a second
scenario entry with identical match data. The result is two TCAM
entries for one rule, in a resource the driver cannot easily reclaim,
plus the entry-matching logic later in the series keying off identical
keys.
In practice the ethtool ioctl path runs under rtnl_lock, so this may be
unreachable from concurrent ethtool -N today, but then the check-then-
act split buys nothing and hides the real serialization. The Flow
Director side keeps the lock held across the dup check and the list
update; is there a reason the ACL path cannot do the same, i.e. hold
fdir_fltr_lock from ice_acl_is_dup_fltr() through
ice_ntuple_update_list_entry()? If the AdminQ work in
ice_flow_add_entry() cannot run under that mutex, please say so in the
commit message and state what actually guarantees exclusion here.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v2 09/12] ice: program ACL entry
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (7 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 08/12] ice: create ACL entry Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-21 19:57 ` netdev-bot+sashiko
2026-09-17 18:39 ` [PATCH net-next v2 10/12] ice: add ACL reset recovery and NTUPLE feature toggle Tony Nguyen
` (4 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Real Valiquette, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Chinh Cao,
Rinitha S
From: Real Valiquette <real.valiquette@intel.com>
Complete the filter programming process; set the flow entry and action into
the scenario and write it to hardware. Configure the VSI for ACL filters.
With this change, ACL implementation is complete. Filters can be
programmed by specifying the 'm' value of ethtool ntuple command
for currently supported fields (src-ip, dst-ip, src-port, and dst-port).
For example:
ethtool -N eth0 flow-type tcp4 dst-port 8880 m 0x00ff action 10
or
ethtool -N eth0 flow-type tcp4 src-ip 192.168.0.55 m 0.0.0.255 action -1
At this time the following flow-types support mask values: tcp4, udp4,
sctp4, and ip4.
Co-developed-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Chinh Cao <chinh.t.cao@intel.com>
Signed-off-by: Real Valiquette <real.valiquette@intel.com>
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 2 +
drivers/net/ethernet/intel/ice/ice_acl.c | 53 ++-
drivers/net/ethernet/intel/ice/ice_acl.h | 21 +
drivers/net/ethernet/intel/ice/ice_acl_ctrl.c | 256 ++++++++++
drivers/net/ethernet/intel/ice/ice_acl_main.c | 31 +-
.../net/ethernet/intel/ice/ice_adminq_cmd.h | 2 +
.../ethernet/intel/ice/ice_ethtool_ntuple.c | 51 +-
drivers/net/ethernet/intel/ice/ice_flow.c | 446 +++++++++++++++++-
drivers/net/ethernet/intel/ice/ice_flow.h | 3 +
drivers/net/ethernet/intel/ice/ice_lib.c | 10 +-
10 files changed, 862 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index 7c72a61ffae5..61a320eaf3c5 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -1064,6 +1064,8 @@ void ice_aq_prep_for_event(struct ice_pf *pf, struct ice_aq_task *task,
u16 opcode);
int ice_aq_wait_for_event(struct ice_pf *pf, struct ice_aq_task *task,
unsigned long timeout);
+int ice_ntuple_update_list_entry(struct ice_pf *pf,
+ struct ice_ntuple_fltr *input, int fltr_idx);
int ice_open(struct net_device *netdev);
int ice_open_internal(struct net_device *netdev);
int ice_stop(struct net_device *netdev);
diff --git a/drivers/net/ethernet/intel/ice/ice_acl.c b/drivers/net/ethernet/intel/ice/ice_acl.c
index 3179ce33e365..ed078e30abfa 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl.c
+++ b/drivers/net/ethernet/intel/ice/ice_acl.c
@@ -156,7 +156,8 @@ static int ice_acl_prof_aq_send(struct ice_hw *hw, u16 opc, u8 prof_id,
cmd = libie_aq_raw(&desc);
cmd->profile_id = prof_id;
- if (opc == ice_aqc_opc_program_acl_prof_extraction)
+ if (opc == ice_aqc_opc_program_acl_prof_extraction ||
+ opc == ice_aqc_opc_program_acl_prof_ranges)
desc.flags |= cpu_to_le16(LIBIE_AQ_FLAG_RD);
return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
@@ -318,6 +319,56 @@ int ice_aq_dealloc_acl_cntrs(struct ice_hw *hw, struct ice_acl_cntrs *cntrs,
return ice_aq_send_cmd(hw, &desc, NULL, 0, cd);
}
+/**
+ * ice_prog_acl_prof_ranges - program ACL profile ranges
+ * @hw: pointer to the HW struct
+ * @prof_id: programmed or updated profile ID
+ * @buf: pointer to input buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Program ACL profile ranges (indirect 0x0C1E)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_prog_acl_prof_ranges(struct ice_hw *hw, u8 prof_id,
+ struct ice_aqc_acl_profile_ranges *buf,
+ struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_profile *cmd;
+ struct libie_aq_desc desc;
+
+ ice_fill_dflt_direct_cmd_desc(&desc,
+ ice_aqc_opc_program_acl_prof_ranges);
+ cmd = libie_aq_raw(&desc);
+ cmd->profile_id = prof_id;
+ desc.flags |= cpu_to_le16(LIBIE_AQ_FLAG_RD);
+ return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
+}
+
+/**
+ * ice_query_acl_prof_ranges - query ACL profile ranges
+ * @hw: pointer to the HW struct
+ * @prof_id: programmed or updated profile ID
+ * @buf: pointer to response buffer
+ * @cd: pointer to command details structure or NULL
+ *
+ * Query ACL profile ranges (indirect 0x0C22)
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_query_acl_prof_ranges(struct ice_hw *hw, u8 prof_id,
+ struct ice_aqc_acl_profile_ranges *buf,
+ struct ice_sq_cd *cd)
+{
+ struct ice_aqc_acl_profile *cmd;
+ struct libie_aq_desc desc;
+
+ ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_query_acl_prof_ranges);
+ cmd = libie_aq_raw(&desc);
+ cmd->profile_id = prof_id;
+ return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
+}
+
/**
* ice_aq_alloc_acl_scen - allocate ACL scenario
* @hw: pointer to the HW struct
diff --git a/drivers/net/ethernet/intel/ice/ice_acl.h b/drivers/net/ethernet/intel/ice/ice_acl.h
index 066665c9d963..0c8163e585d9 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl.h
+++ b/drivers/net/ethernet/intel/ice/ice_acl.h
@@ -43,6 +43,7 @@ struct ice_acl_tbl {
DECLARE_BITMAP(avail, ICE_AQC_ACL_ALLOC_UNITS);
};
+#define ICE_MAX_ACL_TCAM_ENTRY (ICE_AQC_ACL_TCAM_DEPTH * ICE_AQC_ACL_SLICES)
enum ice_acl_entry_prio {
ICE_ACL_PRIO_LOW = 0,
ICE_ACL_PRIO_NORMAL,
@@ -69,6 +70,11 @@ struct ice_acl_scen {
* participate in this scenario
*/
DECLARE_BITMAP(act_mem_bitmap, ICE_AQC_MAX_ACTION_MEMORIES);
+
+ /* If nth bit of entry_bitmap is set, then nth entry will
+ * be available in this scenario
+ */
+ DECLARE_BITMAP(entry_bitmap, ICE_MAX_ACL_TCAM_ENTRY);
u16 first_idx[ICE_ACL_MAX_PRIO];
u16 last_idx[ICE_ACL_MAX_PRIO];
@@ -141,6 +147,12 @@ int ice_aq_alloc_acl_cntrs(struct ice_hw *hw, struct ice_acl_cntrs *cntrs,
struct ice_sq_cd *cd);
int ice_aq_dealloc_acl_cntrs(struct ice_hw *hw, struct ice_acl_cntrs *cntrs,
struct ice_sq_cd *cd);
+int ice_prog_acl_prof_ranges(struct ice_hw *hw, u8 prof_id,
+ struct ice_aqc_acl_profile_ranges *buf,
+ struct ice_sq_cd *cd);
+int ice_query_acl_prof_ranges(struct ice_hw *hw, u8 prof_id,
+ struct ice_aqc_acl_profile_ranges *buf,
+ struct ice_sq_cd *cd);
int ice_aq_alloc_acl_scen(struct ice_hw *hw, u16 *scen_id,
struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd);
int ice_aq_dealloc_acl_scen(struct ice_hw *hw, u16 scen_id,
@@ -149,5 +161,14 @@ int ice_aq_update_acl_scen(struct ice_hw *hw, u16 scen_id,
struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd);
int ice_aq_query_acl_scen(struct ice_hw *hw, u16 scen_id,
struct ice_aqc_acl_scen *buf, struct ice_sq_cd *cd);
+int ice_acl_add_entry(struct ice_hw *hw, struct ice_acl_scen *scen,
+ enum ice_acl_entry_prio prio, u8 *keys, u8 *inverts,
+ struct ice_acl_act_entry *acts, u8 acts_cnt,
+ u16 *entry_idx);
+int ice_acl_prog_act(struct ice_hw *hw, struct ice_acl_scen *scen,
+ struct ice_acl_act_entry *acts, u8 acts_cnt,
+ u16 entry_idx);
+int ice_acl_rem_entry(struct ice_hw *hw, struct ice_acl_scen *scen,
+ u16 entry_idx);
#endif /* _ICE_ACL_H_ */
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
index 6a60da3034cf..0ee86236bc37 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
+++ b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
@@ -6,6 +6,11 @@
/* Determine the TCAM index of entry 'e' within the ACL table */
#define ICE_ACL_TBL_TCAM_IDX(e) ((e) / ICE_AQC_ACL_TCAM_DEPTH)
+/* Determine the entry index within the TCAM */
+#define ICE_ACL_TBL_TCAM_ENTRY_IDX(e) ((e) % ICE_AQC_ACL_TCAM_DEPTH)
+
+#define ICE_ACL_SCEN_ENTRY_INVAL 0xFFFF
+
/**
* ice_acl_init_entry - initialize ACL entry
* @scen: pointer to the scenario struct
@@ -29,6 +34,51 @@ static void ice_acl_init_entry(struct ice_acl_scen *scen)
scen->last_idx[ICE_ACL_PRIO_HIGH] = scen->num_entry / 4 - 1;
}
+/**
+ * ice_acl_scen_assign_entry_idx - find index of an available entry in scenario
+ * @scen: pointer to the scenario struct
+ * @prio: the priority of the flow entry being allocated
+ *
+ * Return: entry index on success, ICE_ACL_SCEN_ENTRY_INVAL on error
+ */
+static u16 ice_acl_scen_assign_entry_idx(struct ice_acl_scen *scen,
+ enum ice_acl_entry_prio prio)
+{
+ u16 first_idx, last_idx, i;
+ s8 step;
+
+ if (prio >= ICE_ACL_MAX_PRIO)
+ return ICE_ACL_SCEN_ENTRY_INVAL;
+
+ first_idx = scen->first_idx[prio];
+ last_idx = scen->last_idx[prio];
+ step = first_idx <= last_idx ? 1 : -1;
+
+ for (i = first_idx; i != last_idx + step; i += step)
+ if (!test_and_set_bit(i, scen->entry_bitmap))
+ return i;
+
+ return ICE_ACL_SCEN_ENTRY_INVAL;
+}
+
+/**
+ * ice_acl_scen_free_entry_idx - mark an entry as available in a scenario
+ * @scen: pointer to the scenario struct
+ * @idx: the index of the flow entry being de-allocated
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_acl_scen_free_entry_idx(struct ice_acl_scen *scen, u16 idx)
+{
+ if (idx >= scen->num_entry)
+ return -EINVAL;
+
+ if (!test_and_clear_bit(idx, scen->entry_bitmap))
+ return -ENOENT;
+
+ return 0;
+}
+
/**
* ice_acl_tbl_calc_end_idx - get end ACL entry index
* @start: start index of the TCAM entry of this partition
@@ -887,3 +937,209 @@ int ice_acl_destroy_tbl(struct ice_hw *hw)
return 0;
}
+
+/**
+ * ice_acl_add_entry - Add a flow entry to ACL scenario
+ * @hw: pointer to the HW struct
+ * @scen: scenario to add the entry to
+ * @prio: priority level of the entry being added
+ * @keys: buffer of the value of the key to be programmed to the ACL entry
+ * @inverts: buffer of the value of the key inverts to be programmed
+ * @acts: pointer to a buffer containing formatted actions
+ * @acts_cnt: indicates the number of actions stored in "acts"
+ * @entry_idx: returned scenario relative index of the added flow entry
+ *
+ * Given an ACL table and a scenario, to add the specified key and key invert
+ * to an available entry in the specified scenario.
+ * The "keys" and "inverts" buffers must be of the size which is the same as
+ * the scenario's width
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_acl_add_entry(struct ice_hw *hw, struct ice_acl_scen *scen,
+ enum ice_acl_entry_prio prio, u8 *keys, u8 *inverts,
+ struct ice_acl_act_entry *acts, u8 acts_cnt,
+ u16 *entry_idx)
+{
+ u8 entry_tcam, num_cscd, offset;
+ struct ice_aqc_acl_data buf = {};
+ int err = 0;
+ u16 idx;
+
+ if (!scen)
+ return -ENOENT;
+
+ *entry_idx = ice_acl_scen_assign_entry_idx(scen, prio);
+ if (*entry_idx >= scen->num_entry) {
+ *entry_idx = 0;
+ return -ENOSPC;
+ }
+
+ /* Determine number of cascaded TCAMs */
+ num_cscd = DIV_ROUND_UP(scen->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
+
+ entry_tcam = ICE_ACL_TBL_TCAM_IDX(scen->start);
+ idx = ICE_ACL_TBL_TCAM_ENTRY_IDX(scen->start + *entry_idx);
+
+ for (u8 i = 0; i < num_cscd; i++) {
+ /* If the key spans more than one TCAM in the case of cascaded
+ * TCAMs, the key and key inverts need to be properly split
+ * among TCAMs. E.g. bytes 0 - 4 go to an index in the first
+ * TCAM and bytes 5 - 9 go to the same index in the next TCAM,
+ * etc. If the entry spans more than one TCAM in a cascaded TCAM
+ * mode, the programming of the entries in the TCAMs must be in
+ * reversed order - the TCAM entry of the rightmost TCAM should
+ * be programmed first; the TCAM entry of the leftmost TCAM
+ * should be programmed last.
+ */
+ offset = num_cscd - i - 1;
+ memcpy(&buf.entry_key.val,
+ &keys[offset * sizeof(buf.entry_key.val)],
+ sizeof(buf.entry_key.val));
+ memcpy(&buf.entry_key_invert.val,
+ &inverts[offset * sizeof(buf.entry_key_invert.val)],
+ sizeof(buf.entry_key_invert.val));
+ err = ice_aq_program_acl_entry(hw, entry_tcam + offset, idx,
+ &buf, NULL);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "aq program acl entry failed status: %d\n",
+ err);
+ goto out;
+ }
+ }
+
+ err = ice_acl_prog_act(hw, scen, acts, acts_cnt, *entry_idx);
+
+out:
+ if (err) {
+ ice_acl_rem_entry(hw, scen, *entry_idx);
+ *entry_idx = 0;
+ }
+
+ return err;
+}
+
+/**
+ * ice_acl_prog_act - Program a scenario's action memory
+ * @hw: pointer to the HW struct
+ * @scen: scenario to add the entry to
+ * @acts: pointer to a buffer containing formatted actions
+ * @acts_cnt: indicates the number of actions stored in "acts"
+ * @entry_idx: scenario relative index of the added flow entry
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_acl_prog_act(struct ice_hw *hw, struct ice_acl_scen *scen,
+ struct ice_acl_act_entry *acts, u8 acts_cnt, u16 entry_idx)
+{
+ u8 entry_tcam, num_cscd, i, actx_idx = 0;
+ struct ice_aqc_actpair act_buf = {};
+ int err = 0;
+ u16 idx;
+
+ if (entry_idx >= scen->num_entry)
+ return -ENOSPC;
+
+ /* Determine number of cascaded TCAMs */
+ num_cscd = DIV_ROUND_UP(scen->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
+
+ entry_tcam = ICE_ACL_TBL_TCAM_IDX(scen->start);
+ idx = ICE_ACL_TBL_TCAM_ENTRY_IDX(scen->start + entry_idx);
+
+ for_each_set_bit(i, scen->act_mem_bitmap, ICE_AQC_MAX_ACTION_MEMORIES) {
+ struct ice_acl_act_mem *mem = &hw->acl_tbl->act_mems[i];
+
+ if (actx_idx >= acts_cnt)
+ break;
+ if (mem->member_of_tcam >= entry_tcam &&
+ mem->member_of_tcam < entry_tcam + num_cscd) {
+ memcpy(&act_buf.act[0], &acts[actx_idx],
+ sizeof(struct ice_acl_act_entry));
+
+ if (++actx_idx < acts_cnt) {
+ memcpy(&act_buf.act[1], &acts[actx_idx],
+ sizeof(struct ice_acl_act_entry));
+ }
+
+ err = ice_aq_program_actpair(hw, i, idx, &act_buf,
+ NULL);
+ if (err) {
+ ice_debug(hw, ICE_DBG_ACL, "program actpair failed status: %d\n",
+ err);
+ break;
+ }
+ actx_idx++;
+ }
+ }
+
+ if (!err && actx_idx < acts_cnt)
+ err = -ENOSPC;
+
+ return err;
+}
+
+/**
+ * ice_acl_rem_entry - Remove a flow entry from an ACL scenario
+ * @hw: pointer to the HW struct
+ * @scen: scenario to remove the entry from
+ * @entry_idx: the scenario-relative index of the flow entry being removed
+ *
+ * Return: 0 on success, negative on error
+ */
+int ice_acl_rem_entry(struct ice_hw *hw, struct ice_acl_scen *scen,
+ u16 entry_idx)
+{
+ struct ice_aqc_actpair act_buf = {};
+ struct ice_aqc_acl_data buf;
+ u8 entry_tcam, num_cscd, i;
+ int err = 0;
+ u16 idx;
+
+ if (!scen)
+ return -ENOENT;
+
+ if (entry_idx >= scen->num_entry)
+ return -ENOSPC;
+
+ if (!test_bit(entry_idx, scen->entry_bitmap))
+ return -ENOENT;
+
+ /* Determine number of cascaded TCAMs */
+ num_cscd = DIV_ROUND_UP(scen->width, ICE_AQC_ACL_KEY_WIDTH_BYTES);
+
+ entry_tcam = ICE_ACL_TBL_TCAM_IDX(scen->start);
+ idx = ICE_ACL_TBL_TCAM_ENTRY_IDX(scen->start + entry_idx);
+
+ /* invalidate the flow entry */
+ memset(&buf, 0, sizeof(buf));
+ for (i = 0; i < num_cscd; i++) {
+ int aq_err = ice_aq_program_acl_entry(hw, entry_tcam + i, idx,
+ &buf, NULL);
+ if (aq_err) {
+ dev_warn(ice_hw_to_dev(hw), "AQ program ACL entry failed, status: %d\n",
+ aq_err);
+ err = aq_err;
+ }
+ }
+
+ for_each_set_bit(i, scen->act_mem_bitmap, ICE_AQC_MAX_ACTION_MEMORIES) {
+ struct ice_acl_act_mem *mem = &hw->acl_tbl->act_mems[i];
+
+ if (mem->member_of_tcam >= entry_tcam &&
+ mem->member_of_tcam < entry_tcam + num_cscd) {
+ /* Invalidate allocated action pairs */
+ int aq_err = ice_aq_program_actpair(hw, i, idx,
+ &act_buf, NULL);
+ if (aq_err) {
+ dev_warn(ice_hw_to_dev(hw), "AQ program ACL action pair failed, status: %d\n",
+ aq_err);
+ err = aq_err;
+ }
+ }
+ }
+
+ if (!err)
+ err = ice_acl_scen_free_entry_idx(scen, entry_idx);
+
+ return err;
+}
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.c b/drivers/net/ethernet/intel/ice/ice_acl_main.c
index 6313d511398a..171b7bf8519c 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_acl_main.c
@@ -328,11 +328,11 @@ int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
struct ethtool_rx_flow_spec *fsp;
struct ice_acl_hw_prof *hw_prof;
struct ice_ntuple_fltr *input;
+ u64 entry_h = 0, old_entry_h;
enum ice_fltr_ptype flow;
struct device *dev;
struct ice_pf *pf;
struct ice_hw *hw;
- u64 entry_h = 0;
int err;
pf = vsi->back;
@@ -371,6 +371,12 @@ int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
flow = ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT);
hw_prof = hw->acl_prof[flow];
+ /* Look up existing HW entry before adding/replacing, so we can
+ * remove it if the new entry has different match data and a fresh
+ * TCAM slot was allocated instead of updating in place.
+ */
+ old_entry_h = ice_flow_find_entry(hw, ICE_BLK_ACL, fsp->location);
+
err = ice_flow_add_entry(hw, ICE_BLK_ACL, hw_prof->prof_id,
fsp->location, vsi->idx, ICE_FLOW_PRIO_NORMAL,
input, acts, ICE_ACL_NUM_ACT, &entry_h);
@@ -379,8 +385,31 @@ int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
goto free_input;
}
+ /* If the match data changed, ice_flow_acl_add_scen_entry_sync()
+ * allocated a new TCAM entry rather than updating in place, leaving
+ * the old entry still programmed. Remove it.
+ */
+ if (old_entry_h != ICE_FLOW_ENTRY_HANDLE_INVAL &&
+ old_entry_h != entry_h) {
+ err = ice_flow_rem_entry(hw, ICE_BLK_ACL, old_entry_h);
+ if (err)
+ goto del_entry;
+ }
+
+ input->acl_fltr = true;
+
+ mutex_lock(&hw->fdir_fltr_lock);
+ /* input struct is added to the HW filter list */
+ err = ice_ntuple_update_list_entry(pf, input, fsp->location);
+ mutex_unlock(&hw->fdir_fltr_lock);
+ if (err)
+ goto del_entry;
+
return 0;
+del_entry:
+ ice_flow_rem_entry(hw, ICE_BLK_ACL, entry_h);
+
free_input:
kfree(input);
diff --git a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
index 44248df765fe..7f85a10a564b 100644
--- a/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
+++ b/drivers/net/ethernet/intel/ice/ice_adminq_cmd.h
@@ -3025,8 +3025,10 @@ enum ice_adminq_opc {
ice_aqc_opc_update_acl_scen = 0x0C1B,
ice_aqc_opc_program_acl_actpair = 0x0C1C,
ice_aqc_opc_program_acl_prof_extraction = 0x0C1D,
+ ice_aqc_opc_program_acl_prof_ranges = 0x0C1E,
ice_aqc_opc_program_acl_entry = 0x0C20,
ice_aqc_opc_query_acl_prof = 0x0C21,
+ ice_aqc_opc_query_acl_prof_ranges = 0x0C22,
ice_aqc_opc_query_acl_scen = 0x0C23,
/* Tx queue handling commands/events */
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
index f67b6e1c0ffb..0ac44d38fdbd 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
@@ -1810,6 +1810,21 @@ void ice_fdir_del_all_fltrs(struct ice_vsi *vsi)
}
}
+/**
+ * ice_del_acl_ethtool - delete an ACL rule entry
+ * @hw: pointer to HW instance
+ * @fltr: filter structure
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_del_acl_ethtool(struct ice_hw *hw, struct ice_ntuple_fltr *fltr)
+{
+ u64 entry;
+
+ entry = ice_flow_find_entry(hw, ICE_BLK_ACL, fltr->fltr_id);
+ return ice_flow_rem_entry(hw, ICE_BLK_ACL, entry);
+}
+
/**
* ice_vsi_manage_fdir - turn on/off flow director
* @vsi: the VSI being changed
@@ -1876,7 +1891,7 @@ ice_fdir_do_rem_flow(struct ice_pf *pf, enum ice_fltr_ptype flow_type)
*
* Return: 0 on success and negative on errors
*/
-static int
+int
ice_ntuple_update_list_entry(struct ice_pf *pf, struct ice_ntuple_fltr *input,
int fltr_idx)
{
@@ -1895,17 +1910,43 @@ ice_ntuple_update_list_entry(struct ice_pf *pf, struct ice_ntuple_fltr *input,
old_fltr = ice_fdir_find_fltr_by_idx(hw, fltr_idx);
if (old_fltr) {
- err = ice_fdir_write_all_fltr(pf, old_fltr, false);
- if (err)
- return err;
+ if (old_fltr->acl_fltr) {
+ /* ACL filter - if the input buffer is present
+ * then this is an update. The caller has already
+ * applied the HW change (including removing the old
+ * TCAM entry when match data changed), so just update
+ * the SW structures. If no input then this is a
+ * delete so we should delete the filter from the HW
+ * and clean up our SW structures.
+ */
+ if (!input) {
+ err = ice_del_acl_ethtool(hw, old_fltr);
+ if (err)
+ return err;
+ }
+ } else {
+ /* FD filter */
+ err = ice_fdir_write_all_fltr(pf, old_fltr, false);
+ if (err)
+ return err;
+ }
+
ice_ntuple_update_cntrs(hw, old_fltr, false);
/* update sb-filters count, specific to ring->channel */
ice_update_per_q_fltr(vsi, old_fltr->orig_q_index, false);
- if (!input && !hw->fdir_fltr_cnt[old_fltr->flow_type])
+ /* Also delete the HW filter info if we have just deleted the
+ * last filter of flow_type.
+ */
+ if (!old_fltr->acl_fltr && !input &&
+ !hw->fdir_fltr_cnt[old_fltr->flow_type])
/* we just deleted the last filter of flow_type so we
* should also delete the HW filter info.
*/
ice_fdir_do_rem_flow(pf, old_fltr->flow_type);
+ else if (old_fltr->acl_fltr && !input &&
+ !hw->acl_fltr_cnt[old_fltr->flow_type])
+ ice_fdir_rem_flow(hw, ICE_BLK_ACL, old_fltr->flow_type);
+
list_del(&old_fltr->fltr_node);
kfree(old_fltr);
}
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.c b/drivers/net/ethernet/intel/ice/ice_flow.c
index 192569ff1d6c..e0a1d8463d6f 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.c
+++ b/drivers/net/ethernet/intel/ice/ice_flow.c
@@ -1741,6 +1741,16 @@ static int ice_flow_rem_entry_sync(struct ice_hw *hw, enum ice_block blk,
return -EINVAL;
if (blk == ICE_BLK_ACL) {
+ int err;
+
+ if (!entry->prof)
+ return -EINVAL;
+
+ err = ice_acl_rem_entry(hw, entry->prof->cfg.scen,
+ entry->scen_entry_idx);
+ if (err)
+ return err;
+
if (entry->acts_cnt && entry->acts)
ice_flow_acl_free_act_cntr(hw, entry->acts,
entry->acts_cnt);
@@ -1876,10 +1886,49 @@ ice_flow_rem_prof_sync(struct ice_hw *hw, enum ice_block blk,
}
if (blk == ICE_BLK_ACL) {
+ struct ice_aqc_acl_prof_generic_frmt buf;
+ u8 prof_id = 0;
+
/* Disassociate the scenario from the profile for the PF */
status = ice_flow_acl_disassoc_scen(hw, prof);
if (status)
return status;
+
+ status = ice_flow_get_hw_prof(hw, blk, prof->id, &prof_id);
+ if (status)
+ return status;
+
+ status = ice_query_acl_prof(hw, prof_id, &buf, NULL);
+ if (status)
+ return status;
+
+ /* Clear the range-checker if the profile ID is no longer
+ * used by any PF
+ */
+ if (!ice_flow_acl_is_prof_in_use(&buf)) {
+ /* Clear the range-checker value for profile ID */
+ struct ice_aqc_acl_profile_ranges query_rng_buf = {};
+
+ status = ice_prog_acl_prof_ranges(hw, prof_id,
+ &query_rng_buf, NULL);
+ if (status)
+ return status;
+
+ /* Reset selections to default. PF association is
+ * already correct (checked by
+ * ice_flow_acl_is_prof_in_use()).
+ */
+ memset(buf.byte_selection, 0,
+ sizeof(buf.byte_selection));
+ memset(buf.word_selection, 0,
+ sizeof(buf.word_selection));
+ memset(buf.dword_selection, 0,
+ sizeof(buf.dword_selection));
+ status = ice_prgm_acl_prof_xtrct(hw, prof_id, &buf,
+ NULL);
+ if (status)
+ return status;
+ }
}
/* Remove all hardware profiles associated with this flow profile */
@@ -2211,6 +2260,44 @@ int ice_flow_rem_prof(struct ice_hw *hw, enum ice_block blk, u64 prof_id)
return status;
}
+/**
+ * ice_flow_find_entry - look for a flow entry using its unique ID
+ * @hw: pointer to the HW struct
+ * @blk: classification stage
+ * @entry_id: unique ID to identify this flow entry
+ *
+ * Look for the flow entry with the specified unique ID in all flow profiles of
+ * the specified classification stage.
+ *
+ * Return: flow entry handle if entry found, ICE_FLOW_ENTRY_ID_INVAL otherwise
+ */
+u64 ice_flow_find_entry(struct ice_hw *hw, enum ice_block blk, u64 entry_id)
+{
+ struct ice_flow_entry *found = NULL;
+ struct ice_flow_prof *p;
+
+ mutex_lock(&hw->fl_profs_locks[blk]);
+
+ list_for_each_entry(p, &hw->fl_profs[blk], l_entry) {
+ struct ice_flow_entry *e;
+
+ mutex_lock(&p->entries_lock);
+ list_for_each_entry(e, &p->entries, l_entry)
+ if (e->id == entry_id) {
+ found = e;
+ break;
+ }
+ mutex_unlock(&p->entries_lock);
+
+ if (found)
+ break;
+ }
+
+ mutex_unlock(&hw->fl_profs_locks[blk]);
+
+ return found ? ICE_FLOW_ENTRY_HNDL(found) : ICE_FLOW_ENTRY_HANDLE_INVAL;
+}
+
/**
* ice_flow_acl_check_actions - Checks the ACL rule's actions
* @hw: pointer to the hardware structure
@@ -2554,6 +2641,346 @@ static int ice_flow_acl_frmt_entry(struct ice_hw *hw,
return err;
}
+
+/**
+ * ice_flow_acl_find_scen_entry_cond - Find an ACL scenario entry that matches
+ * the compared data
+ * @prof: pointer to flow profile
+ * @e: pointer to the comparing flow entry
+ * @do_chg_action: decide if we want to change the ACL action
+ * @do_add_entry: decide if we want to add the new ACL entry
+ * @do_rem_entry: decide if we want to remove the current ACL entry
+ *
+ * Find an ACL scenario entry that matches the compared data. Also figure out:
+ * a) If we want to change the ACL action
+ * b) If we want to add the new ACL entry
+ * c) If we want to remove the current ACL entry
+ *
+ * Return: ACL scenario entry, or NULL if not found
+ */
+static struct ice_flow_entry *
+ice_flow_acl_find_scen_entry_cond(struct ice_flow_prof *prof,
+ struct ice_flow_entry *e, bool *do_chg_action,
+ bool *do_add_entry, bool *do_rem_entry)
+{
+ struct ice_flow_entry *p, *return_entry = NULL;
+
+ /* If:
+ * a) There exists an entry with same matching data but different
+ * priority, then remove this existing ACL entry and add the new
+ * entry to the ACL scenario
+ * b) There exists an entry with same matching data, priority, and
+ * result action, then do nothing
+ * c) There exists an entry with same matching data, priority, but
+ * different action, then only change the action's entry
+ * d) Else, add this new entry to the ACL scenario
+ */
+ *do_chg_action = false;
+ *do_add_entry = true;
+ *do_rem_entry = false;
+ list_for_each_entry(p, &prof->entries, l_entry) {
+ if (memcmp(p->entry, e->entry, p->entry_sz))
+ continue;
+
+ /* Now we have the same matching_data */
+
+ *do_add_entry = false;
+ return_entry = p;
+
+ if (p->priority != e->priority) {
+ /* matching data && !priority */
+ *do_add_entry = true;
+ *do_rem_entry = true;
+ break;
+ }
+
+ /* Now we have matching_data && priority */
+
+ if (p->acts_cnt != e->acts_cnt)
+ *do_chg_action = true;
+ for (int i = 0; i < p->acts_cnt; i++) {
+ if (memcmp(&p->acts[i], &e->acts[i],
+ sizeof(struct ice_flow_action))) {
+ *do_chg_action = true;
+ break;
+ }
+ }
+
+ /* (do_chg_action = true) means :
+ * matching_data && priority && !result_action
+ * (do_chg_action = false) means :
+ * matching_data && priority && result_action
+ */
+ break;
+ }
+
+ return return_entry;
+}
+
+/**
+ * ice_flow_acl_convert_to_acl_prio - convert flow priority to ACL priority
+ * @p: flow priority
+ *
+ * Return: ACL priority
+ */
+static enum ice_acl_entry_prio
+ice_flow_acl_convert_to_acl_prio(enum ice_flow_priority p)
+{
+ switch (p) {
+ case ICE_FLOW_PRIO_LOW:
+ return ICE_ACL_PRIO_LOW;
+ case ICE_FLOW_PRIO_NORMAL:
+ return ICE_ACL_PRIO_NORMAL;
+ case ICE_FLOW_PRIO_HIGH:
+ return ICE_ACL_PRIO_HIGH;
+ default:
+ return ICE_ACL_PRIO_NORMAL;
+ }
+}
+
+/**
+ * ice_flow_acl_union_rng_chk - Perform union operation between two range-range
+ * checker buffers
+ * @dst_buf: pointer to destination range checker buffer
+ * @src_buf: pointer to source range checker buffer
+ *
+ * Do the union between dst_buf and src_buf range checker buffer, and save the
+ * result back to dst_buf.
+ *
+ * Return: 0 on success, negative on error
+ */
+static int
+ice_flow_acl_union_rng_chk(struct ice_aqc_acl_profile_ranges *dst_buf,
+ struct ice_aqc_acl_profile_ranges *src_buf)
+{
+ if (!dst_buf || !src_buf)
+ return -EINVAL;
+
+ for (int i = 0; i < ICE_AQC_ACL_PROF_RANGES_NUM_CFG; i++) {
+ struct ice_acl_rng_data *cfg_data = NULL, *in_data;
+ bool will_populate = false;
+
+ in_data = &src_buf->checker_cfg[i];
+
+ if (!in_data->mask)
+ break;
+
+ for (int j = 0; j < ICE_AQC_ACL_PROF_RANGES_NUM_CFG; j++) {
+ cfg_data = &dst_buf->checker_cfg[j];
+
+ if (!cfg_data->mask ||
+ !memcmp(cfg_data, in_data,
+ sizeof(struct ice_acl_rng_data))) {
+ will_populate = true;
+ break;
+ }
+ }
+
+ if (will_populate) {
+ memcpy(cfg_data, in_data,
+ sizeof(struct ice_acl_rng_data));
+ } else {
+ /* No available slot left to program range checker */
+ return -ENOSPC;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * ice_flow_acl_add_scen_entry_sync - add entry to ACL scenario sync
+ * @hw: pointer to the hardware structure
+ * @prof: pointer to flow profile
+ * @entry: double pointer to the flow entry
+ *
+ * Look at the current added entries in the corresponding ACL scenario and
+ * perform matching logic to see if we want to add/modify/do nothing with this
+ * new entry.
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_flow_acl_add_scen_entry_sync(struct ice_hw *hw,
+ struct ice_flow_prof *prof,
+ struct ice_flow_entry **entry)
+{
+ bool do_add_entry, do_rem_entry, do_chg_action, do_chg_rng_chk;
+ struct ice_aqc_acl_profile_ranges query_rng_buf, cfg_rng_buf;
+ struct ice_acl_act_entry *acts = NULL;
+ struct ice_flow_entry *exist;
+ struct ice_flow_entry *e;
+ int err = 0;
+
+ e = *entry;
+
+ do_chg_rng_chk = false;
+ if (e->range_buf) {
+ u8 prof_id = 0;
+
+ err = ice_flow_get_hw_prof(hw, ICE_BLK_ACL, prof->id, &prof_id);
+ if (err)
+ return err;
+
+ /* Query the current range-checker value in FW */
+ err = ice_query_acl_prof_ranges(hw, prof_id, &query_rng_buf,
+ NULL);
+ if (err)
+ return err;
+ memcpy(&cfg_rng_buf, &query_rng_buf,
+ sizeof(struct ice_aqc_acl_profile_ranges));
+
+ /* Generate the new range-checker value */
+ err = ice_flow_acl_union_rng_chk(&cfg_rng_buf, e->range_buf);
+ if (err)
+ return err;
+
+ /* Reconfigure the range check if the buffer is changed. */
+ do_chg_rng_chk = false;
+ if (memcmp(&query_rng_buf, &cfg_rng_buf,
+ sizeof(struct ice_aqc_acl_profile_ranges))) {
+ err = ice_prog_acl_prof_ranges(hw, prof_id,
+ &cfg_rng_buf, NULL);
+ if (err)
+ return err;
+
+ do_chg_rng_chk = true;
+ }
+ }
+
+ /* Figure out if we want to (change the ACL action) and/or
+ * (Add the new ACL entry) and/or (Remove the current ACL entry)
+ */
+ exist = ice_flow_acl_find_scen_entry_cond(prof, e, &do_chg_action,
+ &do_add_entry, &do_rem_entry);
+
+ if (do_rem_entry) {
+ err = ice_flow_rem_entry_sync(hw, ICE_BLK_ACL, exist);
+ if (err)
+ return err;
+ }
+
+ /* Prepare the result action buffer */
+ acts = kzalloc_objs(*acts, e->acts_cnt);
+ if (!acts)
+ return -ENOMEM;
+
+ for (int i = 0; i < e->acts_cnt; i++)
+ memcpy(&acts[i], &e->acts[i].data.acl_act,
+ sizeof(struct ice_acl_act_entry));
+
+ if (do_add_entry) {
+ enum ice_acl_entry_prio prio;
+ u8 *keys, *inverts;
+ u16 entry_idx;
+
+ keys = (u8 *)e->entry;
+ inverts = keys + (e->entry_sz / 2);
+ prio = ice_flow_acl_convert_to_acl_prio(e->priority);
+
+ err = ice_acl_add_entry(hw, prof->cfg.scen, prio, keys,
+ inverts, acts, e->acts_cnt,
+ &entry_idx);
+ if (err)
+ goto out;
+
+ e->scen_entry_idx = entry_idx;
+ list_add(&e->l_entry, &prof->entries);
+ } else {
+ if (do_chg_action) {
+ /* For the action memory info, update the SW's copy of
+ * exist entry with e's action memory info
+ */
+ if (exist->acts_cnt && exist->acts)
+ ice_flow_acl_free_act_cntr(hw, exist->acts,
+ exist->acts_cnt);
+ kfree(exist->acts);
+ exist->acts = kzalloc_objs(*exist->acts, e->acts_cnt);
+ if (!exist->acts) {
+ exist->acts_cnt = 0;
+ err = -ENOMEM;
+ goto out;
+ }
+ exist->acts_cnt = e->acts_cnt;
+
+ memcpy(exist->acts, e->acts,
+ sizeof(struct ice_flow_action) * e->acts_cnt);
+
+ err = ice_acl_prog_act(hw, prof->cfg.scen, acts,
+ e->acts_cnt,
+ exist->scen_entry_idx);
+ if (err) {
+ /* HW programming failed; e's counters were not
+ * transferred. Drop exist's new acts so the
+ * caller's free_cntrs path does not release IDs
+ * still referenced by exist->acts.
+ */
+ kfree(exist->acts);
+ exist->acts = NULL;
+ exist->acts_cnt = 0;
+ goto out;
+ }
+
+ /* e's counter IDs are now owned by exist->acts; clear
+ * acts_cnt so the trailing ice_flow_acl_free_act_cntr
+ * call does not release IDs that exist->acts owns.
+ * ice_dealloc_flow_entry still frees the acts memory
+ * via kfree(e->acts).
+ */
+ e->acts_cnt = 0;
+ }
+
+ if (do_chg_rng_chk) {
+ /* In this case, we want to update the range checker
+ * information of the exist entry
+ */
+ err = ice_flow_acl_union_rng_chk(exist->range_buf,
+ e->range_buf);
+ if (err)
+ goto out;
+ }
+
+ /* As we don't add the new entry to our SW DB, free its HW
+ * counter resources and deallocate its memory, then return
+ * the exist entry to the caller. Transfer the new caller's
+ * entry_id to exist so that ice_flow_find_entry() can locate
+ * it and the caller's handle remains valid for deletion.
+ */
+ if (e->acts_cnt && e->acts)
+ ice_flow_acl_free_act_cntr(hw, e->acts, e->acts_cnt);
+ exist->id = e->id;
+ kfree(e->entry);
+ kfree(e->range_buf);
+ kfree(e->acts);
+ devm_kfree(ice_hw_to_dev(hw), e);
+ *entry = exist;
+ }
+out:
+ kfree(acts);
+
+ return err;
+}
+
+/**
+ * ice_flow_acl_add_scen_entry - Add entry to ACL scenario
+ * @hw: pointer to the hardware structure
+ * @prof: pointer to flow profile
+ * @e: double pointer to the flow entry
+ *
+ * Return: 0 on success, negative on error
+ */
+static int ice_flow_acl_add_scen_entry(struct ice_hw *hw,
+ struct ice_flow_prof *prof,
+ struct ice_flow_entry **e)
+{
+ int err;
+
+ mutex_lock(&prof->entries_lock);
+ err = ice_flow_acl_add_scen_entry_sync(hw, prof, e);
+ mutex_unlock(&prof->entries_lock);
+
+ return err;
+}
+
/**
* ice_flow_add_entry - Add a flow entry
* @hw: pointer to the HW struct
@@ -2605,7 +3032,7 @@ int ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
mutex_unlock(&hw->fl_profs_locks[blk]);
if (status)
- goto out;
+ goto dealloc_entry;
INIT_LIST_HEAD(&e->l_entry);
e->id = entry_id;
@@ -2622,11 +3049,15 @@ int ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
status = ice_flow_acl_frmt_entry(hw, prof, e, (u8 *)data, acts,
acts_cnt);
if (status)
- goto out;
+ goto dealloc_entry;
+
+ status = ice_flow_acl_add_scen_entry(hw, prof, &e);
+ if (status)
+ goto free_cntrs;
break;
default:
status = -EOPNOTSUPP;
- goto out;
+ goto dealloc_entry;
}
if (blk != ICE_BLK_ACL) {
@@ -2638,8 +3069,13 @@ int ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
*entry_h = ICE_FLOW_ENTRY_HNDL(e);
-out:
- if (status && e) {
+ return 0;
+
+free_cntrs:
+ if (blk == ICE_BLK_ACL && e->acts_cnt && e->acts)
+ ice_flow_acl_free_act_cntr(hw, e->acts, e->acts_cnt);
+dealloc_entry:
+ if (e) {
kfree(e->entry);
kfree(e->range_buf);
kfree(e->acts);
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.h b/drivers/net/ethernet/intel/ice/ice_flow.h
index ddc2bf03af30..2cdc8c09fa37 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.h
+++ b/drivers/net/ethernet/intel/ice/ice_flow.h
@@ -468,6 +468,8 @@ struct ice_flow_entry {
enum ice_flow_priority priority;
u16 vsi_handle;
u16 entry_sz;
+ /* Entry index in the ACL's scenario */
+ u16 scen_entry_idx;
u8 acts_cnt;
};
@@ -535,6 +537,7 @@ ice_flow_add_prof(struct ice_hw *hw, enum ice_block blk, enum ice_flow_dir dir,
struct ice_flow_seg_info *segs, u8 segs_cnt,
bool symm, struct ice_flow_prof **prof);
int ice_flow_rem_prof(struct ice_hw *hw, enum ice_block blk, u64 prof_id);
+u64 ice_flow_find_entry(struct ice_hw *hw, enum ice_block blk, u64 entry_id);
int
ice_flow_set_parser_prof(struct ice_hw *hw, u16 dest_vsi, u16 fdir_vsi,
struct ice_parser_profile *prof, enum ice_block blk);
diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
index 9e08db376d3d..8e842b29f84f 100644
--- a/drivers/net/ethernet/intel/ice/ice_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_lib.c
@@ -1118,7 +1118,7 @@ static void ice_set_fd_vsi_ctx(struct ice_vsi_ctx *ctxt, struct ice_vsi *vsi)
vsi->type != ICE_VSI_VF && vsi->type != ICE_VSI_CHNL)
return;
- val = ICE_AQ_VSI_PROP_FLOW_DIR_VALID;
+ val = ICE_AQ_VSI_PROP_FLOW_DIR_VALID | ICE_AQ_VSI_PROP_ACL_VALID;
ctxt->info.valid_sections |= cpu_to_le16(val);
dflt_q = 0;
dflt_q_group = 0;
@@ -1144,6 +1144,14 @@ static void ice_set_fd_vsi_ctx(struct ice_vsi_ctx *ctxt, struct ice_vsi *vsi)
/* priority of the default qindex action */
val |= FIELD_PREP(ICE_AQ_VSI_FD_DEF_PRIORITY_M, dflt_q_prio);
ctxt->info.fd_report_opt = cpu_to_le16(val);
+
+#define ICE_ACL_RX_PROF_MISS_CNTR \
+ FIELD_PREP_CONST(ICE_AQ_VSI_ACL_DEF_RX_PROF_M, 2)
+#define ICE_ACL_RX_TBL_MISS_CNTR \
+ FIELD_PREP_CONST(ICE_AQ_VSI_ACL_DEF_RX_TABLE_M, 3)
+
+ val = ICE_ACL_RX_PROF_MISS_CNTR | ICE_ACL_RX_TBL_MISS_CNTR;
+ ctxt->info.acl_def_act = cpu_to_le16(val);
}
/**
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 09/12] ice: program ACL entry
2026-09-17 18:39 ` [PATCH net-next v2 09/12] ice: program " Tony Nguyen
@ 2026-09-21 19:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:57 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
lukasz.czapnik, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, sx.rinitha
Thank you for your contribution! Sashiko AI review found 17 potential
issue(s) to consider.
Critical: 0 · High: 2 · Medium: 9 · Low: 6
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] ice_flow_acl_add_scen_entry_sync() merges two distinct ethtool
rules (different 'location' slots) onto one ACL scenario entry…
- [High] Replacing an ACL rule with a Flow Director rule at the same
ethtool location leaves the old ACL TCAM entry programmed and active.
- [Medium] In ice_ntuple_update_list_entry() the new last-ACL-filter
cleanup calls ice_fdir_rem_flow(hw, ICE_BLK_ACL, old_fltr->flow_type),…
- [Medium] ice_set_fd_vsi_ctx() now unconditionally sets
ICE_AQ_VSI_PROP_ACL_VALID and programs acl_def_act (hardcoded miss-…
- [Medium] ACL ice_ntuple_fltr objects are inserted into
hw->fdir_list_head (input->acl_fltr = true) but the teardown paths…
- [Medium] ACL rule placement ignores the ethtool rule-ordering contract.
- [Medium] In ice_acl_add_rule_ethtool(), the new `del_entry` error path
unconditionally calls ice_flow_rem_entry(hw, ICE_BLK_ACL, entry_h).
- [Medium] ice_flow_rem_prof_sync() iterates prof->entries calling
ice_flow_rem_entry_sync() and only `break`s on the first failure; the…
- [Medium] ice_acl_rem_entry() only releases the scenario slot
(ice_acl_scen_free_entry_idx(), i.e. clearing the entry_bitmap bit…
- [Medium] In the in-place action-update branch (do_chg_action) of
ice_flow_acl_add_scen_entry_sync(), the driver releases the existing…
- [Medium] The new ice_flow_find_entry() converts a struct ice_flow_entry
pointer into a u64 'handle' and returns it after releasing both…
- [Low] Concern that ACL filters are never reprogrammed after a PF/core
reset (ice_fdir_replay_fltrs() skips acl_fltr entries and…
- [Low] ice_acl_prof_aq_send() is extended to set LIBIE_AQ_FLAG_RD for
ice_aqc_opc_program_acl_prof_ranges, but that opcode can never reach…
- [Low] ice_flow_find_entry()'s kernel-doc states it returns
'ICE_FLOW_ENTRY_ID_INVAL' when no entry is found, but no such macro…
- [Low] The new struct ice_acl_scen.entry_bitmap comment states that a
set bit means the entry is available, while the allocator and free…
- [Low] ice_flow_acl_find_scen_entry_cond() bounds its action-comparison
loop with the existing entry's count but dereferences both arrays:…
- [Low] ice_acl_add_entry() programs all cascaded TCAM slices - including
the leftmost slice that completes the match - before calling…
--
ice: program ACL entry
This completes the ACL filter programming path: the formatted key and the
action set are written into the scenario's TCAM entry and action memories,
scenario entry allocation/removal helpers are added, and the VSI context is
extended with the ACL section. Masked ntuple rules for tcp4, udp4, sctp4
and ip4 can now be installed through ethtool.
A few questions on the new code below.
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl.c b/drivers/net/ethernet/intel/ice/ice_acl.c
> index 3179ce33e365e..ed078e30abfab 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl.c
> @@ -156,7 +156,8 @@ static int ice_acl_prof_aq_send(struct ice_hw *hw, u16 opc, u8 prof_id,
> cmd = libie_aq_raw(&desc);
> cmd->profile_id = prof_id;
>
> - if (opc == ice_aqc_opc_program_acl_prof_extraction)
> + if (opc == ice_aqc_opc_program_acl_prof_extraction ||
> + opc == ice_aqc_opc_program_acl_prof_ranges)
> desc.flags |= cpu_to_le16(LIBIE_AQ_FLAG_RD);
>
> return ice_aq_send_cmd(hw, &desc, buf, sizeof(*buf), cd);
[Severity: Low]
Can ice_aqc_opc_program_acl_prof_ranges ever reach ice_acl_prof_aq_send()?
The only callers are ice_prgm_acl_prof_xtrct() (extraction) and
ice_query_acl_prof() (query prof).
Also, this helper's buf parameter is struct ice_aqc_acl_prof_generic_frmt *
and it always sends sizeof(*buf) of that type, while 0x0C1E takes a
struct ice_aqc_acl_profile_ranges buffer. The new
ice_prog_acl_prof_ranges() added by this patch builds its own descriptor
and sets LIBIE_AQ_FLAG_RD itself, so should this branch be dropped?
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl.h b/drivers/net/ethernet/intel/ice/ice_acl.h
> index 066665c9d963d..0c8163e585d9d 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl.h
> +++ b/drivers/net/ethernet/intel/ice/ice_acl.h
> @@ -69,6 +70,11 @@ struct ice_acl_scen {
> * participate in this scenario
> */
> DECLARE_BITMAP(act_mem_bitmap, ICE_AQC_MAX_ACTION_MEMORIES);
> +
> + /* If nth bit of entry_bitmap is set, then nth entry will
> + * be available in this scenario
> + */
> + DECLARE_BITMAP(entry_bitmap, ICE_MAX_ACL_TCAM_ENTRY);
[Severity: Low]
Is the polarity in this comment inverted? The users treat a set bit as
"allocated":
if (!test_and_set_bit(i, scen->entry_bitmap))
return i; /* allocate */
if (!test_and_clear_bit(idx, scen->entry_bitmap))
return -ENOENT; /* free */
if (!test_bit(entry_idx, scen->entry_bitmap))
return -ENOENT; /* remove */
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> index 6a60da3034cf2..0ee86236bc37a 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> @@ -29,6 +34,51 @@ static void ice_acl_init_entry(struct ice_acl_scen *scen)
> +static u16 ice_acl_scen_assign_entry_idx(struct ice_acl_scen *scen,
> + enum ice_acl_entry_prio prio)
> +{
> + u16 first_idx, last_idx, i;
> + s8 step;
> +
> + if (prio >= ICE_ACL_MAX_PRIO)
> + return ICE_ACL_SCEN_ENTRY_INVAL;
> +
> + first_idx = scen->first_idx[prio];
> + last_idx = scen->last_idx[prio];
> + step = first_idx <= last_idx ? 1 : -1;
> +
> + for (i = first_idx; i != last_idx + step; i += step)
> + if (!test_and_set_bit(i, scen->entry_bitmap))
> + return i;
> +
> + return ICE_ACL_SCEN_ENTRY_INVAL;
> +}
[Severity: Medium]
How is the ethtool rule ordering contract honoured here?
include/uapi/linux/ethtool.h says for struct ethtool_rx_flow_spec:
* @location: Location of rule in the table. Locations must be
* numbered such that a flow matching multiple rules will be
* classified according to the first (lowest numbered) rule.
ice_acl_add_rule_ethtool() passes ICE_FLOW_PRIO_NORMAL for every rule and
uses fsp->location only as the flow entry id, and this function picks the
first free slot of the priority partition without ever seeing the
location. For two overlapping masked rules, doesn't the hardware match
order end up being the order in which they were added rather than the
location order?
[ ... ]
> @@ -887,3 +937,209 @@ int ice_acl_destroy_tbl(struct ice_hw *hw)
> +int ice_acl_add_entry(struct ice_hw *hw, struct ice_acl_scen *scen,
> + enum ice_acl_entry_prio prio, u8 *keys, u8 *inverts,
> + struct ice_acl_act_entry *acts, u8 acts_cnt,
> + u16 *entry_idx)
> +{
[ ... ]
> + offset = num_cscd - i - 1;
> + memcpy(&buf.entry_key.val,
> + &keys[offset * sizeof(buf.entry_key.val)],
> + sizeof(buf.entry_key.val));
> + memcpy(&buf.entry_key_invert.val,
> + &inverts[offset * sizeof(buf.entry_key_invert.val)],
> + sizeof(buf.entry_key_invert.val));
> + err = ice_aq_program_acl_entry(hw, entry_tcam + offset, idx,
> + &buf, NULL);
> + if (err) {
> + ice_debug(hw, ICE_DBG_ACL, "aq program acl entry failed status: %d\n",
> + err);
> + goto out;
> + }
> + }
> +
> + err = ice_acl_prog_act(hw, scen, acts, acts_cnt, *entry_idx);
[Severity: Low]
Is there a window here where the entry matches but has no action? The
loop programs all cascaded slices, including the leftmost one that
completes the match, and only afterwards does ice_acl_prog_act() program
the action memory in a separate AdminQ command.
The action memories are zeroed at table init, and mdid == 0 is documented
as NOP in struct ice_acl_act_entry, so for the duration of that extra
round trip a matching packet gets a NOP instead of the requested
drop/redirect. If the new entry lands in a higher priority slot than an
existing overlapping rule, does it transiently shadow that rule?
Would programming the action memory first and publishing the completing
slice last avoid this?
> +
> +out:
> + if (err) {
> + ice_acl_rem_entry(hw, scen, *entry_idx);
> + *entry_idx = 0;
> + }
> +
> + return err;
> +}
[ ... ]
> +int ice_acl_rem_entry(struct ice_hw *hw, struct ice_acl_scen *scen,
> + u16 entry_idx)
> +{
[ ... ]
> + for (i = 0; i < num_cscd; i++) {
> + int aq_err = ice_aq_program_acl_entry(hw, entry_tcam + i, idx,
> + &buf, NULL);
> + if (aq_err) {
> + dev_warn(ice_hw_to_dev(hw), "AQ program ACL entry failed, status: %d\n",
> + aq_err);
> + err = aq_err;
> + }
> + }
[ ... ]
> + if (!err)
> + err = ice_acl_scen_free_entry_idx(scen, entry_idx);
> +
> + return err;
> +}
[Severity: Medium]
[Sources: gpt-5-6-sol, gpt-6-astra, opus-5
Can this leak scenario entries? The bit reserved by test_and_set_bit() in
ice_acl_scen_assign_entry_idx() is only released when every
ice_aq_program_acl_entry()/ice_aq_program_actpair() invalidation above
succeeded. The loops themselves treat an AdminQ failure as non-fatal
(dev_warn() and continue), so on such a failure the bit stays set and no
other path clears it.
Each such removal would then permanently consume one TCAM slot until
ice_acl_scen_assign_entry_idx() starts returning
ICE_ACL_SCEN_ENTRY_INVAL and all further adds fail with -ENOSPC.
Related, the rollback in ice_acl_add_entry() calls
ice_acl_rem_entry(hw, scen, *entry_idx) and discards the return value
before zeroing *entry_idx, so a failed add can lose the index it just
reserved.
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.c b/drivers/net/ethernet/intel/ice/ice_acl_main.c
> index 6313d511398a2..171b7bf8519c6 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_main.c
> @@ -371,6 +371,12 @@ int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
> flow = ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT);
> hw_prof = hw->acl_prof[flow];
>
> + /* Look up existing HW entry before adding/replacing, so we can
> + * remove it if the new entry has different match data and a fresh
> + * TCAM slot was allocated instead of updating in place.
> + */
> + old_entry_h = ice_flow_find_entry(hw, ICE_BLK_ACL, fsp->location);
> +
> err = ice_flow_add_entry(hw, ICE_BLK_ACL, hw_prof->prof_id,
> fsp->location, vsi->idx, ICE_FLOW_PRIO_NORMAL,
> input, acts, ICE_ACL_NUM_ACT, &entry_h);
> @@ -379,8 +385,31 @@ int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
> goto free_input;
> }
>
> + /* If the match data changed, ice_flow_acl_add_scen_entry_sync()
> + * allocated a new TCAM entry rather than updating in place, leaving
> + * the old entry still programmed. Remove it.
> + */
> + if (old_entry_h != ICE_FLOW_ENTRY_HANDLE_INVAL &&
> + old_entry_h != entry_h) {
> + err = ice_flow_rem_entry(hw, ICE_BLK_ACL, old_entry_h);
> + if (err)
> + goto del_entry;
> + }
> +
> + input->acl_fltr = true;
> +
> + mutex_lock(&hw->fdir_fltr_lock);
> + /* input struct is added to the HW filter list */
> + err = ice_ntuple_update_list_entry(pf, input, fsp->location);
> + mutex_unlock(&hw->fdir_fltr_lock);
> + if (err)
> + goto del_entry;
> +
> return 0;
>
> +del_entry:
> + ice_flow_rem_entry(hw, ICE_BLK_ACL, entry_h);
> +
[Severity: Medium]
Is entry_h always a freshly created resource on this error path? On the
update path ice_flow_acl_add_scen_entry_sync() can return the
pre-existing entry:
*entry = exist;
which is still referenced by a live software filter record. The
failures that reach del_entry include the early returns of
ice_ntuple_update_list_entry():
if (ice_is_reset_in_progress(pf->state))
return -EBUSY;
vsi = ice_get_main_vsi(pf);
if (!vsi)
return -EINVAL;
Both happen before old_fltr is touched, so does the unconditional
ice_flow_rem_entry() here tear down the hardware state of a filter that
stays in hw->fdir_list_head? Afterwards ice_del_acl_ethtool() cannot
find an entry for that id, ice_flow_rem_entry() returns -EINVAL, and
ice_ntuple_update_list_entry() returns before list_del()/kfree(), so the
rule is listed by ethtool -n forever while not being programmed.
[Severity: Medium]
With input->acl_fltr = true the filter is handed to hw->fdir_list_head,
but do the owners of that list free ACL entries?
ice_fdir_del_all_fltrs() starts with:
list_for_each_entry_safe(f_rule, tmp, &hw->fdir_list_head, fltr_node) {
if (f_rule->acl_fltr)
continue;
and ice_deinit_acl() only calls ice_acl_rem_flows()/ice_acl_destroy_tbl()
and frees hw->acl_prof, never walking the filter list. On unload
(ice_deinit_fdir() -> ice_vsi_manage_fdir(vsi, false) ->
ice_fdir_del_all_fltrs()) does every kzalloc'd struct ice_ntuple_fltr
with acl_fltr set leak?
The same skip also applies to ice_remove_q_channels(), which removes
ntuple filters precisely because queue configuration is changing but only
calls ice_fdir_del_all_fltrs(), so queue-directed ACL rules would keep
pointing at queues that no longer exist.
[Severity: Low]
At this point in the series ACL filters are not reprogrammed after a
PF/core reset: ice_fdir_replay_fltrs() skips acl_fltr records and
ice_rebuild() has no ACL restoration, so the rules stay listed by
ethtool -n while no longer matching in hardware. This is addressed later
in the same series by the commit adding ice_acl_replay_flows() and
ice_acl_replay_fltrs() to ice_rebuild(), so it is only an issue for the
intermediate state at this commit. Would folding the ordering
differently avoid the transient?
> free_input:
> kfree(input);
>
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index f67b6e1c0ffbf..0ac44d38fdbdf 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> @@ -1895,17 +1910,43 @@ ice_ntuple_update_list_entry(struct ice_pf *pf, struct ice_ntuple_fltr *input,
>
> old_fltr = ice_fdir_find_fltr_by_idx(hw, fltr_idx);
> if (old_fltr) {
> - err = ice_fdir_write_all_fltr(pf, old_fltr, false);
> - if (err)
> - return err;
> + if (old_fltr->acl_fltr) {
> + /* ACL filter - if the input buffer is present
> + * then this is an update. The caller has already
> + * applied the HW change (including removing the old
> + * TCAM entry when match data changed), so just update
> + * the SW structures. If no input then this is a
> + * delete so we should delete the filter from the HW
> + * and clean up our SW structures.
> + */
> + if (!input) {
> + err = ice_del_acl_ethtool(hw, old_fltr);
> + if (err)
> + return err;
> + }
[Severity: High]
Does this assumption hold for all callers? The comment says the caller
has already applied the hardware change, which is true for
ice_acl_add_rule_ethtool() (it looks up old_entry_h and removes it), but
ice_add_ntuple_ethtool() also calls:
ret = ice_ntuple_update_list_entry(pf, input, fsp->location);
with a non-NULL input and no ACL hardware removal at all.
So for an ACL rule at location N replaced by a Flow Director rule at the
same location, the "if (!input)" guard skips ice_del_acl_ethtool(), and a
few lines below:
list_del(&old_fltr->fltr_node);
kfree(old_fltr);
drops the only record of the ACL entry id. Does that leave the old ACL
TCAM entry armed (possibly a drop rule) with no way for userspace to see
or delete it, plus a leaked ice_flow_entry, a leaked entry_bitmap slot
and a stale hw->acl_fltr_cnt?
> + } else {
> + /* FD filter */
> + err = ice_fdir_write_all_fltr(pf, old_fltr, false);
> + if (err)
> + return err;
> + }
> +
> ice_ntuple_update_cntrs(hw, old_fltr, false);
> /* update sb-filters count, specific to ring->channel */
> ice_update_per_q_fltr(vsi, old_fltr->orig_q_index, false);
> - if (!input && !hw->fdir_fltr_cnt[old_fltr->flow_type])
> + /* Also delete the HW filter info if we have just deleted the
> + * last filter of flow_type.
> + */
> + if (!old_fltr->acl_fltr && !input &&
> + !hw->fdir_fltr_cnt[old_fltr->flow_type])
> /* we just deleted the last filter of flow_type so we
> * should also delete the HW filter info.
> */
> ice_fdir_do_rem_flow(pf, old_fltr->flow_type);
> + else if (old_fltr->acl_fltr && !input &&
> + !hw->acl_fltr_cnt[old_fltr->flow_type])
> + ice_fdir_rem_flow(hw, ICE_BLK_ACL, old_fltr->flow_type);
> +
[Severity: Medium]
Does this ICE_BLK_ACL call do anything? ice_fdir_rem_flow() resolves the
profile with ice_fdir_get_hw_prof():
if (blk == ICE_BLK_FD && hw->fdir_prof)
return hw->fdir_prof[flow];
return NULL;
and returns immediately on NULL, so for ICE_BLK_ACL it looks like an
unconditional no-op and hw->acl_prof[flow] is never torn down.
Then a later add for the same flow-type still sees the old input set in
ice_acl_prof_add_ethtool():
if (hw_prof->seg) {
if (!memcmp(hw_prof->seg, seg, sizeof(*seg))) {
kfree(seg);
return 0;
}
err = -EINVAL;
So after deleting all tcp4 ACL rules, does adding a tcp4 rule with a
different mask selection fail with -EINVAL for the rest of the
interface's lifetime? Should this use the ACL API instead, the way
ice_acl_rem_flows() does with
ice_flow_rem_prof(hw, ICE_BLK_ACL, prof->prof_id)? The two profile types
also differ (struct ice_acl_hw_prof vs struct ice_fd_hw_prof).
> list_del(&old_fltr->fltr_node);
> kfree(old_fltr);
> }
> diff --git a/drivers/net/ethernet/intel/ice/ice_flow.c b/drivers/net/ethernet/intel/ice/ice_flow.c
> index 192569ff1d6c1..e0a1d8463d6f5 100644
> --- a/drivers/net/ethernet/intel/ice/ice_flow.c
> +++ b/drivers/net/ethernet/intel/ice/ice_flow.c
> @@ -1741,6 +1741,16 @@ static int ice_flow_rem_entry_sync(struct ice_hw *hw, enum ice_block blk,
> return -EINVAL;
>
> if (blk == ICE_BLK_ACL) {
> + int err;
> +
> + if (!entry->prof)
> + return -EINVAL;
> +
> + err = ice_acl_rem_entry(hw, entry->prof->cfg.scen,
> + entry->scen_entry_idx);
> + if (err)
> + return err;
> +
[Severity: Medium]
This makes the ACL branch of ice_flow_rem_entry_sync() fallible for the
first time, and the early return happens before list_del() and
ice_dealloc_flow_entry(). Is the caller prepared for that?
ice_flow_rem_prof_sync() does:
list_for_each_entry_safe(e, t, &prof->entries, l_entry) {
status = ice_flow_rem_entry_sync(hw, blk, e);
if (status)
break;
}
mutex_unlock(&prof->entries_lock);
and the loop's status is then overwritten and execution continues to
ice_rem_prof() and devm_kfree(prof).
So on an AdminQ failure during teardown (for example
ice_acl_rem_flows() -> ice_flow_rem_prof() after a reset), does the
failing entry and everything after it stay linked in prof->entries and
never get freed, while the profile they point to through entry->prof is
devm_kfree()d?
> if (entry->acts_cnt && entry->acts)
> ice_flow_acl_free_act_cntr(hw, entry->acts,
> entry->acts_cnt);
[ ... ]
> @@ -2211,6 +2260,44 @@ int ice_flow_rem_prof(struct ice_hw *hw, enum ice_block blk, u64 prof_id)
> + * Return: flow entry handle if entry found, ICE_FLOW_ENTRY_ID_INVAL otherwise
> + */
[Severity: Low]
ICE_FLOW_ENTRY_ID_INVAL does not exist anywhere in the tree; the code
returns ICE_FLOW_ENTRY_HANDLE_INVAL (defined as 0 in ice_flow.h), which
is also what the new callers compare against.
> +u64 ice_flow_find_entry(struct ice_hw *hw, enum ice_block blk, u64 entry_id)
> +{
> + struct ice_flow_entry *found = NULL;
> + struct ice_flow_prof *p;
> +
> + mutex_lock(&hw->fl_profs_locks[blk]);
> +
> + list_for_each_entry(p, &hw->fl_profs[blk], l_entry) {
> + struct ice_flow_entry *e;
> +
> + mutex_lock(&p->entries_lock);
> + list_for_each_entry(e, &p->entries, l_entry)
> + if (e->id == entry_id) {
> + found = e;
> + break;
> + }
> + mutex_unlock(&p->entries_lock);
> +
> + if (found)
> + break;
> + }
> +
> + mutex_unlock(&hw->fl_profs_locks[blk]);
> +
> + return found ? ICE_FLOW_ENTRY_HNDL(found) : ICE_FLOW_ENTRY_HANDLE_INVAL;
> +}
[Severity: Medium]
What keeps the returned object alive? The raw struct ice_flow_entry
pointer is published as a handle after both hw->fl_profs_locks[blk] and
p->entries_lock have been dropped, with no refcount and no RCU.
Both new callers then dereference it unlocked, since ice_flow_rem_entry()
reads entry->prof, takes prof->entries_lock and list_del()s
entry->l_entry:
ice_del_acl_ethtool()
entry = ice_flow_find_entry(hw, ICE_BLK_ACL, fltr->fltr_id);
return ice_flow_rem_entry(hw, ICE_BLK_ACL, entry);
In ice_acl_add_rule_ethtool() the handle is additionally cached across a
whole ice_flow_add_entry() call, and the only validity test is
old_entry_h != entry_h, which shows identity rather than liveness. Would
resolving the id again under prof->entries_lock, or removing under that
lock, be safer than caching a bare pointer?
> @@ -2554,6 +2641,346 @@ static int ice_flow_acl_frmt_entry(struct ice_hw *hw,
> +static struct ice_flow_entry *
> +ice_flow_acl_find_scen_entry_cond(struct ice_flow_prof *prof,
> + struct ice_flow_entry *e, bool *do_chg_action,
> + bool *do_add_entry, bool *do_rem_entry)
> +{
[ ... ]
> + list_for_each_entry(p, &prof->entries, l_entry) {
> + if (memcmp(p->entry, e->entry, p->entry_sz))
> + continue;
> +
> + /* Now we have the same matching_data */
> +
> + *do_add_entry = false;
> + return_entry = p;
> +
> + if (p->priority != e->priority) {
> + /* matching data && !priority */
> + *do_add_entry = true;
> + *do_rem_entry = true;
> + break;
> + }
> +
> + /* Now we have matching_data && priority */
> +
> + if (p->acts_cnt != e->acts_cnt)
> + *do_chg_action = true;
> + for (int i = 0; i < p->acts_cnt; i++) {
> + if (memcmp(&p->acts[i], &e->acts[i],
> + sizeof(struct ice_flow_action))) {
[Severity: Low]
Can this read past the end of e->acts? The loop is bounded by
p->acts_cnt but indexes both arrays, and the check just above has already
established that the counts can differ. e->acts comes from
ice_flow_acl_frmt_entry():
e->acts = kmemdup(acts, acts_cnt * sizeof(*acts), GFP_KERNEL);
so it is sized exactly to e->acts_cnt. Today every ACL entry is created
with ICE_ACL_NUM_ACT (1) and ice_flow_add_entry() rejects acts_cnt == 0
for ICE_BLK_ACL, so the counts are always equal, but
ICE_FLOW_ACL_MAX_NUM_ACT is 2. Should this use
min(p->acts_cnt, e->acts_cnt), or skip the loop when the counts differ?
> + *do_chg_action = true;
> + break;
> + }
> + }
[ ... ]
> +static int ice_flow_acl_add_scen_entry_sync(struct ice_hw *hw,
> + struct ice_flow_prof *prof,
> + struct ice_flow_entry **entry)
> +{
[ ... ]
> + } else {
> + if (do_chg_action) {
> + /* For the action memory info, update the SW's copy of
> + * exist entry with e's action memory info
> + */
> + if (exist->acts_cnt && exist->acts)
> + ice_flow_acl_free_act_cntr(hw, exist->acts,
> + exist->acts_cnt);
> + kfree(exist->acts);
> + exist->acts = kzalloc_objs(*exist->acts, e->acts_cnt);
> + if (!exist->acts) {
> + exist->acts_cnt = 0;
> + err = -ENOMEM;
> + goto out;
> + }
> + exist->acts_cnt = e->acts_cnt;
> +
> + memcpy(exist->acts, e->acts,
> + sizeof(struct ice_flow_action) * e->acts_cnt);
> +
> + err = ice_acl_prog_act(hw, prof->cfg.scen, acts,
> + e->acts_cnt,
> + exist->scen_entry_idx);
> + if (err) {
> + /* HW programming failed; e's counters were not
> + * transferred. Drop exist's new acts so the
> + * caller's free_cntrs path does not release IDs
> + * still referenced by exist->acts.
> + */
> + kfree(exist->acts);
> + exist->acts = NULL;
> + exist->acts_cnt = 0;
> + goto out;
> + }
[Severity: Medium]
What describes the hardware state after this error path? The existing
entry's counters were already released by ice_flow_acl_free_act_cntr()
before any hardware was touched, and ice_acl_prog_act() issues one
ice_aq_program_actpair() per matching action memory in the cascade, so a
failure on the second or later command leaves some action memories
already updated.
This path then sets exist->acts = NULL and exist->acts_cnt = 0 but leaves
the TCAM entry programmed and exist on prof->entries, so the device keeps
executing an action set (old, new, or a mix) that the driver no longer
tracks.
Also, with acts_cnt zeroed, ice_flow_rem_entry_sync() will skip
ice_flow_acl_free_act_cntr():
if (entry->acts_cnt && entry->acts)
ice_flow_acl_free_act_cntr(hw, entry->acts,
entry->acts_cnt);
so are the counter IDs still referenced by the action memory
unreclaimable, while the ones released earlier can be handed out to
another filter?
> +
> + /* e's counter IDs are now owned by exist->acts; clear
> + * acts_cnt so the trailing ice_flow_acl_free_act_cntr
> + * call does not release IDs that exist->acts owns.
> + * ice_dealloc_flow_entry still frees the acts memory
> + * via kfree(e->acts).
> + */
> + e->acts_cnt = 0;
> + }
[ ... ]
> + /* As we don't add the new entry to our SW DB, free its HW
> + * counter resources and deallocate its memory, then return
> + * the exist entry to the caller. Transfer the new caller's
> + * entry_id to exist so that ice_flow_find_entry() can locate
> + * it and the caller's handle remains valid for deletion.
> + */
> + if (e->acts_cnt && e->acts)
> + ice_flow_acl_free_act_cntr(hw, e->acts, e->acts_cnt);
> + exist->id = e->id;
> + kfree(e->entry);
> + kfree(e->range_buf);
> + kfree(e->acts);
> + devm_kfree(ice_hw_to_dev(hw), e);
> + *entry = exist;
> + }
[Severity: High]
Can overwriting exist->id orphan the rule that previously owned that id?
Two ethtool rules at different locations can end up here with
byte-identical keys:
ice_acl_is_dup_fltr() -> ice_acl_comp_rules() compares raw values and
masks, so rules that differ only in masked-out bits are accepted as
distinct:
a->ip.v4.dst_port == b->ip.v4.dst_port &&
a->ip.v4.src_port == b->ip.v4.src_port &&
but ice_gen_key_word() emits the fixed don't-care key/invert pair for
every masked bit, ignoring the value, so both rules produce identical
e->entry buffers and ice_flow_acl_find_scen_entry_cond()'s
memcmp(p->entry, e->entry, p->entry_sz) matches. ethtool always passes
ICE_FLOW_PRIO_NORMAL, so do_rem_entry stays false, do_add_entry becomes
false, and this merge branch runs.
After exist->id = e->id, no flow entry carries the first rule's id any
more, although its ice_ntuple_fltr is still in hw->fdir_list_head and is
still reported by ethtool -n. Deleting the second rule invalidates the
single shared TCAM entry, so does the first rule remain listed but no
longer enforced? And does deleting the first rule then fail forever,
since ice_del_acl_ethtool() gets handle 0 and ice_flow_rem_entry()
returns -EINVAL before ice_ntuple_update_list_entry() reaches
list_del()/kfree(), leaving hw->acl_fltr_cnt[] and
hw->ntuple_active_fltr_cnt inflated?
> +out:
> + kfree(acts);
> +
> + return err;
> +}
[ ... ]
> diff --git a/drivers/net/ethernet/intel/ice/ice_lib.c b/drivers/net/ethernet/intel/ice/ice_lib.c
> index 9e08db376d3d0..8e842b29f84f2 100644
> --- a/drivers/net/ethernet/intel/ice/ice_lib.c
> +++ b/drivers/net/ethernet/intel/ice/ice_lib.c
> @@ -1118,7 +1118,7 @@ static void ice_set_fd_vsi_ctx(struct ice_vsi_ctx *ctxt, struct ice_vsi *vsi)
> vsi->type != ICE_VSI_VF && vsi->type != ICE_VSI_CHNL)
> return;
>
> - val = ICE_AQ_VSI_PROP_FLOW_DIR_VALID;
> + val = ICE_AQ_VSI_PROP_FLOW_DIR_VALID | ICE_AQ_VSI_PROP_ACL_VALID;
> ctxt->info.valid_sections |= cpu_to_le16(val);
[Severity: Medium]
Is ICE_FLAG_FD_ENA the right condition for this? The only call site is
still:
if (test_bit(ICE_FLAG_FD_ENA, pf->flags))
ice_set_fd_vsi_ctx(ctxt, vsi);
so the ACL section is now advertised for every PF/CTRL/VF/CHNL VSI
whenever Flow Director is on, with no check that ACL was actually
initialized. ice_init_features() treats ACL init failure as non-fatal:
/* Note: ACL init failure is non-fatal to load */
if (ice_init_acl(pf))
dev_err(dev, "Failed to initialize ACL\n");
so hw->acl_tbl can be NULL while the VSI context claims the ACL section.
Conversely, for a configuration where ACL is available but the FD flag is
clear, the ACL VSI configuration is never applied even though
ice_add_ntuple_ethtool() routes rules to ACL based only on
pf->hw.acl_tbl.
> dflt_q = 0;
> dflt_q_group = 0;
[ ... ]
> @@ -1144,6 +1144,14 @@ static void ice_set_fd_vsi_ctx(struct ice_vsi_ctx *ctxt, struct ice_vsi *vsi)
> /* priority of the default qindex action */
> val |= FIELD_PREP(ICE_AQ_VSI_FD_DEF_PRIORITY_M, dflt_q_prio);
> ctxt->info.fd_report_opt = cpu_to_le16(val);
> +
> +#define ICE_ACL_RX_PROF_MISS_CNTR \
> + FIELD_PREP_CONST(ICE_AQ_VSI_ACL_DEF_RX_PROF_M, 2)
> +#define ICE_ACL_RX_TBL_MISS_CNTR \
> + FIELD_PREP_CONST(ICE_AQ_VSI_ACL_DEF_RX_TABLE_M, 3)
> +
> + val = ICE_ACL_RX_PROF_MISS_CNTR | ICE_ACL_RX_TBL_MISS_CNTR;
> + ctxt->info.acl_def_act = cpu_to_le16(val);
> }
Where do the miss-counter indices 2 and 3 come from? They are not
allocated anywhere in the driver and there is no comment explaining the
choice.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v2 10/12] ice: add ACL reset recovery and NTUPLE feature toggle
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (8 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 09/12] ice: program " Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-18 15:44 ` Loktionov, Aleksandr
2026-09-21 19:57 ` netdev-bot+sashiko
2026-09-17 18:39 ` [PATCH net-next v2 11/12] ice: re-introduce ice_dealloc_flow_entry() helper Tony Nguyen
` (3 subsequent siblings)
13 siblings, 2 replies; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Marcin Szycik, anthony.l.nguyen, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Rinitha S
From: Marcin Szycik <marcin.szycik@linux.intel.com>
Extend ACL support to survive PF resets. Add ice_acl_replay_flows() to
rebuild HW flow profiles from preserved SW state, and
ice_acl_replay_fltrs() to reprogram TCAM entries from the SW filter
list. Wire both into ice_rebuild(). On failure, don't fail reset, rather
delete all ACL filters from the SW list.
Reset per-profile HW extraction sequences and range checkers for all
profiles inside ice_acl_create_hw(). This state is separate from the
TCAM entries and survives a PF reset. Explicitly clearing it ensures
that the old configuration is erased.
Add the ICE_FLAG_ACL_ENA PF flag to track ACL status. ACL is always
available, so set it unconditionally in driver initialization. Use the
new flag to track the NTUPLE ethtool feature flag, alongside Flow
Director, as both blocks are used to implement ethtool NTUPLE filters.
Like with Flow Director, disabling the flag deletes all filters, but
enabling it does not reprogram filters.
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice.h | 4 +
drivers/net/ethernet/intel/ice/ice_acl.h | 5 +-
drivers/net/ethernet/intel/ice/ice_acl_ctrl.c | 43 +++++-----
drivers/net/ethernet/intel/ice/ice_acl_main.c | 47 +++++++++++
drivers/net/ethernet/intel/ice/ice_acl_main.h | 1 +
.../ethernet/intel/ice/ice_ethtool_ntuple.c | 79 +++++++++++++++++++
drivers/net/ethernet/intel/ice/ice_main.c | 47 +++++++++++
7 files changed, 201 insertions(+), 25 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice.h b/drivers/net/ethernet/intel/ice/ice.h
index 61a320eaf3c5..fcf1130f511f 100644
--- a/drivers/net/ethernet/intel/ice/ice.h
+++ b/drivers/net/ethernet/intel/ice/ice.h
@@ -503,6 +503,7 @@ enum ice_pf_flags {
ICE_FLAG_DCB_CAPABLE,
ICE_FLAG_DCB_ENA,
ICE_FLAG_FD_ENA,
+ ICE_FLAG_ACL_ENA,
ICE_FLAG_PTP_SUPPORTED, /* PTP is supported by NVM */
ICE_FLAG_ADV_FEATURES,
ICE_FLAG_TC_MQPRIO, /* support for Multi queue TC */
@@ -1019,10 +1020,12 @@ int ice_init_rdma(struct ice_pf *pf);
void ice_deinit_rdma(struct ice_pf *pf);
bool ice_is_wol_supported(struct ice_hw *hw);
void ice_fdir_del_all_fltrs(struct ice_vsi *vsi);
+void ice_acl_del_all_fltrs(struct ice_vsi *vsi);
int
ice_fdir_write_fltr(struct ice_pf *pf, struct ice_ntuple_fltr *input, bool add,
bool is_tun);
void ice_vsi_manage_fdir(struct ice_vsi *vsi, bool ena);
+void ice_vsi_manage_acl(struct ice_vsi *vsi, bool ena);
int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
int ice_get_ethtool_fdir_entry(struct ice_hw *hw, struct ethtool_rxnfc *cmd);
@@ -1040,6 +1043,7 @@ ice_get_fdir_fltr_ids(struct ice_hw *hw, struct ethtool_rxnfc *cmd,
u32 *rule_locs);
void ice_fdir_rem_adq_chnl(struct ice_hw *hw, u16 vsi_idx);
void ice_acl_rem_flows(struct ice_hw *hw);
+void ice_acl_replay_flows(struct ice_hw *hw);
void ice_fdir_release_flows(struct ice_hw *hw);
void ice_fdir_replay_flows(struct ice_hw *hw);
void ice_fdir_replay_fltrs(struct ice_pf *pf);
diff --git a/drivers/net/ethernet/intel/ice/ice_acl.h b/drivers/net/ethernet/intel/ice/ice_acl.h
index 0c8163e585d9..65e16ad1c783 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl.h
+++ b/drivers/net/ethernet/intel/ice/ice_acl.h
@@ -6,6 +6,9 @@
#include "ice_common.h"
+/* Maximum number of ACL HW profiles */
+#define ICE_ACL_MAX_PROF 128
+
/* Marks a PF scenario slot as unused in the ACL profile extraction table */
#define ICE_ACL_INVALID_SCEN 0x3f
@@ -124,7 +127,7 @@ struct ice_acl_cntrs {
};
int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params *params);
-int ice_acl_destroy_tbl(struct ice_hw *hw);
+void ice_acl_destroy_tbl(struct ice_hw *hw);
int ice_acl_create_scen(struct ice_hw *hw, u16 match_width, u16 num_entries,
u16 *scen_id);
int ice_aq_alloc_acl_tbl(struct ice_hw *hw, struct ice_acl_alloc_tbl *tbl,
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
index 0ee86236bc37..4ea08bd9784c 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
+++ b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
@@ -877,9 +877,10 @@ static int ice_acl_destroy_scen(struct ice_hw *hw, u16 scen_id)
* ice_acl_destroy_tbl - Destroy a previously created LEM table for ACL
* @hw: pointer to the HW struct
*
- * Return: 0 on success, negative on error
+ * Continue on AQ errors so SW state is always cleaned up - e.g. after a reset,
+ * where the HW tables might be already gone.
*/
-int ice_acl_destroy_tbl(struct ice_hw *hw)
+void ice_acl_destroy_tbl(struct ice_hw *hw)
{
struct ice_acl_scen *pos_scen, *tmp_scen;
struct ice_aqc_acl_generic resp_buf;
@@ -887,7 +888,7 @@ int ice_acl_destroy_tbl(struct ice_hw *hw)
int err;
if (!hw->acl_tbl)
- return -ENOENT;
+ return;
/* Mark all the created scenario's TCAM to stop the packet lookup and
* delete them afterward
@@ -898,44 +899,38 @@ int ice_acl_destroy_tbl(struct ice_hw *hw)
if (err) {
ice_debug(hw, ICE_DBG_ACL, "ice_aq_query_acl_scen() failed. status: %d\n",
err);
- return err;
- }
-
- for (int i = 0; i < ICE_AQC_ACL_SLICES; i++) {
- buf.tcam_cfg[i].chnk_msk = 0;
- buf.tcam_cfg[i].start_cmp_set =
- ICE_AQC_ACL_ALLOC_SCE_START_CMP;
- }
+ } else {
+ for (int i = 0; i < ICE_AQC_ACL_SLICES; i++) {
+ buf.tcam_cfg[i].chnk_msk = 0;
+ buf.tcam_cfg[i].start_cmp_set =
+ ICE_AQC_ACL_ALLOC_SCE_START_CMP;
+ }
- for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++)
- buf.act_mem_cfg[i] = 0;
+ for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++)
+ buf.act_mem_cfg[i] = 0;
- err = ice_aq_update_acl_scen(hw, pos_scen->id, &buf, NULL);
- if (err) {
- ice_debug(hw, ICE_DBG_ACL, "ice_aq_update_acl_scen() failed. status: %d\n",
- err);
- return err;
+ err = ice_aq_update_acl_scen(hw, pos_scen->id, &buf,
+ NULL);
+ if (err)
+ ice_debug(hw, ICE_DBG_ACL, "scenario update failed, status: %d\n",
+ err);
}
err = ice_acl_destroy_scen(hw, pos_scen->id);
if (err) {
- ice_debug(hw, ICE_DBG_ACL, "deletion of scenario failed. status: %d\n",
+ ice_debug(hw, ICE_DBG_ACL, "deletion of scenario failed, status: %d\n",
err);
- return err;
}
}
err = ice_aq_dealloc_acl_tbl(hw, hw->acl_tbl->id, &resp_buf, NULL);
if (err) {
- ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL failed. status: %d\n",
+ ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL failed, status: %d\n",
err);
- return err;
}
kfree(hw->acl_tbl);
hw->acl_tbl = NULL;
-
- return 0;
}
/**
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.c b/drivers/net/ethernet/intel/ice/ice_acl_main.c
index 171b7bf8519c..d13e8bc1b701 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_acl_main.c
@@ -236,6 +236,53 @@ static void ice_acl_set_act_fwd_queue(struct ice_flow_action *action,
action->data.acl_act.value = cpu_to_le16(queue_index);
}
+/*
+ * ice_acl_replay_fltrs - replay ACL filters from the SW filter list
+ * @pf: board private structure
+ *
+ * Reprograms ACL TCAM entries after a reset using data preserved in the
+ * SW filter list. Relies on ice_acl_replay_flows() having been called first
+ * to restore the flow profiles.
+ */
+void ice_acl_replay_fltrs(struct ice_pf *pf)
+{
+ struct ice_vsi *vsi = ice_get_main_vsi(pf);
+ struct ice_hw *hw = &pf->hw;
+ struct ice_ntuple_fltr *f_rule;
+
+ if (!vsi)
+ return;
+
+ list_for_each_entry(f_rule, &hw->fdir_list_head, fltr_node) {
+ struct ice_flow_action acts[ICE_ACL_NUM_ACT];
+ struct ice_acl_hw_prof *hw_prof;
+ u64 entry_h = 0;
+ int err;
+
+ if (!f_rule->acl_fltr)
+ continue;
+
+ if (!hw->acl_prof || !hw->acl_prof[f_rule->flow_type])
+ continue;
+
+ hw_prof = hw->acl_prof[f_rule->flow_type];
+
+ memset(&acts, 0, sizeof(acts));
+ if (f_rule->dest_ctl == ICE_FLTR_PRGM_DESC_DEST_DROP_PKT)
+ ice_acl_set_act_drop(&acts[0]);
+ else
+ ice_acl_set_act_fwd_queue(&acts[0], f_rule->q_index);
+
+ err = ice_flow_add_entry(hw, ICE_BLK_ACL, hw_prof->prof_id,
+ f_rule->fltr_id, vsi->idx,
+ ICE_FLOW_PRIO_NORMAL, f_rule, acts,
+ ICE_ACL_NUM_ACT, &entry_h);
+ if (err)
+ dev_warn(ice_pf_to_dev(pf), "Could not reprogram filter %d, status %d\n",
+ f_rule->fltr_id, err);
+ }
+}
+
/**
* ice_acl_comp_rules - compare two ACL filters
* @a: first ACL filter
diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.h b/drivers/net/ethernet/intel/ice/ice_acl_main.h
index 6665af2e7053..54053954b244 100644
--- a/drivers/net/ethernet/intel/ice/ice_acl_main.h
+++ b/drivers/net/ethernet/intel/ice/ice_acl_main.h
@@ -6,4 +6,5 @@
#include "ice.h"
#include <linux/ethtool.h>
int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd);
+void ice_acl_replay_fltrs(struct ice_pf *pf);
#endif /* _ICE_ACL_MAIN_H_ */
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
index 0ac44d38fdbd..fef8640d23fb 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
@@ -527,6 +527,37 @@ void ice_fdir_replay_flows(struct ice_hw *hw)
}
}
+/**
+ * ice_acl_replay_flows - replay HW ACL flow profiles
+ * @hw: pointer to HW instance
+ */
+void ice_acl_replay_flows(struct ice_hw *hw)
+{
+ if (!hw->acl_prof)
+ return;
+
+ for (enum ice_fltr_ptype flow = ICE_FLTR_PTYPE_NONF_NONE;
+ flow < ICE_FLTR_PTYPE_MAX; flow++) {
+ struct ice_flow_prof *hw_prof;
+ struct ice_acl_hw_prof *prof;
+ int err;
+
+ flow &= ~FLOW_EXT;
+ prof = hw->acl_prof[flow];
+ if (!prof || !prof->seg)
+ continue;
+
+ err = ice_flow_add_prof(hw, ICE_BLK_ACL, ICE_FLOW_RX,
+ prof->seg, 1, false, &hw_prof);
+ if (err) {
+ dev_err(ice_hw_to_dev(hw), "Could not replay ACL, flow type %d\n",
+ flow);
+ continue;
+ }
+ prof->prof_id = hw_prof->id;
+ }
+}
+
/**
* ice_parse_rx_flow_user_data - deconstruct user-defined data
* @fsp: pointer to ethtool Rx flow specification
@@ -1825,6 +1856,28 @@ static int ice_del_acl_ethtool(struct ice_hw *hw, struct ice_ntuple_fltr *fltr)
return ice_flow_rem_entry(hw, ICE_BLK_ACL, entry);
}
+/**
+ * ice_acl_del_all_fltrs - Delete all ACL filters from the filter list
+ * @vsi: the VSI being changed
+ *
+ * This function needs to be called while holding hw->fdir_fltr_lock
+ */
+void ice_acl_del_all_fltrs(struct ice_vsi *vsi)
+{
+ struct ice_ntuple_fltr *f_rule, *tmp;
+ struct ice_hw *hw = &vsi->back->hw;
+
+ list_for_each_entry_safe(f_rule, tmp, &hw->fdir_list_head, fltr_node) {
+ if (!f_rule->acl_fltr)
+ continue;
+
+ ice_del_acl_ethtool(hw, f_rule);
+ ice_ntuple_update_cntrs(hw, f_rule, false);
+ list_del(&f_rule->fltr_node);
+ kfree(f_rule);
+ }
+}
+
/**
* ice_vsi_manage_fdir - turn on/off flow director
* @vsi: the VSI being changed
@@ -1858,6 +1911,32 @@ void ice_vsi_manage_fdir(struct ice_vsi *vsi, bool ena)
mutex_unlock(&hw->fdir_fltr_lock);
}
+/**
+ * ice_vsi_manage_acl - turn on/off ACL
+ * @vsi: the VSI being changed
+ * @ena: boolean value indicating if this is an enable or disable request
+ */
+void ice_vsi_manage_acl(struct ice_vsi *vsi, bool ena)
+{
+ struct ice_pf *pf = vsi->back;
+ struct ice_hw *hw = &pf->hw;
+
+ if (ena) {
+ set_bit(ICE_FLAG_ACL_ENA, pf->flags);
+ return;
+ }
+
+ mutex_lock(&hw->fdir_fltr_lock);
+ if (!test_and_clear_bit(ICE_FLAG_ACL_ENA, pf->flags))
+ goto release_lock;
+
+ ice_acl_del_all_fltrs(vsi);
+ ice_acl_rem_flows(hw);
+
+release_lock:
+ mutex_unlock(&hw->fdir_fltr_lock);
+}
+
/**
* ice_fdir_do_rem_flow - delete flow and possibly add perfect flow
* @pf: PF structure
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index 3a10b2c2a44a..8a81d3e475a7 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -18,6 +18,7 @@
#include "ice_sf_eth.h"
#include "ice_hwmon.h"
#include "ice_acl.h"
+#include "ice_acl_main.h"
/* Including ice_trace.h with CREATE_TRACE_POINTS defined will generate the
* ice tracepoint functions. This must be done exactly once across the
* ice driver.
@@ -3947,6 +3948,9 @@ static void ice_set_pf_caps(struct ice_pf *pf)
func_caps->fd_fltr_best_effort);
}
+ /* ACL is always initially available */
+ set_bit(ICE_FLAG_ACL_ENA, pf->flags);
+
clear_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags);
if (func_caps->common_cap.ieee_1588)
set_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags);
@@ -4369,6 +4373,30 @@ static int ice_acl_create_hw(struct ice_pf *pf)
if (err)
goto destroy_table;
+ /* Reset profile extraction sequences and range checkers for all
+ * possible HW profile IDs. The TCAM entries are already zeroed by
+ * ice_acl_init_tbl() inside ice_acl_create_tbl(), but profile
+ * extraction and range checker state is separate per-profile HW state
+ * that survives PF reset, therefore must be brought back to default
+ * state.
+ */
+ for (u8 prof_id = 0; prof_id < ICE_ACL_MAX_PROF; prof_id++) {
+ struct ice_aqc_acl_prof_generic_frmt xtrct_buf = {};
+ struct ice_aqc_acl_profile_ranges range_buf = {};
+
+ memset(xtrct_buf.pf_scenario_num, ICE_ACL_INVALID_SCEN,
+ sizeof(xtrct_buf.pf_scenario_num));
+ err = ice_prgm_acl_prof_xtrct(hw, prof_id, &xtrct_buf, NULL);
+ if (err)
+ dev_warn(ice_pf_to_dev(pf), "Failed to reset profile extraction for profile %u\n",
+ prof_id);
+
+ ice_prog_acl_prof_ranges(hw, prof_id, &range_buf, NULL);
+ if (err)
+ dev_warn(ice_pf_to_dev(pf), "Failed to reset range checkers for profile %u\n",
+ prof_id);
+ }
+
return 0;
destroy_table:
@@ -6614,6 +6642,7 @@ ice_set_features(struct net_device *netdev, netdev_features_t features)
bool ena = !!(features & NETIF_F_NTUPLE);
ice_vsi_manage_fdir(vsi, ena);
+ ice_vsi_manage_acl(vsi, ena);
ena ? ice_init_arfs(vsi) : ice_clear_arfs(vsi);
}
@@ -7911,6 +7940,24 @@ static void ice_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type)
ice_rebuild_arfs(pf);
}
+ if (test_bit(ICE_FLAG_ACL_ENA, pf->flags)) {
+ /* Clean up the stale HW table SW state left by the reset,
+ * recreate the HW table and scenario, then replay flow profiles
+ * from preserved SW state.
+ */
+ ice_acl_destroy_tbl(hw);
+ if (!ice_acl_create_hw(pf)) {
+ ice_acl_replay_flows(hw);
+ ice_acl_replay_fltrs(pf);
+ } else {
+ dev_err(dev, "Failed to rebuild ACL\n");
+ mutex_lock(&hw->fdir_fltr_lock);
+ if (vsi)
+ ice_acl_del_all_fltrs(vsi);
+ mutex_unlock(&hw->fdir_fltr_lock);
+ }
+ }
+
if (vsi && vsi->netdev)
netif_device_attach(vsi->netdev);
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* RE: [PATCH net-next v2 10/12] ice: add ACL reset recovery and NTUPLE feature toggle
2026-09-17 18:39 ` [PATCH net-next v2 10/12] ice: add ACL reset recovery and NTUPLE feature toggle Tony Nguyen
@ 2026-09-18 15:44 ` Loktionov, Aleksandr
2026-09-21 19:57 ` netdev-bot+sashiko
1 sibling, 0 replies; 27+ messages in thread
From: Loktionov, Aleksandr @ 2026-09-18 15:44 UTC (permalink / raw)
To: Nguyen, Anthony L, davem@davemloft.net, kuba@kernel.org,
pabeni@redhat.com, edumazet@google.com, andrew+netdev@lunn.ch,
netdev@vger.kernel.org
Cc: Marcin Szycik, Penigalapati, Sandeep, S, Ananth,
alexander.duyck@gmail.com, Rinitha, SX
> -----Original Message-----
> From: Nguyen, Anthony L <anthony.l.nguyen@intel.com>
> Sent: Thursday, September 17, 2026 8:39 PM
> To: davem@davemloft.net; kuba@kernel.org; pabeni@redhat.com;
> edumazet@google.com; andrew+netdev@lunn.ch; netdev@vger.kernel.org
> Cc: Marcin Szycik <marcin.szycik@linux.intel.com>; Nguyen, Anthony L
> <anthony.l.nguyen@intel.com>; Loktionov, Aleksandr
> <aleksandr.loktionov@intel.com>; Penigalapati, Sandeep
> <sandeep.penigalapati@intel.com>; S, Ananth <ananth.s@intel.com>;
> alexander.duyck@gmail.com; Rinitha, SX <sx.rinitha@intel.com>
> Subject: [PATCH net-next v2 10/12] ice: add ACL reset recovery and
> NTUPLE feature toggle
>
> From: Marcin Szycik <marcin.szycik@linux.intel.com>
>
> Extend ACL support to survive PF resets. Add ice_acl_replay_flows() to
> rebuild HW flow profiles from preserved SW state, and
> ice_acl_replay_fltrs() to reprogram TCAM entries from the SW filter
> list. Wire both into ice_rebuild(). On failure, don't fail reset,
> rather delete all ACL filters from the SW list.
>
> Reset per-profile HW extraction sequences and range checkers for all
> profiles inside ice_acl_create_hw(). This state is separate from the
> TCAM entries and survives a PF reset. Explicitly clearing it ensures
> that the old configuration is erased.
>
> Add the ICE_FLAG_ACL_ENA PF flag to track ACL status. ACL is always
> available, so set it unconditionally in driver initialization. Use the
> new flag to track the NTUPLE ethtool feature flag, alongside Flow
> Director, as both blocks are used to implement ethtool NTUPLE filters.
> Like with Flow Director, disabling the flag deletes all filters, but
> enabling it does not reprogram filters.
>
> Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
> Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at
> Intel)
> Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
> ---
> drivers/net/ethernet/intel/ice/ice.h | 4 +
> drivers/net/ethernet/intel/ice/ice_acl.h | 5 +-
> drivers/net/ethernet/intel/ice/ice_acl_ctrl.c | 43 +++++-----
> drivers/net/ethernet/intel/ice/ice_acl_main.c | 47 +++++++++++
> drivers/net/ethernet/intel/ice/ice_acl_main.h | 1 +
> .../ethernet/intel/ice/ice_ethtool_ntuple.c | 79
> +++++++++++++++++++
> drivers/net/ethernet/intel/ice/ice_main.c | 47 +++++++++++
> 7 files changed, 201 insertions(+), 25 deletions(-)
>
> diff --git a/drivers/net/ethernet/intel/ice/ice.h
> b/drivers/net/ethernet/intel/ice/ice.h
> index 61a320eaf3c5..fcf1130f511f 100644
> --- a/drivers/net/ethernet/intel/ice/ice.h
> +++ b/drivers/net/ethernet/intel/ice/ice.h
> @@ -503,6 +503,7 @@ enum ice_pf_flags {
> ICE_FLAG_DCB_CAPABLE,
> ICE_FLAG_DCB_ENA,
> ICE_FLAG_FD_ENA,
> + ICE_FLAG_ACL_ENA,
> ICE_FLAG_PTP_SUPPORTED, /* PTP is supported by NVM */
> ICE_FLAG_ADV_FEATURES,
> ICE_FLAG_TC_MQPRIO, /* support for Multi queue TC */
> @@ -1019,10 +1020,12 @@ int ice_init_rdma(struct ice_pf *pf); void
> ice_deinit_rdma(struct ice_pf *pf); bool ice_is_wol_supported(struct
> ice_hw *hw); void ice_fdir_del_all_fltrs(struct ice_vsi *vsi);
> +void ice_acl_del_all_fltrs(struct ice_vsi *vsi);
> int
> ice_fdir_write_fltr(struct ice_pf *pf, struct ice_ntuple_fltr *input,
> bool add,
> bool is_tun);
> void ice_vsi_manage_fdir(struct ice_vsi *vsi, bool ena);
> +void ice_vsi_manage_acl(struct ice_vsi *vsi, bool ena);
> int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc
> *cmd); int ice_del_ntuple_ethtool(struct ice_vsi *vsi, struct
> ethtool_rxnfc *cmd); int ice_get_ethtool_fdir_entry(struct ice_hw
> *hw, struct ethtool_rxnfc *cmd); @@ -1040,6 +1043,7 @@
> ice_get_fdir_fltr_ids(struct ice_hw *hw, struct ethtool_rxnfc *cmd,
> u32 *rule_locs);
> void ice_fdir_rem_adq_chnl(struct ice_hw *hw, u16 vsi_idx); void
> ice_acl_rem_flows(struct ice_hw *hw);
> +void ice_acl_replay_flows(struct ice_hw *hw);
> void ice_fdir_release_flows(struct ice_hw *hw); void
> ice_fdir_replay_flows(struct ice_hw *hw); void
> ice_fdir_replay_fltrs(struct ice_pf *pf); diff --git
> a/drivers/net/ethernet/intel/ice/ice_acl.h
> b/drivers/net/ethernet/intel/ice/ice_acl.h
> index 0c8163e585d9..65e16ad1c783 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl.h
> +++ b/drivers/net/ethernet/intel/ice/ice_acl.h
> @@ -6,6 +6,9 @@
>
> #include "ice_common.h"
>
> +/* Maximum number of ACL HW profiles */
> +#define ICE_ACL_MAX_PROF 128
> +
> /* Marks a PF scenario slot as unused in the ACL profile extraction
> table */
> #define ICE_ACL_INVALID_SCEN 0x3f
>
> @@ -124,7 +127,7 @@ struct ice_acl_cntrs { };
>
> int ice_acl_create_tbl(struct ice_hw *hw, struct ice_acl_tbl_params
> *params); -int ice_acl_destroy_tbl(struct ice_hw *hw);
> +void ice_acl_destroy_tbl(struct ice_hw *hw);
> int ice_acl_create_scen(struct ice_hw *hw, u16 match_width, u16
> num_entries,
> u16 *scen_id);
> int ice_aq_alloc_acl_tbl(struct ice_hw *hw, struct ice_acl_alloc_tbl
> *tbl, diff --git a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> index 0ee86236bc37..4ea08bd9784c 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> @@ -877,9 +877,10 @@ static int ice_acl_destroy_scen(struct ice_hw
> *hw, u16 scen_id)
> * ice_acl_destroy_tbl - Destroy a previously created LEM table for
> ACL
> * @hw: pointer to the HW struct
> *
> - * Return: 0 on success, negative on error
> + * Continue on AQ errors so SW state is always cleaned up - e.g.
> after
> + a reset,
> + * where the HW tables might be already gone.
> */
> -int ice_acl_destroy_tbl(struct ice_hw *hw)
> +void ice_acl_destroy_tbl(struct ice_hw *hw)
> {
> struct ice_acl_scen *pos_scen, *tmp_scen;
> struct ice_aqc_acl_generic resp_buf;
> @@ -887,7 +888,7 @@ int ice_acl_destroy_tbl(struct ice_hw *hw)
> int err;
>
> if (!hw->acl_tbl)
> - return -ENOENT;
> + return;
>
> /* Mark all the created scenario's TCAM to stop the packet
> lookup and
> * delete them afterward
> @@ -898,44 +899,38 @@ int ice_acl_destroy_tbl(struct ice_hw *hw)
> if (err) {
> ice_debug(hw, ICE_DBG_ACL,
> "ice_aq_query_acl_scen() failed. status: %d\n",
> err);
> - return err;
> - }
> -
> - for (int i = 0; i < ICE_AQC_ACL_SLICES; i++) {
> - buf.tcam_cfg[i].chnk_msk = 0;
> - buf.tcam_cfg[i].start_cmp_set =
> - ICE_AQC_ACL_ALLOC_SCE_START_CMP;
> - }
> + } else {
> + for (int i = 0; i < ICE_AQC_ACL_SLICES; i++) {
> + buf.tcam_cfg[i].chnk_msk = 0;
> + buf.tcam_cfg[i].start_cmp_set =
> +
> ICE_AQC_ACL_ALLOC_SCE_START_CMP;
> + }
>
> - for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES; i++)
> - buf.act_mem_cfg[i] = 0;
> + for (int i = 0; i < ICE_AQC_MAX_ACTION_MEMORIES;
> i++)
> + buf.act_mem_cfg[i] = 0;
>
> - err = ice_aq_update_acl_scen(hw, pos_scen->id, &buf,
> NULL);
> - if (err) {
> - ice_debug(hw, ICE_DBG_ACL,
> "ice_aq_update_acl_scen() failed. status: %d\n",
> - err);
> - return err;
> + err = ice_aq_update_acl_scen(hw, pos_scen->id,
> &buf,
> + NULL);
> + if (err)
> + ice_debug(hw, ICE_DBG_ACL, "scenario update
> failed, status: %d\n",
> + err);
> }
>
> err = ice_acl_destroy_scen(hw, pos_scen->id);
> if (err) {
> - ice_debug(hw, ICE_DBG_ACL, "deletion of scenario
> failed. status: %d\n",
> + ice_debug(hw, ICE_DBG_ACL, "deletion of scenario
> failed, status:
> +%d\n",
> err);
> - return err;
> }
> }
>
> err = ice_aq_dealloc_acl_tbl(hw, hw->acl_tbl->id, &resp_buf,
> NULL);
> if (err) {
> - ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL
> failed. status: %d\n",
> + ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL
> failed, status:
> +%d\n",
> err);
> - return err;
> }
>
> kfree(hw->acl_tbl);
> hw->acl_tbl = NULL;
> -
> - return 0;
> }
>
> /**
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.c
> b/drivers/net/ethernet/intel/ice/ice_acl_main.c
> index 171b7bf8519c..d13e8bc1b701 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_main.c
> @@ -236,6 +236,53 @@ static void ice_acl_set_act_fwd_queue(struct
> ice_flow_action *action,
> action->data.acl_act.value = cpu_to_le16(queue_index); }
>
> +/*
> + * ice_acl_replay_fltrs - replay ACL filters from the SW filter list
> + * @pf: board private structure
> + *
> + * Reprograms ACL TCAM entries after a reset using data preserved in
> +the
> + * SW filter list. Relies on ice_acl_replay_flows() having been
> called
> +first
> + * to restore the flow profiles.
> + */
> +void ice_acl_replay_fltrs(struct ice_pf *pf) {
> + struct ice_vsi *vsi = ice_get_main_vsi(pf);
> + struct ice_hw *hw = &pf->hw;
> + struct ice_ntuple_fltr *f_rule;
> +
> + if (!vsi)
> + return;
> +
> + list_for_each_entry(f_rule, &hw->fdir_list_head, fltr_node) {
> + struct ice_flow_action acts[ICE_ACL_NUM_ACT];
> + struct ice_acl_hw_prof *hw_prof;
> + u64 entry_h = 0;
> + int err;
> +
> + if (!f_rule->acl_fltr)
> + continue;
> +
> + if (!hw->acl_prof || !hw->acl_prof[f_rule->flow_type])
> + continue;
> +
> + hw_prof = hw->acl_prof[f_rule->flow_type];
> +
> + memset(&acts, 0, sizeof(acts));
> + if (f_rule->dest_ctl ==
> ICE_FLTR_PRGM_DESC_DEST_DROP_PKT)
> + ice_acl_set_act_drop(&acts[0]);
> + else
> + ice_acl_set_act_fwd_queue(&acts[0], f_rule-
> >q_index);
> +
> + err = ice_flow_add_entry(hw, ICE_BLK_ACL, hw_prof-
> >prof_id,
> + f_rule->fltr_id, vsi->idx,
> + ICE_FLOW_PRIO_NORMAL, f_rule, acts,
> + ICE_ACL_NUM_ACT, &entry_h);
> + if (err)
> + dev_warn(ice_pf_to_dev(pf), "Could not reprogram
> filter %d, status %d\n",
> + f_rule->fltr_id, err);
> + }
> +}
> +
> /**
> * ice_acl_comp_rules - compare two ACL filters
> * @a: first ACL filter
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.h
> b/drivers/net/ethernet/intel/ice/ice_acl_main.h
> index 6665af2e7053..54053954b244 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl_main.h
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_main.h
> @@ -6,4 +6,5 @@
> #include "ice.h"
> #include <linux/ethtool.h>
> int ice_acl_add_rule_ethtool(struct ice_vsi *vsi, struct
> ethtool_rxnfc *cmd);
> +void ice_acl_replay_fltrs(struct ice_pf *pf);
> #endif /* _ICE_ACL_MAIN_H_ */
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index 0ac44d38fdbd..fef8640d23fb 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> @@ -527,6 +527,37 @@ void ice_fdir_replay_flows(struct ice_hw *hw)
> }
> }
>
...
> if (vsi && vsi->netdev)
> netif_device_attach(vsi->netdev);
>
> --
> 2.47.1
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 10/12] ice: add ACL reset recovery and NTUPLE feature toggle
2026-09-17 18:39 ` [PATCH net-next v2 10/12] ice: add ACL reset recovery and NTUPLE feature toggle Tony Nguyen
2026-09-18 15:44 ` Loktionov, Aleksandr
@ 2026-09-21 19:57 ` netdev-bot+sashiko
1 sibling, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:57 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
lukasz.czapnik, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, sx.rinitha
Thank you for your contribution! Sashiko AI review found 13 potential
issue(s) to consider.
Critical: 0 · High: 6 · Medium: 5 · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] ICE_FLAG_ACL_ENA does not gate ACL rule programming, so the
commit's stated purpose ('Use the new flag to track the NTUPLE ethtool…
- [High] ice_rebuild() creates the ACL HW table whenever ICE_FLAG_ACL_ENA
is set, but ice_set_pf_caps() (ice_main.c:3952) now sets that flag…
- [High] In the new per-profile reset loop in ice_acl_create_hw(), the
return value of ice_prog_acl_prof_ranges() is discarded and the…
- [High] ice_acl_create_hw() unconditionally reprograms the extraction
sequence and range checkers of all 128 ACL HW profiles with zeroed…
- [High] ice_acl_prof_add_ethtool() reuses the ice_acl_hw_prof already
published in hw->acl_prof[fltr_type], but its `free_acl_prof:…
- [High] The new reset-replay code walks and mutates state that every
other accessor protects with hw->fdir_fltr_lock, without taking it.
- [Medium] Per-profile and per-filter replay failures leave ACL rules
present in software but absent from hardware after a reset.
- [Medium] A PF reset taken while NTUPLE is disabled leaves no usable ACL
hardware table after the feature is re-enabled.
- [Medium] ice_acl_replay_flows() only updates prof->prof_id for flow
types whose ice_flow_add_prof() succeeded; on a per-flow failure it…
- [Medium] ice_acl_destroy_tbl()'s new kernel-doc promises that SW state
is always cleaned up even when the AQ calls fail ('e.g. after a reset,…
- [Medium] ice_rebuild()'s new ACL failure branch does
mutex_lock(&hw->fdir_fltr_lock), but that mutex is initialized only…
- [Low] ice_acl_replay_flows() applies the ethtool uAPI modifier bit
FLOW_EXT to a loop variable of type enum ice_fltr_ptype (`flow &=…
- [Low] The new ice_acl_replay_fltrs() documentation block is written in
kernel-doc form (summary line `ice_acl_replay_fltrs - replay ACL…
--
ice: add ACL reset recovery and NTUPLE feature toggle
This adds ice_acl_replay_flows() and ice_acl_replay_fltrs() to rebuild ACL
HW profiles and TCAM entries after a PF reset, wires them into
ice_rebuild(), resets per-profile HW extraction sequences and range
checkers in ice_acl_create_hw(), and introduces the ICE_FLAG_ACL_ENA PF
flag tied to the ethtool NTUPLE feature.
A few questions about the new code below.
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> index 0ee86236bc37a..4ea08bd9784c0 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> @@ -877,9 +877,10 @@ static int ice_acl_destroy_scen(struct ice_hw *hw, u16 scen_id)
> * ice_acl_destroy_tbl - Destroy a previously created LEM table for ACL
> * @hw: pointer to the HW struct
> *
> - * Return: 0 on success, negative on error
> + * Continue on AQ errors so SW state is always cleaned up - e.g. after a reset,
> + * where the HW tables might be already gone.
> */
> -int ice_acl_destroy_tbl(struct ice_hw *hw)
> +void ice_acl_destroy_tbl(struct ice_hw *hw)
[ ... ]
> err = ice_acl_destroy_scen(hw, pos_scen->id);
> if (err) {
> - ice_debug(hw, ICE_DBG_ACL, "deletion of scenario failed. status: %d\n",
> + ice_debug(hw, ICE_DBG_ACL, "deletion of scenario failed, status: %d\n",
> err);
> - return err;
> }
> }
>
> err = ice_aq_dealloc_acl_tbl(hw, hw->acl_tbl->id, &resp_buf, NULL);
> if (err) {
> - ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL failed. status: %d\n",
> + ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of ACL failed, status: %d\n",
> err);
> - return err;
> }
>
> kfree(hw->acl_tbl);
> hw->acl_tbl = NULL;
> -
> - return 0;
> }
[Severity: Medium]
The new comment says SW state is always cleaned up, but does
ice_acl_destroy_scen() still return early before removing the scenario from
the list?
ice_acl_destroy_scen() {
...
err = ice_aq_dealloc_acl_scen(hw, scen_id, NULL);
if (err) {
ice_debug(hw, ICE_DBG_ACL, "AQ de-allocation of scenario failed. status: %d\n",
err);
return err;
}
/* Remove scenario from hw->acl_tbl->scens */
list_for_each_entry_safe(scen, tmp_scen, &hw->acl_tbl->scens, ...
list_del(&scen->list_entry);
devm_kfree(ice_hw_to_dev(hw), scen);
...
}
The scenario stays on hw->acl_tbl->scens, and ice_acl_destroy_tbl() then does
kfree(hw->acl_tbl), which owns that list head.
Does this leak the struct ice_acl_scen (including its
DECLARE_BITMAP(entry_bitmap, ICE_MAX_ACL_TCAM_ENTRY)) once per failing
teardown? Since ice_rebuild() calls ice_acl_destroy_tbl() exactly when the
firmware allocation is expected to be gone, this looks like the normal case
on reset rather than a rare one.
> diff --git a/drivers/net/ethernet/intel/ice/ice_acl_main.c b/drivers/net/ethernet/intel/ice/ice_acl_main.c
> index 171b7bf8519c6..d13e8bc1b7019 100644
> --- a/drivers/net/ethernet/intel/ice/ice_acl_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_acl_main.c
> @@ -236,6 +236,53 @@ static void ice_acl_set_act_fwd_queue(struct ice_flow_action *action,
> action->data.acl_act.value = cpu_to_le16(queue_index);
> }
>
> +/*
> + * ice_acl_replay_fltrs - replay ACL filters from the SW filter list
> + * @pf: board private structure
[Severity: Low]
This isn't a bug, but the block is kernel-doc formatted (summary line plus
@pf) while opened with /* instead of /**, so scripts/kernel-doc skips it.
Other documented functions in ice_acl_main.c use /**.
> + *
> + * Reprograms ACL TCAM entries after a reset using data preserved in the
> + * SW filter list. Relies on ice_acl_replay_flows() having been called first
> + * to restore the flow profiles.
> + */
> +void ice_acl_replay_fltrs(struct ice_pf *pf)
> +{
> + struct ice_vsi *vsi = ice_get_main_vsi(pf);
> + struct ice_hw *hw = &pf->hw;
> + struct ice_ntuple_fltr *f_rule;
> +
> + if (!vsi)
> + return;
> +
> + list_for_each_entry(f_rule, &hw->fdir_list_head, fltr_node) {
[Severity: High]
Should this traversal hold hw->fdir_fltr_lock?
Every other accessor of hw->fdir_list_head takes that mutex, and the patch
itself documents ice_acl_del_all_fltrs() as "needs to be called while holding
hw->fdir_fltr_lock". ice_del_ntuple_ethtool() does list_del() + kfree() on
these nodes under the mutex.
The same question applies to ice_acl_replay_flows(), which reads prof->seg
and writes prof->prof_id unlocked while ice_vsi_manage_acl(vsi, false) ->
ice_acl_rem_flows() kfree()s prof->seg under the mutex.
ice_rebuild() runs from the service task / ice_do_reset() without rtnl_lock,
and the ice_is_reset_in_progress() test at the top of the ethtool handlers is
taken before any lock, so a task that passed it can still be inside
ice_del_ntuple_ethtool() or ice_acl_prof_add_ethtool() while the replay walks
the same objects. Can that traverse a freed list node or call
ice_flow_add_prof() on a freed prof->seg?
Note the failure branch added to ice_rebuild() a few lines below does take
the mutex for the same data, which makes the success path look inconsistent.
> + struct ice_flow_action acts[ICE_ACL_NUM_ACT];
> + struct ice_acl_hw_prof *hw_prof;
> + u64 entry_h = 0;
> + int err;
> +
> + if (!f_rule->acl_fltr)
> + continue;
> +
> + if (!hw->acl_prof || !hw->acl_prof[f_rule->flow_type])
> + continue;
> +
> + hw_prof = hw->acl_prof[f_rule->flow_type];
> +
> + memset(&acts, 0, sizeof(acts));
> + if (f_rule->dest_ctl == ICE_FLTR_PRGM_DESC_DEST_DROP_PKT)
> + ice_acl_set_act_drop(&acts[0]);
> + else
> + ice_acl_set_act_fwd_queue(&acts[0], f_rule->q_index);
> +
> + err = ice_flow_add_entry(hw, ICE_BLK_ACL, hw_prof->prof_id,
> + f_rule->fltr_id, vsi->idx,
> + ICE_FLOW_PRIO_NORMAL, f_rule, acts,
> + ICE_ACL_NUM_ACT, &entry_h);
[Severity: Medium]
Can hw_prof->prof_id be stale here?
ice_acl_replay_flows() only assigns prof->prof_id when ice_flow_add_prof()
succeeds; on failure it logs and continues, so the ice_acl_hw_prof keeps the
pre-reset ID. Profile IDs come from a recyclable bitmap:
ice_flow_add_prof_sync() {
ids = &hw->blk[blk].prof_id;
prof_id = find_first_zero_bit(ids->id, ids->count);
...
}
and that bitmap is cleared during reset prep by ice_clear_hw_tbls(). So a
later flow in the same replay loop can be handed exactly the numeric ID a
failed flow still stores, and ice_flow_find_prof_id() will resolve it to a
valid but different profile.
Would that program the filter's match data into another flow type's
extraction sequence without any error being reported?
> + if (err)
> + dev_warn(ice_pf_to_dev(pf), "Could not reprogram filter %d, status %d\n",
> + f_rule->fltr_id, err);
> + }
> +}
[Severity: Medium]
The commit message says "On failure, don't fail reset, rather delete all ACL
filters from the SW list." Does that happen for replay failures?
Both ice_acl_replay_flows() and ice_acl_replay_fltrs() return void and only
log, so ice_rebuild() only calls ice_acl_del_all_fltrs() when
ice_acl_create_hw() itself fails. After a per-filter ice_flow_add_entry()
error (-ENOSPC on TCAM/scenario exhaustion, -ENOMEM, an AQ error), the reset
completes, the netdev is reattached, and the filter list plus
hw->acl_fltr_cnt[] still report the rule as installed while hardware has no
entry.
Such a rule also looks undeletable afterwards: ice_ntuple_update_list_entry()
calls ice_del_acl_ethtool(), ice_flow_find_entry() returns
ICE_FLOW_ENTRY_HANDLE_INVAL, ice_flow_rem_entry() returns -EINVAL, and the
error is propagated before the SW rule is unlinked. Can the user still
remove the rule without reloading the driver?
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index 0ac44d38fdbdf..fef8640d23fb6 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> @@ -527,6 +527,37 @@ void ice_fdir_replay_flows(struct ice_hw *hw)
> }
> }
>
> +/**
> + * ice_acl_replay_flows - replay HW ACL flow profiles
> + * @hw: pointer to HW instance
> + */
> +void ice_acl_replay_flows(struct ice_hw *hw)
> +{
> + if (!hw->acl_prof)
> + return;
> +
> + for (enum ice_fltr_ptype flow = ICE_FLTR_PTYPE_NONF_NONE;
> + flow < ICE_FLTR_PTYPE_MAX; flow++) {
> + struct ice_flow_prof *hw_prof;
> + struct ice_acl_hw_prof *prof;
> + int err;
> +
> + flow &= ~FLOW_EXT;
[Severity: Low]
This isn't a bug, but FLOW_EXT is an ethtool uAPI modifier bit and flow here
is an already-converted enum ice_fltr_ptype. Since the loop only takes
values below ICE_FLTR_PTYPE_MAX, masking with ~FLOW_EXT (0x7fffffff) clears
nothing, and it writes to the loop counter.
Peer sites strip FLOW_EXT before conversion, e.g. in
ice_acl_prof_add_ethtool():
fltr_type = ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT);
and the structurally equivalent ice_fdir_replay_flows() has no such mask.
Can this statement be dropped? The same line was added earlier in the series
in ice_acl_rem_flows().
> + prof = hw->acl_prof[flow];
> + if (!prof || !prof->seg)
> + continue;
> +
> + err = ice_flow_add_prof(hw, ICE_BLK_ACL, ICE_FLOW_RX,
> + prof->seg, 1, false, &hw_prof);
> + if (err) {
> + dev_err(ice_hw_to_dev(hw), "Could not replay ACL, flow type %d\n",
> + flow);
> + continue;
> + }
> + prof->prof_id = hw_prof->id;
> + }
> +}
[ ... ]
> @@ -1825,6 +1856,28 @@ static int ice_del_acl_ethtool(struct ice_hw *hw, struct ice_ntuple_fltr *fltr)
> return ice_flow_rem_entry(hw, ICE_BLK_ACL, entry);
> }
>
> +/**
> + * ice_acl_del_all_fltrs - Delete all ACL filters from the filter list
> + * @vsi: the VSI being changed
> + *
> + * This function needs to be called while holding hw->fdir_fltr_lock
> + */
> +void ice_acl_del_all_fltrs(struct ice_vsi *vsi)
[ ... ]
> +/**
> + * ice_vsi_manage_acl - turn on/off ACL
> + * @vsi: the VSI being changed
> + * @ena: boolean value indicating if this is an enable or disable request
> + */
> +void ice_vsi_manage_acl(struct ice_vsi *vsi, bool ena)
> +{
> + struct ice_pf *pf = vsi->back;
> + struct ice_hw *hw = &pf->hw;
> +
> + if (ena) {
> + set_bit(ICE_FLAG_ACL_ENA, pf->flags);
> + return;
> + }
> +
> + mutex_lock(&hw->fdir_fltr_lock);
> + if (!test_and_clear_bit(ICE_FLAG_ACL_ENA, pf->flags))
> + goto release_lock;
> +
> + ice_acl_del_all_fltrs(vsi);
> + ice_acl_rem_flows(hw);
> +
> +release_lock:
> + mutex_unlock(&hw->fdir_fltr_lock);
> +}
[Severity: High]
Does ICE_FLAG_ACL_ENA actually gate ACL rule programming?
The commit message says "Use the new flag to track the NTUPLE ethtool feature
flag ... Like with Flow Director, disabling the flag deletes all filters".
After ethtool -K ethX ntuple off, existing filters are deleted here, but the
add path still dispatches on hw->acl_tbl alone:
ice_add_ntuple_ethtool() {
/* ACL filter */
if (pf->hw.acl_tbl && ice_is_acl_filter(fsp))
return ice_acl_add_rule_ethtool(vsi, cmd);
/* Only fdir filters below */
if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
return -EOPNOTSUPP;
...
}
So a new ACL-class rule is still accepted and programmed with NTUPLE off,
while the equivalent Flow Director rule returns -EOPNOTSUPP. Those rules are
also invisible to the new replay, since ice_rebuild() only enters the ACL
block when ICE_FLAG_ACL_ENA is set. Should the ACL dispatch test the new flag
too?
[Severity: Medium]
What restores the ACL hardware table if a reset happens while NTUPLE is off?
ethtool -K ethX ntuple off clears the flag here but leaves hw->acl_tbl set.
A PF reset then skips the whole ACL block in ice_rebuild(), so the SW
hw->acl_tbl object survives while its firmware table and scenario allocation
are gone. Re-enabling only does set_bit(ICE_FLAG_ACL_ENA, pf->flags) with no
hardware reconstruction, and ice_add_ntuple_ethtool() treats the stale
non-NULL hw->acl_tbl as proof the ACL hardware exists.
Can rules then be programmed against a table and scenario that no longer
exist in hardware until the driver is reloaded?
[Severity: High]
Does the ice_acl_rem_flows() call here leave a wrapper in hw->acl_prof[] that
a later error path can free twice?
ice_acl_rem_flows() frees prof->seg and sets it NULL but keeps the
ice_acl_hw_prof installed in hw->acl_prof[flow]:
ice_acl_rem_flows() {
...
kfree(prof->seg);
prof->seg = NULL;
...
}
Since the add path stays open after the disable (it is gated on hw->acl_tbl),
a following ACL rule add reuses that retained wrapper in
ice_acl_prof_add_ethtool(), skips the if (hw_prof->seg) branch, and on any
ice_flow_add_prof() failure takes:
free_acl_prof:
kfree(hw_prof);
free_seg:
kfree(seg);
without clearing hw->acl_prof[fltr_type]. ice_acl_replay_flows() then
dereferences prof->seg/prof->prof_id on the next reset, and ice_deinit_acl()
does kfree(hw_prof->seg) followed by kfree(hw_prof). Is that a
use-after-free plus double free? Before this patch,
ice_acl_rem_flows() was only reachable from ice_deinit_acl(), so the
"wrapper retained, seg == NULL" state did not exist at runtime.
> diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
> index 3a10b2c2a44ae..8a81d3e475a72 100644
> --- a/drivers/net/ethernet/intel/ice/ice_main.c
> +++ b/drivers/net/ethernet/intel/ice/ice_main.c
> @@ -3947,6 +3948,9 @@ static void ice_set_pf_caps(struct ice_pf *pf)
> func_caps->fd_fltr_best_effort);
> }
>
> + /* ACL is always initially available */
> + set_bit(ICE_FLAG_ACL_ENA, pf->flags);
> +
[Severity: High]
Is the flag safe to set unconditionally here, given ice_rebuild() uses it as
the sole gate for creating the ACL HW table?
ice_set_pf_caps() is called from ice_init_dev() regardless of safe mode, but
hw->acl_prof is allocated only by ice_init_acl(), which:
- is never called in safe mode (ice_init_features() returns early), and
- may fail: "Note: ACL init failure is non-fatal to load".
In both cases hw->acl_prof stays NULL. After a PF reset the new block calls
ice_acl_create_hw(), which on success sets hw->acl_tbl while nothing in the
rebuild path allocates acl_prof (ice_acl_replay_flows() just returns on
!hw->acl_prof). An ethtool ACL rule then passes the hw->acl_tbl gate and
reaches:
ice_acl_prof_add_ethtool() {
...
hw_prof = hw->acl_prof[fltr_type];
if (!hw_prof) {
...
}
Can this dereference a NULL hw->acl_prof array?
[Severity: Medium]
Related to the unconditional set_bit: hw->fdir_fltr_lock is initialized only
in ice_init_fdir(), after ice_ctrl_vsi_setup()/ice_vsi_open_ctrl(), and never
in safe mode since ice_init_features() returns before ice_init_fdir(). With
ICE_FLAG_ACL_ENA always set, the new mutex_lock(&hw->fdir_fltr_lock) in
ice_rebuild()'s ACL failure branch can run on a mutex that was never
mutex_init()ed.
Would that trip the CONFIG_DEBUG_MUTEXES magic check, or produce a lockdep
"trying to register non-static key" splat? ice_vsi_manage_fdir() has the same
pre-existing exposure, but this patch adds a new reachable site and decouples
the ACL paths from the FD init that creates the mutex.
> @@ -4369,6 +4373,30 @@ static int ice_acl_create_hw(struct ice_pf *pf)
> if (err)
> goto destroy_table;
>
> + /* Reset profile extraction sequences and range checkers for all
> + * possible HW profile IDs. The TCAM entries are already zeroed by
> + * ice_acl_init_tbl() inside ice_acl_create_tbl(), but profile
> + * extraction and range checker state is separate per-profile HW state
> + * that survives PF reset, therefore must be brought back to default
> + * state.
> + */
> + for (u8 prof_id = 0; prof_id < ICE_ACL_MAX_PROF; prof_id++) {
> + struct ice_aqc_acl_prof_generic_frmt xtrct_buf = {};
> + struct ice_aqc_acl_profile_ranges range_buf = {};
> +
> + memset(xtrct_buf.pf_scenario_num, ICE_ACL_INVALID_SCEN,
> + sizeof(xtrct_buf.pf_scenario_num));
> + err = ice_prgm_acl_prof_xtrct(hw, prof_id, &xtrct_buf, NULL);
[Severity: High]
Is this per-profile state shared between PFs?
struct ice_aqc_acl_prof_generic_frmt carries one scenario slot per PF, and the
existing code treats the record as device-global:
ice_flow_acl_set_xtrct_seq() {
...
if (!ice_flow_acl_is_prof_in_use(&buf)) {
/* Program the profile dependent configuration. This is done
* only once regardless of the number of PFs using that profile
*/
...
}
ice_flow_acl_disassoc_scen() {
/* Clear scenario for this PF */
buf.pf_scenario_num[hw->pf_id] = ICE_ACL_INVALID_SCEN;
...
}
This loop writes a zeroed extraction buffer and sets ICE_ACL_INVALID_SCEN for
every PF slot of all 128 profiles, without querying first and without
restricting the change to hw->pf_id.
Since ice_acl_create_hw() now also runs from ice_rebuild() on every PF reset,
and ICE_FLAG_ACL_ENA is set unconditionally even for a PF that never
programmed an ACL rule, can a probe or reset on one PF clear the key
extraction, range checkers and scenario association of ACL rules belonging to
other PFs on the same adapter while their TCAM entries stay programmed?
> + if (err)
> + dev_warn(ice_pf_to_dev(pf), "Failed to reset profile extraction for profile %u\n",
> + prof_id);
> +
> + ice_prog_acl_prof_ranges(hw, prof_id, &range_buf, NULL);
> + if (err)
> + dev_warn(ice_pf_to_dev(pf), "Failed to reset range checkers for profile %u\n",
> + prof_id);
> + }
> +
> return 0;
[Severity: High]
Should the return value of ice_prog_acl_prof_ranges() be assigned to err?
As written, err is only ever set by ice_prgm_acl_prof_xtrct(), so the second
if (err) re-tests the previous status:
- a real range-checker failure is never reported, and
- an extraction failure prints a second warning claiming the range checkers
failed.
Neither failure is propagated either, since the function returns 0 after the
loop, so ice_rebuild() takes the success path and replays profiles and filters
even though the state this loop exists to clear may still hold pre-reset
values. Does that contradict "Explicitly clearing it ensures that the old
configuration is erased"?
Also, with 128 profiles and a dev_warn per iteration, a device or firmware
that rejects high profile IDs would emit up to 256 warnings and 256 AQ
commands on every probe and every reset. Would rate-limiting or a single
summary message be preferable?
> @@ -7911,6 +7940,24 @@ static void ice_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type)
> ice_rebuild_arfs(pf);
> }
>
> + if (test_bit(ICE_FLAG_ACL_ENA, pf->flags)) {
> + /* Clean up the stale HW table SW state left by the reset,
> + * recreate the HW table and scenario, then replay flow profiles
> + * from preserved SW state.
> + */
> + ice_acl_destroy_tbl(hw);
> + if (!ice_acl_create_hw(pf)) {
> + ice_acl_replay_flows(hw);
> + ice_acl_replay_fltrs(pf);
> + } else {
> + dev_err(dev, "Failed to rebuild ACL\n");
> + mutex_lock(&hw->fdir_fltr_lock);
> + if (vsi)
> + ice_acl_del_all_fltrs(vsi);
> + mutex_unlock(&hw->fdir_fltr_lock);
> + }
> + }
> +
> if (vsi && vsi->netdev)
> netif_device_attach(vsi->netdev);
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net-next v2 11/12] ice: re-introduce ice_dealloc_flow_entry() helper
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (9 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 10/12] ice: add ACL reset recovery and NTUPLE feature toggle Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-17 18:39 ` [PATCH net-next v2 12/12] ice: use ACL for ntuple rules that conflict with FDir Tony Nguyen
` (2 subsequent siblings)
13 siblings, 0 replies; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Marcin Szycik, anthony.l.nguyen, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, Przemek Kitszel,
Rinitha S
From: Marcin Szycik <marcin.szycik@linux.intel.com>
It was removed in commit ad667d626825 ("ice: remove null checks before
devm_kfree() calls"). Now it's useful again.
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
drivers/net/ethernet/intel/ice/ice_flow.c | 35 +++++++++++++----------
1 file changed, 20 insertions(+), 15 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_flow.c b/drivers/net/ethernet/intel/ice/ice_flow.c
index e0a1d8463d6f..de751774affc 100644
--- a/drivers/net/ethernet/intel/ice/ice_flow.c
+++ b/drivers/net/ethernet/intel/ice/ice_flow.c
@@ -1589,6 +1589,23 @@ ice_flow_find_prof_id(struct ice_hw *hw, enum ice_block blk, u64 prof_id)
return NULL;
}
+/**
+ * ice_dealloc_flow_entry - Deallocate flow entry memory
+ * @hw: pointer to the HW struct
+ * @entry: flow entry to be removed
+ */
+static void
+ice_dealloc_flow_entry(struct ice_hw *hw, struct ice_flow_entry *entry)
+{
+ if (!entry)
+ return;
+
+ kfree(entry->entry);
+ kfree(entry->range_buf);
+ kfree(entry->acts);
+ devm_kfree(ice_hw_to_dev(hw), entry);
+}
+
/**
* ice_flow_get_hw_prof - return the HW profile for a specific profile ID handle
* @hw: pointer to the HW struct
@@ -1757,11 +1774,7 @@ static int ice_flow_rem_entry_sync(struct ice_hw *hw, enum ice_block blk,
}
list_del(&entry->l_entry);
-
- kfree(entry->entry);
- kfree(entry->range_buf);
- kfree(entry->acts);
- devm_kfree(ice_hw_to_dev(hw), entry);
+ ice_dealloc_flow_entry(hw, entry);
return 0;
}
@@ -2948,10 +2961,7 @@ static int ice_flow_acl_add_scen_entry_sync(struct ice_hw *hw,
if (e->acts_cnt && e->acts)
ice_flow_acl_free_act_cntr(hw, e->acts, e->acts_cnt);
exist->id = e->id;
- kfree(e->entry);
- kfree(e->range_buf);
- kfree(e->acts);
- devm_kfree(ice_hw_to_dev(hw), e);
+ ice_dealloc_flow_entry(hw, e);
*entry = exist;
}
out:
@@ -3075,12 +3085,7 @@ int ice_flow_add_entry(struct ice_hw *hw, enum ice_block blk, u64 prof_id,
if (blk == ICE_BLK_ACL && e->acts_cnt && e->acts)
ice_flow_acl_free_act_cntr(hw, e->acts, e->acts_cnt);
dealloc_entry:
- if (e) {
- kfree(e->entry);
- kfree(e->range_buf);
- kfree(e->acts);
- devm_kfree(ice_hw_to_dev(hw), e);
- }
+ ice_dealloc_flow_entry(hw, e);
return status;
}
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH net-next v2 12/12] ice: use ACL for ntuple rules that conflict with FDir
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (10 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 11/12] ice: re-introduce ice_dealloc_flow_entry() helper Tony Nguyen
@ 2026-09-17 18:39 ` Tony Nguyen
2026-09-21 19:57 ` netdev-bot+sashiko
2026-09-21 16:02 ` [PATCH net-next v2 00/12][pull request] ice: Add ACL support Marcin Szycik
2026-09-23 1:30 ` patchwork-bot+netdevbpf
13 siblings, 1 reply; 27+ messages in thread
From: Tony Nguyen @ 2026-09-17 18:39 UTC (permalink / raw)
To: davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: Lukasz Czapnik, anthony.l.nguyen, marcin.szycik,
aleksandr.loktionov, sandeep.penigalapati, ananth.s,
alexander.duyck, Rinitha S
From: Lukasz Czapnik <lukasz.czapnik@intel.com>
Flow Director can keep only one input set per flow type. After ACL support
was added for ethtool ntuple rules, the driver still only selected ACL for
rules with partial masks.
That leaves a gap for rules with full masks that still require a different
input set than the one already programmed for Flow Director. Such rules go
through the FDir path, build a different extraction sequence and then fail
because the existing FDir profile cannot be reused.
Detect this case before programming the rule. Build the candidate IP flow
segment, compare it with the active non-tunneled FDir profile and, when the
input sets differ, offload the rule through ACL if ACL is available.
Refactor the IP flow segment setup into a helper so the same logic can be
used both by the extraction-sequence configuration path and by the conflict
check.
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Lukasz Czapnik <lukasz.czapnik@intel.com>
Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Tested-by: Rinitha S <sx.rinitha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
.../ethernet/intel/ice/ice_ethtool_ntuple.c | 189 +++++++++++++-----
1 file changed, 140 insertions(+), 49 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
index fef8640d23fb..691b4500e6be 100644
--- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
+++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
@@ -1484,6 +1484,120 @@ ice_set_fdir_vlan_seg(struct ice_flow_seg_info *seg,
return 0;
}
+/**
+ * ice_set_fdir_ip_flow_seg - set IP flow segment based on ethtool flow type
+ * @fsp: pointer to ethtool Rx flow specification
+ * @seg: flow segment for programming
+ * @perfect_fltr: valid on success; returns true if perfect fltr, false if not
+ *
+ * Return: 0 on success and errno in case of error.
+ */
+static int ice_set_fdir_ip_flow_seg(struct ethtool_rx_flow_spec *fsp,
+ struct ice_flow_seg_info *seg,
+ bool *perfect_fltr)
+{
+ switch (fsp->flow_type & ~FLOW_EXT) {
+ case TCP_V4_FLOW:
+ return ice_set_fdir_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
+ ICE_FLOW_SEG_HDR_TCP, perfect_fltr);
+ case UDP_V4_FLOW:
+ return ice_set_fdir_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
+ ICE_FLOW_SEG_HDR_UDP, perfect_fltr);
+ case SCTP_V4_FLOW:
+ return ice_set_fdir_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
+ ICE_FLOW_SEG_HDR_SCTP,
+ perfect_fltr);
+ case IPV4_USER_FLOW:
+ return ice_set_fdir_ip4_usr_seg(seg, &fsp->m_u.usr_ip4_spec,
+ perfect_fltr);
+ case TCP_V6_FLOW:
+ return ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
+ ICE_FLOW_SEG_HDR_TCP, perfect_fltr);
+ case UDP_V6_FLOW:
+ return ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
+ ICE_FLOW_SEG_HDR_UDP, perfect_fltr);
+ case SCTP_V6_FLOW:
+ return ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
+ ICE_FLOW_SEG_HDR_SCTP,
+ perfect_fltr);
+ case IPV6_USER_FLOW:
+ return ice_set_fdir_ip6_usr_seg(seg, &fsp->m_u.usr_ip6_spec,
+ perfect_fltr);
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * ice_fdir_has_input_set_conflict - Check conflict with existing FD filters
+ * @pf: PF structure
+ * @fsp: pointer to ethtool Rx flow specification
+ * @user: user-defined data parsed from flow specification
+ *
+ * Checks if adding this filter to Flow Director would cause an input set
+ * mismatch with existing filters for the same flow type by building
+ * the segment and comparing with existing profiles.
+ *
+ * Return: true if there's a conflict (use ACL), false otherwise (can use FD)
+ */
+static bool
+ice_fdir_has_input_set_conflict(struct ice_pf *pf,
+ struct ethtool_rx_flow_spec *fsp,
+ const struct ice_rx_flow_userdef *user)
+{
+ struct ice_flow_seg_info *test_seg, *old_seg;
+ bool perfect_fltr = false, conflict = false;
+ struct ice_fd_hw_prof *hw_prof;
+ struct ice_hw *hw = &pf->hw;
+ enum ice_fltr_ptype flow;
+ int err;
+
+ if ((fsp->flow_type & ~FLOW_EXT) == ETHER_FLOW)
+ return false;
+
+ flow = ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT);
+ if (flow >= ICE_FLTR_PTYPE_MAX || !hw->fdir_prof ||
+ !hw->fdir_prof[flow]) {
+ return false;
+ }
+
+ hw_prof = hw->fdir_prof[flow];
+ old_seg = hw_prof->fdir_seg[ICE_FD_HW_SEG_NON_TUN];
+
+ /* A profile with no ethtool FDir filters (fdir_fltr_cnt == 0) may
+ * still be locked by aRFS perfect (4-tuple) filters, which keep their
+ * own active counters separate from fdir_fltr_cnt.
+ */
+ if (!old_seg || (hw->fdir_fltr_cnt[flow] == 0 &&
+ !ice_is_arfs_using_perfect_flow(hw, flow)))
+ return false;
+
+ test_seg = kzalloc_obj(*test_seg);
+ if (!test_seg)
+ return false;
+
+ err = ice_set_fdir_ip_flow_seg(fsp, test_seg, &perfect_fltr);
+
+ if (err) {
+ kfree(test_seg);
+ return false;
+ }
+
+ if (user && user->flex_fltr)
+ ice_flow_add_fld_raw(test_seg, user->flex_offset,
+ ICE_FLTR_PRGM_FLEX_WORD_SIZE,
+ ICE_FLOW_FLD_OFF_INVAL,
+ ICE_FLOW_FLD_OFF_INVAL);
+
+ /* Compare the test segment with the existing segment */
+ if (memcmp(old_seg, test_seg, sizeof(*test_seg)) != 0)
+ conflict = true;
+
+ kfree(test_seg);
+
+ return conflict;
+}
+
/**
* ice_cfg_fdir_xtrct_seq - Configure extraction sequence for the given filter
* @pf: PF structure
@@ -1514,57 +1628,16 @@ ice_cfg_fdir_xtrct_seq(struct ice_pf *pf, struct ethtool_rx_flow_spec *fsp,
return -ENOMEM;
}
- switch (fsp->flow_type & ~FLOW_EXT) {
- case TCP_V4_FLOW:
- ret = ice_set_fdir_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
- ICE_FLOW_SEG_HDR_TCP,
- &perfect_filter);
- break;
- case UDP_V4_FLOW:
- ret = ice_set_fdir_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
- ICE_FLOW_SEG_HDR_UDP,
- &perfect_filter);
- break;
- case SCTP_V4_FLOW:
- ret = ice_set_fdir_ip4_seg(seg, &fsp->m_u.tcp_ip4_spec,
- ICE_FLOW_SEG_HDR_SCTP,
- &perfect_filter);
- break;
- case IPV4_USER_FLOW:
- ret = ice_set_fdir_ip4_usr_seg(seg, &fsp->m_u.usr_ip4_spec,
- &perfect_filter);
- break;
- case TCP_V6_FLOW:
- ret = ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
- ICE_FLOW_SEG_HDR_TCP,
- &perfect_filter);
- break;
- case UDP_V6_FLOW:
- ret = ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
- ICE_FLOW_SEG_HDR_UDP,
- &perfect_filter);
- break;
- case SCTP_V6_FLOW:
- ret = ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
- ICE_FLOW_SEG_HDR_SCTP,
- &perfect_filter);
- break;
- case IPV6_USER_FLOW:
- ret = ice_set_fdir_ip6_usr_seg(seg, &fsp->m_u.usr_ip6_spec,
- &perfect_filter);
- break;
- case ETHER_FLOW:
+ if ((fsp->flow_type & ~FLOW_EXT) == ETHER_FLOW) {
ret = ice_set_ether_flow_seg(dev, seg, &fsp->m_u.ether_spec);
if (!ret && (fsp->m_ext.vlan_etype || fsp->m_ext.vlan_tci)) {
- if (!ice_fdir_vlan_valid(dev, fsp)) {
+ if (!ice_fdir_vlan_valid(dev, fsp))
ret = -EINVAL;
- break;
- }
- ret = ice_set_fdir_vlan_seg(seg, &fsp->m_ext);
+ else
+ ret = ice_set_fdir_vlan_seg(seg, &fsp->m_ext);
}
- break;
- default:
- ret = -EINVAL;
+ } else {
+ ret = ice_set_fdir_ip_flow_seg(fsp, seg, &perfect_filter);
}
if (ret)
goto err_exit;
@@ -2368,9 +2441,27 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
return -ENOSPC;
}
- /* ACL filter */
- if (pf->hw.acl_tbl && ice_is_acl_filter(fsp))
+ /* ACL filter, or this filter would cause an input set conflict with
+ * existing FD filters
+ */
+ if (pf->hw.acl_tbl &&
+ (ice_is_acl_filter(fsp) ||
+ ice_fdir_has_input_set_conflict(pf, fsp, &userdata))) {
+ /* The ACL programming path does not honor flex byte
+ * (user-def) constraints. Routing a flex filter to ACL would
+ * silently drop the flex match and offload a much broader
+ * rule than requested, so reject it explicitly instead.
+ */
+ if (userdata.flex_fltr) {
+ dev_info(dev, "Failed to add filter. Flex byte (user-def) filters cannot be offloaded to ACL.\n");
+ return -EOPNOTSUPP;
+ }
+
+ dev_dbg(dev, "ntuple filter at location %d offloaded to ACL instead of Flow Director\n",
+ fsp->location);
+
return ice_acl_add_rule_ethtool(vsi, cmd);
+ }
/* Only fdir filters below */
if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
--
2.47.1
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 12/12] ice: use ACL for ntuple rules that conflict with FDir
2026-09-17 18:39 ` [PATCH net-next v2 12/12] ice: use ACL for ntuple rules that conflict with FDir Tony Nguyen
@ 2026-09-21 19:57 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-21 19:57 UTC (permalink / raw)
To: anthony.l.nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
lukasz.czapnik, marcin.szycik, aleksandr.loktionov,
sandeep.penigalapati, ananth.s, alexander.duyck, sx.rinitha
Thank you for your contribution! Sashiko AI review found 8 potential
issue(s) to consider.
Critical: 0 · High: 2 (1) · Medium: 2 · Low: 4
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] ice_fdir_has_input_set_conflict()
(drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c:1543) reports a…
- [High] The new conflict check dereferences a possibly-NULL aRFS counter
block.
- [Medium] Conflict-routed rules that carry FLOW_EXT are rejected by the
ACL path with -EINVAL even when they contain no flex match, because…
- [Medium] ice_fdir_has_input_set_conflict() makes the FDir-vs-ACL
routing decision from state that can change before the decision is…
- [Low] Claimed regression: the new `if (userdata.flex_fltr) return
-EOPNOTSUPP;` block in ice_add_ntuple_ethtool() sits inside the…
- [Low] The two new messages in the ACL branch of
ice_add_ntuple_ethtool() describe an offload that may never occur.
- [Low] ice_fdir_has_input_set_conflict() documents `false` as "can use
FD" and ice_add_ntuple_ethtool() consumes it as permission to skip the…
- [Low] Documentation/networking/device_drivers/ethernet/intel/ice.rst
(lines 326-345) still states unconditionally that "For each flow-type,…
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the new input-
set-dependent backend selection makes it reachable for full-mask…
--
ice: use ACL for ntuple rules that conflict with FDir
This adds a pre-check that builds the candidate IP flow segment for an
ethtool ntuple rule, compares it against the active non-tunneled Flow
Director profile, and routes the rule to ACL when the input sets differ.
The IP flow segment setup is factored out into ice_set_fdir_ip_flow_seg()
so both the extraction-sequence path and the new check can use it.
A few questions below about the new helper and the ACL dispatch.
> diff --git a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> index fef8640d23fb6..691b4500e6be6 100644
> --- a/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> +++ b/drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c
> @@ -1484,6 +1484,120 @@ ice_set_fdir_vlan_seg(struct ice_flow_seg_info *seg,
> return 0;
> }
>
> +/**
> + * ice_set_fdir_ip_flow_seg - set IP flow segment based on ethtool flow type
> + * @fsp: pointer to ethtool Rx flow specification
> + * @seg: flow segment for programming
> + * @perfect_fltr: valid on success; returns true if perfect fltr, false if not
> + *
> + * Return: 0 on success and errno in case of error.
> + */
> +static int ice_set_fdir_ip_flow_seg(struct ethtool_rx_flow_spec *fsp,
> + struct ice_flow_seg_info *seg,
> + bool *perfect_fltr)
> +{
> + switch (fsp->flow_type & ~FLOW_EXT) {
[ ... ]
> + case TCP_V6_FLOW:
> + return ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
> + ICE_FLOW_SEG_HDR_TCP, perfect_fltr);
> + case UDP_V6_FLOW:
> + return ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
> + ICE_FLOW_SEG_HDR_UDP, perfect_fltr);
> + case SCTP_V6_FLOW:
> + return ice_set_fdir_ip6_seg(seg, &fsp->m_u.tcp_ip6_spec,
> + ICE_FLOW_SEG_HDR_SCTP,
> + perfect_fltr);
> + case IPV6_USER_FLOW:
> + return ice_set_fdir_ip6_usr_seg(seg, &fsp->m_u.usr_ip6_spec,
> + perfect_fltr);
> + default:
> + return -EINVAL;
> + }
> +}
> +
[ ... ]
> +static bool
> +ice_fdir_has_input_set_conflict(struct ice_pf *pf,
> + struct ethtool_rx_flow_spec *fsp,
> + const struct ice_rx_flow_userdef *user)
> +{
> + struct ice_flow_seg_info *test_seg, *old_seg;
> + bool perfect_fltr = false, conflict = false;
> + struct ice_fd_hw_prof *hw_prof;
> + struct ice_hw *hw = &pf->hw;
> + enum ice_fltr_ptype flow;
> + int err;
> +
> + if ((fsp->flow_type & ~FLOW_EXT) == ETHER_FLOW)
> + return false;
[Severity: High]
This gate excludes only ETHER_FLOW, and ice_set_fdir_ip_flow_seg() above
happily builds segments for TCP_V6_FLOW, UDP_V6_FLOW, SCTP_V6_FLOW and
IPV6_USER_FLOW, so a true verdict can be produced for IPv6 rules.
The only consumer of a true verdict is ice_acl_add_rule_ethtool(), which
starts with ice_acl_prof_add_ethtool(). Can ACL program IPv6 flow types
at all?
drivers/net/ethernet/intel/ice/ice_acl_main.c:ice_acl_prof_add_ethtool() {
switch (fsp->flow_type & ~FLOW_EXT) {
case TCP_V4_FLOW:
...
case IPV4_USER_FLOW:
...
default:
err = -EOPNOTSUPP;
}
}
With this sequence on an ACL-capable device:
ethtool -U ethX flow-type tcp6 src-ip A dst-ip B src-port P dst-port Q action 1
ethtool -U ethX flow-type tcp6 src-ip C action 2
the second rule has a full mask, so ice_is_acl_filter() returns false (it
only inspects the IPv4 specs), while ice_fdir_has_input_set_conflict()
returns true. Does the rule then end up in ice_acl_prof_add_ethtool()'s
default case and get rejected with -EOPNOTSUPP, instead of being offloaded?
If so, the descriptive rejection that used to come from
ice_fdir_set_hw_fltr_rule():
dev_err(dev, "Failed to add filter. Flow director filters on each port must have the same input set.\n");
return -EINVAL;
is no longer reached for these rules. Since the new gate only tests
pf->hw.acl_tbl (ACL block present) and never "ACL can offload this flow
type", should the conflict detection be restricted to the flow types
ice_acl_prof_add_ethtool() supports?
> +
> + flow = ice_ethtool_flow_to_fltr(fsp->flow_type & ~FLOW_EXT);
> + if (flow >= ICE_FLTR_PTYPE_MAX || !hw->fdir_prof ||
> + !hw->fdir_prof[flow]) {
> + return false;
> + }
> +
> + hw_prof = hw->fdir_prof[flow];
> + old_seg = hw_prof->fdir_seg[ICE_FD_HW_SEG_NON_TUN];
> +
> + /* A profile with no ethtool FDir filters (fdir_fltr_cnt == 0) may
> + * still be locked by aRFS perfect (4-tuple) filters, which keep their
> + * own active counters separate from fdir_fltr_cnt.
> + */
> + if (!old_seg || (hw->fdir_fltr_cnt[flow] == 0 &&
> + !ice_is_arfs_using_perfect_flow(hw, flow)))
> + return false;
[Severity: High]
Can ice_is_arfs_using_perfect_flow() be called here with
vsi->arfs_fltr_cntrs still NULL?
With CONFIG_RFS_ACCEL=y it dereferences the counter block without a NULL
check:
drivers/net/ethernet/intel/ice/ice_arfs.c:ice_is_arfs_using_perfect_flow() {
arfs_fltr_cntrs = vsi->arfs_fltr_cntrs;
/* active counters can be updated by multiple CPUs */
smp_mb__before_atomic();
switch (flow_type) {
case ICE_FLTR_PTYPE_NONF_IPV4_UDP:
return atomic_read(&arfs_fltr_cntrs->active_udpv4_cnt) > 0;
...
}
ice_set_features() enables Flow Director and ACL before the fallible aRFS
setup:
drivers/net/ethernet/intel/ice/ice_main.c:ice_set_features() {
ice_vsi_manage_fdir(vsi, ena);
ice_vsi_manage_acl(vsi, ena);
ena ? ice_init_arfs(vsi) : ice_clear_arfs(vsi);
}
and ice_init_arfs() swallows the allocation failure:
drivers/net/ethernet/intel/ice/ice_arfs.c:ice_init_arfs() {
if (ice_init_arfs_cntrs(vsi))
goto free_arfs_fltr_list;
...
}
so after "ethtool -K ethX ntuple on" with a failing allocation,
ICE_FLAG_FD_ENA is set, ice_fdir_create_dflt_rules() has already installed
the default perfect tcp4/udp4/tcp6/udp6 profiles, and
vsi->arfs_fltr_cntrs is NULL. A following "ethtool -U ethX flow-type
tcp4 ..." on a device with hw->acl_tbl set reaches this check with
old_seg != NULL and hw->fdir_fltr_cnt[flow] == 0. Does that oops inside
atomic_read()?
The missing NULL check in ice_is_arfs_using_perfect_flow() predates this
patch, but previously ice_fdir_set_hw_fltr_rule() compared the segments
first and returned -EEXIST without consulting the aRFS counters, so rules
whose input set matches the existing profile never touched them. Does
this new call site widen that exposure to the common case?
[Severity: Medium]
Is the state read here stable until it is acted upon?
hw->fdir_prof[flow], hw_prof->fdir_seg[ICE_FD_HW_SEG_NON_TUN],
hw->fdir_fltr_cnt[flow] and the aRFS atomics are all read without
hw->fdir_fltr_lock; ice_add_ntuple_ethtool() takes that mutex only later,
after ice_cfg_fdir_xtrct_seq() has already programmed the profile. The
aRFS counters are written from contexts that are not serialized against
the ethtool ioctl:
ice_service_task()
ice_sync_arfs_fltrs()
ice_arfs_add_flow_rules()
ice_arfs_update_active_fltr_cntrs()
and also from ice_rx_flow_steer().
So:
CPU0 (ethtool -U)
ice_fdir_has_input_set_conflict()
hw->fdir_fltr_cnt[flow] == 0
aRFS tcpv4 counter == 0 -> returns false, take the FDir path
CPU1 (service task)
ice_arfs_add_flow_rules()
ice_arfs_update_active_fltr_cntrs() -> counter becomes 1
CPU0 continues into ice_fdir_set_hw_fltr_rule(), which re-reads the same
state:
if (ice_is_arfs_using_perfect_flow(hw, flow)) {
dev_err(dev, "aRFS using perfect flow type %d, cannot change input set\n",
flow);
return -EINVAL;
}
Can that produce the -EINVAL this patch is meant to avoid, with no ACL
fallback left at that point? The mirror ordering would route to ACL a
rule Flow Director could have taken.
> +
> + test_seg = kzalloc_obj(*test_seg);
> + if (!test_seg)
> + return false;
[Severity: Low]
The kernel-doc for this helper documents false as "can use FD", and
ice_add_ntuple_ethtool() treats false as permission to skip the ACL
branch. Here false also means "the comparison could not be performed".
On this allocation failure the rule goes down the Flow Director path and
ice_fdir_set_hw_fltr_rule() rejects the differing input set:
if (hw->fdir_fltr_cnt[flow]) {
dev_err(dev, "Failed to add filter. Flow director filters on each port must have the same input set.\n");
return -EINVAL;
}
Should this report -ENOMEM (or still take the ACL route) rather than
returning the value that means no conflict?
The other early false path, ice_set_fdir_ip_flow_seg() returning an error,
looks benign since the FDir path re-runs the same builder and produces the
same errno.
> +
> + err = ice_set_fdir_ip_flow_seg(fsp, test_seg, &perfect_fltr);
> +
> + if (err) {
> + kfree(test_seg);
> + return false;
> + }
> +
> + if (user && user->flex_fltr)
> + ice_flow_add_fld_raw(test_seg, user->flex_offset,
> + ICE_FLTR_PRGM_FLEX_WORD_SIZE,
> + ICE_FLOW_FLD_OFF_INVAL,
> + ICE_FLOW_FLD_OFF_INVAL);
> +
> + /* Compare the test segment with the existing segment */
> + if (memcmp(old_seg, test_seg, sizeof(*test_seg)) != 0)
> + conflict = true;
> +
> + kfree(test_seg);
> +
> + return conflict;
> +}
> +
> /**
> * ice_cfg_fdir_xtrct_seq - Configure extraction sequence for the given filter
> * @pf: PF structure
[ ... ]
> @@ -2368,9 +2441,27 @@ int ice_add_ntuple_ethtool(struct ice_vsi *vsi, struct ethtool_rxnfc *cmd)
> return -ENOSPC;
> }
>
> - /* ACL filter */
> - if (pf->hw.acl_tbl && ice_is_acl_filter(fsp))
> + /* ACL filter, or this filter would cause an input set conflict with
> + * existing FD filters
> + */
> + if (pf->hw.acl_tbl &&
> + (ice_is_acl_filter(fsp) ||
> + ice_fdir_has_input_set_conflict(pf, fsp, &userdata))) {
> + /* The ACL programming path does not honor flex byte
> + * (user-def) constraints. Routing a flex filter to ACL would
> + * silently drop the flex match and offload a much broader
> + * rule than requested, so reject it explicitly instead.
> + */
> + if (userdata.flex_fltr) {
> + dev_info(dev, "Failed to add filter. Flex byte (user-def) filters cannot be offloaded to ACL.\n");
> + return -EOPNOTSUPP;
> + }
[Severity: Low]
This new rejection sits inside the combined condition, so it also applies
to the pre-existing partial-mask ACL route selected by
ice_is_acl_filter().
Such rules already failed before this patch, just with a different errno:
flex_fltr requires FLOW_EXT (ice_parse_rx_flow_user_data() returns early
when !(fsp->flow_type & FLOW_EXT)), and the ACL decoder strips only
FLOW_MAC_EXT:
drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c:ice_ntuple_set_input_set() {
else if (blk == ICE_BLK_ACL)
flow_mask = FLOW_MAC_EXT;
...
flow_type = fsp->flow_type & ~flow_mask;
...
switch (flow_type) {
...
default:
return -EINVAL;
}
}
So a command like
ethtool -U ethX flow-type tcp4 src-ip A m 0.255.255.255 ... user-def 0x4ffff
returned -EINVAL before and returns -EOPNOTSUPP now. Would it be worth
mentioning that user-visible errno change in the changelog, since it is
not a consequence of the conflict detection being added?
> +
> + dev_dbg(dev, "ntuple filter at location %d offloaded to ACL instead of Flow Director\n",
> + fsp->location);
[Severity: Low]
This message is printed before ice_acl_add_rule_ethtool() is called, so it
claims an offload that may never happen. For flow types that
ice_acl_prof_add_ethtool() rejects:
drivers/net/ethernet/intel/ice/ice_acl_main.c:ice_acl_prof_add_ethtool() {
default:
err = -EOPNOTSUPP;
}
the log says the rule was offloaded to ACL while the command failed.
Should it be emitted only after ice_acl_add_rule_ethtool() succeeds?
Similarly, the dev_info above attributes the failure to an ACL flex byte
limitation even for tcp6/udp6/sctp6/ip6, where ACL cannot take the rule at
all and the real blocker is the Flow Director input set conflict.
> +
> return ice_acl_add_rule_ethtool(vsi, cmd);
> + }
[Severity: Medium]
Is FLOW_EXT normalized consistently between the new selector and the ACL
destination?
ice_fdir_has_input_set_conflict() and ice_acl_prof_add_ethtool() both mask
with & ~FLOW_EXT, but ice_ntuple_set_input_set() uses FLOW_MAC_EXT for
ICE_BLK_ACL, so FLOW_EXT survives into its switch and hits
default: return -EINVAL.
A command that sets FLOW_EXT without any flex match, for example
ethtool -U ethX flow-type tcp4 src-ip C vlan 0x1 m 0xffff action 2
against an existing conflicting tcp4 FDir profile, leaves flex_fltr false
(m_ext.data is zero), so it passes the check above and is routed to ACL,
where it fails with -EINVAL.
By that point ice_acl_prof_add_ethtool() has already created and
programmed a profile for the flow type:
hw->acl_prof[fltr_type] = hw_prof;
Does the failed command leave that profile registered, so later ACL rules
for the same flow type with a different input set fail the
if (hw_prof->seg) memcmp check?
[Severity: High]
This isn't a bug introduced by this patch, but the new input-set-dependent
backend selection makes it reachable for full-mask rules: replacing an
ACL-backed ntuple rule with an FDir-backed rule at the same location looks
like it leaves the old ACL TCAM entry programmed.
The only ACL hardware removal in ice_ntuple_update_list_entry() is gated
on the replacement pointer being NULL:
drivers/net/ethernet/intel/ice/ice_ethtool_ntuple.c:ice_ntuple_update_list_entry() {
if (old_fltr->acl_fltr) {
...
if (!input) {
err = ice_del_acl_ethtool(hw, old_fltr);
if (err)
return err;
}
}
...
list_del(&old_fltr->fltr_node);
kfree(old_fltr);
}
which holds only for ice_acl_add_rule_ethtool() replacing ACL with ACL.
The Flow Director path afterwards calls ice_fdir_write_all_fltr(), which
does not touch the ACL table.
Sequence:
1. add an FDir rule matching src-ip
2. add a full-mask dst-ip rule at location L; the new conflict check
routes it to ACL
3. re-add at location L a src-ip rule compatible with the active FDir
profile; it now takes the FDir path
Does the ACL entry for the dst-ip match survive step 3 with no software
record, so it keeps dropping or steering matching packets and is invisible
to ethtool -u?
[Severity: Low]
Should Documentation/networking/device_drivers/ethernet/intel/ice.rst be
updated along with this? The Flow Director Filters notes still say
unconditionally:
For each flow-type, the programmed filters must all have the same
matching input set.
and the example there, showing
ethtool -U ethX flow-type ip4 src-ip 192.168.0.1 src-port 5300 action 7
ethtool -U ethX flow-type ip4 dst-ip 192.168.0.5 src-port 55 action 10
"will fail with an error", plus "on one device, you may not program two
tcp4 filters with different matching fields". On an ACL-capable device
that second rule now succeeds through ACL. The new flex byte limitation
is also undocumented.
>
> /* Only fdir filters below */
> if (!test_bit(ICE_FLAG_FD_ENA, pf->flags))
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v2 00/12][pull request] ice: Add ACL support
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (11 preceding siblings ...)
2026-09-17 18:39 ` [PATCH net-next v2 12/12] ice: use ACL for ntuple rules that conflict with FDir Tony Nguyen
@ 2026-09-21 16:02 ` Marcin Szycik
2026-09-23 1:17 ` Jakub Kicinski
2026-09-23 1:30 ` patchwork-bot+netdevbpf
13 siblings, 1 reply; 27+ messages in thread
From: Marcin Szycik @ 2026-09-21 16:02 UTC (permalink / raw)
To: Tony Nguyen, davem, kuba, pabeni, edumazet, andrew+netdev, netdev
Cc: aleksandr.loktionov, sandeep.penigalapati, ananth.s,
alexander.duyck
I see 0/9/18/2 issues were found by one of the sashikos [1] (other one still
embargoed), I assume some of them are valid. I will be analyzing and fixing
these for next version, unless all are false positives (unlikely).
For reference, previous version tally was 2/20/8/1 [2].
Thanks,
Marcin
[1] https://sashiko.dev/#/patchset/20260917183927.2692068-1-anthony.l.nguyen%40intel.com
[2] https://sashiko.dev/#/patchset/20260603220828.829969-1-anthony.l.nguyen%40intel.com
On 17.09.2026 20:39, Tony Nguyen wrote:
> Marcin Szycik says:
>
> E8xx hardware provides a Ternary Classifier block for implementing
> functions such as ACL (Access Control List). In this series it's simply
> referred to as "ACL".
>
> Implement ACL filtering. This expands support of network flow classification
> rules for the ethtool ntuple command. ACL filtering allows for an ip or port
> field's optional mask to be specified.
>
> Example filters:
> ethtool -N eth0 flow-type tcp4 dst-port 8880 m 0x00ff action 10
> ethtool -N eth0 flow-type tcp4 src-ip 192.168.0.55 m 0.0.0.255 action -1
>
> This is a resurrection of an old series from 2020 [1] with several
> improvements, but the fundamental logic unchanged. v1 was almost pulled
> in, but ultimately it was decided to drop it [2] because of unresolved
> issues. One issue was too many defensive NULL checks. Second issue is
> about inconsistency when using multiple input sets. Both are addressed
> in this patchset.
>
> More about the second issue:
>
> From [3]:
>> I would argue that you need to have some sort of logic that basically
>> checks to see if you are going to hit the input set issue and falls
>> back and applies the ACL rules. Otherwise you are significantly
>> hampering the usefulness of this filter type. It doesn't make sense
>> that dropping a field will cause a rule to fail to be added, but
>> masking a single bit in some field will make it valid. It would make
>> it a nightmare to use from the user point of view as the rules come
>> across as arbitrary.
>
> Flow Director (FD) has a hardware limitation where all filters for the same
> packet type must use identical input sets. Previously, attempting to add the
> second filter would fail.
>
> Patch 12 adds automatic fallback to ACL block when FD cannot accommodate a
> filter due to input set conflicts, which resolves this inconsistency.
>
> [1] https://lore.kernel.org/intel-wired-lan/20200914153720.48498-1-anthony.l.nguyen@intel.com
> [2] https://lore.kernel.org/netdev/7192efe4d27c93148b3205e65f37203c89170316.camel@intel.com/#t
> [3] https://lore.kernel.org/netdev/CAKgT0Ucxd5-gvEwWAdbL04ER2o++RX_oekUV3E0rYquEgFKj1w@mail.gmail.com
> ---
> v2:
> The biggest change is the addition of proper reset handling, which was
> apparently mostly missing in the original code - see patch 10
> * Add patches 2 and 10
> Patch 1:
> * Rename fdir_active_fltr to ntuple_active_fltr_cnt, as it will
> track the sum of fdir and ACL filters in future patches
> * Rename ice_fdir_update_cntrs() to ice_ntuple_update_cntrs(),
> move it to ice_ethtool_ntuple.c, and make it static. In future patches
> it will handle both fdir and ACL
> * ice_ntuple_update_cntrs(): join variable initialization and declaration
> Patch 3:
> * Remove example ethtool filters from commit message, as the feature is not
> fully implemented at this point in the patchset. Moved this part to
> the commit that finalizes functional implementation
> * ice_acl_create_tbl():
> * fill num_dependent_alloc_ids with actual value, not always
> ICE_AQC_MAX_CONCURRENT_ACL_TBL
> * fill remaining alloc_ids with ICE_AQC_CONCURR_ID_INVALID (was unused
> in previous versions and remaining alloc_ids left with 0)
> * struct ice_acl_tbl_params has a new member num_dep_tbls for tracking
> the number of dependent tables. This new member is explicitly set to
> 0 in the only caller, for clarity.
> * Note that the only caller (ice_init_acl()) doesn't use concurrent
> tables, so this specific configuration is currently unused
> * ice_acl_create_tbl(): move alloc_id check against ICE_AQC_ALLOC_ID_4K
> to the success path, as the AQ command might complete successfully
> with alloc_id set to below this value to indicate allocation failure.
> Also, the AQ command might fail in a way that leaves the response
> buffer invalid. IOW - the check was meaningless
> * ice_acl_create_tbl(): add unroll of ice_aq_alloc_acl_tbl() -
> ice_aq_dealloc_acl_tbl()
> * ice_init_features(): remove ICE_FLAG_FD_ENA flag dependency on ACL
> init. ACL should not be blocked by fdir being disabled
> * Add ice_acl_create_hw(). For now it's just called from ice_init_acl(),
> but will be reused in the rebuild path in the future
> Patch 4:
> * Make ice_acl_rem_flows() non-static. Remove its call from
> ice_vsi_manage_fdir(), instead call it from ice_deinit_acl()
> * ice_fdir_del_all_fltrs(), ice_fdir_replay_fltrs(): skip ACL filters
> Patch 5:
> * ice_{add,del}_ntuple_ethtool(): remove dependency on ICE_FLAG_FD_ENA.
> ACL should not be blocked by fdir being disabled
> * ice_acl_prof_add_ethtool(): don't free and NULL hw_prof on existing
> seg mismatch. This prevents deallocating an existing hw_prof, also
> leaking its seg
> Patch 8:
> * Move ICE_ACL_INVALID_SCEN definition to ice_acl.h - will be used by
> other files
> * Add ice_acl_set_act_drop() and ice_acl_set_act_fwd_queue() helpers -
> will be useful later
> * ice_flow_add_entry(): add entry list head initialization. In this
> patch it may be possible to have a NULL dereference when deleting
> entry, because it may not have been added to the list. This is handled
> in one of the following patches, but adding init won't hurt.
> Patch 9:
> * ice_flow_rem_prof_sync(): reset profile extraction if it's unused
> after removal
> * ice_ntuple_update_list_entry(): add a missing ice_fdir_rem_flow() call
> for ACL filter
> * ice_acl_rem_entry(): set err in loops only on failure. This way, AQ
> commands succeeding in the end won't override errors in the middle.
> Final cleanup of entry index will now only happen if all AQ writes
> succeeded
> * ice_acl_add_rule_ethtool(): remove old entry when updating an entry
> (same filter location). Without it, old entry remains in hw after
> update, in addition to the new entry. Repro:
> ethtool -N $PF1 flow-type tcp4 src-port 8080 m 0x0fff action -1 loc 5
> ethtool -N $PF1 flow-type tcp4 src-port 9090 m 0x0fff action -1 loc 5
> ethtool -N eth0 delete 5
> * ice_flow_acl_add_scen_entry_sync(): free hw counters in exchange
> actions and if the entry is being disregarded deallocated. This was
> previously done in a later patch, but makes more sense here.
> * ice_flow_add_entry(): same as above, move the change from future
> patch here
> * ice_flow_acl_add_scen_entry_sync(): store entry ID so it can be
> properly deleted later
> * ice_flow_acl_add_scen_entry_sync(): zero e->acts_cnt on success, so
> that ice_flow_acl_free_act_cntr() will be skipped. On failure, free
> exist->acts so exist won't carry stale counter references
> * Add ethtool command examples to commit message (moved from patch 2)
> Patch 11:
> * ice_flow_acl_add_scen_entry_sync(), ice_flow_add_entry():
> ice_flow_acl_free_act_cntr() additions make more sense in patch adding
> this code, move them to that patch. Now this patch is a pure refactor.
> Patch 12:
> * ice_fdir_has_input_set_conflict(): add a check if aRFS is using perfect
> filters that may cause a conflict
> * ice_add_ntuple_ethtool(): add a guard that rejects flex-byte (user-def)
> filters when they would be routed to ACL (since ACL ignores the flex
> constraint, it would silently offload a broader rule)
> * ice_add_ntuple_ethtool(): join subsequent conditions that call
> ice_acl_add_rule_ethtool() for clarity
>
> v1: https://lore.kernel.org/netdev/20260603220828.829969-11-anthony.l.nguyen@intel.com/
>
> The following are changes since commit 26ee8cd69d46a14b37ba5e512084fe80d730127a:
> net: qualcomm: rmnet: require CAP_NET_ADMIN in the real device netns for config ops
> and are available in the git repository at:
> git://git.kernel.org/pub/scm/linux/kernel/git/tnguy/next-queue 100GbE
>
> Lukasz Czapnik (1):
> ice: use ACL for ntuple rules that conflict with FDir
>
> Marcin Szycik (5):
> ice: remove unused ICE_FD_FLUSH_REQ from PF state
> Revert "ice: remove unused ice_flow_entry fields"
> ice: use plain alloc/dealloc for ice_ntuple_fltr
> ice: add ACL reset recovery and NTUPLE feature toggle
> ice: re-introduce ice_dealloc_flow_entry() helper
>
> Real Valiquette (5):
> ice: initialize ACL table
> ice: initialize ACL scenario
> ice: create flow profile
> ice: create ACL entry
> ice: program ACL entry
>
> Tony Nguyen (1):
> ice: rename shared Flow Director functions and structs
>
> drivers/net/ethernet/intel/ice/Makefile | 5 +-
> drivers/net/ethernet/intel/ice/ice.h | 27 +-
> drivers/net/ethernet/intel/ice/ice_acl.c | 486 +++++++
> drivers/net/ethernet/intel/ice/ice_acl.h | 177 +++
> drivers/net/ethernet/intel/ice/ice_acl_ctrl.c | 1140 +++++++++++++++
> drivers/net/ethernet/intel/ice/ice_acl_main.c | 464 ++++++
> drivers/net/ethernet/intel/ice/ice_acl_main.h | 10 +
> .../net/ethernet/intel/ice/ice_adminq_cmd.h | 393 ++++-
> drivers/net/ethernet/intel/ice/ice_arfs.c | 8 +-
> drivers/net/ethernet/intel/ice/ice_arfs.h | 2 +-
> drivers/net/ethernet/intel/ice/ice_ethtool.c | 13 +-
> ...ce_ethtool_fdir.c => ice_ethtool_ntuple.c} | 817 ++++++++---
> drivers/net/ethernet/intel/ice/ice_fdir.c | 41 +-
> drivers/net/ethernet/intel/ice/ice_fdir.h | 16 +-
> .../net/ethernet/intel/ice/ice_flex_pipe.c | 11 +-
> .../net/ethernet/intel/ice/ice_flex_pipe.h | 2 +
> drivers/net/ethernet/intel/ice/ice_flow.c | 1267 ++++++++++++++++-
> drivers/net/ethernet/intel/ice/ice_flow.h | 39 +-
> .../net/ethernet/intel/ice/ice_lan_tx_rx.h | 3 +
> drivers/net/ethernet/intel/ice/ice_lib.c | 10 +-
> drivers/net/ethernet/intel/ice/ice_main.c | 162 ++-
> drivers/net/ethernet/intel/ice/ice_type.h | 14 +-
> drivers/net/ethernet/intel/ice/virt/fdir.c | 32 +-
> 23 files changed, 4859 insertions(+), 280 deletions(-)
> create mode 100644 drivers/net/ethernet/intel/ice/ice_acl.c
> create mode 100644 drivers/net/ethernet/intel/ice/ice_acl.h
> create mode 100644 drivers/net/ethernet/intel/ice/ice_acl_ctrl.c
> create mode 100644 drivers/net/ethernet/intel/ice/ice_acl_main.c
> create mode 100644 drivers/net/ethernet/intel/ice/ice_acl_main.h
> rename drivers/net/ethernet/intel/ice/{ice_ethtool_fdir.c => ice_ethtool_ntuple.c} (74%)
>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH net-next v2 00/12][pull request] ice: Add ACL support
2026-09-21 16:02 ` [PATCH net-next v2 00/12][pull request] ice: Add ACL support Marcin Szycik
@ 2026-09-23 1:17 ` Jakub Kicinski
0 siblings, 0 replies; 27+ messages in thread
From: Jakub Kicinski @ 2026-09-23 1:17 UTC (permalink / raw)
To: Marcin Szycik
Cc: Tony Nguyen, davem, pabeni, edumazet, andrew+netdev, netdev,
aleksandr.loktionov, sandeep.penigalapati, ananth.s,
alexander.duyck
On Mon, 21 Sep 2026 18:02:49 +0200 Marcin Szycik wrote:
> I see 0/9/18/2 issues were found by one of the sashikos [1] (other one still
> embargoed), I assume some of them are valid. I will be analyzing and fixing
> these for next version, unless all are false positives (unlikely).
> For reference, previous version tally was 2/20/8/1 [2].
Thanks, FWIW I'll pick up the first two patches.
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net-next v2 00/12][pull request] ice: Add ACL support
2026-09-17 18:39 [PATCH net-next v2 00/12][pull request] ice: Add ACL support Tony Nguyen
` (12 preceding siblings ...)
2026-09-21 16:02 ` [PATCH net-next v2 00/12][pull request] ice: Add ACL support Marcin Szycik
@ 2026-09-23 1:30 ` patchwork-bot+netdevbpf
13 siblings, 0 replies; 27+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-23 1:30 UTC (permalink / raw)
To: Tony Nguyen
Cc: davem, kuba, pabeni, edumazet, andrew+netdev, netdev,
marcin.szycik, aleksandr.loktionov, sandeep.penigalapati,
ananth.s, alexander.duyck
Hello:
This series was applied to netdev/net-next.git (main)
by Tony Nguyen <anthony.l.nguyen@intel.com>:
On Thu, 17 Sep 2026 11:39:12 -0700 you wrote:
> Marcin Szycik says:
>
> E8xx hardware provides a Ternary Classifier block for implementing
> functions such as ACL (Access Control List). In this series it's simply
> referred to as "ACL".
>
> Implement ACL filtering. This expands support of network flow classification
> rules for the ethtool ntuple command. ACL filtering allows for an ip or port
> field's optional mask to be specified.
>
> [...]
Here is the summary with links:
- [net-next,v2,01/12] ice: rename shared Flow Director functions and structs
https://git.kernel.org/netdev/net-next/c/e3a93c886d9a
- [net-next,v2,02/12] ice: remove unused ICE_FD_FLUSH_REQ from PF state
https://git.kernel.org/netdev/net-next/c/9782619d054b
- [net-next,v2,03/12] ice: initialize ACL table
(no matching commit)
- [net-next,v2,04/12] ice: initialize ACL scenario
(no matching commit)
- [net-next,v2,05/12] ice: create flow profile
(no matching commit)
- [net-next,v2,06/12] Revert "ice: remove unused ice_flow_entry fields"
(no matching commit)
- [net-next,v2,07/12] ice: use plain alloc/dealloc for ice_ntuple_fltr
(no matching commit)
- [net-next,v2,08/12] ice: create ACL entry
(no matching commit)
- [net-next,v2,09/12] ice: program ACL entry
(no matching commit)
- [net-next,v2,10/12] ice: add ACL reset recovery and NTUPLE feature toggle
(no matching commit)
- [net-next,v2,11/12] ice: re-introduce ice_dealloc_flow_entry() helper
(no matching commit)
- [net-next,v2,12/12] ice: use ACL for ntuple rules that conflict with FDir
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 27+ messages in thread