| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * lib/hexdump.c |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. See README and COPYING for |
| * more details. |
| */ |
| |
| #include <hexdump.h> |
| #include <mapmem.h> |
| #include <stdio.h> |
| #include <linux/ctype.h> |
| #include <linux/compat.h> |
| #include <linux/log2.h> |
| #include <asm/unaligned.h> |
| |
| #define MAX_LINE_LENGTH_BYTES 64 |
| |
| const char hex_asc[] = "0123456789abcdef"; |
| const char hex_asc_upper[] = "0123456789ABCDEF"; |
| |
| #if CONFIG_IS_ENABLED(HEXDUMP) |
| int hex_dump_to_buffer(const void *buf, size_t len, int rowsize, int groupsize, |
| char *linebuf, size_t linebuflen, bool ascii) |
| { |
| const u8 *ptr = buf; |
| int ngroups; |
| u8 ch; |
| int j, lx = 0; |
| int ascii_column; |
| int ret; |
| |
| if (!rowsize) |
| rowsize = 16; |
| else |
| rowsize = min(rowsize, MAX_LINE_LENGTH_BYTES); |
| |
| if (len > rowsize) /* limit to one line at a time */ |
| len = rowsize; |
| if (!is_power_of_2(groupsize) || groupsize > 8) |
| groupsize = 1; |
| if ((len % groupsize) != 0) /* no mixed size output */ |
| groupsize = 1; |
| |
| ngroups = len / groupsize; |
| ascii_column = rowsize * 2 + rowsize / groupsize + 1; |
| |
| if (!linebuflen) |
| goto overflow1; |
| |
| if (!len) |
| goto nil; |
| |
| if (groupsize == 8) { |
| const u64 *ptr8 = buf; |
| |
| for (j = 0; j < ngroups; j++) { |
| ret = snprintf(linebuf + lx, linebuflen - lx, |
| "%s%16.16llx", j ? " " : "", |
| get_unaligned(ptr8 + j)); |
| if (ret >= linebuflen - lx) |
| goto overflow1; |
| lx += ret; |
| } |
| } else if (groupsize == 4) { |
| const u32 *ptr4 = buf; |
| |
| for (j = 0; j < ngroups; j++) { |
| ret = snprintf(linebuf + lx, linebuflen - lx, |
| "%s%8.8x", j ? " " : "", |
| get_unaligned(ptr4 + j)); |
| if (ret >= linebuflen - lx) |
| goto overflow1; |
| lx += ret; |
| } |
| } else if (groupsize == 2) { |
| const u16 *ptr2 = buf; |
| |
| for (j = 0; j < ngroups; j++) { |
| ret = snprintf(linebuf + lx, linebuflen - lx, |
| "%s%4.4x", j ? " " : "", |
| get_unaligned(ptr2 + j)); |
| if (ret >= linebuflen - lx) |
| goto overflow1; |
| lx += ret; |
| } |
| } else { |
| for (j = 0; j < len; j++) { |
| if (linebuflen < lx + 2) |
| goto overflow2; |
| ch = ptr[j]; |
| linebuf[lx++] = hex_asc_hi(ch); |
| if (linebuflen < lx + 2) |
| goto overflow2; |
| linebuf[lx++] = hex_asc_lo(ch); |
| if (linebuflen < lx + 2) |
| goto overflow2; |
| linebuf[lx++] = ' '; |
| } |
| if (j) |
| lx--; |
| } |
| if (!ascii) |
| goto nil; |
| |
| while (lx < ascii_column) { |
| if (linebuflen < lx + 2) |
| goto overflow2; |
| linebuf[lx++] = ' '; |
| } |
| for (j = 0; j < len; j++) { |
| if (linebuflen < lx + 2) |
| goto overflow2; |
| ch = ptr[j]; |
| linebuf[lx++] = (isascii(ch) && isprint(ch)) ? ch : '.'; |
| } |
| nil: |
| linebuf[lx] = '\0'; |
| return lx; |
| overflow2: |
| linebuf[lx++] = '\0'; |
| overflow1: |
| return ascii ? ascii_column + len : (groupsize * 2 + 1) * ngroups - 1; |
| } |
| |
| int print_hex_dump(const char *prefix_str, int prefix_type, int rowsize, |
| int groupsize, const void *buf, size_t len, bool ascii) |
| { |
| const u8 *ptr = buf; |
| int i, linelen, remaining = len; |
| char linebuf[MAX_LINE_LENGTH_BYTES * 3 + 2 + MAX_LINE_LENGTH_BYTES + 1]; |
| |
| if (!rowsize) |
| rowsize = 16; |
| else |
| rowsize = min(rowsize, MAX_LINE_LENGTH_BYTES); |
| |
| for (i = 0; i < len; i += rowsize) { |
| linelen = min(remaining, rowsize); |
| remaining -= rowsize; |
| |
| hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize, |
| linebuf, sizeof(linebuf), ascii); |
| |
| switch (prefix_type) { |
| case DUMP_PREFIX_ADDRESS: |
| printf("%s%0*lx: %s\n", prefix_str, |
| IS_ENABLED(CONFIG_PHYS_64BIT) ? 16 : 8, |
| (ulong)map_to_sysmem(ptr) + i, linebuf); |
| break; |
| case DUMP_PREFIX_OFFSET: |
| printf("%s%.8x: %s\n", prefix_str, i, linebuf); |
| break; |
| default: |
| printf("%s%s\n", prefix_str, linebuf); |
| break; |
| } |
| if (!IS_ENABLED(CONFIG_XPL_BUILD) && ctrlc()) |
| return -EINTR; |
| } |
| |
| return 0; |
| } |
| |
| void print_hex_dump_bytes(const char *prefix_str, int prefix_type, |
| const void *buf, size_t len) |
| { |
| print_hex_dump(prefix_str, prefix_type, 16, 1, buf, len, true); |
| } |
| #else |
| /* |
| * Some code in U-Boot copy-pasted from Linux kernel uses both |
| * functions below so to keep stuff compilable we keep these stubs here. |
| */ |
| int print_hex_dump(const char *prefix_str, int prefix_type, int rowsize, |
| int groupsize, const void *buf, size_t len, bool ascii) |
| { |
| return -ENOSYS; |
| } |
| |
| void print_hex_dump_bytes(const char *prefix_str, int prefix_type, |
| const void *buf, size_t len) |
| { |
| } |
| #endif /* CONFIG_HEXDUMP */ |