aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2024-01-18 10:43:41 -0500
committerTom Rini <trini@konsulko.com>2024-01-18 10:43:41 -0500
commita094d61350f52497fa4235f513f48cfb442623a5 (patch)
tree17050ea75d3c312914d5a55de17a161618e1142f
parent3c3a73424e6d6f7b86b9347da7c0e78379933319 (diff)
parent4a6a4fc7bafae0c0edfc568b6f9d4769207bc151 (diff)
downloadu-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.h60
-rw-r--r--cmd/printf.c53
-rw-r--r--cmd/setexpr.c48
-rw-r--r--doc/develop/printf.rst4
-rw-r--r--include/command.h27
-rw-r--r--include/linux/bitmap.h7
-rw-r--r--include/vsprintf.h7
-rw-r--r--lib/strto.c35
-rw-r--r--lib/vsprintf.c42
-rw-r--r--test/cmd/setexpr.c22
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);