FILE
prestera_counter.c
File preview with syntax highlighting.
linux-6.19.14/drivers/net/ethernet/marvell/prestera/prestera_counter.c
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 = to_delayed_work(work); 340 struct prestera_counter *counter = 341 container_of(dl_work, struct prestera_counter, stats_dw); 342 struct prestera_counter_block *block; 343 u32 resched_time = COUNTER_POLL_TIME; 344 u32 count = COUNTER_BULK_SIZE; 345 bool done = false; 346 int err; 347 u32 i; 348 349 block = prestera_counter_block_get_by_idx(counter, counter->curr_idx); 350 if (!block) { 351 if (counter->is_fetching) 352 goto abort; 353 354 goto next; 355 } 356 357 if (!counter->is_fetching) { 358 err = prestera_hw_counter_trigger(counter->sw, block->id); 359 if (err) 360 goto abort; 361 362 prestera_counter_block_lock(block); 363 block->is_updating = true; 364 prestera_counter_block_unlock(block); 365 366 counter->is_fetching = true; 367 counter->total_read = 0; 368 resched_time = COUNTER_RESCHED_TIME; 369 goto resched; 370 } 371 372 prestera_counter_block_lock(block); 373 err = prestera_hw_counters_get(counter->sw, counter->total_read, 374 &count, &done, 375 &block->stats[counter->total_read]); 376 prestera_counter_block_unlock(block); 377 if (err) 378 goto abort; 379 380 counter->total_read += count; 381 if (!done || counter->total_read < block->num_counters) { 382 resched_time = COUNTER_RESCHED_TIME; 383 goto resched; 384 } 385 386 for (i = 0; i < block->num_counters; i++) { 387 if (block->counter_flag[i] == COUNTER_FLAG_INVALID) { 388 prestera_counter_block_lock(block); 389 block->counter_flag[i] = COUNTER_FLAG_READY; 390 memset(&block->stats[i], 0, sizeof(*block->stats)); 391 prestera_counter_block_unlock(block); 392 } 393 } 394 395 prestera_counter_block_lock(block); 396 block->is_updating = false; 397 prestera_counter_block_unlock(block); 398 399 goto next; 400abort: 401 prestera_hw_counter_abort(counter->sw); 402next: 403 counter->is_fetching = false; 404 counter->curr_idx = 405 prestera_counter_block_idx_next(counter, counter->curr_idx); 406resched: 407 if (block) 408 prestera_counter_block_put(counter, block); 409 410 schedule_delayed_work(&counter->stats_dw, resched_time); 411} 412 413/* Can be executed without rtnl_lock(). 414 * So pay attention when something changing. 415 */ 416int prestera_counter_stats_get(struct prestera_counter *counter, 417 struct prestera_counter_block *block, 418 u32 counter_id, u64 *packets, u64 *bytes) 419{ 420 if (!block || !prestera_counter_is_ready(block, counter_id)) { 421 *packets = 0; 422 *bytes = 0; 423 return 0; 424 } 425 426 prestera_counter_block_lock(block); 427 *packets = block->stats[counter_id - block->offset].packets; 428 *bytes = block->stats[counter_id - block->offset].bytes; 429 430 prestera_counter_stats_clear(block, counter_id); 431 prestera_counter_block_unlock(block); 432 433 return 0; 434} 435 436int prestera_counter_init(struct prestera_switch *sw) 437{ 438 struct prestera_counter *counter; 439 440 counter = kzalloc(sizeof(*counter), GFP_KERNEL); 441 if (!counter) 442 return -ENOMEM; 443 444 counter->block_list = kzalloc(sizeof(*counter->block_list), GFP_KERNEL); 445 if (!counter->block_list) { 446 kfree(counter); 447 return -ENOMEM; 448 } 449 450 mutex_init(&counter->mtx); 451 counter->block_list_len = 1; 452 counter->sw = sw; 453 sw->counter = counter; 454 455 INIT_DELAYED_WORK(&counter->stats_dw, prestera_counter_stats_work); 456 schedule_delayed_work(&counter->stats_dw, COUNTER_POLL_TIME); 457 458 return 0; 459} 460 461void prestera_counter_fini(struct prestera_switch *sw) 462{ 463 struct prestera_counter *counter = sw->counter; 464 u32 i; 465 466 cancel_delayed_work_sync(&counter->stats_dw); 467 468 for (i = 0; i < counter->block_list_len; i++) 469 WARN_ON(counter->block_list[i]); 470 471 mutex_destroy(&counter->mtx); 472 kfree(counter->block_list); 473 kfree(counter); 474}
~/
Read-only file preview.