// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2021 Marvell International Ltd. All rights reserved */ #include "prestera.h" #include "prestera_hw.h" #include "prestera_acl.h" #include "prestera_counter.h" #define COUNTER_POLL_TIME (msecs_to_jiffies(1000)) #define COUNTER_RESCHED_TIME (msecs_to_jiffies(50)) #define COUNTER_BULK_SIZE (256) struct prestera_counter { struct prestera_switch *sw; struct delayed_work stats_dw; struct mutex mtx; /* protect block_list */ struct prestera_counter_block **block_list; u32 total_read; u32 block_list_len; u32 curr_idx; bool is_fetching; }; struct prestera_counter_block { struct list_head list; u32 id; u32 offset; u32 num_counters; u32 client; struct idr counter_idr; refcount_t refcnt; struct mutex mtx; /* protect stats and counter_idr */ struct prestera_counter_stats *stats; u8 *counter_flag; bool is_updating; bool full; }; enum { COUNTER_FLAG_READY = 0, COUNTER_FLAG_INVALID = 1 }; static bool prestera_counter_is_ready(struct prestera_counter_block *block, u32 id) { return block->counter_flag[id - block->offset] == COUNTER_FLAG_READY; } static void prestera_counter_lock(struct prestera_counter *counter) { mutex_lock(&counter->mtx); } static void prestera_counter_unlock(struct prestera_counter *counter) { mutex_unlock(&counter->mtx); } static void prestera_counter_block_lock(struct prestera_counter_block *block) { mutex_lock(&block->mtx); } static void prestera_counter_block_unlock(struct prestera_counter_block *block) { mutex_unlock(&block->mtx); } static bool prestera_counter_block_incref(struct prestera_counter_block *block) { return refcount_inc_not_zero(&block->refcnt); } static bool prestera_counter_block_decref(struct prestera_counter_block *block) { return refcount_dec_and_test(&block->refcnt); } /* must be called with prestera_counter_block_lock() */ static void prestera_counter_stats_clear(struct prestera_counter_block *block, u32 counter_id) { memset(&block->stats[counter_id - block->offset], 0, sizeof(*block->stats)); } static struct prestera_counter_block * prestera_counter_block_lookup_not_full(struct prestera_counter *counter, u32 client) { u32 i; prestera_counter_lock(counter); for (i = 0; i < counter->block_list_len; i++) { if (counter->block_list[i] && counter->block_list[i]->client == client && !counter->block_list[i]->full && prestera_counter_block_incref(counter->block_list[i])) { prestera_counter_unlock(counter); return counter->block_list[i]; } } prestera_counter_unlock(counter); return NULL; } static int prestera_counter_block_list_add(struct prestera_counter *counter, struct prestera_counter_block *block) { struct prestera_counter_block **arr; u32 i; prestera_counter_lock(counter); for (i = 0; i < counter->block_list_len; i++) { if (counter->block_list[i]) continue; counter->block_list[i] = block; prestera_counter_unlock(counter); return 0; } arr = krealloc(counter->block_list, (counter->block_list_len + 1) * sizeof(*counter->block_list), GFP_KERNEL); if (!arr) { prestera_counter_unlock(counter); return -ENOMEM; } counter->block_list = arr; counter->block_list[counter->block_list_len] = block; counter->block_list_len++; prestera_counter_unlock(counter); return 0; } static struct prestera_counter_block * prestera_counter_block_get(struct prestera_counter *counter, u32 client) { struct prestera_counter_block *block; int err; block = prestera_counter_block_lookup_not_full(counter, client); if (block) return block; block = kzalloc(sizeof(*block), GFP_KERNEL); if (!block) return ERR_PTR(-ENOMEM); err = prestera_hw_counter_block_get(counter->sw, client, &block->id, &block->offset, &block->num_counters); if (err) goto err_block; block->stats = kcalloc(block->num_counters, sizeof(*block->stats), GFP_KERNEL); if (!block->stats) { err = -ENOMEM; goto err_stats; } block->counter_flag = kcalloc(block->num_counters, sizeof(*block->counter_flag), GFP_KERNEL); if (!block->counter_flag) { err = -ENOMEM; goto err_flag; } block->client = client; mutex_init(&block->mtx); refcount_set(&block->refcnt, 1); idr_init_base(&block->counter_idr, block->offset); err = prestera_counter_block_list_add(counter, block); if (err) goto err_list_add; return block; err_list_add: idr_destroy(&block->counter_idr); mutex_destroy(&block->mtx); kfree(block->counter_flag); err_flag: kfree(block->stats); err_stats: prestera_hw_counter_block_release(counter->sw, block->id); err_block: kfree(block); return ERR_PTR(err); } static void prestera_counter_block_put(struct prestera_counter *counter, struct prestera_counter_block *block) { u32 i; if (!prestera_counter_block_decref(block)) return; prestera_counter_lock(counter); for (i = 0; i < counter->block_list_len; i++) { if (counter->block_list[i] && counter->block_list[i]->id == block->id) { counter->block_list[i] = NULL; break; } } prestera_counter_unlock(counter); WARN_ON(!idr_is_empty(&block->counter_idr)); prestera_hw_counter_block_release(counter->sw, block->id); idr_destroy(&block->counter_idr); mutex_destroy(&block->mtx); kfree(block->stats); kfree(block); } static int prestera_counter_get_vacant(struct prestera_counter_block *block, u32 *id) { int free_id; if (block->full) return -ENOSPC; prestera_counter_block_lock(block); free_id = idr_alloc_cyclic(&block->counter_idr, NULL, block->offset, block->offset + block->num_counters, GFP_KERNEL); if (free_id < 0) { if (free_id == -ENOSPC) block->full = true; prestera_counter_block_unlock(block); return free_id; } *id = free_id; prestera_counter_block_unlock(block); return 0; } int prestera_counter_get(struct prestera_counter *counter, u32 client, struct prestera_counter_block **bl, u32 *counter_id) { struct prestera_counter_block *block; int err; u32 id; get_next_block: block = prestera_counter_block_get(counter, client); if (IS_ERR(block)) return PTR_ERR(block); err = prestera_counter_get_vacant(block, &id); if (err) { prestera_counter_block_put(counter, block); if (err == -ENOSPC) goto get_next_block; return err; } prestera_counter_block_lock(block); if (block->is_updating) block->counter_flag[id - block->offset] = COUNTER_FLAG_INVALID; prestera_counter_block_unlock(block); *counter_id = id; *bl = block; return 0; } void prestera_counter_put(struct prestera_counter *counter, struct prestera_counter_block *block, u32 counter_id) { if (!block) return; prestera_counter_block_lock(block); idr_remove(&block->counter_idr, counter_id); block->full = false; prestera_counter_stats_clear(block, counter_id); prestera_counter_block_unlock(block); prestera_hw_counter_clear(counter->sw, block->id, counter_id); prestera_counter_block_put(counter, block); } static u32 prestera_counter_block_idx_next(struct prestera_counter *counter, u32 curr_idx) { u32 idx, i, start = curr_idx + 1; prestera_counter_lock(counter); for (i = 0; i < counter->block_list_len; i++) { idx = (start + i) % counter->block_list_len; if (!counter->block_list[idx]) continue; prestera_counter_unlock(counter); return idx; } prestera_counter_unlock(counter); return 0; } static struct prestera_counter_block * prestera_counter_block_get_by_idx(struct prestera_counter *counter, u32 idx) { if (idx >= counter->block_list_len) return NULL; prestera_counter_lock(counter); if (!counter->block_list[idx] || !prestera_counter_block_incref(counter->block_list[idx])) { prestera_counter_unlock(counter); return NULL; } prestera_counter_unlock(counter); return counter->block_list[idx]; } static void prestera_counter_stats_work(struct work_struct *work) { struct delayed_work *dl_work = container_of(work, struct delayed_work, work); struct prestera_counter *counter = container_of(dl_work, struct prestera_counter, stats_dw); struct prestera_counter_block *block; u32 resched_time = COUNTER_POLL_TIME; u32 count = COUNTER_BULK_SIZE; bool done = false; int err; u32 i; block = prestera_counter_block_get_by_idx(counter, counter->curr_idx); if (!block) { if (counter->is_fetching) goto abort; goto next; } if (!counter->is_fetching) { err = prestera_hw_counter_trigger(counter->sw, block->id); if (err) goto abort; prestera_counter_block_lock(block); block->is_updating = true; prestera_counter_block_unlock(block); counter->is_fetching = true; counter->total_read = 0; resched_time = COUNTER_RESCHED_TIME; goto resched; } prestera_counter_block_lock(block); err = prestera_hw_counters_get(counter->sw, counter->total_read, &count, &done, &block->stats[counter->total_read]); prestera_counter_block_unlock(block); if (err) goto abort; counter->total_read += count; if (!done || counter->total_read < block->num_counters) { resched_time = COUNTER_RESCHED_TIME; goto resched; } for (i = 0; i < block->num_counters; i++) { if (block->counter_flag[i] == COUNTER_FLAG_INVALID) { prestera_counter_block_lock(block); block->counter_flag[i] = COUNTER_FLAG_READY; memset(&block->stats[i], 0, sizeof(*block->stats)); prestera_counter_block_unlock(block); } } prestera_counter_block_lock(block); block->is_updating = false; prestera_counter_block_unlock(block); goto next; abort: prestera_hw_counter_abort(counter->sw); next: counter->is_fetching = false; counter->curr_idx = prestera_counter_block_idx_next(counter, counter->curr_idx); resched: if (block) prestera_counter_block_put(counter, block); schedule_delayed_work(&counter->stats_dw, resched_time); } /* Can be executed without rtnl_lock(). * So pay attention when something changing. */ int prestera_counter_stats_get(struct prestera_counter *counter, struct prestera_counter_block *block, u32 counter_id, u64 *packets, u64 *bytes) { if (!block || !prestera_counter_is_ready(block, counter_id)) { *packets = 0; *bytes = 0; return 0; } prestera_counter_block_lock(block); *packets = block->stats[counter_id - block->offset].packets; *bytes = block->stats[counter_id - block->offset].bytes; prestera_counter_stats_clear(block, counter_id); prestera_counter_block_unlock(block); return 0; } int prestera_counter_init(struct prestera_switch *sw) { struct prestera_counter *counter; counter = kzalloc(sizeof(*counter), GFP_KERNEL); if (!counter) return -ENOMEM; counter->block_list = kzalloc(sizeof(*counter->block_list), GFP_KERNEL); if (!counter->block_list) { kfree(counter); return -ENOMEM; } mutex_init(&counter->mtx); counter->block_list_len = 1; counter->sw = sw; sw->counter = counter; INIT_DELAYED_WORK(&counter->stats_dw, prestera_counter_stats_work); schedule_delayed_work(&counter->stats_dw, COUNTER_POLL_TIME); return 0; } void prestera_counter_fini(struct prestera_switch *sw) { struct prestera_counter *counter = sw->counter; u32 i; cancel_delayed_work_sync(&counter->stats_dw); for (i = 0; i < counter->block_list_len; i++) WARN_ON(counter->block_list[i]); mutex_destroy(&counter->mtx); kfree(counter->block_list); kfree(counter); }