xref: /linux/drivers/net/ethernet/marvell/prestera/prestera_counter.c (revision 32a92f8c89326985e05dce8b22d3f0aa07a3e1bd)
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