diff options
author | Tom Rini <trini@konsulko.com> | 2024-01-18 10:43:41 -0500 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2024-01-18 10:43:41 -0500 |
commit | a094d61350f52497fa4235f513f48cfb442623a5 (patch) | |
tree | 17050ea75d3c312914d5a55de17a161618e1142f | |
parent | 3c3a73424e6d6f7b86b9347da7c0e78379933319 (diff) | |
parent | 4a6a4fc7bafae0c0edfc568b6f9d4769207bc151 (diff) | |
download | u-boot-WIP/18Jul2024.zip u-boot-WIP/18Jul2024.tar.gz u-boot-WIP/18Jul2024.tar.bz2 |
Merge patch series "Enable setexpr command to print cpu-list like bitmaps"WIP/18Jul2024
lukas.funke-oss@weidmueller.com <lukas.funke-oss@weidmueller.com> says:
From: Lukas Funke <lukas.funke@weidmueller.com>
This series enables the 'setexpr' command to print "cpu list"-like
bitmaps based on the printk format specifier [1].
One use-case is to pass cpu list [2] based kernel parameter like
'isolcpu', 'nohz_full', irq affinity or RCU related CPU parameter to
the kernel via a separate firmware variable without exposing the
'bootargs' variable to directly.
Example:
=> env set value 0xdeadbeef
=> setexpr a fmt isolcpus=%32pbl $value
=> echo $a
isolcpus=0-3,5-7,9-13,15-16,18-19,21,23,25-28,30-31
[1] https://www.kernel.org/doc/Documentation/printk-formats.txt
[2] https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
-rw-r--r-- | arch/sandbox/include/asm/bitops.h | 60 | ||||
-rw-r--r-- | cmd/printf.c | 53 | ||||
-rw-r--r-- | cmd/setexpr.c | 48 | ||||
-rw-r--r-- | doc/develop/printf.rst | 4 | ||||
-rw-r--r-- | include/command.h | 27 | ||||
-rw-r--r-- | include/linux/bitmap.h | 7 | ||||
-rw-r--r-- | include/vsprintf.h | 7 | ||||
-rw-r--r-- | lib/strto.c | 35 | ||||
-rw-r--r-- | lib/vsprintf.c | 42 | ||||
-rw-r--r-- | test/cmd/setexpr.c | 22 |
10 files changed, 263 insertions, 42 deletions
diff --git a/arch/sandbox/include/asm/bitops.h b/arch/sandbox/include/asm/bitops.h index f27d5e9..6950916 100644 --- a/arch/sandbox/include/asm/bitops.h +++ b/arch/sandbox/include/asm/bitops.h @@ -104,9 +104,6 @@ static inline int __test_and_change_bit(int nr, void *addr) return (old & mask) != 0; } -extern int find_first_zero_bit(void *addr, unsigned size); -extern int find_next_zero_bit(void *addr, int size, int offset); - /* * This routine doesn't need to be atomic. */ @@ -119,27 +116,46 @@ static inline int test_bit(int nr, const void *addr) * ffz = Find First Zero in word. Undefined if no zero exists, * so code should check against ~0UL first.. */ -static inline unsigned long ffz(unsigned long word) -{ - int k; - - word = ~word; - k = 31; - if (word & 0x0000ffff) { - k -= 16; word <<= 16; - } - if (word & 0x00ff0000) { - k -= 8; word <<= 8; - } - if (word & 0x0f000000) { - k -= 4; word <<= 4; +#define ffz(x) __ffs(~(x)) + +#define find_first_zero_bit(addr, size) \ + find_next_zero_bit((addr), (size), 0) + +static inline int find_next_zero_bit(const unsigned long *addr, int size, + int offset) { + unsigned long *p = ((unsigned long *)addr) + (offset / BITS_PER_LONG); + unsigned long result = offset & ~(BITS_PER_LONG - 1); + unsigned long tmp; + + if (offset >= size) + return size; + size -= result; + offset &= (BITS_PER_LONG - 1); + if (offset) { + tmp = *(p++); + tmp |= ~0UL >> (BITS_PER_LONG - offset); + if (size < BITS_PER_LONG) + goto found_first; + if (~tmp) + goto found_middle; + size -= BITS_PER_LONG; + result += BITS_PER_LONG; } - if (word & 0x30000000) { - k -= 2; word <<= 2; + while (size & ~(BITS_PER_LONG - 1)) { + tmp = *(p++); + if (~tmp) + goto found_middle; + result += BITS_PER_LONG; + size -= BITS_PER_LONG; } - if (word & 0x40000000) - k -= 1; - return k; + if (!size) + return result; + tmp = *p; + +found_first: + tmp |= ~0UL << size; +found_middle: + return result + ffz(tmp); } /* diff --git a/cmd/printf.c b/cmd/printf.c index 0c6887e..2e54faf 100644 --- a/cmd/printf.c +++ b/cmd/printf.c @@ -85,11 +85,13 @@ */ #include <common.h> +#include <command.h> #include <ctype.h> #include <errno.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> +#include <linux/bitmap.h> #define WANT_HEX_ESCAPES 0 #define PRINT_CONVERSION_ERROR 1 @@ -476,6 +478,38 @@ static int get_width_prec(const char *str) return (int)v; } +static int print_pointer(struct print_inf *inf, char *format, + unsigned int fmt_length, int field_width, + int precision, const char *argument) +{ + struct expr_arg aval; + + if (setexpr_get_arg(skip_whitespace(argument), field_width >> 3, &aval)) + return CMD_RET_FAILURE; + + if (field_width > BITS_PER_LONG) { + printf_str(inf, format, aval.bmap); + free(aval.bmap); + } else { + printf_str(inf, format, &aval.ival); + } + + switch (inf->error) { + case 0: + return 0; + case PRINT_SIZE_ERROR: + printf("printf: size error\n"); break; + case PRINT_CONVERSION_ERROR: + printf("printf: conversion error\n"); break; + case PRINT_TRUNCATED_ERROR: + printf("printf: output truncated\n"); break; + default: + printf("printf: unknown error\n"); + } + + return -1; +} + /* Print the text in FORMAT, using ARGV for arguments to any '%' directives. * Return advanced ARGV. */ @@ -517,7 +551,7 @@ static char **print_formatted(struct print_inf *inf, char *f, char **argv, int * field_width = get_width_prec(*argv++); } else { while (isdigit(*f)) { - ++f; + field_width = field_width * 10 + *(f++) - '0'; ++direc_length; } } @@ -536,6 +570,23 @@ static char **print_formatted(struct print_inf *inf, char *f, char **argv, int * } } } + if (*f == 'p') { + static const char ptr_format_chars[] = "bl"; + ++f; + ++direc_length; + char *p = strchr(ptr_format_chars, *f); + /* consume whole format token */ + while (*f != '\0' && *(p++) == *f) { + ++f; + ++direc_length; + } + if (print_pointer(inf, direc_start, direc_length, + field_width, precision, *argv++)) { + return saved_argv - 1; + } + f--; + break; + } /* Remove "lLhz" size modifiers, repeatedly. * bash does not like "%lld", but coreutils diff --git a/cmd/setexpr.c b/cmd/setexpr.c index 233471f..fed457b 100644 --- a/cmd/setexpr.c +++ b/cmd/setexpr.c @@ -17,26 +17,16 @@ #include <malloc.h> #include <mapmem.h> #include <linux/sizes.h> +#include <linux/err.h> #include "printf.h" #define MAX_STR_LEN 128 -/** - * struct expr_arg: Holds an argument to an expression - * - * @ival: Integer value (if width is not CMD_DATA_SIZE_STR) - * @sval: String value (if width is CMD_DATA_SIZE_STR) - */ -struct expr_arg { - union { - ulong ival; - char *sval; - }; -}; - -static int get_arg(char *s, int w, struct expr_arg *argp) +int setexpr_get_arg(char *s, int w, struct expr_arg *argp) { struct expr_arg arg; + uchar *bmap; + ulong val; /* * If the parameter starts with a '*' then assume it is a pointer to @@ -45,7 +35,6 @@ static int get_arg(char *s, int w, struct expr_arg *argp) if (s[0] == '*') { ulong *p; ulong addr; - ulong val; int len; char *str; @@ -84,17 +73,38 @@ static int get_arg(char *s, int w, struct expr_arg *argp) unmap_sysmem(p); arg.ival = val; break; - default: +#if BITS_PER_LONG == 64 + case 8: p = map_sysmem(addr, sizeof(ulong)); val = *p; unmap_sysmem(p); arg.ival = val; break; +#endif + default: + p = map_sysmem(addr, w); + bmap = malloc(w); + if (!bmap) { + printf("Out of memory\n"); + return -ENOMEM; + } + memcpy(bmap, p, w); + arg.bmap = bmap; + unmap_sysmem(p); } } else { if (w == CMD_DATA_SIZE_STR) return -EINVAL; - arg.ival = hextoul(s, NULL); + if (w > sizeof(ulong)) { + bmap = hextobarray(s); + if (IS_ERR(bmap)) { + printf("Out of memory\n"); + return -ENOMEM; + } + arg.bmap = bmap; + } else { + arg.ival = hextoul(s, NULL); + } } *argp = arg; @@ -388,7 +398,7 @@ static int do_setexpr(struct cmd_tbl *cmdtp, int flag, int argc, w = cmd_get_data_size(argv[0], 4); - if (get_arg(argv[2], w, &aval)) + if (setexpr_get_arg(argv[2], w, &aval)) return CMD_RET_FAILURE; /* format string assignment: "setexpr name fmt %d value" */ @@ -441,7 +451,7 @@ static int do_setexpr(struct cmd_tbl *cmdtp, int flag, int argc, if (strlen(argv[3]) != 1) return CMD_RET_USAGE; - if (get_arg(argv[4], w, &bval)) { + if (setexpr_get_arg(argv[4], w, &bval)) { if (w == CMD_DATA_SIZE_STR) free(aval.sval); return CMD_RET_FAILURE; diff --git a/doc/develop/printf.rst b/doc/develop/printf.rst index 99d0506..8220c7c 100644 --- a/doc/develop/printf.rst +++ b/doc/develop/printf.rst @@ -165,6 +165,10 @@ Pointers * phys_size_t * resource_size_t +%pbl + '%pbl' outputs a bitmap as range list with field width as + the number of bits. e.g. '0,8-11,13-16,18-19,22-25,27,29,31' + %pD prints a UEFI device path diff --git a/include/command.h b/include/command.h index 4158ca1..d84aaac 100644 --- a/include/command.h +++ b/include/command.h @@ -248,6 +248,33 @@ int do_env_set_efi(struct cmd_tbl *cmdtp, int flag, int argc, int setexpr_regex_sub(char *data, uint data_size, char *nbuf, uint nbuf_size, const char *r, const char *s, bool global); +/** + * struct expr_arg: Holds an argument to an expression + * + * @ival: Integer value (if width is not CMD_DATA_SIZE_STR) + * @sval: String value (if width is CMD_DATA_SIZE_STR) + * @bmap: Bitmap value (if width is > u64) + */ +struct expr_arg { + union { + ulong ival; + char *sval; + uchar *bmap; + }; +}; + +/** + * setexpr_get_arg() - Converts a string argument to it's value. If argument + * starts with a '*' treat it as a pointer and dereference. + * + * @s: Argument string + * @w: Byte width of argument + * @argp: Pointer where the value should be stored + * + * Return: 0 on success, -EINVAL on failure + */ +int setexpr_get_arg(char *s, int w, struct expr_arg *argp); + /* * Error codes that commands return to cmd_process(). We use the standard 0 * and 1 for success and failure, but add one more case - failure with a diff --git a/include/linux/bitmap.h b/include/linux/bitmap.h index 0a8503a..9714533 100644 --- a/include/linux/bitmap.h +++ b/include/linux/bitmap.h @@ -159,6 +159,13 @@ static inline unsigned long find_first_bit(const unsigned long *addr, unsigned l (bit) < (size); \ (bit) = find_next_bit((addr), (size), (bit) + 1)) +#define for_each_set_bitrange(b, e, addr, size) \ + for ((b) = 0; \ + (b) = find_next_bit((addr), (size), b), \ + (e) = find_next_zero_bit((addr), (size), (b) + 1), \ + (b) < (size); \ + (b) = (e) + 1) + static inline unsigned long bitmap_find_next_zero_area(unsigned long *map, unsigned long size, diff --git a/include/vsprintf.h b/include/vsprintf.h index ed8a060..82c8bf0 100644 --- a/include/vsprintf.h +++ b/include/vsprintf.h @@ -368,4 +368,11 @@ int vsscanf(const char *inp, char const *fmt0, va_list ap); */ int sscanf(const char *buf, const char *fmt, ...); +/** + * hextobarray - Convert a hex-string to a byte array + * @cp: hex string to convert + * Return: a pointer to a byte array, -ENOMEM on error + */ +uchar *hextobarray(const char *cp); + #endif diff --git a/lib/strto.c b/lib/strto.c index 5157332..eb507e4 100644 --- a/lib/strto.c +++ b/lib/strto.c @@ -13,6 +13,7 @@ #include <malloc.h> #include <vsprintf.h> #include <linux/ctype.h> +#include <linux/err.h> /* from lib/kstrtox.c */ static const char *_parse_integer_fixup_radix(const char *s, uint *basep) @@ -73,6 +74,40 @@ ulong simple_strtoul(const char *cp, char **endp, uint base) return result; } +uchar *hextobarray(const char *cp) +{ + int i, len; + __maybe_unused unsigned int base; + __maybe_unused const char *endptr; + unsigned char *array; + + len = strlen(cp); + array = (unsigned char *)malloc(len); + if (!array) + return ERR_PTR(-ENOMEM); + + memset(array, 0, len); + +#if __BYTE_ORDER == __LITTLE_ENDIAN + endptr = (cp + len - 1); + for (i = 0; i < len && endptr > cp; i++) { + array[i] |= decode_digit(*(endptr)); + endptr--; + array[i] |= decode_digit(*endptr) << 4; + endptr--; + } +#else + cp = _parse_integer_fixup_radix(cp, &base); + for (i = 0; i < len && *cp; i++) { + array[i] |= decode_digit(*cp) << 4; + cp++; + array[i] |= decode_digit(*cp); + cp++; + } +#endif + return array; +} + ulong hextoul(const char *cp, char **endp) { return simple_strtoul(cp, endp, 16); diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 27ea9c9..e1779d7 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -24,6 +24,7 @@ #include <linux/err.h> #include <linux/types.h> #include <linux/string.h> +#include <linux/bitmap.h> /* we use this so that we can do without the ctype library */ #define is_digit(c) ((c) >= '0' && (c) <= '9') @@ -389,6 +390,33 @@ static char *ip4_addr_string(char *buf, char *end, u8 *addr, int field_width, flags & ~SPECIAL); } +static char *bitmap_list_string(char *buf, char *end, unsigned long *addr, + int field_width, int precision, int flags) +{ + int nr_bits = max_t(int, field_width, 0); + int first = 1; + int rbot, rtop; + + for_each_set_bitrange(rbot, rtop, addr, nr_bits) { + if (!first) { + if (buf < end) + *buf = ','; + buf++; + } + first = 0; + + buf = number(buf, end, rbot, 10, 0, -1, 0); + if (rtop == rbot + 1) + continue; + + if (buf < end) + *buf = '-'; + buf = number(++buf, end, rtop - 1, 10, 0, -1, 0); + } + + return buf; +} + #ifdef CONFIG_LIB_UUID /* * This works (roughly) the same way as Linux's. @@ -502,6 +530,20 @@ static char *pointer(const char *fmt, char *buf, char *end, void *ptr, precision, flags); flags &= ~SPECIAL; break; + case 'b': + switch (fmt[1]) { + case 'l': + /* if the field width is not a multiple of the underlying + * datatype (ulong), we get incorrect results from the bit twiddle + * macros. Thus, round up to a multiple of field width of ulong + */ + field_width = field_width % BITS_PER_LONG ? + ALIGN(field_width, BITS_PER_LONG) : field_width; + return bitmap_list_string(buf, end, ptr, field_width, + precision, flags); + default: + return ERR_PTR(-EINVAL); + } #ifdef CONFIG_LIB_UUID case 'U': return uuid_string(buf, end, ptr, field_width, precision, diff --git a/test/cmd/setexpr.c b/test/cmd/setexpr.c index 312593e..c536c9e 100644 --- a/test/cmd/setexpr.c +++ b/test/cmd/setexpr.c @@ -465,6 +465,28 @@ static int setexpr_test_fmt(struct unit_test_state *uts) ut_asserteq(1, run_command("setexpr fred fmt hello% bf", 0)); /* Error exceeding maximum string length */ ut_asserteq(1, run_command("setexpr fred fmt \"%0128d\" 456", 0)); + /* Test bitmask long string */ + ut_assertok(run_command("setexpr fred fmt isolcpu=%64pbl 0x1F1", 0)); + ut_asserteq_str("isolcpu=0,4-8", env_get("fred")); + /* Test bitmask long string (more complicated) */ + ut_assertok(run_command("setexpr fred fmt nohz_full=%32pbl 0x55555555", 0)); + ut_asserteq_str("nohz_full=0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30", env_get("fred")); + ut_assertok(run_command("setexpr fred fmt %64pbl 0xdeadbeef", 0)); + ut_asserteq_str("0-3,5-7,9-13,15-16,18-19,21,23,25-28,30-31", env_get("fred")); + /* Test bitmask on 64...256 */ + ut_assertok(run_command("setexpr fred fmt %64pbl 0xf0f0f0f0f0f0f0f0", 0)); + ut_asserteq_str("4-7,12-15,20-23,28-31,36-39,44-47,52-55,60-63", env_get("fred")); + ut_assertok(run_command("setexpr fred fmt %128pbl 0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0", 0)); + ut_asserteq_str("4-7,12-15,20-23,28-31,36-39,44-47,52-55,60-63,68-71,76-79,84-87,92-95,100-103,108-111,116-119,124-127", env_get("fred")); + /* clear lower bitmask, otherwise output gets truncated */ + ut_assertok(run_command("setexpr fred fmt %256pbl 0xf0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f000000000000000000000000000000000", 0)); + ut_asserteq_str("132-135,140-143,148-151,156-159,164-167,172-175,180-183,188-191,196-199,204-207,212-215,220-223,228-231,236-239,244-247,252-255", env_get("fred")); + /* Test memory access */ + memset(buf, 0, BUF_SIZE); + ut_assertok(run_command("env set myaddr 0x0;" + "mw.l $myaddr 0xdeadbeef 1;" + "setexpr fred fmt %64pbl *$myaddr", 0)); + ut_asserteq_str("0-3,5-7,9-13,15-16,18-19,21,23,25-28,30-31", env_get("fred")); unmap_sysmem(buf); |