Commit aeb7c75c authored by Ong Boon Leong's avatar Ong Boon Leong Committed by David S. Miller

net: stmmac: fix tc flower deletion for VLAN priority Rx steering

To replicate the issue:-

1) Add 1 flower filter for VLAN Priority based frame steering:-
$ IFDEVNAME=eth0
$ tc qdisc add dev $IFDEVNAME ingress
$ tc qdisc add dev $IFDEVNAME root mqprio num_tc 8 \
   map 0 1 2 3 4 5 6 7 0 0 0 0 0 0 0 0 \
   queues 1@0 1@1 1@2 1@3 1@4 1@5 1@6 1@7 hw 0
$ tc filter add dev $IFDEVNAME parent ffff: protocol 802.1Q \
   flower vlan_prio 0 hw_tc 0

2) Get the 'pref' id
$ tc filter show dev $IFDEVNAME ingress

3) Delete a specific tc flower record (say pref 49151)
$ tc filter del dev $IFDEVNAME parent ffff: pref 49151

From dmesg, we will observe kernel NULL pointer ooops

[  197.170464] BUG: kernel NULL pointer dereference, address: 0000000000000000
[  197.171367] #PF: supervisor read access in kernel mode
[  197.171367] #PF: error_code(0x0000) - not-present page
[  197.171367] PGD 0 P4D 0
[  197.171367] Oops: 0000 [#1] PREEMPT SMP NOPTI

<snip>

[  197.171367] RIP: 0010:tc_setup_cls+0x20b/0x4a0 [stmmac]

<snip>

[  197.171367] Call Trace:
[  197.171367]  <TASK>
[  197.171367]  ? __stmmac_disable_all_queues+0xa8/0xe0 [stmmac]
[  197.171367]  stmmac_setup_tc_block_cb+0x70/0x110 [stmmac]
[  197.171367]  tc_setup_cb_destroy+0xb3/0x180
[  197.171367]  fl_hw_destroy_filter+0x94/0xc0 [cls_flower]

The above issue is due to previous incorrect implementation of
tc_del_vlan_flow(), shown below, that uses flow_cls_offload_flow_rule()
to get struct flow_rule *rule which is no longer valid for tc filter
delete operation.

  struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
  struct flow_dissector *dissector = rule->match.dissector;

So, to ensure tc_del_vlan_flow() deletes the right VLAN cls record for
earlier configured RX queue (configured by hw_tc) in tc_add_vlan_flow(),
this patch introduces stmmac_rfs_entry as driver-side flow_cls_offload
record for 'RX frame steering' tc flower, currently used for VLAN
priority. The implementation has taken consideration for future extension
to include other type RX frame steering such as EtherType based.

v2:
 - Clean up overly extensive backtrace and rewrite git message to better
   explain the kernel NULL pointer issue.

Fixes: 0e039f5c ("net: stmmac: add RX frame steering based on VLAN priority in tc flower")
Tested-by: Kurt Kanzenbach's avatarKurt Kanzenbach <kurt@linutronix.de>
Signed-off-by: default avatarOng Boon Leong <boon.leong.ong@intel.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent b0cdc5db
...@@ -172,6 +172,19 @@ struct stmmac_flow_entry { ...@@ -172,6 +172,19 @@ struct stmmac_flow_entry {
int is_l4; int is_l4;
}; };
/* Rx Frame Steering */
enum stmmac_rfs_type {
STMMAC_RFS_T_VLAN,
STMMAC_RFS_T_MAX,
};
struct stmmac_rfs_entry {
unsigned long cookie;
int in_use;
int type;
int tc;
};
struct stmmac_priv { struct stmmac_priv {
/* Frequently used values are kept adjacent for cache effect */ /* Frequently used values are kept adjacent for cache effect */
u32 tx_coal_frames[MTL_MAX_TX_QUEUES]; u32 tx_coal_frames[MTL_MAX_TX_QUEUES];
...@@ -289,6 +302,10 @@ struct stmmac_priv { ...@@ -289,6 +302,10 @@ struct stmmac_priv {
struct stmmac_tc_entry *tc_entries; struct stmmac_tc_entry *tc_entries;
unsigned int flow_entries_max; unsigned int flow_entries_max;
struct stmmac_flow_entry *flow_entries; struct stmmac_flow_entry *flow_entries;
unsigned int rfs_entries_max[STMMAC_RFS_T_MAX];
unsigned int rfs_entries_cnt[STMMAC_RFS_T_MAX];
unsigned int rfs_entries_total;
struct stmmac_rfs_entry *rfs_entries;
/* Pulse Per Second output */ /* Pulse Per Second output */
struct stmmac_pps_cfg pps[STMMAC_PPS_MAX]; struct stmmac_pps_cfg pps[STMMAC_PPS_MAX];
......
...@@ -232,11 +232,33 @@ static int tc_setup_cls_u32(struct stmmac_priv *priv, ...@@ -232,11 +232,33 @@ static int tc_setup_cls_u32(struct stmmac_priv *priv,
} }
} }
static int tc_rfs_init(struct stmmac_priv *priv)
{
int i;
priv->rfs_entries_max[STMMAC_RFS_T_VLAN] = 8;
for (i = 0; i < STMMAC_RFS_T_MAX; i++)
priv->rfs_entries_total += priv->rfs_entries_max[i];
priv->rfs_entries = devm_kcalloc(priv->device,
priv->rfs_entries_total,
sizeof(*priv->rfs_entries),
GFP_KERNEL);
if (!priv->rfs_entries)
return -ENOMEM;
dev_info(priv->device, "Enabled RFS Flow TC (entries=%d)\n",
priv->rfs_entries_total);
return 0;
}
static int tc_init(struct stmmac_priv *priv) static int tc_init(struct stmmac_priv *priv)
{ {
struct dma_features *dma_cap = &priv->dma_cap; struct dma_features *dma_cap = &priv->dma_cap;
unsigned int count; unsigned int count;
int i; int ret, i;
if (dma_cap->l3l4fnum) { if (dma_cap->l3l4fnum) {
priv->flow_entries_max = dma_cap->l3l4fnum; priv->flow_entries_max = dma_cap->l3l4fnum;
...@@ -250,10 +272,14 @@ static int tc_init(struct stmmac_priv *priv) ...@@ -250,10 +272,14 @@ static int tc_init(struct stmmac_priv *priv)
for (i = 0; i < priv->flow_entries_max; i++) for (i = 0; i < priv->flow_entries_max; i++)
priv->flow_entries[i].idx = i; priv->flow_entries[i].idx = i;
dev_info(priv->device, "Enabled Flow TC (entries=%d)\n", dev_info(priv->device, "Enabled L3L4 Flow TC (entries=%d)\n",
priv->flow_entries_max); priv->flow_entries_max);
} }
ret = tc_rfs_init(priv);
if (ret)
return -ENOMEM;
if (!priv->plat->fpe_cfg) { if (!priv->plat->fpe_cfg) {
priv->plat->fpe_cfg = devm_kzalloc(priv->device, priv->plat->fpe_cfg = devm_kzalloc(priv->device,
sizeof(*priv->plat->fpe_cfg), sizeof(*priv->plat->fpe_cfg),
...@@ -607,16 +633,45 @@ static int tc_del_flow(struct stmmac_priv *priv, ...@@ -607,16 +633,45 @@ static int tc_del_flow(struct stmmac_priv *priv,
return ret; return ret;
} }
static struct stmmac_rfs_entry *tc_find_rfs(struct stmmac_priv *priv,
struct flow_cls_offload *cls,
bool get_free)
{
int i;
for (i = 0; i < priv->rfs_entries_total; i++) {
struct stmmac_rfs_entry *entry = &priv->rfs_entries[i];
if (entry->cookie == cls->cookie)
return entry;
if (get_free && entry->in_use == false)
return entry;
}
return NULL;
}
#define VLAN_PRIO_FULL_MASK (0x07) #define VLAN_PRIO_FULL_MASK (0x07)
static int tc_add_vlan_flow(struct stmmac_priv *priv, static int tc_add_vlan_flow(struct stmmac_priv *priv,
struct flow_cls_offload *cls) struct flow_cls_offload *cls)
{ {
struct stmmac_rfs_entry *entry = tc_find_rfs(priv, cls, false);
struct flow_rule *rule = flow_cls_offload_flow_rule(cls); struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct flow_dissector *dissector = rule->match.dissector; struct flow_dissector *dissector = rule->match.dissector;
int tc = tc_classid_to_hwtc(priv->dev, cls->classid); int tc = tc_classid_to_hwtc(priv->dev, cls->classid);
struct flow_match_vlan match; struct flow_match_vlan match;
if (!entry) {
entry = tc_find_rfs(priv, cls, true);
if (!entry)
return -ENOENT;
}
if (priv->rfs_entries_cnt[STMMAC_RFS_T_VLAN] >=
priv->rfs_entries_max[STMMAC_RFS_T_VLAN])
return -ENOENT;
/* Nothing to do here */ /* Nothing to do here */
if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_VLAN)) if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_VLAN))
return -EINVAL; return -EINVAL;
...@@ -638,6 +693,12 @@ static int tc_add_vlan_flow(struct stmmac_priv *priv, ...@@ -638,6 +693,12 @@ static int tc_add_vlan_flow(struct stmmac_priv *priv,
prio = BIT(match.key->vlan_priority); prio = BIT(match.key->vlan_priority);
stmmac_rx_queue_prio(priv, priv->hw, prio, tc); stmmac_rx_queue_prio(priv, priv->hw, prio, tc);
entry->in_use = true;
entry->cookie = cls->cookie;
entry->tc = tc;
entry->type = STMMAC_RFS_T_VLAN;
priv->rfs_entries_cnt[STMMAC_RFS_T_VLAN]++;
} }
return 0; return 0;
...@@ -646,20 +707,19 @@ static int tc_add_vlan_flow(struct stmmac_priv *priv, ...@@ -646,20 +707,19 @@ static int tc_add_vlan_flow(struct stmmac_priv *priv,
static int tc_del_vlan_flow(struct stmmac_priv *priv, static int tc_del_vlan_flow(struct stmmac_priv *priv,
struct flow_cls_offload *cls) struct flow_cls_offload *cls)
{ {
struct flow_rule *rule = flow_cls_offload_flow_rule(cls); struct stmmac_rfs_entry *entry = tc_find_rfs(priv, cls, false);
struct flow_dissector *dissector = rule->match.dissector;
int tc = tc_classid_to_hwtc(priv->dev, cls->classid);
/* Nothing to do here */ if (!entry || !entry->in_use || entry->type != STMMAC_RFS_T_VLAN)
if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_VLAN)) return -ENOENT;
return -EINVAL;
if (tc < 0) { stmmac_rx_queue_prio(priv, priv->hw, 0, entry->tc);
netdev_err(priv->dev, "Invalid traffic class\n");
return -EINVAL; entry->in_use = false;
} entry->cookie = 0;
entry->tc = 0;
entry->type = 0;
stmmac_rx_queue_prio(priv, priv->hw, 0, tc); priv->rfs_entries_cnt[STMMAC_RFS_T_VLAN]--;
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment