Commit 25027c84 authored by Vladimir Oltean's avatar Vladimir Oltean Committed by David S. Miller

net: dsa: felix: check the 32-bit PSFP stats against overflow

The Felix PSFP counters suffer from the same problem as the ocelot
ndo_get_stats64 ones - they are 32-bit, so they can easily overflow and
this can easily go undetected.

Add a custom hook in ocelot_check_stats_work() through which driver
specific actions can be taken, and update the stats for the existing
PSFP filters from that hook.

Previously, vsc9959_psfp_filter_add() and vsc9959_psfp_filter_del() were
serialized with respect to each other via rtnl_lock(). However, with the
new entry point into &psfp->sfi_list coming from the periodic worker, we
now need an explicit mutex to serialize access to these lists.

We used to keep a struct felix_stream_filter_counters on stack, through
which vsc9959_psfp_stats_get() - a FLOW_CLS_STATS callback - would
retrieve data from vsc9959_psfp_counters_get(). We need to become
smarter about that in 3 ways:

- we need to keep a persistent set of counters for each stream instead
  of keeping them on stack

- we need to promote those counters from u32 to u64, and create a
  procedure that properly keeps 64-bit counters. Since we clear the
  hardware counters anyway, and we poll every 2 seconds, a simple
  increment of a u64 counter with a u32 value will perfectly do the job.

- FLOW_CLS_STATS also expect incremental counters, so we also need to
  zeroize our u64 counters every time sch_flower calls us
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 96980ff7
...@@ -2046,7 +2046,15 @@ struct felix_stream { ...@@ -2046,7 +2046,15 @@ struct felix_stream {
u32 ssid; u32 ssid;
}; };
struct felix_stream_filter_counters {
u64 match;
u64 not_pass_gate;
u64 not_pass_sdu;
u64 red;
};
struct felix_stream_filter { struct felix_stream_filter {
struct felix_stream_filter_counters stats;
struct list_head list; struct list_head list;
refcount_t refcount; refcount_t refcount;
u32 index; u32 index;
...@@ -2061,13 +2069,6 @@ struct felix_stream_filter { ...@@ -2061,13 +2069,6 @@ struct felix_stream_filter {
u32 maxsdu; u32 maxsdu;
}; };
struct felix_stream_filter_counters {
u32 match;
u32 not_pass_gate;
u32 not_pass_sdu;
u32 red;
};
struct felix_stream_gate { struct felix_stream_gate {
u32 index; u32 index;
u8 enable; u8 enable;
...@@ -2571,31 +2572,6 @@ static void vsc9959_psfp_sgi_table_del(struct ocelot *ocelot, ...@@ -2571,31 +2572,6 @@ static void vsc9959_psfp_sgi_table_del(struct ocelot *ocelot,
} }
} }
static void vsc9959_psfp_counters_get(struct ocelot *ocelot, u32 index,
struct felix_stream_filter_counters *counters)
{
mutex_lock(&ocelot->stat_view_lock);
ocelot_rmw(ocelot, SYS_STAT_CFG_STAT_VIEW(index),
SYS_STAT_CFG_STAT_VIEW_M,
SYS_STAT_CFG);
counters->match = ocelot_read(ocelot, SYS_COUNT_SF_MATCHING_FRAMES);
counters->not_pass_gate = ocelot_read(ocelot,
SYS_COUNT_SF_NOT_PASSING_FRAMES);
counters->not_pass_sdu = ocelot_read(ocelot,
SYS_COUNT_SF_NOT_PASSING_SDU);
counters->red = ocelot_read(ocelot, SYS_COUNT_SF_RED_FRAMES);
/* Clear the PSFP counter. */
ocelot_write(ocelot,
SYS_STAT_CFG_STAT_VIEW(index) |
SYS_STAT_CFG_STAT_CLEAR_SHOT(0x10),
SYS_STAT_CFG);
mutex_unlock(&ocelot->stat_view_lock);
}
static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port, static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port,
struct flow_cls_offload *f) struct flow_cls_offload *f)
{ {
...@@ -2620,6 +2596,8 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port, ...@@ -2620,6 +2596,8 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port,
return ret; return ret;
} }
mutex_lock(&psfp->lock);
flow_action_for_each(i, a, &f->rule->action) { flow_action_for_each(i, a, &f->rule->action) {
switch (a->id) { switch (a->id) {
case FLOW_ACTION_GATE: case FLOW_ACTION_GATE:
...@@ -2661,6 +2639,7 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port, ...@@ -2661,6 +2639,7 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port,
sfi.maxsdu = a->police.mtu; sfi.maxsdu = a->police.mtu;
break; break;
default: default:
mutex_unlock(&psfp->lock);
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
} }
...@@ -2730,6 +2709,8 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port, ...@@ -2730,6 +2709,8 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port,
goto err; goto err;
} }
mutex_unlock(&psfp->lock);
return 0; return 0;
err: err:
...@@ -2739,6 +2720,8 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port, ...@@ -2739,6 +2720,8 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port,
if (sfi.fm_valid) if (sfi.fm_valid)
ocelot_vcap_policer_del(ocelot, sfi.fmid); ocelot_vcap_policer_del(ocelot, sfi.fmid);
mutex_unlock(&psfp->lock);
return ret; return ret;
} }
...@@ -2746,18 +2729,22 @@ static int vsc9959_psfp_filter_del(struct ocelot *ocelot, ...@@ -2746,18 +2729,22 @@ static int vsc9959_psfp_filter_del(struct ocelot *ocelot,
struct flow_cls_offload *f) struct flow_cls_offload *f)
{ {
struct felix_stream *stream, tmp, *stream_entry; struct felix_stream *stream, tmp, *stream_entry;
struct ocelot_psfp_list *psfp = &ocelot->psfp;
static struct felix_stream_filter *sfi; static struct felix_stream_filter *sfi;
struct ocelot_psfp_list *psfp;
psfp = &ocelot->psfp; mutex_lock(&psfp->lock);
stream = vsc9959_stream_table_get(&psfp->stream_list, f->cookie); stream = vsc9959_stream_table_get(&psfp->stream_list, f->cookie);
if (!stream) if (!stream) {
mutex_unlock(&psfp->lock);
return -ENOMEM; return -ENOMEM;
}
sfi = vsc9959_psfp_sfi_table_get(&psfp->sfi_list, stream->sfid); sfi = vsc9959_psfp_sfi_table_get(&psfp->sfi_list, stream->sfid);
if (!sfi) if (!sfi) {
mutex_unlock(&psfp->lock);
return -ENOMEM; return -ENOMEM;
}
if (sfi->sg_valid) if (sfi->sg_valid)
vsc9959_psfp_sgi_table_del(ocelot, sfi->sgid); vsc9959_psfp_sgi_table_del(ocelot, sfi->sgid);
...@@ -2783,27 +2770,83 @@ static int vsc9959_psfp_filter_del(struct ocelot *ocelot, ...@@ -2783,27 +2770,83 @@ static int vsc9959_psfp_filter_del(struct ocelot *ocelot,
stream_entry->ports); stream_entry->ports);
} }
mutex_unlock(&psfp->lock);
return 0; return 0;
} }
static void vsc9959_update_sfid_stats(struct ocelot *ocelot,
struct felix_stream_filter *sfi)
{
struct felix_stream_filter_counters *s = &sfi->stats;
u32 match, not_pass_gate, not_pass_sdu, red;
u32 sfid = sfi->index;
lockdep_assert_held(&ocelot->stat_view_lock);
ocelot_rmw(ocelot, SYS_STAT_CFG_STAT_VIEW(sfid),
SYS_STAT_CFG_STAT_VIEW_M,
SYS_STAT_CFG);
match = ocelot_read(ocelot, SYS_COUNT_SF_MATCHING_FRAMES);
not_pass_gate = ocelot_read(ocelot, SYS_COUNT_SF_NOT_PASSING_FRAMES);
not_pass_sdu = ocelot_read(ocelot, SYS_COUNT_SF_NOT_PASSING_SDU);
red = ocelot_read(ocelot, SYS_COUNT_SF_RED_FRAMES);
/* Clear the PSFP counter. */
ocelot_write(ocelot,
SYS_STAT_CFG_STAT_VIEW(sfid) |
SYS_STAT_CFG_STAT_CLEAR_SHOT(0x10),
SYS_STAT_CFG);
s->match += match;
s->not_pass_gate += not_pass_gate;
s->not_pass_sdu += not_pass_sdu;
s->red += red;
}
/* Caller must hold &ocelot->stat_view_lock */
static void vsc9959_update_stats(struct ocelot *ocelot)
{
struct ocelot_psfp_list *psfp = &ocelot->psfp;
struct felix_stream_filter *sfi;
mutex_lock(&psfp->lock);
list_for_each_entry(sfi, &psfp->sfi_list, list)
vsc9959_update_sfid_stats(ocelot, sfi);
mutex_unlock(&psfp->lock);
}
static int vsc9959_psfp_stats_get(struct ocelot *ocelot, static int vsc9959_psfp_stats_get(struct ocelot *ocelot,
struct flow_cls_offload *f, struct flow_cls_offload *f,
struct flow_stats *stats) struct flow_stats *stats)
{ {
struct felix_stream_filter_counters counters; struct ocelot_psfp_list *psfp = &ocelot->psfp;
struct ocelot_psfp_list *psfp; struct felix_stream_filter_counters *s;
static struct felix_stream_filter *sfi;
struct felix_stream *stream; struct felix_stream *stream;
psfp = &ocelot->psfp;
stream = vsc9959_stream_table_get(&psfp->stream_list, f->cookie); stream = vsc9959_stream_table_get(&psfp->stream_list, f->cookie);
if (!stream) if (!stream)
return -ENOMEM; return -ENOMEM;
vsc9959_psfp_counters_get(ocelot, stream->sfid, &counters); sfi = vsc9959_psfp_sfi_table_get(&psfp->sfi_list, stream->sfid);
if (!sfi)
return -EINVAL;
mutex_lock(&ocelot->stat_view_lock);
vsc9959_update_sfid_stats(ocelot, sfi);
s = &sfi->stats;
stats->pkts = s->match;
stats->drops = s->not_pass_gate + s->not_pass_sdu + s->red;
stats->pkts = counters.match; memset(s, 0, sizeof(*s));
stats->drops = counters.not_pass_gate + counters.not_pass_sdu +
counters.red; mutex_unlock(&ocelot->stat_view_lock);
return 0; return 0;
} }
...@@ -2815,6 +2858,7 @@ static void vsc9959_psfp_init(struct ocelot *ocelot) ...@@ -2815,6 +2858,7 @@ static void vsc9959_psfp_init(struct ocelot *ocelot)
INIT_LIST_HEAD(&psfp->stream_list); INIT_LIST_HEAD(&psfp->stream_list);
INIT_LIST_HEAD(&psfp->sfi_list); INIT_LIST_HEAD(&psfp->sfi_list);
INIT_LIST_HEAD(&psfp->sgi_list); INIT_LIST_HEAD(&psfp->sgi_list);
mutex_init(&psfp->lock);
} }
/* When using cut-through forwarding and the egress port runs at a higher data /* When using cut-through forwarding and the egress port runs at a higher data
...@@ -2913,6 +2957,7 @@ static const struct ocelot_ops vsc9959_ops = { ...@@ -2913,6 +2957,7 @@ static const struct ocelot_ops vsc9959_ops = {
.psfp_stats_get = vsc9959_psfp_stats_get, .psfp_stats_get = vsc9959_psfp_stats_get,
.cut_through_fwd = vsc9959_cut_through_fwd, .cut_through_fwd = vsc9959_cut_through_fwd,
.tas_clock_adjust = vsc9959_tas_clock_adjust, .tas_clock_adjust = vsc9959_tas_clock_adjust,
.update_stats = vsc9959_update_stats,
}; };
static const struct felix_info felix_info_vsc9959 = { static const struct felix_info felix_info_vsc9959 = {
......
...@@ -1934,6 +1934,9 @@ static void ocelot_check_stats_work(struct work_struct *work) ...@@ -1934,6 +1934,9 @@ static void ocelot_check_stats_work(struct work_struct *work)
spin_unlock(&ocelot->stats_lock); spin_unlock(&ocelot->stats_lock);
} }
if (!err && ocelot->ops->update_stats)
ocelot->ops->update_stats(ocelot);
mutex_unlock(&ocelot->stat_view_lock); mutex_unlock(&ocelot->stat_view_lock);
if (err) if (err)
......
...@@ -729,6 +729,7 @@ struct ocelot_ops { ...@@ -729,6 +729,7 @@ struct ocelot_ops {
struct flow_stats *stats); struct flow_stats *stats);
void (*cut_through_fwd)(struct ocelot *ocelot); void (*cut_through_fwd)(struct ocelot *ocelot);
void (*tas_clock_adjust)(struct ocelot *ocelot); void (*tas_clock_adjust)(struct ocelot *ocelot);
void (*update_stats)(struct ocelot *ocelot);
}; };
struct ocelot_vcap_policer { struct ocelot_vcap_policer {
...@@ -766,6 +767,8 @@ struct ocelot_psfp_list { ...@@ -766,6 +767,8 @@ struct ocelot_psfp_list {
struct list_head stream_list; struct list_head stream_list;
struct list_head sfi_list; struct list_head sfi_list;
struct list_head sgi_list; struct list_head sgi_list;
/* Serialize access to the lists */
struct mutex lock;
}; };
enum ocelot_sb { enum ocelot_sb {
......
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