A size with a binary unit can only be printed to the console with
print_size(). Code which builds a string, such as a boot menu entry,
has to format the size itself.
Add a Z suffix to the %u conversion which prints the value as a size in
bytes with a binary unit, as print_size() does, so that sizes can be
printed with printf() and its variants:
printf("%lluZ\n", 0x113000ULL); /* 1 MiB */
printf("%.3lluZ\n", 0x113000ULL); /* 1.074 MiB */
The precision is the number of decimal places, with the last one rounded
and trailing zeros left out, so that "%.1lluZ" prints the same as
print_size(). The Z only has this meaning after %u, since an existing
format string has "%dZ" followed by other text. Compilers which check
format strings see the Z as an ordinary character, so this does not
cause warnings.
Document the format and add tests, including one which checks that
"%.1lluZ" prints the same as print_size() for a range of sizes.
Suggested-by: Heinrich Schuchardt <[email protected]>
Signed-off-by: Padmarao Begari <[email protected]>
---
Changes in v3:
- New patch, replacing "lib: display_options: add snprint_size()":
print sizes with a Z suffix to the %u conversion, as suggested by
Heinrich Schuchardt
checkpatch reports 'Please use a blank line after
function/struct/union/enum declarations' for each COMMON_TEST() line.
COMMON_TEST() is placed directly after its test function, as in the
other tests.
doc/develop/printf.rst | 30 +++++++++++++
lib/vsprintf.c | 98 ++++++++++++++++++++++++++++++++++++++++++
test/common/print.c | 97 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 225 insertions(+)
diff --git a/doc/develop/printf.rst b/doc/develop/printf.rst
index 99d05061b14..33f2e027fa0 100644
--- a/doc/develop/printf.rst
+++ b/doc/develop/printf.rst
@@ -127,6 +127,36 @@ size_t %zu, %zx, %zX
ssize_t %zd, %zx, %zX
=================== ==================
+Sizes
+-----
+
+%uZ, %luZ, %lluZ, %zuZ
+ prints an unsigned integer as a size in bytes with a binary unit:
+ "xxx Bytes" below 1 KiB, otherwise "xxx KiB", "xxx MiB" and so on up
+ to "xxx EiB", using the largest unit which gives at least 1.
+
+ The precision is the number of decimal places, at most 9. The last
+ one is rounded and trailing zeros are left out. Without a precision,
+ the size is rounded to a whole number of the unit. A field width pads
+ the whole string with spaces.
+
+ "%.1lluZ" prints a size the same way as print_size().
+
+ The Z only has this meaning after %u. After other conversions, such
+ as %d or %x, it is printed as a normal character. The SPL and TPL
+ tiny printf() does not support it.
+
+=================== ======================= ==============
+Format Value Output
+=================== ======================= ==============
+%lluZ 0x113000 1 MiB
+%.1lluZ 0x113000 1.1 MiB
+%.3lluZ 0x113000 1.074 MiB
+%.3lluZ 0x100000 1 MiB
+%zuZ 512 512 Bytes
+%10lluZ 1024 " 1 KiB"
+=================== ======================= ==============
+
Characters
----------
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 49dc9c38c65..a14f8c600b7 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -151,6 +151,7 @@ static noinline char *put_dec(char *buf, uint64_t num)
#define SMALL 32 /* Must be 32 == 0x20 */
#define SPECIAL 64 /* 0x */
#define ERRSTR 128 /* %dE showing error string if enabled */
+#define SIZEUNIT 256 /* %uZ showing a size with a unit */
/*
* Macro to add a new character to our output string, but only if it will
@@ -281,6 +282,94 @@ static char *string(char *buf, char *end, const char *s,
int field_width,
return buf;
}
+/* Most decimal places which %uZ shows */
+#define SIZE_MAX_DECIMALS 9
+
+/**
+ * size_string() - Print a size with a binary unit, for %uZ
+ *
+ * Print a size in bytes as "xxx Bytes" below 1 KiB, and otherwise as
+ * "xxx KiB", "xxx MiB", ... "xxx EiB", with the largest unit that gives at
+ * least 1. The precision is the number of decimal places, at most
+ * SIZE_MAX_DECIMALS, with the last one rounded and trailing zeros left out,
+ * so that "%.1lluZ" prints the same as print_size(). There are no decimal
+ * places without a precision.
+ *
+ * @buf: Buffer to write to
+ * @end: End of the buffer
+ * @size: Size in bytes
+ * @field_width: Minimum width of the output, padded with spaces
+ * @precision: Number of decimal places, -1 for none
+ * @flags: Formatting flags; only LEFT is used
+ * Return: Pointer to the end of the output in @buf
+ */
+static char *size_string(char *buf, char *end, u64 size, int field_width,
+ int precision, int flags)
+{
+ static const char units[] = "KMGTPE";
+ char tmp[32], *p, *tmp_end = tmp + sizeof(tmp);
+ int unit, shift, decimals, i;
+ u64 whole, rem, mask;
+ u32 frac, scale;
+
+ /* Find the largest unit which gives at least 1 */
+ for (unit = -1; unit + 1 < (int)ARRAY_SIZE(units) - 1; unit++) {
+ if (!(size >> (10 * (unit + 2))))
+ break;
+ }
+
+ if (unit < 0) {
+ p = number(tmp, tmp_end, size, 10, -1, -1, 0);
+ strcpy(p, " Bytes");
+
+ return string(buf, end, tmp, field_width, -1, flags);
+ }
+
+ shift = 10 * (unit + 1);
+ mask = (1ULL << shift) - 1;
+ whole = size >> shift;
+ rem = size & mask;
+ decimals = precision < 0 ? 0 : min(precision, SIZE_MAX_DECIMALS);
+
+ /* Work out each decimal place in turn, then round the last one */
+ frac = 0;
+ scale = 1;
+ for (i = 0; i < decimals; i++) {
+ rem *= 10;
+ frac = frac * 10 + (rem >> shift);
+ rem &= mask;
+ scale *= 10;
+ }
+ if (rem >> (shift - 1))
+ frac++;
+
+ /* Rounding can carry into the whole part, and on into the next unit */
+ if (frac >= scale) {
+ frac -= scale;
+ whole++;
+ if (whole == 1024 && units[unit + 1]) {
+ whole = 1;
+ unit++;
+ }
+ }
+
+ while (decimals > 0 && !(frac % 10)) {
+ frac /= 10;
+ decimals--;
+ }
+
+ p = number(tmp, tmp_end, whole, 10, -1, -1, 0);
+ if (decimals) {
+ *p++ = '.';
+ p = number(p, tmp_end, frac, 10, -1, decimals, 0);
+ }
+ *p++ = ' ';
+ *p++ = units[unit];
+ strcpy(p, "iB");
+
+ return string(buf, end, tmp, field_width, -1, flags);
+}
+
/* U-Boot uses UTF-16 strings in the EFI context only. */
static __maybe_unused char *string16(char *buf, char *end, u16 *s,
int field_width, int precision, int flags)
@@ -726,6 +815,10 @@ repeat:
flags |= SIGN;
/* fallthrough */
case 'u':
+ if (*fmt == 'u' && fmt[1] == 'Z') {
+ flags |= SIZEUNIT;
+ fmt++;
+ }
break;
default:
@@ -755,6 +848,11 @@ repeat:
if (flags & SIGN)
num = (signed int) num;
}
+ if (flags & SIZEUNIT) {
+ str = size_string(str, end, num, field_width, precision,
+ flags);
+ continue;
+ }
str = number(str, end, num, base, field_width, precision,
flags);
if (IS_ENABLED(CONFIG_ERRNO_STR) && (flags & ERRSTR)) {
diff --git a/test/common/print.c b/test/common/print.c
index 27fdb444d0d..1d2ee8f3839 100644
--- a/test/common/print.c
+++ b/test/common/print.c
@@ -164,6 +164,103 @@ static int print_printf(struct unit_test_state *uts)
}
COMMON_TEST(print_printf, 0);
+/* Test printing sizes with %uZ */
+static int print_size_unit(struct unit_test_state *uts)
+{
+ char str[40];
+
+ /* The integer types */
+ snprintf(str, sizeof(str), "%uZ", 0x100000);
+ ut_asserteq_str("1 MiB", str);
+ snprintf(str, sizeof(str), "%luZ", 0x100000UL);
+ ut_asserteq_str("1 MiB", str);
+ snprintf(str, sizeof(str), "%lluZ", 0x100000ULL);
+ ut_asserteq_str("1 MiB", str);
+ snprintf(str, sizeof(str), "%zuZ", (size_t)0x100000);
+ ut_asserteq_str("1 MiB", str);
+
+ /* Below 1 KiB, and each unit */
+ snprintf(str, sizeof(str), "%lluZ", 0ULL);
+ ut_asserteq_str("0 Bytes", str);
+ snprintf(str, sizeof(str), "%lluZ", 1023ULL);
+ ut_asserteq_str("1023 Bytes", str);
+ snprintf(str, sizeof(str), "%lluZ", 1ULL << 10);
+ ut_asserteq_str("1 KiB", str);
+ snprintf(str, sizeof(str), "%lluZ", 1ULL << 30);
+ ut_asserteq_str("1 GiB", str);
+ snprintf(str, sizeof(str), "%lluZ", 1ULL << 40);
+ ut_asserteq_str("1 TiB", str);
+ snprintf(str, sizeof(str), "%lluZ", 1ULL << 50);
+ ut_asserteq_str("1 PiB", str);
+ snprintf(str, sizeof(str), "%lluZ", 1ULL << 60);
+ ut_asserteq_str("1 EiB", str);
+ snprintf(str, sizeof(str), "%.1lluZ", ~0ULL);
+ ut_asserteq_str("16 EiB", str);
+
+ /* The precision is the number of decimal places */
+ snprintf(str, sizeof(str), "%lluZ", 0x113000ULL);
+ ut_asserteq_str("1 MiB", str);
+ snprintf(str, sizeof(str), "%.1lluZ", 0x113000ULL);
+ ut_asserteq_str("1.1 MiB", str);
+ snprintf(str, sizeof(str), "%.3lluZ", 0x113000ULL);
+ ut_asserteq_str("1.074 MiB", str);
+ snprintf(str, sizeof(str), "%.*lluZ", 3, 0x113000ULL);
+ ut_asserteq_str("1.074 MiB", str);
+
+ /* Leading zeros of the decimals are kept, trailing ones left out */
+ snprintf(str, sizeof(str), "%.3lluZ", 0x101000ULL);
+ ut_asserteq_str("1.004 MiB", str);
+ snprintf(str, sizeof(str), "%.3lluZ", 0x100000ULL);
+ ut_asserteq_str("1 MiB", str);
+ snprintf(str, sizeof(str), "%.3lluZ", 0x180000ULL);
+ ut_asserteq_str("1.5 MiB", str);
+
+ /* Rounding, also into the next unit */
+ snprintf(str, sizeof(str), "%lluZ", 0x180000ULL);
+ ut_asserteq_str("2 MiB", str);
+ snprintf(str, sizeof(str), "%.1lluZ", (1ULL << 30) - 1);
+ ut_asserteq_str("1 GiB", str);
+
+ /* Field width */
+ snprintf(str, sizeof(str), "[%10lluZ]", 1ULL << 10);
+ ut_asserteq_str("[ 1 KiB]", str);
+ snprintf(str, sizeof(str), "[%-10lluZ]", 1ULL << 10);
+ ut_asserteq_str("[1 KiB ]", str);
+
+ /* Only %u takes the Z */
+ snprintf(str, sizeof(str), "%dZ %xZ", 0x100000, 0x100000);
+ ut_asserteq_str("1048576Z 100000Z", str);
+
+ /* A size which does not fit is truncated like any other output */
+ ut_asserteq(9, snprintf(str, 5, "%.3lluZ", 0x113000ULL));
+ ut_asserteq_str("1.07", str);
+
+ return 0;
+}
+COMMON_TEST(print_size_unit, 0);
+
+/* Test that %.1lluZ prints the same as print_size() */
+static int print_size_unit_compat(struct unit_test_state *uts)
+{
+ static const u64 sizes[] = {
+ 0, 1, 999, 1023, 1024, 1025, 1536, 1588, 1638, 0x3ffff,
+ 0xfffff, 0x100000, 0x113000, 0x1f00000, 0x7e00000,
+ 0x3fffffff, 0x40000000, 0x76c0000000ULL, 0xe8d4a51000ULL,
+ 0xfffffffffffULL, 1ULL << 40, (1ULL << 50) + (1ULL << 49),
+ (1ULL << 60) - 1, 3ULL << 60, ~0ULL,
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sizes); i++) {
+ print_size(sizes[i], "\n");
+ ut_assert_nextline("%.1lluZ", sizes[i]);
+ }
+ ut_assert_console_end();
+
+ return 0;
+}
+COMMON_TEST(print_size_unit_compat, UTF_CONSOLE);
+
static int print_display_buffer(struct unit_test_state *uts)
{
u8 *buf;
--
2.34.1