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
13 struct 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
24 struct 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
39 enum {
40 COUNTER_FLAG_READY = 0,
41 COUNTER_FLAG_INVALID = 1
42 };
43
44 static bool
prestera_counter_is_ready(struct prestera_counter_block * block,u32 id)45 prestera_counter_is_ready(struct prestera_counter_block *block, u32 id)
46 {
47 return block->counter_flag[id - block->offset] == COUNTER_FLAG_READY;
48 }
49
prestera_counter_lock(struct prestera_counter * counter)50 static void prestera_counter_lock(struct prestera_counter *counter)
51 {
52 mutex_lock(&counter->mtx);
53 }
54
prestera_counter_unlock(struct prestera_counter * counter)55 static void prestera_counter_unlock(struct prestera_counter *counter)
56 {
57 mutex_unlock(&counter->mtx);
58 }
59
prestera_counter_block_lock(struct prestera_counter_block * block)60 static void prestera_counter_block_lock(struct prestera_counter_block *block)
61 {
62 mutex_lock(&block->mtx);
63 }
64
prestera_counter_block_unlock(struct prestera_counter_block * block)65 static void prestera_counter_block_unlock(struct prestera_counter_block *block)
66 {
67 mutex_unlock(&block->mtx);
68 }
69
prestera_counter_block_incref(struct prestera_counter_block * block)70 static bool prestera_counter_block_incref(struct prestera_counter_block *block)
71 {
72 return refcount_inc_not_zero(&block->refcnt);
73 }
74
prestera_counter_block_decref(struct prestera_counter_block * block)75 static 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() */
prestera_counter_stats_clear(struct prestera_counter_block * block,u32 counter_id)81 static 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
88 static struct prestera_counter_block *
prestera_counter_block_lookup_not_full(struct prestera_counter * counter,u32 client)89 prestera_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
prestera_counter_block_list_add(struct prestera_counter * counter,struct prestera_counter_block * block)109 static 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
140 static struct prestera_counter_block *
prestera_counter_block_get(struct prestera_counter * counter,u32 client)141 prestera_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_obj(*block);
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 = kzalloc_objs(*block->stats, block->num_counters);
161 if (!block->stats) {
162 err = -ENOMEM;
163 goto err_stats;
164 }
165
166 block->counter_flag = kcalloc(block->num_counters,
167 sizeof(*block->counter_flag),
168 GFP_KERNEL);
169 if (!block->counter_flag) {
170 err = -ENOMEM;
171 goto err_flag;
172 }
173
174 block->client = client;
175 mutex_init(&block->mtx);
176 refcount_set(&block->refcnt, 1);
177 idr_init_base(&block->counter_idr, block->offset);
178
179 err = prestera_counter_block_list_add(counter, block);
180 if (err)
181 goto err_list_add;
182
183 return block;
184
185 err_list_add:
186 idr_destroy(&block->counter_idr);
187 mutex_destroy(&block->mtx);
188 kfree(block->counter_flag);
189 err_flag:
190 kfree(block->stats);
191 err_stats:
192 prestera_hw_counter_block_release(counter->sw, block->id);
193 err_block:
194 kfree(block);
195 return ERR_PTR(err);
196 }
197
prestera_counter_block_put(struct prestera_counter * counter,struct prestera_counter_block * block)198 static void prestera_counter_block_put(struct prestera_counter *counter,
199 struct prestera_counter_block *block)
200 {
201 u32 i;
202
203 if (!prestera_counter_block_decref(block))
204 return;
205
206 prestera_counter_lock(counter);
207 for (i = 0; i < counter->block_list_len; i++) {
208 if (counter->block_list[i] &&
209 counter->block_list[i]->id == block->id) {
210 counter->block_list[i] = NULL;
211 break;
212 }
213 }
214 prestera_counter_unlock(counter);
215
216 WARN_ON(!idr_is_empty(&block->counter_idr));
217
218 prestera_hw_counter_block_release(counter->sw, block->id);
219 idr_destroy(&block->counter_idr);
220 mutex_destroy(&block->mtx);
221 kfree(block->stats);
222 kfree(block);
223 }
224
prestera_counter_get_vacant(struct prestera_counter_block * block,u32 * id)225 static int prestera_counter_get_vacant(struct prestera_counter_block *block,
226 u32 *id)
227 {
228 int free_id;
229
230 if (block->full)
231 return -ENOSPC;
232
233 prestera_counter_block_lock(block);
234 free_id = idr_alloc_cyclic(&block->counter_idr, NULL, block->offset,
235 block->offset + block->num_counters,
236 GFP_KERNEL);
237 if (free_id < 0) {
238 if (free_id == -ENOSPC)
239 block->full = true;
240
241 prestera_counter_block_unlock(block);
242 return free_id;
243 }
244 *id = free_id;
245 prestera_counter_block_unlock(block);
246
247 return 0;
248 }
249
prestera_counter_get(struct prestera_counter * counter,u32 client,struct prestera_counter_block ** bl,u32 * counter_id)250 int prestera_counter_get(struct prestera_counter *counter, u32 client,
251 struct prestera_counter_block **bl, u32 *counter_id)
252 {
253 struct prestera_counter_block *block;
254 int err;
255 u32 id;
256
257 get_next_block:
258 block = prestera_counter_block_get(counter, client);
259 if (IS_ERR(block))
260 return PTR_ERR(block);
261
262 err = prestera_counter_get_vacant(block, &id);
263 if (err) {
264 prestera_counter_block_put(counter, block);
265
266 if (err == -ENOSPC)
267 goto get_next_block;
268
269 return err;
270 }
271
272 prestera_counter_block_lock(block);
273 if (block->is_updating)
274 block->counter_flag[id - block->offset] = COUNTER_FLAG_INVALID;
275 prestera_counter_block_unlock(block);
276
277 *counter_id = id;
278 *bl = block;
279
280 return 0;
281 }
282
prestera_counter_put(struct prestera_counter * counter,struct prestera_counter_block * block,u32 counter_id)283 void prestera_counter_put(struct prestera_counter *counter,
284 struct prestera_counter_block *block, u32 counter_id)
285 {
286 if (!block)
287 return;
288
289 prestera_counter_block_lock(block);
290 idr_remove(&block->counter_idr, counter_id);
291 block->full = false;
292 prestera_counter_stats_clear(block, counter_id);
293 prestera_counter_block_unlock(block);
294
295 prestera_hw_counter_clear(counter->sw, block->id, counter_id);
296 prestera_counter_block_put(counter, block);
297 }
298
prestera_counter_block_idx_next(struct prestera_counter * counter,u32 curr_idx)299 static u32 prestera_counter_block_idx_next(struct prestera_counter *counter,
300 u32 curr_idx)
301 {
302 u32 idx, i, start = curr_idx + 1;
303
304 prestera_counter_lock(counter);
305 for (i = 0; i < counter->block_list_len; i++) {
306 idx = (start + i) % counter->block_list_len;
307 if (!counter->block_list[idx])
308 continue;
309
310 prestera_counter_unlock(counter);
311 return idx;
312 }
313 prestera_counter_unlock(counter);
314
315 return 0;
316 }
317
318 static struct prestera_counter_block *
prestera_counter_block_get_by_idx(struct prestera_counter * counter,u32 idx)319 prestera_counter_block_get_by_idx(struct prestera_counter *counter, u32 idx)
320 {
321 if (idx >= counter->block_list_len)
322 return NULL;
323
324 prestera_counter_lock(counter);
325
326 if (!counter->block_list[idx] ||
327 !prestera_counter_block_incref(counter->block_list[idx])) {
328 prestera_counter_unlock(counter);
329 return NULL;
330 }
331
332 prestera_counter_unlock(counter);
333 return counter->block_list[idx];
334 }
335
prestera_counter_stats_work(struct work_struct * work)336 static void prestera_counter_stats_work(struct work_struct *work)
337 {
338 struct delayed_work *dl_work = to_delayed_work(work);
339 struct prestera_counter *counter =
340 container_of(dl_work, struct prestera_counter, stats_dw);
341 struct prestera_counter_block *block;
342 u32 resched_time = COUNTER_POLL_TIME;
343 u32 count = COUNTER_BULK_SIZE;
344 bool done = false;
345 int err;
346 u32 i;
347
348 block = prestera_counter_block_get_by_idx(counter, counter->curr_idx);
349 if (!block) {
350 if (counter->is_fetching)
351 goto abort;
352
353 goto next;
354 }
355
356 if (!counter->is_fetching) {
357 err = prestera_hw_counter_trigger(counter->sw, block->id);
358 if (err)
359 goto abort;
360
361 prestera_counter_block_lock(block);
362 block->is_updating = true;
363 prestera_counter_block_unlock(block);
364
365 counter->is_fetching = true;
366 counter->total_read = 0;
367 resched_time = COUNTER_RESCHED_TIME;
368 goto resched;
369 }
370
371 prestera_counter_block_lock(block);
372 err = prestera_hw_counters_get(counter->sw, counter->total_read,
373 &count, &done,
374 &block->stats[counter->total_read]);
375 prestera_counter_block_unlock(block);
376 if (err)
377 goto abort;
378
379 counter->total_read += count;
380 if (!done || counter->total_read < block->num_counters) {
381 resched_time = COUNTER_RESCHED_TIME;
382 goto resched;
383 }
384
385 for (i = 0; i < block->num_counters; i++) {
386 if (block->counter_flag[i] == COUNTER_FLAG_INVALID) {
387 prestera_counter_block_lock(block);
388 block->counter_flag[i] = COUNTER_FLAG_READY;
389 memset(&block->stats[i], 0, sizeof(*block->stats));
390 prestera_counter_block_unlock(block);
391 }
392 }
393
394 prestera_counter_block_lock(block);
395 block->is_updating = false;
396 prestera_counter_block_unlock(block);
397
398 goto next;
399 abort:
400 prestera_hw_counter_abort(counter->sw);
401 next:
402 counter->is_fetching = false;
403 counter->curr_idx =
404 prestera_counter_block_idx_next(counter, counter->curr_idx);
405 resched:
406 if (block)
407 prestera_counter_block_put(counter, block);
408
409 schedule_delayed_work(&counter->stats_dw, resched_time);
410 }
411
412 /* Can be executed without rtnl_lock().
413 * So pay attention when something changing.
414 */
prestera_counter_stats_get(struct prestera_counter * counter,struct prestera_counter_block * block,u32 counter_id,u64 * packets,u64 * bytes)415 int prestera_counter_stats_get(struct prestera_counter *counter,
416 struct prestera_counter_block *block,
417 u32 counter_id, u64 *packets, u64 *bytes)
418 {
419 if (!block || !prestera_counter_is_ready(block, counter_id)) {
420 *packets = 0;
421 *bytes = 0;
422 return 0;
423 }
424
425 prestera_counter_block_lock(block);
426 *packets = block->stats[counter_id - block->offset].packets;
427 *bytes = block->stats[counter_id - block->offset].bytes;
428
429 prestera_counter_stats_clear(block, counter_id);
430 prestera_counter_block_unlock(block);
431
432 return 0;
433 }
434
prestera_counter_init(struct prestera_switch * sw)435 int prestera_counter_init(struct prestera_switch *sw)
436 {
437 struct prestera_counter *counter;
438
439 counter = kzalloc_obj(*counter);
440 if (!counter)
441 return -ENOMEM;
442
443 counter->block_list = kzalloc_obj(*counter->block_list);
444 if (!counter->block_list) {
445 kfree(counter);
446 return -ENOMEM;
447 }
448
449 mutex_init(&counter->mtx);
450 counter->block_list_len = 1;
451 counter->sw = sw;
452 sw->counter = counter;
453
454 INIT_DELAYED_WORK(&counter->stats_dw, prestera_counter_stats_work);
455 schedule_delayed_work(&counter->stats_dw, COUNTER_POLL_TIME);
456
457 return 0;
458 }
459
prestera_counter_fini(struct prestera_switch * sw)460 void prestera_counter_fini(struct prestera_switch *sw)
461 {
462 struct prestera_counter *counter = sw->counter;
463 u32 i;
464
465 cancel_delayed_work_sync(&counter->stats_dw);
466
467 for (i = 0; i < counter->block_list_len; i++)
468 WARN_ON(counter->block_list[i]);
469
470 mutex_destroy(&counter->mtx);
471 kfree(counter->block_list);
472 kfree(counter);
473 }
474