Skip to content

Commit

Permalink
net: stmmac: fix tc flower deletion for VLAN priority Rx steering
Browse files Browse the repository at this point in the history
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: 0e039f5 ("net: stmmac: add RX frame steering based on VLAN priority in tc flower")
Tested-by: Kurt Kanzenbach <[email protected]>
Signed-off-by: Ong Boon Leong <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
elvinongbl authored and davem330 committed Dec 14, 2021
1 parent b0cdc5d commit aeb7c75
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 13 deletions.
17 changes: 17 additions & 0 deletions drivers/net/ethernet/stmicro/stmmac/stmmac.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,19 @@ struct stmmac_flow_entry {
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 {
/* Frequently used values are kept adjacent for cache effect */
u32 tx_coal_frames[MTL_MAX_TX_QUEUES];
Expand Down Expand Up @@ -289,6 +302,10 @@ struct stmmac_priv {
struct stmmac_tc_entry *tc_entries;
unsigned int flow_entries_max;
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 */
struct stmmac_pps_cfg pps[STMMAC_PPS_MAX];
Expand Down
86 changes: 73 additions & 13 deletions drivers/net/ethernet/stmicro/stmmac/stmmac_tc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
struct dma_features *dma_cap = &priv->dma_cap;
unsigned int count;
int i;
int ret, i;

if (dma_cap->l3l4fnum) {
priv->flow_entries_max = dma_cap->l3l4fnum;
Expand All @@ -250,10 +272,14 @@ static int tc_init(struct stmmac_priv *priv)
for (i = 0; i < priv->flow_entries_max; 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);
}

ret = tc_rfs_init(priv);
if (ret)
return -ENOMEM;

if (!priv->plat->fpe_cfg) {
priv->plat->fpe_cfg = devm_kzalloc(priv->device,
sizeof(*priv->plat->fpe_cfg),
Expand Down Expand Up @@ -607,16 +633,45 @@ static int tc_del_flow(struct stmmac_priv *priv,
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)

static int tc_add_vlan_flow(struct stmmac_priv *priv,
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_dissector *dissector = rule->match.dissector;
int tc = tc_classid_to_hwtc(priv->dev, cls->classid);
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 */
if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_VLAN))
return -EINVAL;
Expand All @@ -638,6 +693,12 @@ static int tc_add_vlan_flow(struct stmmac_priv *priv,

prio = BIT(match.key->vlan_priority);
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;
Expand All @@ -646,20 +707,19 @@ static int tc_add_vlan_flow(struct stmmac_priv *priv,
static int tc_del_vlan_flow(struct stmmac_priv *priv,
struct flow_cls_offload *cls)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct flow_dissector *dissector = rule->match.dissector;
int tc = tc_classid_to_hwtc(priv->dev, cls->classid);
struct stmmac_rfs_entry *entry = tc_find_rfs(priv, cls, false);

/* Nothing to do here */
if (!dissector_uses_key(dissector, FLOW_DISSECTOR_KEY_VLAN))
return -EINVAL;
if (!entry || !entry->in_use || entry->type != STMMAC_RFS_T_VLAN)
return -ENOENT;

if (tc < 0) {
netdev_err(priv->dev, "Invalid traffic class\n");
return -EINVAL;
}
stmmac_rx_queue_prio(priv, priv->hw, 0, entry->tc);

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;
}
Expand Down

0 comments on commit aeb7c75

Please sign in to comment.