1 /*
2  * Copyright (c) 2021 Intel Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <errno.h>
8 #include <stddef.h>
9 #include <stdbool.h>
10 #include <stdio.h>
11 #include <sys/bitarray.h>
12 #include <sys/check.h>
13 #include <sys/sys_io.h>
14 
15 /* Number of bits represented by one bundle */
16 #define bundle_bitness(ba)	(sizeof(ba->bundles[0]) * 8)
17 
18 struct bundle_data {
19 	 /* Start and end index of bundles */
20 	size_t sidx, eidx;
21 
22 	 /* Offset inside start and end bundles */
23 	size_t soff, eoff;
24 
25 	 /* Masks for start/end bundles */
26 	uint32_t smask, emask;
27 };
28 
setup_bundle_data(sys_bitarray_t * bitarray,struct bundle_data * bd,size_t offset,size_t num_bits)29 static void setup_bundle_data(sys_bitarray_t *bitarray,
30 			      struct bundle_data *bd,
31 			      size_t offset, size_t num_bits)
32 {
33 	bd->sidx = offset / bundle_bitness(bitarray);
34 	bd->soff = offset % bundle_bitness(bitarray);
35 
36 	bd->eidx = (offset + num_bits - 1) / bundle_bitness(bitarray);
37 	bd->eoff = (offset + num_bits - 1) % bundle_bitness(bitarray);
38 
39 	bd->smask = ~(BIT(bd->soff) - 1);
40 	bd->emask = (BIT(bd->eoff) - 1) | BIT(bd->eoff);
41 
42 	if (bd->sidx == bd->eidx) {
43 		/* The region lies within the same bundle. So combine the masks. */
44 		bd->smask &= bd->emask;
45 	}
46 }
47 
48 /*
49  * Find out if the bits in a region is all set or all clear.
50  *
51  * @param[in]  bitarray  Bitarray struct
52  * @param[in]  offset    Starting bit location
53  * @param[in]  num_bits  Number of bits in the region
54  * @param[in]  match_set True if matching all set bits,
55  *                       False if matching all cleared bits
56  * @param[out] bd        Data related to matching which can be
57  *                       used later to find out where the region
58  *                       lies in the bitarray bundles.
59  * @param[out] mismatch  Offset to the mismatched bit.
60  *                       Can be NULL.
61  *
62  * @retval     true      If all bits are set or cleared
63  * @retval     false     Not all bits are set or cleared
64  */
match_region(sys_bitarray_t * bitarray,size_t offset,size_t num_bits,bool match_set,struct bundle_data * bd,size_t * mismatch)65 static bool match_region(sys_bitarray_t *bitarray, size_t offset,
66 			 size_t num_bits, bool match_set,
67 			 struct bundle_data *bd,
68 			 size_t *mismatch)
69 {
70 	int idx;
71 	uint32_t bundle;
72 	uint32_t mismatch_bundle;
73 	uint32_t mismatch_mask;
74 	size_t mismatch_bundle_idx;
75 	size_t mismatch_bit_off;
76 
77 	setup_bundle_data(bitarray, bd, offset, num_bits);
78 
79 	if (bd->sidx == bd->eidx) {
80 		bundle = bitarray->bundles[bd->sidx];
81 		if (!match_set) {
82 			bundle = ~bundle;
83 		}
84 
85 		if ((bundle & bd->smask) != bd->smask) {
86 			/* Not matching to mask. */
87 			mismatch_bundle = ~bundle & bd->smask;
88 			mismatch_bundle_idx = bd->sidx;
89 			mismatch_mask = bd->smask;
90 			goto mismatch;
91 		} else {
92 			/* Matching to mask. */
93 			goto out;
94 		}
95 	}
96 
97 	/* Region lies in a number of bundles. Need to loop through them. */
98 
99 	/* Start of bundles */
100 	bundle = bitarray->bundles[bd->sidx];
101 	if (!match_set) {
102 		bundle = ~bundle;
103 	}
104 
105 	if ((bundle & bd->smask) != bd->smask) {
106 		/* Start bundle not matching to mask. */
107 		mismatch_bundle = ~bundle & bd->smask;
108 		mismatch_bundle_idx = bd->sidx;
109 		mismatch_mask = bd->smask;
110 		goto mismatch;
111 	}
112 
113 	/* End of bundles */
114 	bundle = bitarray->bundles[bd->eidx];
115 	if (!match_set) {
116 		bundle = ~bundle;
117 	}
118 
119 	if ((bundle & bd->emask) != bd->emask) {
120 		/* End bundle not matching to mask. */
121 		mismatch_bundle = ~bundle & bd->emask;
122 		mismatch_bundle_idx = bd->eidx;
123 		mismatch_mask = bd->emask;
124 		goto mismatch;
125 	}
126 
127 	/* In-between bundles */
128 	for (idx = bd->sidx + 1; idx < bd->eidx; idx++) {
129 		/* Note that this is opposite from above so that
130 		 * we are simply checking if bundle == 0.
131 		 */
132 		bundle = bitarray->bundles[idx];
133 		if (match_set) {
134 			bundle = ~bundle;
135 		}
136 
137 		if (bundle != 0U) {
138 			/* Bits in "between bundles" do not match */
139 			mismatch_bundle = ~bundle;
140 			mismatch_bundle_idx = idx;
141 			mismatch_mask = ~0U;
142 			goto mismatch;
143 		}
144 	}
145 
146 out:
147 	/* All bits in region matched. */
148 	return true;
149 
150 mismatch:
151 	if (mismatch != NULL) {
152 		/* Must have at least 1 bit set to indicate
153 		 * where the mismatch is.
154 		 */
155 		__ASSERT_NO_MSG(mismatch_bundle != 0);
156 
157 		mismatch_bit_off = find_lsb_set(mismatch_bundle) - 1;
158 		mismatch_bit_off += mismatch_bundle_idx *
159 				    bundle_bitness(bitarray);
160 		*mismatch = (uint32_t)mismatch_bit_off;
161 	}
162 	return false;
163 }
164 
165 /*
166  * Set or clear a region of bits.
167  *
168  * @param bitarray Bitarray struct
169  * @param offset   Starting bit location
170  * @param num_bits Number of bits in the region
171  * @param to_set   True if to set all bits.
172  *                 False if to clear all bits.
173  * @param bd       Bundle data. Can reuse the output from
174  *                 match_region(). NULL if there is no
175  *                 prior call to match_region().
176  */
set_region(sys_bitarray_t * bitarray,size_t offset,size_t num_bits,bool to_set,struct bundle_data * bd)177 static void set_region(sys_bitarray_t *bitarray, size_t offset,
178 		       size_t num_bits, bool to_set,
179 		       struct bundle_data *bd)
180 {
181 	int idx;
182 	struct bundle_data bdata;
183 
184 	if (bd == NULL) {
185 		bd = &bdata;
186 		setup_bundle_data(bitarray, bd, offset, num_bits);
187 	}
188 
189 	if (bd->sidx == bd->eidx) {
190 		/* Start/end at same bundle */
191 		if (to_set) {
192 			bitarray->bundles[bd->sidx] |= bd->smask;
193 		} else {
194 			bitarray->bundles[bd->sidx] &= ~bd->smask;
195 		}
196 	} else {
197 		/* Start/end at different bundle.
198 		 * So set/clear the bits in start and end bundles
199 		 * separately. For in-between bundles,
200 		 * set/clear all bits.
201 		 */
202 		if (to_set) {
203 			bitarray->bundles[bd->sidx] |= bd->smask;
204 			bitarray->bundles[bd->eidx] |= bd->emask;
205 			for (idx = bd->sidx + 1; idx < bd->eidx; idx++) {
206 				bitarray->bundles[idx] = ~0U;
207 			}
208 		} else {
209 			bitarray->bundles[bd->sidx] &= ~bd->smask;
210 			bitarray->bundles[bd->eidx] &= ~bd->emask;
211 			for (idx = bd->sidx + 1; idx < bd->eidx; idx++) {
212 				bitarray->bundles[idx] = 0U;
213 			}
214 		}
215 	}
216 }
217 
sys_bitarray_set_bit(sys_bitarray_t * bitarray,size_t bit)218 int sys_bitarray_set_bit(sys_bitarray_t *bitarray, size_t bit)
219 {
220 	k_spinlock_key_t key;
221 	int ret;
222 	size_t idx, off;
223 
224 	key = k_spin_lock(&bitarray->lock);
225 
226 	__ASSERT_NO_MSG(bitarray->num_bits > 0);
227 
228 	if (bit >= bitarray->num_bits) {
229 		ret = -EINVAL;
230 		goto out;
231 	}
232 
233 	idx = bit / bundle_bitness(bitarray);
234 	off = bit % bundle_bitness(bitarray);
235 
236 	bitarray->bundles[idx] |= BIT(off);
237 
238 	ret = 0;
239 
240 out:
241 	k_spin_unlock(&bitarray->lock, key);
242 	return ret;
243 }
244 
sys_bitarray_clear_bit(sys_bitarray_t * bitarray,size_t bit)245 int sys_bitarray_clear_bit(sys_bitarray_t *bitarray, size_t bit)
246 {
247 	k_spinlock_key_t key;
248 	int ret;
249 	size_t idx, off;
250 
251 	key = k_spin_lock(&bitarray->lock);
252 
253 	__ASSERT_NO_MSG(bitarray->num_bits > 0);
254 
255 	if (bit >= bitarray->num_bits) {
256 		ret = -EINVAL;
257 		goto out;
258 	}
259 
260 	idx = bit / bundle_bitness(bitarray);
261 	off = bit % bundle_bitness(bitarray);
262 
263 	bitarray->bundles[idx] &= ~BIT(off);
264 
265 	ret = 0;
266 
267 out:
268 	k_spin_unlock(&bitarray->lock, key);
269 	return ret;
270 }
271 
sys_bitarray_test_bit(sys_bitarray_t * bitarray,size_t bit,int * val)272 int sys_bitarray_test_bit(sys_bitarray_t *bitarray, size_t bit, int *val)
273 {
274 	k_spinlock_key_t key;
275 	int ret;
276 	size_t idx, off;
277 
278 	key = k_spin_lock(&bitarray->lock);
279 
280 	__ASSERT_NO_MSG(bitarray->num_bits > 0);
281 
282 	CHECKIF(val == NULL) {
283 		ret = -EINVAL;
284 		goto out;
285 	}
286 
287 	if (bit >= bitarray->num_bits) {
288 		ret = -EINVAL;
289 		goto out;
290 	}
291 
292 	idx = bit / bundle_bitness(bitarray);
293 	off = bit % bundle_bitness(bitarray);
294 
295 	if ((bitarray->bundles[idx] & BIT(off)) != 0) {
296 		*val = 1;
297 	} else {
298 		*val = 0;
299 	}
300 
301 	ret = 0;
302 
303 out:
304 	k_spin_unlock(&bitarray->lock, key);
305 	return ret;
306 }
307 
sys_bitarray_test_and_set_bit(sys_bitarray_t * bitarray,size_t bit,int * prev_val)308 int sys_bitarray_test_and_set_bit(sys_bitarray_t *bitarray, size_t bit, int *prev_val)
309 {
310 	k_spinlock_key_t key;
311 	int ret;
312 	size_t idx, off;
313 
314 	key = k_spin_lock(&bitarray->lock);
315 
316 	__ASSERT_NO_MSG(bitarray->num_bits > 0);
317 
318 	CHECKIF(prev_val == NULL) {
319 		ret = -EINVAL;
320 		goto out;
321 	}
322 
323 	if (bit >= bitarray->num_bits) {
324 		ret = -EINVAL;
325 		goto out;
326 	}
327 
328 	idx = bit / bundle_bitness(bitarray);
329 	off = bit % bundle_bitness(bitarray);
330 
331 	if ((bitarray->bundles[idx] & BIT(off)) != 0) {
332 		*prev_val = 1;
333 	} else {
334 		*prev_val = 0;
335 	}
336 
337 	bitarray->bundles[idx] |= BIT(off);
338 
339 	ret = 0;
340 
341 out:
342 	k_spin_unlock(&bitarray->lock, key);
343 	return ret;
344 }
345 
sys_bitarray_test_and_clear_bit(sys_bitarray_t * bitarray,size_t bit,int * prev_val)346 int sys_bitarray_test_and_clear_bit(sys_bitarray_t *bitarray, size_t bit, int *prev_val)
347 {
348 	k_spinlock_key_t key;
349 	int ret;
350 	size_t idx, off;
351 
352 	key = k_spin_lock(&bitarray->lock);
353 
354 	__ASSERT_NO_MSG(bitarray->num_bits > 0);
355 
356 	CHECKIF(prev_val == NULL) {
357 		ret = -EINVAL;
358 		goto out;
359 	}
360 
361 	if (bit >= bitarray->num_bits) {
362 		ret = -EINVAL;
363 		goto out;
364 	}
365 
366 	idx = bit / bundle_bitness(bitarray);
367 	off = bit % bundle_bitness(bitarray);
368 
369 	if ((bitarray->bundles[idx] & BIT(off)) != 0) {
370 		*prev_val = 1;
371 	} else {
372 		*prev_val = 0;
373 	}
374 
375 	bitarray->bundles[idx] &= ~BIT(off);
376 
377 	ret = 0;
378 
379 out:
380 	k_spin_unlock(&bitarray->lock, key);
381 	return ret;
382 }
383 
sys_bitarray_alloc(sys_bitarray_t * bitarray,size_t num_bits,size_t * offset)384 int sys_bitarray_alloc(sys_bitarray_t *bitarray, size_t num_bits,
385 		       size_t *offset)
386 {
387 	k_spinlock_key_t key;
388 	uint32_t bit_idx;
389 	int ret;
390 	struct bundle_data bd;
391 	size_t off_start, off_end;
392 	size_t mismatch;
393 
394 	key = k_spin_lock(&bitarray->lock);
395 
396 	__ASSERT_NO_MSG(bitarray->num_bits > 0);
397 
398 	CHECKIF(offset == NULL) {
399 		ret = -EINVAL;
400 		goto out;
401 	}
402 
403 	if ((num_bits == 0) || (num_bits > bitarray->num_bits)) {
404 		ret = -EINVAL;
405 		goto out;
406 	}
407 
408 	bit_idx = 0;
409 
410 	/* Find the first non-allocated bit by looking at bundles
411 	 * instead of individual bits.
412 	 *
413 	 * On RISC-V 64-bit, it complains about undefined reference to `ffs`.
414 	 * So don't use this on RISCV64.
415 	 */
416 	for (ret = 0; ret < bitarray->num_bundles; ret++) {
417 		if (~bitarray->bundles[ret] == 0U) {
418 			/* bundle is all 1s => all allocated, skip */
419 			bit_idx += bundle_bitness(bitarray);
420 			continue;
421 		}
422 
423 		if (bitarray->bundles[ret] != 0U) {
424 			/* Find the first free bit in bundle if not all free */
425 			off_start = find_lsb_set(~bitarray->bundles[ret]) - 1;
426 			bit_idx += off_start;
427 		}
428 
429 		break;
430 	}
431 
432 	off_end = bitarray->num_bits - num_bits;
433 	ret = -ENOSPC;
434 	while (bit_idx <= off_end) {
435 		if (match_region(bitarray, bit_idx, num_bits, false,
436 				 &bd, &mismatch)) {
437 			off_end = bit_idx + num_bits - 1;
438 
439 			set_region(bitarray, bit_idx, num_bits, true, &bd);
440 
441 			*offset = bit_idx;
442 			ret = 0;
443 			break;
444 		}
445 
446 		/* Fast-forward to the bit just after
447 		 * the mismatched bit.
448 		 */
449 		bit_idx = mismatch + 1;
450 	}
451 
452 out:
453 	k_spin_unlock(&bitarray->lock, key);
454 	return ret;
455 }
456 
sys_bitarray_free(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)457 int sys_bitarray_free(sys_bitarray_t *bitarray, size_t num_bits,
458 		      size_t offset)
459 {
460 	k_spinlock_key_t key;
461 	int ret;
462 	size_t off_end = offset + num_bits - 1;
463 	struct bundle_data bd;
464 
465 	key = k_spin_lock(&bitarray->lock);
466 
467 	__ASSERT_NO_MSG(bitarray->num_bits > 0);
468 
469 	if ((num_bits == 0)
470 	    || (num_bits > bitarray->num_bits)
471 	    || (offset >= bitarray->num_bits)
472 	    || (off_end >= bitarray->num_bits)) {
473 		ret = -EINVAL;
474 		goto out;
475 	}
476 
477 	/* Note that we need to make sure the bits in specified region
478 	 * (offset to offset + num_bits) are all allocated before we clear
479 	 * them.
480 	 */
481 	if (match_region(bitarray, offset, num_bits, true, &bd, NULL)) {
482 		set_region(bitarray, offset, num_bits, false, &bd);
483 		ret = 0;
484 	} else {
485 		ret = -EFAULT;
486 	}
487 
488 out:
489 	k_spin_unlock(&bitarray->lock, key);
490 	return ret;
491 }
492 
is_region_set_clear(sys_bitarray_t * bitarray,size_t num_bits,size_t offset,bool to_set)493 static bool is_region_set_clear(sys_bitarray_t *bitarray, size_t num_bits,
494 				size_t offset, bool to_set)
495 {
496 	bool ret;
497 	struct bundle_data bd;
498 	size_t off_end = offset + num_bits - 1;
499 	k_spinlock_key_t key = k_spin_lock(&bitarray->lock);
500 
501 	__ASSERT_NO_MSG(bitarray->num_bits > 0);
502 
503 	if ((num_bits == 0)
504 	    || (num_bits > bitarray->num_bits)
505 	    || (offset >= bitarray->num_bits)
506 	    || (off_end >= bitarray->num_bits)) {
507 		ret = false;
508 		goto out;
509 	}
510 
511 	ret = match_region(bitarray, offset, num_bits, to_set, &bd, NULL);
512 
513 out:
514 	k_spin_unlock(&bitarray->lock, key);
515 	return ret;
516 }
517 
sys_bitarray_is_region_set(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)518 bool sys_bitarray_is_region_set(sys_bitarray_t *bitarray, size_t num_bits,
519 				size_t offset)
520 {
521 	return is_region_set_clear(bitarray, num_bits, offset, true);
522 }
523 
sys_bitarray_is_region_cleared(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)524 bool sys_bitarray_is_region_cleared(sys_bitarray_t *bitarray, size_t num_bits,
525 				    size_t offset)
526 {
527 	return is_region_set_clear(bitarray, num_bits, offset, false);
528 }
529 
set_clear_region(sys_bitarray_t * bitarray,size_t num_bits,size_t offset,bool to_set)530 static int set_clear_region(sys_bitarray_t *bitarray, size_t num_bits,
531 			    size_t offset, bool to_set)
532 {
533 	int ret;
534 	size_t off_end = offset + num_bits - 1;
535 	k_spinlock_key_t key = k_spin_lock(&bitarray->lock);
536 
537 	__ASSERT_NO_MSG(bitarray->num_bits > 0);
538 
539 	if ((num_bits == 0)
540 	    || (num_bits > bitarray->num_bits)
541 	    || (offset >= bitarray->num_bits)
542 	    || (off_end >= bitarray->num_bits)) {
543 		ret = -EINVAL;
544 		goto out;
545 	}
546 
547 	set_region(bitarray, offset, num_bits, to_set, NULL);
548 	ret = 0;
549 
550 out:
551 	k_spin_unlock(&bitarray->lock, key);
552 	return ret;
553 }
554 
sys_bitarray_set_region(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)555 int sys_bitarray_set_region(sys_bitarray_t *bitarray, size_t num_bits,
556 			    size_t offset)
557 {
558 	return set_clear_region(bitarray, num_bits, offset, true);
559 }
560 
sys_bitarray_clear_region(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)561 int sys_bitarray_clear_region(sys_bitarray_t *bitarray, size_t num_bits,
562 			      size_t offset)
563 {
564 	return set_clear_region(bitarray, num_bits, offset, false);
565 }
566