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 <zephyr/sys/bitarray.h>
12 #include <zephyr/sys/check.h>
13 #include <zephyr/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 size_t idx;
71 uint32_t bundle;
72 uint32_t mismatch_bundle;
73 size_t mismatch_bundle_idx;
74 size_t mismatch_bit_off;
75
76 setup_bundle_data(bitarray, bd, offset, num_bits);
77
78 if (bd->sidx == bd->eidx) {
79 bundle = bitarray->bundles[bd->sidx];
80 if (!match_set) {
81 bundle = ~bundle;
82 }
83
84 if ((bundle & bd->smask) != bd->smask) {
85 /* Not matching to mask. */
86 mismatch_bundle = ~bundle & bd->smask;
87 mismatch_bundle_idx = bd->sidx;
88 goto mismatch;
89 } else {
90 /* Matching to mask. */
91 goto out;
92 }
93 }
94
95 /* Region lies in a number of bundles. Need to loop through them. */
96
97 /* Start of bundles */
98 bundle = bitarray->bundles[bd->sidx];
99 if (!match_set) {
100 bundle = ~bundle;
101 }
102
103 if ((bundle & bd->smask) != bd->smask) {
104 /* Start bundle not matching to mask. */
105 mismatch_bundle = ~bundle & bd->smask;
106 mismatch_bundle_idx = bd->sidx;
107 goto mismatch;
108 }
109
110 /* End of bundles */
111 bundle = bitarray->bundles[bd->eidx];
112 if (!match_set) {
113 bundle = ~bundle;
114 }
115
116 if ((bundle & bd->emask) != bd->emask) {
117 /* End bundle not matching to mask. */
118 mismatch_bundle = ~bundle & bd->emask;
119 mismatch_bundle_idx = bd->eidx;
120 goto mismatch;
121 }
122
123 /* In-between bundles */
124 for (idx = bd->sidx + 1; idx < bd->eidx; idx++) {
125 /* Note that this is opposite from above so that
126 * we are simply checking if bundle == 0.
127 */
128 bundle = bitarray->bundles[idx];
129 if (match_set) {
130 bundle = ~bundle;
131 }
132
133 if (bundle != 0U) {
134 /* Bits in "between bundles" do not match */
135 mismatch_bundle = bundle;
136 mismatch_bundle_idx = idx;
137 goto mismatch;
138 }
139 }
140
141 out:
142 /* All bits in region matched. */
143 return true;
144
145 mismatch:
146 if (mismatch != NULL) {
147 /* Must have at least 1 bit set to indicate
148 * where the mismatch is.
149 */
150 __ASSERT_NO_MSG(mismatch_bundle != 0);
151
152 mismatch_bit_off = find_lsb_set(mismatch_bundle) - 1;
153 mismatch_bit_off += mismatch_bundle_idx *
154 bundle_bitness(bitarray);
155 *mismatch = (uint32_t)mismatch_bit_off;
156 }
157 return false;
158 }
159
160 /*
161 * Set or clear a region of bits.
162 *
163 * @param bitarray Bitarray struct
164 * @param offset Starting bit location
165 * @param num_bits Number of bits in the region
166 * @param to_set True if to set all bits.
167 * False if to clear all bits.
168 * @param bd Bundle data. Can reuse the output from
169 * match_region(). NULL if there is no
170 * prior call to match_region().
171 */
set_region(sys_bitarray_t * bitarray,size_t offset,size_t num_bits,bool to_set,struct bundle_data * bd)172 static void set_region(sys_bitarray_t *bitarray, size_t offset,
173 size_t num_bits, bool to_set,
174 struct bundle_data *bd)
175 {
176 size_t idx;
177 struct bundle_data bdata;
178
179 if (bd == NULL) {
180 bd = &bdata;
181 setup_bundle_data(bitarray, bd, offset, num_bits);
182 }
183
184 if (bd->sidx == bd->eidx) {
185 /* Start/end at same bundle */
186 if (to_set) {
187 bitarray->bundles[bd->sidx] |= bd->smask;
188 } else {
189 bitarray->bundles[bd->sidx] &= ~bd->smask;
190 }
191 } else {
192 /* Start/end at different bundle.
193 * So set/clear the bits in start and end bundles
194 * separately. For in-between bundles,
195 * set/clear all bits.
196 */
197 if (to_set) {
198 bitarray->bundles[bd->sidx] |= bd->smask;
199 bitarray->bundles[bd->eidx] |= bd->emask;
200 for (idx = bd->sidx + 1; idx < bd->eidx; idx++) {
201 bitarray->bundles[idx] = ~0U;
202 }
203 } else {
204 bitarray->bundles[bd->sidx] &= ~bd->smask;
205 bitarray->bundles[bd->eidx] &= ~bd->emask;
206 for (idx = bd->sidx + 1; idx < bd->eidx; idx++) {
207 bitarray->bundles[idx] = 0U;
208 }
209 }
210 }
211 }
212
sys_bitarray_set_bit(sys_bitarray_t * bitarray,size_t bit)213 int sys_bitarray_set_bit(sys_bitarray_t *bitarray, size_t bit)
214 {
215 k_spinlock_key_t key;
216 int ret;
217 size_t idx, off;
218
219 key = k_spin_lock(&bitarray->lock);
220
221 __ASSERT_NO_MSG(bitarray != NULL);
222 __ASSERT_NO_MSG(bitarray->num_bits > 0);
223
224 if (bit >= bitarray->num_bits) {
225 ret = -EINVAL;
226 goto out;
227 }
228
229 idx = bit / bundle_bitness(bitarray);
230 off = bit % bundle_bitness(bitarray);
231
232 bitarray->bundles[idx] |= BIT(off);
233
234 ret = 0;
235
236 out:
237 k_spin_unlock(&bitarray->lock, key);
238 return ret;
239 }
240
sys_bitarray_clear_bit(sys_bitarray_t * bitarray,size_t bit)241 int sys_bitarray_clear_bit(sys_bitarray_t *bitarray, size_t bit)
242 {
243 k_spinlock_key_t key;
244 int ret;
245 size_t idx, off;
246
247 __ASSERT_NO_MSG(bitarray != NULL);
248 __ASSERT_NO_MSG(bitarray->num_bits > 0);
249
250 key = k_spin_lock(&bitarray->lock);
251
252 if (bit >= bitarray->num_bits) {
253 ret = -EINVAL;
254 goto out;
255 }
256
257 idx = bit / bundle_bitness(bitarray);
258 off = bit % bundle_bitness(bitarray);
259
260 bitarray->bundles[idx] &= ~BIT(off);
261
262 ret = 0;
263
264 out:
265 k_spin_unlock(&bitarray->lock, key);
266 return ret;
267 }
268
sys_bitarray_test_bit(sys_bitarray_t * bitarray,size_t bit,int * val)269 int sys_bitarray_test_bit(sys_bitarray_t *bitarray, size_t bit, int *val)
270 {
271 k_spinlock_key_t key;
272 int ret;
273 size_t idx, off;
274
275 __ASSERT_NO_MSG(bitarray != NULL);
276 __ASSERT_NO_MSG(bitarray->num_bits > 0);
277
278 key = k_spin_lock(&bitarray->lock);
279
280 CHECKIF(val == NULL) {
281 ret = -EINVAL;
282 goto out;
283 }
284
285 if (bit >= bitarray->num_bits) {
286 ret = -EINVAL;
287 goto out;
288 }
289
290 idx = bit / bundle_bitness(bitarray);
291 off = bit % bundle_bitness(bitarray);
292
293 if ((bitarray->bundles[idx] & BIT(off)) != 0) {
294 *val = 1;
295 } else {
296 *val = 0;
297 }
298
299 ret = 0;
300
301 out:
302 k_spin_unlock(&bitarray->lock, key);
303 return ret;
304 }
305
sys_bitarray_test_and_set_bit(sys_bitarray_t * bitarray,size_t bit,int * prev_val)306 int sys_bitarray_test_and_set_bit(sys_bitarray_t *bitarray, size_t bit, int *prev_val)
307 {
308 k_spinlock_key_t key;
309 int ret;
310 size_t idx, off;
311
312 __ASSERT_NO_MSG(bitarray != NULL);
313 __ASSERT_NO_MSG(bitarray->num_bits > 0);
314
315 key = k_spin_lock(&bitarray->lock);
316
317 CHECKIF(prev_val == NULL) {
318 ret = -EINVAL;
319 goto out;
320 }
321
322 if (bit >= bitarray->num_bits) {
323 ret = -EINVAL;
324 goto out;
325 }
326
327 idx = bit / bundle_bitness(bitarray);
328 off = bit % bundle_bitness(bitarray);
329
330 if ((bitarray->bundles[idx] & BIT(off)) != 0) {
331 *prev_val = 1;
332 } else {
333 *prev_val = 0;
334 }
335
336 bitarray->bundles[idx] |= BIT(off);
337
338 ret = 0;
339
340 out:
341 k_spin_unlock(&bitarray->lock, key);
342 return ret;
343 }
344
sys_bitarray_test_and_clear_bit(sys_bitarray_t * bitarray,size_t bit,int * prev_val)345 int sys_bitarray_test_and_clear_bit(sys_bitarray_t *bitarray, size_t bit, int *prev_val)
346 {
347 k_spinlock_key_t key;
348 int ret;
349 size_t idx, off;
350
351 __ASSERT_NO_MSG(bitarray != NULL);
352 __ASSERT_NO_MSG(bitarray->num_bits > 0);
353
354 key = k_spin_lock(&bitarray->lock);
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 __ASSERT_NO_MSG(bitarray != NULL);
395 __ASSERT_NO_MSG(bitarray->num_bits > 0);
396
397 key = k_spin_lock(&bitarray->lock);
398
399 CHECKIF(offset == NULL) {
400 ret = -EINVAL;
401 goto out;
402 }
403
404 if ((num_bits == 0) || (num_bits > bitarray->num_bits)) {
405 ret = -EINVAL;
406 goto out;
407 }
408
409 bit_idx = 0;
410
411 /* Find the first non-allocated bit by looking at bundles
412 * instead of individual bits.
413 *
414 * On RISC-V 64-bit, it complains about undefined reference to `ffs`.
415 * So don't use this on RISCV64.
416 */
417 for (size_t idx = 0; idx < bitarray->num_bundles; idx++) {
418 if (~bitarray->bundles[idx] == 0U) {
419 /* bundle is all 1s => all allocated, skip */
420 bit_idx += bundle_bitness(bitarray);
421 continue;
422 }
423
424 if (bitarray->bundles[idx] != 0U) {
425 /* Find the first free bit in bundle if not all free */
426 off_start = find_lsb_set(~bitarray->bundles[idx]) - 1;
427 bit_idx += off_start;
428 }
429
430 break;
431 }
432
433 off_end = bitarray->num_bits - num_bits;
434 ret = -ENOSPC;
435 while (bit_idx <= off_end) {
436 if (match_region(bitarray, bit_idx, num_bits, false,
437 &bd, &mismatch)) {
438 set_region(bitarray, bit_idx, num_bits, true, &bd);
439
440 *offset = bit_idx;
441 ret = 0;
442 break;
443 }
444
445 /* Fast-forward to the bit just after
446 * the mismatched bit.
447 */
448 bit_idx = mismatch + 1;
449 }
450
451 out:
452 k_spin_unlock(&bitarray->lock, key);
453 return ret;
454 }
455
sys_bitarray_free(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)456 int sys_bitarray_free(sys_bitarray_t *bitarray, size_t num_bits,
457 size_t offset)
458 {
459 k_spinlock_key_t key;
460 int ret;
461 size_t off_end = offset + num_bits - 1;
462 struct bundle_data bd;
463
464 __ASSERT_NO_MSG(bitarray != NULL);
465 __ASSERT_NO_MSG(bitarray->num_bits > 0);
466
467 key = k_spin_lock(&bitarray->lock);
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 != NULL);
502 __ASSERT_NO_MSG(bitarray->num_bits > 0);
503
504 if ((num_bits == 0)
505 || (num_bits > bitarray->num_bits)
506 || (offset >= bitarray->num_bits)
507 || (off_end >= bitarray->num_bits)) {
508 ret = false;
509 goto out;
510 }
511
512 ret = match_region(bitarray, offset, num_bits, to_set, &bd, NULL);
513
514 out:
515 k_spin_unlock(&bitarray->lock, key);
516 return ret;
517 }
518
sys_bitarray_is_region_set(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)519 bool sys_bitarray_is_region_set(sys_bitarray_t *bitarray, size_t num_bits,
520 size_t offset)
521 {
522 return is_region_set_clear(bitarray, num_bits, offset, true);
523 }
524
sys_bitarray_is_region_cleared(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)525 bool sys_bitarray_is_region_cleared(sys_bitarray_t *bitarray, size_t num_bits,
526 size_t offset)
527 {
528 return is_region_set_clear(bitarray, num_bits, offset, false);
529 }
530
set_clear_region(sys_bitarray_t * bitarray,size_t num_bits,size_t offset,bool to_set)531 static int set_clear_region(sys_bitarray_t *bitarray, size_t num_bits,
532 size_t offset, bool to_set)
533 {
534 int ret;
535 size_t off_end = offset + num_bits - 1;
536 k_spinlock_key_t key = k_spin_lock(&bitarray->lock);
537
538 __ASSERT_NO_MSG(bitarray != NULL);
539 __ASSERT_NO_MSG(bitarray->num_bits > 0);
540
541 if ((num_bits == 0)
542 || (num_bits > bitarray->num_bits)
543 || (offset >= bitarray->num_bits)
544 || (off_end >= bitarray->num_bits)) {
545 ret = -EINVAL;
546 goto out;
547 }
548
549 set_region(bitarray, offset, num_bits, to_set, NULL);
550 ret = 0;
551
552 out:
553 k_spin_unlock(&bitarray->lock, key);
554 return ret;
555 }
556
sys_bitarray_test_and_set_region(sys_bitarray_t * bitarray,size_t num_bits,size_t offset,bool to_set)557 int sys_bitarray_test_and_set_region(sys_bitarray_t *bitarray, size_t num_bits,
558 size_t offset, bool to_set)
559 {
560 int ret;
561 bool region_clear;
562 struct bundle_data bd;
563
564 __ASSERT_NO_MSG(bitarray != NULL);
565 __ASSERT_NO_MSG(bitarray->num_bits > 0);
566
567 size_t off_end = offset + num_bits - 1;
568 k_spinlock_key_t key = k_spin_lock(&bitarray->lock);
569
570
571 if ((num_bits == 0)
572 || (num_bits > bitarray->num_bits)
573 || (offset >= bitarray->num_bits)
574 || (off_end >= bitarray->num_bits)) {
575 ret = -EINVAL;
576 goto out;
577 }
578
579 region_clear = match_region(bitarray, offset, num_bits, !to_set, &bd, NULL);
580 if (region_clear) {
581 set_region(bitarray, offset, num_bits, to_set, &bd);
582 ret = 0;
583 } else {
584 ret = -EEXIST;
585 }
586
587 out:
588 k_spin_unlock(&bitarray->lock, key);
589 return ret;
590 }
591
sys_bitarray_set_region(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)592 int sys_bitarray_set_region(sys_bitarray_t *bitarray, size_t num_bits,
593 size_t offset)
594 {
595 return set_clear_region(bitarray, num_bits, offset, true);
596 }
597
sys_bitarray_clear_region(sys_bitarray_t * bitarray,size_t num_bits,size_t offset)598 int sys_bitarray_clear_region(sys_bitarray_t *bitarray, size_t num_bits,
599 size_t offset)
600 {
601 return set_clear_region(bitarray, num_bits, offset, false);
602 }
603