diff options
author | Vladimir Oltean <vladimir.oltean@nxp.com> | 2022-09-08 19:48:05 +0300 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2022-09-09 10:59:12 +0100 |
commit | 25027c8409b4541611d0060077607d8ca70740df (patch) | |
tree | d44a818d9880dac79d364bcf7c518ebcf52d8cf8 /drivers/net/dsa/ocelot | |
parent | 96980ff7c2caa5baef0c684e719547a53762e82c (diff) | |
download | linux-25027c8409b4541611d0060077607d8ca70740df.tar.gz |
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: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/dsa/ocelot')
-rw-r--r-- | drivers/net/dsa/ocelot/felix_vsc9959.c | 131 |
1 files changed, 88 insertions, 43 deletions
diff --git a/drivers/net/dsa/ocelot/felix_vsc9959.c b/drivers/net/dsa/ocelot/felix_vsc9959.c index 858ccf1e67f0..b815bc4278d9 100644 --- a/drivers/net/dsa/ocelot/felix_vsc9959.c +++ b/drivers/net/dsa/ocelot/felix_vsc9959.c @@ -2046,7 +2046,15 @@ struct felix_stream { 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_counters stats; struct list_head list; refcount_t refcount; u32 index; @@ -2061,13 +2069,6 @@ struct felix_stream_filter { u32 maxsdu; }; -struct felix_stream_filter_counters { - u32 match; - u32 not_pass_gate; - u32 not_pass_sdu; - u32 red; -}; - struct felix_stream_gate { u32 index; u8 enable; @@ -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, struct flow_cls_offload *f) { @@ -2620,6 +2596,8 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port, return ret; } + mutex_lock(&psfp->lock); + flow_action_for_each(i, a, &f->rule->action) { switch (a->id) { case FLOW_ACTION_GATE: @@ -2661,6 +2639,7 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port, sfi.maxsdu = a->police.mtu; break; default: + mutex_unlock(&psfp->lock); return -EOPNOTSUPP; } } @@ -2730,6 +2709,8 @@ static int vsc9959_psfp_filter_add(struct ocelot *ocelot, int port, goto err; } + mutex_unlock(&psfp->lock); + return 0; err: @@ -2739,6 +2720,8 @@ err: if (sfi.fm_valid) ocelot_vcap_policer_del(ocelot, sfi.fmid); + mutex_unlock(&psfp->lock); + return ret; } @@ -2746,18 +2729,22 @@ static int vsc9959_psfp_filter_del(struct ocelot *ocelot, struct flow_cls_offload *f) { struct felix_stream *stream, tmp, *stream_entry; + struct ocelot_psfp_list *psfp = &ocelot->psfp; 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); - if (!stream) + if (!stream) { + mutex_unlock(&psfp->lock); return -ENOMEM; + } sfi = vsc9959_psfp_sfi_table_get(&psfp->sfi_list, stream->sfid); - if (!sfi) + if (!sfi) { + mutex_unlock(&psfp->lock); return -ENOMEM; + } if (sfi->sg_valid) vsc9959_psfp_sgi_table_del(ocelot, sfi->sgid); @@ -2783,27 +2770,83 @@ static int vsc9959_psfp_filter_del(struct ocelot *ocelot, stream_entry->ports); } + mutex_unlock(&psfp->lock); + 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, struct flow_cls_offload *f, struct flow_stats *stats) { - struct felix_stream_filter_counters counters; - struct ocelot_psfp_list *psfp; + struct ocelot_psfp_list *psfp = &ocelot->psfp; + struct felix_stream_filter_counters *s; + static struct felix_stream_filter *sfi; struct felix_stream *stream; - psfp = &ocelot->psfp; stream = vsc9959_stream_table_get(&psfp->stream_list, f->cookie); if (!stream) 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; - stats->drops = counters.not_pass_gate + counters.not_pass_sdu + - counters.red; + memset(s, 0, sizeof(*s)); + + mutex_unlock(&ocelot->stat_view_lock); return 0; } @@ -2815,6 +2858,7 @@ static void vsc9959_psfp_init(struct ocelot *ocelot) INIT_LIST_HEAD(&psfp->stream_list); INIT_LIST_HEAD(&psfp->sfi_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 @@ -2913,6 +2957,7 @@ static const struct ocelot_ops vsc9959_ops = { .psfp_stats_get = vsc9959_psfp_stats_get, .cut_through_fwd = vsc9959_cut_through_fwd, .tas_clock_adjust = vsc9959_tas_clock_adjust, + .update_stats = vsc9959_update_stats, }; static const struct felix_info felix_info_vsc9959 = { |