blob: 8bae1c91499dde10573a8eb942b4ad9e1fd184f1 [file] [log] [blame]
/*
* Taken from Linux kernel version v5.15.
*/
#include <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <strings.h>
#include "linux/bitmap.h"
#include "linux/bitops.h"
#include "linux/err.h"
/*
* Region 9-38:4/10 describes the following bitmap structure:
* 0 9 12 18 38 N
* .........****......****......****..................
* ^ ^ ^ ^ ^
* start off group_len end nbits
*/
struct region {
unsigned int start;
unsigned int off;
unsigned int group_len;
unsigned int end;
unsigned int nbits;
};
void __bitmap_set(unsigned long *map, unsigned int start, int len)
{
unsigned long *p = map + BIT_WORD(start);
const unsigned int size = start + len;
int bits_to_set = BITS_PER_LONG - (start % BITS_PER_LONG);
unsigned long mask_to_set = BITMAP_FIRST_WORD_MASK(start);
while (len - bits_to_set >= 0) {
*p |= mask_to_set;
len -= bits_to_set;
bits_to_set = BITS_PER_LONG;
mask_to_set = ~0UL;
p++;
}
if (len) {
mask_to_set &= BITMAP_LAST_WORD_MASK(size);
*p |= mask_to_set;
}
}
static void bitmap_set_region(const struct region *r, unsigned long *bitmap)
{
unsigned int start;
for (start = r->start; start <= r->end; start += r->group_len)
bitmap_set(bitmap, start, min(r->end - start + 1, r->off));
}
static inline bool __end_of_region(char c)
{
return isspace(c) || c == ',';
}
static inline bool end_of_str(char c)
{
return c == '\0' || c == '\n';
}
static inline bool end_of_region(char c)
{
return __end_of_region(c) || end_of_str(c);
}
/*
* The format allows commas and whitespaces at the beginning
* of the region.
*/
static const char *bitmap_find_region(const char *str)
{
while (__end_of_region(*str))
str++;
return end_of_str(*str) ? NULL : str;
}
static int bitmap_check_region(const struct region *r)
{
if (r->start > r->end || r->group_len == 0 || r->off > r->group_len)
return -EINVAL;
if (r->end >= r->nbits)
return -ERANGE;
return 0;
}
static const char *bitmap_getnum(const char *str, unsigned int *num,
unsigned int lastbit)
{
unsigned long long n;
char *endptr;
if (str[0] == 'N') {
*num = lastbit;
return str + 1;
}
n = strtoll(str, &endptr, 10);
/* No digits found. */
if (n == 0 && endptr == str)
return ERR_PTR(-EINVAL);
/* Check for overflows and negative numbers. */
if (n == ULLONG_MAX || n != (unsigned long)n || n != (unsigned int)n)
return ERR_PTR(-EOVERFLOW);
*num = n;
return endptr;
}
static const char *bitmap_parse_region(const char *str, struct region *r)
{
unsigned int lastbit = r->nbits - 1;
if (!strncasecmp(str, "all", 3)) {
r->start = 0;
r->end = lastbit;
str += 3;
goto check_pattern;
}
str = bitmap_getnum(str, &r->start, lastbit);
if (IS_ERR(str))
return str;
if (end_of_region(*str))
goto no_end;
if (*str != '-')
return ERR_PTR(-EINVAL);
str = bitmap_getnum(str + 1, &r->end, lastbit);
if (IS_ERR(str))
return str;
check_pattern:
if (end_of_region(*str))
goto no_pattern;
if (*str != ':')
return ERR_PTR(-EINVAL);
str = bitmap_getnum(str + 1, &r->off, lastbit);
if (IS_ERR(str))
return str;
if (*str != '/')
return ERR_PTR(-EINVAL);
return bitmap_getnum(str + 1, &r->group_len, lastbit);
no_end:
r->end = r->start;
no_pattern:
r->off = r->end + 1;
r->group_len = r->end + 1;
return end_of_str(*str) ? NULL : str;
}
/**
* bitmap_parselist - convert list format ASCII string to bitmap
* @buf: read user string from this buffer; must be terminated
* with a \0 or \n.
* @maskp: write resulting mask here
* @nmaskbits: number of bits in mask to be written
*
* Input format is a comma-separated list of decimal numbers and
* ranges. Consecutively set bits are shown as two hyphen-separated
* decimal numbers, the smallest and largest bit numbers set in
* the range.
* Optionally each range can be postfixed to denote that only parts of it
* should be set. The range will divided to groups of specific size.
* From each group will be used only defined amount of bits.
* Syntax: range:used_size/group_size
* Example: 0-1023:2/256 ==> 0,1,256,257,512,513,768,769
* The value 'N' can be used as a dynamically substituted token for the
* maximum allowed value; i.e (nmaskbits - 1). Keep in mind that it is
* dynamic, so if system changes cause the bitmap width to change, such
* as more cores in a CPU list, then any ranges using N will also change.
*
* Returns: 0 on success, -errno on invalid input strings. Error values:
*
* - ``-EINVAL``: wrong region format
* - ``-EINVAL``: invalid character in string
* - ``-ERANGE``: bit number specified too large for mask
* - ``-EOVERFLOW``: integer overflow in the input parameters
*/
int bitmap_parselist(const char *buf, unsigned long *maskp, int nmaskbits)
{
struct region r;
long ret;
r.nbits = nmaskbits;
bitmap_zero(maskp, r.nbits);
while (buf) {
buf = bitmap_find_region(buf);
if (buf == NULL)
return 0;
buf = bitmap_parse_region(buf, &r);
if (IS_ERR(buf))
return PTR_ERR(buf);
ret = bitmap_check_region(&r);
if (ret)
return ret;
bitmap_set_region(&r, maskp);
}
return 0;
}
bool __bitmap_and(unsigned long *dst, const unsigned long *src1,
const unsigned long *src2, unsigned int nbits)
{
unsigned int lim = nbits / BITS_PER_LONG;
unsigned long result = 0;
unsigned int k;
for (k = 0; k < lim; k++)
result |= (dst[k] = src1[k] & src2[k]);
if (nbits % BITS_PER_LONG) {
result |= (dst[k] = src1[k] & src2[k] &
BITMAP_LAST_WORD_MASK(nbits));
}
return result != 0;
}
bool __bitmap_subset(const unsigned long *bitmap1, const unsigned long *bitmap2,
unsigned int nbits)
{
unsigned int k, lim = nbits / BITS_PER_LONG;
for (k = 0; k < lim; k++)
if (bitmap1[k] & ~bitmap2[k])
return false;
if (nbits % BITS_PER_LONG) {
if ((bitmap1[k] & ~bitmap2[k]) & BITMAP_LAST_WORD_MASK(nbits))
return false;
}
return true;
}