prestera_counter.c (11370B)
1// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 2/* Copyright (c) 2021 Marvell International Ltd. All rights reserved */ 3 4#include "prestera.h" 5#include "prestera_hw.h" 6#include "prestera_acl.h" 7#include "prestera_counter.h" 8 9#define COUNTER_POLL_TIME (msecs_to_jiffies(1000)) 10#define COUNTER_RESCHED_TIME (msecs_to_jiffies(50)) 11#define COUNTER_BULK_SIZE (256) 12 13struct prestera_counter { 14 struct prestera_switch *sw; 15 struct delayed_work stats_dw; 16 struct mutex mtx; /* protect block_list */ 17 struct prestera_counter_block **block_list; 18 u32 total_read; 19 u32 block_list_len; 20 u32 curr_idx; 21 bool is_fetching; 22}; 23 24struct prestera_counter_block { 25 struct list_head list; 26 u32 id; 27 u32 offset; 28 u32 num_counters; 29 u32 client; 30 struct idr counter_idr; 31 refcount_t refcnt; 32 struct mutex mtx; /* protect stats and counter_idr */ 33 struct prestera_counter_stats *stats; 34 u8 *counter_flag; 35 bool is_updating; 36 bool full; 37}; 38 39enum { 40 COUNTER_FLAG_READY = 0, 41 COUNTER_FLAG_INVALID = 1 42}; 43 44static bool 45prestera_counter_is_ready(struct prestera_counter_block *block, u32 id) 46{ 47 return block->counter_flag[id - block->offset] == COUNTER_FLAG_READY; 48} 49 50static void prestera_counter_lock(struct prestera_counter *counter) 51{ 52 mutex_lock(&counter->mtx); 53} 54 55static void prestera_counter_unlock(struct prestera_counter *counter) 56{ 57 mutex_unlock(&counter->mtx); 58} 59 60static void prestera_counter_block_lock(struct prestera_counter_block *block) 61{ 62 mutex_lock(&block->mtx); 63} 64 65static void prestera_counter_block_unlock(struct prestera_counter_block *block) 66{ 67 mutex_unlock(&block->mtx); 68} 69 70static bool prestera_counter_block_incref(struct prestera_counter_block *block) 71{ 72 return refcount_inc_not_zero(&block->refcnt); 73} 74 75static bool prestera_counter_block_decref(struct prestera_counter_block *block) 76{ 77 return refcount_dec_and_test(&block->refcnt); 78} 79 80/* must be called with prestera_counter_block_lock() */ 81static void prestera_counter_stats_clear(struct prestera_counter_block *block, 82 u32 counter_id) 83{ 84 memset(&block->stats[counter_id - block->offset], 0, 85 sizeof(*block->stats)); 86} 87 88static struct prestera_counter_block * 89prestera_counter_block_lookup_not_full(struct prestera_counter *counter, 90 u32 client) 91{ 92 u32 i; 93 94 prestera_counter_lock(counter); 95 for (i = 0; i < counter->block_list_len; i++) { 96 if (counter->block_list[i] && 97 counter->block_list[i]->client == client && 98 !counter->block_list[i]->full && 99 prestera_counter_block_incref(counter->block_list[i])) { 100 prestera_counter_unlock(counter); 101 return counter->block_list[i]; 102 } 103 } 104 prestera_counter_unlock(counter); 105 106 return NULL; 107} 108 109static int prestera_counter_block_list_add(struct prestera_counter *counter, 110 struct prestera_counter_block *block) 111{ 112 struct prestera_counter_block **arr; 113 u32 i; 114 115 prestera_counter_lock(counter); 116 117 for (i = 0; i < counter->block_list_len; i++) { 118 if (counter->block_list[i]) 119 continue; 120 121 counter->block_list[i] = block; 122 prestera_counter_unlock(counter); 123 return 0; 124 } 125 126 arr = krealloc(counter->block_list, (counter->block_list_len + 1) * 127 sizeof(*counter->block_list), GFP_KERNEL); 128 if (!arr) { 129 prestera_counter_unlock(counter); 130 return -ENOMEM; 131 } 132 133 counter->block_list = arr; 134 counter->block_list[counter->block_list_len] = block; 135 counter->block_list_len++; 136 prestera_counter_unlock(counter); 137 return 0; 138} 139 140static struct prestera_counter_block * 141prestera_counter_block_get(struct prestera_counter *counter, u32 client) 142{ 143 struct prestera_counter_block *block; 144 int err; 145 146 block = prestera_counter_block_lookup_not_full(counter, client); 147 if (block) 148 return block; 149 150 block = kzalloc(sizeof(*block), GFP_KERNEL); 151 if (!block) 152 return ERR_PTR(-ENOMEM); 153 154 err = prestera_hw_counter_block_get(counter->sw, client, 155 &block->id, &block->offset, 156 &block->num_counters); 157 if (err) 158 goto err_block; 159 160 block->stats = kcalloc(block->num_counters, 161 sizeof(*block->stats), GFP_KERNEL); 162 if (!block->stats) { 163 err = -ENOMEM; 164 goto err_stats; 165 } 166 167 block->counter_flag = kcalloc(block->num_counters, 168 sizeof(*block->counter_flag), 169 GFP_KERNEL); 170 if (!block->counter_flag) { 171 err = -ENOMEM; 172 goto err_flag; 173 } 174 175 block->client = client; 176 mutex_init(&block->mtx); 177 refcount_set(&block->refcnt, 1); 178 idr_init_base(&block->counter_idr, block->offset); 179 180 err = prestera_counter_block_list_add(counter, block); 181 if (err) 182 goto err_list_add; 183 184 return block; 185 186err_list_add: 187 idr_destroy(&block->counter_idr); 188 mutex_destroy(&block->mtx); 189 kfree(block->counter_flag); 190err_flag: 191 kfree(block->stats); 192err_stats: 193 prestera_hw_counter_block_release(counter->sw, block->id); 194err_block: 195 kfree(block); 196 return ERR_PTR(err); 197} 198 199static void prestera_counter_block_put(struct prestera_counter *counter, 200 struct prestera_counter_block *block) 201{ 202 u32 i; 203 204 if (!prestera_counter_block_decref(block)) 205 return; 206 207 prestera_counter_lock(counter); 208 for (i = 0; i < counter->block_list_len; i++) { 209 if (counter->block_list[i] && 210 counter->block_list[i]->id == block->id) { 211 counter->block_list[i] = NULL; 212 break; 213 } 214 } 215 prestera_counter_unlock(counter); 216 217 WARN_ON(!idr_is_empty(&block->counter_idr)); 218 219 prestera_hw_counter_block_release(counter->sw, block->id); 220 idr_destroy(&block->counter_idr); 221 mutex_destroy(&block->mtx); 222 kfree(block->stats); 223 kfree(block); 224} 225 226static int prestera_counter_get_vacant(struct prestera_counter_block *block, 227 u32 *id) 228{ 229 int free_id; 230 231 if (block->full) 232 return -ENOSPC; 233 234 prestera_counter_block_lock(block); 235 free_id = idr_alloc_cyclic(&block->counter_idr, NULL, block->offset, 236 block->offset + block->num_counters, 237 GFP_KERNEL); 238 if (free_id < 0) { 239 if (free_id == -ENOSPC) 240 block->full = true; 241 242 prestera_counter_block_unlock(block); 243 return free_id; 244 } 245 *id = free_id; 246 prestera_counter_block_unlock(block); 247 248 return 0; 249} 250 251int prestera_counter_get(struct prestera_counter *counter, u32 client, 252 struct prestera_counter_block **bl, u32 *counter_id) 253{ 254 struct prestera_counter_block *block; 255 int err; 256 u32 id; 257 258get_next_block: 259 block = prestera_counter_block_get(counter, client); 260 if (IS_ERR(block)) 261 return PTR_ERR(block); 262 263 err = prestera_counter_get_vacant(block, &id); 264 if (err) { 265 prestera_counter_block_put(counter, block); 266 267 if (err == -ENOSPC) 268 goto get_next_block; 269 270 return err; 271 } 272 273 prestera_counter_block_lock(block); 274 if (block->is_updating) 275 block->counter_flag[id - block->offset] = COUNTER_FLAG_INVALID; 276 prestera_counter_block_unlock(block); 277 278 *counter_id = id; 279 *bl = block; 280 281 return 0; 282} 283 284void prestera_counter_put(struct prestera_counter *counter, 285 struct prestera_counter_block *block, u32 counter_id) 286{ 287 if (!block) 288 return; 289 290 prestera_counter_block_lock(block); 291 idr_remove(&block->counter_idr, counter_id); 292 block->full = false; 293 prestera_counter_stats_clear(block, counter_id); 294 prestera_counter_block_unlock(block); 295 296 prestera_hw_counter_clear(counter->sw, block->id, counter_id); 297 prestera_counter_block_put(counter, block); 298} 299 300static u32 prestera_counter_block_idx_next(struct prestera_counter *counter, 301 u32 curr_idx) 302{ 303 u32 idx, i, start = curr_idx + 1; 304 305 prestera_counter_lock(counter); 306 for (i = 0; i < counter->block_list_len; i++) { 307 idx = (start + i) % counter->block_list_len; 308 if (!counter->block_list[idx]) 309 continue; 310 311 prestera_counter_unlock(counter); 312 return idx; 313 } 314 prestera_counter_unlock(counter); 315 316 return 0; 317} 318 319static struct prestera_counter_block * 320prestera_counter_block_get_by_idx(struct prestera_counter *counter, u32 idx) 321{ 322 if (idx >= counter->block_list_len) 323 return NULL; 324 325 prestera_counter_lock(counter); 326 327 if (!counter->block_list[idx] || 328 !prestera_counter_block_incref(counter->block_list[idx])) { 329 prestera_counter_unlock(counter); 330 return NULL; 331 } 332 333 prestera_counter_unlock(counter); 334 return counter->block_list[idx]; 335} 336 337static void prestera_counter_stats_work(struct work_struct *work) 338{ 339 struct delayed_work *dl_work = 340 container_of(work, struct delayed_work, work); 341 struct prestera_counter *counter = 342 container_of(dl_work, struct prestera_counter, stats_dw); 343 struct prestera_counter_block *block; 344 u32 resched_time = COUNTER_POLL_TIME; 345 u32 count = COUNTER_BULK_SIZE; 346 bool done = false; 347 int err; 348 u32 i; 349 350 block = prestera_counter_block_get_by_idx(counter, counter->curr_idx); 351 if (!block) { 352 if (counter->is_fetching) 353 goto abort; 354 355 goto next; 356 } 357 358 if (!counter->is_fetching) { 359 err = prestera_hw_counter_trigger(counter->sw, block->id); 360 if (err) 361 goto abort; 362 363 prestera_counter_block_lock(block); 364 block->is_updating = true; 365 prestera_counter_block_unlock(block); 366 367 counter->is_fetching = true; 368 counter->total_read = 0; 369 resched_time = COUNTER_RESCHED_TIME; 370 goto resched; 371 } 372 373 prestera_counter_block_lock(block); 374 err = prestera_hw_counters_get(counter->sw, counter->total_read, 375 &count, &done, 376 &block->stats[counter->total_read]); 377 prestera_counter_block_unlock(block); 378 if (err) 379 goto abort; 380 381 counter->total_read += count; 382 if (!done || counter->total_read < block->num_counters) { 383 resched_time = COUNTER_RESCHED_TIME; 384 goto resched; 385 } 386 387 for (i = 0; i < block->num_counters; i++) { 388 if (block->counter_flag[i] == COUNTER_FLAG_INVALID) { 389 prestera_counter_block_lock(block); 390 block->counter_flag[i] = COUNTER_FLAG_READY; 391 memset(&block->stats[i], 0, sizeof(*block->stats)); 392 prestera_counter_block_unlock(block); 393 } 394 } 395 396 prestera_counter_block_lock(block); 397 block->is_updating = false; 398 prestera_counter_block_unlock(block); 399 400 goto next; 401abort: 402 prestera_hw_counter_abort(counter->sw); 403next: 404 counter->is_fetching = false; 405 counter->curr_idx = 406 prestera_counter_block_idx_next(counter, counter->curr_idx); 407resched: 408 if (block) 409 prestera_counter_block_put(counter, block); 410 411 schedule_delayed_work(&counter->stats_dw, resched_time); 412} 413 414/* Can be executed without rtnl_lock(). 415 * So pay attention when something changing. 416 */ 417int prestera_counter_stats_get(struct prestera_counter *counter, 418 struct prestera_counter_block *block, 419 u32 counter_id, u64 *packets, u64 *bytes) 420{ 421 if (!block || !prestera_counter_is_ready(block, counter_id)) { 422 *packets = 0; 423 *bytes = 0; 424 return 0; 425 } 426 427 prestera_counter_block_lock(block); 428 *packets = block->stats[counter_id - block->offset].packets; 429 *bytes = block->stats[counter_id - block->offset].bytes; 430 431 prestera_counter_stats_clear(block, counter_id); 432 prestera_counter_block_unlock(block); 433 434 return 0; 435} 436 437int prestera_counter_init(struct prestera_switch *sw) 438{ 439 struct prestera_counter *counter; 440 441 counter = kzalloc(sizeof(*counter), GFP_KERNEL); 442 if (!counter) 443 return -ENOMEM; 444 445 counter->block_list = kzalloc(sizeof(*counter->block_list), GFP_KERNEL); 446 if (!counter->block_list) { 447 kfree(counter); 448 return -ENOMEM; 449 } 450 451 mutex_init(&counter->mtx); 452 counter->block_list_len = 1; 453 counter->sw = sw; 454 sw->counter = counter; 455 456 INIT_DELAYED_WORK(&counter->stats_dw, prestera_counter_stats_work); 457 schedule_delayed_work(&counter->stats_dw, COUNTER_POLL_TIME); 458 459 return 0; 460} 461 462void prestera_counter_fini(struct prestera_switch *sw) 463{ 464 struct prestera_counter *counter = sw->counter; 465 u32 i; 466 467 cancel_delayed_work_sync(&counter->stats_dw); 468 469 for (i = 0; i < counter->block_list_len; i++) 470 WARN_ON(counter->block_list[i]); 471 472 mutex_destroy(&counter->mtx); 473 kfree(counter->block_list); 474 kfree(counter); 475}