diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/.gitignore | 6 | ||||
-rw-r--r-- | cmd/2048.c | 398 | ||||
-rw-r--r-- | cmd/Kconfig | 3128 | ||||
-rw-r--r-- | cmd/Makefile | 301 | ||||
-rw-r--r-- | cmd/abootimg.c | 319 | ||||
-rw-r--r-- | cmd/acpi.c | 216 | ||||
-rw-r--r-- | cmd/adc.c | 166 | ||||
-rw-r--r-- | cmd/addrmap.c | 34 | ||||
-rw-r--r-- | cmd/adtimg.c | 244 | ||||
-rw-r--r-- | cmd/aes.c | 344 | ||||
-rw-r--r-- | cmd/arm/Makefile | 7 | ||||
-rw-r--r-- | cmd/arm/exception.c | 59 | ||||
-rw-r--r-- | cmd/arm/exception64.c | 87 | ||||
-rw-r--r-- | cmd/armffa.c | 201 | ||||
-rw-r--r-- | cmd/armflash.c | 310 | ||||
-rw-r--r-- | cmd/avb.c | 485 | ||||
-rw-r--r-- | cmd/axi.c | 355 | ||||
-rw-r--r-- | cmd/bcb.c | 504 | ||||
-rw-r--r-- | cmd/bdinfo.c | 216 | ||||
-rw-r--r-- | cmd/bind.c | 260 | ||||
-rw-r--r-- | cmd/binop.c | 154 | ||||
-rw-r--r-- | cmd/blk_common.c | 131 | ||||
-rw-r--r-- | cmd/blkcache.c | 75 | ||||
-rw-r--r-- | cmd/blkmap.c | 239 | ||||
-rw-r--r-- | cmd/blob.c | 124 | ||||
-rw-r--r-- | cmd/bloblist.c | 37 | ||||
-rw-r--r-- | cmd/bmp.c | 96 | ||||
-rw-r--r-- | cmd/boot.c | 72 | ||||
-rw-r--r-- | cmd/bootcount.c | 55 | ||||
-rw-r--r-- | cmd/bootdev.c | 151 | ||||
-rw-r--r-- | cmd/bootefi.c | 269 | ||||
-rw-r--r-- | cmd/bootflow.c | 638 | ||||
-rw-r--r-- | cmd/booti.c | 178 | ||||
-rw-r--r-- | cmd/bootm.c | 585 | ||||
-rw-r--r-- | cmd/bootmenu.c | 717 | ||||
-rw-r--r-- | cmd/bootmeth.c | 134 | ||||
-rw-r--r-- | cmd/bootstage.c | 102 | ||||
-rw-r--r-- | cmd/bootstd.c | 65 | ||||
-rw-r--r-- | cmd/bootz.c | 132 | ||||
-rw-r--r-- | cmd/broadcom/Makefile | 6 | ||||
-rw-r--r-- | cmd/broadcom/chimp_boot.c | 36 | ||||
-rw-r--r-- | cmd/broadcom/chimp_handshake.c | 32 | ||||
-rw-r--r-- | cmd/broadcom/nitro_image_load.c | 126 | ||||
-rw-r--r-- | cmd/btrfs.c | 26 | ||||
-rw-r--r-- | cmd/button.c | 85 | ||||
-rw-r--r-- | cmd/c5_pl330_dma.c | 49 | ||||
-rw-r--r-- | cmd/cache.c | 102 | ||||
-rw-r--r-- | cmd/cat.c | 70 | ||||
-rw-r--r-- | cmd/cbfs.c | 230 | ||||
-rw-r--r-- | cmd/cedit.c | 321 | ||||
-rw-r--r-- | cmd/cli.c | 128 | ||||
-rw-r--r-- | cmd/clk.c | 153 | ||||
-rw-r--r-- | cmd/clone.c | 128 | ||||
-rw-r--r-- | cmd/cls.c | 21 | ||||
-rw-r--r-- | cmd/config.c | 44 | ||||
-rw-r--r-- | cmd/conitrace.c | 49 | ||||
-rw-r--r-- | cmd/console.c | 56 | ||||
-rw-r--r-- | cmd/cpu.c | 131 | ||||
-rw-r--r-- | cmd/cramfs.c | 210 | ||||
-rw-r--r-- | cmd/cros_ec.c | 568 | ||||
-rw-r--r-- | cmd/cyclic.c | 84 | ||||
-rw-r--r-- | cmd/date.c | 215 | ||||
-rw-r--r-- | cmd/demo.c | 135 | ||||
-rw-r--r-- | cmd/dfu.c | 112 | ||||
-rw-r--r-- | cmd/diag.c | 59 | ||||
-rw-r--r-- | cmd/disk.c | 131 | ||||
-rw-r--r-- | cmd/dm.c | 131 | ||||
-rw-r--r-- | cmd/echo.c | 43 | ||||
-rw-r--r-- | cmd/eeprom.c | 577 | ||||
-rw-r--r-- | cmd/efi.c | 330 | ||||
-rw-r--r-- | cmd/efi_common.c | 23 | ||||
-rw-r--r-- | cmd/eficonfig.c | 2507 | ||||
-rw-r--r-- | cmd/eficonfig_sbkey.c | 543 | ||||
-rw-r--r-- | cmd/efidebug.c | 1700 | ||||
-rw-r--r-- | cmd/elf.c | 338 | ||||
-rw-r--r-- | cmd/erofs.c | 42 | ||||
-rw-r--r-- | cmd/ethsw.c | 1103 | ||||
-rw-r--r-- | cmd/event.c | 24 | ||||
-rw-r--r-- | cmd/exit.c | 26 | ||||
-rw-r--r-- | cmd/ext2.c | 52 | ||||
-rw-r--r-- | cmd/ext4.c | 90 | ||||
-rw-r--r-- | cmd/extension_board.c | 202 | ||||
-rw-r--r-- | cmd/fastboot.c | 187 | ||||
-rw-r--r-- | cmd/fat.c | 135 | ||||
-rw-r--r-- | cmd/fdt.c | 1159 | ||||
-rw-r--r-- | cmd/flash.c | 700 | ||||
-rw-r--r-- | cmd/font.c | 89 | ||||
-rw-r--r-- | cmd/fpga.c | 452 | ||||
-rw-r--r-- | cmd/fpgad.c | 101 | ||||
-rw-r--r-- | cmd/fs.c | 154 | ||||
-rw-r--r-- | cmd/fs_uuid.c | 24 | ||||
-rw-r--r-- | cmd/fuse.c | 211 | ||||
-rw-r--r-- | cmd/fwu_mdata.c | 89 | ||||
-rw-r--r-- | cmd/gettime.c | 39 | ||||
-rw-r--r-- | cmd/gpio.c | 317 | ||||
-rw-r--r-- | cmd/gpt.c | 1240 | ||||
-rw-r--r-- | cmd/hash.c | 57 | ||||
-rw-r--r-- | cmd/help.c | 36 | ||||
-rw-r--r-- | cmd/history.c | 22 | ||||
-rw-r--r-- | cmd/host.c | 273 | ||||
-rw-r--r-- | cmd/i2c.c | 1984 | ||||
-rw-r--r-- | cmd/i3c.c | 271 | ||||
-rw-r--r-- | cmd/ide.c | 78 | ||||
-rw-r--r-- | cmd/ini.c | 251 | ||||
-rw-r--r-- | cmd/io.c | 125 | ||||
-rw-r--r-- | cmd/iotrace.c | 129 | ||||
-rw-r--r-- | cmd/irq.c | 37 | ||||
-rw-r--r-- | cmd/itest.c | 221 | ||||
-rw-r--r-- | cmd/jffs2.c | 611 | ||||
-rw-r--r-- | cmd/kaslrseed.c | 43 | ||||
-rw-r--r-- | cmd/led.c | 133 | ||||
-rw-r--r-- | cmd/legacy-mtd-utils.c | 102 | ||||
-rw-r--r-- | cmd/legacy-mtd-utils.h | 12 | ||||
-rw-r--r-- | cmd/legacy_led.c | 186 | ||||
-rw-r--r-- | cmd/license.c | 45 | ||||
-rw-r--r-- | cmd/load.c | 1193 | ||||
-rw-r--r-- | cmd/log.c | 420 | ||||
-rw-r--r-- | cmd/lsblk.c | 52 | ||||
-rw-r--r-- | cmd/lwip/Makefile | 6 | ||||
-rw-r--r-- | cmd/lwip/dhcp.c | 9 | ||||
-rw-r--r-- | cmd/lwip/dns.c | 8 | ||||
-rw-r--r-- | cmd/lwip/ping.c | 183 | ||||
-rw-r--r-- | cmd/lwip/sntp.c | 133 | ||||
-rw-r--r-- | cmd/lwip/tftp.c | 9 | ||||
-rw-r--r-- | cmd/lwip/wget.c | 222 | ||||
-rw-r--r-- | cmd/lzmadec.c | 55 | ||||
-rw-r--r-- | cmd/mbr.c | 315 | ||||
-rw-r--r-- | cmd/md5sum.c | 55 | ||||
-rw-r--r-- | cmd/mdio.c | 333 | ||||
-rw-r--r-- | cmd/mem.c | 1436 | ||||
-rw-r--r-- | cmd/meminfo.c | 105 | ||||
-rw-r--r-- | cmd/meson/Makefile | 5 | ||||
-rw-r--r-- | cmd/meson/sm.c | 191 | ||||
-rw-r--r-- | cmd/mii.c | 478 | ||||
-rw-r--r-- | cmd/misc.c | 131 | ||||
-rw-r--r-- | cmd/mmc.c | 1368 | ||||
-rw-r--r-- | cmd/mp.c | 93 | ||||
-rw-r--r-- | cmd/mtd.c | 831 | ||||
-rw-r--r-- | cmd/mtdparts.c | 2126 | ||||
-rw-r--r-- | cmd/mux.c | 183 | ||||
-rw-r--r-- | cmd/mvebu/Kconfig | 82 | ||||
-rw-r--r-- | cmd/mvebu/Makefile | 8 | ||||
-rw-r--r-- | cmd/mvebu/bubt.c | 1265 | ||||
-rw-r--r-- | cmd/mvebu/comphy_rx_training.c | 56 | ||||
-rw-r--r-- | cmd/nand.c | 1253 | ||||
-rw-r--r-- | cmd/net-common.c | 106 | ||||
-rw-r--r-- | cmd/net.c | 677 | ||||
-rw-r--r-- | cmd/nvedit.c | 1296 | ||||
-rw-r--r-- | cmd/nvedit_efi.c | 533 | ||||
-rw-r--r-- | cmd/nvme.c | 56 | ||||
-rw-r--r-- | cmd/onenand.c | 599 | ||||
-rw-r--r-- | cmd/optee.c | 71 | ||||
-rw-r--r-- | cmd/optee_rpmb.c | 282 | ||||
-rw-r--r-- | cmd/osd.c | 291 | ||||
-rw-r--r-- | cmd/panic.c | 23 | ||||
-rw-r--r-- | cmd/part.c | 318 | ||||
-rw-r--r-- | cmd/pause.c | 32 | ||||
-rw-r--r-- | cmd/pcap.c | 71 | ||||
-rw-r--r-- | cmd/pci.c | 645 | ||||
-rw-r--r-- | cmd/pci_mps.c | 161 | ||||
-rw-r--r-- | cmd/pinmux.c | 180 | ||||
-rw-r--r-- | cmd/pmc.c | 82 | ||||
-rw-r--r-- | cmd/pmic.c | 230 | ||||
-rw-r--r-- | cmd/printf.c | 647 | ||||
-rw-r--r-- | cmd/printf.h | 8 | ||||
-rw-r--r-- | cmd/pstore.c | 580 | ||||
-rw-r--r-- | cmd/pvblock.c | 28 | ||||
-rw-r--r-- | cmd/pwm.c | 115 | ||||
-rw-r--r-- | cmd/pxe.c | 335 | ||||
-rw-r--r-- | cmd/qfw.c | 123 | ||||
-rw-r--r-- | cmd/read.c | 83 | ||||
-rw-r--r-- | cmd/reginfo.c | 24 | ||||
-rw-r--r-- | cmd/regulator.c | 468 | ||||
-rw-r--r-- | cmd/remoteproc.c | 282 | ||||
-rw-r--r-- | cmd/riscv/Makefile | 4 | ||||
-rw-r--r-- | cmd/riscv/exception.c | 91 | ||||
-rw-r--r-- | cmd/riscv/sbi.c | 136 | ||||
-rw-r--r-- | cmd/rkmtd.c | 203 | ||||
-rw-r--r-- | cmd/rng.c | 80 | ||||
-rw-r--r-- | cmd/rockusb.c | 74 | ||||
-rw-r--r-- | cmd/rtc.c | 167 | ||||
-rw-r--r-- | cmd/sandbox/Makefile | 3 | ||||
-rw-r--r-- | cmd/sandbox/exception.c | 44 | ||||
-rw-r--r-- | cmd/sata.c | 126 | ||||
-rw-r--r-- | cmd/sb.c | 58 | ||||
-rw-r--r-- | cmd/scmi.c | 384 | ||||
-rw-r--r-- | cmd/scp03.c | 51 | ||||
-rw-r--r-- | cmd/scsi.c | 71 | ||||
-rw-r--r-- | cmd/seama.c | 158 | ||||
-rw-r--r-- | cmd/setexpr.c | 569 | ||||
-rw-r--r-- | cmd/sf.c | 653 | ||||
-rw-r--r-- | cmd/sha1sum.c | 53 | ||||
-rw-r--r-- | cmd/sleep.c | 59 | ||||
-rw-r--r-- | cmd/smbios.c | 500 | ||||
-rw-r--r-- | cmd/smccc.c | 72 | ||||
-rw-r--r-- | cmd/sound.c | 104 | ||||
-rw-r--r-- | cmd/source.c | 72 | ||||
-rw-r--r-- | cmd/spawn.c | 188 | ||||
-rw-r--r-- | cmd/spi.c | 176 | ||||
-rw-r--r-- | cmd/spl.c | 167 | ||||
-rw-r--r-- | cmd/sqfs.c | 42 | ||||
-rw-r--r-- | cmd/stackprot_test.c | 27 | ||||
-rw-r--r-- | cmd/strings.c | 47 | ||||
-rw-r--r-- | cmd/sysboot.c | 134 | ||||
-rw-r--r-- | cmd/tcpm.c | 136 | ||||
-rw-r--r-- | cmd/temperature.c | 84 | ||||
-rw-r--r-- | cmd/terminal.c | 75 | ||||
-rw-r--r-- | cmd/test.c | 237 | ||||
-rw-r--r-- | cmd/thordown.c | 84 | ||||
-rw-r--r-- | cmd/ti/Kconfig | 19 | ||||
-rw-r--r-- | cmd/ti/Makefile | 5 | ||||
-rw-r--r-- | cmd/ti/ddr3.c | 342 | ||||
-rw-r--r-- | cmd/ti/pd.c | 184 | ||||
-rw-r--r-- | cmd/time.c | 44 | ||||
-rw-r--r-- | cmd/timer.c | 35 | ||||
-rw-r--r-- | cmd/tlv_eeprom.c | 1111 | ||||
-rw-r--r-- | cmd/tpm-common.c | 422 | ||||
-rw-r--r-- | cmd/tpm-user-utils.h | 29 | ||||
-rw-r--r-- | cmd/tpm-v1.c | 839 | ||||
-rw-r--r-- | cmd/tpm-v2.c | 598 | ||||
-rw-r--r-- | cmd/tpm_test.c | 573 | ||||
-rw-r--r-- | cmd/trace.c | 124 | ||||
-rw-r--r-- | cmd/ubi.c | 921 | ||||
-rw-r--r-- | cmd/ubifs.c | 171 | ||||
-rw-r--r-- | cmd/ufetch.c | 234 | ||||
-rw-r--r-- | cmd/ufs.c | 38 | ||||
-rw-r--r-- | cmd/unlz4.c | 48 | ||||
-rw-r--r-- | cmd/unzip.c | 93 | ||||
-rw-r--r-- | cmd/upl.c | 119 | ||||
-rw-r--r-- | cmd/usb.c | 725 | ||||
-rw-r--r-- | cmd/usb_gadget_sdp.c | 58 | ||||
-rw-r--r-- | cmd/usb_mass_storage.c | 259 | ||||
-rw-r--r-- | cmd/vbe.c | 116 | ||||
-rw-r--r-- | cmd/version.c | 38 | ||||
-rw-r--r-- | cmd/video.c | 60 | ||||
-rw-r--r-- | cmd/virtio.c | 53 | ||||
-rw-r--r-- | cmd/w1.c | 126 | ||||
-rw-r--r-- | cmd/wdt.c | 173 | ||||
-rw-r--r-- | cmd/wol.c | 33 | ||||
-rw-r--r-- | cmd/x86/Makefile | 9 | ||||
-rw-r--r-- | cmd/x86/cbcmos.c | 139 | ||||
-rw-r--r-- | cmd/x86/cbsysinfo.c | 474 | ||||
-rw-r--r-- | cmd/x86/cpuid.c | 37 | ||||
-rw-r--r-- | cmd/x86/exception.c | 27 | ||||
-rw-r--r-- | cmd/x86/fsp.c | 109 | ||||
-rw-r--r-- | cmd/x86/hob.c | 163 | ||||
-rw-r--r-- | cmd/x86/msr.c | 52 | ||||
-rw-r--r-- | cmd/x86/mtrr.c | 140 | ||||
-rw-r--r-- | cmd/x86/zboot.c | 192 | ||||
-rw-r--r-- | cmd/ximg.c | 297 | ||||
-rw-r--r-- | cmd/xxd.c | 82 | ||||
-rw-r--r-- | cmd/zfs.c | 167 | ||||
-rw-r--r-- | cmd/zip.c | 43 |
253 files changed, 67745 insertions, 0 deletions
diff --git a/cmd/.gitignore b/cmd/.gitignore new file mode 100644 index 00000000000..bab889fd3d7 --- /dev/null +++ b/cmd/.gitignore @@ -0,0 +1,6 @@ +config_data.gz +config_data_gz.h +config_data_size.h +license_data.gz +license_data_gz.h +license_data_size.h diff --git a/cmd/2048.c b/cmd/2048.c new file mode 100644 index 00000000000..aa0f82721dc --- /dev/null +++ b/cmd/2048.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2014 Maurits van der Schee + +/* Console version of the game "2048" for GNU/Linux */ + +#include <cli.h> +#include <command.h> +#include <rand.h> +#include <vsprintf.h> +#include <linux/delay.h> +#include <linux/string.h> + +#define SIZE 4 +static uint score; + +static void getColor(uint value, char *color, size_t length) +{ + u8 original[] = { + 8, 255, 1, 255, 2, 255, 3, 255, + 4, 255, 5, 255, 6, 255, 7, 255, + 9, 0, 10, 0, 11, 0, 12, 0, 13, + 0, 14, 0, 255, 0, 255, 0}; + u8 *scheme = original; + u8 *background = scheme + 0; + u8 *foreground = scheme + 1; + + if (value > 0) { + while (value >>= 1) { + if (background + 2 < scheme + sizeof(original)) { + background += 2; + foreground += 2; + } + } + } + snprintf(color, length, "\033[38;5;%d;48;5;%dm", *foreground, + *background); +} + +static void drawBoard(u16 board[SIZE][SIZE]) +{ + int x, y; + char color[40], reset[] = "\033[0m"; + + printf("\033[H"); + printf("2048.c %17d pts\n\n", score); + + for (y = 0; y < SIZE; y++) { + for (x = 0; x < SIZE; x++) { + getColor(board[x][y], color, 40); + printf("%s", color); + printf(" "); + printf("%s", reset); + } + printf("\n"); + for (x = 0; x < SIZE; x++) { + getColor(board[x][y], color, 40); + printf("%s", color); + if (board[x][y] != 0) { + char s[8]; + s8 t; + + snprintf(s, 8, "%u", board[x][y]); + t = 7 - strlen(s); + printf("%*s%s%*s", t - t / 2, "", s, t / 2, ""); + } else { + printf(" · "); + } + printf("%s", reset); + } + printf("\n"); + for (x = 0; x < SIZE; x++) { + getColor(board[x][y], color, 40); + printf("%s", color); + printf(" "); + printf("%s", reset); + } + printf("\n"); + } + printf("\n"); + printf(" ←, ↑, →, ↓ or q \n"); + printf("\033[A"); +} + +static int8_t findTarget(u16 array[SIZE], int x, int stop) +{ + int t; + + /* if the position is already on the first, don't evaluate */ + if (x == 0) + return x; + for (t = x - 1; t >= 0; t--) { + if (array[t]) { + if (array[t] != array[x]) { + /* merge is not possible, take next position */ + return t + 1; + } + return t; + } + + /* we should not slide further, return this one */ + if (t == stop) + return t; + } + /* we did not find a */ + return x; +} + +static bool slideArray(u16 array[SIZE]) +{ + bool success = false; + int x, t, stop = 0; + + for (x = 0; x < SIZE; x++) { + if (array[x] != 0) { + t = findTarget(array, x, stop); + /* + * if target is not original position, then move or + * merge + */ + if (t != x) { + /* + * if target is not zero, set stop to avoid + * double merge + */ + if (array[t]) { + score += array[t] + array[x]; + stop = t + 1; + } + array[t] += array[x]; + array[x] = 0; + success = true; + } + } + } + return success; +} + +static void rotateBoard(u16 board[SIZE][SIZE]) +{ + s8 i, j, n = SIZE; + int tmp; + + for (i = 0; i < n / 2; i++) { + for (j = i; j < n - i - 1; j++) { + tmp = board[i][j]; + board[i][j] = board[j][n - i - 1]; + board[j][n - i - 1] = board[n - i - 1][n - j - 1]; + board[n - i - 1][n - j - 1] = board[n - j - 1][i]; + board[n - j - 1][i] = tmp; + } + } +} + +static bool moveUp(u16 board[SIZE][SIZE]) +{ + bool success = false; + int x; + + for (x = 0; x < SIZE; x++) + success |= slideArray(board[x]); + + return success; +} + +static bool moveLeft(u16 board[SIZE][SIZE]) +{ + bool success; + + rotateBoard(board); + success = moveUp(board); + rotateBoard(board); + rotateBoard(board); + rotateBoard(board); + return success; +} + +static bool moveDown(u16 board[SIZE][SIZE]) +{ + bool success; + + rotateBoard(board); + rotateBoard(board); + success = moveUp(board); + rotateBoard(board); + rotateBoard(board); + return success; +} + +static bool moveRight(u16 board[SIZE][SIZE]) +{ + bool success; + + rotateBoard(board); + rotateBoard(board); + rotateBoard(board); + success = moveUp(board); + rotateBoard(board); + return success; +} + +static bool findPairDown(u16 board[SIZE][SIZE]) +{ + bool success = false; + int x, y; + + for (x = 0; x < SIZE; x++) { + for (y = 0; y < SIZE - 1; y++) { + if (board[x][y] == board[x][y + 1]) + return true; + } + } + + return success; +} + +static int16_t countEmpty(u16 board[SIZE][SIZE]) +{ + int x, y; + int count = 0; + + for (x = 0; x < SIZE; x++) { + for (y = 0; y < SIZE; y++) { + if (board[x][y] == 0) + count++; + } + } + return count; +} + +static bool gameEnded(u16 board[SIZE][SIZE]) +{ + bool ended = true; + + if (countEmpty(board) > 0) + return false; + if (findPairDown(board)) + return false; + rotateBoard(board); + if (findPairDown(board)) + ended = false; + rotateBoard(board); + rotateBoard(board); + rotateBoard(board); + + return ended; +} + +static void addRandom(u16 board[SIZE][SIZE]) +{ + int x, y; + int r, len = 0; + u16 n, list[SIZE * SIZE][2]; + + for (x = 0; x < SIZE; x++) { + for (y = 0; y < SIZE; y++) { + if (board[x][y] == 0) { + list[len][0] = x; + list[len][1] = y; + len++; + } + } + } + + if (len > 0) { + r = rand() % len; + x = list[r][0]; + y = list[r][1]; + n = ((rand() % 10) / 9 + 1) * 2; + board[x][y] = n; + } +} + +static int test(void) +{ + u16 array[SIZE]; + u16 data[] = { + 0, 0, 0, 2, 2, 0, 0, 0, + 0, 0, 2, 2, 4, 0, 0, 0, + 0, 2, 0, 2, 4, 0, 0, 0, + 2, 0, 0, 2, 4, 0, 0, 0, + 2, 0, 2, 0, 4, 0, 0, 0, + 2, 2, 2, 0, 4, 2, 0, 0, + 2, 0, 2, 2, 4, 2, 0, 0, + 2, 2, 0, 2, 4, 2, 0, 0, + 2, 2, 2, 2, 4, 4, 0, 0, + 4, 4, 2, 2, 8, 4, 0, 0, + 2, 2, 4, 4, 4, 8, 0, 0, + 8, 0, 2, 2, 8, 4, 0, 0, + 4, 0, 2, 2, 4, 4, 0, 0 + }; + u16 *in, *out; + u16 t, tests; + int i; + bool success = true; + + tests = (sizeof(data) / sizeof(data[0])) / (2 * SIZE); + for (t = 0; t < tests; t++) { + in = data + t * 2 * SIZE; + out = in + SIZE; + for (i = 0; i < SIZE; i++) + array[i] = in[i]; + slideArray(array); + for (i = 0; i < SIZE; i++) { + if (array[i] != out[i]) + success = false; + } + if (!success) { + for (i = 0; i < SIZE; i++) + printf("%d ", in[i]); + printf(" = > "); + for (i = 0; i < SIZE; i++) + printf("%d ", array[i]); + printf("expected "); + for (i = 0; i < SIZE; i++) + printf("%d ", in[i]); + printf(" = > "); + for (i = 0; i < SIZE; i++) + printf("%d ", out[i]); + printf("\n"); + break; + } + } + if (success) + printf("All %u tests executed successfully\n", tests); + + return !success; +} + +static int do_2048(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cli_ch_state cch_s, *cch = &cch_s; + u16 board[SIZE][SIZE]; + bool success; + + if (argc == 2 && strcmp(argv[1], "test") == 0) + return test(); + + score = 0; + + printf("\033[?25l\033[2J\033[H"); + + memset(board, 0, sizeof(board)); + addRandom(board); + addRandom(board); + drawBoard(board); + cli_ch_init(cch); + while (true) { + int c; + + c = cli_ch_process(cch, 0); + if (!c) { + c = getchar(); + c = cli_ch_process(cch, c); + } + switch (c) { + case CTL_CH('b'): /* left arrow */ + success = moveLeft(board); + break; + case CTL_CH('f'): /* right arrow */ + success = moveRight(board); + break; + case CTL_CH('p'):/* up arrow */ + success = moveUp(board); + break; + case CTL_CH('n'): /* down arrow */ + success = moveDown(board); + break; + default: + success = false; + } + if (success) { + drawBoard(board); + mdelay(150); + addRandom(board); + drawBoard(board); + if (gameEnded(board)) { + printf(" GAME OVER \n"); + break; + } + } + if (c == 'q') { + printf(" QUIT \n"); + break; + } + } + + printf("\033[?25h"); + + return 0; +} + +U_BOOT_CMD( + 2048, 2, 1, do_2048, + "The 2048 game", + "Use your arrow keys to move the tiles. When two tiles with " + "the same number touch, they merge into one!" +); diff --git a/cmd/Kconfig b/cmd/Kconfig new file mode 100644 index 00000000000..eb615552a00 --- /dev/null +++ b/cmd/Kconfig @@ -0,0 +1,3128 @@ +menuconfig CMDLINE + bool "Command line interface" + default y + help + Enable U-Boot's command-line functions. This provides a means + to enter commands into U-Boot for a wide variety of purposes. It + also allows scripts (containing commands) to be executed. + Various commands and command categories can be individually enabled. + Depending on the number of commands enabled, this can add + substantially to the size of U-Boot. + +if CMDLINE + +config HUSH_PARSER + bool "Use hush shell" + help + This option enables the "hush" shell (from Busybox) as command line + interpreter, thus enabling powerful command line syntax like + if...then...else...fi conditionals or `&&' and '||' + constructs ("shell scripts"). + + If disabled, you get the old, much simpler behaviour with a somewhat + smaller memory footprint. + +menu "Hush flavor to use" +depends on HUSH_PARSER + +config HUSH_OLD_PARSER + bool "Use hush old parser" + default y + help + This option enables the old flavor of hush based on hush Busybox from + 2005. + + It is actually the default U-Boot shell when decided to use hush as shell. + +config HUSH_MODERN_PARSER + bool "Use hush modern parser" + help + This option enables the new flavor of hush based on hush upstream + Busybox. + + This parser is experimental and not well tested. + +config HUSH_SELECTABLE + bool + default y if HUSH_OLD_PARSER && HUSH_MODERN_PARSER +endmenu + +config CMDLINE_EDITING + bool "Enable command line editing" + default y + help + Enable editing and History functions for interactive command line + input operations + +config CMDLINE_PS_SUPPORT + bool "Enable support for changing the command prompt string at run-time" + depends on HUSH_PARSER + help + Only static string in the prompt is supported so far. The string is + obtained from environment variables PS1 and PS2. + +config AUTO_COMPLETE + bool "Enable auto complete using TAB" + default y + help + Enable auto completion of commands using TAB. + +config SYS_LONGHELP + bool "Enable long help messages" + default y + help + Defined when you want long help messages included + Do not set this option when short of memory. + +config SYS_PROMPT + string "Shell prompt" + default "Zynq> " if ARCH_ZYNQ + default "ZynqMP> " if ARCH_ZYNQMP + default "=> " + help + This string is displayed in the command line to the left of the + cursor. + +config SYS_PROMPT_HUSH_PS2 + string "Hush shell secondary prompt" + depends on HUSH_PARSER + default "> " + help + This defines the secondary prompt string, which is + printed when the command interpreter needs more input + to complete a command. Usually "> ". + +config SYS_MAXARGS + int "Maximum number arguments accepted by commands" + default 64 + +config SYS_XTRACE + bool "Command execution tracer" + default y + help + This option enables the possiblity to print all commands before + executing them and after all variables are evaluated (similar + to Bash's xtrace/'set -x' feature). + To enable the tracer a variable "xtrace" needs to be defined in + the environment. + +config BUILD_BIN2C + bool + +comment "Commands" + +menu "Info commands" + +config CMD_ACPI + bool "acpi" + depends on ACPI + default y + help + List and dump ACPI tables. ACPI (Advanced Configuration and Power + Interface) is used mostly on x86 for providing information to the + Operating System about devices in the system. The tables are set up + by the firmware, typically U-Boot but possibly an earlier firmware + module, if U-Boot is chain-loaded from something else. ACPI tables + can also include code, to perform hardware-specific tasks required + by the Operating Systems. This allows some amount of separation + between the firmware and OS, and is particularly useful when you + want to make hardware changes without the OS needing to be adjusted. + +config CMD_ADDRMAP + bool "addrmap" + depends on ADDR_MAP + default y + help + List non-identity virtual-physical memory mappings for 32-bit CPUs. + +config CMD_BDI + bool "bdinfo" + default y + help + Print board info + +config CMD_BDINFO_EXTRA + bool "bdinfo extra features" + default y if SANDBOX || X86 + help + Show additional information about the board. This uses a little more + code space but provides more options, particularly those useful for + bringup, development and debugging. + +config CMD_CONFIG + bool "config" + default SANDBOX + select BUILD_BIN2C + select GZIP + help + Print ".config" contents. + + If this option is enabled, the ".config" file contents are embedded + in the U-Boot image and can be printed on the console by the "config" + command. This provides information of which options are enabled on + the running U-Boot. + +config CMD_CONSOLE + bool "coninfo" + default y + help + Print console devices and information. + +config CMD_CPU + bool "cpu" + depends on CPU + help + Print information about available CPUs. This normally shows the + number of CPUs, type (e.g. manufacturer, architecture, product or + internal name) and clock frequency. Other information may be + available depending on the CPU driver. + +config CMD_UFETCH + bool "U-Boot fetch" + help + Fetch utility for U-Boot (akin to neofetch). Prints information + about U-Boot and the board it is running on in a pleasing format. + +config CMD_FWU_METADATA + bool "fwu metadata read" + depends on FWU_MULTI_BANK_UPDATE + imply HEXDUMP if FWU_MDATA_V2 + help + Command to read the metadata and dump it's contents + +config CMD_HELP + bool "help" + default y + help + Command to show help information about other commands. + +config CMD_HISTORY + bool "history" + depends on CMDLINE_EDITING + help + Show the command-line history, i.e. a list of commands that are in + the history buffer. + +config CMD_HISTORY_USE_CALLOC + bool "dynamically allocate memory" + default y + depends on CMD_HISTORY + help + Saying Y to this will use calloc to get the space for history + storing. Otherwise the history buffer will be an uninitialized + static array directly, without the memory allocation, and it is + writable after relocation to RAM. If u-boot is running from ROM + all the time or unsure, say Y to this. + +config CMD_LICENSE + bool "license" + select BUILD_BIN2C + depends on GZIP + help + Print GPL license text + +config CMD_PMC + bool "pmc" + help + Provides access to the Intel Power-Management Controller (PMC) so + that its state can be examined. This does not currently support + changing the state but it is still useful for debugging and seeing + what is going on. + +config CMD_REGINFO + bool "reginfo" + depends on PPC + help + Register dump + +config CMD_TCPM + bool "tcpm" + depends on TYPEC_TCPM + help + Show voltage and current negotiated via USB PD as well as the + current state of the Type C Port Manager (TCPM) state machine. + +config CMD_TLV_EEPROM + bool "tlv_eeprom" + depends on I2C_EEPROM + select CRC32 + help + Display and program the system EEPROM data block in ONIE Tlvinfo + format. TLV stands for Type-Length-Value. + +config SPL_CMD_TLV_EEPROM + bool "tlv_eeprom for SPL" + depends on SPL_I2C_EEPROM + select SPL_DRIVERS_MISC + select SPL_CRC32 + help + Read system EEPROM data block in ONIE Tlvinfo format from SPL. + +config CMD_SBI + bool "sbi" + depends on RISCV_SMODE && SBI_V02 + help + Display information about the SBI implementation. + +config CMD_SMBIOS + bool "smbios" + depends on SMBIOS + help + Display the SMBIOS information. + +endmenu + +menu "Boot commands" + +config CMD_BOOTD + bool "bootd" + default y + help + Run the command stored in the environment "bootcmd", i.e. + "bootd" does the same thing as "run bootcmd". + +config CMD_BOOTM + bool "bootm" + default y + help + Boot an application image from the memory. + +config CMD_BOOTM_PRE_LOAD + bool "enable pre-load on bootm" + depends on CMD_BOOTM + depends on IMAGE_PRE_LOAD + help + Enable support of stage pre-load for the bootm command. + This stage allow to check or modify the image provided + to the bootm command. + +config CMD_BOOTDEV + bool "bootdev" + depends on BOOTSTD + default y if BOOTSTD_FULL + help + Support listing available bootdevs (boot devices) which can provide an + OS to boot, as well as showing information about a particular one. + + This command is not necessary for bootstd to work. + +config CMD_BOOTFLOW + bool "bootflow" + depends on BOOTSTD + default y + help + Support scanning for bootflows available with the bootdevs. The + bootflows can optionally be booted. + +config CMD_BOOTFLOW_FULL + bool "bootflow - extract subcommands" + depends on BOOTSTD_FULL + default y + help + Add the ability to list the available bootflows, select one and obtain + information about it. + + This command is not necessary for bootstd to work. + +config CMD_BOOTMETH + bool "bootmeth" + depends on BOOTSTD + default y if BOOTSTD_FULL + help + Support listing available bootmeths (methods used to boot an + Operating System), as well as selecting the order that the bootmeths + are used. + + This command is not necessary for bootstd to work. + +config CMD_BOOTSTD + bool "bootstd" + depends on BOOTSTD + default y if BOOTSTD_FULL + help + Provide general information and control for bootstd. + + This command is not necessary for bootstd to work. + +config BOOTM_EFI + bool "Support booting UEFI FIT images" + depends on EFI_BINARY_EXEC && CMD_BOOTM && FIT + default y + help + Support booting UEFI FIT images via the bootm command. + +config BOOTM_ELF + bool "Support booting ELF images" + depends on CMD_BOOTM && LIB_ELF + default y if LIB_ELF + help + Support booting ELF images via the bootm command. + +config CMD_BOOTZ + bool "bootz" + help + Boot the Linux zImage + +config CMD_BOOTI + bool "booti" + depends on ARM64 || RISCV || SANDBOX + default y + help + Boot an AArch64 Linux Kernel image from memory. + +config BOOTM_LINUX + bool "Support booting Linux OS images" + depends on CMD_BOOTM || CMD_BOOTZ || CMD_BOOTI + default y + help + Support booting the Linux kernel directly via a command such as bootm + or booti or bootz. + +config BOOTM_NETBSD + bool "Support booting NetBSD (non-EFI) loader images" + depends on CMD_BOOTM + default y + help + Support booting NetBSD via the bootm command. + +config BOOTM_OPENRTOS + bool "Support booting OPENRTOS / FreeRTOS images" + depends on CMD_BOOTM + help + Support booting OPENRTOS / FreeRTOS via the bootm command. + +config BOOTM_OSE + bool "Support booting Enea OSE images" + depends on (ARM && (ARM64 || CPU_V7A || CPU_V7R) || SANDBOX || PPC || X86) + depends on CMD_BOOTM + help + Support booting Enea OSE images via the bootm command. + +config BOOTM_PLAN9 + bool "Support booting Plan9 OS images" + depends on CMD_BOOTM + default y + help + Support booting Plan9 images via the bootm command. + +config BOOTM_RTEMS + bool "Support booting RTEMS OS images" + depends on CMD_BOOTM + default y + help + Support booting RTEMS images via the bootm command. + +config CMD_SEAMA + bool "Support read SEAMA NAND images" + depends on (TARGET_BCMNS || TARGET_BCMNS3) && MTD_RAW_NAND + help + Support reading NAND Seattle Image (SEAMA) images. + +config CMD_UPL + bool "upl - Universal Payload Specification" + help + Provides commands to deal with UPL payloads and handoff information. + U-Boot supports generating and accepting handoff information. The + mkimage tool will eventually support creating payloads. + +config CMD_VBE + bool "vbe - Verified Boot for Embedded" + depends on BOOTMETH_VBE + default y if BOOTSTD_FULL + help + Provides various subcommands related to VBE, such as listing the + available methods, looking at the state and changing which method + is used to boot. Updating the parameters is not currently + supported. + +config BOOTM_VXWORKS + bool "Support booting VxWorks OS images" + depends on CMD_BOOTM + default y + help + Support booting VxWorks images via the bootm command. + +config CMD_BOOTEFI + bool "bootefi" + depends on EFI_LOADER + default y + help + Boot an EFI image from memory. + +if CMD_BOOTEFI +config CMD_BOOTEFI_BINARY + bool "Allow booting an EFI binary directly" + depends on EFI_BINARY_EXEC + default y + help + Select this option to enable direct execution of binary at 'bootefi'. + This subcommand will allow you to load the UEFI binary using + other U-Boot commands or external methods and then run it. + +config CMD_BOOTEFI_BOOTMGR + bool "UEFI Boot Manager command" + depends on EFI_BOOTMGR + default y + help + Select this option to enable the 'bootmgr' subcommand of 'bootefi'. + This subcommand will allow you to select the UEFI binary to be booted + via UEFI variables Boot####, BootOrder, and BootNext. + +config CMD_BOOTEFI_HELLO + bool "Allow booting a standard EFI hello world for testing" + depends on CMD_BOOTEFI_BINARY && BOOTEFI_HELLO_COMPILE + default y if CMD_BOOTEFI_SELFTEST + help + This adds a standard EFI hello world application to U-Boot so that + it can be used with the 'bootefi hello' command. This is useful + for testing that EFI is working at a basic level, and for bringing + up EFI support on a new architecture. + +source "lib/efi_selftest/Kconfig" +endif + +config CMD_BOOTMENU + bool "bootmenu" + select MENU + select CHARSET + help + Add an ANSI terminal boot menu command. + +config CMD_ADTIMG + bool "adtimg" + help + Android DTB/DTBO image manipulation commands. Read dtb/dtbo files from + image into RAM, dump image structure information, etc. Those dtb/dtbo + files should be merged in one dtb further, which needs to be passed to + the kernel, as part of a boot process. + +config CMD_ABOOTIMG + bool "abootimg" + depends on ANDROID_BOOT_IMAGE + help + Android Boot Image manipulation commands. Allows one to extract + images contained in boot.img, like kernel, ramdisk, dtb, etc, and + obtain corresponding meta-information from boot.img. + + See doc/android/boot-image.rst for details. + +config CMD_CEDIT + bool "cedit - Configuration editor" + depends on CEDIT + default y + help + Provides a command to allow editing of board configuration and + providing a UI for the user to adjust settings. Subcommands allow + loading and saving of configuration as well as showing an editor. + +config CMD_ELF + bool "bootelf" + default y + select LIB_ELF + help + Boot an ELF image from memory. + +config CMD_ELF_BOOTVX + bool "bootvx" + default y + depends on CMD_ELF + help + Boot a vxWorks image from memory + +config CMD_ELF_FDT_SETUP + bool "Flattened Device Tree setup in bootelf cmd" + depends on CMD_ELF + select OF_LIBFDT + help + Do FDT setup in bootelf command optionally by param -d, which + allows to bring additional system info (e.g. /memory node) to + the Operating System or application. + +config CMD_FDT + bool "Flattened Device Tree utility commands" + default y + depends on OF_LIBFDT + help + Do FDT related setup before booting into the Operating System. + +config SUPPORT_EXTENSION_SCAN + bool + +config CMD_EXTENSION + bool "Extension board management command" + select CMD_FDT + depends on SUPPORT_EXTENSION_SCAN + help + Enables the "extension" command, which allows to detect + extension boards connected to the system, and apply + corresponding Device Tree overlays. + +config CMD_GO + bool "go" + default y + help + Start an application at a given address. + +config CMD_RUN + bool "run" + default y + help + Run the command in the given environment variable. + +config CMD_IMI + bool "iminfo" + default y + help + Print header information for application image. + +config CMD_IMLS + bool "imls" + depends on MTD_NOR_FLASH || FLASH_CFI_DRIVER + help + List all images found in flash + +config CMD_XIMG + bool "imxtract" + default y + help + Extract a part of a multi-image. + +config SYS_XIMG_LEN + hex "imxtract max gunzip size" + default 0x800000 + depends on CMD_XIMG && GZIP + help + This provides the size of the commad-line argument area + used by imxtract for extracting pieces of FIT image. + It should be large enough to fit uncompressed size of + FIT piece we are extracting. + +config CMD_SPL + bool "spl export - Export boot information for Falcon boot" + depends on SPL + help + Falcon mode allows booting directly from SPL into an Operating + System such as Linux, thus skipping U-Boot proper. See + doc/README.falcon for full information about how to use this + command. + +config CMD_SPL_NAND_OFS + hex "Offset of OS args or dtb for Falcon-mode NAND boot" + depends on CMD_SPL && (TPL_NAND_SUPPORT || SPL_NAND_SUPPORT) + default 0x0 + help + This provides the offset of the command line arguments for Linux + when booting from NAND in Falcon mode. See doc/README.falcon + for full information about how to use this option (and also see + board/gateworks/gw_ventana/README for an example). + +config CMD_SPL_NOR_OFS + hex "Offset of OS args or dtb for Falcon-mode NOR boot" + depends on CMD_SPL && SPL_NOR_SUPPORT + default 0x0 + help + This provides the offset of the command line arguments or dtb for + Linux when booting from NOR in Falcon mode. + +config CMD_SPL_WRITE_SIZE + hex "Size of argument area" + depends on CMD_SPL + default 0x2000 + help + This provides the size of the command-line argument area in NAND + flash used by Falcon-mode boot. See the documentation until CMD_SPL + for detail. + +config CMD_THOR_DOWNLOAD + bool "thor - TIZEN 'thor' download" + select DFU + select USB_FUNCTION_THOR + depends on USB_GADGET_DOWNLOAD + help + Implements the 'thor' download protocol. This is a way of + downloading a software update over USB from an attached host. + There is no documentation about this within the U-Boot source code + but you should be able to find something on the interwebs. + +config THOR_RESET_OFF + bool "thor: Disable reset on completion" + depends on CMD_THOR_DOWNLOAD + +config CMD_ZBOOT + bool "zboot - x86 boot command" + depends on ZBOOT + default y + help + With x86 machines it is common to boot a bzImage file which + contains both a kernel and a setup.bin file. The latter includes + configuration information from the dark ages which x86 boards still + need to pick things out of. + + Consider using FIT in preference to this since it supports directly + booting both 32- and 64-bit kernels, as well as secure boot. + Documentation is available in doc/usage/fit/x86-fit-boot.rst. + +endmenu + +menu "Environment commands" + +config CMD_ASKENV + bool "ask for env variable" + help + Ask for environment variable + +config CMD_EXPORTENV + bool "env export" + default y + help + Export environments. + +config CMD_IMPORTENV + bool "env import" + default y + help + Import environments. + +config CMD_EDITENV + bool "editenv" + default y + help + Edit environment variable. + +config CMD_GREPENV + bool "search env" + help + Allow for searching environment variables + +config CMD_SAVEENV + bool "saveenv" + default y + help + Save all environment variables into the compiled-in persistent + storage. + +config CMD_ERASEENV + bool "eraseenv" + depends on CMD_SAVEENV + help + Erase environment variables from the compiled-in persistent + storage. + +config CMD_ENV_EXISTS + bool "env exists" + default y + help + Check if a variable is defined in the environment for use in + shell scripting. + +config CMD_ENV_CALLBACK + bool "env callbacks - print callbacks and their associated variables" + help + Some environment variable have callbacks defined by + U_BOOT_ENV_CALLBACK. These are called when the variable changes. + For example changing "baudrate" adjust the serial baud rate. This + command lists the currently defined callbacks. + +config CMD_ENV_FLAGS + bool "env flags -print variables that have non-default flags" + help + Some environment variables have special flags that control their + behaviour. For example, serial# can only be written once and cannot + be deleted. This command shows the variables that have special + flags. + +config CMD_NVEDIT_EFI + bool "env [set|print] -e - set/print UEFI variables" + depends on EFI_LOADER + imply HEXDUMP + help + UEFI variables are encoded as some form of U-Boot variables. + If enabled, we are allowed to set/print UEFI variables using + "env" command with "-e" option without knowing details. + +config CMD_NVEDIT_INDIRECT + bool "env indirect - Sets environment value from another" + +config CMD_NVEDIT_INFO + bool "env info - print or evaluate environment information" + help + Print environment information: + - env_valid : is environment valid + - env_ready : is environment imported into hash table + - env_use_default : is default environment used + + This command can be optionally used for evaluation in scripts: + [-d] : evaluate whether default environment is used + [-p] : evaluate whether environment can be persisted + [-q] : quiet output + The result of multiple evaluations will be combined with AND. + +config CMD_NVEDIT_LOAD + bool "env load" + help + Load all environment variables from the compiled-in persistent + storage. + +config CMD_NVEDIT_SELECT + bool "env select" + help + Select the compiled-in persistent storage of environment variables. + +endmenu + +menu "Memory commands" + +config CMD_BINOP + bool "binop" + help + Compute binary operations (xor, or, and) of byte arrays of arbitrary + size from memory and store the result in memory or the environment. + +config CMD_BLOBLIST + bool "bloblist" + depends on BLOBLIST + default y + help + Show information about the bloblist, a collection of binary blobs + held in memory that persist between SPL and U-Boot. In the case of + x86 devices the bloblist can be used to hold ACPI tables so that they + remain available in memory. + +config CMD_CRC32 + bool "crc32" + default y + select HASH + help + Compute CRC32. + +config CRC32_VERIFY + bool "crc32 -v" + depends on CMD_CRC32 + help + Add -v option to verify data against a crc32 checksum. + +config CMD_EEPROM + bool "eeprom - EEPROM subsystem" + depends on DM_I2C || SYS_I2C_LEGACY + help + (deprecated, needs conversion to driver model) + Provides commands to read and write EEPROM (Electrically Erasable + Programmable Read Only Memory) chips that are connected over an + I2C bus. + +config CMD_EEPROM_LAYOUT + bool "Enable layout-aware eeprom commands" + depends on CMD_EEPROM + help + (deprecated, needs conversion to driver model) + When enabled, additional eeprom sub-commands become available. + + eeprom print - prints the contents of the eeprom in a human-readable + way (eeprom layout fields, and data formatted to be fit for human + consumption). + + eeprom update - allows user to update eeprom fields by specifying + the field name, and providing the new data in a human readable format + (same format as displayed by the eeprom print command). + + Both commands can either auto detect the layout, or be told which + layout to use. + + Feature API: + __weak int parse_layout_version(char *str) + - override to provide your own layout name parsing + __weak void __eeprom_layout_assign(struct eeprom_layout *layout, + int layout_version); + - override to setup the layout metadata based on the version + __weak int eeprom_layout_detect(unsigned char *data) + - override to provide your own algorithm for detecting layout + version + eeprom_field.c + - contains various printing and updating functions for common + types of eeprom fields. Can be used for defining + custom layouts. + +config EEPROM_LAYOUT_VERSIONS + bool "Support specifying eeprom layout version" + depends on CMD_EEPROM_LAYOUT + help + Support specifying eeprom layout version in the 'eeprom' command + via the -l option. + +config EEPROM_LAYOUT_HELP_STRING + string "Tells user what layout names are supported" + depends on EEPROM_LAYOUT_VERSIONS + default "<not defined>" + help + Help printed with the LAYOUT VERSIONS part of the 'eeprom' + command's help. + +config SYS_I2C_EEPROM_BUS + int "I2C bus of the EEPROM device." + depends on CMD_EEPROM + default 0 + +config SYS_I2C_EEPROM_ADDR_LEN + int "Length in bytes of the EEPROM memory array address" + depends on CMD_EEPROM || ID_EEPROM + default 1 + range 1 2 + help + Note: This is NOT the chip address length! + +config SYS_EEPROM_SIZE + depends on CMD_EEPROM + int "Size in bytes of the EEPROM device" + default 256 + +config SYS_EEPROM_PAGE_WRITE_BITS + int "Number of bits used to address bytes in a single page" + depends on CMD_EEPROM || ENV_IS_IN_EEPROM + default 8 + help + The EEPROM page size is 2^SYS_EEPROM_PAGE_WRITE_BITS. + A 64 byte page, for example would require six bits. + +config SYS_EEPROM_PAGE_WRITE_DELAY_MS + int "Number of milliseconds to delay between page writes" + depends on CMD_EEPROM || CMD_I2C + default 0 + +config LOOPW + bool "loopw" + help + Infinite write loop on address range + +config CMD_MD5SUM + bool "md5sum" + select MD5 + select HASH + help + Compute MD5 checksum. + +config MD5SUM_VERIFY + bool "md5sum -v" + depends on CMD_MD5SUM + help + Add -v option to verify data against an MD5 checksum. + +config CMD_MEMINFO + bool "meminfo" + default y if SANDBOX || X86 + help + Display memory information. + +config CMD_MEMINFO_MAP + bool "- with memory map" + depends on CMD_MEMINFO + default y if SANDBOX || X86 + help + Shows a memory map, in addition to just the DRAM size. This allows + seeing where U-Boot's memory area is, at the top of DRAM, as well as + detail about each piece of it. + + See doc/usage/cmd/meminfo.rst for more information. + +config CMD_MEMORY + bool "md, mm, nm, mw, cp, cmp, base, loop" + default y + help + Memory commands. + md - memory display + mm - memory modify (auto-incrementing address) + nm - memory modify (constant address) + mw - memory write (fill) + cp - memory copy + cmp - memory compare + base - print or set address offset + loop - initialize loop on address range + +config CMD_MEM_SEARCH + bool "ms - Memory search" + help + Memory-search command + + This allows searching through a region of memory looking for hex + data (byte, 16-bit word, 32-bit long, also 64-bit on machines that + support it). It is also possible to search for a string. The + command accepts a memory range and a list of values to search for. + The values need to appear in memory in the same order they are given + in the command. At most 10 matches can be returned at a time, but + pressing return will show the next 10 matches. Environment variables + are set for use with scripting (memmatches, memaddr, mempos). + +config CMD_MX_CYCLIC + bool "Enable cyclic md/mw commands" + depends on CMD_MEMORY + help + Add the "mdc" and "mwc" memory commands. These are cyclic + "md/mw" commands. + Examples: + + => mdc.b 10 4 500 + This command will print 4 bytes (10,11,12,13) each 500 ms. + + => mwc.l 100 12345678 10 + This command will write 12345678 to address 100 all 10 ms. + +config CMD_RANDOM + bool "random" + default y + depends on CMD_MEMORY && (LIB_RAND || LIB_HW_RAND) + help + random - fill memory with random data + +config CMD_MEMTEST + bool "memtest" + help + Simple RAM read/write test. + +if CMD_MEMTEST + +config SYS_ALT_MEMTEST + bool "Alternative test" + help + Use a more complete alternative memory test. + +if SYS_ALT_MEMTEST + +config SYS_ALT_MEMTEST_BITFLIP + bool "Bitflip test" + default y + help + The alternative memory test includes bitflip test since 2020.07. + The bitflip test significantly increases the overall test time. + Bitflip test can optionally be disabled here. + +endif + +config SYS_MEMTEST_START + hex "default start address for mtest" + default 0x0 + help + This is the default start address for mtest for simple read/write + test. If no arguments are given to mtest, default address is used + as start address. + +config SYS_MEMTEST_END + hex "default end address for mtest" + default 0x1000 + help + This is the default end address for mtest for simple read/write + test. If no arguments are given to mtest, default address is used + as end address. + +endif + +config CMD_SHA1SUM + bool "sha1sum" + select SHA1 + help + Compute SHA1 checksum. + +config SHA1SUM_VERIFY + bool "sha1sum -v" + depends on CMD_SHA1SUM + help + Add -v option to verify data against a SHA1 checksum. + +config CMD_STRINGS + bool "strings - display strings in memory" + help + This works similarly to the Unix 'strings' command except that it + works with a memory range. String of printable characters found + within the range are displayed. The minimum number of characters + for a sequence to be considered a string can be provided. + +endmenu + +menu "Compression commands" + +config CMD_LZMADEC + bool "lzmadec" + default y if CMD_BOOTI + select LZMA + help + Support decompressing an LZMA (Lempel-Ziv-Markov chain algorithm) + image from memory. + +config CMD_UNLZ4 + bool "unlz4" + default y if CMD_BOOTI + select LZ4 + help + Support decompressing an LZ4 image from memory region. + +config CMD_UNZIP + bool "unzip" + default y if CMD_BOOTI + select GZIP + help + Uncompress a zip-compressed memory region. + +config CMD_ZIP + bool "zip" + select GZIP_COMPRESSED + help + Compress a memory region with zlib deflate method. + +endmenu + +menu "Device access commands" + +config CMD_ARMFFA + bool "Arm FF-A test command" + depends on ARM_FFA_TRANSPORT + help + Provides a test command for the FF-A support + supported options: + - Listing the partition(s) info + - Sending a data pattern to the specified partition + - Displaying the arm_ffa device info + +config CMD_ARMFLASH + bool "armflash" + depends on FLASH_CFI_DRIVER + help + ARM Ltd reference designs flash partition access + +config CMD_ADC + bool "adc - Access Analog to Digital Converters info and data" + select ADC + depends on DM_REGULATOR + help + Shows ADC device info and permit printing one-shot analog converted + data from a named Analog to Digital Converter. + +config CMD_BCB + bool "bcb" + depends on PARTITIONS + help + Read/modify/write the fields of Bootloader Control Block, usually + stored on the flash "misc" partition with its structure defined in: + https://android.googlesource.com/platform/bootable/recovery/+/master/ + bootloader_message/include/bootloader_message/bootloader_message.h + + Some real-life use-cases include (but are not limited to): + - Determine the "boot reason" (and act accordingly): + https://source.android.com/devices/bootloader/boot-reason + - Get/pass a list of commands from/to recovery: + https://android.googlesource.com/platform/bootable/recovery + - Inspect/dump the contents of the BCB fields + +config CMD_BIND + bool "bind/unbind - Bind or unbind a device to/from a driver" + depends on DM + imply CMD_DM + help + Bind or unbind a device to/from a driver from the command line. + This is useful in situations where a device may be handled by several + drivers. For example, this can be used to bind a UDC to the usb ether + gadget driver from the command line. + +config CMD_CLK + bool "clk - Show and set clock frequencies" + depends on CLK + help + Show and set clock frequencies managed by CLK uclass drivers. + +config CMD_DEMO + bool "demo - Demonstration commands for driver model" + depends on DM + help + Provides a 'demo' command which can be used to play around with + driver model. To use this properly you will need to enable one or + both of the demo devices (DM_DEMO_SHAPE and DM_DEMO_SIMPLE). + Otherwise you will always get an empty list of devices. The demo + devices are defined in the sandbox device tree, so the easiest + option is to use sandbox and pass the -d point to sandbox's + u-boot.dtb file. + +config CMD_DFU + bool "dfu" + select DFU + help + Enables the command "dfu" which is used to have U-Boot create a DFU + class device via USB. This command requires that the "dfu_alt_info" + environment variable be set and define the alt settings to expose to + the host. + +config CMD_DM + bool "dm - Access to driver model information" + depends on DM + help + Provides access to driver model data structures and information, + such as a list of devices, list of uclasses and the state of each + device (e.g. activated). This is not required for operation, but + can be useful to see the state of driver model for debugging or + interest. + +config CMD_FASTBOOT + bool "fastboot - Android fastboot support" + depends on FASTBOOT + help + This enables the command "fastboot" which enables the Android + fastboot mode for the platform. Fastboot is a protocol for + downloading images, flashing and device control used on + Android devices. Fastboot requires either the network stack + enabled or support for acting as a USB device. + + See doc/android/fastboot.rst for more information. + +config CMD_FLASH + bool "flinfo, erase, protect" + default y + depends on FLASH_CFI_DRIVER || MTD_NOR_FLASH + help + NOR flash support. + flinfo - print FLASH memory information + erase - FLASH memory + protect - enable or disable FLASH write protection + +config CMD_FPGA + bool "fpga" + depends on FPGA + default y + help + FPGA support. + +config CMD_FPGA_LOADB + bool "fpga loadb - load bitstream file" + default y + depends on CMD_FPGA && FPGA_XILINX + help + Supports loading an FPGA device from a bitstream file (.BIT) + +config CMD_FPGA_LOADP + bool "fpga loadp - load partial bitstream" + depends on CMD_FPGA + help + Supports loading an FPGA device from a bitstream buffer (.BIN) + containing a partial bitstream. + +config CMD_FPGA_LOADBP + bool "fpga loadbp - load partial bitstream file" + depends on CMD_FPGA && FPGA_XILINX + help + Supports loading an FPGA device from a bitstream file (.BIT) + containing a partial bitstream. + +config CMD_FPGA_LOADFS + bool "fpga loadfs - load bitstream from FAT filesystem" + depends on CMD_FPGA && FPGA_XILINX + help + Supports loading an FPGA device from a FAT filesystem. + +config CMD_FPGA_LOADMK + bool "fpga loadmk - load bitstream from image" + depends on CMD_FPGA + help + Supports loading an FPGA device from a image generated by mkimage. + +config CMD_FPGA_LOAD_SECURE + bool "fpga loads - loads secure bitstreams" + depends on CMD_FPGA && FPGA_XILINX + select FPGA_LOAD_SECURE + help + Enables the fpga loads command which is used to load secure + (authenticated or encrypted or both) bitstreams on to FPGA. + +config CMD_FPGAD + bool "fpgad - dump FPGA registers" + depends on GDSYS_LEGACY_DRIVERS + help + (legacy, needs conversion to driver model) + Provides a way to dump FPGA registers by calling the board-specific + fpga_get_reg() function. This functions similarly to the 'md' + command. + +config CMD_FUSE + bool "fuse - support for the fuse subssystem" + depends on !COMPILE_TEST + help + (deprecated - needs conversion to driver model) + This allows reading, sensing, programming or overriding fuses + which control the behaviour of the device. The command uses the + fuse_...() API. + +config CMD_FUSE_WRITEBUFF + bool "Support for the fuse writebuff" + depends on CMD_FUSE + help + This allows programming fuses, which control the behaviour of + the device, using a structured buffer in memory. The command + uses the fuse_writebuff() API. + +config CMD_GPIO + bool "gpio" + help + GPIO support. + +config CMD_GPIO_READ + bool "gpio read - save GPIO value to variable" + depends on CMD_GPIO + help + Enables the 'gpio read' command that saves the value + of a GPIO pin to a variable. + +config CMD_PWM + bool "pwm" + depends on DM_PWM + help + Control PWM channels, this allows invert/config/enable/disable PWM channels. + +config CMD_GPT + bool "GPT (GUID Partition Table) command" + select EFI_PARTITION + select PARTITION_UUIDS + imply RANDOM_UUID + help + Enable the 'gpt' command to ready and write GPT style partition + tables. + +config CMD_GPT_RENAME + bool "GPT partition renaming commands" + depends on CMD_GPT + help + Enables the 'gpt' command to interchange names on two GPT + partitions via the 'gpt swap' command or to rename single + partitions via the 'rename' command. + +config CMD_IDE + bool "ide - Support for IDE drivers" + depends on !COMPILE_TEST + select IDE + help + Provides an 'ide' command which allows accessing the IDE drive, + resetting the IDE interface, printing the partition table and + geting device info. It also enables the 'diskboot' command which + permits booting from an IDE drive. + +config CMD_IO + bool "io - Support for performing I/O accesses" + help + Provides an 'iod' command to display I/O space and an 'iow' command + to write values to the I/O space. This can be useful for manually + checking the state of devices during boot when debugging device + drivers, etc. + +config CMD_IOTRACE + bool "iotrace - Support for tracing I/O activity" + help + Provides an 'iotrace' command which supports recording I/O reads and + writes in a trace buffer in memory . It also maintains a checksum + of the trace records (even if space is exhausted) so that the + sequence of I/O accesses can be verified. + + When debugging drivers it is useful to see what I/O accesses were + done and in what order. + + Even if the individual accesses are of little interest it can be + useful to verify that the access pattern is consistent each time + an operation is performed. In this case a checksum can be used to + characterise the operation of a driver. The checksum can be compared + across different runs of the operation to verify that the driver is + working properly. + + In particular, when performing major refactoring of the driver, where + the access pattern should not change, the checksum provides assurance + that the refactoring work has not broken the driver. + + This works by sneaking into the io.h heder for an architecture and + redirecting I/O accesses through iotrace's tracing mechanism. + + For now no commands are provided to examine the trace buffer. The + format is fairly simple, so 'md' is a reasonable substitute. + + Note: The checksum feature is only useful for I/O regions where the + contents do not change outside of software control. Where this is not + suitable you can fall back to manually comparing the addresses. It + might be useful to enhance tracing to only checksum the accesses and + not the data read/written. + +config CMD_I2C + bool "i2c" + help + I2C support. + +config CMD_I3C + bool "i3c" + depends on I3C + help + Enable command to list i3c devices connected to the i3c controller + and perform read and write on the connected i3c devices. + +config CMD_W1 + depends on W1 + default y if W1 + bool "w1 - Support for Dallas 1-Wire protocol" + help + Dallas 1-wire protocol support + +config CMD_LOADB + bool "loadb" + default y + help + Load a binary file over serial line. + +config CMD_LOADM + bool "loadm" + help + Load a binary over memory mapped. + +config CMD_LOADS + bool "loads - Load a file over serial in S-Record format" + default y + help + Load an S-Record file over serial line + +config LOADS_ECHO + bool "Echo all characters received during a loads back to console" + depends on CMD_LOADS + help + If enabled, all characters received during a serial download (using + the "loads" command) are echoed back. This might be needed by some + terminal emulations (like "cu"), but may as well just take time on + others. This sets the initial value of the "loads_echo" environment + variable to 1. + +config CMD_SAVES + bool "saves - Save a file over serial in S-Record format" + depends on CMD_LOADS + help + Provides a way to save a binary file using the Motorola S-Record + format over the serial line. + +config SYS_LOADS_BAUD_CHANGE + bool "Enable a temporary baudrate change during loads/saves command" + depends on CMD_LOADS || CMD_SAVES + +config CMD_LOADXY_TIMEOUT + int "loadxy_timeout" + range 0 2000 + default 90 + help + Initial timeout for loadx and loady commands. Zero means infinity. + +config CMD_LSBLK + depends on BLK + bool "lsblk - list block drivers and devices" + help + Print list of available block device drivers, and for each, the list + of known block devices. + +config CMD_MBR + bool "MBR (Master Boot Record) command" + select DOS_PARTITION + help + Enable the 'mbr' command to ready and write MBR (Master Boot Record) + style partition tables. + +config CMD_MISC + bool "misc" + depends on MISC + help + Enable the command "misc" for accessing miscellaneous devices with + a MISC uclass driver. The command provides listing all MISC devices + as well as read and write functionalities via their drivers. + +config CMD_MMC + bool "mmc" + depends on MMC + help + MMC memory mapped support. + +if CMD_MMC + +config CMD_BKOPS_ENABLE + bool "mmc bkops enable" + depends on CMD_MMC + help + Enable command for setting manual background operations handshake + on a eMMC device. The feature is optionally available on eMMC devices + conforming to standard >= 4.41. + +config CMD_MMC_REG + bool "Enable support for reading card registers in the mmc command" + depends on CMD_MMC + help + Enable the commands for reading card registers. This is useful + mostly for debugging or extracting details from the card. + +config CMD_MMC_RPMB + bool "Enable support for RPMB in the mmc command" + depends on SUPPORT_EMMC_RPMB + help + Enable the commands for reading, writing and programming the + key for the Replay Protection Memory Block partition in eMMC. + +config CMD_MMC_SWRITE + bool "mmc swrite" + depends on MMC_WRITE + select IMAGE_SPARSE + help + Enable support for the "mmc swrite" command to write Android sparse + images to eMMC. + +config MMC_SPEED_MODE_SET + bool "set speed mode using mmc command" + help + Enable setting speed mode using mmc rescan and mmc dev commands. + The speed mode is provided as the last argument in these commands + and is indicated using the index from enum bus_mode in + include/mmc.h. A speed mode can be set only if it has already + been enabled in the device tree. + +endif + +config CMD_CLONE + bool "clone" + depends on BLK + help + Enable storage cloning over block devices, useful for + initial flashing by external block device without network + or usb support. + +config CMD_OPTEE_RPMB + bool "Enable read/write support on RPMB via OPTEE" + depends on (SUPPORT_EMMC_RPMB && OPTEE) || SANDBOX_TEE + default y if SANDBOX_TEE + select OPTEE_TA_AVB if SANDBOX_TEE + help + Enable the commands for reading, writing persistent named values + in the Replay Protection Memory Block partition in eMMC by + using Persistent Objects in OPTEE + +config CMD_OPTEE + bool "Enable OP-TEE commands" + depends on OPTEE + help + OP-TEE commands support. + +config CMD_MTD + bool "mtd" + depends on MTD + select MTD_PARTITIONS + help + MTD commands support. + +config CMD_MTD_OTP + bool "mtd otp" + depends on CMD_MTD + select HEXDUMP + help + MTD commands for OTP access. + +config CMD_MUX + bool "mux" + depends on MULTIPLEXER + help + List, select, and deselect mux controllers on the fly. + +config CMD_NAND + bool "nand" + default y if NAND_SUNXI + depends on MTD_RAW_NAND + help + NAND support. + +if CMD_NAND +config CMD_NAND_TRIMFFS + bool "nand write.trimffs" + default y if ARCH_SUNXI + help + Allows one to skip empty pages when flashing something on a NAND. + +config CMD_NAND_LOCK_UNLOCK + bool "nand lock/unlock" + help + NAND locking support. + +config CMD_NAND_TORTURE + bool "nand torture" + help + NAND torture support. + +config CMD_NAND_WATCH + bool "nand watch" + help + NAND watch bitflip support. + +endif # CMD_NAND + +config CMD_NVME + bool "nvme" + depends on NVME + default y if NVME + help + NVM Express device support + +config CMD_ONENAND + bool "onenand - access to onenand device" + depends on MTD + depends on !COMPILE_TEST + help + OneNAND is a brand of NAND ('Not AND' gate) flash which provides + various useful features. This command allows reading, writing, + and erasing blocks. It allso provides a way to show and change + bad blocks, and test the device. + +config USE_ONENAND_BOARD_INIT + bool "Call onenand_board_init() in the onenand command" + depends on CMD_ONENAND + depends on !COMPILE_TEST + +config CMD_OSD + bool "osd" + depends on OSD + help + Enable the 'osd' command which allows to query information from and + write text data to a on-screen display (OSD) device; a virtual device + associated with a display capable of displaying a text overlay on the + display it's associated with.. + +config CMD_PART + bool "part" + depends on PARTITIONS + select PARTITION_UUIDS + help + Read and display information about the partition table on + various media. + +config CMD_PCI + bool "pci - Access PCI devices" + help + Provide access to PCI (Peripheral Interconnect Bus), a type of bus + used on some devices to allow the CPU to communicate with its + peripherals. Sub-commands allow bus enumeration, displaying and + changing configuration space and a few other features. + +config CMD_PCI_MPS + bool "pci_mps - Configure PCI device MPS" + depends on PCI + help + Enables PCI Express Maximum Packet Size (MPS) tuning. This + command configures the PCI Express MPS of each endpoint to the + largest value supported by all devices below the root complex. + The Maximum Read Request Size will not be altered. This method is + the same algorithm as used by Linux pci=pcie_bus_safe. + +config CMD_PINMUX + bool "pinmux - show pins muxing" + depends on PINCTRL + default y if PINCTRL + help + Parse all available pin-controllers and show pins muxing. This + is useful for debug purpoer to check the pin muxing and to know if + a pin is configured as a GPIO or as an alternate function. + +config CMD_POWEROFF + bool "poweroff" + help + Poweroff/Shutdown the system + +config CMD_READ + bool "read - Read binary data from a partition" + help + Provides low-level access to the data in a partition. + +config CMD_REMOTEPROC + bool "remoteproc" + depends on REMOTEPROC + default y if ARCH_K3 + help + Support for Remote Processor control + +config CMD_SATA + bool "sata - Access SATA subsystem" + select SATA + help + SATA (Serial Advanced Technology Attachment) is a serial bus + standard for connecting to hard drives and other storage devices. + This command provides information about attached devices and allows + reading, writing and other operations. + + SATA replaces PATA (originally just ATA), which stands for Parallel AT + Attachment, where AT refers to an IBM AT (Advanced Technology) + computer released in 1984. + +config CMD_SCSI + bool "scsi - Access to SCSI devices" + depends on SCSI + default y + help + This provides a 'scsi' command which provides access to SCSI (Small + Computer System Interface) devices. The command provides a way to + scan the bus, reset the bus, read and write data and get information + about devices. + +config CMD_SDRAM + bool "sdram - Print SDRAM configuration information" + help + Provides information about attached SDRAM. This assumed that the + SDRAM has an EEPROM with information that can be read using the + I2C bus. This is only available on some boards. + +config CMD_SF + bool "sf" + depends on DM_SPI_FLASH || SPI_FLASH + default y if DM_SPI_FLASH + help + SPI Flash support + +config CMD_SF_TEST + bool "sf test - Allow testing of SPI flash" + depends on CMD_SF + help + Provides a way to test that SPI flash is working correctly. The + test is destructive, in that an area of SPI flash must be provided + for the test to use. Performance information is also provided, + measuring the performance of reading, writing and erasing in + Mbps (Million Bits Per Second). This value should approximately + equal the SPI bus speed for a single-bit-wide SPI bus, assuming + everything is working properly. + +config CMD_SPI + bool "sspi - Command to access spi device" + depends on SPI + help + SPI utility command. + +config DEFAULT_SPI_BUS + int "default spi bus used by sspi command" + depends on CMD_SPI + default 0 + +config DEFAULT_SPI_MODE + hex "default spi mode used by sspi command (see include/spi.h)" + depends on CMD_SPI + default 0x0 + +config CMD_TEMPERATURE + bool "temperature - display the temperature from thermal sensors" + depends on DM_THERMAL + help + Provides a way to list thermal sensors and to get their readings. + +config CMD_UFS + bool "ufs - Universal Flash Storage commands" + depends on UFS + help + "This provides commands to initialise and configure universal flash + subsystem devices" + +config CMD_USB + bool "usb" + depends on USB_HOST + help + USB support. + +config CMD_USB_SDP + bool "sdp" + depends on USB_GADGET_DOWNLOAD + select USB_FUNCTION_SDP + help + Enables the command "sdp" which is used to have U-Boot emulating the + Serial Download Protocol (SDP) via USB. + +config CMD_RKMTD + bool "rkmtd" + select RKMTD + help + Enable the command "rkmtd" to create a virtual block device to transfer + Rockchip boot block data to and from NAND with block orientated tools + like "ums" and "rockusb". + +config CMD_ROCKUSB + bool "rockusb" + depends on USB_FUNCTION_ROCKUSB + help + Rockusb protocol is widely used by Rockchip SoC based devices. It can + read/write info, image to/from devices. This enable rockusb command + support to communication with rockusb device. for more detail about + this command, please read doc/README.rockusb. + +config CMD_USB_MASS_STORAGE + bool "UMS usb mass storage" + depends on BLK && USB_GADGET_DOWNLOAD + select USB_FUNCTION_MASS_STORAGE + help + Enables the command "ums" and the USB mass storage support to the + export a block device: U-Boot, the USB device, acts as a simple + external hard drive plugged on the host USB port. + +config CMD_UMS_ABORT_KEYED + bool "UMS abort with any key" + depends on CMD_USB_MASS_STORAGE + help + Allow interruption of usb mass storage run with any key pressed. + +config CMD_PVBLOCK + bool "Xen para-virtualized block device" + depends on XEN + select PVBLOCK + help + Xen para-virtualized block device support + +config CMD_VIRTIO + bool "virtio" + depends on VIRTIO + default y if VIRTIO + help + VirtIO block device support + +config CMD_WDT + bool "wdt" + depends on WDT + help + This provides commands to control the watchdog timer devices. + +config CMD_WRITE + bool "write - Write binary data to a partition" + help + Provides low-level write access to a partition. + +config CMD_AXI + bool "axi" + depends on AXI + help + Enable the command "axi" for accessing AXI (Advanced eXtensible + Interface) busses, a on-chip interconnect specification for managing + functional blocks in SoC designs, which is also often used in designs + involving FPGAs (e.g. communication with IP cores in Xilinx FPGAs). +endmenu + + +menu "Shell scripting commands" + + +config CMD_C5_PL330_DMA + bool "Release Reset DMA Channels for PL330 Handshake" + depends on TARGET_SOCFPGA_CYCLONE5_SOCDK + help + Provides access to Reset Manager Per2ModRst. Enables DMA + channels for ARM PrimeCell PL330 via reset release. + +config CMD_CAT + bool "cat" + help + Print file to standard output + +config CMD_ECHO + bool "echo" + default y + help + Echo args to console + +config CMD_ITEST + bool "itest" + default y + help + Return true/false on integer compare. + +config CMD_SOURCE + bool "source" + default y + help + Run script from memory + +config CMD_SETEXPR + bool "setexpr" + default y + help + Evaluate boolean and math expressions and store the result in an env + variable. + Also supports loading the value at a memory location into a variable. + If CONFIG_REGEX is enabled, setexpr also supports a gsub function. + +config CMD_SETEXPR_FMT + bool "setexpr_fmt" + depends on CMD_SETEXPR + help + Evaluate format string expression and store result in an environment + variable. + +config CMD_XXD + bool "xxd" + help + Print file as hexdump to standard output + +endmenu + +if NET || NET_LWIP + +menuconfig CMD_NET + bool "Network commands" + default y + +if CMD_NET + +if NET + +config CMD_BOOTP + bool "bootp" + default y + help + bootp - boot image via network using BOOTP/TFTP protocol + +config CMD_DHCP6 + bool "dhcp6" + depends on IPV6 + help + Boot image via network using DHCPv6/TFTP protocol using IPv6. + + Will perform 4-message exchange with DHCPv6 server, requesting + the minimum required options to TFTP boot. Complies with RFC 8415. + +if CMD_DHCP6 + +config DHCP6_PXE_DHCP_OPTION + bool "Request & store 'pxe_configfile' from DHCP6 server" + +config DHCP6_ENTERPRISE_ID + int "Enterprise ID to send in DHCPv6 Vendor Class Option" + default 0 + +endif + +config BOOTP_MAY_FAIL + bool "Allow for the BOOTP/DHCP server to not be found" + depends on CMD_BOOTP + help + If the DHCP server is not found after the configured retry count, the + call will fail instead of starting over. This can be used to fail + over to Link-local IP address configuration if the DHCP server is not + available. + +config BOOTP_BOOTPATH + bool "Request & store 'rootpath' from BOOTP/DHCP server" + default y + depends on CMD_BOOTP + help + Even though the config is called BOOTP_BOOTPATH, it stores the + path in the variable 'rootpath'. + +config BOOTP_VENDOREX + bool "Support vendor extensions from BOOTP/DHCP server" + depends on CMD_BOOTP + +config BOOTP_BOOTFILESIZE + bool "Request & store 'bootfilesize' from BOOTP/DHCP server" + depends on CMD_BOOTP + +config BOOTP_DNS + bool "Request & store 'dnsip' from BOOTP/DHCP server" + default y + depends on CMD_BOOTP + help + The primary DNS server is stored as 'dnsip'. If two servers are + returned, you must set BOOTP_DNS2 to store that second server IP + also. + +config BOOTP_DNS2 + bool "Store 'dnsip2' from BOOTP/DHCP server" + depends on BOOTP_DNS + help + If a DHCP client requests the DNS server IP from a DHCP server, + it is possible that more than one DNS serverip is offered to the + client. If CONFIG_BOOTP_DNS2 is enabled, the secondary DNS + server IP will be stored in the additional environment + variable "dnsip2". The first DNS serverip is always + stored in the variable "dnsip", when BOOTP_DNS is defined. + +config BOOTP_GATEWAY + bool "Request & store 'gatewayip' from BOOTP/DHCP server" + default y + depends on CMD_BOOTP + +config BOOTP_HOSTNAME + bool "Request & store 'hostname' from BOOTP/DHCP server" + default y + depends on CMD_BOOTP + help + The name may or may not be qualified with the local domain name. + +config BOOTP_PREFER_SERVERIP + bool "serverip variable takes precedent over DHCP server IP." + depends on CMD_BOOTP + help + By default a BOOTP/DHCP reply will overwrite the 'serverip' variable. + + With this option enabled, the 'serverip' variable in the environment + takes precedence over DHCP server IP and will only be set by the DHCP + server if not already set in the environment. + +config BOOTP_SUBNETMASK + bool "Request & store 'netmask' from BOOTP/DHCP server" + default y + depends on CMD_BOOTP + +config BOOTP_NISDOMAIN + bool "Request & store 'nisdomain' from BOOTP/DHCP server" + depends on CMD_BOOTP + +config BOOTP_NTPSERVER + bool "Request & store 'ntpserverip' from BOOTP/DHCP server" + depends on CMD_BOOTP + +config BOOTP_TIMEOFFSET + bool "Request & store 'timeoffset' from BOOTP/DHCP server" + depends on CMD_BOOTP && CMD_SNTP + +config CMD_PCAP + bool "pcap capture" + help + Selecting this will allow capturing all Ethernet packets and store + them in physical memory in a PCAP formated file, + later to be analyzed by PCAP reader application (IE. WireShark). + +config BOOTP_PXE + bool "Send PXE client arch to BOOTP/DHCP server" + default y + depends on CMD_BOOTP && CMD_PXE + help + Supported for ARM, ARM64, and x86 for now. + +config DHCP_PXE_CLIENTARCH + hex "DCHCP client system architecture type" + depends on BOOTP_PXE || CMD_DHCP6 + default 0x16 if ARM64 # arm 64 uboot + default 0x15 if ARM # arm 32 uboot + default 0x0 if X86 # x86 BIOS + default 0xFF # DHCP option not sent + help + DHCP option 93 (defined in RFC4578) or DHCPv6 option 61 (defined in + RFC 5970) is used to transmit the client system architecture type + to the DHCP server. The DHCP server may use this information to + choose the boot file. For a complete list of assigned values see + https://www.iana.org/assignments/dhcpv6-parameters#processor-architecture. + + If the value is set to the reserved value 0xFF, the DHCP option will + not be sent by U-Boot. + +config BOOTP_PXE_DHCP_OPTION + bool "Request & store 'pxe_configfile' from BOOTP/DHCP server" + default y + depends on BOOTP_PXE + +config BOOTP_VCI_STRING + string + depends on CMD_BOOTP + default "U-Boot.armv7" if CPU_V7A || CPU_V7M || CPU_V7R + default "U-Boot.armv8" if ARM64 + default "U-Boot.arm" if ARM + default "U-Boot" + +config BOOTP_RANDOM_XID + bool "Send random transaction ID to BOOTP/DHCP server" + depends on CMD_BOOTP && (LIB_RAND || LIB_HW_RAND) + help + Selecting this will allow for a random transaction ID to get + selected for each BOOTP/DHCPv4 exchange. + +config CMD_TFTPPUT + bool "tftp put" + depends on CMD_TFTPBOOT + help + TFTP put command, for uploading files to a server + +config CMD_TFTPSRV + bool "tftpsrv" + depends on CMD_TFTPBOOT + help + Act as a TFTP server and boot the first received file + +config NET_TFTP_VARS + bool "Control TFTP timeout and count through environment" + depends on CMD_TFTPBOOT + default y + help + If set, allows controlling the TFTP timeout through the + environment variable tftptimeout, and the TFTP maximum + timeout count through the variable tftptimeoutcountmax. + If unset, timeout and maximum are hard-defined as 1 second + and 10 timouts per TFTP transfer. + +config CMD_RARP + bool "rarpboot" + help + Boot image via network using RARP/TFTP protocol + +config CMD_NFS + bool "nfs" + help + Boot image via network using NFS protocol. + +config NFS_TIMEOUT + int "Timeout in milliseconds for NFS mounts" + depends on CMD_NFS + default 2000 + help + Timeout in milliseconds used in NFS protocol. If you encounter + "ERROR: Cannot umount" in nfs command, try longer timeout such as + 10000. + +config SYS_DISABLE_AUTOLOAD + bool "Disable automatically loading files over the network" + depends on CMD_BOOTP || CMD_DHCP || CMD_NFS || CMD_RARP + help + Typically, commands such as "dhcp" will attempt to automatically + load a file from the network, once the initial network configuration + is complete. Enable this option to disable this behavior and instead + require files to be loaded over the network by subsequent commands. + +config CMD_PING6 + bool "ping6" + depends on IPV6 + default y if (CMD_PING && IPV6) + help + Send ICMPv6 ECHO_REQUEST to network host + +config CMD_CDP + bool "cdp" + help + The cdp command is used to announce the U-Boot device in the network + and to retrieve configuration data including the VLAN id using the + proprietary Cisco Discovery Protocol (CDP). + +config CMD_LINK_LOCAL + bool "linklocal" + depends on (LIB_RAND || LIB_HW_RAND) + help + Acquire a network IP address using the link-local protocol + +config CMD_NCSI + bool "ncsi" + depends on PHY_NCSI + help + Manually configure the attached NIC via NC-SI. + Normally this happens automatically before other network + operations. + +config IPV6_ROUTER_DISCOVERY + bool "Do IPv6 router discovery" + depends on IPV6 + help + Will automatically perform router solicitation on first IPv6 + network operation + +config CMD_ETHSW + bool "ethsw" + help + Allow control of L2 Ethernet switch commands. These are supported + by the vsc9953 Ethernet driver at present. Sub-commands allow + operations such as enabling / disabling a port and + viewing/maintaining the filtering database (FDB) + +config CMD_WOL + bool "wol" + help + Wait for wake-on-lan Magic Packet + +endif # if NET + +config CMD_DHCP + bool "dhcp" + select PROT_DHCP_LWIP if NET_LWIP + help + Boot image via network using DHCP/TFTP protocol + +config CMD_DNS + bool "dns" + select DNS + help + Lookup the IP of a hostname + +config CMD_MII + bool "mii" + imply CMD_MDIO + help + If set, allows 802.3(clause 22) MII Management functions interface access + The management interface specified in Clause 22 provides + a simple, two signal, serial interface to connect a + Station Management entity and a managed PHY for providing access + to management parameters and services. + The interface is referred to as the MII management interface. + +config MII_INIT + bool "Call mii_init() in the mii command" + depends on CMD_MII && (MPC8XX_FEC || FSLDMAFE || MCFFEC) + +config CMD_MDIO + bool "mdio" + depends on PHYLIB + help + If set, allows Enable 802.3(clause 45) MDIO interface registers access + The MDIO interface is orthogonal to the MII interface and extends + it by adding access to more registers through indirect addressing. + +config CMD_PING + bool "ping" + select PROT_RAW_LWIP if NET_LWIP + help + Send ICMP ECHO_REQUEST to network host + +config CMD_SNTP + bool "sntp" + select PROT_UDP if NET + select PROT_UDP_LWIP if NET_LWIP + help + Synchronize RTC via network + +config CMD_TFTPBOOT + bool "tftp" + select PROT_UDP_LWIP if NET_LWIP + default y + help + tftpboot - load file via network using TFTP protocol + +config CMD_WGET + bool "wget" + default y if SANDBOX || ARCH_QEMU + select WGET + help + wget is a simple command to download kernel, or other files, + from a http server over TCP. + +config WGET_HTTPS + bool "wget https" + depends on CMD_WGET + depends on PROT_TCP_LWIP + depends on MBEDTLS_LIB + depends on DM_RNG + select SHA256 + select RSA + select ASYMMETRIC_KEY_TYPE + select ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select X509_CERTIFICATE_PARSER + select PKCS7_MESSAGE_PARSER + select MBEDTLS_LIB_TLS + select RSA_VERIFY_WITH_PKEY + select X509_CERTIFICATE_PARSER + select PKCS7_MESSAGE_PARSER + help + Enable TLS over http for wget. + +config WGET_CACERT + bool "wget cacert" + depends on CMD_WGET + depends on WGET_HTTPS + help + Adds the "cacert" sub-command to wget to provide root certificates + to the HTTPS engine. Must be in DER format. + +config WGET_BUILTIN_CACERT + bool "Built-in CA certificates" + depends on WGET_HTTPS + select BUILD_BIN2C + +config WGET_BUILTIN_CACERT_PATH + string "Path to root certificates" + depends on WGET_BUILTIN_CACERT + default "cacert.crt" + help + Set this to the path to a DER-encoded X509 file containing + Certification Authority certificates, a.k.a. root certificates, for + the purpose of authenticating HTTPS connections. + +config CMD_PXE + bool "pxe" + select PXE_UTILS + imply CMD_TFTPBOOT + help + Boot image via network using PXE protocol + +endif # if CMD_NET + +endif # NET || NET_LWIP + +menu "Misc commands" + +config CMD_2048 + bool "Play 2048" + help + This is a simple sliding block puzzle game designed by Italian web + developer Gabriele Cirulli. The game's objective is to slide numbered + tiles on a grid to combine them to create a tile with the number + 2048. + + This needs ANSI support on your terminal to work. It is not fully + functional on a video device. + +config CMD_BMP + bool "Enable 'bmp' command" + depends on VIDEO + select BMP + help + This provides a way to obtain information about a BMP-format image + and to display it. BMP (which presumably stands for BitMaP) is a + file format defined by Microsoft which supports images of various + depths, formats and compression methods. Headers on the file + determine the formats used. This command can be used by first loading + the image into RAM, then using this command to look at it or display + it. + +config CMD_BOOTCOUNT + bool "bootcount" + depends on BOOTCOUNT_LIMIT + help + Enable the bootcount command, which allows interrogation and + reset of the bootcounter. + +config CMD_BSP + bool "Enable board-specific commands" + help + (deprecated: instead, please define a Kconfig option for each command) + + Some boards have board-specific commands which are only enabled + during developemnt and need to be turned off for production. This + option provides a way to control this. The commands that are enabled + vary depending on the board. + +config CMD_BLOCK_CACHE + bool "blkcache - control and stats for block cache" + depends on BLOCK_CACHE + default y if BLOCK_CACHE + help + Enable the blkcache command, which can be used to control the + operation of the cache functions. + This is most useful when fine-tuning the operation of the cache + during development, but also allows the cache to be disabled when + it might hurt performance (e.g. when using the ums command). + +config CMD_BLKMAP + bool "blkmap - Composable virtual block devices" + depends on BLKMAP + default y if BLKMAP + help + Create virtual block devices that are backed by various sources, + e.g. RAM, or parts of an existing block device. Though much more + rudimentary, it borrows a lot of ideas from Linux's device mapper + subsystem. + + Example use-cases: + - Treat a region of RAM as a block device, i.e. a RAM disk. This let's + you extract files from filesystem images stored in RAM (perhaps as a + result of a TFTP transfer). + - Create a virtual partition on an existing device. This let's you + access filesystems that aren't stored at an exact partition + boundary. A common example is a filesystem image embedded in an FIT + image. + +config CMD_BUTTON + bool "button" + depends on BUTTON + default y if BUTTON + help + Enable the 'button' command which allows to get the status of + buttons supported by the board. The buttonss can be listed with + 'button list' and state can be known with 'button <label>'. + Any button drivers can be controlled with this command, e.g. + button_gpio. + +config CMD_CACHE + bool "icache or dcache" + help + Enable the "icache" and "dcache" commands + +config CMD_CONITRACE + bool "conitrace - trace console input codes" + help + Enable the 'conitrace' command which displays the codes received + from the console input as hexadecimal numbers. + +config CMD_CLS + bool "Enable clear screen command 'cls'" + default y if LCD || VIDEO + help + Enable the 'cls' command which clears the screen contents + on video frame buffer. + +config CMD_EFIDEBUG + bool "efidebug - display/configure UEFI environment" + depends on EFI_LOADER + select EFI_DEVICE_PATH_TO_TEXT + help + Enable the 'efidebug' command which provides a subset of UEFI + shell utility with simplified functionality. It will be useful + particularly for managing boot parameters as well as examining + various EFI status for debugging. + +config CMD_EFICONFIG + bool "eficonfig - provide menu-driven uefi variables maintenance interface" + default y if !HAS_BOARD_SIZE_LIMIT + depends on EFI_BOOTMGR + select MENU + help + Enable the 'eficonfig' command which provides the menu-driven UEFI + variable maintenance interface. + +config CMD_EXCEPTION + bool "exception - raise exception" + depends on ARM || RISCV || SANDBOX || X86 + help + Enable the 'exception' command which allows to raise an exception. + +config CMD_LED + bool "led" + depends on LED + default y if LED + help + Enable the 'led' command which allows for control of LEDs supported + by the board. The LEDs can be listed with 'led list' and controlled + with led on/off/togle/blink. Any LED drivers can be controlled with + this command, e.g. led_gpio. + +config CMD_INI + bool "ini" + help + Enable the 'ini' command which allows a .ini file to be parsed and + placed into environment variables. Please check the source code for + this as there is no documentation. + +config CMD_DATE + bool "date" + default y if DM_RTC + select LIB_DATE + help + Enable the 'date' command for getting/setting the time/date in RTC + devices. + +config CMD_RTC + bool "rtc" + depends on DM_RTC + default y if X86 + help + Enable the 'rtc' command for low-level access to RTC devices. + +config CMD_TIME + bool "time" + help + Run commands and summarize execution time. + +config CMD_GETTIME + bool "gettime - read elapsed time" + help + Enable the 'gettime' command which reads the elapsed time since + U-Boot started running. This shows the time in seconds and + milliseconds. See also the 'bootstage' command which provides more + flexibility for boot timing. + +config CMD_PAUSE + bool "pause command" + help + Delay execution waiting for any user input. + Useful to allow the user to read a failure log. + +config CMD_RNG + bool "rng command" + depends on DM_RNG + default y if SANDBOX + select HEXDUMP + help + Print bytes from the hardware random number generator. + +config CMD_KASLRSEED + bool "kaslrseed" + depends on DM_RNG + help + Set the kaslr-seed in the chosen node with entropy provided by a + hardware random number generator. + +config CMD_SLEEP + bool "sleep" + default y + help + Delay execution for some time + +config CMD_MP + bool "support for multiprocessor commands" + depends on MP && !CPU + default y + help + This enables commands to bringup different processors + in multiprocessor cases. + +config CMD_TIMER + bool "timer" + help + Access the system timer. + +config CMD_SOUND + bool "sound" + depends on SOUND + help + This provides basic access to the U-Boot's sound support. The main + feature is to play a beep. + + sound init - set up sound system + sound play - play a sound + +config CMD_SYSBOOT + bool "sysboot" + select PXE_UTILS + help + Boot image via local extlinux.conf file + +config CMD_QFW + bool "qfw" + select QFW + default y if TARGET_QEMU_ARM_32BIT || TARGET_QEMU_ARM_64BIT || \ + TARGET_QEMU_X86 || TARGET_QEMU_X86_64 + help + This provides access to the QEMU firmware interface. The main + feature is to allow easy loading of files passed to qemu-system + via -kernel / -initrd + +config CMD_PSTORE + bool "pstore" + help + This provides access to Linux PStore with Rammoops backend. The main + feature is to allow to display or save PStore records. + + See doc/pstore.rst for more information. + +if CMD_PSTORE + +config CMD_PSTORE_MEM_ADDR + hex "Memory Address" + depends on CMD_PSTORE + help + Base addr used for PStore ramoops memory, should be identical to + ramoops.mem_address parameter used by kernel + +config CMD_PSTORE_MEM_SIZE + hex "Memory size" + depends on CMD_PSTORE + default "0x10000" + help + Size of PStore ramoops memory, should be identical to ramoops.mem_size + parameter used by kernel, a power of 2 and larger than the sum of the + record sizes + +config CMD_PSTORE_RECORD_SIZE + hex "Dump record size" + depends on CMD_PSTORE + default "0x1000" + help + Size of each dump done on oops/panic, should be identical to + ramoops.record_size parameter used by kernel and a power of 2 + Must be non-zero + +config CMD_PSTORE_CONSOLE_SIZE + hex "Kernel console log size" + depends on CMD_PSTORE + default "0x1000" + help + Size of kernel console log, should be identical to + ramoops.console_size parameter used by kernel and a power of 2 + Must be non-zero + +config CMD_PSTORE_FTRACE_SIZE + hex "FTrace log size" + depends on CMD_PSTORE + default "0x1000" + help + Size of ftrace log, should be identical to ramoops.ftrace_size + parameter used by kernel and a power of 2 + +config CMD_PSTORE_PMSG_SIZE + hex "User space message log size" + depends on CMD_PSTORE + default "0x1000" + help + Size of user space message log, should be identical to + ramoops.pmsg_size parameter used by kernel and a power of 2 + +config CMD_PSTORE_ECC_SIZE + int "ECC size" + depends on CMD_PSTORE + default "0" + help + if non-zero, the option enables ECC support and specifies ECC buffer + size in bytes (1 is a special value, means 16 bytes ECC), should be + identical to ramoops.ramoops_ecc parameter used by kernel + +endif + +source "cmd/mvebu/Kconfig" + +config CMD_TERMINAL + bool "terminal - provides a way to attach a serial terminal" + help + Provides a 'cu'-like serial terminal command. This can be used to + access other serial ports from the system console. The terminal + is very simple with no special processing of characters. As with + cu, you can press ~. (tilde followed by period) to exit. + +config CMD_UUID + bool "uuid, guid - generation of unique IDs" + select LIB_UUID + help + This enables two commands: + + uuid - generate random Universally Unique Identifier + guid - generate Globally Unique Identifier based on random UUID + + The two commands are very similar except for the endianness of the + output. + +config CMD_VIDCONSOLE + bool "lcdputs and setcurs" + depends on VIDEO + default y + help + Enabling this will provide 'setcurs' and 'lcdputs' commands which + support cursor positioning and drawing strings on the video + console (framebuffer). + + The name 'lcdputs' is a bit of a misnomer, but so named because the + video device is often an LCD. + +config CMD_SELECT_FONT + bool "select font size" + depends on VIDEO + default y if CONSOLE_TRUETYPE + help + Enabling this will provide 'font' command. + Allows font selection at runtime. + +endmenu + +source "cmd/ti/Kconfig" + +config CMD_BOOTSTAGE + bool "Enable the 'bootstage' command" + depends on BOOTSTAGE + help + Add a 'bootstage' command which supports printing a report + and un/stashing of bootstage data. + +menu "Power commands" +config CMD_PMIC + bool "Enable Driver Model PMIC command" + depends on DM_PMIC + help + This is the pmic command, based on a driver model pmic's API. + Command features are unchanged: + - list - list pmic devices + - pmic dev <id> - show or [set] operating pmic device (NEW) + - pmic dump - dump registers + - pmic read address - read byte of register at address + - pmic write address - write byte to register at address + The only one change for this command is 'dev' subcommand. + +config CMD_REGULATOR + bool "Enable Driver Model REGULATOR command" + depends on DM_REGULATOR + help + This command is based on driver model regulator's API. + User interface features: + - list - list regulator devices + - regulator dev <id> - show or [set] operating regulator device + - regulator info - print constraints info + - regulator status - print operating status + - regulator value <val] <-f> - print/[set] voltage value [uV] + - regulator current <val> - print/[set] current value [uA] + - regulator mode <id> - print/[set] operating mode id + - regulator enable - enable the regulator output + - regulator disable - disable the regulator output + + The '-f' (force) option can be used for set the value which exceeds + the limits, which are found in device-tree and are kept in regulator's + uclass plat structure. + +endmenu + +menu "Security commands" +config CMD_AES + bool "Enable the 'aes' command" + select AES + help + This provides a means to encrypt and decrypt data using the AES + (Advanced Encryption Standard). This algorithm uses a symetric key + and is widely used as a streaming cipher. Different key lengths are + supported by the algorithm but this command only supports 128 bits + at present. + +config CMD_BLOB + bool "Enable the 'blob' command" + depends on !MX6ULL && !MX6SLL && !MX6SL + depends on SYS_FSL_SEC_COMPAT >= 4 + select IMX_HAB if ARCH_MX6 || ARCH_MX7 || ARCH_MX7ULP || ARCH_IMX8M + help + This is used with the Freescale secure boot mechanism. + + Freescale's SEC block has built-in Blob Protocol which provides + a method for protecting user-defined data across system power + cycles. SEC block protects data in a data structure called a Blob, + which provides both confidentiality and integrity protection. + + Encapsulating data as a blob + Each time that the Blob Protocol is used to protect data, a + different randomly generated key is used to encrypt the data. + This random key is itself encrypted using a key which is derived + from SoC's non-volatile secret key and a 16 bit Key identifier. + The resulting encrypted key along with encrypted data is called a + blob. The non-volatile secure key is available for use only during + secure boot. + + During decapsulation, the reverse process is performed to get back + the original data. + + Sub-commands: + blob enc - encapsulating data as a cryptgraphic blob + blob dec - decapsulating cryptgraphic blob to get the data + + Syntax: + + blob enc src dst len km + + Encapsulate and create blob of data $len bytes long + at address $src and store the result at address $dst. + $km is the 16 byte key modifier is also required for + generation/use as key for cryptographic operation. Key + modifier should be 16 byte long. + + blob dec src dst len km + + Decapsulate the blob of data at address $src and + store result of $len byte at addr $dst. + $km is the 16 byte key modifier is also required for + generation/use as key for cryptographic operation. Key + modifier should be 16 byte long. + +config CMD_HASH + bool "Support 'hash' command" + select HASH + help + This provides a way to hash data in memory using various supported + algorithms (such as SHA1, MD5, CRC32). The computed digest can be + saved to memory or to an environment variable. It is also possible + to verify a hash against data in memory. + +config CMD_HVC + bool "Support the 'hvc' command" + depends on ARM_SMCCC + help + Allows issuing Hypervisor Calls (HVCs). Mostly useful for + development and testing. + +config CMD_SMC + bool "Support the 'smc' command" + depends on ARM_SMCCC + help + Allows issuing Secure Monitor Calls (SMCs). Mostly useful for + development and testing. + +config HASH_VERIFY + bool "hash -v" + depends on CMD_HASH + help + Add -v option to verify data against a hash. + +config CMD_SCP03 + bool "scp03 - SCP03 enable and rotate/provision operations" + depends on SCP03 + help + This command provides access to a Trusted Application + running in a TEE to request Secure Channel Protocol 03 + (SCP03) enablement and/or rotation of its SCP03 keys. + +config CMD_TPM_V1 + bool + +config CMD_TPM_V2 + bool + +config CMD_TPM + bool "Enable the 'tpm' command" + depends on TPM_V1 || TPM_V2 + select CMD_TPM_V1 if TPM_V1 + select CMD_TPM_V2 if TPM_V2 + help + This provides a means to talk to a TPM from the command line. A wide + range of commands if provided - see 'tpm help' for details. The + command requires a suitable TPM on your board and the correct driver + must be enabled. + +if CMD_TPM + +config CMD_TPM_TEST + bool "Enable the 'tpm test' command" + depends on TPM_V1 + help + This provides a a series of tests to confirm that the TPMv1.x is + working correctly. The tests cover initialisation, non-volatile RAM, + extend, global lock and checking that timing is within expectations. + The tests pass correctly on Infineon TPMs but may need to be adjusted + for other devices. + +endif + +endmenu + +menu "Firmware commands" +config CMD_CROS_EC + bool "Enable crosec command" + depends on CROS_EC + default y + help + Enable command-line access to the Chrome OS EC (Embedded + Controller). This provides the 'crosec' command which has + a number of sub-commands for performing EC tasks such as + updating its flash, accessing a small saved context area + and talking to the I2C bus behind the EC (if there is one). + +config CMD_SCMI + bool "Enable scmi command" + depends on SCMI_FIRMWARE + help + This command provides user interfaces to several SCMI (System + Control and Management Interface) protocols available on Arm + platforms to manage system resources. +endmenu + +menu "Filesystem commands" +config CMD_BTRFS + bool "Enable the 'btrsubvol' command" + select FS_BTRFS + help + This enables the 'btrsubvol' command to list subvolumes + of a BTRFS filesystem. There are no special commands for + listing BTRFS directories or loading BTRFS files - this + can be done by the generic 'fs' commands (see CMD_FS_GENERIC) + when BTRFS is enabled (see FS_BTRFS). + +config CMD_CBFS + bool "Enable the 'cbfs' command" + depends on FS_CBFS + help + Define this to enable support for reading from a Coreboot + filesystem. This is a ROM-based filesystem used for accessing files + on systems that use coreboot as the first boot-loader and then load + U-Boot to actually boot the Operating System. Available commands are + cbfsinit, cbfsinfo, cbfsls and cbfsload. + +config CMD_CRAMFS + bool "Enable the 'cramfs' command" + depends on FS_CRAMFS + help + This provides commands for dealing with CRAMFS (Compressed ROM + filesystem). CRAMFS is useful when space is tight since files are + compressed. Two commands are provided: + + cramfsls - lists files in a cramfs image + cramfsload - loads a file from a cramfs image + +config CMD_EROFS + bool "EROFS command support" + select FS_EROFS + help + Support for the EROFS fs + +config CMD_EXT2 + bool "ext2 command support" + select FS_EXT4 + help + Enables EXT2 FS command + +config CMD_EXT4 + bool "ext4 command support" + select FS_EXT4 + help + Enables EXT4 FS command + +config CMD_EXT4_WRITE + depends on CMD_EXT4 + bool "ext4 write command support" + select EXT4_WRITE + help + Enables EXT4 FS write command + +config CMD_FAT + bool "FAT command support" + select FS_FAT + help + Support for the FAT fs + +config CMD_SQUASHFS + bool "SquashFS command support" + select FS_SQUASHFS + help + Enables SquashFS filesystem commands (e.g. load, ls). + +config CMD_FS_GENERIC + bool "filesystem commands" + help + Enables filesystem commands (e.g. load, ls) that work for multiple + fs types. + +config CMD_FS_UUID + bool "fsuuid command" + help + Enables fsuuid command for filesystem UUID. + +config CMD_JFFS2 + bool "jffs2 command" + select FS_JFFS2 + help + Enables commands to support the JFFS2 (Journalling Flash File System + version 2) filesystem. This enables fsload, ls and fsinfo which + provide the ability to load files, list directories and obtain + filesystem information. + +config JFFS2_DEV + string "Default device for JFFS2" + depends on CMD_JFFS2 + default "nor0" + help + The default device to use with the jffs2 command. + +config JFFS2_PART_OFFSET + hex "Default offset within flash to locate the JFFS2 image" + depends on CMD_JFFS2 + default 0x0 + help + The default offset within flash to locate the JFFS2 image. + +config JFFS2_PART_SIZE + hex "Default size of JFFS2 partition" + depends on CMD_JFFS2 + default 0xFFFFFFFF + help + The default size of the JFFS2 partition + +config CMD_MTDPARTS + bool "MTD partition support" + depends on !COMPILE_TEST + depends on MTD + select MTD_PARTITIONS + help + MTD partitioning tool support. + It is strongly encouraged to avoid using this command + anymore along with 'sf', 'nand', 'onenand'. One can still + declare the partitions in the mtdparts environment variable + but better use the MTD stack and the 'mtd' command instead. + +config CMD_MTDPARTS_SPREAD + bool "Padd partition size to take account of bad blocks" + depends on CMD_MTDPARTS + help + This enables the 'spread' sub-command of the mtdparts command. + This command will modify the existing mtdparts variable by increasing + the size of the partitions such that 1) each partition's net size is + at least as large as the size specified in the mtdparts variable and + 2) each partition starts on a good block. + +config CMD_MTDPARTS_SHOW_NET_SIZES + bool "Show net size (w/o bad blocks) of partitions" + depends on CMD_MTDPARTS + help + Adds two columns to the printed partition table showing the + effective usable size of a partition, if bad blocks are taken + into account. + +config MTDIDS_DEFAULT + string "Default MTD IDs" + depends on MTD || SPI_FLASH + depends on !SYS_MTDPARTS_RUNTIME + help + Defines a default MTD IDs list for use with MTD partitions in the + Linux MTD command line partitions format. + +config MTDPARTS_DEFAULT + string "Default MTD partition scheme" + depends on MTD || SPI_FLASH + depends on !SYS_MTDPARTS_RUNTIME + help + Defines a default MTD partitioning scheme in the Linux MTD command + line partitions format + +config CMD_ZFS + bool "zfs - Access of ZFS filesystem" + help + This provides commands to accessing a ZFS filesystem, commonly used + on Solaris systems. Two sub-commands are provided: + + zfsls - list files in a directory + zfsload - load a file + + See doc/README.zfs for more details. + +endmenu + +menu "Debug commands" + +config CMD_CBSYSINFO + bool "cbsysinfo" + depends on X86 + default y if SYS_COREBOOT + help + This provides information about the coreboot sysinfo table stored in + memory by coreboot before jumping to U-Boot. It can be useful for + debugging the beaaviour of coreboot or U-Boot. + +config CMD_CBCMOS + bool "cbcmos" + depends on X86 + default y if SYS_COREBOOT + help + This provides information options to check the CMOS RAM checksum, + if present, as well as to update it. + + It is useful when coreboot CMOS-RAM settings must be examined or + updated. + +config CMD_CYCLIC + bool "cyclic - Show information about cyclic functions" + depends on CYCLIC + default y + help + This enables the 'cyclic' command which provides information about + cyclic execution functions. This infrastructure allows registering + functions to be executed cyclically, e.g. every 100ms. These commands + are supported: + + cyclic list - list cyclic functions + cyclic cyclic demo <cycletime_ms> <delay_us> - register cyclic + demo function + + See doc/develop/cyclic.rst for more details. + +config CMD_DIAG + bool "diag - Board diagnostics" + help + This command provides access to board diagnostic tests. These are + called Power-on Self Tests (POST). The command allows listing of + available tests and running either all the tests, or specific tests + identified by name. + +config CMD_EVENT + bool "event - Show information about events" + depends on EVENT + default y if EVENT_DEBUG + help + This enables the 'event' command which provides information about + events and event-handler routines. This can help to device event + hadling. + +config CMD_IRQ + bool "irq - Show information about interrupts" + depends on NIOS2 || PPC || X86 + help + This enables two commands: + + interrupts - enable or disable interrupts + irqinfo - print device-specific interrupt information + +config CMD_KGDB + bool "kgdb - Allow debugging of U-Boot with gdb" + depends on PPC + help + This enables a 'kgdb' command which allows gdb to connect to U-Boot + over a serial link for debugging purposes. This allows + single-stepping, inspecting variables, etc. This is supported only + on PowerPC at present. + +config CMD_LOG + bool "log - Generation, control and access to logging" + select LOG + select GETOPT + help + This provides access to logging features. It allows the output of + log data to be controlled to a limited extent (setting up the default + maximum log level for emitting of records). It also provides access + to a command used for testing the log system. + +config CMD_TRACE + bool "trace - Support tracing of function calls and timing" + depends on TRACE + default y + help + Enables a command to control using of function tracing within + U-Boot. This allows recording of call traces including timing + information. The command can write data to memory for exporting + for analysis (e.g. using bootchart). See doc/develop/trace.rst + for full details. + +config CMD_AVB + bool "avb - Android Verified Boot 2.0 operations" + depends on AVB_VERIFY + help + Enables a "avb" command to perform verification of partitions using + Android Verified Boot 2.0 functionality. It includes such subcommands: + avb init - initialize avb2 subsystem + avb read_rb - read rollback index + avb write_rb - write rollback index + avb is_unlocked - check device lock state + avb get_uuid - read and print uuid of a partition + avb read_part - read data from partition + avb read_part_hex - read data from partition and output to stdout + avb write_part - write data to partition + avb verify - run full verification chain + +config CMD_STACKPROTECTOR_TEST + bool "Test command for stack protector" + depends on STACKPROTECTOR + help + Enable stackprot_test command + The stackprot_test command will force a stack overrun to test + the stack smashing detection mechanisms. + +endmenu + +config CMD_UBI + tristate "Enable UBI - Unsorted block images commands" + select MTD_UBI + help + UBI is a software layer above MTD layer which admits use of LVM-like + logical volumes on top of MTD devices, hides some complexities of + flash chips like wear and bad blocks and provides some other useful + capabilities. Please, consult the MTD web site for more details + (www.linux-mtd.infradead.org). Activate this option if you want + to use U-Boot UBI commands. + It is also strongly encouraged to also enable CONFIG_MTD to get full + partition support. + +config CMD_UBI_RENAME + bool "Enable rename" + depends on CMD_UBI + help + Enable a "ubi" command to rename ubi volume: + ubi rename <oldname> <newname> + +config CMD_UBIFS + tristate "Enable UBIFS - Unsorted block images filesystem commands" + depends on CMD_UBI + default y if CMD_UBI + select LZO + select GZIP + help + UBIFS is a file system for flash devices which works on top of UBI. + +config CMD_MESON + bool "Amlogic Meson commands" + depends on ARCH_MESON + default y + help + Enable useful commands for the Meson Soc family developed by Amlogic Inc. + +config CMD_SPAWN + bool "spawn and wait commands" + depends on UTHREAD + help + spawn runs a command in the background and sets the job_id environment + variable. wait is used to suspend the shell execution until one or more + jobs are complete. + +config CMD_SPAWN_NUM_JOBS + int "Maximum number of simultaneous jobs for spawn" + default 16 + help + Job identifiers are in the range 1..CMD_SPAWN_NUM_JOBS. In other words + there can be no more that CMD_SPAWN_NUM_JOBS running simultaneously. + When a jobs exits, its identifier is available to be re-used by the next + spawn command. + +endif diff --git a/cmd/Makefile b/cmd/Makefile new file mode 100644 index 00000000000..25479907797 --- /dev/null +++ b/cmd/Makefile @@ -0,0 +1,301 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2004-2006 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. + +ifndef CONFIG_XPL_BUILD +# core command +obj-y += boot.o +obj-$(CONFIG_CMD_BOOTM) += bootm.o +obj-$(CONFIG_CMD_HELP) += help.o +obj-y += panic.o +obj-y += version.o + +# command +obj-$(CONFIG_CMD_ARMFFA) += armffa.o +obj-$(CONFIG_CMD_2048) += 2048.o +obj-$(CONFIG_CMD_ACPI) += acpi.o +obj-$(CONFIG_CMD_ADDRMAP) += addrmap.o +obj-$(CONFIG_CMD_AES) += aes.o +obj-$(CONFIG_CMD_ADC) += adc.o +obj-$(CONFIG_CMD_ARMFLASH) += armflash.o +obj-$(CONFIG_BLK) += blk_common.o +obj-$(CONFIG_CMD_BOOTDEV) += bootdev.o +obj-$(CONFIG_CMD_BOOTFLOW) += bootflow.o +obj-$(CONFIG_CMD_BOOTMETH) += bootmeth.o +obj-$(CONFIG_CMD_BOOTSTD) += bootstd.o +obj-$(CONFIG_CMD_SOURCE) += source.o +obj-$(CONFIG_CMD_BCB) += bcb.o +obj-$(CONFIG_CMD_BDI) += bdinfo.o +obj-$(CONFIG_CMD_BIND) += bind.o +obj-$(CONFIG_CMD_BINOP) += binop.o +obj-$(CONFIG_CMD_BLKMAP) += blkmap.o +obj-$(CONFIG_CMD_BLOBLIST) += bloblist.o +obj-$(CONFIG_CMD_BLOCK_CACHE) += blkcache.o +obj-$(CONFIG_CMD_BMP) += bmp.o +obj-$(CONFIG_CMD_BOOTCOUNT) += bootcount.o +obj-$(CONFIG_CMD_BOOTEFI) += bootefi.o +obj-$(CONFIG_CMD_BOOTMENU) += bootmenu.o +obj-$(CONFIG_CMD_BOOTSTAGE) += bootstage.o +obj-$(CONFIG_CMD_BOOTZ) += bootz.o +obj-$(CONFIG_CMD_BOOTI) += booti.o +obj-$(CONFIG_CMD_BTRFS) += btrfs.o +obj-$(CONFIG_CMD_BUTTON) += button.o +obj-$(CONFIG_CMD_C5_PL330_DMA) += c5_pl330_dma.o +obj-$(CONFIG_CMD_CAT) += cat.o +obj-$(CONFIG_CMD_CACHE) += cache.o +obj-$(CONFIG_CMD_CBFS) += cbfs.o +obj-$(CONFIG_CMD_CEDIT) += cedit.o +obj-$(CONFIG_CMD_CLK) += clk.o +obj-$(CONFIG_CMD_CLS) += cls.o +obj-$(CONFIG_CMD_CONFIG) += config.o +obj-$(CONFIG_CMD_CONITRACE) += conitrace.o +obj-$(CONFIG_CMD_CONSOLE) += console.o +obj-$(CONFIG_CMD_CPU) += cpu.o +obj-$(CONFIG_CMD_DATE) += date.o +obj-$(CONFIG_CMD_DEMO) += demo.o +obj-$(CONFIG_CMD_DM) += dm.o +obj-$(CONFIG_CMD_UFETCH) += ufetch.o +obj-$(CONFIG_CMD_SOUND) += sound.o +ifdef CONFIG_POST +obj-$(CONFIG_CMD_DIAG) += diag.o +endif +obj-$(CONFIG_CMD_ADTIMG) += adtimg.o +obj-$(CONFIG_CMD_ABOOTIMG) += abootimg.o +obj-$(CONFIG_CMD_CYCLIC) += cyclic.o +obj-$(CONFIG_CMD_EVENT) += event.o +obj-$(CONFIG_CMD_EXTENSION) += extension_board.o +obj-$(CONFIG_CMD_ECHO) += echo.o +obj-$(CONFIG_ENV_IS_IN_EEPROM) += eeprom.o +obj-$(CONFIG_CMD_EEPROM) += eeprom.o +obj-$(CONFIG_EFI_CLIENT) += efi.o efi_common.o +obj-$(CONFIG_CMD_EFIDEBUG) += efidebug.o efi_common.o +obj-$(CONFIG_CMD_EFICONFIG) += eficonfig.o +ifdef CONFIG_CMD_EFICONFIG +ifdef CONFIG_EFI_MM_COMM_TEE +obj-$(CONFIG_EFI_SECURE_BOOT) += eficonfig_sbkey.o +endif +endif +obj-$(CONFIG_CMD_ELF) += elf.o +obj-$(CONFIG_CMD_EROFS) += erofs.o +obj-$(CONFIG_HUSH_PARSER) += exit.o +obj-$(CONFIG_CMD_EXT4) += ext4.o +obj-$(CONFIG_CMD_EXT2) += ext2.o +obj-$(CONFIG_CMD_FAT) += fat.o +obj-$(CONFIG_CMD_FDT) += fdt.o +obj-$(CONFIG_CMD_SQUASHFS) += sqfs.o +obj-$(CONFIG_CMD_SELECT_FONT) += font.o +obj-$(CONFIG_CMD_FLASH) += flash.o +obj-$(CONFIG_CMD_FPGA) += fpga.o +obj-$(CONFIG_CMD_FPGAD) += fpgad.o +obj-$(CONFIG_CMD_FS_GENERIC) += fs.o +obj-$(CONFIG_CMD_FUSE) += fuse.o +obj-$(CONFIG_CMD_FWU_METADATA) += fwu_mdata.o +obj-$(CONFIG_CMD_GETTIME) += gettime.o +obj-$(CONFIG_CMD_GPIO) += gpio.o +obj-$(CONFIG_CMD_HISTORY) += history.o +obj-$(CONFIG_CMD_HVC) += smccc.o +obj-$(CONFIG_CMD_I2C) += i2c.o +obj-$(CONFIG_CMD_I3C) += i3c.o +obj-$(CONFIG_CMD_IOTRACE) += iotrace.o +obj-$(CONFIG_CMD_HASH) += hash.o +obj-$(CONFIG_CMD_IDE) += ide.o disk.o +obj-$(CONFIG_CMD_INI) += ini.o +obj-$(CONFIG_CMD_IRQ) += irq.o +obj-$(CONFIG_CMD_ITEST) += itest.o +obj-$(CONFIG_CMD_JFFS2) += jffs2.o +obj-$(CONFIG_CMD_CRAMFS) += cramfs.o +obj-$(CONFIG_LED_STATUS_CMD) += legacy_led.o +obj-$(CONFIG_CMD_LED) += led.o +obj-$(CONFIG_CMD_LICENSE) += license.o +obj-y += load.o +obj-$(CONFIG_CMD_LOG) += log.o +obj-$(CONFIG_CMD_LSBLK) += lsblk.o +obj-$(CONFIG_CMD_MD5SUM) += md5sum.o +obj-$(CONFIG_CMD_MEMORY) += mem.o +obj-$(CONFIG_CMD_MEMINFO) += meminfo.o +obj-$(CONFIG_CMD_IO) += io.o +obj-$(CONFIG_CMD_MII) += mii.o +obj-$(CONFIG_CMD_MISC) += misc.o +obj-$(CONFIG_CMD_MDIO) += mdio.o +obj-$(CONFIG_CMD_PAUSE) += pause.o +obj-$(CONFIG_CMD_SLEEP) += sleep.o +obj-$(CONFIG_CMD_MMC) += mmc.o +obj-$(CONFIG_CMD_OPTEE_RPMB) += optee_rpmb.o +obj-$(CONFIG_CMD_OPTEE) += optee.o +obj-$(CONFIG_CMD_MP) += mp.o +obj-$(CONFIG_CMD_MTD) += mtd.o +obj-$(CONFIG_CMD_MTDPARTS) += mtdparts.o +obj-$(CONFIG_CMD_CLONE) += clone.o +ifneq ($(CONFIG_CMD_NAND)$(CONFIG_CMD_SF),) +obj-y += legacy-mtd-utils.o +endif +obj-$(CONFIG_CMD_MUX) += mux.o +obj-$(CONFIG_CMD_NAND) += nand.o +ifdef CONFIG_NET +obj-$(CONFIG_CMD_NET) += net.o net-common.o +else ifdef CONFIG_NET_LWIP +obj-$(CONFIG_CMD_NET) += net-common.o +obj-y += lwip/ +endif +obj-$(CONFIG_ENV_SUPPORT) += nvedit.o +obj-$(CONFIG_CMD_NVEDIT_EFI) += nvedit_efi.o +obj-$(CONFIG_CMD_ONENAND) += onenand.o +obj-$(CONFIG_CMD_OSD) += osd.o +obj-$(CONFIG_CMD_PART) += part.o +obj-$(CONFIG_CMD_PCAP) += pcap.o +ifdef CONFIG_PCI +obj-$(CONFIG_CMD_PCI) += pci.o +obj-$(CONFIG_CMD_PCI_MPS) += pci_mps.o +endif +obj-$(CONFIG_CMD_PINMUX) += pinmux.o +obj-$(CONFIG_CMD_PMC) += pmc.o +obj-$(CONFIG_CMD_PSTORE) += pstore.o +obj-$(CONFIG_CMD_PWM) += pwm.o +obj-$(CONFIG_CMD_PXE) += pxe.o +obj-$(CONFIG_CMD_WOL) += wol.o +obj-$(CONFIG_CMD_QFW) += qfw.o +obj-$(CONFIG_CMD_READ) += read.o +obj-$(CONFIG_CMD_WRITE) += read.o +obj-$(CONFIG_CMD_REGINFO) += reginfo.o +obj-$(CONFIG_CMD_REMOTEPROC) += remoteproc.o +obj-$(CONFIG_CMD_RNG) += rng.o +obj-$(CONFIG_CMD_KASLRSEED) += kaslrseed.o +obj-$(CONFIG_CMD_RKMTD) += rkmtd.o +obj-$(CONFIG_CMD_ROCKUSB) += rockusb.o +obj-$(CONFIG_CMD_RTC) += rtc.o +obj-$(CONFIG_SANDBOX) += host.o +obj-$(CONFIG_CMD_SATA) += sata.o +obj-$(CONFIG_CMD_NVME) += nvme.o +obj-$(CONFIG_SANDBOX) += sb.o +obj-$(CONFIG_CMD_SF) += sf.o +obj-$(CONFIG_CMD_SCMI) += scmi.o +obj-$(CONFIG_CMD_SCSI) += scsi.o disk.o +obj-$(CONFIG_CMD_SHA1SUM) += sha1sum.o +obj-$(CONFIG_CMD_SEAMA) += seama.o +obj-$(CONFIG_CMD_SETEXPR) += setexpr.o +obj-$(CONFIG_CMD_SETEXPR_FMT) += printf.o +obj-$(CONFIG_CMD_SPI) += spi.o +obj-$(CONFIG_CMD_STRINGS) += strings.o +obj-$(CONFIG_CMD_SMBIOS) += smbios.o +obj-$(CONFIG_CMD_SMC) += smccc.o +obj-$(CONFIG_CMD_SYSBOOT) += sysboot.o +obj-$(CONFIG_CMD_STACKPROTECTOR_TEST) += stackprot_test.o +obj-$(CONFIG_CMD_TCPM) += tcpm.o +obj-$(CONFIG_CMD_TEMPERATURE) += temperature.o +obj-$(CONFIG_CMD_TERMINAL) += terminal.o +obj-$(CONFIG_CMD_TIME) += time.o +obj-$(CONFIG_CMD_TIMER) += timer.o +obj-$(CONFIG_CMD_TRACE) += trace.o +obj-$(CONFIG_HUSH_PARSER) += test.o +obj-$(CONFIG_CMD_TPM) += tpm-common.o +obj-$(CONFIG_CMD_TPM_V1) += tpm-v1.o +obj-$(CONFIG_CMD_TPM_TEST) += tpm_test.o +obj-$(CONFIG_CMD_TPM_V2) += tpm-v2.o +obj-$(CONFIG_CMD_CROS_EC) += cros_ec.o +obj-$(CONFIG_CMD_UBI) += ubi.o +obj-$(CONFIG_CMD_UBIFS) += ubifs.o +obj-$(CONFIG_CMD_UNLZ4) += unlz4.o +obj-$(CONFIG_CMD_UNZIP) += unzip.o +obj-$(CONFIG_CMD_UPL) += upl.o +obj-$(CONFIG_CMD_VIRTIO) += virtio.o +obj-$(CONFIG_CMD_WDT) += wdt.o +obj-$(CONFIG_CMD_LZMADEC) += lzmadec.o +obj-$(CONFIG_CMD_UFS) += ufs.o +obj-$(CONFIG_CMD_USB) += usb.o disk.o +obj-$(CONFIG_CMD_VIDCONSOLE) += video.o + +obj-$(CONFIG_CMD_FASTBOOT) += fastboot.o +obj-$(CONFIG_CMD_FS_UUID) += fs_uuid.o + +obj-$(CONFIG_CMD_USB_MASS_STORAGE) += usb_mass_storage.o +obj-$(CONFIG_CMD_USB_SDP) += usb_gadget_sdp.o +obj-$(CONFIG_CMD_THOR_DOWNLOAD) += thordown.o +obj-$(CONFIG_CMD_VBE) += vbe.o +obj-$(CONFIG_CMD_XIMG) += ximg.o +obj-$(CONFIG_CMD_XXD) += xxd.o +obj-$(CONFIG_CMD_SPL) += spl.o +obj-$(CONFIG_CMD_W1) += w1.o +obj-$(CONFIG_CMD_ZIP) += zip.o +obj-$(CONFIG_CMD_ZFS) += zfs.o + +obj-$(CONFIG_CMD_DFU) += dfu.o +obj-$(CONFIG_CMD_GPT) += gpt.o +obj-$(CONFIG_CMD_MBR) += mbr.o +obj-$(CONFIG_CMD_ETHSW) += ethsw.o +obj-$(CONFIG_CMD_AXI) += axi.o +obj-$(CONFIG_CMD_PVBLOCK) += pvblock.o + +# Power +obj-$(CONFIG_CMD_PMIC) += pmic.o +obj-$(CONFIG_CMD_REGULATOR) += regulator.o + +obj-$(CONFIG_CMD_BLOB) += blob.o + +# Android Verified Boot 2.0 +obj-$(CONFIG_CMD_AVB) += avb.o + +# Foundries.IO SCP03 +obj-$(CONFIG_CMD_SCP03) += scp03.o + +obj-$(CONFIG_HUSH_SELECTABLE) += cli.o + +obj-$(CONFIG_CMD_SPAWN) += spawn.o + +obj-$(CONFIG_ARM) += arm/ +obj-$(CONFIG_RISCV) += riscv/ +obj-$(CONFIG_SANDBOX) += sandbox/ +obj-$(CONFIG_X86) += x86/ + +# Meson +obj-$(CONFIG_CMD_MESON) += meson/ + +obj-$(CONFIG_ARCH_MVEBU) += mvebu/ + +# TI +obj-$(CONFIG_ARCH_KEYSTONE) += ti/ +obj-$(CONFIG_ARCH_K3) += ti/ +obj-$(CONFIG_ARCH_OMAP2PLUS) += ti/ +endif # !CONFIG_XPL_BUILD + +obj-$(CONFIG_$(PHASE_)CMD_TLV_EEPROM) += tlv_eeprom.o + +obj-$(CONFIG_CMD_BCM_EXT_UTILS) += broadcom/ + +filechk_data_gz = (echo "static const char data_gz[] ="; cat $< | scripts/bin2c; echo ";") + +filechk_data_size = \ + (echo "static const size_t data_size = "; \ + cat $< | wc -c; echo ";") + +# "config" command +$(obj)/config.o: $(obj)/config_data_gz.h $(obj)/config_data_size.h + +targets += config_data.gz +$(obj)/config_data.gz: $(KCONFIG_CONFIG) FORCE + $(call if_changed,gzip) + +targets += config_data_gz.h +$(obj)/config_data_gz.h: $(obj)/config_data.gz FORCE + $(call filechk,data_gz) + +targets += config_data_size.h +$(obj)/config_data_size.h: $(KCONFIG_CONFIG) FORCE + $(call filechk,data_size) + +# "license" command +$(obj)/license.o: $(obj)/license_data_gz.h $(obj)/license_data_size.h + +targets += license_data.gz +$(obj)/license_data.gz: $(srctree)/Licenses/gpl-2.0.txt FORCE + $(call if_changed,gzip) + +targets += license_data_gz.h +$(obj)/license_data_gz.h: $(obj)/license_data.gz FORCE + $(call filechk,data_gz) + +targets += license_data_size.h +$(obj)/license_data_size.h: $(srctree)/Licenses/gpl-2.0.txt FORCE + $(call filechk,data_size) + +CFLAGS_ethsw.o := -Wno-enum-conversion diff --git a/cmd/abootimg.c b/cmd/abootimg.c new file mode 100644 index 00000000000..6fb52153786 --- /dev/null +++ b/cmd/abootimg.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2020 + * Sam Protsenko <joe.skb7@gmail.com> + */ + +#include <android_image.h> +#include <command.h> +#include <env.h> +#include <image.h> +#include <mapmem.h> + +#define abootimg_addr() \ + (_abootimg_addr == -1 ? image_load_addr : _abootimg_addr) + +/* Please use abootimg_addr() macro to obtain the boot image address */ +static ulong _abootimg_addr = -1; +static ulong _ainit_bootimg_addr = -1; +static ulong _avendor_bootimg_addr = -1; + +ulong get_abootimg_addr(void) +{ + return (_abootimg_addr == -1 ? image_load_addr : _abootimg_addr); +} + +void set_abootimg_addr(ulong addr) +{ + _abootimg_addr = addr; +} + +ulong get_ainit_bootimg_addr(void) +{ + return _ainit_bootimg_addr; +} + +ulong get_avendor_bootimg_addr(void) +{ + return _avendor_bootimg_addr; +} + +void set_avendor_bootimg_addr(ulong addr) +{ + _avendor_bootimg_addr = addr; +} + +static int abootimg_get_ver(int argc, char *const argv[]) +{ + const struct andr_boot_img_hdr_v0 *hdr; + int res = CMD_RET_SUCCESS; + + if (argc > 1) + return CMD_RET_USAGE; + + hdr = map_sysmem(abootimg_addr(), sizeof(*hdr)); + if (!is_android_boot_image_header(hdr)) { + printf("Error: Boot Image header is incorrect\n"); + res = CMD_RET_FAILURE; + goto exit; + } + + if (argc == 0) + printf("%u\n", hdr->header_version); + else + env_set_ulong(argv[0], hdr->header_version); + +exit: + unmap_sysmem(hdr); + return res; +} + +static int abootimg_get_recovery_dtbo(int argc, char *const argv[]) +{ + ulong addr; + u32 size; + + if (argc > 2) + return CMD_RET_USAGE; + + if (!android_image_get_dtbo(abootimg_addr(), &addr, &size)) + return CMD_RET_FAILURE; + + if (argc == 0) { + printf("%lx\n", addr); + } else { + env_set_hex(argv[0], addr); + if (argc == 2) + env_set_hex(argv[1], size); + } + + return CMD_RET_SUCCESS; +} + +static int abootimg_get_dtb_load_addr(int argc, char *const argv[]) +{ + if (argc > 1) + return CMD_RET_USAGE; + struct andr_image_data img_data = {0}; + const struct andr_boot_img_hdr_v0 *hdr; + const struct andr_vnd_boot_img_hdr *vhdr = NULL; + + hdr = map_sysmem(abootimg_addr(), sizeof(*hdr)); + if (get_avendor_bootimg_addr() != -1) + vhdr = map_sysmem(get_avendor_bootimg_addr(), sizeof(*vhdr)); + + if (!android_image_get_data(hdr, vhdr, &img_data)) { + if (get_avendor_bootimg_addr() != -1) + unmap_sysmem(vhdr); + unmap_sysmem(hdr); + return CMD_RET_FAILURE; + } + + if (get_avendor_bootimg_addr() != -1) + unmap_sysmem(vhdr); + unmap_sysmem(hdr); + + if (img_data.header_version < 2) { + printf("Error: header_version must be >= 2 for this\n"); + return CMD_RET_FAILURE; + } + + if (!img_data.dtb_load_addr) { + printf("Error: failed to read dtb_load_addr\n"); + return CMD_RET_FAILURE; + } + + if (argc == 0) + printf("%lx\n", (ulong)img_data.dtb_load_addr); + else + env_set_hex(argv[0], (ulong)img_data.dtb_load_addr); + + return CMD_RET_SUCCESS; +} + +static int abootimg_get_dtb_by_index(int argc, char *const argv[]) +{ + const char *index_str; + u32 num; + char *endp; + ulong addr; + u32 size; + + if (argc < 1 || argc > 3) + return CMD_RET_USAGE; + + index_str = argv[0] + strlen("--index="); + if (index_str[0] == '\0') { + printf("Error: Wrong index num\n"); + return CMD_RET_FAILURE; + } + + num = simple_strtoul(index_str, &endp, 0); + if (*endp != '\0') { + printf("Error: Wrong index num\n"); + return CMD_RET_FAILURE; + } + + if (!android_image_get_dtb_by_index(abootimg_addr(), + get_avendor_bootimg_addr(), num, + &addr, &size)) { + return CMD_RET_FAILURE; + } + + if (argc == 1) { + printf("%lx\n", addr); + } else { + if (env_set_hex(argv[1], addr)) { + printf("Error: Can't set [addr_var]\n"); + return CMD_RET_FAILURE; + } + + if (argc == 3) { + if (env_set_hex(argv[2], size)) { + printf("Error: Can't set [size_var]\n"); + return CMD_RET_FAILURE; + } + } + } + + return CMD_RET_SUCCESS; +} + +static int abootimg_get_dtb(int argc, char *const argv[]) +{ + if (argc < 1) + return CMD_RET_USAGE; + + if (strstr(argv[0], "--index=")) + return abootimg_get_dtb_by_index(argc, argv); + + return CMD_RET_USAGE; +} + +static int do_abootimg_addr(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *endp; + ulong img_addr; + + if (argc < 2 || argc > 4) + return CMD_RET_USAGE; + + img_addr = hextoul(argv[1], &endp); + if (*endp != '\0') { + printf("Error: Wrong image address\n"); + return CMD_RET_FAILURE; + } + + _abootimg_addr = img_addr; + + if (argc > 2) { + img_addr = simple_strtoul(argv[2], &endp, 16); + if (*endp != '\0') { + printf("Error: Wrong vendor_boot image address\n"); + return CMD_RET_FAILURE; + } + + _avendor_bootimg_addr = img_addr; + } + + if (argc == 4) { + img_addr = simple_strtoul(argv[3], &endp, 16); + if (*endp != '\0') { + printf("Error: Wrong init_boot image address\n"); + return CMD_RET_FAILURE; + } + + _ainit_bootimg_addr = img_addr; + } + + return CMD_RET_SUCCESS; +} + +static int do_abootimg_get(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *param; + + if (argc < 2) + return CMD_RET_USAGE; + + param = argv[1]; + argc -= 2; + argv += 2; + if (!strcmp(param, "ver")) + return abootimg_get_ver(argc, argv); + else if (!strcmp(param, "recovery_dtbo")) + return abootimg_get_recovery_dtbo(argc, argv); + else if (!strcmp(param, "dtb_load_addr")) + return abootimg_get_dtb_load_addr(argc, argv); + else if (!strcmp(param, "dtb")) + return abootimg_get_dtb(argc, argv); + + return CMD_RET_USAGE; +} + +static int do_abootimg_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc != 2) + return CMD_RET_USAGE; + + if (!strcmp(argv[1], "dtb")) { + if (android_image_print_dtb_contents(abootimg_addr())) + return CMD_RET_FAILURE; + } else { + return CMD_RET_USAGE; + } + + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl cmd_abootimg_sub[] = { + U_BOOT_CMD_MKENT(addr, 4, 1, do_abootimg_addr, "", ""), + U_BOOT_CMD_MKENT(dump, 2, 1, do_abootimg_dump, "", ""), + U_BOOT_CMD_MKENT(get, 5, 1, do_abootimg_get, "", ""), +}; + +static int do_abootimg(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cp; + + cp = find_cmd_tbl(argv[1], cmd_abootimg_sub, + ARRAY_SIZE(cmd_abootimg_sub)); + + /* Strip off leading 'abootimg' command argument */ + argc--; + argv++; + + if (!cp || argc > cp->maxargs) + return CMD_RET_USAGE; + if (flag == CMD_FLAG_REPEAT && !cmd_is_repeatable(cp)) + return CMD_RET_SUCCESS; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD( + abootimg, CONFIG_SYS_MAXARGS, 0, do_abootimg, + "manipulate Android Boot Image", + "addr <boot_img_addr> [<vendor_boot_img_addr> [<init_boot_img_addr>]]\n" + " - set the address in RAM where boot image is located\n" + " ($loadaddr is used by default)\n" + "abootimg dump dtb\n" + " - print info for all DT blobs in DTB area\n" + "abootimg get ver [varname]\n" + " - get header version\n" + "abootimg get recovery_dtbo [addr_var [size_var]]\n" + " - get address and size (hex) of recovery DTBO area in the image\n" + " [addr_var]: variable name to contain DTBO area address\n" + " [size_var]: variable name to contain DTBO area size\n" + "abootimg get dtb_load_addr [varname]\n" + " - get load address (hex) of DTB, from image header\n" + "abootimg get dtb --index=<num> [addr_var [size_var]]\n" + " - get address and size (hex) of DT blob in the image by index\n" + " <num>: index number of desired DT blob in DTB area\n" + " [addr_var]: variable name to contain DT blob address\n" + " [size_var]: variable name to contain DT blob size" +); diff --git a/cmd/acpi.c b/cmd/acpi.c new file mode 100644 index 00000000000..bb243202009 --- /dev/null +++ b/cmd/acpi.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ +#include <command.h> +#include <display_options.h> +#include <log.h> +#include <mapmem.h> +#include <tables_csum.h> +#include <acpi/acpi_table.h> +#include <asm/acpi_table.h> +#include <asm/global_data.h> +#include <linux/errno.h> +#include <dm/acpi.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const char *show_checksum(void *ptr, uint size, bool chksums) +{ + uint checksum; + + if (!chksums) + return ""; + checksum = table_compute_checksum(ptr, size); + + return checksum ? " bad" : " OK"; +} + +/** + * dump_hdr() - Dump an ACPI header + * + * Except for the Firmware ACPI Control Structure (FACS) + * additionally show the revision information. + * + * @hdr: ACPI header to dump + */ +static void dump_hdr(struct acpi_table_header *hdr, bool chksums) +{ + bool has_hdr = memcmp(hdr->signature, "FACS", ACPI_NAME_LEN); + + printf("%.*s %16lx %5x", ACPI_NAME_LEN, hdr->signature, + (ulong)map_to_sysmem(hdr), hdr->length); + if (has_hdr) { + printf(" v%02d %.6s %.8s %x %.4s %x%s\n", hdr->revision, + hdr->oem_id, hdr->oem_table_id, hdr->oem_revision, + hdr->creator_id, hdr->creator_revision, + show_checksum(hdr, hdr->length, chksums)); + } else { + printf("\n"); + } +} + +static int dump_table_name(const char *sig) +{ + struct acpi_table_header *hdr; + + hdr = acpi_find_table(sig); + if (!hdr) + return -ENOENT; + printf("%.*s @ %16lx\n", ACPI_NAME_LEN, hdr->signature, + (ulong)nomap_to_sysmem(hdr)); + print_buffer(0, hdr, 1, hdr->length, 0); + + return 0; +} + +static void list_fadt(struct acpi_fadt *fadt, bool chksums) +{ + if (fadt->header.revision >= 3 && fadt->x_dsdt) + dump_hdr(nomap_sysmem(fadt->x_dsdt, 0), chksums); + else if (fadt->dsdt) + dump_hdr(nomap_sysmem(fadt->dsdt, 0), chksums); + if (!IS_ENABLED(CONFIG_X86) && !IS_ENABLED(CONFIG_SANDBOX) && + !(fadt->flags & ACPI_FADT_HW_REDUCED_ACPI)) + log_err("FADT not ACPI-hardware-reduced-compliant\n"); + if (fadt->header.revision >= 3 && fadt->x_firmware_ctrl) + dump_hdr(nomap_sysmem(fadt->x_firmware_ctrl, 0), chksums); + else if (fadt->firmware_ctrl) + dump_hdr(nomap_sysmem(fadt->firmware_ctrl, 0), chksums); +} + +static void list_rsdt(struct acpi_rsdp *rsdp, bool chksums) +{ + int len, i, count; + struct acpi_rsdt *rsdt; + struct acpi_xsdt *xsdt; + + if (rsdp->rsdt_address) { + rsdt = nomap_sysmem(rsdp->rsdt_address, 0); + dump_hdr(&rsdt->header, chksums); + } + if (rsdp->xsdt_address) { + xsdt = nomap_sysmem(rsdp->xsdt_address, 0); + dump_hdr(&xsdt->header, chksums); + len = xsdt->header.length - sizeof(xsdt->header); + count = len / sizeof(u64); + } else if (rsdp->rsdt_address) { + len = rsdt->header.length - sizeof(rsdt->header); + count = len / sizeof(u32); + } else { + return; + } + + for (i = 0; i < count; i++) { + struct acpi_table_header *hdr; + u64 entry; + + if (rsdp->xsdt_address) + entry = xsdt->entry[i]; + else + entry = rsdt->entry[i]; + if (!entry) + break; + hdr = nomap_sysmem(entry, 0); + dump_hdr(hdr, chksums); + if (!memcmp(hdr->signature, "FACP", ACPI_NAME_LEN)) + list_fadt((struct acpi_fadt *)hdr, chksums); + } +} + +static void list_rsdp(struct acpi_rsdp *rsdp, bool chksums) +{ + printf("RSDP %16lx %5x v%02d %.6s%s%s\n", + (ulong)map_to_sysmem(rsdp), rsdp->length, rsdp->revision, + rsdp->oem_id, show_checksum(rsdp, 0x14, chksums), + show_checksum(rsdp, rsdp->length, chksums)); + list_rsdt(rsdp, chksums); +} + +static int do_acpi_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct acpi_rsdp *rsdp; + bool chksums; + + chksums = argc >= 2 && !strcmp("-c", argv[1]); + rsdp = map_sysmem(gd_acpi_start(), 0); + if (!rsdp) { + printf("No ACPI tables present\n"); + return 0; + } + printf("Name Base Size Detail\n" + "---- ---------------- ----- ----------------------------\n"); + list_rsdp(rsdp, chksums); + + return 0; +} + +static int do_acpi_set(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong val; + + if (argc < 2) { + printf("ACPI pointer: %lx\n", gd_acpi_start()); + } else { + val = hextoul(argv[1], NULL); + printf("Setting ACPI pointer to %lx\n", val); + gd_set_acpi_start(val); + } + + return 0; +} + +static int do_acpi_items(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool dump_contents; + + dump_contents = argc >= 2 && !strcmp("-d", argv[1]); + if (!IS_ENABLED(CONFIG_ACPIGEN)) { + printf("Not supported (enable ACPIGEN)\n"); + return CMD_RET_FAILURE; + } + acpi_dump_items(dump_contents ? ACPI_DUMP_CONTENTS : ACPI_DUMP_LIST); + + return 0; +} + +static int do_acpi_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *name; + char sig[ACPI_NAME_LEN]; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + name = argv[1]; + if (strlen(name) != ACPI_NAME_LEN) { + printf("Table name '%s' must be four characters\n", name); + return CMD_RET_FAILURE; + } + str_to_upper(name, sig, ACPI_NAME_LEN); + ret = dump_table_name(sig); + if (ret) { + printf("Table '%.*s' not found\n", ACPI_NAME_LEN, sig); + return CMD_RET_FAILURE; + } + + return 0; +} + +U_BOOT_LONGHELP(acpi, + "list [-c] - list ACPI tables [check checksums]\n" + "acpi items [-d] - List/dump each piece of ACPI data from devices\n" + "acpi set [<addr>] - Set or show address of ACPI tables\n" + "acpi dump <name> - Dump ACPI table"); + +U_BOOT_CMD_WITH_SUBCMDS(acpi, "ACPI tables", acpi_help_text, + U_BOOT_SUBCMD_MKENT(list, 2, 1, do_acpi_list), + U_BOOT_SUBCMD_MKENT(items, 2, 1, do_acpi_items), + U_BOOT_SUBCMD_MKENT(set, 2, 1, do_acpi_set), + U_BOOT_SUBCMD_MKENT(dump, 2, 1, do_acpi_dump)); diff --git a/cmd/adc.c b/cmd/adc.c new file mode 100644 index 00000000000..334ba7fdeca --- /dev/null +++ b/cmd/adc.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ +#include <command.h> +#include <dm.h> +#include <env.h> +#include <adc.h> +#include <linux/printk.h> + +static int do_adc_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret, err; + + ret = err = uclass_first_device_check(UCLASS_ADC, &dev); + + while (dev) { + printf("- %s status: %i\n", dev->name, ret); + + ret = uclass_next_device_check(&dev); + if (ret) + err = ret; + } + + return err ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static int do_adc_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + unsigned int data_mask, ch_mask; + int ret, vss, vdd; + + if (argc < 2) + return CMD_RET_USAGE; + + ret = uclass_get_device_by_name(UCLASS_ADC, argv[1], &dev); + if (ret) { + printf("Unknown ADC device %s\n", argv[1]); + return CMD_RET_FAILURE; + } + + printf("ADC Device '%s' :\n", argv[1]); + + ret = adc_channel_mask(dev, &ch_mask); + if (!ret) + printf("channel mask: %x\n", ch_mask); + + ret = adc_data_mask(dev, &data_mask); + if (!ret) + printf("data mask: %x\n", data_mask); + + ret = adc_vdd_value(dev, &vdd); + if (!ret) + printf("vdd: %duV\n", vdd); + + ret = adc_vss_value(dev, &vss); + if (!ret) + printf("vss: %duV\n", vss); + + return CMD_RET_SUCCESS; +} + +static int do_adc_single(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *varname = NULL; + struct udevice *dev; + unsigned int data; + int ret, uV, val; + + if (argc < 3) + return CMD_RET_USAGE; + + if (argc >= 4) + varname = argv[3]; + + ret = adc_channel_single_shot(argv[1], simple_strtol(argv[2], NULL, 0), + &data); + if (ret) { + printf("Error getting single shot for device %s channel %s (ret=%d)\n", + argv[1], argv[2], ret); + return CMD_RET_FAILURE; + } + + ret = uclass_get_device_by_name(UCLASS_ADC, argv[1], &dev); + if (!ret && !adc_raw_to_uV(dev, data, &uV)) { + val = uV; + printf("%u, %d uV\n", data, uV); + } else { + val = data; + printf("%u\n", data); + } + + if (varname) + env_set_ulong(varname, val); + + return CMD_RET_SUCCESS; +} + +static int do_adc_scan(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct adc_channel ch[ADC_MAX_CHANNEL]; + struct udevice *dev; + unsigned int ch_mask; + int i, chan, ret, uV; + + if (argc < 2) + return CMD_RET_USAGE; + + ret = uclass_get_device_by_name(UCLASS_ADC, argv[1], &dev); + if (ret) { + pr_err("Can't get the ADC %s: %d\n", argv[1], ret); + return CMD_RET_FAILURE; + } + + switch (argc) { + case 3: + ch_mask = simple_strtoul(argv[2], NULL, 0); + if (ch_mask) + break; + case 2: + ret = adc_channel_mask(dev, &ch_mask); + if (ret) { + pr_err("Can't get mask for %s: %d\n", dev->name, ret); + return CMD_RET_FAILURE; + } + break; + } + + ret = adc_channels_single_shot(dev->name, ch_mask, ch); + if (ret) { + pr_err("Can't get single shot for %s (chans mask: 0x%x): %d\n", + dev->name, ch_mask, ret); + return CMD_RET_FAILURE; + } + + for (chan = 0, i = 0; chan < ADC_MAX_CHANNEL; chan++) { + if (!(ch_mask & ADC_CHANNEL(chan))) + continue; + if (!adc_raw_to_uV(dev, ch[i].data, &uV)) + printf("[%02d]: %u, %d uV\n", ch[i].id, ch[i].data, uV); + else + printf("[%02d]: %u\n", ch[i].id, ch[i].data); + i++; + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(adc, + "list - list ADC devices\n" + "adc info <name> - Get ADC device info\n" + "adc single <name> <channel> [varname] - Get Single data of ADC device channel\n" + "adc scan <name> [channel mask] - Scan all [or masked] ADC channels\n"); + +U_BOOT_CMD_WITH_SUBCMDS(adc, "ADC sub-system", adc_help_text, + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_adc_list), + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_adc_info), + U_BOOT_SUBCMD_MKENT(single, 4, 1, do_adc_single), + U_BOOT_SUBCMD_MKENT(scan, 3, 1, do_adc_scan)); diff --git a/cmd/addrmap.c b/cmd/addrmap.c new file mode 100644 index 00000000000..f7e4d9206de --- /dev/null +++ b/cmd/addrmap.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021, Bin Meng <bmeng.cn@gmail.com> + */ + +#include <command.h> +#include <addr_map.h> + +static int do_addrmap(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i; + + printf(" vaddr paddr size\n"); + printf("================ ================ ================\n"); + + for (i = 0; i < CONFIG_SYS_NUM_ADDR_MAP; i++) { + if (address_map[i].size == 0) + continue; + + printf("%16.8lx %16.8llx %16.8llx\n", + address_map[i].vaddr, + (unsigned long long)address_map[i].paddr, + (unsigned long long)address_map[i].size); + } + + return 0; +} + +U_BOOT_CMD( + addrmap, 1, 1, do_addrmap, + "List non-identity virtual-physical memory mappings for 32-bit CPUs", + "" +); diff --git a/cmd/adtimg.c b/cmd/adtimg.c new file mode 100644 index 00000000000..53f33764fbe --- /dev/null +++ b/cmd/adtimg.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2018 Linaro Ltd. + * Sam Protsenko <semen.protsenko@linaro.org> + * Eugeniu Rosca <rosca.eugeniu@gmail.com> + */ + +#include <command.h> +#include <env.h> +#include <vsprintf.h> +#include <image-android-dt.h> + +#define OPT_INDEX "--index" + +/* + * Current/working DTB/DTBO Android image address. + * Similar to 'working_fdt' variable in 'fdt' command. + */ +static ulong working_img; + +static int do_adtimg_addr(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *endp; + ulong hdr_addr; + + if (argc != 2) + return CMD_RET_USAGE; + + hdr_addr = hextoul(argv[1], &endp); + if (*endp != '\0') { + printf("Error: Wrong image address '%s'\n", argv[1]); + return CMD_RET_FAILURE; + } + + /* + * Allow users to set an address prior to copying the DTB/DTBO + * image to that same address, i.e. skip header verification. + */ + + working_img = hdr_addr; + return CMD_RET_SUCCESS; +} + +static int adtimg_check_working_img(void) +{ + if (!working_img) { + printf("Error: Please, call 'adtimg addr <addr>'. Aborting!\n"); + return CMD_RET_FAILURE; + } + + if (!android_dt_check_header(working_img)) { + printf("Error: Invalid image header at 0x%lx\n", working_img); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_adtimg_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc != 1) + return CMD_RET_USAGE; + + if (adtimg_check_working_img() != CMD_RET_SUCCESS) + return CMD_RET_FAILURE; + + android_dt_print_contents(working_img); + + return CMD_RET_SUCCESS; +} + +static int adtimg_getopt_u32(char * const opt, char * const name, u32 *optval) +{ + char *endp, *str; + u32 val; + + if (!opt || !name || !optval) + return CMD_RET_FAILURE; + + str = strchr(opt, '='); + if (!str) { + printf("Error: Option '%s' not followed by '='\n", name); + return CMD_RET_FAILURE; + } + + if (*++str == '\0') { + printf("Error: Option '%s=' not followed by value\n", name); + return CMD_RET_FAILURE; + } + + val = simple_strtoul(str, &endp, 0); + if (*endp != '\0') { + printf("Error: Wrong integer value '%s=%s'\n", name, str); + return CMD_RET_FAILURE; + } + + *optval = val; + return CMD_RET_SUCCESS; +} + +static int adtimg_getopt_index(int argc, char *const argv[], u32 *index, + char **avar, char **svar) +{ + int ret; + + if (!argv || !avar || !svar) + return CMD_RET_FAILURE; + + if (argc > 3) { + printf("Error: Unexpected argument '%s'\n", argv[3]); + return CMD_RET_FAILURE; + } + + ret = adtimg_getopt_u32(argv[0], OPT_INDEX, index); + if (ret != CMD_RET_SUCCESS) + return ret; + + if (argc > 1) + *avar = argv[1]; + if (argc > 2) + *svar = argv[2]; + + return CMD_RET_SUCCESS; +} + +static int adtimg_get_dt_by_index(int argc, char *const argv[]) +{ + ulong addr; + u32 index, size; + int ret; + char *avar = NULL, *svar = NULL; + + ret = adtimg_getopt_index(argc, argv, &index, &avar, &svar); + if (ret != CMD_RET_SUCCESS) + return ret; + + if (!android_dt_get_fdt_by_index(working_img, index, &addr, &size)) + return CMD_RET_FAILURE; + + if (avar && svar) { + ret = env_set_hex(avar, addr); + if (ret) { + printf("Error: Can't set '%s' to 0x%lx\n", avar, addr); + return CMD_RET_FAILURE; + } + ret = env_set_hex(svar, size); + if (ret) { + printf("Error: Can't set '%s' to 0x%x\n", svar, size); + return CMD_RET_FAILURE; + } + } else if (avar) { + ret = env_set_hex(avar, addr); + if (ret) { + printf("Error: Can't set '%s' to 0x%lx\n", avar, addr); + return CMD_RET_FAILURE; + } + printf("0x%x (%d)\n", size, size); + } else { + printf("0x%lx, 0x%x (%d)\n", addr, size, size); + } + + return CMD_RET_SUCCESS; +} + +static int adtimg_get_dt(int argc, char *const argv[]) +{ + if (argc < 2) { + printf("Error: No options passed to '%s'\n", argv[0]); + return CMD_RET_FAILURE; + } + + /* Strip off leading 'dt' command argument */ + argc--; + argv++; + + if (!strncmp(argv[0], OPT_INDEX, sizeof(OPT_INDEX) - 1)) + return adtimg_get_dt_by_index(argc, argv); + + printf("Error: Option '%s' not supported\n", argv[0]); + return CMD_RET_FAILURE; +} + +static int do_adtimg_get(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc < 2) { + printf("Error: No arguments passed to '%s'\n", argv[0]); + return CMD_RET_FAILURE; + } + + if (adtimg_check_working_img() != CMD_RET_SUCCESS) + return CMD_RET_FAILURE; + + /* Strip off leading 'get' command argument */ + argc--; + argv++; + + if (!strcmp(argv[0], "dt")) + return adtimg_get_dt(argc, argv); + + printf("Error: Wrong argument '%s'\n", argv[0]); + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_adtimg_sub[] = { + U_BOOT_CMD_MKENT(addr, CONFIG_SYS_MAXARGS, 1, do_adtimg_addr, "", ""), + U_BOOT_CMD_MKENT(dump, CONFIG_SYS_MAXARGS, 1, do_adtimg_dump, "", ""), + U_BOOT_CMD_MKENT(get, CONFIG_SYS_MAXARGS, 1, do_adtimg_get, "", ""), +}; + +static int do_adtimg(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cp; + + cp = find_cmd_tbl(argv[1], cmd_adtimg_sub, ARRAY_SIZE(cmd_adtimg_sub)); + + /* Strip off leading 'adtimg' command argument */ + argc--; + argv++; + + if (!cp || argc > cp->maxargs) + return CMD_RET_USAGE; + if (flag == CMD_FLAG_REPEAT && !cmd_is_repeatable(cp)) + return CMD_RET_SUCCESS; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD( + adtimg, CONFIG_SYS_MAXARGS, 0, do_adtimg, + "manipulate dtb/dtbo Android image", + "addr <addr> - Set image location to <addr>\n" + "adtimg dump - Print out image contents\n" + "adtimg get dt --index=<index> [avar [svar]] - Get DT address/size by index\n" + "\n" + "Legend:\n" + " - <addr>: DTB/DTBO image address (hex) in RAM\n" + " - <index>: index (hex/dec) of desired DT in the image\n" + " - <avar>: variable name to contain DT address (hex)\n" + " - <svar>: variable name to contain DT size (hex)" +); diff --git a/cmd/aes.c b/cmd/aes.c new file mode 100644 index 00000000000..3fd83013ffe --- /dev/null +++ b/cmd/aes.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2014 Marek Vasut <marex@denx.de> + * Copyright (C) 2025 Ion Agorria <ion@agorria.com> + * + * Command for AES-[128/192/256] operations. + */ + +#include <command.h> +#include <uboot_aes.h> +#include <malloc.h> +#include <asm/byteorder.h> +#include <linux/compiler.h> +#include <mapmem.h> +#include <vsprintf.h> +#include <dm/uclass.h> +#include <dm/device.h> + +u32 aes_get_key_len(char *command) +{ + u32 key_len = AES128_KEY_LENGTH; + + if (!strcmp(command, "aes.192")) + key_len = AES192_KEY_LENGTH; + else if (!strcmp(command, "aes.256")) + key_len = AES256_KEY_LENGTH; + + return key_len; +} + +int aes_get_driver(struct udevice **dev) +{ + int ret; + + ret = uclass_get_device(UCLASS_AES, 0, dev); + if (ret) { + printf("Failed to get AES driver: %d\n", ret); + return ret; + } + + return 0; +} + +int cmd_aes_cbc_simple(int argc, char *const argv[], u32 key_len) +{ + uint32_t key_addr, iv_addr, src_addr, dst_addr, len; + uint8_t *key_ptr, *iv_ptr, *src_ptr, *dst_ptr; + u8 key_exp[AES256_EXPAND_KEY_LENGTH]; + u32 aes_blocks; + int enc; + + if (argc != 7) + return CMD_RET_USAGE; + + if (!strncmp(argv[1], "enc", 3)) + enc = 1; + else if (!strncmp(argv[1], "dec", 3)) + enc = 0; + else + return CMD_RET_USAGE; + + key_addr = hextoul(argv[2], NULL); + iv_addr = hextoul(argv[3], NULL); + src_addr = hextoul(argv[4], NULL); + dst_addr = hextoul(argv[5], NULL); + len = hextoul(argv[6], NULL); + + key_ptr = (uint8_t *)map_sysmem(key_addr, key_len); + iv_ptr = (uint8_t *)map_sysmem(iv_addr, 128 / 8); + src_ptr = (uint8_t *)map_sysmem(src_addr, len); + dst_ptr = (uint8_t *)map_sysmem(dst_addr, len); + + /* First we expand the key. */ + aes_expand_key(key_ptr, key_len, key_exp); + + /* Calculate the number of AES blocks to encrypt. */ + aes_blocks = DIV_ROUND_UP(len, AES_BLOCK_LENGTH); + + if (enc) + aes_cbc_encrypt_blocks(key_len, key_exp, iv_ptr, src_ptr, + dst_ptr, aes_blocks); + else + aes_cbc_decrypt_blocks(key_len, key_exp, iv_ptr, src_ptr, + dst_ptr, aes_blocks); + + unmap_sysmem(key_ptr); + unmap_sysmem(iv_ptr); + unmap_sysmem(src_ptr); + unmap_sysmem(dst_ptr); + + return CMD_RET_SUCCESS; +} + +int cmd_aes_get_slots(void) +{ + struct udevice *dev; + u8 slots; + int ret; + + ret = aes_get_driver(&dev); + if (ret) + return ret; + + slots = dm_aes_get_available_key_slots(dev); + printf("Available slots: %d\n", slots); + + return CMD_RET_SUCCESS; +} + +int cmd_aes_set_key(int argc, char *const argv[], u32 key_len) +{ + struct udevice *dev; + u32 key_addr, slot; + u8 *key_ptr; + int ret; + + if (argc != 4) + return CMD_RET_USAGE; + + ret = aes_get_driver(&dev); + if (ret) + return ret; + + key_addr = hextoul(argv[2], NULL); + slot = hextoul(argv[3], NULL); + + key_ptr = (uint8_t *)map_sysmem(key_addr, key_len); + + ret = dm_aes_set_key_for_key_slot(dev, key_len * 8, key_ptr, slot); + unmap_sysmem(key_ptr); + if (ret) { + printf("Unable to set key at slot: %d\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +int cmd_aes_select_slot(int argc, char *const argv[], u32 key_len) +{ + struct udevice *dev; + u32 slot; + int ret; + + if (argc != 3) + return CMD_RET_USAGE; + + ret = aes_get_driver(&dev); + if (ret) + return ret; + + slot = hextoul(argv[2], NULL); + + ret = dm_aes_select_key_slot(dev, key_len * 8, slot); + if (ret) { + printf("Unable to select key slot: %d\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +int cmd_aes_ecb(int argc, char *const argv[], u32 key_len) +{ + struct udevice *dev; + u32 src_addr, dst_addr, len; + u8 *src_ptr, *dst_ptr; + u32 aes_blocks; + int enc, ret; + + if (argc != 6) + return CMD_RET_USAGE; + + ret = aes_get_driver(&dev); + if (ret) + return ret; + + if (!strncmp(argv[1], "enc", 3)) + enc = 1; + else if (!strncmp(argv[1], "dec", 3)) + enc = 0; + else + return CMD_RET_USAGE; + + src_addr = hextoul(argv[3], NULL); + dst_addr = hextoul(argv[4], NULL); + len = hextoul(argv[5], NULL); + + src_ptr = (uint8_t *)map_sysmem(src_addr, len); + dst_ptr = (uint8_t *)map_sysmem(dst_addr, len); + + /* Calculate the number of AES blocks to encrypt. */ + aes_blocks = DIV_ROUND_UP(len, AES_BLOCK_LENGTH); + + if (enc) + ret = dm_aes_ecb_encrypt(dev, src_ptr, dst_ptr, aes_blocks); + else + ret = dm_aes_ecb_decrypt(dev, src_ptr, dst_ptr, aes_blocks); + + unmap_sysmem(src_ptr); + unmap_sysmem(dst_ptr); + + if (ret) { + printf("Unable to do ecb operation: %d\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +int cmd_aes_cbc(int argc, char *const argv[], u32 key_len) +{ + struct udevice *dev; + u32 iv_addr, src_addr, dst_addr, len; + u8 *iv_ptr, *src_ptr, *dst_ptr; + u32 aes_blocks; + int enc, ret; + + if (argc != 7) + return CMD_RET_USAGE; + + ret = aes_get_driver(&dev); + if (ret) + return ret; + + if (!strncmp(argv[1], "enc", 3)) + enc = 1; + else if (!strncmp(argv[1], "dec", 3)) + enc = 0; + else + return CMD_RET_USAGE; + + iv_addr = hextoul(argv[3], NULL); + src_addr = hextoul(argv[4], NULL); + dst_addr = hextoul(argv[5], NULL); + len = hextoul(argv[6], NULL); + + iv_ptr = (uint8_t *)map_sysmem(iv_addr, AES_BLOCK_LENGTH); + src_ptr = (uint8_t *)map_sysmem(src_addr, len); + dst_ptr = (uint8_t *)map_sysmem(dst_addr, len); + + /* Calculate the number of AES blocks to encrypt. */ + aes_blocks = DIV_ROUND_UP(len, AES_BLOCK_LENGTH); + + if (enc) + ret = dm_aes_cbc_encrypt(dev, iv_ptr, src_ptr, dst_ptr, aes_blocks); + else + ret = dm_aes_cbc_decrypt(dev, iv_ptr, src_ptr, dst_ptr, aes_blocks); + + unmap_sysmem(iv_ptr); + unmap_sysmem(src_ptr); + unmap_sysmem(dst_ptr); + + if (ret) { + printf("Unable to do cbc operation: %d\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +/** + * do_aes() - Handle the "aes" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +static int do_aes(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + u32 key_len; + + if (argc < 2) + return CMD_RET_USAGE; + + key_len = aes_get_key_len(argv[0]); + + if (!strncmp(argv[1], "enc", 3) || !strncmp(argv[1], "dec", 3)) + return cmd_aes_cbc_simple(argc, argv, key_len); + else if (CONFIG_IS_ENABLED(DM_AES) && !strncmp(argv[1], "get_slots", 9)) + return cmd_aes_get_slots(); + else if (CONFIG_IS_ENABLED(DM_AES) && !strncmp(argv[1], "set_key", 7)) + return cmd_aes_set_key(argc, argv, key_len); + else if (CONFIG_IS_ENABLED(DM_AES) && !strncmp(argv[1], "select_slot", 11)) + return cmd_aes_select_slot(argc, argv, key_len); + else if (CONFIG_IS_ENABLED(DM_AES) && !strncmp(argv[1], "ecb", 3)) + return cmd_aes_ecb(argc, argv, key_len); + else if (CONFIG_IS_ENABLED(DM_AES) && !strncmp(argv[1], "cbc", 3)) + return cmd_aes_cbc(argc, argv, key_len); + else + return CMD_RET_USAGE; +} + +/***************************************************/ +U_BOOT_LONGHELP(aes, + "[.128,.192,.256] enc key iv src dst len - CBC encrypt block of data $len bytes long\n" + " at address $src using a key at address\n" + " $key with initialization vector at address\n" + " $iv. Store the result at address $dst.\n" + " The $len size must be multiple of 16 bytes.\n" + " The $key and $iv must be 16 bytes long.\n" + "aes [.128,.192,.256] dec key iv src dst len - CBC decrypt block of data $len bytes long\n" + " at address $src using a key at address\n" + " $key with initialization vector at address\n" + " $iv. Store the result at address $dst.\n" + " The $len size must be multiple of 16 bytes.\n" + " The $key and $iv must be 16 bytes long." + +#if CONFIG_IS_ENABLED(DM_AES) + "\n" + "aes get_slots - Gives number of available key slots\n" + "aes [.128,.192,.256] set_key key slot - Load key at address $key into the slot $slot\n" + "aes [.128,.192,.256] select_slot slot - Select current active key slot\n" + "aes [.128,.192,.256] ecb enc src dst len - ECB encrypt block of data $len bytes long\n" + " at address $src using a key at current\n" + " slot. Store the result at address $dst.\n" + " The $len size must be multiple of 16 bytes.\n" + "aes [.128,.192,.256] ecb dec src dst len - ECB decrypt block of data $len bytes long\n" + " at address $src using a key at current\n" + " slot. Store the result at address $dst.\n" + " The $len size must be multiple of 16 bytes.\n" + "aes [.128,.192,.256] cbc enc iv src dst len - CBC encrypt block of data $len bytes long\n" + " at address $src using a key at current\n" + " slot with initialization vector at address\n" + " $iv. Store the result at address $dst.\n" + " The $len size must be multiple of 16 bytes.\n" + " The $iv must be 16 bytes long.\n" + "aes [.128,.192,.256] cbc dec iv src dst len - CBC decrypt block of data $len bytes long\n" + " at address $src using a key at current\n" + " slot with initialization vector at address\n" + " $iv. Store the result at address $dst.\n" + " The $len size must be multiple of 16 bytes.\n" + " The $iv must be 16 bytes long." +#endif +); + +U_BOOT_CMD( + aes, 7, 1, do_aes, + "AES 128/192/256 operations", + aes_help_text +); diff --git a/cmd/arm/Makefile b/cmd/arm/Makefile new file mode 100644 index 00000000000..94367dcb459 --- /dev/null +++ b/cmd/arm/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ + +ifdef CONFIG_ARM64 +obj-$(CONFIG_CMD_EXCEPTION) += exception64.o +else +obj-$(CONFIG_CMD_EXCEPTION) += exception.o +endif diff --git a/cmd/arm/exception.c b/cmd/arm/exception.c new file mode 100644 index 00000000000..8857f121604 --- /dev/null +++ b/cmd/arm/exception.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'exception' command can be used for testing exception handling. + * + * Copyright (c) 2018, Heinrich Schuchardt <xypron.glpk@gmx.de> + */ + +#include <command.h> + +static int do_unaligned(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + /* + * The LDRD instruction requires the data source to be four byte aligned + * even if strict alignment fault checking is disabled in the system + * control register. + */ + asm volatile ( + "MOV r5, sp\n" + "ADD r5, #1\n" + "LDRD r6, r7, [r5]\n"); + return CMD_RET_FAILURE; +} + +static int do_breakpoint(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + asm volatile ("BKPT #123\n"); + return CMD_RET_FAILURE; +} + +static int do_undefined(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + /* + * 0xe7f...f. is undefined in ARM mode + * 0xde.. is undefined in Thumb mode + */ + asm volatile (".word 0xe7f7defb\n"); + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_sub[] = { + U_BOOT_CMD_MKENT(breakpoint, CONFIG_SYS_MAXARGS, 1, do_breakpoint, + "", ""), + U_BOOT_CMD_MKENT(unaligned, CONFIG_SYS_MAXARGS, 1, do_unaligned, + "", ""), + U_BOOT_CMD_MKENT(undefined, CONFIG_SYS_MAXARGS, 1, do_undefined, + "", ""), +}; + +U_BOOT_LONGHELP(exception, + "<ex>\n" + " The following exceptions are available:\n" + " breakpoint - prefetch abort\n" + " unaligned - data abort\n" + " undefined - undefined instruction\n"); + +#include <exception.h> diff --git a/cmd/arm/exception64.c b/cmd/arm/exception64.c new file mode 100644 index 00000000000..4c5b953168c --- /dev/null +++ b/cmd/arm/exception64.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'exception' command can be used for testing exception handling. + * + * Copyright (c) 2018, Heinrich Schuchardt <xypron.glpk@gmx.de> + */ + +#include <command.h> +#include <linux/bitops.h> + +static int do_undefined(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + /* + * Instructions starting with the upper 16 bits all 0 are permanently + * undefined. The lower 16 bits can be used for some kind of immediate. + * --- ARMv8 ARM (ARM DDI 0487G.a C6.2.339: "UDF") + */ + asm volatile (".word 0x00001234\n"); + + return CMD_RET_FAILURE; +} + +/* + * The ID_AA64MMFR2_EL1 register name is only know to binutils for ARMv8.2 + * and later architecture revisions. However the register is valid regardless + * of binutils architecture support or the core the code is running on, so + * just use the generic encoding. + */ +#define ID_AA64MMFR2_EL1 "S3_0_C0_C7_2" + +static int do_unaligned(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint64_t reg; + + /* + * The unaligned LDAR access below is only guaranteed to generate an + * alignment fault on cores not implementing FEAT_LSE2. To avoid false + * negatives, check this condition before we exectute LDAR. + */ + asm ("mrs %0, "ID_AA64MMFR2_EL1"\n" : "=r" (reg)); + if (reg & GENMASK(35, 32)) { + printf("unaligned access check only supported on pre-v8.4 cores\n"); + return CMD_RET_FAILURE; + } + + /* + * The load acquire instruction requires the data source to be + * naturally aligned, and will fault even if strict alignment fault + * checking is disabled (but only without FEAT_LSE2). + * --- ARMv8 ARM (ARM DDI 0487G.a B2.5.2: "Alignment of data accesses") + */ + asm volatile ( + "mov x1, sp\n\t" + "orr x1, x1, #3\n\t" + "ldar x0, [x1]\n" + ::: "x0", "x1" ); + + return CMD_RET_FAILURE; +} + +static int do_breakpoint(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + asm volatile ("brk #123\n"); + + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_sub[] = { + U_BOOT_CMD_MKENT(breakpoint, CONFIG_SYS_MAXARGS, 1, do_breakpoint, + "", ""), + U_BOOT_CMD_MKENT(unaligned, CONFIG_SYS_MAXARGS, 1, do_unaligned, + "", ""), + U_BOOT_CMD_MKENT(undefined, CONFIG_SYS_MAXARGS, 1, do_undefined, + "", ""), +}; + +U_BOOT_LONGHELP(exception, + "<ex>\n" + " The following exceptions are available:\n" + " breakpoint - breakpoint instruction exception\n" + " unaligned - unaligned LDAR data abort\n" + " undefined - undefined instruction exception\n"); + +#include <exception.h> diff --git a/cmd/armffa.c b/cmd/armffa.c new file mode 100644 index 00000000000..181e31bc49a --- /dev/null +++ b/cmd/armffa.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022-2023 Arm Limited and/or its affiliates <open-source-office@arm.com> + * + * Authors: + * Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com> + */ +#include <arm_ffa.h> +#include <command.h> +#include <dm.h> +#include <mapmem.h> +#include <stdlib.h> +#include <asm/io.h> + +/* Select the right physical address formatting according to the platform */ +#ifdef CONFIG_PHYS_64BIT +#define PhysAddrLength "ll" +#else +#define PhysAddrLength "" +#endif +#define PHYS_ADDR_LN "%" PhysAddrLength "x" + +/** + * ffa_get_dev() - Return the FF-A device + * @devp: pointer to the FF-A device + * + * Search for the FF-A device. + * + * Return: + * 0 on success. Otherwise, failure + */ +static int ffa_get_dev(struct udevice **devp) +{ + int ret; + + ret = uclass_first_device_err(UCLASS_FFA, devp); + if (ret) { + log_err("Cannot find FF-A bus device\n"); + return ret; + } + + return 0; +} + +/** + * do_ffa_getpart() - implementation of the getpart subcommand + * @cmdtp: Command Table + * @flag: flags + * @argc: number of arguments + * @argv: arguments + * + * Query a secure partition information. The secure partition UUID is provided + * as an argument. The function uses the arm_ffa driver + * partition_info_get operation which implements FFA_PARTITION_INFO_GET + * ABI to retrieve the data. The input UUID string is expected to be in big + * endian format. + * + * Return: + * + * CMD_RET_SUCCESS: on success, otherwise failure + */ +static int do_ffa_getpart(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 count = 0; + int ret; + struct ffa_partition_desc *descs; + u32 i; + struct udevice *dev; + + if (argc != 2) { + log_err("Missing argument\n"); + return CMD_RET_USAGE; + } + + ret = ffa_get_dev(&dev); + if (ret) + return CMD_RET_FAILURE; + + /* Ask the driver to fill the buffer with the SPs info */ + + ret = ffa_partition_info_get(dev, argv[1], &count, &descs); + if (ret) { + log_err("Failure in querying partition(s) info (error code: %d)\n", ret); + return CMD_RET_FAILURE; + } + + /* SPs found , show the partition information */ + for (i = 0; i < count ; i++) { + log_info("Partition: id = %x , exec_ctxt %x , properties %x\n", + descs[i].info.id, + descs[i].info.exec_ctxt, + descs[i].info.properties); + } + + return CMD_RET_SUCCESS; +} + +/** + * do_ffa_ping() - implementation of the ping subcommand + * @cmdtp: Command Table + * @flag: flags + * @argc: number of arguments + * @argv: arguments + * + * Send data to a secure partition. The secure partition UUID is provided + * as an argument. Use the arm_ffa driver sync_send_receive operation + * which implements FFA_MSG_SEND_DIRECT_{REQ,RESP} ABIs to send/receive data. + * + * Return: + * + * CMD_RET_SUCCESS: on success, otherwise failure + */ +static int do_ffa_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct ffa_send_direct_data msg = { + .data0 = 0xaaaaaaaa, + .data1 = 0xbbbbbbbb, + .data2 = 0xcccccccc, + .data3 = 0xdddddddd, + .data4 = 0xeeeeeeee, + }; + u16 part_id; + int ret; + struct udevice *dev; + + if (argc != 2) { + log_err("Missing argument\n"); + return CMD_RET_USAGE; + } + + part_id = strtoul(argv[1], NULL, 16); + if (!part_id) { + log_err("Partition ID can not be 0\n"); + return CMD_RET_USAGE; + } + + ret = ffa_get_dev(&dev); + if (ret) + return CMD_RET_FAILURE; + + ret = ffa_sync_send_receive(dev, part_id, &msg, 1); + if (!ret) { + u8 cnt; + + log_info("SP response:\n[LSB]\n"); + for (cnt = 0; + cnt < sizeof(struct ffa_send_direct_data) / sizeof(u64); + cnt++) + log_info("%llx\n", ((u64 *)&msg)[cnt]); + return CMD_RET_SUCCESS; + } + + log_err("Sending direct request error (%d)\n", ret); + return CMD_RET_FAILURE; +} + +/** + *do_ffa_devlist() - implementation of the devlist subcommand + * @cmdtp: [in] Command Table + * @flag: flags + * @argc: number of arguments + * @argv: arguments + * + * Query the device belonging to the UCLASS_FFA + * class. + * + * Return: + * + * CMD_RET_SUCCESS: on success, otherwise failure + */ +static int do_ffa_devlist(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct udevice *dev; + int ret; + + ret = ffa_get_dev(&dev); + if (ret) + return CMD_RET_FAILURE; + + log_info("device %s, addr " PHYS_ADDR_LN ", driver %s, ops " PHYS_ADDR_LN "\n", + dev->name, + map_to_sysmem(dev), + dev->driver->name, + map_to_sysmem(dev->driver->ops)); + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(armffa, + "getpart <partition UUID>\n" + " - lists the partition(s) info\n" + "ping <partition ID>\n" + " - sends a data pattern to the specified partition\n" + "devlist\n" + " - displays information about the FF-A device/driver\n"); + +U_BOOT_CMD_WITH_SUBCMDS(armffa, "Arm FF-A test command", armffa_help_text, + U_BOOT_SUBCMD_MKENT(getpart, 2, 1, do_ffa_getpart), + U_BOOT_SUBCMD_MKENT(ping, 2, 1, do_ffa_ping), + U_BOOT_SUBCMD_MKENT(devlist, 1, 1, do_ffa_devlist)); diff --git a/cmd/armflash.c b/cmd/armflash.c new file mode 100644 index 00000000000..cde275c881b --- /dev/null +++ b/cmd/armflash.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2015 + * Linus Walleij, Linaro + * + * Support for ARM Flash Partitions + */ +#include <command.h> +#include <console.h> +#include <env.h> +#include <flash.h> +#include <vsprintf.h> +#include <linux/string.h> +#include <asm/io.h> + +#define MAX_REGIONS 4 +#define MAX_IMAGES 32 + +struct afs_region { + u32 load_address; + u32 size; + u32 offset; +}; + +struct afs_image { + flash_info_t *flinfo; + const char *name; + u32 version; + u32 entrypoint; + u32 attributes; + u32 region_count; + struct afs_region regions[MAX_REGIONS]; + ulong flash_mem_start; + ulong flash_mem_end; +}; + +static struct afs_image afs_images[MAX_IMAGES]; +static int num_afs_images; + +static u32 compute_crc(ulong start, u32 len) +{ + u32 sum = 0; + int i; + + if (len % 4 != 0) { + printf("bad checksumming\n"); + return 0; + } + + for (i = 0; i < len; i += 4) { + u32 val; + + val = readl((void *)start + i); + if (val > ~sum) + sum++; + sum += val; + } + return ~sum; +} + +static void parse_bank(ulong bank) +{ + int i; + ulong flstart, flend; + flash_info_t *info; + + info = &flash_info[bank]; + if (info->flash_id != FLASH_MAN_CFI) { + printf("Bank %lu: missing or unknown FLASH type\n", bank); + return; + } + if (!info->sector_count) { + printf("Bank %lu: no FLASH sectors\n", bank); + return; + } + + flstart = info->start[0]; + flend = flstart + info->size; + + for (i = 0; i < info->sector_count; ++i) { + ulong secend; + u32 foot1, foot2; + + if (ctrlc()) + break; + + if (i == info->sector_count-1) + secend = flend; + else + secend = info->start[i+1]; + + /* Check for v1 header */ + foot1 = readl((void *)secend - 0x0c); + if (foot1 == 0xA0FFFF9FU) { + struct afs_image *afi = &afs_images[num_afs_images]; + ulong imginfo; + + afi->flinfo = info; + afi->version = 1; + afi->flash_mem_start = readl((void *)secend - 0x10); + afi->flash_mem_end = readl((void *)secend - 0x14); + afi->attributes = readl((void *)secend - 0x08); + /* Adjust to even address */ + imginfo = afi->flash_mem_end + afi->flash_mem_end % 4; + /* Record as a single region */ + afi->region_count = 1; + afi->regions[0].offset = readl((void *)imginfo + 0x04); + afi->regions[0].load_address = + readl((void *)imginfo + 0x08); + afi->regions[0].size = readl((void *)imginfo + 0x0C); + afi->entrypoint = readl((void *)imginfo + 0x10); + afi->name = (const char *)imginfo + 0x14; + num_afs_images++; + } + + /* Check for v2 header */ + foot1 = readl((void *)secend - 0x04); + foot2 = readl((void *)secend - 0x08); + /* This makes up the string "HSLFTOOF" flash footer */ + if (foot1 == 0x464F4F54U && foot2 == 0x464C5348U) { + struct afs_image *afi = &afs_images[num_afs_images]; + ulong imginfo; + u32 block_start, block_end; + int j; + + afi->flinfo = info; + afi->version = readl((void *)secend - 0x0c); + imginfo = secend - 0x30 - readl((void *)secend - 0x10); + afi->name = (const char *)secend - 0x30; + + afi->entrypoint = readl((void *)imginfo+0x08); + afi->attributes = readl((void *)imginfo+0x0c); + afi->region_count = readl((void *)imginfo+0x10); + block_start = readl((void *)imginfo+0x54); + block_end = readl((void *)imginfo+0x58); + afi->flash_mem_start = afi->flinfo->start[block_start]; + afi->flash_mem_end = afi->flinfo->start[block_end]; + + /* + * Check footer CRC, the algorithm saves the inverse + * checksum as part of the summed words, and thus + * the result should be zero. + */ + if (compute_crc(imginfo + 8, 0x88) != 0) { + printf("BAD CRC on ARM image info\n"); + printf("(continuing anyway)\n"); + } + + /* Parse regions */ + for (j = 0; j < afi->region_count; j++) { + afi->regions[j].load_address = + readl((void *)imginfo+0x14 + j*0x10); + afi->regions[j].size = + readl((void *)imginfo+0x18 + j*0x10); + afi->regions[j].offset = + readl((void *)imginfo+0x1c + j*0x10); + /* + * At offset 0x20 + j*0x10 there is a region + * checksum which seems to be the running + * sum + 3, however since we anyway checksum + * the entire footer this is skipped over for + * checking here. + */ + } + num_afs_images++; + } + } +} + +static void parse_flash(void) +{ + ulong bank; + + /* We have already parsed the images in flash */ + if (num_afs_images > 0) + return; + for (bank = 0; bank < CONFIG_SYS_MAX_FLASH_BANKS; ++bank) + parse_bank(bank); +} + +static int load_image(const char * const name, const ulong address) +{ + struct afs_image *afi = NULL; + int i; + loff_t len_read = 0; + + parse_flash(); + for (i = 0; i < num_afs_images; i++) { + struct afs_image *tmp = &afs_images[i]; + + if (!strcmp(tmp->name, name)) { + afi = tmp; + break; + } + } + if (!afi) { + printf("image \"%s\" not found in flash\n", name); + return CMD_RET_FAILURE; + } + + for (i = 0; i < afi->region_count; i++) { + ulong from, to; + u32 size; + + from = afi->flash_mem_start + afi->regions[i].offset; + if (address) { + to = address; + } else if (afi->regions[i].load_address) { + to = afi->regions[i].load_address; + } else { + printf("no valid load address\n"); + return CMD_RET_FAILURE; + } + + size = afi->regions[i].size; + memcpy((void *)to, (void *)from, size); + + printf("loaded region %d from %08lX to %08lX, %08X bytes\n", + i, + from, + to, + size); + + len_read += size; + } + + env_set_hex("filesize", len_read); + + return CMD_RET_SUCCESS; +} + +static void print_images(void) +{ + int i; + + parse_flash(); + for (i = 0; i < num_afs_images; i++) { + struct afs_image *afi = &afs_images[i]; + int j; + + printf("Image: \"%s\" (v%d):\n", afi->name, afi->version); + printf(" Entry point: 0x%08X\n", afi->entrypoint); + printf(" Attributes: 0x%08X: ", afi->attributes); + if (afi->attributes == 0x01) + printf("ARM executable"); + if (afi->attributes == 0x08) + printf("ARM backup"); + printf("\n"); + printf(" Flash mem start: 0x%08lX\n", + afi->flash_mem_start); + printf(" Flash mem end: 0x%08lX\n", + afi->flash_mem_end); + for (j = 0; j < afi->region_count; j++) { + printf(" region %d\n" + " load address: %08X\n" + " size: %08X\n" + " offset: %08X\n", + j, + afi->regions[j].load_address, + afi->regions[j].size, + afi->regions[j].offset); + } + } +} + +static int exists(const char * const name) +{ + int i; + + parse_flash(); + for (i = 0; i < num_afs_images; i++) { + struct afs_image *afi = &afs_images[i]; + + if (strcmp(afi->name, name) == 0) + return CMD_RET_SUCCESS; + } + return CMD_RET_FAILURE; +} + +static int do_afs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int ret = CMD_RET_SUCCESS; + + if (argc == 1) { + print_images(); + } else if (argc == 3 && !strcmp(argv[1], "exists")) { + ret = exists(argv[2]); + } else if (argc == 3 && !strcmp(argv[1], "load")) { + ret = load_image(argv[2], 0x0); + } else if (argc == 4 && !strcmp(argv[1], "load")) { + ulong load_addr; + + load_addr = hextoul(argv[3], NULL); + ret = load_image(argv[2], load_addr); + } else { + return CMD_RET_USAGE; + } + + return ret; +} + +U_BOOT_CMD(afs, 4, 0, do_afs, "show AFS partitions", + "no arguments\n" + " - list images in flash\n" + "exists <image>\n" + " - returns 1 if an image exists, else 0\n" + "load <image>\n" + " - load an image to the location indicated in the header\n" + "load <image> 0x<address>\n" + " - load an image to the location specified\n"); diff --git a/cmd/avb.c b/cmd/avb.c new file mode 100644 index 00000000000..8fbd48ee5a2 --- /dev/null +++ b/cmd/avb.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2018, Linaro Limited + */ + +#include <avb_verify.h> +#include <command.h> +#include <env.h> +#include <image.h> +#include <malloc.h> +#include <mmc.h> + +#define AVB_BOOTARGS "avb_bootargs" + +static struct AvbOps *avb_ops; + +int do_avb_init(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + unsigned long mmc_dev; + + if (argc != 2) + return CMD_RET_USAGE; + + mmc_dev = hextoul(argv[1], NULL); + + if (avb_ops) + avb_ops_free(avb_ops); + + avb_ops = avb_ops_alloc(mmc_dev); + if (avb_ops) + return CMD_RET_SUCCESS; + else + printf("Can't allocate AvbOps"); + + printf("Failed to initialize AVB\n"); + + return CMD_RET_FAILURE; +} + +int do_avb_read_part(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *part; + s64 offset; + size_t bytes, bytes_read = 0; + void *buffer; + int ret; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc != 5) + return CMD_RET_USAGE; + + part = argv[1]; + offset = hextoul(argv[2], NULL); + bytes = hextoul(argv[3], NULL); + buffer = (void *)hextoul(argv[4], NULL); + + ret = avb_ops->read_from_partition(avb_ops, part, offset, + bytes, buffer, &bytes_read); + if (ret == AVB_IO_RESULT_OK) { + printf("Read %zu bytes\n", bytes_read); + return CMD_RET_SUCCESS; + } + + printf("Failed to read from partition '%s', err = %d\n", + part, ret); + + return CMD_RET_FAILURE; +} + +int do_avb_read_part_hex(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *part; + s64 offset; + size_t bytes, bytes_read = 0; + char *buffer; + int ret; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc != 4) + return CMD_RET_USAGE; + + part = argv[1]; + offset = hextoul(argv[2], NULL); + bytes = hextoul(argv[3], NULL); + + buffer = malloc(bytes); + if (!buffer) { + printf("Failed to tlb_allocate buffer for data\n"); + return CMD_RET_FAILURE; + } + memset(buffer, 0, bytes); + + ret = avb_ops->read_from_partition(avb_ops, part, offset, + bytes, buffer, &bytes_read); + if (ret == AVB_IO_RESULT_OK) { + printf("Requested %zu, read %zu bytes\n", bytes, bytes_read); + printf("Data: "); + for (int i = 0; i < bytes_read; i++) + printf("%02X", buffer[i]); + + printf("\n"); + + free(buffer); + return CMD_RET_SUCCESS; + } + + printf("Failed to read from partition '%s', err = %d\n", + part, ret); + + free(buffer); + return CMD_RET_FAILURE; +} + +int do_avb_write_part(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *part; + s64 offset; + size_t bytes; + void *buffer; + int ret; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc != 5) + return CMD_RET_USAGE; + + part = argv[1]; + offset = hextoul(argv[2], NULL); + bytes = hextoul(argv[3], NULL); + buffer = (void *)hextoul(argv[4], NULL); + + ret = avb_ops->write_to_partition(avb_ops, part, offset, + bytes, buffer); + if (ret == AVB_IO_RESULT_OK) { + printf("Wrote %zu bytes\n", bytes); + return CMD_RET_SUCCESS; + } + + printf("Failed to write in partition '%s', err = %d\n", + part, ret); + + return CMD_RET_FAILURE; +} + +int do_avb_read_rb(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + size_t index; + u64 rb_idx; + int ret; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc != 2) + return CMD_RET_USAGE; + + index = (size_t)hextoul(argv[1], NULL); + + ret = avb_ops->read_rollback_index(avb_ops, index, &rb_idx); + if (ret == AVB_IO_RESULT_OK) { + printf("Rollback index: %llx\n", rb_idx); + return CMD_RET_SUCCESS; + } + + printf("Failed to read rollback index id = %zu, err = %d\n", + index, ret); + + return CMD_RET_FAILURE; +} + +int do_avb_write_rb(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + size_t index; + u64 rb_idx; + int ret; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc != 3) + return CMD_RET_USAGE; + + index = (size_t)hextoul(argv[1], NULL); + rb_idx = hextoul(argv[2], NULL); + + ret = avb_ops->write_rollback_index(avb_ops, index, rb_idx); + if (ret == AVB_IO_RESULT_OK) + return CMD_RET_SUCCESS; + + printf("Failed to write rollback index id = %zu, err = %d\n", + index, ret); + + return CMD_RET_FAILURE; +} + +int do_avb_get_uuid(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + const char *part; + char buffer[UUID_STR_LEN + 1]; + int ret; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc != 2) + return CMD_RET_USAGE; + + part = argv[1]; + + ret = avb_ops->get_unique_guid_for_partition(avb_ops, part, + buffer, + UUID_STR_LEN + 1); + if (ret == AVB_IO_RESULT_OK) { + printf("'%s' UUID: %s\n", part, buffer); + return CMD_RET_SUCCESS; + } + + printf("Failed to read partition '%s' UUID, err = %d\n", + part, ret); + + return CMD_RET_FAILURE; +} + +int do_avb_verify_part(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + const char * const requested_partitions[] = {"boot", NULL}; + AvbSlotVerifyResult slot_result; + AvbSlotVerifyData *out_data; + enum avb_boot_state boot_state; + char *cmdline; + char *extra_args; + char *slot_suffix = ""; + int ret; + + bool unlocked = false; + int res = CMD_RET_FAILURE; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc < 1 || argc > 2) + return CMD_RET_USAGE; + + if (argc == 2) + slot_suffix = argv[1]; + + printf("## Android Verified Boot 2.0 version %s\n", + avb_version_string()); + + ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked); + if (ret != AVB_IO_RESULT_OK) { + printf("Can't determine device lock state, err = %d\n", + ret); + return CMD_RET_FAILURE; + } + + slot_result = + avb_slot_verify(avb_ops, + requested_partitions, + slot_suffix, + unlocked, + AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, + &out_data); + + /* + * LOCKED devices with custom root of trust setup is not supported (YELLOW) + */ + if (slot_result == AVB_SLOT_VERIFY_RESULT_OK) { + printf("Verification passed successfully\n"); + + /* + * ORANGE state indicates that device may be freely modified. + * Device integrity is left to the user to verify out-of-band. + */ + if (unlocked) + boot_state = AVB_ORANGE; + else + boot_state = AVB_GREEN; + + /* export boot state to AVB_BOOTARGS env var */ + extra_args = avb_set_state(avb_ops, boot_state); + if (extra_args) + cmdline = append_cmd_line(out_data->cmdline, + extra_args); + else + cmdline = out_data->cmdline; + + env_set(AVB_BOOTARGS, cmdline); + + res = CMD_RET_SUCCESS; + } else { + printf("Verification failed, reason: %s\n", str_avb_slot_error(slot_result)); + } + + if (out_data) + avb_slot_verify_data_free(out_data); + + return res; +} + +int do_avb_is_unlocked(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + bool unlock; + int ret; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc != 1) { + printf("--%s(-1)\n", __func__); + return CMD_RET_USAGE; + } + + ret = avb_ops->read_is_device_unlocked(avb_ops, &unlock); + if (ret == AVB_IO_RESULT_OK) { + printf("Unlocked = %d\n", unlock); + return CMD_RET_SUCCESS; + } + + printf("Can't determine device lock state, err = %d\n", + ret); + + return CMD_RET_FAILURE; +} + +int do_avb_read_pvalue(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *name; + size_t bytes; + size_t bytes_read; + void *buffer; + char *endp; + int ret; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc != 3) + return CMD_RET_USAGE; + + name = argv[1]; + bytes = dectoul(argv[2], &endp); + if (*endp && *endp != '\n') + return CMD_RET_USAGE; + + buffer = malloc(bytes); + if (!buffer) + return CMD_RET_FAILURE; + + ret = avb_ops->read_persistent_value(avb_ops, name, bytes, + buffer, &bytes_read); + if (ret == AVB_IO_RESULT_OK) { + printf("Read %zu bytes, value = %s\n", bytes_read, + (char *)buffer); + free(buffer); + return CMD_RET_SUCCESS; + } + + printf("Failed to read persistent value, err = %d\n", ret); + + free(buffer); + + return CMD_RET_FAILURE; +} + +int do_avb_write_pvalue(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *name; + const char *value; + int ret; + + if (!avb_ops) { + printf("AVB is not initialized, please run 'avb init <id>'\n"); + return CMD_RET_FAILURE; + } + + if (argc != 3) + return CMD_RET_USAGE; + + name = argv[1]; + value = argv[2]; + + ret = avb_ops->write_persistent_value(avb_ops, name, + strlen(value) + 1, + (const uint8_t *)value); + if (ret == AVB_IO_RESULT_OK) { + printf("Wrote %zu bytes\n", strlen(value) + 1); + return CMD_RET_SUCCESS; + } + + printf("Failed to write persistent value `%s` = `%s`, err = %d\n", + name, value, ret); + + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_avb[] = { + U_BOOT_CMD_MKENT(init, 2, 0, do_avb_init, "", ""), + U_BOOT_CMD_MKENT(read_rb, 2, 0, do_avb_read_rb, "", ""), + U_BOOT_CMD_MKENT(write_rb, 3, 0, do_avb_write_rb, "", ""), + U_BOOT_CMD_MKENT(is_unlocked, 1, 0, do_avb_is_unlocked, "", ""), + U_BOOT_CMD_MKENT(get_uuid, 2, 0, do_avb_get_uuid, "", ""), + U_BOOT_CMD_MKENT(read_part, 5, 0, do_avb_read_part, "", ""), + U_BOOT_CMD_MKENT(read_part_hex, 4, 0, do_avb_read_part_hex, "", ""), + U_BOOT_CMD_MKENT(write_part, 5, 0, do_avb_write_part, "", ""), + U_BOOT_CMD_MKENT(verify, 2, 0, do_avb_verify_part, "", ""), +#ifdef CONFIG_OPTEE_TA_AVB + U_BOOT_CMD_MKENT(read_pvalue, 3, 0, do_avb_read_pvalue, "", ""), + U_BOOT_CMD_MKENT(write_pvalue, 3, 0, do_avb_write_pvalue, "", ""), +#endif +}; + +static int do_avb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *cp; + + cp = find_cmd_tbl(argv[1], cmd_avb, ARRAY_SIZE(cmd_avb)); + + argc--; + argv++; + + if (!cp || argc > cp->maxargs) + return CMD_RET_USAGE; + + if (flag == CMD_FLAG_REPEAT) + return CMD_RET_FAILURE; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD( + avb, 29, 0, do_avb, + "Provides commands for testing Android Verified Boot 2.0 functionality", + "init <dev> - initialize avb2 for <dev>\n" + "avb read_rb <num> - read rollback index at location <num>\n" + "avb write_rb <num> <rb> - write rollback index <rb> to <num>\n" + "avb is_unlocked - returns unlock status of the device\n" + "avb get_uuid <partname> - read and print uuid of partition <part>\n" + "avb read_part <partname> <offset> <num> <addr> - read <num> bytes from\n" + " partition <partname> to buffer <addr>\n" + "avb read_part_hex <partname> <offset> <num> - read <num> bytes from\n" + " partition <partname> and print to stdout\n" + "avb write_part <partname> <offset> <num> <addr> - write <num> bytes to\n" + " <partname> by <offset> using data from <addr>\n" +#ifdef CONFIG_OPTEE_TA_AVB + "avb read_pvalue <name> <bytes> - read a persistent value <name>\n" + "avb write_pvalue <name> <value> - write a persistent value <name>\n" +#endif + "avb verify [slot_suffix] - run verification process using hash data\n" + " from vbmeta structure\n" + " [slot_suffix] - _a, _b, etc (if vbmeta partition is slotted)\n" + ); diff --git a/cmd/axi.c b/cmd/axi.c new file mode 100644 index 00000000000..3dbea0499de --- /dev/null +++ b/cmd/axi.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2016 + * Dirk Eibach, Guntermann & Drunck GmbH, dirk.eibach@gdsys.cc + * + * (C) Copyright 2017, 2018 + * Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <axi.h> +#include <command.h> +#include <console.h> +#include <display_options.h> +#include <dm.h> +#include <log.h> + +/* Currently selected AXI bus device */ +static struct udevice *axi_cur_bus; +/* Transmission size from last command */ +static uint dp_last_size; +/* Address from last command */ +static uint dp_last_addr; +/* Number of bytes to display from last command; default = 64 */ +static uint dp_last_length = 0x40; + +/** + * show_bus() - Show devices on a single AXI bus + * @bus: The AXI bus device to printt information for + */ +static void show_bus(struct udevice *bus) +{ + struct udevice *dev; + + printf("Bus %d:\t%s", dev_seq(bus), bus->name); + if (device_active(bus)) + printf(" (active)"); + printf("\n"); + for (device_find_first_child(bus, &dev); + dev; + device_find_next_child(&dev)) + printf(" %s\n", dev->name); +} + +/** + * axi_set_cur_bus() - Set the currently active AXI bus + * @busnum: The number of the bus (i.e. its sequence number) that should be + * made active + * + * The operations supplied by this command operate only on the currently active + * bus. + * + * Return: 0 if OK, -ve on error + */ +static int axi_set_cur_bus(unsigned int busnum) +{ + struct udevice *bus; + struct udevice *dummy; + int ret; + + /* Make sure that all sequence numbers are initialized */ + for (uclass_first_device(UCLASS_AXI, &dummy); + dummy; + uclass_next_device(&dummy)) + ; + + ret = uclass_get_device_by_seq(UCLASS_AXI, busnum, &bus); + if (ret) { + debug("%s: No bus %d\n", __func__, busnum); + return ret; + } + axi_cur_bus = bus; + + return 0; +} + +/** + * axi_get_cur_bus() - Retrieve the currently active AXI bus device + * @busp: Pointer to a struct udevice that receives the currently active bus + * device + * + * Return: 0 if OK, -ve on error + */ +static int axi_get_cur_bus(struct udevice **busp) +{ + if (!axi_cur_bus) { + puts("No AXI bus selected\n"); + return -ENODEV; + } + *busp = axi_cur_bus; + + return 0; +} + +/* + * Command handlers + */ + +static int do_axi_show_bus(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dummy; + + /* Make sure that all sequence numbers are initialized */ + for (uclass_first_device(UCLASS_AXI, &dummy); + dummy; + uclass_next_device(&dummy)) + ; + + if (argc == 1) { + /* show all busses */ + struct udevice *bus; + + for (uclass_first_device(UCLASS_AXI, &bus); + bus; + uclass_next_device(&bus)) + show_bus(bus); + } else { + int i; + + /* show specific bus */ + i = dectoul(argv[1], NULL); + + struct udevice *bus; + int ret; + + ret = uclass_get_device_by_seq(UCLASS_AXI, i, &bus); + if (ret) { + printf("Invalid bus %d: err=%d\n", i, ret); + return CMD_RET_FAILURE; + } + show_bus(bus); + } + + return 0; +} + +static int do_axi_bus_num(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret = 0; + int bus_no; + + if (argc == 1) { + /* querying current setting */ + struct udevice *bus; + + if (!axi_get_cur_bus(&bus)) + bus_no = dev_seq(bus); + else + bus_no = -1; + + printf("Current bus is %d\n", bus_no); + } else { + bus_no = dectoul(argv[1], NULL); + printf("Setting bus to %d\n", bus_no); + + ret = axi_set_cur_bus(bus_no); + if (ret) + printf("Failure changing bus number (%d)\n", ret); + } + + return ret ? CMD_RET_FAILURE : 0; +} + +static int do_axi_md(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + /* Print that many bytes per line */ + const uint DISP_LINE_LEN = 16; + u8 linebuf[DISP_LINE_LEN]; + unsigned int k; + ulong addr, length, size; + ulong nbytes; + enum axi_size_t axisize; + int unitsize; + + /* + * We use the last specified parameters, unless new ones are + * entered. + */ + size = dp_last_size; + addr = dp_last_addr; + length = dp_last_length; + + if (argc < 3) + return CMD_RET_USAGE; + + if (!axi_cur_bus) { + puts("No AXI bus selected\n"); + return CMD_RET_FAILURE; + } + + if ((flag & CMD_FLAG_REPEAT) == 0) { + size = dectoul(argv[1], NULL); + + /* + * Address is specified since argc >= 3 + */ + addr = hextoul(argv[2], NULL); + + /* + * If there's another parameter, it is the length to display; + * length is the number of objects, not number of bytes + */ + if (argc > 3) + length = hextoul(argv[3], NULL); + } + + switch (size) { + case 8: + axisize = AXI_SIZE_8; + unitsize = 1; + break; + case 16: + axisize = AXI_SIZE_16; + unitsize = 2; + break; + case 32: + axisize = AXI_SIZE_32; + unitsize = 4; + break; + default: + printf("Unknown read size '%lu'\n", size); + return CMD_RET_USAGE; + }; + + nbytes = length * unitsize; + do { + ulong linebytes = (nbytes > DISP_LINE_LEN) ? + DISP_LINE_LEN : nbytes; + + for (k = 0; k < linebytes / unitsize; ++k) { + int ret = axi_read(axi_cur_bus, addr + k * unitsize, + linebuf + k * unitsize, axisize); + + if (!ret) /* Continue if axi_read was successful */ + continue; + + if (ret == -ENOSYS) + printf("axi_read failed; read size not supported?\n"); + else + printf("axi_read failed: err = %d\n", ret); + + return CMD_RET_FAILURE; + } + print_buffer(addr, (void *)linebuf, unitsize, + linebytes / unitsize, + DISP_LINE_LEN / unitsize); + + nbytes -= max(linebytes, 1UL); + addr += linebytes; + + if (ctrlc()) + break; + } while (nbytes > 0); + + dp_last_size = size; + dp_last_addr = addr; + dp_last_length = length; + + return 0; +} + +static int do_axi_mw(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 writeval; + ulong addr, count, size; + enum axi_size_t axisize; + + if (argc <= 3 || argc >= 6) + return CMD_RET_USAGE; + + size = dectoul(argv[1], NULL); + + switch (size) { + case 8: + axisize = AXI_SIZE_8; + break; + case 16: + axisize = AXI_SIZE_16; + break; + case 32: + axisize = AXI_SIZE_32; + break; + default: + printf("Unknown write size '%lu'\n", size); + return CMD_RET_USAGE; + }; + + /* Address is specified since argc > 4 */ + addr = hextoul(argv[2], NULL); + + /* Get the value to write */ + writeval = hextoul(argv[3], NULL); + + /* Count ? */ + if (argc == 5) + count = hextoul(argv[4], NULL); + else + count = 1; + + while (count-- > 0) { + int ret = axi_write(axi_cur_bus, addr + count * sizeof(u32), + &writeval, axisize); + + if (ret) { + printf("axi_write failed: err = %d\n", ret); + return CMD_RET_FAILURE; + } + } + + return 0; +} + +static struct cmd_tbl cmd_axi_sub[] = { + U_BOOT_CMD_MKENT(bus, 1, 1, do_axi_show_bus, "", ""), + U_BOOT_CMD_MKENT(dev, 1, 1, do_axi_bus_num, "", ""), + U_BOOT_CMD_MKENT(md, 4, 1, do_axi_md, "", ""), + U_BOOT_CMD_MKENT(mw, 5, 1, do_axi_mw, "", ""), +}; + +static int do_ihs_axi(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c; + + if (argc < 2) + return CMD_RET_USAGE; + + /* Strip off leading 'axi' command argument */ + argc--; + argv++; + + /* Hand off rest of command line to sub-commands */ + c = find_cmd_tbl(argv[0], &cmd_axi_sub[0], ARRAY_SIZE(cmd_axi_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_LONGHELP(axi, + "bus - show AXI bus info\n" + "axi dev [bus] - show or set current AXI bus to bus number [bus]\n" + "axi md size addr [# of objects] - read from AXI device at address [addr] and data width [size] (one of 8, 16, 32)\n" + "axi mw size addr value [count] - write data [value] to AXI device at address [addr] and data width [size] (one of 8, 16, 32)\n"); + +U_BOOT_CMD(axi, 7, 1, do_ihs_axi, + "AXI sub-system", + axi_help_text +); diff --git a/cmd/bcb.c b/cmd/bcb.c new file mode 100644 index 00000000000..d6d944bd6b3 --- /dev/null +++ b/cmd/bcb.c @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 Eugeniu Rosca <rosca.eugeniu@gmail.com> + * + * Command to read/modify/write Android BCB fields + */ + +#include <android_bootloader_message.h> +#include <bcb.h> +#include <command.h> +#include <env.h> +#include <android_ab.h> +#include <display_options.h> +#include <log.h> +#include <part.h> +#include <malloc.h> +#include <memalign.h> +#include <vsprintf.h> +#include <linux/err.h> + +static const char * const fields[] = { + "command", + "status", + "recovery", + "stage" +}; + +static struct bootloader_message bcb __aligned(ARCH_DMA_MINALIGN) = { { 0 } }; +static struct disk_partition partition_data; + +static struct blk_desc *block; +static struct disk_partition *partition = &partition_data; + +static int bcb_not_loaded(void) +{ + printf("Error: Please, load BCB first!\n"); + return -1; +} + +static int bcb_field_get(const char *name, char **fieldp, int *sizep) +{ + if (!strcmp(name, "command")) { + *fieldp = bcb.command; + *sizep = sizeof(bcb.command); + } else if (!strcmp(name, "status")) { + *fieldp = bcb.status; + *sizep = sizeof(bcb.status); + } else if (!strcmp(name, "recovery")) { + *fieldp = bcb.recovery; + *sizep = sizeof(bcb.recovery); + } else if (!strcmp(name, "stage")) { + *fieldp = bcb.stage; + *sizep = sizeof(bcb.stage); + } else if (!strcmp(name, "reserved")) { + *fieldp = bcb.reserved; + *sizep = sizeof(bcb.reserved); + } else { + printf("Error: Unknown bcb field '%s'\n", name); + return -1; + } + + return 0; +} + +static void __bcb_reset(void) +{ + block = NULL; + partition = &partition_data; + memset(&partition_data, 0, sizeof(struct disk_partition)); + memset(&bcb, 0, sizeof(struct bootloader_message)); +} + +static int __bcb_initialize(const char *iface, int devnum, const char *partp) +{ + char *endp; + int part, ret; + + block = blk_get_dev(iface, devnum); + if (!block) { + ret = -ENODEV; + goto err_read_fail; + } + + /* + * always select the first hwpart in case another + * blk operation selected a different hwpart + */ + ret = blk_dselect_hwpart(block, 0); + if (IS_ERR_VALUE(ret)) { + ret = -ENODEV; + goto err_read_fail; + } + + part = simple_strtoul(partp, &endp, 0); + if (*endp == '\0') { + ret = part_get_info(block, part, partition); + if (ret) + goto err_read_fail; + } else { + part = part_get_info_by_name(block, partp, partition); + if (part < 0) { + ret = part; + goto err_read_fail; + } + } + + return CMD_RET_SUCCESS; + +err_read_fail: + printf("Error: %s %d:%s read failed (%d)\n", iface, devnum, + partition->name, ret); + __bcb_reset(); + return CMD_RET_FAILURE; +} + +static int __bcb_load(void) +{ + u64 cnt; + int ret; + + cnt = DIV_ROUND_UP(sizeof(struct bootloader_message), partition->blksz); + if (cnt > partition->size) + goto err_too_small; + + if (blk_dread(block, partition->start, cnt, &bcb) != cnt) { + ret = -EIO; + goto err_read_fail; + } + + debug("%s: Loaded from %d %d:%s\n", __func__, block->uclass_id, + block->devnum, partition->name); + + return CMD_RET_SUCCESS; +err_read_fail: + printf("Error: %d %d:%s read failed (%d)\n", block->uclass_id, + block->devnum, partition->name, ret); + goto err; +err_too_small: + printf("Error: %d %d:%s too small!", block->uclass_id, + block->devnum, partition->name); +err: + __bcb_reset(); + return CMD_RET_FAILURE; +} + +static int do_bcb_load(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + int ret; + int devnum; + char *endp; + char *iface = "mmc"; + + if (argc < 3) + return CMD_RET_USAGE; + + if (argc == 4) { + iface = argv[1]; + argc--; + argv++; + } + + devnum = simple_strtoul(argv[1], &endp, 0); + if (*endp != '\0') { + printf("Error: Device id '%s' not a number\n", argv[1]); + return CMD_RET_FAILURE; + } + + ret = __bcb_initialize(iface, devnum, argv[2]); + if (ret != CMD_RET_SUCCESS) + return ret; + + return __bcb_load(); +} + +static int __bcb_set(const char *fieldp, const char *valp) +{ + int size, len; + char *field, *str, *found, *tmp; + + if (bcb_field_get(fieldp, &field, &size)) + return CMD_RET_FAILURE; + + len = strlen(valp); + if (len >= size) { + printf("Error: sizeof('%s') = %d >= %d = sizeof(bcb.%s)\n", + valp, len, size, fieldp); + return CMD_RET_FAILURE; + } + str = strdup(valp); + if (!str) { + printf("Error: Out of memory while strdup\n"); + return CMD_RET_FAILURE; + } + + tmp = str; + field[0] = '\0'; + while ((found = strsep(&tmp, ":"))) { + if (field[0] != '\0') + strcat(field, "\n"); + strcat(field, found); + } + free(str); + + return CMD_RET_SUCCESS; +} + +static int do_bcb_set(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + if (argc < 3) + return CMD_RET_USAGE; + + if (!block) + return bcb_not_loaded(); + + return __bcb_set(argv[1], argv[2]); +} + +static int do_bcb_clear(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int size; + char *field; + + if (!block) + return bcb_not_loaded(); + + if (argc == 1) { + memset(&bcb, 0, sizeof(bcb)); + return CMD_RET_SUCCESS; + } + + if (bcb_field_get(argv[1], &field, &size)) + return CMD_RET_FAILURE; + + memset(field, 0, size); + + return CMD_RET_SUCCESS; +} + +static int do_bcb_test(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int size; + char *field; + char *op; + + if (argc < 4) + return CMD_RET_USAGE; + + if (!block) + return bcb_not_loaded(); + + op = argv[2]; + + if (bcb_field_get(argv[1], &field, &size)) + return CMD_RET_FAILURE; + + if (*op == '=' && *(op + 1) == '\0') { + if (!strncmp(argv[3], field, size)) + return CMD_RET_SUCCESS; + else + return CMD_RET_FAILURE; + } else if (*op == '~' && *(op + 1) == '\0') { + if (!strstr(field, argv[3])) + return CMD_RET_FAILURE; + else + return CMD_RET_SUCCESS; + } else { + printf("Error: Unknown operator '%s'\n", op); + } + + return CMD_RET_FAILURE; +} + +static int do_bcb_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int size; + char *field; + + if (argc < 2) + return CMD_RET_USAGE; + + if (!block) + return bcb_not_loaded(); + + if (bcb_field_get(argv[1], &field, &size)) + return CMD_RET_FAILURE; + + print_buffer((ulong)field - (ulong)&bcb, (void *)field, 1, size, 16); + + return CMD_RET_SUCCESS; +} + +static int __bcb_store(void) +{ + u64 cnt; + int ret; + + cnt = DIV_ROUND_UP(sizeof(struct bootloader_message), partition->blksz); + + if (blk_dwrite(block, partition->start, cnt, &bcb) != cnt) { + ret = -EIO; + goto err; + } + + return CMD_RET_SUCCESS; +err: + printf("Error: %d %d:%s write failed (%d)\n", block->uclass_id, + block->devnum, partition->name, ret); + + return CMD_RET_FAILURE; +} + +static int do_bcb_store(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + if (!block) + return bcb_not_loaded(); + + return __bcb_store(); +} + +int bcb_find_partition_and_load(const char *iface, int devnum, char *partp) +{ + int ret; + + __bcb_reset(); + + ret = __bcb_initialize(iface, devnum, partp); + if (ret != CMD_RET_SUCCESS) + return ret; + + return __bcb_load(); +} + +int bcb_load(struct blk_desc *block_description, struct disk_partition *disk_partition) +{ + __bcb_reset(); + + block = block_description; + partition = disk_partition; + + return __bcb_load(); +} + +int bcb_set(enum bcb_field field, const char *value) +{ + if (field > BCB_FIELD_STAGE) + return CMD_RET_FAILURE; + return __bcb_set(fields[field], value); +} + +int bcb_get(enum bcb_field field, char *value_out, size_t value_size) +{ + int size; + char *field_value; + + if (field > BCB_FIELD_STAGE) + return CMD_RET_FAILURE; + if (bcb_field_get(fields[field], &field_value, &size)) + return CMD_RET_FAILURE; + + strlcpy(value_out, field_value, value_size); + + return CMD_RET_SUCCESS; +} + +int bcb_store(void) +{ + return __bcb_store(); +} + +void bcb_reset(void) +{ + __bcb_reset(); +} + +__maybe_unused static int do_bcb_ab_select(struct cmd_tbl *cmdtp, + int flag, int argc, + char * const argv[]) +{ + int ret; + struct blk_desc *dev_desc; + struct disk_partition part_info; + char slot[2]; + bool dec_tries = true; + + if (argc < 4) + return CMD_RET_USAGE; + + for (int i = 4; i < argc; i++) { + if (!strcmp(argv[i], "--no-dec")) + dec_tries = false; + else + return CMD_RET_USAGE; + } + + /* Lookup the "misc" partition from argv[2] and argv[3] */ + if (part_get_info_by_dev_and_name_or_num(argv[2], argv[3], + &dev_desc, &part_info, + false) < 0) { + return CMD_RET_FAILURE; + } + + ret = ab_select_slot(dev_desc, &part_info, dec_tries); + if (ret < 0) { + printf("Android boot failed, error %d.\n", ret); + return CMD_RET_FAILURE; + } + + /* Android standard slot names are 'a', 'b', ... */ + slot[0] = BOOT_SLOT_NAME(ret); + slot[1] = '\0'; + env_set(argv[1], slot); + printf("ANDROID: Booting slot: %s\n", slot); + + return CMD_RET_SUCCESS; +} + +__maybe_unused static int do_bcb_ab_dump(struct cmd_tbl *cmdtp, + int flag, int argc, + char *const argv[]) +{ + int ret; + struct blk_desc *dev_desc; + struct disk_partition part_info; + + if (argc < 3) + return CMD_RET_USAGE; + + if (part_get_info_by_dev_and_name_or_num(argv[1], argv[2], + &dev_desc, &part_info, + false) < 0) { + return CMD_RET_FAILURE; + } + + ret = ab_dump_abc(dev_desc, &part_info); + if (ret < 0) { + printf("Cannot dump ABC data, error %d.\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(bcb, + "load <interface> <dev> <part> - load BCB from <interface> <dev>:<part>\n" + "load <dev> <part> - load BCB from mmc <dev>:<part>\n" + "bcb set <field> <val> - set BCB <field> to <val>\n" + "bcb clear [<field>] - clear BCB <field> or all fields\n" + "bcb test <field> <op> <val> - test BCB <field> against <val>\n" + "bcb dump <field> - dump BCB <field>\n" + "bcb store - store BCB back to <interface>\n" + "\n" +#if IS_ENABLED(CONFIG_ANDROID_AB) + "bcb ab_select -\n" + " Select the slot used to boot from and register the boot attempt.\n" + " <slot_var_name> <interface> <dev[:part|#part_name]> [--no-dec]\n" + " - Load the slot metadata from the partition 'part' on\n" + " device type 'interface' instance 'dev' and store the active\n" + " slot in the 'slot_var_name' variable. This also updates the\n" + " Android slot metadata with a boot attempt, which can cause\n" + " successive calls to this function to return a different result\n" + " if the returned slot runs out of boot attempts.\n" + " - If 'part_name' is passed, preceded with a # instead of :, the\n" + " partition name whose label is 'part_name' will be looked up in\n" + " the partition table. This is commonly the \"misc\" partition.\n" + " - If '--no-dec' is set, the number of tries remaining will not\n" + " decremented for the selected boot slot\n" + "\n" + "bcb ab_dump -\n" + " Dump boot_control information from specific partition.\n" + " <interface> <dev[:part|#part_name]>\n" + "\n" +#endif + "Legend:\n" + "<interface> - storage device interface (virtio, mmc, etc)\n" + "<dev> - storage device index containing the BCB partition\n" + "<part> - partition index or name containing the BCB\n" + "<field> - one of {command,status,recovery,stage,reserved}\n" + "<op> - the binary operator used in 'bcb test':\n" + " '=' returns true if <val> matches the string stored in <field>\n" + " '~' returns true if <val> matches a subset of <field>'s string\n" + "<val> - string/text provided as input to bcb {set,test}\n" + " NOTE: any ':' character in <val> will be replaced by line feed\n" + " during 'bcb set' and used as separator by upper layers\n" +); + +U_BOOT_CMD_WITH_SUBCMDS(bcb, + "Load/set/clear/test/dump/store Android BCB fields", bcb_help_text, + U_BOOT_SUBCMD_MKENT(load, 4, 1, do_bcb_load), + U_BOOT_SUBCMD_MKENT(set, 3, 1, do_bcb_set), + U_BOOT_SUBCMD_MKENT(clear, 2, 1, do_bcb_clear), + U_BOOT_SUBCMD_MKENT(test, 4, 1, do_bcb_test), + U_BOOT_SUBCMD_MKENT(dump, 2, 1, do_bcb_dump), + U_BOOT_SUBCMD_MKENT(store, 1, 1, do_bcb_store), +#if IS_ENABLED(CONFIG_ANDROID_AB) + U_BOOT_SUBCMD_MKENT(ab_select, 5, 1, do_bcb_ab_select), + U_BOOT_SUBCMD_MKENT(ab_dump, 3, 1, do_bcb_ab_dump), +#endif +); diff --git a/cmd/bdinfo.c b/cmd/bdinfo.c new file mode 100644 index 00000000000..20c8c97f0cd --- /dev/null +++ b/cmd/bdinfo.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Implements the 'bd' command to show board information + * + * (C) Copyright 2003 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <dm.h> +#include <env.h> +#include <getopt.h> +#include <lmb.h> +#include <mapmem.h> +#include <net.h> +#include <serial.h> +#include <video.h> +#include <vsprintf.h> +#include <asm/cache.h> +#include <asm/global_data.h> +#include <display_options.h> + +DECLARE_GLOBAL_DATA_PTR; + +void bdinfo_print_size(const char *name, uint64_t size) +{ + printf("%-12s= ", name); + print_size(size, "\n"); +} + +void bdinfo_print_str(const char *name, const char *str) +{ + printf("%-12s= %s\n", name, str); +} + +void bdinfo_print_num_l(const char *name, ulong value) +{ + printf("%-12s= 0x%0*lx\n", name, 2 * (int)sizeof(value), value); +} + +void bdinfo_print_num_ll(const char *name, unsigned long long value) +{ + printf("%-12s= 0x%.*llx\n", name, 2 * (int)sizeof(ulong), value); +} + +static void print_eth(void) +{ + const int idx = eth_get_dev_index(); + char ipstr[] = "ipaddr\0\0"; + uchar enetaddr[6]; + char name[10]; + int ret; + + if (idx) + sprintf(name, "eth%iaddr", idx); + else + strcpy(name, "ethaddr"); + + ret = eth_env_get_enetaddr_by_index("eth", idx, enetaddr); + + printf("current eth = %s\n", eth_get_name()); + if (!ret) + printf("%-12s= (not set)\n", name); + else + printf("%-12s= %pM\n", name, enetaddr); + + if (idx > 0) + sprintf(ipstr, "ipaddr%d", idx); + + printf("IP addr = %s\n", env_get(ipstr)); +} + +void bdinfo_print_mhz(const char *name, unsigned long hz) +{ + char buf[32]; + + printf("%-12s= %6s MHz\n", name, strmhz(buf, hz)); +} + +static void print_bi_dram(const struct bd_info *bd) +{ + int i; + + for (i = 0; i < CONFIG_NR_DRAM_BANKS; ++i) { + if (bd->bi_dram[i].size) { + bdinfo_print_num_l("DRAM bank", i); + bdinfo_print_num_ll("-> start", bd->bi_dram[i].start); + bdinfo_print_num_ll("-> size", bd->bi_dram[i].size); + } + } +} + +__weak void arch_print_bdinfo(void) +{ +} + +static void show_video_info(void) +{ + const struct udevice *dev; + struct uclass *uc; + + uclass_id_foreach_dev(UCLASS_VIDEO, dev, uc) { + printf("%-12s= %s %sactive\n", "Video", dev->name, + device_active(dev) ? "" : "in"); + if (device_active(dev)) { + struct video_priv *upriv = dev_get_uclass_priv(dev); + struct video_uc_plat *plat = dev_get_uclass_plat(dev); + + bdinfo_print_num_ll("FB base", (ulong)upriv->fb); + if (upriv->copy_fb) { + bdinfo_print_num_ll("FB copy", + (ulong)upriv->copy_fb); + bdinfo_print_num_l(" copy size", + plat->copy_size); + } + printf("%-12s= %dx%dx%d\n", "FB size", upriv->xsize, + upriv->ysize, 1 << upriv->bpix); + } + } +} + +static void print_serial(struct udevice *dev) +{ + struct serial_device_info info; + int ret; + + if (!dev || !IS_ENABLED(CONFIG_DM_SERIAL)) + return; + + ret = serial_getinfo(dev, &info); + if (ret) + return; + + bdinfo_print_num_l("serial addr", info.addr); + bdinfo_print_num_l(" width", info.reg_width); + bdinfo_print_num_l(" shift", info.reg_shift); + bdinfo_print_num_l(" offset", info.reg_offset); + bdinfo_print_num_l(" clock", info.clock); +} + +static int bdinfo_print_all(struct bd_info *bd) +{ +#ifdef DEBUG + bdinfo_print_num_l("bd address", (ulong)bd); +#endif + bdinfo_print_num_l("boot_params", (ulong)bd->bi_boot_params); + print_bi_dram(bd); + bdinfo_print_num_l("flashstart", (ulong)bd->bi_flashstart); + bdinfo_print_num_l("flashsize", (ulong)bd->bi_flashsize); + bdinfo_print_num_l("flashoffset", (ulong)bd->bi_flashoffset); + printf("baudrate = %u bps\n", gd->baudrate); + bdinfo_print_num_l("relocaddr", gd->relocaddr); + bdinfo_print_num_l("reloc off", gd->reloc_off); + printf("%-12s= %u-bit\n", "Build", (uint)sizeof(void *) * 8); + if (IS_ENABLED(CONFIG_CMD_NET) || IS_ENABLED(CONFIG_CMD_NET_LWIP)) + print_eth(); + bdinfo_print_num_l("fdt_blob", (ulong)map_to_sysmem(gd->fdt_blob)); + if (IS_ENABLED(CONFIG_VIDEO)) + show_video_info(); +#if CONFIG_IS_ENABLED(MULTI_DTB_FIT) + bdinfo_print_num_l("multi_dtb_fit", (ulong)gd->multi_dtb_fit); +#endif + if (IS_ENABLED(CONFIG_LMB) && gd->fdt_blob) { + lmb_dump_all_force(); + if (IS_ENABLED(CONFIG_OF_REAL)) + printf("devicetree = %s\n", fdtdec_get_srcname()); + } + print_serial(gd->cur_serial_dev); + + if (IS_ENABLED(CONFIG_CMD_BDINFO_EXTRA)) { + bdinfo_print_num_ll("stack ptr", (ulong)&bd); + bdinfo_print_num_ll("ram_top ptr", (ulong)gd->ram_top); + bdinfo_print_num_l("malloc base", gd_malloc_start()); + } + + arch_print_bdinfo(); + + return 0; +} + +int do_bdinfo(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct bd_info *bd = gd->bd; + struct getopt_state gs; + int opt; + + if (!CONFIG_IS_ENABLED(GETOPT) || argc == 1) + return bdinfo_print_all(bd); + + getopt_init_state(&gs); + while ((opt = getopt(&gs, argc, argv, "aem")) > 0) { + switch (opt) { + case 'a': + return bdinfo_print_all(bd); + case 'e': + if (!IS_ENABLED(CONFIG_CMD_NET) && + !IS_ENABLED(CONFIG_CMD_NET_LWIP)) + return CMD_RET_USAGE; + print_eth(); + return CMD_RET_SUCCESS; + case 'm': + print_bi_dram(bd); + return CMD_RET_SUCCESS; + default: + return CMD_RET_USAGE; + } + } + + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + bdinfo, 2, 1, do_bdinfo, + "print Board Info structure", + "" +); diff --git a/cmd/bind.c b/cmd/bind.c new file mode 100644 index 00000000000..c0d31f5eb16 --- /dev/null +++ b/cmd/bind.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2018 JJ Hiblot <jjhiblot@ti.com> + */ + +#include <command.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/root.h> +#include <dm/uclass-internal.h> + +static int bind_by_class_seq(const char *uclass, int seq, + const char *drv_name) +{ + static enum uclass_id uclass_id; + struct udevice *dev; + struct udevice *parent; + int ret; + struct driver *drv; + + drv = lists_driver_lookup_name(drv_name); + if (!drv) { + printf("Cannot find driver '%s'\n", drv_name); + return -ENOENT; + } + + uclass_id = uclass_get_by_name(uclass); + if (uclass_id == UCLASS_INVALID) { + printf("%s is not a valid uclass\n", uclass); + return -EINVAL; + } + + ret = uclass_find_device_by_seq(uclass_id, seq, &parent); + if (!parent || ret) { + printf("Cannot find device %d of class %s\n", seq, uclass); + return ret; + } + + ret = device_bind_with_driver_data(parent, drv, drv->name, 0, + ofnode_null(), &dev); + if (!dev || ret) { + printf("Unable to bind. err:%d\n", ret); + return ret; + } + + return 0; +} + +static int find_dev(const char *uclass, int seq, struct udevice **devp) +{ + static enum uclass_id uclass_id; + int rc; + + uclass_id = uclass_get_by_name(uclass); + if (uclass_id == UCLASS_INVALID) { + printf("%s is not a valid uclass\n", uclass); + return -EINVAL; + } + + rc = uclass_find_device_by_seq(uclass_id, seq, devp); + if (!*devp || rc) { + printf("Cannot find device %d of class %s\n", seq, uclass); + return rc; + } + + return 0; +} + +static int unbind_by_class_seq(const char *uclass, int seq) +{ + int ret; + struct udevice *dev; + + ret = find_dev(uclass, seq, &dev); + if (ret) + return ret; + + ret = device_remove(dev, DM_REMOVE_NORMAL); + if (ret) { + printf("Unable to remove. err:%d\n", ret); + return ret; + } + + ret = device_unbind(dev); + if (ret) { + printf("Unable to unbind. err:%d\n", ret); + return ret; + } + + return 0; +} + +static int unbind_child_by_class_seq(const char *uclass, int seq, + const char *drv_name) +{ + struct udevice *parent; + int ret; + struct driver *drv; + + drv = lists_driver_lookup_name(drv_name); + if (!drv) { + printf("Cannot find driver '%s'\n", drv_name); + return -ENOENT; + } + + ret = find_dev(uclass, seq, &parent); + if (ret) + return ret; + + ret = device_chld_remove(parent, drv, DM_REMOVE_NORMAL); + if (ret) + printf("Unable to remove all. err:%d\n", ret); + + ret = device_chld_unbind(parent, drv); + if (ret) + printf("Unable to unbind all. err:%d\n", ret); + + return ret; +} + +static int bind_by_node_path(const char *path, const char *drv_name) +{ + struct udevice *dev; + struct udevice *parent = NULL; + int ret; + ofnode ofnode; + struct driver *drv; + + drv = lists_driver_lookup_name(drv_name); + if (!drv) { + printf("%s is not a valid driver name\n", drv_name); + return -ENOENT; + } + + ofnode = ofnode_path(path); + if (!ofnode_valid(ofnode)) { + printf("%s is not a valid node path\n", path); + return -EINVAL; + } + + while (ofnode_valid(ofnode)) { + if (!device_find_global_by_ofnode(ofnode, &parent)) + break; + ofnode = ofnode_get_parent(ofnode); + } + + if (!parent) { + printf("Cannot find a parent device for node path %s\n", path); + return -ENODEV; + } + + ofnode = ofnode_path(path); + ret = lists_bind_fdt(parent, ofnode, &dev, drv, false); + + if (!dev || ret) { + printf("Unable to bind. err:%d\n", ret); + return ret; + } + + return 0; +} + +static int unbind_by_node_path(const char *path) +{ + struct udevice *dev; + int ret; + ofnode ofnode; + + ofnode = ofnode_path(path); + if (!ofnode_valid(ofnode)) { + printf("%s is not a valid node path\n", path); + return -EINVAL; + } + + ret = device_find_global_by_ofnode(ofnode, &dev); + + if (!dev || ret) { + printf("Cannot find a device with path %s\n", path); + return -ENODEV; + } + + ret = device_remove(dev, DM_REMOVE_NORMAL); + if (ret) { + printf("Unable to remove. err:%d\n", ret); + return ret; + } + + ret = device_unbind(dev); + if (ret) { + printf("Unable to unbind. err:%d\n", ret); + return ret; + } + + return 0; +} + +static int do_bind_unbind(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret = 0; + bool bind; + bool by_node; + + if (argc < 2) + return CMD_RET_USAGE; + + bind = (argv[0][0] == 'b'); + by_node = (argv[1][0] == '/'); + + if (by_node && bind) { + if (argc != 3) + return CMD_RET_USAGE; + ret = bind_by_node_path(argv[1], argv[2]); + } else if (by_node && !bind) { + if (argc != 2) + return CMD_RET_USAGE; + ret = unbind_by_node_path(argv[1]); + } else if (!by_node && bind) { + int seq = (argc > 2) ? dectoul(argv[2], NULL) : 0; + + if (argc != 4) + return CMD_RET_USAGE; + ret = bind_by_class_seq(argv[1], seq, argv[3]); + } else if (!by_node && !bind) { + int seq = (argc > 2) ? dectoul(argv[2], NULL) : 0; + + if (argc == 3) + ret = unbind_by_class_seq(argv[1], seq); + else if (argc == 4) + ret = unbind_child_by_class_seq(argv[1], seq, + argv[3]); + else + return CMD_RET_USAGE; + } + + if (ret) + return CMD_RET_FAILURE; + else + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + bind, 4, 0, do_bind_unbind, + "Bind a device to a driver", + "<node path> <driver>\n" + "bind <class> <seq> <driver>\n" + "Use 'dm tree' to list all devices registered in the driver model,\n" + "their path, class, sequence and current driver.\n" +); + +U_BOOT_CMD( + unbind, 4, 0, do_bind_unbind, + "Unbind a device from a driver", + "<node path>\n" + "unbind <class> <seq>\n" + "unbind <class> <seq> <driver>\n" + "Use 'dm tree' to list all devices registered in the driver model,\n" + "their path, class, sequence and current driver.\n" +); diff --git a/cmd/binop.c b/cmd/binop.c new file mode 100644 index 00000000000..10d91b5dbf2 --- /dev/null +++ b/cmd/binop.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <command.h> +#include <env.h> +#include <hexdump.h> +#include <malloc.h> +#include <mapmem.h> +#include <vsprintf.h> +#include <linux/ctype.h> + +enum { + OP_ID_XOR, + OP_ID_AND, + OP_ID_OR, +}; + +void write_to_env_var(char *varname, u8 *result, ulong len) +{ + char *str_output; + char *str_ptr; + int i; + + str_output = malloc(len * 2 + 1); + str_ptr = str_output; + + for (i = 0; i < len; i++) { + sprintf(str_ptr, "%02x", result[i]); + str_ptr += 2; + } + *str_ptr = '\0'; + env_set(varname, str_output); + + free(str_output); +} + +void read_from_env_var(char *varname, u8 *result) +{ + char *str_value; + + str_value = env_get(varname); + if (str_value) + hex2bin(result, str_value, strlen(str_value) / 2); + else + hex2bin(result, varname, strlen(varname) / 2); +} + +void read_from_mem(ulong addr, u8 *result, ulong len) +{ + u8 *src; + + src = map_sysmem(addr, len); + memcpy(result, src, len); + unmap_sysmem(src); +} + +void write_to_mem(char *varname, u8 *result, ulong len) +{ + ulong addr; + u8 *buf; + + addr = hextoul(varname, NULL); + buf = map_sysmem(addr, len); + memcpy(buf, result, len); + unmap_sysmem(buf); +} + +static int do_binop(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong len; + u8 *result, *src1, *src2; + char *oparg, *lenarg, *src1arg, *src2arg, *destarg; + int i, op; + + if (argc < 5) + return CMD_RET_USAGE; + + oparg = argv[1]; + lenarg = argv[2]; + src1arg = argv[3]; + src2arg = argv[4]; + + if (!strcmp(oparg, "xor")) + op = OP_ID_XOR; + else if (!strcmp(oparg, "or")) + op = OP_ID_OR; + else if (!strcmp(oparg, "and")) + op = OP_ID_AND; + else + return CMD_RET_USAGE; + + len = dectoul(lenarg, NULL); + + src1 = malloc(len); + src2 = malloc(len); + + if (*src1arg == '*') + read_from_mem(hextoul(src1arg + 1, NULL), src1, len); + else + read_from_env_var(src1arg, src1); + + if (*src2arg == '*') + read_from_mem(hextoul(src2arg + 1, NULL), src2, len); + else + read_from_env_var(src2arg, src2); + + result = malloc(len); + + switch (op) { + case OP_ID_XOR: + for (i = 0; i < len; i++) + result[i] = src1[i] ^ src2[i]; + break; + case OP_ID_OR: + for (i = 0; i < len; i++) + result[i] = src1[i] | src2[i]; + break; + case OP_ID_AND: + for (i = 0; i < len; i++) + result[i] = src1[i] & src2[i]; + break; + } + + if (argc == 5) { + for (i = 0; i < len; i++) { + printf("%02x ", result[i]); + if (i % 16 == 15) + puts("\n"); + } + puts("\n"); + + goto exit; + } + + destarg = argv[5]; + + if (*destarg == '*') + write_to_mem(destarg + 1, result, len); /* Skip asterisk */ + else + write_to_env_var(destarg, result, len); +exit: + free(result); + free(src2); + free(src1); + + return 0; +} + +U_BOOT_CMD( + binop, 6, 1, do_binop, + "compute binary operation", + "op count [*]src1 [*]src2 [[*]dest]\n" + " - compute binary operation of data at/in src1 and\n src2 (either *memaddr, env var name or hex string)\n and store result in/at dest, where op is one of\n xor, or, and." +); diff --git a/cmd/blk_common.c b/cmd/blk_common.c new file mode 100644 index 00000000000..56529702a47 --- /dev/null +++ b/cmd/blk_common.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Handling of common block commands + * + * Copyright (c) 2017 Google, Inc + * + * (C) Copyright 2000-2011 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <blk.h> +#include <command.h> +#include <mapmem.h> +#include <vsprintf.h> + +int blk_common_cmd(int argc, char *const argv[], enum uclass_id uclass_id, + int *cur_devnump) +{ + const char *if_name = blk_get_uclass_name(uclass_id); + + switch (argc) { + case 0: + case 1: + return CMD_RET_USAGE; + case 2: + if (strncmp(argv[1], "inf", 3) == 0) { + blk_list_devices(uclass_id); + return CMD_RET_SUCCESS; + } else if (strncmp(argv[1], "dev", 3) == 0) { + if (blk_print_device_num(uclass_id, *cur_devnump)) { + printf("\nno %s devices available\n", if_name); + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; + } else if (strncmp(argv[1], "part", 4) == 0) { + if (blk_list_part(uclass_id)) + printf("\nno %s partition table available\n", + if_name); + return CMD_RET_SUCCESS; + } + return CMD_RET_USAGE; + case 3: + if (strncmp(argv[1], "dev", 3) == 0) { + int dev = (int)dectoul(argv[2], NULL); + + if (!blk_show_device(uclass_id, dev)) { + *cur_devnump = dev; + printf("... is now current device\n"); + } else { + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; + } else if (strncmp(argv[1], "part", 4) == 0) { + int dev = (int)dectoul(argv[2], NULL); + + if (blk_print_part_devnum(uclass_id, dev)) { + printf("\n%s device %d not available\n", + if_name, dev); + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; + } + return CMD_RET_USAGE; + + default: /* at least 4 args */ + if (strcmp(argv[1], "read") == 0) { + phys_addr_t paddr = hextoul(argv[2], NULL); + lbaint_t blk = hextoul(argv[3], NULL); + ulong cnt = hextoul(argv[4], NULL); + struct blk_desc *desc; + void *vaddr; + ulong n; + int ret; + + printf("\n%s read: device %d block # "LBAFU", count %lu ... ", + if_name, *cur_devnump, blk, cnt); + + ret = blk_get_desc(uclass_id, *cur_devnump, &desc); + if (ret) + return CMD_RET_FAILURE; + vaddr = map_sysmem(paddr, desc->blksz * cnt); + n = blk_dread(desc, blk, cnt, vaddr); + unmap_sysmem(vaddr); + + printf("%ld blocks read: %s\n", n, + n == cnt ? "OK" : "ERROR"); + return n == cnt ? CMD_RET_SUCCESS : CMD_RET_FAILURE; + } else if (strcmp(argv[1], "write") == 0) { + phys_addr_t paddr = hextoul(argv[2], NULL); + lbaint_t blk = hextoul(argv[3], NULL); + ulong cnt = hextoul(argv[4], NULL); + struct blk_desc *desc; + void *vaddr; + ulong n; + int ret; + + printf("\n%s write: device %d block # "LBAFU", count %lu ... ", + if_name, *cur_devnump, blk, cnt); + + ret = blk_get_desc(uclass_id, *cur_devnump, &desc); + if (ret) + return CMD_RET_FAILURE; + vaddr = map_sysmem(paddr, desc->blksz * cnt); + n = blk_dwrite(desc, blk, cnt, vaddr); + unmap_sysmem(vaddr); + + printf("%ld blocks written: %s\n", n, + n == cnt ? "OK" : "ERROR"); + return n == cnt ? CMD_RET_SUCCESS : CMD_RET_FAILURE; + } else if (strcmp(argv[1], "erase") == 0) { + lbaint_t blk = hextoul(argv[2], NULL); + ulong cnt = hextoul(argv[3], NULL); + struct blk_desc *desc; + ulong n; + + printf("\n%s erase: device %d block # "LBAFU", count %lu ... ", + if_name, *cur_devnump, blk, cnt); + + if (blk_get_desc(uclass_id, *cur_devnump, &desc)) + return CMD_RET_FAILURE; + + n = blk_derase(desc, blk, cnt); + + printf("%ld blocks erased: %s\n", n, + n == cnt ? "OK" : "ERROR"); + return n == cnt ? CMD_RET_SUCCESS : CMD_RET_FAILURE; + } else { + return CMD_RET_USAGE; + } + } +} diff --git a/cmd/blkcache.c b/cmd/blkcache.c new file mode 100644 index 00000000000..dbd03df14dc --- /dev/null +++ b/cmd/blkcache.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) Nelson Integration, LLC 2016 + * Author: Eric Nelson<eric@nelint.com> + * + */ +#include <command.h> +#include <config.h> +#include <malloc.h> +#include <part.h> +#include <vsprintf.h> + +static int blkc_show(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct block_cache_stats stats; + blkcache_stats(&stats); + + printf("hits: %u\n" + "misses: %u\n" + "entries: %u\n" + "max blocks/entry: %u\n" + "max cache entries: %u\n", + stats.hits, stats.misses, stats.entries, + stats.max_blocks_per_entry, stats.max_entries); + return 0; +} + +static int blkc_configure(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + unsigned blocks_per_entry, max_entries; + if (argc != 3) + return CMD_RET_USAGE; + + blocks_per_entry = simple_strtoul(argv[1], 0, 0); + max_entries = simple_strtoul(argv[2], 0, 0); + blkcache_configure(blocks_per_entry, max_entries); + printf("changed to max of %u entries of %u blocks each\n", + max_entries, blocks_per_entry); + return 0; +} + +static struct cmd_tbl cmd_blkc_sub[] = { + U_BOOT_CMD_MKENT(show, 0, 0, blkc_show, "", ""), + U_BOOT_CMD_MKENT(configure, 3, 0, blkc_configure, "", ""), +}; + +static int do_blkcache(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct cmd_tbl *c; + + if (argc < 2) + return CMD_RET_USAGE; + + /* Strip off leading argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], &cmd_blkc_sub[0], ARRAY_SIZE(cmd_blkc_sub)); + + if (!c) + return CMD_RET_USAGE; + + return c->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD( + blkcache, 4, 0, do_blkcache, + "block cache diagnostics and control", + "show - show and reset statistics\n" + "blkcache configure <blocks> <entries> " + "- set max blocks per entry and max cache entries\n" +); diff --git a/cmd/blkmap.c b/cmd/blkmap.c new file mode 100644 index 00000000000..65edec899e2 --- /dev/null +++ b/cmd/blkmap.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2023 Addiva Elektronik + * Author: Tobias Waldekranz <tobias@waldekranz.com> + */ + +#include <blk.h> +#include <blkmap.h> +#include <command.h> +#include <env.h> +#include <malloc.h> +#include <dm/device.h> + +static int blkmap_curr_dev; + +struct map_ctx { + struct udevice *dev; + lbaint_t blknr, blkcnt; +}; + +typedef int (*map_parser_fn)(struct map_ctx *ctx, int argc, char *const argv[]); + +struct map_handler { + const char *name; + map_parser_fn fn; +}; + +static int do_blkmap_map_linear(struct map_ctx *ctx, int argc, + char *const argv[]) +{ + struct blk_desc *lbd; + int err, ldevnum; + lbaint_t lblknr; + + if (argc < 4) + return CMD_RET_USAGE; + + ldevnum = dectoul(argv[2], NULL); + lblknr = dectoul(argv[3], NULL); + + lbd = blk_get_devnum_by_uclass_idname(argv[1], ldevnum); + if (!lbd) { + printf("Found no device matching \"%s %d\"\n", + argv[1], ldevnum); + return CMD_RET_FAILURE; + } + + err = blkmap_map_linear(ctx->dev, ctx->blknr, ctx->blkcnt, + lbd->bdev, lblknr); + if (err) { + printf("Unable to map \"%s %d\" at block 0x" LBAF ": %d\n", + argv[1], ldevnum, ctx->blknr, err); + + return CMD_RET_FAILURE; + } + + printf("Block 0x" LBAF "+0x" LBAF " mapped to block 0x" LBAF " of \"%s %d\"\n", + ctx->blknr, ctx->blkcnt, lblknr, argv[1], ldevnum); + return CMD_RET_SUCCESS; +} + +static int do_blkmap_map_mem(struct map_ctx *ctx, int argc, char *const argv[]) +{ + phys_addr_t addr; + int err; + bool preserve = false; + + if (argc < 2) + return CMD_RET_USAGE; + + addr = hextoul(argv[1], NULL); + + if (argc == 3 && !strcmp(argv[2], "preserve")) + preserve = true; + + err = blkmap_map_pmem(ctx->dev, ctx->blknr, ctx->blkcnt, addr, + preserve); + if (err) { + printf("Unable to map %#llx at block 0x" LBAF ": %d\n", + (unsigned long long)addr, ctx->blknr, err); + return CMD_RET_FAILURE; + } + + printf("Block 0x" LBAF "+0x" LBAF " mapped to %#llx\n", + ctx->blknr, ctx->blkcnt, (unsigned long long)addr); + return CMD_RET_SUCCESS; +} + +static struct map_handler map_handlers[] = { + { .name = "linear", .fn = do_blkmap_map_linear }, + { .name = "mem", .fn = do_blkmap_map_mem }, + + { .name = NULL } +}; + +static int do_blkmap_map(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct map_handler *handler; + struct map_ctx ctx; + + if (argc < 5) + return CMD_RET_USAGE; + + ctx.dev = blkmap_from_label(argv[1]); + if (!ctx.dev) { + printf("\"%s\" is not the name of any known blkmap\n", argv[1]); + return CMD_RET_FAILURE; + } + + ctx.blknr = hextoul(argv[2], NULL); + ctx.blkcnt = hextoul(argv[3], NULL); + argc -= 4; + argv += 4; + + for (handler = map_handlers; handler->name; handler++) { + if (!strcmp(handler->name, argv[0])) + return handler->fn(&ctx, argc, argv); + } + + printf("Unknown map type \"%s\"\n", argv[0]); + return CMD_RET_USAGE; +} + +static int do_blkmap_create(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + const char *label; + int err; + + if (argc != 2) + return CMD_RET_USAGE; + + label = argv[1]; + + err = blkmap_create(label, NULL); + if (err) { + printf("Unable to create \"%s\": %d\n", label, err); + return CMD_RET_FAILURE; + } + + printf("Created \"%s\"\n", label); + return CMD_RET_SUCCESS; +} + +static int do_blkmap_destroy(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct udevice *dev; + const char *label; + int err; + + if (argc != 2) + return CMD_RET_USAGE; + + label = argv[1]; + + dev = blkmap_from_label(label); + if (!dev) { + printf("\"%s\" is not the name of any known blkmap\n", label); + return CMD_RET_FAILURE; + } + + err = blkmap_destroy(dev); + if (err) { + printf("Unable to destroy \"%s\": %d\n", label, err); + return CMD_RET_FAILURE; + } + + printf("Destroyed \"%s\"\n", label); + return CMD_RET_SUCCESS; +} + +static int do_blkmap_get(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct udevice *dev; + const char *label; + int err; + + if (argc < 3) + return CMD_RET_USAGE; + + label = argv[1]; + + dev = blkmap_from_label(label); + if (!dev) { + printf("\"%s\" is not the name of any known blkmap\n", label); + return CMD_RET_FAILURE; + } + + if (!strcmp(argv[2], "dev")) { + if (argc == 3) { + printf("%d\n", dev_seq(dev)); + } else { + err = env_set_hex(argv[3], dev_seq(dev)); + if (err) + return CMD_RET_FAILURE; + } + } else { + return CMD_RET_USAGE; + } + + return CMD_RET_SUCCESS; +} + +static int do_blkmap_common(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + /* The subcommand parsing pops the original argv[0] ("blkmap") + * which blk_common_cmd expects. Push it back again. + */ + argc++; + argv--; + + return blk_common_cmd(argc, argv, UCLASS_BLKMAP, &blkmap_curr_dev); +} + +U_BOOT_CMD_WITH_SUBCMDS( + blkmap, "Composeable virtual block devices", + "info - list configured devices\n" + "blkmap part - list available partitions on current blkmap device\n" + "blkmap dev [<dev>] - show or set current blkmap device\n" + "blkmap read <addr> <blk#> <cnt>\n" + "blkmap write <addr> <blk#> <cnt>\n" + "blkmap get <label> dev [<var>] - store device number in variable\n" + "blkmap create <label> - create device\n" + "blkmap destroy <label> - destroy device\n" + "blkmap map <label> <blk#> <cnt> linear <interface> <dev> <blk#> - device mapping\n" + "blkmap map <label> <blk#> <cnt> mem <addr> [preserve] - memory mapping\n", + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_blkmap_common), + U_BOOT_SUBCMD_MKENT(part, 2, 1, do_blkmap_common), + U_BOOT_SUBCMD_MKENT(dev, 4, 1, do_blkmap_common), + U_BOOT_SUBCMD_MKENT(read, 5, 1, do_blkmap_common), + U_BOOT_SUBCMD_MKENT(write, 5, 1, do_blkmap_common), + U_BOOT_SUBCMD_MKENT(get, 5, 1, do_blkmap_get), + U_BOOT_SUBCMD_MKENT(create, 2, 1, do_blkmap_create), + U_BOOT_SUBCMD_MKENT(destroy, 2, 1, do_blkmap_destroy), + U_BOOT_SUBCMD_MKENT(map, 32, 1, do_blkmap_map)); diff --git a/cmd/blob.c b/cmd/blob.c new file mode 100644 index 00000000000..b1c72e3f440 --- /dev/null +++ b/cmd/blob.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * + * Command for encapsulating/decapsulating blob of memory. + */ + +#include <command.h> +#include <malloc.h> +#include <vsprintf.h> +#include <asm/byteorder.h> +#include <linux/compiler.h> +#if defined(CONFIG_ARCH_MX6) || defined(CONFIG_ARCH_MX7) || \ + defined(CONFIG_ARCH_MX7ULP) || defined(CONFIG_ARCH_IMX8M) +#include <fsl_sec.h> +#include <asm/arch/clock.h> +#endif + +/** + * blob_decap() - Decapsulate the data as a blob + * @key_mod: - Pointer to key modifier/key + * @src: - Address of data to be decapsulated + * @dst: - Address of data to be decapsulated + * @len: - Size of data to be decapsulated + * + * Returns zero on success,and negative on error. + */ +__weak int blob_decap(u8 *key_mod, u8 *src, u8 *dst, u32 len) +{ + return 0; +} + +/** + * blob_encap() - Encapsulate the data as a blob + * @key_mod: - Pointer to key modifier/key + * @src: - Address of data to be encapsulated + * @dst: - Address of data to be encapsulated + * @len: - Size of data to be encapsulated + * + * Returns zero on success,and negative on error. + */ +__weak int blob_encap(u8 *key_mod, u8 *src, u8 *dst, u32 len) +{ + return 0; +} + +/** + * do_blob() - Handle the "blob" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +static int do_blob(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong key_addr, src_addr, dst_addr, len; + uint8_t *km_ptr, *src_ptr, *dst_ptr; + int enc, ret = 0; + + if (argc != 6) + return CMD_RET_USAGE; + + if (!strncmp(argv[1], "enc", 3)) + enc = 1; + else if (!strncmp(argv[1], "dec", 3)) + enc = 0; + else + return CMD_RET_USAGE; + + src_addr = hextoul(argv[2], NULL); + dst_addr = hextoul(argv[3], NULL); + len = hextoul(argv[4], NULL); + key_addr = hextoul(argv[5], NULL); + + km_ptr = (uint8_t *)(uintptr_t)key_addr; + src_ptr = (uint8_t *)(uintptr_t)src_addr; + dst_ptr = (uint8_t *)(uintptr_t)dst_addr; + +#if defined(CONFIG_ARCH_MX6) || defined(CONFIG_ARCH_MX7) || \ + defined(CONFIG_ARCH_MX7ULP) || defined(CONFIG_ARCH_IMX8M) + + hab_caam_clock_enable(1); + + u32 out_jr_size = sec_in32(CFG_SYS_FSL_JR0_ADDR + + FSL_CAAM_ORSR_JRa_OFFSET); + if (out_jr_size != FSL_CAAM_MAX_JR_SIZE) + sec_init(); +#endif + + if (enc) + ret = blob_encap(km_ptr, src_ptr, dst_ptr, len); + else + ret = blob_decap(km_ptr, src_ptr, dst_ptr, len); + + return ret; +} + +/***************************************************/ +U_BOOT_LONGHELP(blob, + "enc src dst len km - Encapsulate and create blob of data\n" + " $len bytes long at address $src and\n" + " store the result at address $dst.\n" + " $km is the address where the key\n" + " modifier is stored.\n" + " The modifier is required for generation\n" + " /use as key for cryptographic operation.\n" + " Key modifier should be 16 byte long.\n" + "blob dec src dst len km - Decapsulate the blob of data at address\n" + " $src and store result of $len byte at\n" + " addr $dst.\n" + " $km is the address where the key\n" + " modifier is stored.\n" + " The modifier is required for generation\n" + " /use as key for cryptographic operation.\n" + " Key modifier should be 16 byte long.\n"); + +U_BOOT_CMD( + blob, 6, 1, do_blob, + "Blob encapsulation/decryption", + blob_help_text +); diff --git a/cmd/bloblist.c b/cmd/bloblist.c new file mode 100644 index 00000000000..333ae558142 --- /dev/null +++ b/cmd/bloblist.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Command-line access to bloblist features + * + * Copyright 2020 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <bloblist.h> +#include <command.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static int do_bloblist_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bloblist_show_stats(); + + return 0; +} + +static int do_bloblist_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bloblist_show_list(); + + return 0; +} + +U_BOOT_LONGHELP(bloblist, + "info - show information about the bloblist\n" + "bloblist list - list blobs in the bloblist"); + +U_BOOT_CMD_WITH_SUBCMDS(bloblist, "Bloblists", bloblist_help_text, + U_BOOT_SUBCMD_MKENT(info, 1, 1, do_bloblist_info), + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_bloblist_list)); diff --git a/cmd/bmp.c b/cmd/bmp.c new file mode 100644 index 00000000000..3b618448624 --- /dev/null +++ b/cmd/bmp.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2002 + * Detlev Zundel, DENX Software Engineering, dzu@denx.de. + */ + +/* + * BMP handling routines + */ + +#include <command.h> +#include <image.h> +#include <mapmem.h> +#include <splash.h> +#include <video.h> +#include <stdlib.h> + +static int do_bmp_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr; + + switch (argc) { + case 1: /* use image_load_addr as default address */ + addr = image_load_addr; + break; + case 2: /* use argument */ + addr = hextoul(argv[1], NULL); + break; + default: + return CMD_RET_USAGE; + } + + return (bmp_info(addr)); +} + +static int do_bmp_display(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr; + int x = 0, y = 0; + + splash_get_pos(&x, &y); + + switch (argc) { + case 1: /* use image_load_addr as default address */ + addr = image_load_addr; + break; + case 2: /* use argument */ + addr = hextoul(argv[1], NULL); + break; + case 4: + addr = hextoul(argv[1], NULL); + if (!strcmp(argv[2], "m")) + x = BMP_ALIGN_CENTER; + else + x = dectoul(argv[2], NULL); + if (!strcmp(argv[3], "m")) + y = BMP_ALIGN_CENTER; + else + y = dectoul(argv[3], NULL); + break; + default: + return CMD_RET_USAGE; + } + + return (bmp_display(addr, x, y)); +} + +static struct cmd_tbl cmd_bmp_sub[] = { + U_BOOT_CMD_MKENT(info, 3, 0, do_bmp_info, "", ""), + U_BOOT_CMD_MKENT(display, 5, 0, do_bmp_display, "", ""), +}; + +static int do_bmp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *c; + + /* Strip off leading 'bmp' command argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], &cmd_bmp_sub[0], ARRAY_SIZE(cmd_bmp_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + bmp, 5, 1, do_bmp, + "manipulate BMP image data", + "info <imageAddr> - display image info\n" + "bmp display <imageAddr> [x y] - display image at x,y" +); diff --git a/cmd/boot.c b/cmd/boot.c new file mode 100644 index 00000000000..23496cafdf5 --- /dev/null +++ b/cmd/boot.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2003 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * Misc boot support + */ +#include <command.h> +#include <net.h> +#include <vsprintf.h> + +#ifdef CONFIG_CMD_GO + +/* Allow ports to override the default behavior */ +__attribute__((weak)) +unsigned long do_go_exec(ulong (*entry)(int, char * const []), int argc, + char *const argv[]) +{ + return entry (argc, argv); +} + +static int do_go(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + ulong addr, rc; + int rcode = 0; + + if (argc < 2) + return CMD_RET_USAGE; + + addr = hextoul(argv[1], NULL); + + printf ("## Starting application at 0x%08lX ...\n", addr); + flush(); + + /* + * pass address parameter as argv[0] (aka command name), + * and all remaining args + */ + rc = do_go_exec ((void *)addr, argc - 1, argv + 1); + if (rc != 0) rcode = 1; + + printf ("## Application terminated, rc = 0x%lX\n", rc); + return rcode; +} + +/* -------------------------------------------------------------------- */ + +U_BOOT_CMD( + go, CONFIG_SYS_MAXARGS, 1, do_go, + "start application at address 'addr'", + "addr [arg ...]\n - start application at address 'addr'\n" + " passing 'arg' as arguments" +); + +#endif + +U_BOOT_CMD( + reset, 2, 0, do_reset, + "Perform RESET of the CPU", + "- cold boot without level specifier\n" + "reset -w - warm reset if implemented" +); + +#ifdef CONFIG_CMD_POWEROFF +U_BOOT_CMD( + poweroff, 1, 0, do_poweroff, + "Perform POWEROFF of the device", + "" +); +#endif diff --git a/cmd/bootcount.c b/cmd/bootcount.c new file mode 100644 index 00000000000..5e3b66e676b --- /dev/null +++ b/cmd/bootcount.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <command.h> +#include <bootcount.h> + +static int do_bootcount_print(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + printf("%lu\n", bootcount_load()); + return CMD_RET_SUCCESS; +} + +static int do_bootcount_reset(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + /* + * note that we're explicitly not resetting the environment + * variable, so you still have the old bootcounter available + */ + bootcount_store(0); + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl bootcount_sub[] = { + U_BOOT_CMD_MKENT(print, 1, 1, do_bootcount_print, "", ""), + U_BOOT_CMD_MKENT(reset, 1, 1, do_bootcount_reset, "", ""), +}; + +static int do_bootcount(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cp; + + if (argc < 2) + return CMD_RET_USAGE; + + /* drop initial "bootcount" arg */ + argc--; + argv++; + + cp = find_cmd_tbl(argv[0], bootcount_sub, ARRAY_SIZE(bootcount_sub)); + if (cp) + return cp->cmd(cmdtp, flag, argc, argv); + + return CMD_RET_USAGE; +} + +U_BOOT_LONGHELP(bootcount, + "print - print current bootcounter\n" + "reset - reset the bootcounter"); + +U_BOOT_CMD(bootcount, 2, 1, do_bootcount, + "bootcount", + bootcount_help_text +); diff --git a/cmd/bootdev.c b/cmd/bootdev.c new file mode 100644 index 00000000000..4bc229e809a --- /dev/null +++ b/cmd/bootdev.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 'bootdev' command + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <bootdev.h> +#include <bootflow.h> +#include <bootstd.h> +#include <command.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> + +static int bootdev_check_state(struct bootstd_priv **stdp) +{ + struct bootstd_priv *std; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + if (!std->cur_bootdev) { + printf("Please use 'bootdev select' first\n"); + return -ENOENT; + } + *stdp = std; + + return 0; +} + +static int do_bootdev_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool probe; + + probe = argc >= 2 && !strcmp(argv[1], "-p"); + bootdev_list(probe); + + return 0; +} + +static int do_bootdev_select(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct udevice *dev; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + if (argc < 2) { + std->cur_bootdev = NULL; + return 0; + } + if (bootdev_find_by_any(argv[1], &dev, NULL)) + return CMD_RET_FAILURE; + + std->cur_bootdev = dev; + + return 0; +} + +static int do_bootdev_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *priv; + struct bootflow *bflow; + int ret, i, num_valid; + struct udevice *dev; + bool probe; + + probe = argc >= 2 && !strcmp(argv[1], "-p"); + + ret = bootdev_check_state(&priv); + if (ret) + return CMD_RET_FAILURE; + + dev = priv->cur_bootdev; + + /* Count the number of bootflows, including how many are valid */ + num_valid = 0; + for (ret = bootdev_first_bootflow(dev, &bflow), i = 0; + !ret; + ret = bootdev_next_bootflow(&bflow), i++) + num_valid += bflow->state == BOOTFLOWST_READY; + + /* + * Prove the device, if requested, otherwise assume that there is no + * error + */ + ret = 0; + if (probe) + ret = device_probe(dev); + + printf("Name: %s\n", dev->name); + printf("Sequence: %d\n", dev_seq(dev)); + printf("Status: %s\n", ret ? simple_itoa(-ret) : device_active(dev) ? + "Probed" : "OK"); + printf("Uclass: %s\n", dev_get_uclass_name(dev_get_parent(dev))); + printf("Bootflows: %d (%d valid)\n", i, num_valid); + + return 0; +} + +static int do_bootdev_hunt(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *priv; + const char *spec = NULL; + bool list = false; + int ret = 0; + + if (argc >= 2) { + if (!strcmp(argv[1], "-l")) + list = true; + else + spec = argv[1]; + } + + ret = bootstd_get_priv(&priv); + if (ret) + return ret; + if (list) { + bootdev_list_hunters(priv); + } else { + ret = bootdev_hunt(spec, true); + if (ret) { + printf("Failed (err=%dE)\n", ret); + + return CMD_RET_FAILURE; + } + } + + return 0; +} + +U_BOOT_LONGHELP(bootdev, + "list [-p] - list all available bootdevs (-p to probe)\n" + "bootdev hunt [-l|<spec>] - use hunt drivers to find bootdevs\n" + "bootdev select <bd> - select a bootdev by name | label | seq\n" + "bootdev info [-p] - show information about a bootdev (-p to probe)"); + +U_BOOT_CMD_WITH_SUBCMDS(bootdev, "Boot devices", bootdev_help_text, + U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootdev_list), + U_BOOT_SUBCMD_MKENT(hunt, 2, 1, do_bootdev_hunt), + U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootdev_select), + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootdev_info)); diff --git a/cmd/bootefi.c b/cmd/bootefi.c new file mode 100644 index 00000000000..b8f5bb35950 --- /dev/null +++ b/cmd/bootefi.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application loader + * + * Copyright (c) 2016 Alexander Graf + */ + +#define LOG_CATEGORY LOGC_EFI + +#include <command.h> +#include <efi.h> +#include <efi_device_path.h> +#include <efi_loader.h> +#include <exports.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <vsprintf.h> +#include <asm-generic/sections.h> +#include <asm/global_data.h> +#include <linux/string.h> + +DECLARE_GLOBAL_DATA_PTR; + +static struct efi_device_path *test_image_path; +static struct efi_device_path *test_device_path; + +static efi_status_t bootefi_run_prepare(const char *load_options_path, + struct efi_device_path *device_path, + struct efi_device_path *image_path, + struct efi_loaded_image_obj **image_objp, + struct efi_loaded_image **loaded_image_infop) +{ + efi_status_t ret; + u16 *load_options; + + ret = efi_setup_loaded_image(device_path, image_path, image_objp, + loaded_image_infop); + if (ret != EFI_SUCCESS) + return ret; + + (*image_objp)->auth_status = EFI_IMAGE_AUTH_PASSED; + (*image_objp)->entry = efi_selftest; + + /* Transfer environment variable as load options */ + return efi_env_set_load_options((efi_handle_t)*image_objp, + load_options_path, + &load_options); +} + +/** + * bootefi_test_prepare() - prepare to run an EFI test + * + * Prepare to run a test as if it were provided by a loaded image. + * + * @image_objp: pointer to be set to the loaded image handle + * @loaded_image_infop: pointer to be set to the loaded image protocol + * @path: dummy file path used to construct the device path + * set in the loaded image protocol + * @load_options_path: name of a U-Boot environment variable. Its value is + * set as load options in the loaded image protocol. + * Return: status code + */ +static efi_status_t bootefi_test_prepare + (struct efi_loaded_image_obj **image_objp, + struct efi_loaded_image **loaded_image_infop, const char *path, + const char *load_options_path) +{ + efi_status_t ret; + + /* Construct a dummy device path */ + test_device_path = efi_dp_from_mem(EFI_RESERVED_MEMORY_TYPE, 0, 0); + if (!test_device_path) + return EFI_OUT_OF_RESOURCES; + + test_image_path = efi_dp_from_file(NULL, path); + if (!test_image_path) { + ret = EFI_OUT_OF_RESOURCES; + goto failure; + } + + ret = bootefi_run_prepare(load_options_path, test_device_path, + test_image_path, image_objp, + loaded_image_infop); + if (ret == EFI_SUCCESS) + return ret; + +failure: + efi_free_pool(test_device_path); + efi_free_pool(test_image_path); + /* TODO: not sure calling clear function is necessary */ + efi_clear_bootdev(); + return ret; +} + +/** + * do_efi_selftest() - execute EFI selftest + * + * Return: status code + */ +static int do_efi_selftest(void) +{ + struct efi_loaded_image_obj *image_obj; + struct efi_loaded_image *loaded_image_info; + efi_status_t ret; + + ret = bootefi_test_prepare(&image_obj, &loaded_image_info, + "\\selftest", "efi_selftest"); + if (ret != EFI_SUCCESS) + return CMD_RET_FAILURE; + + /* Execute the test */ + ret = do_bootefi_exec(&image_obj->header, + loaded_image_info->load_options); + efi_free_pool(test_device_path); + efi_free_pool(test_image_path); + if (ret != EFI_SUCCESS) + efi_delete_handle(&image_obj->header); + else + ret = efi_delete_handle(&image_obj->header); + + return ret != EFI_SUCCESS; +} + +/** + * do_bootefi() - execute `bootefi` command + * + * @cmdtp: table entry describing command + * @flag: bitmap indicating how the command was invoked + * @argc: number of arguments + * @argv: command line arguments + * Return: status code + */ +static int do_bootefi(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + efi_status_t ret; + char *p; + void *fdt, *initrd = NULL, *image_buf; + unsigned long addr, size, rd_len = 0, fdt_addr = 0; + void *image_addr; + size_t image_size; + int fdt_arg = 2; + + if (argc < 2) + return CMD_RET_USAGE; + + if (argc > 2) { + ulong rd_addr = 0; + char *end = strchr(argv[2], ':'); + + if (end) { + rd_addr = hextoul(argv[2], NULL); + if (!rd_addr) + return CMD_RET_USAGE; + + rd_len = hextoul(++end, NULL); + initrd = map_sysmem(rd_addr, rd_len); + ++fdt_arg; + } + } + + if (argc > fdt_arg + 1) + return CMD_RET_USAGE; + if (argc == fdt_arg + 1) + fdt_addr = hextoul(argv[fdt_arg], NULL); + + if (fdt_addr) + fdt = map_sysmem(fdt_addr, 0); + else + fdt = EFI_FDT_USE_INTERNAL; + + if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR) && + !strcmp(argv[1], "bootmgr")) { + ret = efi_bootmgr_run(fdt); + + if (ret != EFI_SUCCESS) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; + } + + if (IS_ENABLED(CONFIG_CMD_BOOTEFI_SELFTEST) && + !strcmp(argv[1], "selftest")) { + /* Initialize EFI drivers */ + ret = efi_init_obj_list(); + if (ret != EFI_SUCCESS) { + log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n", + ret & ~EFI_ERROR_MASK); + return CMD_RET_FAILURE; + } + + ret = efi_install_fdt(fdt); + if (ret != EFI_SUCCESS) + return CMD_RET_FAILURE; + + return do_efi_selftest(); + } + + if (!IS_ENABLED(CONFIG_CMD_BOOTEFI_BINARY)) + return CMD_RET_SUCCESS; + + if (IS_ENABLED(CONFIG_CMD_BOOTEFI_HELLO) && + !strcmp(argv[1], "hello")) { + image_buf = __efi_helloworld_begin; + size = __efi_helloworld_end - __efi_helloworld_begin; + /* TODO: not sure calling clear function is necessary */ + efi_clear_bootdev(); + } else { + addr = strtoul(argv[1], NULL, 16); + /* Check that a numeric value was passed */ + if (!addr) + return CMD_RET_USAGE; + image_buf = map_sysmem(addr, 0); + + p = strchr(argv[1], ':'); + if (p) { + size = strtoul(++p, NULL, 16); + if (!size) + return CMD_RET_USAGE; + efi_clear_bootdev(); + } else { + /* Image should be already loaded */ + efi_get_image_parameters(&image_addr, &image_size); + + if (image_buf != image_addr) { + log_err("No UEFI binary known at %s\n", + argv[1]); + return CMD_RET_FAILURE; + } + size = image_size; + } + } + + ret = efi_binary_run(image_buf, size, fdt, initrd, rd_len); + + if (ret != EFI_SUCCESS) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(bootefi, + "<image address>[:<size>] [<initrd address>:<size>] [<fdt address>]\n" + " - boot EFI payload\n" +#ifdef CONFIG_CMD_BOOTEFI_HELLO + "bootefi hello\n" + " - boot a sample Hello World application stored within U-Boot\n" +#endif +#ifdef CONFIG_CMD_BOOTEFI_SELFTEST + "bootefi selftest [fdt address]\n" + " - boot an EFI selftest application stored within U-Boot\n" + " Use environment variable efi_selftest to select a single test.\n" + " Use 'setenv efi_selftest list' to enumerate all tests.\n" +#endif +#ifdef CONFIG_CMD_BOOTEFI_BOOTMGR + "bootefi bootmgr [fdt address]\n" + " - load and boot EFI payload based on BootOrder/BootXXXX variables.\n" + "\n" + " If specified, the device tree located at <fdt address> gets\n" + " exposed as EFI configuration table.\n" +#endif + ); + +U_BOOT_CMD( + bootefi, 4, 0, do_bootefi, + "Boots an EFI payload from memory", + bootefi_help_text +); diff --git a/cmd/bootflow.c b/cmd/bootflow.c new file mode 100644 index 00000000000..551dffbb8b8 --- /dev/null +++ b/cmd/bootflow.c @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 'bootflow' command + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <bootdev.h> +#include <bootflow.h> +#include <bootm.h> +#include <bootstd.h> +#include <command.h> +#include <console.h> +#include <dm.h> +#include <env.h> +#include <expo.h> +#include <log.h> +#include <mapmem.h> + +/** + * report_bootflow_err() - Report where a bootflow failed + * + * When a bootflow does not make it to the 'loaded' state, something went wrong. + * Print a helpful message if there is an error + * + * @bflow: Bootflow to process + * @err: Error code (0 if none) + */ +static void report_bootflow_err(struct bootflow *bflow, int err) +{ + if (!err) + return; + + /* Indent out to 'Method' */ + printf(" ** "); + + switch (bflow->state) { + case BOOTFLOWST_BASE: + printf("No media/partition found"); + break; + case BOOTFLOWST_MEDIA: + printf("No partition found"); + break; + case BOOTFLOWST_PART: + printf("No filesystem found"); + break; + case BOOTFLOWST_FS: + printf("File not found"); + break; + case BOOTFLOWST_FILE: + printf("File cannot be loaded"); + break; + case BOOTFLOWST_READY: + printf("Ready"); + break; + case BOOTFLOWST_COUNT: + break; + } + + printf(", err=%dE\n", err); +} + +/** + * show_bootflow() - Show the status of a bootflow + * + * @seq: Bootflow index + * @bflow: Bootflow to show + * @errors: True to show the error received, if any + */ +static void show_bootflow(int index, struct bootflow *bflow, bool errors) +{ + printf("%3x %-11s %-6s %-9.9s %4x %-25.25s %s\n", index, + bflow->method->name, bootflow_state_get_name(bflow->state), + bflow->dev ? dev_get_uclass_name(dev_get_parent(bflow->dev)) : + "(none)", bflow->part, bflow->name, bflow->fname ?: ""); + if (errors) + report_bootflow_err(bflow, bflow->err); +} + +static void show_header(void) +{ + printf("Seq Method State Uclass Part Name Filename\n"); + printf("--- ----------- ------ -------- ---- ------------------------ ----------------\n"); +} + +static void show_footer(int count, int num_valid) +{ + printf("--- ----------- ------ -------- ---- ------------------------ ----------------\n"); + printf("(%d bootflow%s, %d valid)\n", count, count != 1 ? "s" : "", + num_valid); +} + +/** + * bootflow_handle_menu() - Handle running the menu and updating cur bootflow + * + * This shows the menu, allows the user to select something and then prints + * what happened + * + * @std: bootstd information + * @text_mode: true to run the menu in text mode + * @bflowp: Returns selected bootflow, on success + * Return: 0 on success (a bootflow was selected), -EAGAIN if nothing was + * chosen, other -ve value on other error + */ +__maybe_unused static int bootflow_handle_menu(struct bootstd_priv *std, + bool text_mode, + struct bootflow **bflowp) +{ + struct expo *exp; + struct bootflow *bflow; + int ret, seq; + + ret = bootflow_menu_start(std, text_mode, &exp); + if (ret) + return log_msg_ret("bhs", ret); + + ret = -ERESTART; + do { + if (ret == -ERESTART) { + ret = expo_render(exp); + if (ret) + return log_msg_ret("bhr", ret); + } + ret = bootflow_menu_poll(exp, &seq); + } while (ret == -EAGAIN || ret == -ERESTART); + + if (ret == -EPIPE) { + printf("Nothing chosen\n"); + std->cur_bootflow = NULL; + } else if (ret) { + printf("Menu failed (err=%d)\n", ret); + } else { + bflow = alist_getw(&std->bootflows, seq, struct bootflow); + printf("Selected: %s\n", bflow->os_name ? bflow->os_name : + bflow->name); + std->cur_bootflow = bflow; + *bflowp = bflow; + } + expo_destroy(exp); + if (ret) + return ret; + + return 0; +} + +static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow_iter iter; + struct udevice *dev = NULL; + struct bootflow bflow; + bool all = false, boot = false, errors = false, no_global = false; + bool list = false, no_hunter = false, menu = false, text_mode = false; + int num_valid = 0; + const char *label = NULL; + bool has_args; + int ret, i; + int flags; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + has_args = argc > 1 && *argv[1] == '-'; + if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL)) { + if (has_args) { + all = strchr(argv[1], 'a'); + boot = strchr(argv[1], 'b'); + errors = strchr(argv[1], 'e'); + no_global = strchr(argv[1], 'G'); + list = strchr(argv[1], 'l'); + no_hunter = strchr(argv[1], 'H'); + menu = strchr(argv[1], 'm'); + text_mode = strchr(argv[1], 't'); + argc--; + argv++; + } + if (argc > 1) + label = argv[1]; + if (!label) + dev = std->cur_bootdev; + } else { + if (has_args) { + printf("Flags not supported: enable CONFIG_BOOTSTD_FULL\n"); + return CMD_RET_USAGE; + } + boot = true; + } + + std->cur_bootflow = NULL; + + flags = BOOTFLOWIF_ONLY_BOOTABLE; + if (list) + flags |= BOOTFLOWIF_SHOW; + if (all) + flags |= BOOTFLOWIF_ALL; + if (no_global) + flags |= BOOTFLOWIF_SKIP_GLOBAL; + if (!no_hunter) + flags |= BOOTFLOWIF_HUNT; + + /* + * If we have a device, just scan for bootflows attached to that device + */ + if (list) { + printf("Scanning for bootflows "); + if (dev) + printf("in bootdev '%s'\n", dev->name); + else if (label) + printf("with label '%s'\n", label); + else + printf("in all bootdevs\n"); + show_header(); + } + if (dev) + bootstd_clear_bootflows_for_bootdev(dev); + else + bootstd_clear_glob(); + for (i = 0, + ret = bootflow_scan_first(dev, label, &iter, flags, &bflow); + i < 1000 && ret != -ENODEV; + i++, ret = bootflow_scan_next(&iter, &bflow)) { + bflow.err = ret; + if (!ret) + num_valid++; + ret = bootstd_add_bootflow(&bflow); + if (ret < 0) { + printf("Out of memory\n"); + return CMD_RET_FAILURE; + } + if (list) + show_bootflow(i, &bflow, errors); + if (!menu && boot && !bflow.err) + bootflow_run_boot(&iter, &bflow); + } + bootflow_iter_uninit(&iter); + if (list) + show_footer(i, num_valid); + + if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL) && IS_ENABLED(CONFIG_EXPO)) { + if (!num_valid && !list) { + printf("No bootflows found; try again with -l\n"); + } else if (menu) { + struct bootflow *sel_bflow; + + ret = bootflow_handle_menu(std, text_mode, &sel_bflow); + if (!ret && boot) { + ret = console_clear(); + if (ret) { + log_err("Failed to clear console: %dE\n", + ret); + return ret; + } + + bootflow_run_boot(NULL, sel_bflow); + } + } + } + + return 0; +} + +#ifdef CONFIG_CMD_BOOTFLOW_FULL +static int do_bootflow_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct udevice *dev; + struct bootflow *bflow; + int num_valid = 0; + bool errors = false; + int ret, i; + + if (argc > 1 && *argv[1] == '-') + errors = strchr(argv[1], 'e'); + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + dev = std->cur_bootdev; + + /* If we have a device, just list bootflows attached to that device */ + if (dev) { + printf("Showing bootflows for bootdev '%s'\n", dev->name); + show_header(); + for (ret = bootdev_first_bootflow(dev, &bflow), i = 0; + !ret; + ret = bootdev_next_bootflow(&bflow), i++) { + num_valid += bflow->state == BOOTFLOWST_READY; + show_bootflow(i, bflow, errors); + } + } else { + printf("Showing all bootflows\n"); + show_header(); + for (ret = bootflow_first_glob(&bflow), i = 0; + !ret; + ret = bootflow_next_glob(&bflow), i++) { + num_valid += bflow->state == BOOTFLOWST_READY; + show_bootflow(i, bflow, errors); + } + } + show_footer(i, num_valid); + + return 0; +} + +static int do_bootflow_select(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow, *found; + struct udevice *dev; + const char *name; + char *endp; + int seq, i; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; +; + if (argc < 2) { + std->cur_bootflow = NULL; + return 0; + } + dev = std->cur_bootdev; + + name = argv[1]; + seq = simple_strtol(name, &endp, 16); + found = NULL; + + /* + * If we have a bootdev device, only allow selection of bootflows + * attached to that device + */ + if (dev) { + for (ret = bootdev_first_bootflow(dev, &bflow), i = 0; + !ret; + ret = bootdev_next_bootflow(&bflow), i++) { + if (*endp ? !strcmp(bflow->name, name) : i == seq) { + found = bflow; + break; + } + } + } else { + for (ret = bootflow_first_glob(&bflow), i = 0; + !ret; + ret = bootflow_next_glob(&bflow), i++) { + if (*endp ? !strcmp(bflow->name, name) : i == seq) { + found = bflow; + break; + } + } + } + + if (!found) { + printf("Cannot find bootflow '%s' ", name); + if (dev) + printf("in bootdev '%s' ", dev->name); + printf("(err=%d)\n", ret); + return CMD_RET_FAILURE; + } + std->cur_bootflow = found; + if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) { + if (env_set("bootargs", found->cmdline)) { + printf("Cannot set bootargs\n"); + return CMD_RET_FAILURE; + } + } + + return 0; +} + +static int do_bootflow_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow; + bool x86_setup = false; + bool dump = false; + int ret; + + if (argc > 1 && *argv[1] == '-') { + dump = strchr(argv[1], 'd'); + x86_setup = strchr(argv[1], 's'); + } + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + if (!std->cur_bootflow) { + printf("No bootflow selected\n"); + return CMD_RET_FAILURE; + } + bflow = std->cur_bootflow; + + if (IS_ENABLED(CONFIG_X86) && x86_setup) { + zimage_dump(bflow->x86_setup, false); + + return 0; + } + + printf("Name: %s\n", bflow->name); + printf("Device: %s\n", bflow->dev->name); + printf("Block dev: %s\n", bflow->blk ? bflow->blk->name : "(none)"); + printf("Method: %s\n", bflow->method->name); + printf("State: %s\n", bootflow_state_get_name(bflow->state)); + printf("Partition: %d\n", bflow->part); + printf("Subdir: %s\n", bflow->subdir ? bflow->subdir : "(none)"); + printf("Filename: %s\n", bflow->fname); + printf("Buffer: "); + if (bflow->buf) + printf("%lx\n", (ulong)map_to_sysmem(bflow->buf)); + else + printf("(not loaded)\n"); + printf("Size: %x (%d bytes)\n", bflow->size, bflow->size); + printf("OS: %s\n", bflow->os_name ? bflow->os_name : "(none)"); + printf("Cmdline: "); + if (bflow->cmdline) + puts(bflow->cmdline); + else + puts("(none)"); + putc('\n'); + if (bflow->x86_setup) + printf("X86 setup: %lx\n", + (ulong)map_to_sysmem(bflow->x86_setup)); + printf("Logo: %s\n", bflow->logo ? + simple_xtoa((ulong)map_to_sysmem(bflow->logo)) : "(none)"); + if (bflow->logo) { + printf("Logo size: %x (%d bytes)\n", bflow->logo_size, + bflow->logo_size); + } + printf("FDT: %s\n", bflow->fdt_fname); + if (bflow->fdt_fname) { + printf("FDT size: %x (%d bytes)\n", bflow->fdt_size, + bflow->fdt_size); + printf("FDT addr: %lx\n", bflow->fdt_addr); + } + printf("Error: %d\n", bflow->err); + if (dump && bflow->buf) { + /* Set some sort of maximum on the size */ + int size = min(bflow->size, 10 << 10); + int i; + + printf("Contents:\n\n"); + for (i = 0; i < size; i++) { + putc(bflow->buf[i]); + if (!(i % 128) && ctrlc()) { + printf("...interrupted\n"); + break; + } + } + } + + return 0; +} + +static int do_bootflow_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + /* + * Require a current bootflow. Users can use 'bootflow scan -b' to + * automatically scan and boot, if needed. + */ + if (!std->cur_bootflow) { + printf("No bootflow selected\n"); + return CMD_RET_FAILURE; + } + bflow = std->cur_bootflow; + ret = bootflow_read_all(bflow); + if (ret) { + printf("Failed: err=%dE\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + /* + * Require a current bootflow. Users can use 'bootflow scan -b' to + * automatically scan and boot, if needed. + */ + if (!std->cur_bootflow) { + printf("No bootflow selected\n"); + return CMD_RET_FAILURE; + } + bflow = std->cur_bootflow; + ret = bootflow_run_boot(NULL, bflow); + if (ret) + return CMD_RET_FAILURE; + + return 0; +} + +static int do_bootflow_menu(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow; + bool text_mode = false; + int ret; + + if (!IS_ENABLED(CONFIG_EXPO)) { + printf("Menu not supported\n"); + return CMD_RET_FAILURE; + } + + if (argc > 1 && *argv[1] == '-') + text_mode = strchr(argv[1], 't'); + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + ret = bootflow_handle_menu(std, text_mode, &bflow); + if (ret) + return CMD_RET_FAILURE; + + return 0; +} + +static int do_bootflow_cmdline(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow; + const char *op, *arg, *val = NULL; + int ret; + + if (argc < 3) + return CMD_RET_USAGE; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + bflow = std->cur_bootflow; + if (!bflow) { + printf("No bootflow selected\n"); + return CMD_RET_FAILURE; + } + + op = argv[1]; + arg = argv[2]; + if (*op == 's') { + val = argv[3] ?: (const char *)BOOTFLOWCL_EMPTY; + } + + switch (*op) { + case 'c': /* clear */ + val = ""; + fallthrough; + case 's': /* set */ + case 'd': /* delete */ + ret = bootflow_cmdline_set_arg(bflow, arg, val, true); + break; + case 'g': /* get */ + ret = bootflow_cmdline_get_arg(bflow, arg, &val); + if (ret >= 0) + printf("%.*s\n", ret, val); + break; + case 'a': /* auto */ + ret = bootflow_cmdline_auto(bflow, arg); + break; + } + switch (ret) { + case -E2BIG: + printf("Argument too long\n"); + break; + case -ENOENT: + printf("Argument not found\n"); + break; + case -EINVAL: + printf("Mismatched quotes\n"); + break; + case -EBADF: + printf("Value must be quoted\n"); + break; + default: + if (ret < 0) + printf("Unknown error: %dE\n", ret); + } + if (ret < 0) + return CMD_RET_FAILURE; + + return 0; +} +#endif /* CONFIG_CMD_BOOTFLOW_FULL */ + +U_BOOT_LONGHELP(bootflow, +#ifdef CONFIG_CMD_BOOTFLOW_FULL + "scan [-abeGl] [bdev] - scan for valid bootflows (-l list, -a all, -e errors, -b boot, -G no global)\n" + "bootflow list [-e] - list scanned bootflows (-e errors)\n" + "bootflow select [<num>|<name>] - select a bootflow\n" + "bootflow info [-ds] - show info on current bootflow (-d dump bootflow)\n" + "bootflow read - read all current-bootflow files\n" + "bootflow boot - boot current bootflow\n" + "bootflow menu [-t] - show a menu of available bootflows\n" + "bootflow cmdline [set|get|clear|delete|auto] <param> [<value>] - update cmdline" +#else + "scan - boot first available bootflow\n" +#endif + ); + +U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text, + U_BOOT_SUBCMD_MKENT(scan, 3, 1, do_bootflow_scan), +#ifdef CONFIG_CMD_BOOTFLOW_FULL + U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootflow_list), + U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootflow_select), + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootflow_info), + U_BOOT_SUBCMD_MKENT(read, 1, 1, do_bootflow_read), + U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot), + U_BOOT_SUBCMD_MKENT(menu, 2, 1, do_bootflow_menu), + U_BOOT_SUBCMD_MKENT(cmdline, 4, 1, do_bootflow_cmdline), +#endif +); diff --git a/cmd/booti.c b/cmd/booti.c new file mode 100644 index 00000000000..e6f67d6e136 --- /dev/null +++ b/cmd/booti.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2009 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <bootm.h> +#include <command.h> +#include <env.h> +#include <image.h> +#include <irq_func.h> +#include <lmb.h> +#include <log.h> +#include <mapmem.h> +#include <asm/global_data.h> +#include <linux/kernel.h> +#include <linux/sizes.h> + +DECLARE_GLOBAL_DATA_PTR; +/* + * Image booting support + */ +static int booti_start(struct bootm_info *bmi) +{ + struct bootm_headers *images = bmi->images; + int ret; + ulong ld; + ulong relocated_addr; + ulong image_size; + uint8_t *temp; + ulong dest; + ulong dest_end; + phys_addr_t ep_addr; + unsigned long comp_len; + unsigned long decomp_len; + int ctype; + + ret = bootm_run_states(bmi, BOOTM_STATE_START); + + /* Setup Linux kernel Image entry point */ + if (!bmi->addr_img) { + ld = image_load_addr; + debug("* kernel: default image load address = 0x%08lx\n", + image_load_addr); + } else { + ld = hextoul(bmi->addr_img, NULL); + debug("* kernel: cmdline image address = 0x%08lx\n", ld); + } + + temp = map_sysmem(ld, 0); + ctype = image_decomp_type(temp, 2); + if (ctype > 0) { + dest = env_get_ulong("kernel_comp_addr_r", 16, 0); + comp_len = env_get_ulong("kernel_comp_size", 16, 0); + if (!dest || !comp_len) { + puts("kernel_comp_addr_r or kernel_comp_size is not provided!\n"); + return -EINVAL; + } + if (dest < gd->ram_base || dest > gd->ram_top) { + puts("kernel_comp_addr_r is outside of DRAM range!\n"); + return -EINVAL; + } + + debug("kernel image compression type %d size = 0x%08lx address = 0x%08lx\n", + ctype, comp_len, (ulong)dest); + decomp_len = comp_len * 10; + ret = image_decomp(ctype, 0, ld, IH_TYPE_KERNEL, + (void *)dest, (void *)ld, comp_len, + decomp_len, &dest_end); + if (ret) + return ret; + /* dest_end contains the uncompressed Image size */ + memmove((void *) ld, (void *)dest, dest_end); + } + unmap_sysmem((void *)ld); + + ret = booti_setup(ld, &relocated_addr, &image_size, false); + if (ret) + return 1; + + /* Handle BOOTM_STATE_LOADOS */ + if (relocated_addr != ld) { + printf("Moving Image from 0x%lx to 0x%lx, end=0x%lx\n", ld, + relocated_addr, relocated_addr + image_size); + memmove((void *)relocated_addr, (void *)ld, image_size); + } + + images->ep = relocated_addr; + images->os.start = relocated_addr; + images->os.end = relocated_addr + image_size; + + ep_addr = (phys_addr_t)images->ep; + ret = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &ep_addr, + le32_to_cpu(image_size), LMB_NONE); + if (ret) { + printf("Failed to allocate memory for the image at %#llx\n", + (unsigned long long)images->ep); + return 1; + } + + /* + * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not + * have a header that provide this informaiton. + */ + if (bootm_find_images(image_load_addr, bmi->conf_ramdisk, bmi->conf_fdt, + relocated_addr, image_size)) + return 1; + + return 0; +} + +int do_booti(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct bootm_info bmi; + int states; + int ret; + + /* Consume 'booti' */ + argc--; argv++; + + bootm_init(&bmi); + if (argc) + bmi.addr_img = argv[0]; + if (argc > 1) + bmi.conf_ramdisk = argv[1]; + if (argc > 2) + bmi.conf_fdt = argv[2]; + bmi.boot_progress = true; + bmi.cmd_name = "booti"; + /* do not set up argc and argv[] since nothing uses them */ + + if (booti_start(&bmi)) + return 1; + + /* + * We are doing the BOOTM_STATE_LOADOS state ourselves, so must + * disable interrupts ourselves + */ + bootm_disable_interrupts(); + + images.os.os = IH_OS_LINUX; + if (IS_ENABLED(CONFIG_RISCV_SMODE)) + images.os.arch = IH_ARCH_RISCV; + else if (IS_ENABLED(CONFIG_ARM64)) + images.os.arch = IH_ARCH_ARM64; + + states = BOOTM_STATE_MEASURE | BOOTM_STATE_OS_PREP | + BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO; + if (IS_ENABLED(CONFIG_SYS_BOOT_RAMDISK_HIGH)) + states |= BOOTM_STATE_RAMDISK; + + ret = bootm_run_states(&bmi, states); + + return ret; +} + +U_BOOT_LONGHELP(booti, + "[addr [initrd[:size]] [fdt]]\n" + " - boot Linux flat or compressed 'Image' stored at 'addr'\n" + "\tThe argument 'initrd' is optional and specifies the address\n" + "\tof an initrd in memory. The optional parameter ':size' allows\n" + "\tspecifying the size of a RAW initrd.\n" + "\tCurrently only booting from gz, bz2, lzma and lz4 compression\n" + "\ttypes are supported. In order to boot from any of these compressed\n" + "\timages, user have to set kernel_comp_addr_r and kernel_comp_size environment\n" + "\tvariables beforehand.\n" +#if defined(CONFIG_OF_LIBFDT) + "\tSince booting a Linux kernel requires a flat device-tree, a\n" + "\tthird argument providing the address of the device-tree blob\n" + "\tis required. To boot a kernel with a device-tree blob but\n" + "\twithout an initrd image, use a '-' for the initrd argument.\n" +#endif + ); + +U_BOOT_CMD( + booti, CONFIG_SYS_MAXARGS, 1, do_booti, + "boot Linux kernel 'Image' format from memory", booti_help_text +); diff --git a/cmd/bootm.c b/cmd/bootm.c new file mode 100644 index 00000000000..2c5aea26d98 --- /dev/null +++ b/cmd/bootm.c @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2009 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * Boot support + */ +#include <bootm.h> +#include <command.h> +#include <env.h> +#include <errno.h> +#include <image.h> +#include <malloc.h> +#include <nand.h> +#include <asm/byteorder.h> +#include <asm/global_data.h> +#include <linux/ctype.h> +#include <linux/err.h> +#include <u-boot/zlib.h> +#include <mapmem.h> + +DECLARE_GLOBAL_DATA_PTR; + +#if defined(CONFIG_CMD_IMI) +static int image_info(unsigned long addr); +#endif + +#if defined(CONFIG_CMD_IMLS) +#include <flash.h> +#include <mtd/cfi_flash.h> +#endif + +#if defined(CONFIG_CMD_IMLS) || defined(CONFIG_CMD_IMLS_NAND) +static int do_imls(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]); +#endif + +/* we overload the cmd field with our state machine info instead of a + * function pointer */ +static struct cmd_tbl cmd_bootm_sub[] = { + U_BOOT_CMD_MKENT(start, 0, 1, (void *)BOOTM_STATE_START, "", ""), + U_BOOT_CMD_MKENT(loados, 0, 1, (void *)BOOTM_STATE_LOADOS, "", ""), +#ifdef CONFIG_CMD_BOOTM_PRE_LOAD + U_BOOT_CMD_MKENT(preload, 0, 1, (void *)BOOTM_STATE_PRE_LOAD, "", ""), +#endif +#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH + U_BOOT_CMD_MKENT(ramdisk, 0, 1, (void *)BOOTM_STATE_RAMDISK, "", ""), +#endif +#ifdef CONFIG_OF_LIBFDT + U_BOOT_CMD_MKENT(fdt, 0, 1, (void *)BOOTM_STATE_FDT, "", ""), +#endif + U_BOOT_CMD_MKENT(cmdline, 0, 1, (void *)BOOTM_STATE_OS_CMDLINE, "", ""), + U_BOOT_CMD_MKENT(bdt, 0, 1, (void *)BOOTM_STATE_OS_BD_T, "", ""), + U_BOOT_CMD_MKENT(prep, 0, 1, (void *)BOOTM_STATE_OS_PREP, "", ""), + U_BOOT_CMD_MKENT(fake, 0, 1, (void *)BOOTM_STATE_OS_FAKE_GO, "", ""), + U_BOOT_CMD_MKENT(go, 0, 1, (void *)BOOTM_STATE_OS_GO, "", ""), +}; + +#if defined(CONFIG_CMD_BOOTM_PRE_LOAD) +static ulong bootm_get_addr(int argc, char *const argv[]) +{ + ulong addr; + + if (argc > 0) + addr = hextoul(argv[0], NULL); + else + addr = image_load_addr; + + return addr; +} +#endif + +static int do_bootm_subcommand(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootm_info bmi; + int ret = 0; + long state; + struct cmd_tbl *c; + + c = find_cmd_tbl(argv[0], &cmd_bootm_sub[0], ARRAY_SIZE(cmd_bootm_sub)); + argc--; argv++; + + if (c) { + state = (long)c->cmd; + if (state == BOOTM_STATE_START) + state |= BOOTM_STATE_PRE_LOAD | BOOTM_STATE_FINDOS | + BOOTM_STATE_FINDOTHER; +#if defined(CONFIG_CMD_BOOTM_PRE_LOAD) + if (state == BOOTM_STATE_PRE_LOAD) + state |= BOOTM_STATE_START; +#endif + } else { + /* Unrecognized command */ + return CMD_RET_USAGE; + } + + if (((state & BOOTM_STATE_START) != BOOTM_STATE_START) && + images.state >= state) { + printf("Trying to execute a command out of order\n"); + return CMD_RET_USAGE; + } + + bootm_init(&bmi); + if (argc) + bmi.addr_img = argv[0]; + if (argc > 1) + bmi.conf_ramdisk = argv[1]; + if (argc > 2) + bmi.conf_fdt = argv[2]; + bmi.cmd_name = "bootm"; + bmi.boot_progress = false; + + /* set up argc and argv[] since some OSes use them */ + bmi.argc = argc; + bmi.argv = argv; + + ret = bootm_run_states(&bmi, state); + +#if defined(CONFIG_CMD_BOOTM_PRE_LOAD) + if (!ret && (state & BOOTM_STATE_PRE_LOAD)) + env_set_hex("loadaddr_verified", + bootm_get_addr(argc, argv) + image_load_offset); +#endif + + return ret ? CMD_RET_FAILURE : 0; +} + +/*******************************************************************/ +/* bootm - boot application image from image in memory */ +/*******************************************************************/ + +int do_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct bootm_info bmi; + int ret; + + /* determine if we have a sub command */ + argc--; argv++; + if (argc > 0) { + char *endp; + + hextoul(argv[0], &endp); + /* endp pointing to NULL means that argv[0] was just a + * valid number, pass it along to the normal bootm processing + * + * If endp is ':' or '#' assume a FIT identifier so pass + * along for normal processing. + * + * Right now we assume the first arg should never be '-' + */ + if ((*endp != 0) && (*endp != ':') && (*endp != '#')) + return do_bootm_subcommand(cmdtp, flag, argc, argv); + } + + bootm_init(&bmi); + if (argc) + bmi.addr_img = argv[0]; + if (argc > 1) + bmi.conf_ramdisk = argv[1]; + if (argc > 2) + bmi.conf_fdt = argv[2]; + + /* set up argc and argv[] since some OSes use them */ + bmi.argc = argc; + bmi.argv = argv; + + ret = bootm_run(&bmi); + + return ret ? CMD_RET_FAILURE : 0; +} + +int bootm_maybe_autostart(struct cmd_tbl *cmdtp, const char *cmd) +{ + if (env_get_autostart()) { + char *local_args[2]; + local_args[0] = (char *)cmd; + local_args[1] = NULL; + printf("Automatic boot of image at addr 0x%08lX ...\n", + image_load_addr); + return do_bootm(cmdtp, 0, 1, local_args); + } + + return 0; +} + +U_BOOT_LONGHELP(bootm, + "[addr [arg ...]]\n - boot application image stored in memory\n" + "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n" + "\t'arg' can be the address of an initrd image\n" +#if defined(CONFIG_OF_LIBFDT) + "\tWhen booting a Linux kernel which requires a flat device-tree\n" + "\ta third argument is required which is the address of the\n" + "\tdevice-tree blob. To boot that kernel without an initrd image,\n" + "\tuse a '-' for the second argument. If you do not pass a third\n" + "\ta bd_info struct will be passed instead\n" +#endif +#if defined(CONFIG_FIT) + "\t\nFor the new multi component uImage format (FIT) addresses\n" + "\tmust be extended to include component or configuration unit name:\n" + "\taddr:<subimg_uname> - direct component image specification\n" + "\taddr#<conf_uname> - configuration specification\n" + "\tUse iminfo command to get the list of existing component\n" + "\timages and configurations.\n" +#endif + "\nSub-commands to do part of the bootm sequence. The sub-commands " + "must be\n" + "issued in the order below (it's ok to not issue all sub-commands):\n" + "\tstart [addr [arg ...]]\n" +#if defined(CONFIG_CMD_BOOTM_PRE_LOAD) + "\tpreload [addr [arg ..]] - run only the preload stage\n" +#endif + "\tloados - load OS image\n" +#if defined(CONFIG_SYS_BOOT_RAMDISK_HIGH) + "\tramdisk - relocate initrd, set env initrd_start/initrd_end\n" +#endif +#if defined(CONFIG_OF_LIBFDT) + "\tfdt - relocate flat device tree\n" +#endif + "\tcmdline - OS specific command line processing/setup\n" + "\tbdt - OS specific bd_info processing\n" + "\tprep - OS specific prep before relocation or go\n" +#if defined(CONFIG_TRACE) + "\tfake - OS specific fake start without go\n" +#endif + "\tgo - start OS"); + +U_BOOT_CMD( + bootm, CONFIG_SYS_MAXARGS, 1, do_bootm, + "boot application image from memory", bootm_help_text +); + +/*******************************************************************/ +/* bootd - boot default image */ +/*******************************************************************/ +#if defined(CONFIG_CMD_BOOTD) +int do_bootd(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return run_command(env_get("bootcmd"), flag); +} + +U_BOOT_CMD( + boot, 1, 1, do_bootd, + "boot default, i.e., run 'bootcmd'", + "" +); + +/* keep old command name "bootd" for backward compatibility */ +U_BOOT_CMD( + bootd, 1, 1, do_bootd, + "boot default, i.e., run 'bootcmd'", + "" +); + +#endif + +/*******************************************************************/ +/* iminfo - print header info for a requested image */ +/*******************************************************************/ +#if defined(CONFIG_CMD_IMI) +static int do_iminfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int arg; + ulong addr; + int rcode = 0; + + if (argc < 2) { + return image_info(image_load_addr); + } + + for (arg = 1; arg < argc; ++arg) { + addr = hextoul(argv[arg], NULL); + if (image_info(addr) != 0) + rcode = 1; + } + return rcode; +} + +static int image_info(ulong addr) +{ + void *hdr = (void *)map_sysmem(addr, 0); + + printf("\n## Checking Image at %08lx ...\n", addr); + + switch (genimg_get_format(hdr)) { +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + case IMAGE_FORMAT_LEGACY: + puts(" Legacy image found\n"); + if (!image_check_magic(hdr)) { + puts(" Bad Magic Number\n"); + unmap_sysmem(hdr); + return 1; + } + + if (!image_check_hcrc(hdr)) { + puts(" Bad Header Checksum\n"); + unmap_sysmem(hdr); + return 1; + } + + image_print_contents(hdr); + + puts(" Verifying Checksum ... "); + if (!image_check_dcrc(hdr)) { + puts(" Bad Data CRC\n"); + unmap_sysmem(hdr); + return 1; + } + puts("OK\n"); + unmap_sysmem(hdr); + return 0; +#endif +#if defined(CONFIG_ANDROID_BOOT_IMAGE) + case IMAGE_FORMAT_ANDROID: + puts(" Android image found\n"); + android_print_contents(hdr); + unmap_sysmem(hdr); + return 0; +#endif +#if defined(CONFIG_FIT) + case IMAGE_FORMAT_FIT: + puts(" FIT image found\n"); + + if (fit_check_format(hdr, IMAGE_SIZE_INVAL)) { + puts("Bad FIT image format!\n"); + unmap_sysmem(hdr); + return 1; + } + + fit_print_contents(hdr); + + if (!fit_all_image_verify(hdr)) { + puts("Bad hash in FIT image!\n"); + unmap_sysmem(hdr); + return 1; + } + + unmap_sysmem(hdr); + return 0; +#endif + default: + puts("Unknown image format!\n"); + break; + } + + unmap_sysmem(hdr); + return 1; +} + +U_BOOT_CMD( + iminfo, CONFIG_SYS_MAXARGS, 1, do_iminfo, + "print header information for application image", + "addr [addr ...]\n" + " - print header information for application image starting at\n" + " address 'addr' in memory; this includes verification of the\n" + " image contents (magic number, header and payload checksums)" +); +#endif + +/*******************************************************************/ +/* imls - list all images found in flash */ +/*******************************************************************/ +#if defined(CONFIG_CMD_IMLS) +static int do_imls_nor(void) +{ + flash_info_t *info; + int i, j; + void *hdr; + + for (i = 0, info = &flash_info[0]; + i < CFI_FLASH_BANKS; ++i, ++info) { + + if (info->flash_id == FLASH_UNKNOWN) + goto next_bank; + for (j = 0; j < info->sector_count; ++j) { + + hdr = (void *)info->start[j]; + if (!hdr) + goto next_sector; + + switch (genimg_get_format(hdr)) { +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + case IMAGE_FORMAT_LEGACY: + if (!image_check_hcrc(hdr)) + goto next_sector; + + printf("Legacy Image at %08lX:\n", (ulong)hdr); + image_print_contents(hdr); + + puts(" Verifying Checksum ... "); + if (!image_check_dcrc(hdr)) { + puts("Bad Data CRC\n"); + } else { + puts("OK\n"); + } + break; +#endif +#if defined(CONFIG_FIT) + case IMAGE_FORMAT_FIT: + if (fit_check_format(hdr, IMAGE_SIZE_INVAL)) + goto next_sector; + + printf("FIT Image at %08lX:\n", (ulong)hdr); + fit_print_contents(hdr); + break; +#endif + default: + goto next_sector; + } + +next_sector: ; + } +next_bank: ; + } + return 0; +} +#endif + +#if defined(CONFIG_CMD_IMLS_NAND) +static int nand_imls_legacyimage(struct mtd_info *mtd, int nand_dev, + loff_t off, size_t len) +{ + void *imgdata; + int ret; + + imgdata = malloc(len); + if (!imgdata) { + printf("May be a Legacy Image at NAND device %d offset %08llX:\n", + nand_dev, off); + printf(" Low memory(cannot allocate memory for image)\n"); + return -ENOMEM; + } + + ret = nand_read_skip_bad(mtd, off, &len, NULL, mtd->size, imgdata); + if (ret < 0 && ret != -EUCLEAN) { + free(imgdata); + return ret; + } + + if (!image_check_hcrc(imgdata)) { + free(imgdata); + return 0; + } + + printf("Legacy Image at NAND device %d offset %08llX:\n", + nand_dev, off); + image_print_contents(imgdata); + + puts(" Verifying Checksum ... "); + if (!image_check_dcrc(imgdata)) + puts("Bad Data CRC\n"); + else + puts("OK\n"); + + free(imgdata); + + return 0; +} + +static int nand_imls_fitimage(struct mtd_info *mtd, int nand_dev, loff_t off, + size_t len) +{ + void *imgdata; + int ret; + + imgdata = malloc(len); + if (!imgdata) { + printf("May be a FIT Image at NAND device %d offset %08llX:\n", + nand_dev, off); + printf(" Low memory(cannot allocate memory for image)\n"); + return -ENOMEM; + } + + ret = nand_read_skip_bad(mtd, off, &len, NULL, mtd->size, imgdata); + if (ret < 0 && ret != -EUCLEAN) { + free(imgdata); + return ret; + } + + if (fit_check_format(imgdata, IMAGE_SIZE_INVAL)) { + free(imgdata); + return 0; + } + + printf("FIT Image at NAND device %d offset %08llX:\n", nand_dev, off); + + fit_print_contents(imgdata); + free(imgdata); + + return 0; +} + +static int do_imls_nand(void) +{ + struct mtd_info *mtd; + int nand_dev = nand_curr_device; + size_t len; + loff_t off; + u32 buffer[16]; + + if (nand_dev < 0 || nand_dev >= CONFIG_SYS_MAX_NAND_DEVICE) { + puts("\nNo NAND devices available\n"); + return -ENODEV; + } + + printf("\n"); + + for (nand_dev = 0; nand_dev < CONFIG_SYS_MAX_NAND_DEVICE; nand_dev++) { + mtd = get_nand_dev_by_index(nand_dev); + if (!mtd->name || !mtd->size) + continue; + + for (off = 0; off < mtd->size; off += mtd->erasesize) { + const struct legacy_img_hdr *header; + int ret; + + if (nand_block_isbad(mtd, off)) + continue; + + len = sizeof(buffer); + + ret = nand_read(mtd, off, &len, (u8 *)buffer); + if (ret < 0 && ret != -EUCLEAN) { + printf("NAND read error %d at offset %08llX\n", + ret, off); + continue; + } + + switch (genimg_get_format(buffer)) { +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + case IMAGE_FORMAT_LEGACY: + header = (const struct legacy_img_hdr *)buffer; + + len = image_get_image_size(header); + nand_imls_legacyimage(mtd, nand_dev, off, len); + break; +#endif +#if defined(CONFIG_FIT) + case IMAGE_FORMAT_FIT: + len = fit_get_size(buffer); + nand_imls_fitimage(mtd, nand_dev, off, len); + break; +#endif + } + } + } + + return 0; +} +#endif + +#if defined(CONFIG_CMD_IMLS) || defined(CONFIG_CMD_IMLS_NAND) +static int do_imls(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret_nor = 0, ret_nand = 0; + +#if defined(CONFIG_CMD_IMLS) + ret_nor = do_imls_nor(); +#endif + +#if defined(CONFIG_CMD_IMLS_NAND) + ret_nand = do_imls_nand(); +#endif + + if (ret_nor) + return ret_nor; + + if (ret_nand) + return ret_nand; + + return 0; +} + +U_BOOT_CMD( + imls, 1, 1, do_imls, + "list all images found in flash", + "\n" + " - Prints information about all images found at sector/block\n" + " boundaries in nor/nand flash." +); +#endif diff --git a/cmd/bootmenu.c b/cmd/bootmenu.c new file mode 100644 index 00000000000..d3108778c6f --- /dev/null +++ b/cmd/bootmenu.c @@ -0,0 +1,717 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2011-2013 Pali Rohár <pali@kernel.org> + */ + +#include <charset.h> +#include <cli.h> +#include <command.h> +#include <ansi.h> +#include <efi_config.h> +#include <efi_variable.h> +#include <env.h> +#include <log.h> +#include <menu.h> +#include <watchdog.h> +#include <malloc.h> +#include <linux/delay.h> +#include <linux/string.h> + +/* maximum bootmenu entries */ +#define MAX_COUNT 99 + +/* maximal size of bootmenu env + * 9 = strlen("bootmenu_") + * 2 = strlen(MAX_COUNT) + * 1 = NULL term + */ +#define MAX_ENV_SIZE (9 + 2 + 1) + +enum bootmenu_ret { + BOOTMENU_RET_SUCCESS = 0, + BOOTMENU_RET_FAIL, + BOOTMENU_RET_QUIT, + BOOTMENU_RET_UPDATED, +}; + +enum boot_type { + BOOTMENU_TYPE_NONE = 0, + BOOTMENU_TYPE_BOOTMENU, + BOOTMENU_TYPE_UEFI_BOOT_OPTION, +}; + +struct bootmenu_entry { + unsigned short int num; /* unique number 0 .. MAX_COUNT */ + char key[3]; /* key identifier of number */ + char *title; /* title of entry */ + char *command; /* hush command of entry */ + enum boot_type type; /* boot type of entry */ + u16 bootorder; /* order for each boot type */ + struct bootmenu_data *menu; /* this bootmenu */ + struct bootmenu_entry *next; /* next menu entry (num+1) */ +}; + +static char *bootmenu_getoption(unsigned short int n) +{ + char name[MAX_ENV_SIZE]; + + if (n > MAX_COUNT) + return NULL; + + sprintf(name, "bootmenu_%d", n); + return env_get(name); +} + +static void bootmenu_print_entry(void *data) +{ + struct bootmenu_entry *entry = data; + int reverse = (entry->menu->active == entry->num); + + /* + * Move cursor to line where the entry will be drown (entry->num) + * First 3 lines contain bootmenu header + 1 empty line + */ + printf(ANSI_CURSOR_POSITION, entry->num + 4, 7); + + if (reverse) + puts(ANSI_COLOR_REVERSE); + + printf("%s", entry->title); + + if (reverse) + puts(ANSI_COLOR_RESET); +} + +static char *bootmenu_choice_entry(void *data) +{ + struct cli_ch_state s_cch, *cch = &s_cch; + struct bootmenu_data *menu = data; + struct bootmenu_entry *iter; + enum bootmenu_key key = BKEY_NONE; + int i; + + cli_ch_init(cch); + + while (1) { + if (menu->delay >= 0) { + /* Autoboot was not stopped */ + key = bootmenu_autoboot_loop(menu, cch); + } else { + /* Some key was pressed, so autoboot was stopped */ + key = bootmenu_loop(menu, cch); + } + + switch (key) { + case BKEY_UP: + menu->last_active = menu->active; + if (menu->active > 0) + --menu->active; + /* no menu key selected, regenerate menu */ + return NULL; + case BKEY_DOWN: + menu->last_active = menu->active; + if (menu->active < menu->count - 1) + ++menu->active; + /* no menu key selected, regenerate menu */ + return NULL; + case BKEY_SHORTCUT: + /* invalid shortcut, regenerate menu */ + if (cch->shortcut_key >= menu->count - 1) + return NULL; + /* shortcut_key value for Exit is is -1 */ + menu->active = cch->shortcut_key < 0 ? menu->count - 1 : + cch->shortcut_key; + fallthrough; + case BKEY_SELECT: + iter = menu->first; + for (i = 0; i < menu->active; ++i) + iter = iter->next; + return iter->key; + case BKEY_QUIT: + /* Quit by choosing the last entry */ + iter = menu->first; + while (iter->next) + iter = iter->next; + return iter->key; + default: + break; + } + } + + /* never happens */ + debug("bootmenu: this should not happen"); + return NULL; +} + +static bool bootmenu_need_reprint(void *data) +{ + struct bootmenu_data *menu = data; + bool need_reprint; + + need_reprint = menu->last_active != menu->active; + menu->last_active = menu->active; + + return need_reprint; +} + +static void bootmenu_destroy(struct bootmenu_data *menu) +{ + struct bootmenu_entry *iter = menu->first; + struct bootmenu_entry *next; + + while (iter) { + next = iter->next; + free(iter->title); + free(iter->command); + free(iter); + iter = next; + } + free(menu); +} + +static char bootmenu_entry_shortcut_key(int index) +{ + switch (index) { + /* 1-9 shortcut key (0 reserved) */ + case 0 ... 8: + return '1' + index; + /* a-z shortcut key */ + case 9 ... 34: + return 'a' + index - 9; + /* We support shortcut for up to 34 options (0 reserved) */ + default: + return -ENOENT; + } +} + +/** + * prepare_bootmenu_entry() - generate the bootmenu_xx entries + * + * This function read the "bootmenu_x" U-Boot environment variable + * and generate the bootmenu entries. + * + * @menu: pointer to the bootmenu structure + * @current: pointer to the last bootmenu entry list + * @index: pointer to the index of the last bootmenu entry, + * the number of bootmenu entry is added by this function + * Return: 1 on success, negative value on error + */ +static int prepare_bootmenu_entry(struct bootmenu_data *menu, + struct bootmenu_entry **current, + unsigned short int *index) +{ + char *sep; + const char *option; + unsigned short int i = *index; + struct bootmenu_entry *entry = NULL; + struct bootmenu_entry *iter = *current; + + while ((option = bootmenu_getoption(i))) { + char shortcut_key; + int len; + + /* bootmenu_[num] format is "[title]=[commands]" */ + sep = strchr(option, '='); + if (!sep) { + printf("Invalid bootmenu entry: %s\n", option); + break; + } + + entry = malloc(sizeof(struct bootmenu_entry)); + if (!entry) + return -ENOMEM; + + /* Add shotcut key option: %c. %s\0 */ + len = sep - option + 4; + + entry->title = malloc(len); + if (!entry->title) { + free(entry); + return -ENOMEM; + } + + shortcut_key = bootmenu_entry_shortcut_key(i); + /* Use emtpy space if entry doesn't support shortcut key */ + snprintf(entry->title, len, "%c%c %s", + shortcut_key > 0 ? shortcut_key : ' ', + shortcut_key > 0 ? '.' : ' ', + option); + + entry->command = strdup(sep + 1); + if (!entry->command) { + free(entry->title); + free(entry); + return -ENOMEM; + } + + sprintf(entry->key, "%d", i); + + entry->num = i; + entry->menu = menu; + entry->type = BOOTMENU_TYPE_BOOTMENU; + entry->bootorder = i; + entry->next = NULL; + + if (!iter) + menu->first = entry; + else + iter->next = entry; + + iter = entry; + ++i; + + if (i == MAX_COUNT - 1) + break; + } + + *index = i; + *current = iter; + + return 1; +} + +#if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR)) && (IS_ENABLED(CONFIG_CMD_EFICONFIG)) +/** + * prepare_uefi_bootorder_entry() - generate the uefi bootmenu entries + * + * This function reads the "BootOrder" UEFI variable + * and generate the bootmenu entries in the order of "BootOrder". + * + * @menu: pointer to the bootmenu structure + * @current: pointer to the last bootmenu entry list + * @index: pointer to the index of the last bootmenu entry, + * the number of uefi entry is added by this function + * Return: 1 on success, negative value on error + */ +static int prepare_uefi_bootorder_entry(struct bootmenu_data *menu, + struct bootmenu_entry **current, + unsigned short int *index) +{ + u16 *bootorder; + efi_status_t ret; + unsigned short j; + efi_uintn_t num, size; + void *load_option; + struct efi_load_option lo; + u16 varname[] = u"Boot####"; + unsigned short int i = *index; + struct bootmenu_entry *entry = NULL; + struct bootmenu_entry *iter = *current; + + bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size); + if (!bootorder) + return -ENOENT; + + num = size / sizeof(u16); + for (j = 0; j < num; j++) { + entry = malloc(sizeof(struct bootmenu_entry)); + if (!entry) + return -ENOMEM; + + efi_create_indexed_name(varname, sizeof(varname), + "Boot", bootorder[j]); + load_option = efi_get_var(varname, &efi_global_variable_guid, &size); + if (!load_option) + continue; + + ret = efi_deserialize_load_option(&lo, load_option, &size); + if (ret != EFI_SUCCESS) { + log_warning("Invalid load option for %ls\n", varname); + free(load_option); + free(entry); + continue; + } + + if (lo.attributes & LOAD_OPTION_ACTIVE) { + char *buf; + + buf = calloc(1, utf16_utf8_strlen(lo.label) + 1); + if (!buf) { + free(load_option); + free(entry); + free(bootorder); + return -ENOMEM; + } + entry->title = buf; + utf16_utf8_strncpy(&buf, lo.label, u16_strlen(lo.label)); + entry->command = strdup("bootefi bootmgr"); + sprintf(entry->key, "%d", i); + entry->num = i; + entry->menu = menu; + entry->type = BOOTMENU_TYPE_UEFI_BOOT_OPTION; + entry->bootorder = bootorder[j]; + entry->next = NULL; + + if (!iter) + menu->first = entry; + else + iter->next = entry; + + iter = entry; + i++; + } + + free(load_option); + + if (i == MAX_COUNT - 1) + break; + } + + free(bootorder); + *index = i; + *current = iter; + + return 1; +} +#endif + +/** + * bootmenu_create() - create boot menu entries + * + * @uefi: consider UEFI boot options + * @delay: autostart delay in seconds + */ +static struct bootmenu_data *bootmenu_create(int uefi, int delay) +{ + int ret; + unsigned short int i = 0; + struct bootmenu_data *menu; + struct bootmenu_entry *iter = NULL; + struct bootmenu_entry *entry; + char *default_str; + + menu = malloc(sizeof(struct bootmenu_data)); + if (!menu) + return NULL; + + menu->delay = delay; + menu->active = 0; + menu->last_active = -1; + menu->first = NULL; + + default_str = env_get("bootmenu_default"); + if (default_str) + menu->active = (int)simple_strtol(default_str, NULL, 10); + + ret = prepare_bootmenu_entry(menu, &iter, &i); + if (ret < 0) + goto cleanup; + +#if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR)) && (IS_ENABLED(CONFIG_CMD_EFICONFIG)) + if (uefi && i < MAX_COUNT - 1) { + efi_status_t efi_ret; + + /* + * UEFI specification requires booting from removal media using + * a architecture-specific default image name such as BOOTAA64.EFI. + */ + efi_ret = efi_bootmgr_update_media_device_boot_option(); + if (efi_ret != EFI_SUCCESS) + goto cleanup; + + ret = prepare_uefi_bootorder_entry(menu, &iter, &i); + if (ret < 0 && ret != -ENOENT) + goto cleanup; + } +#endif + + /* Add Exit entry at the end */ + if (i <= MAX_COUNT - 1) { + entry = malloc(sizeof(struct bootmenu_entry)); + if (!entry) + goto cleanup; + + /* Add Quit entry if exiting bootmenu is disabled */ + if (!IS_ENABLED(CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE)) + entry->title = strdup("0. Exit"); + else + entry->title = strdup("0. Quit"); + + if (!entry->title) { + free(entry); + goto cleanup; + } + + entry->command = strdup(""); + if (!entry->command) { + free(entry->title); + free(entry); + goto cleanup; + } + + sprintf(entry->key, "%d", i); + + entry->num = i; + entry->menu = menu; + entry->type = BOOTMENU_TYPE_NONE; + entry->next = NULL; + + if (!iter) + menu->first = entry; + else + iter->next = entry; + + iter = entry; + ++i; + } + + menu->count = i; + + if ((menu->active >= menu->count)||(menu->active < 0)) { //ensure active menuitem is inside menu + printf("active menuitem (%d) is outside menu (0..%d)\n",menu->active,menu->count-1); + menu->active=0; + } + + return menu; + +cleanup: + bootmenu_destroy(menu); + return NULL; +} + +static void menu_display_statusline(struct menu *m) +{ + struct bootmenu_entry *entry; + struct bootmenu_data *menu; + + if (menu_default_choice(m, (void *)&entry) < 0) + return; + + menu = entry->menu; + + printf(ANSI_CURSOR_POSITION, 1, 1); + puts(ANSI_CLEAR_LINE); + printf(ANSI_CURSOR_POSITION, 2, 3); + puts("*** U-Boot Boot Menu ***"); + puts(ANSI_CLEAR_LINE_TO_END); + printf(ANSI_CURSOR_POSITION, 3, 1); + puts(ANSI_CLEAR_LINE); + + /* First 3 lines are bootmenu header + 2 empty lines between entries */ + printf(ANSI_CURSOR_POSITION, menu->count + 5, 1); + puts(ANSI_CLEAR_LINE); + printf(ANSI_CURSOR_POSITION, menu->count + 6, 3); + puts("Press UP/DOWN to move, ENTER to select, ESC to quit"); + puts(ANSI_CLEAR_LINE_TO_END); + printf(ANSI_CURSOR_POSITION, menu->count + 7, 1); + puts(ANSI_CLEAR_LINE); +} + +static void handle_uefi_bootnext(void) +{ + u16 bootnext; + efi_status_t ret; + efi_uintn_t size; + + /* Initialize EFI drivers */ + ret = efi_init_obj_list(); + if (ret != EFI_SUCCESS) { + log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n", + ret & ~EFI_ERROR_MASK); + + return; + } + + /* If UEFI BootNext variable is set, boot the BootNext load option */ + size = sizeof(u16); + ret = efi_get_variable_int(u"BootNext", + &efi_global_variable_guid, + NULL, &size, &bootnext, NULL); + if (ret == EFI_SUCCESS) + /* BootNext does exist here, try to boot */ + run_command("bootefi bootmgr", 0); +} + +/** + * bootmenu_show - display boot menu + * + * @uefi: generated entries for UEFI boot options + * @delay: autoboot delay in seconds + */ +static enum bootmenu_ret bootmenu_show(int uefi, int delay) +{ + int cmd_ret; + int init = 0; + void *choice = NULL; + char *title = NULL; + char *command = NULL; + struct menu *menu; + struct bootmenu_entry *iter; + int ret = BOOTMENU_RET_SUCCESS; + struct bootmenu_data *bootmenu; + efi_status_t efi_ret = EFI_SUCCESS; + char *option, *sep; + + if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR) && uefi) + handle_uefi_bootnext(); + + /* If delay is 0 do not create menu, just run first entry */ + if (delay == 0) { + option = bootmenu_getoption(0); + if (!option) { + puts("bootmenu option 0 was not found\n"); + return BOOTMENU_RET_FAIL; + } + sep = strchr(option, '='); + if (!sep) { + puts("bootmenu option 0 is invalid\n"); + return BOOTMENU_RET_FAIL; + } + cmd_ret = run_command(sep + 1, 0); + return (cmd_ret == CMD_RET_SUCCESS ? BOOTMENU_RET_SUCCESS : BOOTMENU_RET_FAIL); + } + + bootmenu = bootmenu_create(uefi, delay); + if (!bootmenu) + return BOOTMENU_RET_FAIL; + + menu = menu_create(NULL, bootmenu->delay, 1, menu_display_statusline, + bootmenu_print_entry, bootmenu_choice_entry, + bootmenu_need_reprint, bootmenu); + if (!menu) { + bootmenu_destroy(bootmenu); + return BOOTMENU_RET_FAIL; + } + + for (iter = bootmenu->first; iter; iter = iter->next) { + if (menu_item_add(menu, iter->key, iter) != 1) + goto cleanup; + } + + /* Default menu entry is always first */ + menu_default_set(menu, "0"); + + puts(ANSI_CURSOR_HIDE); + puts(ANSI_CLEAR_CONSOLE); + printf(ANSI_CURSOR_POSITION, 1, 1); + + init = 1; + + if (menu_get_choice(menu, &choice) == 1) { + iter = choice; + title = strdup(iter->title); + command = strdup(iter->command); + + /* last entry exits bootmenu */ + if (iter->num == iter->menu->count - 1) { + ret = BOOTMENU_RET_QUIT; + goto cleanup; + } + } else { + goto cleanup; + } + + /* + * If the selected entry is UEFI BOOT####, set the BootNext variable. + * Then uefi bootmgr is invoked by the preset command in iter->command. + */ + if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR)) { + if (iter->type == BOOTMENU_TYPE_UEFI_BOOT_OPTION) { + /* + * UEFI specification requires BootNext variable needs non-volatile + * attribute, but this BootNext is only used inside of U-Boot and + * removed by efi bootmgr once BootNext is processed. + * So this BootNext can be volatile. + */ + efi_ret = efi_set_variable_int(u"BootNext", &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(u16), &iter->bootorder, false); + if (efi_ret != EFI_SUCCESS) + goto cleanup; + } + } + +cleanup: + menu_destroy(menu); + bootmenu_destroy(bootmenu); + + if (init) { + puts(ANSI_CURSOR_SHOW); + puts(ANSI_CLEAR_CONSOLE); + printf(ANSI_CURSOR_POSITION, 1, 1); + } + + if (title && command) { + debug("Starting entry '%s'\n", title); + free(title); + if (efi_ret == EFI_SUCCESS) + cmd_ret = run_command(command, 0); + free(command); + } + +#ifdef CFG_POSTBOOTMENU + run_command(CFG_POSTBOOTMENU, 0); +#endif + + if (efi_ret != EFI_SUCCESS || cmd_ret != CMD_RET_SUCCESS) + ret = BOOTMENU_RET_FAIL; + + return ret; +} + +#ifdef CONFIG_AUTOBOOT_MENU_SHOW +int menu_show(int bootdelay) +{ + int ret; + + while (1) { + ret = bootmenu_show(1, bootdelay); + bootdelay = -1; + if (ret == BOOTMENU_RET_UPDATED) + continue; + + if (IS_ENABLED(CONFIG_BOOTMENU_DISABLE_UBOOT_CONSOLE)) { + if (ret == BOOTMENU_RET_QUIT) { + /* default boot process */ + if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR)) + run_command("bootefi bootmgr", 0); + + run_command("run bootcmd", 0); + } + } else { + break; + } + } + + return -1; /* -1 - abort boot and run monitor code */ +} +#endif + +int do_bootmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *delay_str = NULL; + int delay = 10; + int uefi = 0; + +#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) + delay = CONFIG_BOOTDELAY; +#endif + + if (argc >= 2) { + if (!strcmp("-e", argv[1])) { + uefi = 1; + --argc; + ++argv; + } + } + if (argc >= 2) + delay_str = argv[1]; + + if (!delay_str) + delay_str = env_get("bootmenu_delay"); + + if (delay_str) + delay = (int)simple_strtol(delay_str, NULL, 10); + + bootmenu_show(uefi, delay); + return 0; +} + +U_BOOT_CMD( + bootmenu, 2, 1, do_bootmenu, + "ANSI terminal bootmenu", + "[-e] [delay]\n" + "-e - show UEFI entries\n" + "delay - show ANSI terminal bootmenu with autoboot delay" +); diff --git a/cmd/bootmeth.c b/cmd/bootmeth.c new file mode 100644 index 00000000000..ea4b3f47db8 --- /dev/null +++ b/cmd/bootmeth.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 'bootmeth' command + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <bootdev.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <command.h> +#include <dm.h> +#include <env.h> +#include <malloc.h> +#include <dm/uclass-internal.h> + +static int do_bootmeth_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct udevice *dev; + bool use_order; + bool all = false; + int ret; + int i; + + if (argc > 1 && *argv[1] == '-') { + all = strchr(argv[1], 'a'); + argc--; + argv++; + } + + ret = bootstd_get_priv(&std); + if (ret) { + printf("Cannot get bootstd (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + + printf("Order Seq Name Description\n"); + printf("----- --- ------------------ ------------------\n"); + + /* + * Use the ordering if we have one, so long as we are not trying to list + * all bootmethds + */ + use_order = std->bootmeth_count && !all; + if (use_order) + dev = std->bootmeth_order[0]; + else + ret = uclass_find_first_device(UCLASS_BOOTMETH, &dev); + + for (i = 0; dev;) { + struct bootmeth_uc_plat *ucp = dev_get_uclass_plat(dev); + int order = i; + + /* + * With the -a flag we may list bootdevs that are not in the + * ordering. Find their place in the order + */ + if (all && std->bootmeth_count) { + int j; + + /* Find the position of this bootmeth in the order */ + order = -1; + for (j = 0; j < std->bootmeth_count; j++) { + if (std->bootmeth_order[j] == dev) + order = j; + } + } + + if (ucp->flags & BOOTMETHF_GLOBAL) + printf("%5s", "glob"); + else if (order == -1) + printf("%5s", "-"); + else + printf("%5x", order); + printf(" %3x %-19.19s %s\n", dev_seq(dev), dev->name, + ucp->desc); + i++; + if (use_order) + dev = std->bootmeth_order[i]; + else + uclass_find_next_device(&dev); + } + printf("----- --- ------------------ ------------------\n"); + printf("(%d bootmeth%s)\n", i, i != 1 ? "s" : ""); + + return 0; +} + +static int do_bootmeth_order(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + ret = bootmeth_set_order(argv[1]); + if (ret) { + printf("Failed (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + env_set("bootmeths", argv[1]); + + return 0; +} + +static int do_bootmeth_set(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + if (argc < 4) { + printf("Required parameters not provided\n"); + return CMD_RET_FAILURE; + } + + ret = bootmeth_set_property(argv[1], argv[2], argv[3]); + if (ret) { + printf("Failed (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +U_BOOT_LONGHELP(bootmeth, + "list [-a] - list available bootmeths (-a all)\n" + "bootmeth order [<bd> ...] - select bootmeth order / subset to use\n" + "bootmeth set <bootmeth> <property> <value> - set optional property"); + +U_BOOT_CMD_WITH_SUBCMDS(bootmeth, "Boot methods", bootmeth_help_text, + U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootmeth_list), + U_BOOT_SUBCMD_MKENT(order, CONFIG_SYS_MAXARGS, 1, do_bootmeth_order), + U_BOOT_SUBCMD_MKENT(set, CONFIG_SYS_MAXARGS, 1, do_bootmeth_set)); diff --git a/cmd/bootstage.c b/cmd/bootstage.c new file mode 100644 index 00000000000..5c6d5a3ab45 --- /dev/null +++ b/cmd/bootstage.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2012, Google Inc. All rights reserved. + */ + +#include <bootstage.h> +#include <command.h> +#include <vsprintf.h> +#include <linux/string.h> + +static int do_bootstage_report(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bootstage_report(); + + return 0; +} + +#if IS_ENABLED(CONFIG_BOOTSTAGE_STASH) +static int get_base_size(int argc, char *const argv[], ulong *basep, + ulong *sizep) +{ + char *endp; + + *basep = CONFIG_BOOTSTAGE_STASH_ADDR; + *sizep = CONFIG_BOOTSTAGE_STASH_SIZE; + if (argc < 2) + return 0; + *basep = hextoul(argv[1], &endp); + if (*argv[1] == 0 || *endp != 0) + return -1; + if (argc == 2) + return 0; + *sizep = hextoul(argv[2], &endp); + if (*argv[2] == 0 || *endp != 0) + return -1; + + return 0; +} + +static int do_bootstage_stash(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong base, size; + int ret; + + if (get_base_size(argc, argv, &base, &size)) + return CMD_RET_USAGE; + if (base == -1UL) { + printf("No bootstage stash area defined\n"); + return 1; + } + + if (0 == strcmp(argv[0], "stash")) + ret = bootstage_stash((void *)base, size); + else + ret = bootstage_unstash((void *)base, size); + if (ret) + return 1; + + return 0; +} +#endif + +static struct cmd_tbl cmd_bootstage_sub[] = { + U_BOOT_CMD_MKENT(report, 2, 1, do_bootstage_report, "", ""), +#if IS_ENABLED(CONFIG_BOOTSTAGE_STASH) + U_BOOT_CMD_MKENT(stash, 4, 0, do_bootstage_stash, "", ""), + U_BOOT_CMD_MKENT(unstash, 4, 0, do_bootstage_stash, "", ""), +#endif +}; + +/* + * Process a bootstage sub-command + */ +static int do_boostage(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c; + + /* Strip off leading 'bootstage' command argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], cmd_bootstage_sub, + ARRAY_SIZE(cmd_bootstage_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_CMD(bootstage, 4, 1, do_boostage, + "Boot stage command", + " - check boot progress and timing\n" + "report - Print a report\n" +#if IS_ENABLED(CONFIG_BOOTSTAGE_STASH) + "stash [<start> [<size>]] - Stash data into memory\n" + "unstash [<start> [<size>]] - Unstash data from memory\n" +#endif +); diff --git a/cmd/bootstd.c b/cmd/bootstd.c new file mode 100644 index 00000000000..e1d82744eb5 --- /dev/null +++ b/cmd/bootstd.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 'bootstd' command + * + * Copyright 2024 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <command.h> +#include <dm.h> +#include <malloc.h> +#include <dm/uclass-internal.h> + +static int do_bootstd_images(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const struct bootflow *bflow; + struct bootstd_priv *std; + int ret, i; + + ret = bootstd_get_priv(&std); + if (ret) { + printf("Cannot get bootstd (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + + printf("Seq Bootflow Type At Size Filename\n"); + printf("--- ------------------- -------------- -------- -------- ----------------\n"); + + /* + * Use the ordering if we have one, so long as we are not trying to list + * all bootmethds + */ + i = 0; + alist_for_each(bflow, &std->bootflows) { + const struct bootflow_img *img; + + alist_for_each(img, &bflow->images) { + printf("%3d %-20.20s %-15.15s ", + bootflow_get_seq(bflow), bflow->name, + bootflow_img_type_name(img->type)); + if (img->addr) + printf("%8lx", img->addr); + else + printf("%8s", "-"); + printf(" %8lx %s\n", img->size, img->fname); + i++; + } + } + + printf("--- ------------------- -------------- -------- -------- ----------------\n"); + printf("(%d image%s)\n", i, i != 1 ? "s" : ""); + + return 0; +} + +U_BOOT_LONGHELP(bootstd, + "images - list loaded images"); + +U_BOOT_CMD_WITH_SUBCMDS(bootstd, "Standard-boot operation", bootstd_help_text, + U_BOOT_SUBCMD_MKENT(images, 1, 1, do_bootstd_images)); diff --git a/cmd/bootz.c b/cmd/bootz.c new file mode 100644 index 00000000000..44af7d012aa --- /dev/null +++ b/cmd/bootz.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2009 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <bootm.h> +#include <command.h> +#include <image.h> +#include <irq_func.h> +#include <lmb.h> +#include <log.h> +#include <linux/compiler.h> + +int __weak bootz_setup(ulong image, ulong *start, ulong *end) +{ + /* Please define bootz_setup() for your platform */ + + puts("Your platform's zImage format isn't supported yet!\n"); + return -1; +} + +/* + * zImage booting support + */ +static int bootz_start(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[], struct bootm_headers *images) +{ + ulong zi_start, zi_end; + struct bootm_info bmi; + phys_addr_t ep_addr; + int ret; + + bootm_init(&bmi); + if (argc) + bmi.addr_img = argv[0]; + if (argc > 1) + bmi.conf_ramdisk = argv[1]; + if (argc > 2) + bmi.conf_fdt = argv[2]; + /* do not set up argc and argv[] since nothing uses them */ + + ret = bootm_run_states(&bmi, BOOTM_STATE_START); + + /* Setup Linux kernel zImage entry point */ + if (!argc) { + images->ep = image_load_addr; + debug("* kernel: default image load address = 0x%08lx\n", + image_load_addr); + } else { + images->ep = hextoul(argv[0], NULL); + debug("* kernel: cmdline image address = 0x%08lx\n", + images->ep); + } + + ret = bootz_setup(images->ep, &zi_start, &zi_end); + if (ret != 0) + return 1; + + ep_addr = (phys_addr_t)images->ep; + ret = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &ep_addr, zi_end - zi_start, + LMB_NONE); + if (ret) { + printf("Failed to allocate memory for the image at %#llx\n", + (unsigned long long)images->ep); + return 1; + } + + /* + * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not + * have a header that provide this informaiton. + */ + if (bootm_find_images(image_load_addr, cmd_arg1(argc, argv), + cmd_arg2(argc, argv), images->ep, + zi_end - zi_start)) + return 1; + + return 0; +} + +int do_bootz(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct bootm_info bmi; + int ret; + + /* Consume 'bootz' */ + argc--; argv++; + + if (bootz_start(cmdtp, flag, argc, argv, &images)) + return 1; + + /* + * We are doing the BOOTM_STATE_LOADOS state ourselves, so must + * disable interrupts ourselves + */ + bootm_disable_interrupts(); + + images.os.os = IH_OS_LINUX; + + bootm_init(&bmi); + if (argc) + bmi.addr_img = argv[0]; + if (argc > 1) + bmi.conf_ramdisk = argv[1]; + if (argc > 2) + bmi.conf_fdt = argv[2]; + bmi.cmd_name = "bootz"; + + ret = bootz_run(&bmi); + + return ret; +} + +U_BOOT_LONGHELP(bootz, + "[addr [initrd[:size]] [fdt]]\n" + " - boot Linux zImage stored in memory\n" + "\tThe argument 'initrd' is optional and specifies the address\n" + "\tof the initrd in memory. The optional argument ':size' allows\n" + "\tspecifying the size of RAW initrd.\n" +#if defined(CONFIG_OF_LIBFDT) + "\tWhen booting a Linux kernel which requires a flat device-tree\n" + "\ta third argument is required which is the address of the\n" + "\tdevice-tree blob. To boot that kernel without an initrd image,\n" + "\tuse a '-' for the second argument. If you do not pass a third\n" + "\ta bd_info struct will be passed instead\n" +#endif + ); + +U_BOOT_CMD( + bootz, CONFIG_SYS_MAXARGS, 1, do_bootz, + "boot Linux zImage image from memory", bootz_help_text +); diff --git a/cmd/broadcom/Makefile b/cmd/broadcom/Makefile new file mode 100644 index 00000000000..62268d98d0d --- /dev/null +++ b/cmd/broadcom/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2020 Broadcom + +obj-y += chimp_boot.o +obj-y += nitro_image_load.o +obj-y += chimp_handshake.o diff --git a/cmd/broadcom/chimp_boot.c b/cmd/broadcom/chimp_boot.c new file mode 100644 index 00000000000..ae0a81179d0 --- /dev/null +++ b/cmd/broadcom/chimp_boot.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020 Broadcom + */ + +#include <command.h> +#include <broadcom/chimp.h> + +static int do_chimp_fastboot_secure(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 health = 0; + + if (chimp_health_status_optee(&health)) { + pr_err("Chimp health command fail\n"); + return CMD_RET_FAILURE; + } + + if (health == BCM_CHIMP_RUNNIG_GOOD) { + printf("skip fastboot...\n"); + return CMD_RET_SUCCESS; + } + + if (chimp_fastboot_optee()) { + pr_err("Failed to load secure ChiMP image\n"); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD + (chimp_ld_secure, 1, 0, do_chimp_fastboot_secure, + "Invoke chimp fw load via optee", + "chimp_ld_secure\n" +); diff --git a/cmd/broadcom/chimp_handshake.c b/cmd/broadcom/chimp_handshake.c new file mode 100644 index 00000000000..e2742671963 --- /dev/null +++ b/cmd/broadcom/chimp_handshake.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020 Broadcom + */ + +#include <command.h> +#include <broadcom/chimp.h> + +/* This command should be called after loading the nitro binaries */ +static int do_chimp_hs(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret = CMD_RET_USAGE; + u32 hstatus; + + /* Returns 1, if handshake call is success */ + if (chimp_handshake_status_optee(0, &hstatus)) + ret = CMD_RET_SUCCESS; + + if (hstatus == CHIMP_HANDSHAKE_SUCCESS) + printf("ChiMP Handshake successful\n"); + else + printf("ERROR: ChiMP Handshake status 0x%x\n", hstatus); + + return ret; +} + +U_BOOT_CMD + (chimp_hs, 1, 1, do_chimp_hs, + "Verify the Chimp handshake", + "chimp_hs\n" +); diff --git a/cmd/broadcom/nitro_image_load.c b/cmd/broadcom/nitro_image_load.c new file mode 100644 index 00000000000..fe08679840e --- /dev/null +++ b/cmd/broadcom/nitro_image_load.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020 Broadcom + */ + +#include <command.h> +#include <env.h> +#include <vsprintf.h> + +#define FW_IMAGE_SIG 0xff123456 +#define CFG_IMAGE_SIG 0xcf54321a + +/* + * structure for bin file + * signature: fw itb file + * size: fw itb file + * signature: NS3 config file + * size: NS3 config file + * Data: fw itb file + * ............................ + * ............................ + * Data: NS3 config file + * ............................ + * ............................ + */ + +static struct img_header { + u32 bin_sig; + u32 bin_size; + u32 cfg1_sig; + u32 cfg1_size; +} *img_header; + +static int env_set_val(const char *varname, ulong val) +{ + int ret; + + ret = env_set_hex(varname, val); + if (ret) + pr_err("Failed to %s env var\n", varname); + + return ret; +} + +static int do_spi_images_addr(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uintptr_t images_load_addr; + uintptr_t spi_load_addr; + u32 len; + u32 spi_data_offset = sizeof(struct img_header); + + if (argc != 3) + return CMD_RET_USAGE; + + /* convert command parameter to fastboot address (base 16), i.e. hex */ + images_load_addr = hextoul(argv[1], NULL); + if (!images_load_addr) { + pr_err("Invalid load address\n"); + return CMD_RET_USAGE; + } + + spi_load_addr = hextoul(argv[2], NULL); + if (!spi_load_addr) { + pr_err("Invalid spi load address\n"); + return CMD_RET_USAGE; + } + + img_header = (struct img_header *)images_load_addr; + + if (img_header->bin_sig != FW_IMAGE_SIG) { + pr_err("Invalid Nitro bin file\n"); + goto error; + } + + if (env_set_val("spi_nitro_fw_itb_start_addr", 0)) + goto error; + + if (env_set_val("spi_nitro_fw_itb_len", 0)) + goto error; + + if (env_set_val("spi_nitro_fw_ns3_cfg_start_addr", 0)) + goto error; + + if (env_set_val("spi_nitro_fw_ns3_cfg_len", 0)) + goto error; + + len = img_header->bin_size; + + if (env_set_val("spi_nitro_fw_itb_start_addr", + (spi_load_addr + spi_data_offset))) + goto error; + + if (env_set_val("spi_nitro_fw_itb_len", img_header->bin_size)) + goto error; + + spi_data_offset += len; + + if (img_header->cfg1_sig == CFG_IMAGE_SIG) { + len = img_header->cfg1_size; + + if (env_set_val("spi_nitro_fw_ns3_cfg_start_addr", + (spi_load_addr + spi_data_offset))) + goto error; + + if (env_set_val("spi_nitro_fw_ns3_cfg_len", len)) + goto error; + + spi_data_offset += len; + } + + /* disable secure boot */ + if (env_set_val("nitro_fastboot_secure", 0)) + goto error; + + return CMD_RET_SUCCESS; + +error: + return CMD_RET_FAILURE; +} + +U_BOOT_CMD + (spi_nitro_images_addr, 3, 1, do_spi_images_addr, + "Load the bnxt bin header and sets envs ", + "spi_nitro_images_addr <load_addr> <spi_base_addr>\n" +); diff --git a/cmd/btrfs.c b/cmd/btrfs.c new file mode 100644 index 00000000000..69d1b1f830d --- /dev/null +++ b/cmd/btrfs.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 2017 by Marek Behún <kabel@kernel.org> + */ + +#include <command.h> +#include <btrfs.h> +#include <fs.h> + +int do_btrsubvol(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + if (argc != 3) + return CMD_RET_USAGE; + + if (fs_set_blk_dev(argv[1], argv[2], FS_TYPE_BTRFS)) + return 1; + + btrfs_list_subvols(); + return 0; +} + +U_BOOT_CMD(btrsubvol, 3, 1, do_btrsubvol, + "list subvolumes of a BTRFS filesystem", + "<interface> <dev[:part]>\n" + " - List subvolumes of a BTRFS filesystem." +); diff --git a/cmd/button.c b/cmd/button.c new file mode 100644 index 00000000000..3e6db3f5b8e --- /dev/null +++ b/cmd/button.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Philippe Reynes <philippe.reynes@softathome.com> + * + * Based on led.c + */ + +#include <command.h> +#include <dm.h> +#include <button.h> +#include <dm/uclass-internal.h> + +static const char *const state_label[] = { + [BUTTON_OFF] = "off", + [BUTTON_ON] = "on", +}; + +static int show_button_state(struct udevice *dev) +{ + int ret; + + ret = button_get_state(dev); + if (ret >= BUTTON_COUNT) + ret = -EINVAL; + if (ret >= 0) + printf("%s\n", state_label[ret]); + + return ret; +} + +static int list_buttons(void) +{ + struct udevice *dev; + int ret; + + for (uclass_find_first_device(UCLASS_BUTTON, &dev); + dev; + uclass_find_next_device(&dev)) { + struct button_uc_plat *plat = dev_get_uclass_plat(dev); + + if (!plat->label) + continue; + printf("%-15s ", plat->label); + if (device_active(dev)) { + ret = show_button_state(dev); + if (ret < 0) + printf("Error %d\n", ret); + } else { + printf("<inactive>\n"); + } + } + + return 0; +} + +int do_button(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + const char *button_label; + struct udevice *dev; + int ret; + + /* Validate arguments */ + if (argc < 2) + return CMD_RET_USAGE; + button_label = argv[1]; + if (strncmp(button_label, "list", 4) == 0) + return list_buttons(); + + ret = button_get_by_label(button_label, &dev); + if (ret) { + printf("Button '%s' not found (err=%d)\n", button_label, ret); + return CMD_RET_FAILURE; + } + + ret = show_button_state(dev); + + return !ret; +} + +U_BOOT_CMD( + button, 2, 1, do_button, + "manage buttons", + "<button_label> \tGet button state\n" + "button list\t\tShow a list of buttons" +); diff --git a/cmd/c5_pl330_dma.c b/cmd/c5_pl330_dma.c new file mode 100644 index 00000000000..75e8c9b0d92 --- /dev/null +++ b/cmd/c5_pl330_dma.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Brian Sune <briansune@gmail.com> + */ + +#include <vsprintf.h> +#include <command.h> +#include <asm/io.h> + +#include <asm/arch/base_addr_ac5.h> + +#define RSTMGR_PERMODRST 0x18 /* PERMODRST register offset */ + +static int do_dmareset(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + u8 val; + int i, ch; + + if (argc < 2) { + printf("Usage: dmareset <channel 0-7> [<channel 0-7> ...]\n"); + return CMD_RET_USAGE; + } + + /* Read current register value */ + val = readb(SOCFPGA_RSTMGR_ADDRESS + RSTMGR_PERMODRST); + + /* Iterate over all channels given as arguments */ + for (i = 1; i < argc; i++) { + ch = simple_strtoul(argv[i], NULL, 0); + if (ch < 0 || ch > 7) { + printf("Error: channel must be 0-7\n"); + return CMD_RET_USAGE; + } + val &= ~(1 << ch); + printf("PL330 DMA channel %d reset released\n", ch); + } + + /* Write back */ + writeb(val, (SOCFPGA_RSTMGR_ADDRESS + RSTMGR_PERMODRST)); + + return 0; +} + +U_BOOT_CMD( + dmareset, 8, 0, do_dmareset, + "Release PL330 DMA channel reset(s) for SoCFPGA", + "dmareset <channel 0-7> [<channel 0-7> ...] - release reset for one or more DMA channels" +); diff --git a/cmd/cache.c b/cmd/cache.c new file mode 100644 index 00000000000..b7007877ab0 --- /dev/null +++ b/cmd/cache.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * Cache support: switch on or off, get status + */ +#include <command.h> +#include <cpu_func.h> +#include <linux/compiler.h> +#include <linux/string.h> + +static int parse_argv(const char *); + +static int do_icache(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + switch (argc) { + case 2: /* on / off / flush */ + switch (parse_argv(argv[1])) { + case 0: + icache_disable(); + break; + case 1: + icache_enable(); + break; + case 2: + invalidate_icache_all(); + break; + default: + return CMD_RET_USAGE; + } + break; + case 1: /* get status */ + printf("Instruction Cache is %s\n", + icache_status() ? "ON" : "OFF"); + return 0; + default: + return CMD_RET_USAGE; + } + return 0; +} + +static int do_dcache(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + switch (argc) { + case 2: /* on / off / flush */ + switch (parse_argv(argv[1])) { + case 0: + dcache_disable(); + break; + case 1: + dcache_enable(); +#ifdef CONFIG_SYS_NONCACHED_MEMORY + noncached_set_region(); +#endif + break; + case 2: + flush_dcache_all(); + break; + default: + return CMD_RET_USAGE; + } + break; + case 1: /* get status */ + printf("Data (writethrough) Cache is %s\n", + dcache_status() ? "ON" : "OFF"); + return 0; + default: + return CMD_RET_USAGE; + } + return 0; +} + +static int parse_argv(const char *s) +{ + if (strcmp(s, "flush") == 0) + return 2; + else if (strcmp(s, "on") == 0) + return 1; + else if (strcmp(s, "off") == 0) + return 0; + + return -1; +} + +U_BOOT_CMD( + icache, 2, 1, do_icache, + "enable or disable instruction cache", + "[on, off, flush]\n" + " - enable, disable, or flush instruction cache" +); + +U_BOOT_CMD( + dcache, 2, 1, do_dcache, + "enable or disable data cache", + "[on, off, flush]\n" + " - enable, disable, or flush data (writethrough) cache" +); diff --git a/cmd/cat.c b/cmd/cat.c new file mode 100644 index 00000000000..3167cda6032 --- /dev/null +++ b/cmd/cat.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 + * Roger Knecht <rknecht@pm.de> + */ + +#include <command.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> +#include <linux/errno.h> + +static int do_cat(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *ifname; + char *dev; + char *file; + char *buffer; + ulong file_size; + int ret; + + if (argc < 4) + return CMD_RET_USAGE; + + ifname = argv[1]; + dev = argv[2]; + file = argv[3]; + + ret = fs_load_alloc(ifname, dev, file, 0, 0, (void **)&buffer, + &file_size); + + // check file exists + switch (ret) { + case 0: + break; + case -ENOMEDIUM: + return CMD_RET_FAILURE; + case -ENOENT: + log_err("File does not exist: ifname=%s dev=%s file=%s\n", ifname, dev, file); + return CMD_RET_FAILURE; + case -E2BIG: + log_err("File is too large: ifname=%s dev=%s file=%s\n", ifname, dev, file); + return CMD_RET_FAILURE; + case -ENOMEM: + log_err("Not enough memory: ifname=%s dev=%s file=%s\n", ifname, dev, file); + return CMD_RET_FAILURE; + default: + case -EIO: + log_err("File-read failed: ifname=%s dev=%s file=%s\n", ifname, dev, file); + return CMD_RET_FAILURE; + } + + // print file content + buffer[file_size] = '\0'; + puts(buffer); + + free(buffer); + + return 0; +} + +U_BOOT_LONGHELP(cat, + "<interface> <dev[:part]> <file>\n" + " - Print file from 'dev' on 'interface' to standard output\n"); + +U_BOOT_CMD(cat, 4, 1, do_cat, + "Print file to standard output", + cat_help_text +); diff --git a/cmd/cbfs.c b/cmd/cbfs.c new file mode 100644 index 00000000000..c1035461df1 --- /dev/null +++ b/cmd/cbfs.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + */ + +/* + * CBFS commands + */ +#include <command.h> +#include <env.h> +#include <cbfs.h> +#include <vsprintf.h> + +static int do_cbfs_init(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uintptr_t end_of_rom = 0xffffffff; + char *ep; + + if (argc > 2) { + printf("usage: cbfsls [end of rom]>\n"); + return 0; + } + if (argc == 2) { + end_of_rom = hextoul(argv[1], &ep); + if (*ep) { + puts("\n** Invalid end of ROM **\n"); + return 1; + } + } + if (file_cbfs_init(end_of_rom)) { + printf("%s.\n", file_cbfs_error()); + return 1; + } + return 0; +} + +U_BOOT_CMD( + cbfsinit, 2, 0, do_cbfs_init, + "initialize the cbfs driver", + "[end of rom]\n" + " - Initialize the cbfs driver. The optional 'end of rom'\n" + " parameter specifies where the end of the ROM is that the\n" + " CBFS is in. It defaults to 0xFFFFFFFF\n" +); + +static int do_cbfs_fsload(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const struct cbfs_cachenode *file; + unsigned long offset; + unsigned long count; + long size; + + if (argc < 3) { + printf("usage: cbfsload <addr> <filename> [bytes]\n"); + return 1; + } + + /* parse offset and count */ + offset = hextoul(argv[1], NULL); + if (argc == 4) + count = hextoul(argv[3], NULL); + else + count = 0; + + file = file_cbfs_find(argv[2]); + if (!file) { + if (cbfs_get_result() == CBFS_FILE_NOT_FOUND) + printf("%s: %s\n", file_cbfs_error(), argv[2]); + else + printf("%s.\n", file_cbfs_error()); + return 1; + } + + printf("reading %s\n", file_cbfs_name(file)); + + size = file_cbfs_read(file, (void *)offset, count); + + printf("\n%ld bytes read\n", size); + + env_set_hex("filesize", size); + + return 0; +} + +U_BOOT_CMD( + cbfsload, 4, 0, do_cbfs_fsload, + "load binary file from a cbfs filesystem", + "<addr> <filename> [bytes]\n" + " - load binary file 'filename' from the cbfs to address 'addr'\n" +); + +static int do_cbfs_ls(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const struct cbfs_cachenode *file = file_cbfs_get_first(); + int files = 0; + + if (!file) { + printf("%s.\n", file_cbfs_error()); + return 1; + } + + printf(" size type name\n"); + printf("------------------------------------------\n"); + while (file) { + int type = file_cbfs_type(file); + char *type_name = NULL; + const char *filename = file_cbfs_name(file); + + printf(" %8d", file_cbfs_size(file)); + + switch (type) { + case CBFS_TYPE_BOOTBLOCK: + type_name = "bootblock"; + break; + case CBFS_TYPE_CBFSHEADER: + type_name = "cbfs header"; + break; + case CBFS_TYPE_LEGACY_STAGE: + type_name = "stage"; + break; + case CBFS_TYPE_PAYLOAD: + type_name = "payload"; + break; + case CBFS_TYPE_FIT: + type_name = "fit"; + break; + case CBFS_TYPE_OPTIONROM: + type_name = "option rom"; + break; + case CBFS_TYPE_BOOTSPLASH: + type_name = "boot splash"; + break; + case CBFS_TYPE_RAW: + type_name = "raw"; + break; + case CBFS_TYPE_VSA: + type_name = "vsa"; + break; + case CBFS_TYPE_MBI: + type_name = "mbi"; + break; + case CBFS_TYPE_MICROCODE: + type_name = "microcode"; + break; + case CBFS_TYPE_FSP: + type_name = "fsp"; + break; + case CBFS_TYPE_MRC: + type_name = "mrc"; + break; + case CBFS_TYPE_MMA: + type_name = "mma"; + break; + case CBFS_TYPE_EFI: + type_name = "efi"; + break; + case CBFS_TYPE_STRUCT: + type_name = "struct"; + break; + case CBFS_TYPE_CMOS_DEFAULT: + type_name = "cmos default"; + break; + case CBFS_TYPE_SPD: + type_name = "spd"; + break; + case CBFS_TYPE_MRC_CACHE: + type_name = "mrc cache"; + break; + case CBFS_TYPE_CMOS_LAYOUT: + type_name = "cmos layout"; + break; + case -1: + case 0: + type_name = "null"; + break; + } + if (type_name) + printf(" %16s", type_name); + else + printf(" %16d", type); + + if (filename[0]) + printf(" %s\n", filename); + else + printf(" %s\n", "(empty)"); + file_cbfs_get_next(&file); + files++; + } + + printf("\n%d file(s)\n\n", files); + return 0; +} + +U_BOOT_CMD( + cbfsls, 1, 1, do_cbfs_ls, + "list files", + " - list the files in the cbfs\n" +); + +static int do_cbfs_fsinfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const struct cbfs_header *header = file_cbfs_get_header(); + + if (!header) { + printf("%s.\n", file_cbfs_error()); + return 1; + } + + printf("\n"); + printf("CBFS version: %#x\n", header->version); + printf("ROM size: %#x\n", header->rom_size); + printf("Boot block size: %#x\n", header->boot_block_size); + printf("CBFS size: %#x\n", + header->rom_size - header->boot_block_size - header->offset); + printf("Alignment: %d\n", header->align); + printf("Offset: %#x\n", header->offset); + printf("\n"); + + return 0; +} + +U_BOOT_CMD( + cbfsinfo, 1, 1, do_cbfs_fsinfo, + "print information about filesystem", + " - print information about the cbfs filesystem\n" +); diff --git a/cmd/cedit.c b/cmd/cedit.c new file mode 100644 index 00000000000..20f48ae0007 --- /dev/null +++ b/cmd/cedit.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 'cedit' command + * + * Copyright 2023 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <abuf.h> +#include <cedit.h> +#include <command.h> +#include <dm.h> +#include <expo.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> +#include <dm/ofnode.h> +#include <linux/sizes.h> + +struct expo *cur_exp; + +static int check_cur_expo(void) +{ + if (!cur_exp) { + printf("No expo loaded\n"); + return -ENOENT; + } + + return 0; +} + +static int do_cedit_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *fname; + struct expo *exp; + oftree tree; + ulong size; + void *buf; + int ret; + + if (argc < 4) + return CMD_RET_USAGE; + fname = argv[3]; + + ret = fs_load_alloc(argv[1], argv[2], argv[3], SZ_1M, 0, &buf, &size); + if (ret) { + printf("File not found\n"); + return CMD_RET_FAILURE; + } + + tree = oftree_from_fdt(buf); + if (!oftree_valid(tree)) { + printf("Cannot create oftree\n"); + return CMD_RET_FAILURE; + } + + ret = expo_build(oftree_root(tree), &exp); + oftree_dispose(tree); + if (ret) { + printf("Failed to build expo: %dE\n", ret); + return CMD_RET_FAILURE; + } + + cur_exp = exp; + + return 0; +} + +#ifdef CONFIG_COREBOOT_SYSINFO +static int do_cedit_cb_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct expo *exp; + int ret; + + if (argc > 1) + return CMD_RET_USAGE; + + ret = cb_expo_build(&exp); + if (ret) { + printf("Failed to build expo: %dE\n", ret); + return CMD_RET_FAILURE; + } + + cur_exp = exp; + + return 0; +} +#endif /* CONFIG_COREBOOT_SYSINFO */ + +static int do_cedit_write_fdt(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *fname; + struct abuf buf; + loff_t bytes; + int ret; + + if (argc < 4) + return CMD_RET_USAGE; + fname = argv[3]; + + if (check_cur_expo()) + return CMD_RET_FAILURE; + + ret = cedit_write_settings(cur_exp, &buf); + if (ret) { + printf("Failed to write settings: %dE\n", ret); + return CMD_RET_FAILURE; + } + + if (fs_set_blk_dev(argv[1], argv[2], FS_TYPE_ANY)) + return CMD_RET_FAILURE; + + ret = fs_write(fname, map_to_sysmem(abuf_data(&buf)), 0, + abuf_size(&buf), &bytes); + if (ret) + return CMD_RET_FAILURE; + + return 0; +} + +static int do_cedit_read_fdt(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *fname; + void *buf; + oftree tree; + ulong size; + int ret; + + if (argc < 4) + return CMD_RET_USAGE; + fname = argv[3]; + + ret = fs_load_alloc(argv[1], argv[2], argv[3], SZ_1M, 0, &buf, &size); + if (ret) { + printf("File not found\n"); + return CMD_RET_FAILURE; + } + + tree = oftree_from_fdt(buf); + if (!oftree_valid(tree)) { + free(buf); + printf("Cannot create oftree\n"); + return CMD_RET_FAILURE; + } + + ret = cedit_read_settings(cur_exp, tree); + oftree_dispose(tree); + free(buf); + if (ret) { + printf("Failed to read settings: %dE\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +static int do_cedit_write_env(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool verbose; + int ret; + + if (check_cur_expo()) + return CMD_RET_FAILURE; + + verbose = argc > 1 && !strcmp(argv[1], "-v"); + + ret = cedit_write_settings_env(cur_exp, verbose); + if (ret) { + printf("Failed to write settings to environment: %dE\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +static int do_cedit_read_env(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool verbose; + int ret; + + if (check_cur_expo()) + return CMD_RET_FAILURE; + + verbose = argc > 1 && !strcmp(argv[1], "-v"); + + ret = cedit_read_settings_env(cur_exp, verbose); + if (ret) { + printf("Failed to read settings from environment: %dE\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +static int do_cedit_write_cmos(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + bool verbose = false; + int ret; + + if (check_cur_expo()) + return CMD_RET_FAILURE; + + if (argc > 1 && !strcmp(argv[1], "-v")) { + verbose = true; + argc--; + argv++; + } + + if (argc > 1) + ret = uclass_get_device_by_name(UCLASS_RTC, argv[1], &dev); + else + ret = uclass_first_device_err(UCLASS_RTC, &dev); + if (ret) { + printf("Failed to get RTC device: %dE\n", ret); + return CMD_RET_FAILURE; + } + + if (cedit_write_settings_cmos(cur_exp, dev, verbose)) { + printf("Failed to write settings to CMOS\n"); + return CMD_RET_FAILURE; + } + + return 0; +} + +static int do_cedit_read_cmos(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + bool verbose = false; + int ret; + + if (check_cur_expo()) + return CMD_RET_FAILURE; + + if (argc > 1 && !strcmp(argv[1], "-v")) { + verbose = true; + argc--; + argv++; + } + + if (argc > 1) + ret = uclass_get_device_by_name(UCLASS_RTC, argv[1], &dev); + else + ret = uclass_first_device_err(UCLASS_RTC, &dev); + if (ret) { + printf("Failed to get RTC device: %dE\n", ret); + return CMD_RET_FAILURE; + } + + ret = cedit_read_settings_cmos(cur_exp, dev, verbose); + if (ret) { + printf("Failed to read settings from CMOS: %dE\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +static int do_cedit_run(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ofnode node; + int ret; + + if (check_cur_expo()) + return CMD_RET_FAILURE; + + node = ofnode_path("/bootstd/cedit-theme"); + if (ofnode_valid(node)) { + ret = expo_apply_theme(cur_exp, node); + if (ret) + return CMD_RET_FAILURE; + } else { + log_warning("No theme found\n"); + } + ret = cedit_run(cur_exp); + if (ret) { + log_err("Failed (err=%dE)\n", ret); + return CMD_RET_FAILURE; + } + expo_destroy(cur_exp); + cur_exp = NULL; + + return 0; +} + +U_BOOT_LONGHELP(cedit, + "load <interface> <dev[:part]> <filename> - load config editor\n" +#ifdef CONFIG_COREBOOT_SYSINFO + "cb_load - load coreboot CMOS editor\n" +#endif + "cedit read_fdt <i/f> <dev[:part]> <filename> - read settings\n" + "cedit write_fdt <i/f> <dev[:part]> <filename> - write settings\n" + "cedit read_env [-v] - read settings from env vars\n" + "cedit write_env [-v] - write settings to env vars\n" + "cedit read_cmos [-v] [dev] - read settings from CMOS RAM\n" + "cedit write_cmos [-v] [dev] - write settings to CMOS RAM\n" + "cedit run - run config editor"); + +U_BOOT_CMD_WITH_SUBCMDS(cedit, "Configuration editor", cedit_help_text, + U_BOOT_SUBCMD_MKENT(load, 5, 1, do_cedit_load), +#ifdef CONFIG_COREBOOT_SYSINFO + U_BOOT_SUBCMD_MKENT(cb_load, 5, 1, do_cedit_cb_load), +#endif + U_BOOT_SUBCMD_MKENT(read_fdt, 5, 1, do_cedit_read_fdt), + U_BOOT_SUBCMD_MKENT(write_fdt, 5, 1, do_cedit_write_fdt), + U_BOOT_SUBCMD_MKENT(read_env, 2, 1, do_cedit_read_env), + U_BOOT_SUBCMD_MKENT(write_env, 2, 1, do_cedit_write_env), + U_BOOT_SUBCMD_MKENT(read_cmos, 2, 1, do_cedit_read_cmos), + U_BOOT_SUBCMD_MKENT(write_cmos, 2, 1, do_cedit_write_cmos), + U_BOOT_SUBCMD_MKENT(run, 1, 1, do_cedit_run), +); diff --git a/cmd/cli.c b/cmd/cli.c new file mode 100644 index 00000000000..e0ddd0a43d0 --- /dev/null +++ b/cmd/cli.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <cli.h> +#include <command.h> +#include <string.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const char *gd_flags_to_parser_name(void) +{ + if (gd->flags & GD_FLG_HUSH_OLD_PARSER) + return "old"; + if (gd->flags & GD_FLG_HUSH_MODERN_PARSER) + return "modern"; + return NULL; +} + +static int do_cli_get(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *current = gd_flags_to_parser_name(); + + if (!current) { + printf("current cli value is not valid, this should not happen!\n"); + return CMD_RET_FAILURE; + } + + printf("%s\n", current); + + return CMD_RET_SUCCESS; +} + +static int parser_string_to_gd_flags(const char *parser) +{ + if (!strcmp(parser, "old")) + return GD_FLG_HUSH_OLD_PARSER; + if (!strcmp(parser, "modern")) + return GD_FLG_HUSH_MODERN_PARSER; + return -1; +} + +static int gd_flags_to_parser_config(int flag) +{ + if (gd->flags & GD_FLG_HUSH_OLD_PARSER) + return CONFIG_VAL(HUSH_OLD_PARSER); + if (gd->flags & GD_FLG_HUSH_MODERN_PARSER) + return CONFIG_VAL(HUSH_MODERN_PARSER); + return -1; +} + +static void reset_parser_gd_flags(void) +{ + gd->flags &= ~GD_FLG_HUSH_OLD_PARSER; + gd->flags &= ~GD_FLG_HUSH_MODERN_PARSER; +} + +static int do_cli_set(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *parser_name; + int parser_config; + int parser_flag; + + if (argc < 2) + return CMD_RET_USAGE; + + parser_name = argv[1]; + + parser_flag = parser_string_to_gd_flags(parser_name); + if (parser_flag == -1) { + printf("Bad value for parser name: %s\n", parser_name); + return CMD_RET_USAGE; + } + + parser_config = gd_flags_to_parser_config(parser_flag); + switch (parser_config) { + case -1: + printf("Bad value for parser flags: %d\n", parser_flag); + return CMD_RET_FAILURE; + case 0: + printf("Want to set current parser to %s, but its code was not compiled!\n", + parser_name); + return CMD_RET_FAILURE; + } + + reset_parser_gd_flags(); + gd->flags |= parser_flag; + + cli_init(); + cli_loop(); + + /* cli_loop() should never return. */ + return CMD_RET_FAILURE; +} + +static struct cmd_tbl parser_sub[] = { + U_BOOT_CMD_MKENT(get, 1, 1, do_cli_get, "", ""), + U_BOOT_CMD_MKENT(set, 2, 1, do_cli_set, "", ""), +}; + +static int do_cli(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cp; + + if (argc < 2) + return CMD_RET_USAGE; + + /* drop initial "parser" arg */ + argc--; + argv++; + + cp = find_cmd_tbl(argv[0], parser_sub, ARRAY_SIZE(parser_sub)); + if (cp) + return cp->cmd(cmdtp, flag, argc, argv); + + return CMD_RET_USAGE; +} + +U_BOOT_LONGHELP(cli, + "get - print current cli\n" + "set - set the current cli, possible value are: old, modern\n"); + +U_BOOT_CMD(cli, 3, 1, do_cli, + "cli", + cli_help_text +); diff --git a/cmd/clk.c b/cmd/clk.c new file mode 100644 index 00000000000..2fc834e5549 --- /dev/null +++ b/cmd/clk.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 Xilinx, Inc. + */ +#include <command.h> +#include <clk.h> +#include <dm.h> +#include <dm/device.h> +#include <dm/root.h> +#include <dm/device-internal.h> +#include <linux/clk-provider.h> + +static void show_clks(struct udevice *dev, int depth, int last_flag) +{ + int i, is_last; + struct udevice *child; + struct clk *clkp, *parent; + u32 rate; + + clkp = dev_get_clk_ptr(dev); + if (clkp) { + parent = clk_get_parent(clkp); + if (!IS_ERR(parent) && depth == -1) + return; + depth++; + rate = clk_get_rate(clkp); + + printf(" %-12u %8d ", rate, clkp->enable_count); + + for (i = depth; i >= 0; i--) { + is_last = (last_flag >> i) & 1; + if (i) { + if (is_last) + printf(" "); + else + printf("| "); + } else { + if (is_last) + printf("`-- "); + else + printf("|-- "); + } + } + + printf("%s\n", dev->name); + } + + device_foreach_child_probe(child, dev) { + if (device_get_uclass_id(child) != UCLASS_CLK) + continue; + if (child == dev) + continue; + is_last = list_is_last(&child->sibling_node, &dev->child_head); + show_clks(child, depth, (last_flag << 1) | is_last); + } +} + +static int soc_clk_dump(void) +{ + struct udevice *dev; + const struct clk_ops *ops; + + printf(" Rate Usecnt Name\n"); + printf("------------------------------------------\n"); + + uclass_foreach_dev_probe(UCLASS_CLK, dev) + show_clks(dev, -1, 0); + + uclass_foreach_dev_probe(UCLASS_CLK, dev) { + ops = dev_get_driver_ops(dev); + if (ops && ops->dump) { + printf("\n%s %s:\n", dev->driver->name, dev->name); + ops->dump(dev); + } + } + + return 0; +} + +static int do_clk_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + ret = soc_clk_dump(); + if (ret < 0) { + printf("Clock dump error %d\n", ret); + ret = CMD_RET_FAILURE; + } + + return ret; +} + +static int do_clk_setfreq(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct clk *clk = NULL; + s32 freq; + struct udevice *dev; + + if (argc != 3) + return CMD_RET_USAGE; + + freq = dectoul(argv[2], NULL); + + if (!uclass_get_device_by_name(UCLASS_CLK, argv[1], &dev)) + clk = dev_get_clk_ptr(dev); + + if (!clk) { + printf("clock '%s' not found.\n", argv[1]); + return CMD_RET_FAILURE; + } + + freq = clk_set_rate(clk, freq); + if (freq < 0) { + printf("set_rate failed: %d\n", freq); + return CMD_RET_FAILURE; + } + + printf("set_rate returns %u\n", freq); + return 0; +} + +static struct cmd_tbl cmd_clk_sub[] = { + U_BOOT_CMD_MKENT(dump, 1, 1, do_clk_dump, "", ""), + U_BOOT_CMD_MKENT(setfreq, 3, 1, do_clk_setfreq, "", ""), +}; + +static int do_clk(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c; + + if (argc < 2) + return CMD_RET_USAGE; + + /* Strip off leading 'clk' command argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], &cmd_clk_sub[0], ARRAY_SIZE(cmd_clk_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_LONGHELP(clk, + "dump - Print clock frequencies\n" + "clk setfreq [clk] [freq] - Set clock frequency"); + +U_BOOT_CMD(clk, 4, 1, do_clk, "CLK sub-system", clk_help_text); diff --git a/cmd/clone.c b/cmd/clone.c new file mode 100644 index 00000000000..1f3cff1836d --- /dev/null +++ b/cmd/clone.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 John Chau <john@harmon.hk> + * + */ + +#include <command.h> +#include <malloc.h> +#include <part.h> +#include <blk.h> +#include <time.h> +#include <vsprintf.h> + +#define BUFSIZE (1 * 1024 * 1024) +static int do_clone(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + int srcdev, destdev; + struct blk_desc *srcdesc, *destdesc; + int srcbz, destbz, ret; + char *unit, *buf; + unsigned long wrcnt, rdcnt, requested, srcblk, destblk; + unsigned long timer; + const unsigned long buffersize = 1024 * 1024; + + if (argc < 6) + return CMD_RET_USAGE; + + srcdev = blk_get_device_by_str(argv[1], argv[2], &srcdesc); + destdev = blk_get_device_by_str(argv[3], argv[4], &destdesc); + if (srcdev < 0) { + printf("Unable to open source device\n"); + return 1; + } else if (destdev < 0) { + printf("Unable to open destination device\n"); + return 1; + } + requested = dectoul(argv[5], &unit); + srcbz = srcdesc->blksz; + destbz = destdesc->blksz; + + if ((srcbz * (buffersize / srcbz) != buffersize) || + (destbz * (buffersize / destbz) != buffersize)) { + printf("failed: cannot match device block sizes\n"); + return 1; + } + if (requested == 0) { + unsigned long a = srcdesc->lba * srcdesc->blksz; + unsigned long b = destdesc->lba * destdesc->blksz; + + if (a > b) + requested = a; + else + requested = b; + } else { + switch (unit[0]) { + case 'g': + case 'G': + requested *= 1024 * 1024 * 1024; + break; + case 'm': + case 'M': + requested *= 1024 * 1024; + break; + case 'k': + case 'K': + requested *= 1024; + break; + } + } + printf("Copying %ld bytes from %s:%s to %s:%s\n", + requested, argv[1], argv[2], argv[3], argv[4]); + wrcnt = 0; + rdcnt = 0; + buf = (char *)malloc(BUFSIZE); + srcblk = 0; + destblk = 0; + timer = get_timer(0); + while (wrcnt < requested) { + unsigned long toread = BUFSIZE / srcbz; + unsigned long towrite = BUFSIZE / destbz; + unsigned long offset = 0; + +read: + ret = blk_dread(srcdesc, srcblk, toread, buf + offset); + if (ret < 0) { + printf("Src read error @blk %ld\n", srcblk); + goto exit; + } + rdcnt += ret * srcbz; + srcblk += ret; + if (ret < toread) { + toread -= ret; + offset += ret * srcbz; + goto read; + } + offset = 0; +write: + ret = blk_dwrite(destdesc, destblk, towrite, buf + offset); + if (ret < 0) { + printf("Dest write error @blk %ld\n", srcblk); + goto exit; + } + wrcnt += ret * destbz; + destblk += ret; + if (ret < towrite) { + towrite -= ret; + offset += ret * destbz; + goto write; + } + } + +exit: + timer = get_timer(timer); + timer = 1000 * timer / CONFIG_SYS_HZ; + printf("%ld read\n", rdcnt); + printf("%ld written\n", wrcnt); + printf("%ldms, %ldkB/s\n", timer, wrcnt / timer); + free(buf); + + return 0; +} + +U_BOOT_CMD( + clone, 6, 1, do_clone, + "simple storage cloning", + "<src interface> <src dev> <dest interface> <dest dev> <size[K/M/G]>\n" + "clone storage from 'src dev' on 'src interface' to 'dest dev' on 'dest interface' with maximum 'size' bytes (or 0 for clone to end)" +); diff --git a/cmd/cls.c b/cmd/cls.c new file mode 100644 index 00000000000..b1e0619334b --- /dev/null +++ b/cmd/cls.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2018 + * DENX Software Engineering, Anatolij Gustschin <agust@denx.de> + * + * cls - clear screen command + */ +#include <command.h> +#include <console.h> +#include <dm.h> + +static int do_video_clear(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (console_clear()) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(cls, 1, 0, do_video_clear, "clear screen", ""); diff --git a/cmd/config.c b/cmd/config.c new file mode 100644 index 00000000000..f0d2033c61f --- /dev/null +++ b/cmd/config.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 Masahiro Yamada <yamada.masahiro@socionext.com> + */ + +#include <command.h> +#include <gzip.h> +#include <malloc.h> + +#include "config_data_gz.h" +#include "config_data_size.h" + +static int do_config(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *dst; + unsigned long len = data_size; + int ret = CMD_RET_SUCCESS; + + dst = malloc(data_size + 1); + if (!dst) + return CMD_RET_FAILURE; + + ret = gunzip(dst, data_size, (unsigned char *)data_gz, &len); + if (ret) { + printf("failed to uncompress .config data\n"); + ret = CMD_RET_FAILURE; + goto free; + } + + dst[data_size] = 0; + puts(dst); + +free: + free(dst); + + return ret; +} + +U_BOOT_CMD( + config, 1, 1, do_config, + "print .config", + "" +); diff --git a/cmd/conitrace.c b/cmd/conitrace.c new file mode 100644 index 00000000000..6cc113328eb --- /dev/null +++ b/cmd/conitrace.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'conitrace' command prints the codes received from the console input as + * hexadecimal numbers. + * + * Copyright (c) 2018, Heinrich Schuchardt <xypron.glpk@gmx.de> + */ +#include <command.h> +#include <linux/delay.h> + +static int do_conitrace(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool first = true; + + printf("Waiting for your input\n"); + printf("To terminate type 'x'\n"); + + /* Empty input buffer */ + while (tstc()) + getchar(); + + for (;;) { + int c = getchar(); + + if (first && (c == 'x' || c == 'X')) + break; + + printf("%02x ", c); + first = false; + + /* 10 ms delay - serves to detect separate keystrokes */ + udelay(10000); + if (!tstc()) { + printf("\n"); + first = true; + } + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(conitrace, ""); + +U_BOOT_CMD_COMPLETE( + conitrace, 2, 0, do_conitrace, + "trace console input", + conitrace_help_text, NULL +); diff --git a/cmd/console.c b/cmd/console.c new file mode 100644 index 00000000000..a8133ee3fa3 --- /dev/null +++ b/cmd/console.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * Boot support + */ +#include <command.h> +#include <iomux.h> +#include <stdio_dev.h> + +extern void _do_coninfo (void); +static int do_coninfo(struct cmd_tbl *cmd, int flag, int argc, + char *const argv[]) +{ + int l; + struct list_head *list = stdio_get_list(); + struct list_head *pos; + struct stdio_dev *dev; + + /* Scan for valid output and input devices */ + + puts("List of available devices\n"); + + list_for_each(pos, list) { + dev = list_entry(pos, struct stdio_dev, list); + + printf("|-- %s (%s%s)\n", + dev->name, + (dev->flags & DEV_FLAGS_INPUT) ? "I" : "", + (dev->flags & DEV_FLAGS_OUTPUT) ? "O" : ""); + + for (l = 0; l < MAX_FILES; l++) { + if (CONFIG_IS_ENABLED(CONSOLE_MUX)) { + if (iomux_match_device(console_devices[l], + cd_count[l], dev) >= 0) + printf("| |-- %s\n", stdio_names[l]); + } else { + if (stdio_devices[l] == dev) + printf("| |-- %s\n", stdio_names[l]); + } + + } + } + return 0; +} + +/***************************************************/ + +U_BOOT_CMD( + coninfo, 3, 1, do_coninfo, + "print console devices and information", + "" +); diff --git a/cmd/cpu.c b/cmd/cpu.c new file mode 100644 index 00000000000..27552507564 --- /dev/null +++ b/cmd/cpu.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2015 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * Copyright (c) 2017 Álvaro Fernández Rojas <noltari@gmail.com> + * Copyright 2024 NXP + */ + +#include <command.h> +#include <cpu.h> +#include <display_options.h> +#include <dm.h> +#include <errno.h> + +static const char *cpu_feature_name[CPU_FEAT_COUNT] = { + "L1 cache", + "MMU", + "Microcode", + "Device ID", +}; + +static struct udevice *cpu_find_device(unsigned long cpu_id) +{ + struct udevice *dev; + + for (uclass_first_device(UCLASS_CPU, &dev); dev; + uclass_next_device(&dev)) { + if (cpu_id == dev_seq(dev)) + return dev; + } + + return NULL; +} + +static int print_cpu_list(bool detail) +{ + struct udevice *dev; + char buf[100]; + + for (uclass_first_device(UCLASS_CPU, &dev); + dev; + uclass_next_device(&dev)) { + struct cpu_plat *plat = dev_get_parent_plat(dev); + struct cpu_info info; + bool first = true; + int ret, i; + + ret = cpu_get_desc(dev, buf, sizeof(buf)); + printf("%3d: %-10s %s\n", dev_seq(dev), dev->name, + ret ? "<no description>" : buf); + if (!detail) + continue; + ret = cpu_get_info(dev, &info); + if (ret) { + printf("\t(no detail available"); + if (ret != -ENOSYS) + printf(": err=%d", ret); + printf(")\n"); + continue; + } + printf("\tID = %d, freq = ", plat->cpu_id); + print_freq(info.cpu_freq, ""); + for (i = 0; i < CPU_FEAT_COUNT; i++) { + if (info.features & (1 << i)) { + printf("%s%s", first ? ": " : ", ", + cpu_feature_name[i]); + first = false; + } + } + printf("\n"); + if (info.features & (1 << CPU_FEAT_UCODE)) + printf("\tMicrocode version %#x\n", + plat->ucode_version); + if (info.features & (1 << CPU_FEAT_DEVICE_ID)) + printf("\tDevice ID %#lx\n", plat->device_id); + } + + return 0; +} + +static int do_cpu_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (print_cpu_list(false)) + return CMD_RET_FAILURE; + + return 0; +} + +static int do_cpu_detail(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (print_cpu_list(true)) + return CMD_RET_FAILURE; + + return 0; +} + +static int do_cpu_release(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + unsigned long cpu_id; + unsigned long long boot_addr; + + if (argc != 3) + return CMD_RET_USAGE; + + cpu_id = dectoul(argv[1], NULL); + dev = cpu_find_device(cpu_id); + if (!dev) + return CMD_RET_FAILURE; + + boot_addr = simple_strtoull(argv[2], NULL, 16); + + if (cpu_release_core(dev, boot_addr)) + return CMD_RET_FAILURE; + + return 0; +} + +U_BOOT_LONGHELP(cpu, + "list - list available CPUs\n" + "cpu detail - show CPU detail\n" + "cpu release <core ID> <addr> - Release CPU <core ID> at <addr>\n" + " <core ID>: the sequence number in list subcommand outputs"); + +U_BOOT_CMD_WITH_SUBCMDS(cpu, "display information about CPUs", cpu_help_text, + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_cpu_list), + U_BOOT_SUBCMD_MKENT(detail, 1, 0, do_cpu_detail), + U_BOOT_SUBCMD_MKENT(release, 3, 0, do_cpu_release)); diff --git a/cmd/cramfs.c b/cmd/cramfs.c new file mode 100644 index 00000000000..baff50d1bd6 --- /dev/null +++ b/cmd/cramfs.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * + * based on: cmd_jffs2.c + * + * Add support for a CRAMFS located in RAM + */ + +/* + * CRAMFS support + */ +#include <command.h> +#include <env.h> +#include <image.h> +#include <malloc.h> +#include <mapmem.h> +#include <linux/list.h> +#include <linux/ctype.h> +#include <jffs2/jffs2.h> +#include <jffs2/load_kernel.h> +#include <cramfs/cramfs_fs.h> +#include <asm/io.h> + +/* enable/disable debugging messages */ +#define DEBUG_CRAMFS +#undef DEBUG_CRAMFS + +#ifdef DEBUG_CRAMFS +# define DEBUGF(fmt, args...) printf(fmt ,##args) +#else +# define DEBUGF(fmt, args...) +#endif + +#ifndef CONFIG_MTD_NOR_FLASH +# define OFFSET_ADJUSTMENT 0 +#else +#include <flash.h> +# define OFFSET_ADJUSTMENT (flash_info[id.num].start[0]) +#endif + +#ifndef CONFIG_FS_JFFS2 +#include <linux/stat.h> +char *mkmodestr(unsigned long mode, char *str) +{ + static const char *l = "xwr"; + int mask = 1, i; + char c; + + switch (mode & S_IFMT) { + case S_IFDIR: str[0] = 'd'; break; + case S_IFBLK: str[0] = 'b'; break; + case S_IFCHR: str[0] = 'c'; break; + case S_IFIFO: str[0] = 'f'; break; + case S_IFLNK: str[0] = 'l'; break; + case S_IFSOCK: str[0] = 's'; break; + case S_IFREG: str[0] = '-'; break; + default: str[0] = '?'; + } + + for(i = 0; i < 9; i++) { + c = l[i%3]; + str[9-i] = (mode & mask)?c:'-'; + mask = mask<<1; + } + + if(mode & S_ISUID) str[3] = (mode & S_IXUSR)?'s':'S'; + if(mode & S_ISGID) str[6] = (mode & S_IXGRP)?'s':'S'; + if(mode & S_ISVTX) str[9] = (mode & S_IXOTH)?'t':'T'; + str[10] = '\0'; + return str; +} +#endif /* CONFIG_FS_JFFS2 */ + +extern int cramfs_check (struct part_info *info); +extern int cramfs_load (char *loadoffset, struct part_info *info, char *filename); +extern int cramfs_ls (struct part_info *info, char *filename); +extern int cramfs_info (struct part_info *info); + +/***************************************************/ +/* U-Boot commands */ +/***************************************************/ + +/** + * Routine implementing fsload u-boot command. This routine tries to load + * a requested file from cramfs filesystem at location 'cramfsaddr'. + * cramfsaddr is an evironment variable. + * + * @param cmdtp command internal data + * @param flag command flag + * @param argc number of arguments supplied to the command + * @param argv arguments list + * Return: 0 on success, 1 otherwise + */ +int do_cramfs_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *filename; + int size; + ulong offset = image_load_addr; + char *offset_virt; + + struct part_info part; + struct mtd_device dev; + struct mtdids id; + + ulong addr; + addr = hextoul(env_get("cramfsaddr"), NULL); + + /* hack! */ + /* cramfs_* only supports NOR flash chips */ + /* fake the device type */ + id.type = MTD_DEV_TYPE_NOR; + id.num = 0; + dev.id = &id; + part.dev = &dev; + /* fake the address offset */ + part.offset = (u64)(uintptr_t) map_sysmem(addr - OFFSET_ADJUSTMENT, 0); + + /* pre-set Boot file name */ + filename = env_get("bootfile"); + if (!filename) + filename = "uImage"; + + if (argc == 2) { + filename = argv[1]; + } + if (argc == 3) { + offset = simple_strtoul(argv[1], NULL, 0); + image_load_addr = offset; + filename = argv[2]; + } + + offset_virt = map_sysmem(offset, 0); + size = 0; + if (cramfs_check(&part)) + size = cramfs_load (offset_virt, &part, filename); + + if (size > 0) { + printf("### CRAMFS load complete: %d bytes loaded to 0x%lx\n", + size, offset); + env_set_hex("filesize", size); + } else { + printf("### CRAMFS LOAD ERROR<%x> for %s!\n", size, filename); + } + + unmap_sysmem(offset_virt); + unmap_sysmem((void *)(uintptr_t)part.offset); + + return !(size > 0); +} + +/** + * Routine implementing u-boot ls command which lists content of a given + * directory at location 'cramfsaddr'. + * cramfsaddr is an evironment variable. + * + * @param cmdtp command internal data + * @param flag command flag + * @param argc number of arguments supplied to the command + * @param argv arguments list + * Return: 0 on success, 1 otherwise + */ +int do_cramfs_ls(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *filename = "/"; + int ret; + struct part_info part; + struct mtd_device dev; + struct mtdids id; + + ulong addr; + addr = hextoul(env_get("cramfsaddr"), NULL); + + /* hack! */ + /* cramfs_* only supports NOR flash chips */ + /* fake the device type */ + id.type = MTD_DEV_TYPE_NOR; + id.num = 0; + dev.id = &id; + part.dev = &dev; + /* fake the address offset */ + part.offset = (u64)(uintptr_t) map_sysmem(addr - OFFSET_ADJUSTMENT, 0); + + if (argc == 2) + filename = argv[1]; + + ret = 0; + if (cramfs_check(&part)) + ret = cramfs_ls (&part, filename); + unmap_sysmem((void *)(uintptr_t)part.offset); + + return ret ? 0 : 1; +} + +/* command line only */ + +/***************************************************/ +U_BOOT_CMD( + cramfsload, 3, 0, do_cramfs_load, + "load binary file from a filesystem image", + "[ off ] [ filename ]\n" + " - load binary file from address 'cramfsaddr'\n" + " with offset 'off'\n" +); +U_BOOT_CMD( + cramfsls, 2, 1, do_cramfs_ls, + "list files in a directory (default /)", + "[ directory ]\n" + " - list files in a directory.\n" +); diff --git a/cmd/cros_ec.c b/cmd/cros_ec.c new file mode 100644 index 00000000000..7b60e415b6c --- /dev/null +++ b/cmd/cros_ec.c @@ -0,0 +1,568 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Chromium OS cros_ec driver + * + * Copyright (c) 2016 The Chromium OS Authors. + * Copyright (c) 2016 National Instruments Corp + */ + +#include <command.h> +#include <cros_ec.h> +#include <dm.h> +#include <log.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> + +/* Note: depends on enum ec_current_image */ +static const char * const ec_current_image_name[] = {"unknown", "RO", "RW"}; + +/** + * Decode a flash region parameter + * + * @param argc Number of params remaining + * @param argv List of remaining parameters + * Return: flash region (EC_FLASH_REGION_...) or -1 on error + */ +static int cros_ec_decode_region(int argc, char *const argv[]) +{ + if (argc > 0) { + if (0 == strcmp(*argv, "rw")) + return EC_FLASH_REGION_ACTIVE; + else if (0 == strcmp(*argv, "ro")) + return EC_FLASH_REGION_RO; + + debug("%s: Invalid region '%s'\n", __func__, *argv); + } else { + debug("%s: Missing region parameter\n", __func__); + } + + return -1; +} + +/** + * Perform a flash read or write command + * + * @param dev CROS-EC device to read/write + * @param is_write 1 do to a write, 0 to do a read + * @param argc Number of arguments + * @param argv Arguments (2 is region, 3 is address) + * Return: 0 for ok, 1 for a usage error or -ve for ec command error + * (negative EC_RES_...) + */ +static int do_read_write(struct udevice *dev, int is_write, int argc, + char *const argv[]) +{ + uint32_t offset, size = -1U, region_size; + unsigned long addr; + char *endp; + int region; + int ret; + + region = cros_ec_decode_region(argc - 2, argv + 2); + if (region == -1) + return 1; + if (argc < 4) + return 1; + addr = hextoul(argv[3], &endp); + if (*argv[3] == 0 || *endp != 0) + return 1; + if (argc > 4) { + size = hextoul(argv[4], &endp); + if (*argv[4] == 0 || *endp != 0) + return 1; + } + + ret = cros_ec_flash_offset(dev, region, &offset, ®ion_size); + if (ret) { + debug("%s: Could not read region info\n", __func__); + return ret; + } + if (size == -1U) + size = region_size; + + ret = is_write ? + cros_ec_flash_write(dev, (uint8_t *)addr, offset, size) : + cros_ec_flash_read(dev, (uint8_t *)addr, offset, size); + if (ret) { + debug("%s: Could not %s region\n", __func__, + is_write ? "write" : "read"); + return ret; + } + + return 0; +} + +static const char *const feat_name[64] = { + "limited", + "flash", + "pwm_fan", + "pwm_keyb", + "lightbar", + "led", + "motion_sense", + "keyb", + "pstore", + "port80", + "thermal", + "bklight_switch", + "wifi_switch", + "host_events", + "gpio", + "i2c", + "charger", + "battery", + "smart_battery", + "hang_detect", + "pmu", + "sub_mcu", + "usb_pd", + "usb_mux", + "motion_sense_fifo", + "vstore", + "usbc_ss_mux_virtual", + "rtc", + "fingerprint", + "touchpad", + "rwsig", + "device_event", + "unified_wake_masks", + "host_event64", + "exec_in_ram", + "cec", + "motion_sense_tight_timestamps", + "refined_tablet_mode_hysteresis", + "efs2", + "scp", + "ish", + "typec_cmd", + "typec_require_ap_mode_entry", + "typec_mux_require_ap_ack", +}; + +static int do_show_features(struct udevice *dev) +{ + u64 feat; + int ret; + uint i; + + ret = cros_ec_get_features(dev, &feat); + if (ret) + return ret; + for (i = 0; i < ARRAY_SIZE(feat_name); i++) { + if (feat & (1ULL << i)) { + if (feat_name[i]) + printf("%s\n", feat_name[i]); + else + printf("unknown %d\n", i); + } + } + + return 0; +} + +static const char *const switch_name[8] = { + "lid open", + "power button pressed", + "write-protect disabled", + NULL, + "dedicated recovery", + NULL, + NULL, + NULL, +}; + +static int do_show_switches(struct udevice *dev) +{ + uint switches; + int ret; + uint i; + + ret = cros_ec_get_switches(dev); + if (ret < 0) + return log_msg_ret("get", ret); + switches = ret; + for (i = 0; i < ARRAY_SIZE(switch_name); i++) { + uint mask = 1 << i; + + if (switches & mask) { + if (switch_name[i]) + printf("%s\n", switch_name[i]); + else + printf("unknown %02x\n", mask); + } + } + + return 0; +} + +static const char *const event_name[] = { + "lid_closed", + "lid_open", + "power_button", + "ac_connected", + "ac_disconnected", + "battery_low", + "battery_critical", + "battery", + "thermal_threshold", + "device", + "thermal", + "usb_charger", + "key_pressed", + "interface_ready", + "keyboard_recovery", + "thermal_shutdown", + "battery_shutdown", + "throttle_start", + "throttle_stop", + "hang_detect", + "hang_reboot", + "pd_mcu", + "battery_status", + "panic", + "keyboard_fastboot", + "rtc", + "mkbp", + "usb_mux", + "mode_change", + "keyboard_recovery_hw_reinit", + "extended", + "invalid", +}; + +static int do_show_events(struct udevice *dev) +{ + u32 events; + int ret; + uint i; + + ret = cros_ec_get_host_events(dev, &events); + if (ret) + return ret; + printf("%08x\n", events); + for (i = 0; i < ARRAY_SIZE(event_name); i++) { + enum host_event_code code = i + 1; + u64 mask = EC_HOST_EVENT_MASK(code); + + if (events & mask) { + if (event_name[i]) + printf("%s\n", event_name[i]); + else + printf("unknown code %#x\n", code); + } + } + + return 0; +} + +static int do_cros_ec(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + const char *cmd; + int ret = 0; + + if (argc < 2) + return CMD_RET_USAGE; + + cmd = argv[1]; + if (0 == strcmp("init", cmd)) { + /* Remove any existing device */ + ret = uclass_find_device(UCLASS_CROS_EC, 0, &dev); + if (!ret) + device_remove(dev, DM_REMOVE_NORMAL); + ret = uclass_get_device(UCLASS_CROS_EC, 0, &dev); + if (ret) { + printf("Could not init cros_ec device (err %d)\n", ret); + return 1; + } + return 0; + } + + ret = uclass_get_device(UCLASS_CROS_EC, 0, &dev); + if (ret) { + printf("Cannot get cros-ec device (err=%d)\n", ret); + return 1; + } + if (0 == strcmp("id", cmd)) { + char id[MSG_BYTES]; + + if (cros_ec_read_id(dev, id, sizeof(id))) { + debug("%s: Could not read KBC ID\n", __func__); + return 1; + } + printf("%s\n", id); + } else if (0 == strcmp("info", cmd)) { + struct ec_response_mkbp_info info; + + if (cros_ec_info(dev, &info)) { + debug("%s: Could not read KBC info\n", __func__); + return 1; + } + printf("rows = %u\n", info.rows); + printf("cols = %u\n", info.cols); + } else if (!strcmp("features", cmd)) { + ret = do_show_features(dev); + + if (ret) + printf("Error: %d\n", ret); + } else if (!strcmp("switches", cmd)) { + ret = do_show_switches(dev); + + if (ret) + printf("Error: %d\n", ret); + } else if (0 == strcmp("curimage", cmd)) { + enum ec_current_image image; + + if (cros_ec_read_current_image(dev, &image)) { + debug("%s: Could not read KBC image\n", __func__); + return 1; + } + printf("%d\n", image); + } else if (0 == strcmp("hash", cmd)) { + struct ec_response_vboot_hash hash; + int i; + + if (cros_ec_read_hash(dev, EC_VBOOT_HASH_OFFSET_ACTIVE, &hash)) { + debug("%s: Could not read KBC hash\n", __func__); + return 1; + } + + if (hash.hash_type == EC_VBOOT_HASH_TYPE_SHA256) + printf("type: SHA-256\n"); + else + printf("type: %d\n", hash.hash_type); + + printf("offset: 0x%08x\n", hash.offset); + printf("size: 0x%08x\n", hash.size); + + printf("digest: "); + for (i = 0; i < hash.digest_size; i++) + printf("%02x", hash.hash_digest[i]); + printf("\n"); + } else if (0 == strcmp("reboot", cmd)) { + int region; + enum ec_reboot_cmd cmd; + + if (argc >= 3 && !strcmp(argv[2], "cold")) { + cmd = EC_REBOOT_COLD; + } else { + region = cros_ec_decode_region(argc - 2, argv + 2); + if (region == EC_FLASH_REGION_RO) + cmd = EC_REBOOT_JUMP_RO; + else if (region == EC_FLASH_REGION_ACTIVE) + cmd = EC_REBOOT_JUMP_RW; + else + return CMD_RET_USAGE; + } + + if (cros_ec_reboot(dev, cmd, 0)) { + debug("%s: Could not reboot KBC\n", __func__); + return 1; + } + } else if (0 == strcmp("events", cmd)) { + ret = do_show_events(dev); + + if (ret) + printf("Error: %d\n", ret); + } else if (0 == strcmp("clrevents", cmd)) { + uint32_t events = 0x7fffffff; + + if (argc >= 3) + events = simple_strtol(argv[2], NULL, 0); + + if (cros_ec_clear_host_events(dev, events)) { + debug("%s: Could not clear host events\n", __func__); + return 1; + } + } else if (0 == strcmp("read", cmd)) { + ret = do_read_write(dev, 0, argc, argv); + if (ret > 0) + return CMD_RET_USAGE; + } else if (0 == strcmp("write", cmd)) { + ret = do_read_write(dev, 1, argc, argv); + if (ret > 0) + return CMD_RET_USAGE; + } else if (0 == strcmp("erase", cmd)) { + int region = cros_ec_decode_region(argc - 2, argv + 2); + uint32_t offset, size; + + if (region == -1) + return CMD_RET_USAGE; + if (cros_ec_flash_offset(dev, region, &offset, &size)) { + debug("%s: Could not read region info\n", __func__); + ret = -1; + } else { + ret = cros_ec_flash_erase(dev, offset, size); + if (ret) { + debug("%s: Could not erase region\n", + __func__); + } + } + } else if (0 == strcmp("regioninfo", cmd)) { + int region = cros_ec_decode_region(argc - 2, argv + 2); + uint32_t offset, size; + + if (region == -1) + return CMD_RET_USAGE; + ret = cros_ec_flash_offset(dev, region, &offset, &size); + if (ret) { + debug("%s: Could not read region info\n", __func__); + } else { + printf("Region: %s\n", region == EC_FLASH_REGION_RO ? + "RO" : "RW"); + printf("Offset: %x\n", offset); + printf("Size: %x\n", size); + } + } else if (0 == strcmp("flashinfo", cmd)) { + struct ec_response_flash_info p; + + ret = cros_ec_read_flashinfo(dev, &p); + if (!ret) { + printf("Flash size: %u\n", p.flash_size); + printf("Write block size: %u\n", p.write_block_size); + printf("Erase block size: %u\n", p.erase_block_size); + } + } else if (0 == strcmp("vbnvcontext", cmd)) { + uint8_t block[EC_VBNV_BLOCK_SIZE]; + char buf[3]; + int i, len; + unsigned long result; + + if (argc <= 2) { + ret = cros_ec_read_nvdata(dev, block, + EC_VBNV_BLOCK_SIZE); + if (!ret) { + printf("vbnv_block: "); + for (i = 0; i < EC_VBNV_BLOCK_SIZE; i++) + printf("%02x", block[i]); + putc('\n'); + } + } else { + /* + * TODO(clchiou): Move this to a utility function as + * cmd_spi might want to call it. + */ + memset(block, 0, EC_VBNV_BLOCK_SIZE); + len = strlen(argv[2]); + buf[2] = '\0'; + for (i = 0; i < EC_VBNV_BLOCK_SIZE; i++) { + if (i * 2 >= len) + break; + buf[0] = argv[2][i * 2]; + if (i * 2 + 1 >= len) + buf[1] = '0'; + else + buf[1] = argv[2][i * 2 + 1]; + strict_strtoul(buf, 16, &result); + block[i] = result; + } + ret = cros_ec_write_nvdata(dev, block, + EC_VBNV_BLOCK_SIZE); + } + if (ret) { + debug("%s: Could not %s VbNvContext\n", __func__, + argc <= 2 ? "read" : "write"); + } + } else if (0 == strcmp("test", cmd)) { + int result = cros_ec_test(dev); + + if (result) + printf("Test failed with error %d\n", result); + else + puts("Test passed\n"); + } else if (0 == strcmp("version", cmd)) { + struct ec_response_get_version *p; + char *build_string; + + ret = cros_ec_read_version(dev, &p); + if (!ret) { + /* Print versions */ + printf("RO version: %1.*s\n", + (int)sizeof(p->version_string_ro), + p->version_string_ro); + printf("RW version: %1.*s\n", + (int)sizeof(p->version_string_rw), + p->version_string_rw); + printf("Firmware copy: %s\n", + (p->current_image < + ARRAY_SIZE(ec_current_image_name) ? + ec_current_image_name[p->current_image] : + "?")); + ret = cros_ec_read_build_info(dev, &build_string); + if (!ret) + printf("Build info: %s\n", build_string); + } + } else if (0 == strcmp("ldo", cmd)) { + uint8_t index, state; + char *endp; + + if (argc < 3) + return CMD_RET_USAGE; + index = dectoul(argv[2], &endp); + if (*argv[2] == 0 || *endp != 0) + return CMD_RET_USAGE; + if (argc > 3) { + state = dectoul(argv[3], &endp); + if (*argv[3] == 0 || *endp != 0) + return CMD_RET_USAGE; + ret = cros_ec_set_ldo(dev, index, state); + } else { + ret = cros_ec_get_ldo(dev, index, &state); + if (!ret) { + printf("LDO%d: %s\n", index, + state == EC_LDO_STATE_ON ? + "on" : "off"); + } + } + + if (ret) { + debug("%s: Could not access LDO%d\n", __func__, index); + return ret; + } + } else if (!strcmp("sku", cmd)) { + ret = cros_ec_get_sku_id(dev); + + if (ret >= 0) { + printf("%d\n", ret); + ret = 0; + } else { + printf("Error: %d\n", ret); + } + } else { + return CMD_RET_USAGE; + } + + if (ret < 0) { + printf("Error: CROS-EC command failed (error %d)\n", ret); + ret = 1; + } + + return ret; +} + +U_BOOT_CMD( + crosec, 6, 1, do_cros_ec, + "CROS-EC utility command", + "init Re-init CROS-EC (done on startup automatically)\n" + "crosec id Read CROS-EC ID\n" + "crosec info Read CROS-EC info\n" + "crosec features Read CROS-EC features\n" + "crosec switches Read CROS-EC switches\n" + "crosec curimage Read CROS-EC current image\n" + "crosec hash Read CROS-EC hash\n" + "crosec reboot [rw | ro | cold] Reboot CROS-EC\n" + "crosec events Read CROS-EC host events\n" + "crosec eventsb Read CROS-EC host events_b\n" + "crosec clrevents [mask] Clear CROS-EC host events\n" + "crosec regioninfo <ro|rw> Read image info\n" + "crosec flashinfo Read flash info\n" + "crosec erase <ro|rw> Erase EC image\n" + "crosec read <ro|rw> <addr> [<size>] Read EC image\n" + "crosec write <ro|rw> <addr> [<size>] Write EC image\n" + "crosec vbnvcontext [hexstring] Read [write] VbNvContext from EC\n" + "crosec ldo <idx> [<state>] Switch/Read LDO state\n" + "crosec sku Read board SKU ID\n" + "crosec test run tests on cros_ec\n" + "crosec version Read CROS-EC version" +); diff --git a/cmd/cyclic.c b/cmd/cyclic.c new file mode 100644 index 00000000000..339dd4a7bce --- /dev/null +++ b/cmd/cyclic.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * A general-purpose cyclic execution infrastructure, to allow "small" + * (run-time wise) functions to be executed at a specified frequency. + * Things like LED blinking or watchdog triggering are examples for such + * tasks. + * + * Copyright (C) 2022 Stefan Roese <sr@denx.de> + */ + +#include <command.h> +#include <cyclic.h> +#include <div64.h> +#include <malloc.h> +#include <time.h> +#include <vsprintf.h> +#include <linux/delay.h> +#include <linux/kernel.h> + +struct cyclic_demo_info { + struct cyclic_info cyclic; + uint delay_us; +}; + +static void cyclic_demo(struct cyclic_info *c) +{ + struct cyclic_demo_info *info = container_of(c, struct cyclic_demo_info, cyclic); + + /* Just a small dummy delay here */ + udelay(info->delay_us); +} + +static int do_cyclic_demo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cyclic_demo_info *info; + uint time_ms; + + if (argc < 3) + return CMD_RET_USAGE; + + info = malloc(sizeof(struct cyclic_demo_info)); + if (!info) { + printf("out of memory\n"); + return CMD_RET_FAILURE; + } + + time_ms = simple_strtoul(argv[1], NULL, 0); + info->delay_us = simple_strtoul(argv[2], NULL, 0); + + /* Register demo cyclic function */ + cyclic_register(&info->cyclic, cyclic_demo, time_ms * 1000, "cyclic_demo"); + + printf("Registered function \"%s\" to be executed all %dms\n", + "cyclic_demo", time_ms); + + return 0; +} + +static int do_cyclic_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cyclic_info *cyclic; + struct hlist_node *tmp; + u64 cnt, freq; + + hlist_for_each_entry_safe(cyclic, tmp, cyclic_get_list(), list) { + cnt = cyclic->run_cnt * 1000000ULL * 100ULL; + freq = lldiv(cnt, timer_get_us() - cyclic->start_time_us); + printf("function: %s, cpu-time: %lld us, frequency: %lld.%02d times/s\n", + cyclic->name, cyclic->cpu_time_us, + lldiv(freq, 100), do_div(freq, 100)); + } + + return 0; +} + +U_BOOT_LONGHELP(cyclic, + "demo <cycletime_ms> <delay_us> - register cyclic demo function\n" + "cyclic list - list cyclic functions\n"); + +U_BOOT_CMD_WITH_SUBCMDS(cyclic, "Cyclic", cyclic_help_text, + U_BOOT_SUBCMD_MKENT(demo, 3, 1, do_cyclic_demo), + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_cyclic_list)); diff --git a/cmd/date.c b/cmd/date.c new file mode 100644 index 00000000000..8614f022761 --- /dev/null +++ b/cmd/date.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2001 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * RTC, Date & Time support: get and set date & time + */ +#include <command.h> +#include <dm.h> +#include <rtc.h> +#include <i2c.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const char * const weekdays[] = { + "Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur", +}; + +int mk_date (const char *, struct rtc_time *); + +static struct rtc_time default_tm = { 0, 0, 0, 1, 1, 2000, 6, 0, 0 }; + +static int do_date(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct rtc_time tm; + int rcode = 0; + int old_bus __maybe_unused; + + /* switch to correct I2C bus */ + struct udevice *dev; + + rcode = uclass_get_device_by_seq(UCLASS_RTC, 0, &dev); + if (rcode) { + rcode = uclass_get_device(UCLASS_RTC, 0, &dev); + if (rcode) { + printf("Cannot find RTC: err=%d\n", rcode); + return CMD_RET_FAILURE; + } + } + + switch (argc) { + case 2: /* set date & time */ + if (strcmp(argv[1],"reset") == 0) { + puts ("Reset RTC...\n"); + rcode = dm_rtc_reset(dev); + if (!rcode) + rcode = dm_rtc_set(dev, &default_tm); + if (rcode) + puts("## Failed to set date after RTC reset\n"); + } else { + /* initialize tm with current time */ + rcode = dm_rtc_get(dev, &tm); + if (!rcode) { + /* insert new date & time */ + if (mk_date(argv[1], &tm) != 0) { + puts ("## Bad date format\n"); + break; + } + /* and write to RTC */ + rcode = dm_rtc_set(dev, &tm); + if (rcode) { + printf("## Set date failed: err=%d\n", + rcode); + } + } else { + puts("## Get date failed\n"); + } + } + fallthrough; + case 1: /* get date & time */ + rcode = dm_rtc_get(dev, &tm); + if (rcode) { + puts("## Get date failed\n"); + break; + } + + printf ("Date: %4d-%02d-%02d (%sday) Time: %2d:%02d:%02d\n", + tm.tm_year, tm.tm_mon, tm.tm_mday, + (tm.tm_wday<0 || tm.tm_wday>6) ? + "unknown " : weekdays[tm.tm_wday], + tm.tm_hour, tm.tm_min, tm.tm_sec); + + break; + default: + rcode = CMD_RET_USAGE; + } + + return rcode ? CMD_RET_FAILURE : 0; +} + +/* + * simple conversion of two-digit string with error checking + */ +static int cnvrt2 (const char *str, int *valp) +{ + int val; + + if ((*str < '0') || (*str > '9')) + return (-1); + + val = *str - '0'; + + ++str; + + if ((*str < '0') || (*str > '9')) + return (-1); + + *valp = 10 * val + (*str - '0'); + + return (0); +} + +/* + * Convert date string: MMDDhhmm[[CC]YY][.ss] + * + * Some basic checking for valid values is done, but this will not catch + * all possible error conditions. + */ +int mk_date (const char *datestr, struct rtc_time *tmp) +{ + int len, val; + char *ptr; + + ptr = strchr(datestr, '.'); + len = strlen(datestr); + + /* Set seconds */ + if (ptr) { + int sec; + + ptr++; + if ((len - (ptr - datestr)) != 2) + return (-1); + + len -= 3; + + if (cnvrt2 (ptr, &sec)) + return (-1); + + tmp->tm_sec = sec; + } else { + tmp->tm_sec = 0; + } + + if (len == 12) { /* MMDDhhmmCCYY */ + int year, century; + + if (cnvrt2 (datestr+ 8, ¢ury) || + cnvrt2 (datestr+10, &year) ) { + return (-1); + } + tmp->tm_year = 100 * century + year; + } else if (len == 10) { /* MMDDhhmmYY */ + int year, century; + + century = tmp->tm_year / 100; + if (cnvrt2 (datestr+ 8, &year)) + return (-1); + tmp->tm_year = 100 * century + year; + } + + switch (len) { + case 8: /* MMDDhhmm */ + /* fall thru */ + case 10: /* MMDDhhmmYY */ + /* fall thru */ + case 12: /* MMDDhhmmCCYY */ + if (cnvrt2 (datestr+0, &val) || + val > 12) { + break; + } + tmp->tm_mon = val; + if (cnvrt2 (datestr+2, &val) || + val > ((tmp->tm_mon==2) ? 29 : 31)) { + break; + } + tmp->tm_mday = val; + + if (cnvrt2 (datestr+4, &val) || + val > 23) { + break; + } + tmp->tm_hour = val; + + if (cnvrt2 (datestr+6, &val) || + val > 59) { + break; + } + tmp->tm_min = val; + + /* calculate day of week */ + rtc_calc_weekday(tmp); + + return (0); + default: + break; + } + + return (-1); +} + +/***************************************************/ + +U_BOOT_CMD( + date, 2, 1, do_date, + "get/set/reset date & time", + "[MMDDhhmm[[CC]YY][.ss]]\ndate reset\n" + " - without arguments: print date & time\n" + " - with numeric argument: set the system date & time\n" + " - with 'reset' argument: reset the RTC" +); diff --git a/cmd/demo.c b/cmd/demo.c new file mode 100644 index 00000000000..5c422ac165b --- /dev/null +++ b/cmd/demo.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2013 Google, Inc + * + * (C) Copyright 2012 + * Pavel Herrmann <morpheus.ibis@gmail.com> + */ + +#include <command.h> +#include <dm.h> +#include <dm-demo.h> +#include <mapmem.h> +#include <asm/io.h> + +struct udevice *demo_dev; + +static int do_demo_hello(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ch = 0; + + if (argc) + ch = *argv[0]; + + return demo_hello(demo_dev, ch); +} + +static int do_demo_status(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int status; + int ret; + + ret = demo_status(demo_dev, &status); + if (ret) + return ret; + + printf("Status: %d\n", status); + + return 0; +} + +static int do_demo_light(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int light; + int ret; + + if (argc) { + light = hextoul(argv[0], NULL); + ret = demo_set_light(demo_dev, light); + } else { + ret = demo_get_light(demo_dev); + if (ret >= 0) { + printf("Light: %x\n", ret); + ret = 0; + } + } + + return ret; +} + +int do_demo_list(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct udevice *dev; + int i, ret, err = 0; + + puts("Demo uclass entries:\n"); + + for (i = 0, ret = uclass_first_device_check(UCLASS_DEMO, &dev); + dev; + ret = uclass_next_device_check(&dev)) { + printf("entry %d - instance %08x, ops %08x, plat %08x, status %i\n", + i++, (uint)map_to_sysmem(dev), + (uint)map_to_sysmem(dev->driver->ops), + (uint)map_to_sysmem(dev_get_plat(dev)), + ret); + if (ret) + err = ret; + } + + return cmd_process_error(cmdtp, err); +} + +static struct cmd_tbl demo_commands[] = { + U_BOOT_CMD_MKENT(list, 0, 1, do_demo_list, "", ""), + U_BOOT_CMD_MKENT(hello, 2, 1, do_demo_hello, "", ""), + U_BOOT_CMD_MKENT(light, 2, 1, do_demo_light, "", ""), + U_BOOT_CMD_MKENT(status, 1, 1, do_demo_status, "", ""), +}; + +static int do_demo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *demo_cmd; + int devnum = 0; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + demo_cmd = find_cmd_tbl(argv[1], demo_commands, + ARRAY_SIZE(demo_commands)); + argc -= 2; + argv += 2; + + if ((!demo_cmd || argc > demo_cmd->maxargs) || + ((demo_cmd->name[0] != 'l') && (argc < 1))) + return CMD_RET_USAGE; + + if (argc) { + devnum = dectoul(argv[0], NULL); + ret = uclass_get_device(UCLASS_DEMO, devnum, &demo_dev); + if (ret) + return cmd_process_error(cmdtp, ret); + argc--; + argv++; + } else { + demo_dev = NULL; + if (demo_cmd->cmd != do_demo_list) + return CMD_RET_USAGE; + } + + ret = demo_cmd->cmd(demo_cmd, flag, argc, argv); + + return cmd_process_error(demo_cmd, ret); +} + +U_BOOT_CMD( + demo, 4, 1, do_demo, + "Driver model (dm) demo operations", + "list List available demo devices\n" + "demo hello <num> [<char>] Say hello\n" + "demo light [<num>] Set or get the lights\n" + "demo status <num> Get demo device status\n" +); diff --git a/cmd/dfu.c b/cmd/dfu.c new file mode 100644 index 00000000000..46f0190588e --- /dev/null +++ b/cmd/dfu.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cmd_dfu.c -- dfu command + * + * Copyright (C) 2015 + * Lukasz Majewski <l.majewski@majess.pl> + * + * Copyright (C) 2012 Samsung Electronics + * authors: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * Lukasz Majewski <l.majewski@samsung.com> + */ + +#include <command.h> +#include <watchdog.h> +#include <dfu.h> +#include <console.h> +#include <g_dnl.h> +#include <usb.h> +#include <net.h> + +static int do_dfu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + + if (argc < 2) + return CMD_RET_USAGE; + +#ifdef CONFIG_DFU_OVER_USB + char *usb_controller = argv[1]; +#endif + char *interface = NULL; + char *devstring = NULL; +#if defined(CONFIG_DFU_TIMEOUT) || defined(CONFIG_DFU_OVER_TFTP) + unsigned long value = 0; +#endif + if (argc >= 4) { + interface = argv[2]; + devstring = argv[3]; + } + +#if defined(CONFIG_DFU_TIMEOUT) || defined(CONFIG_DFU_OVER_TFTP) + if (argc == 5 || argc == 3) + value = simple_strtoul(argv[argc - 1], NULL, 0); +#endif + + int ret = 0; +#ifdef CONFIG_DFU_OVER_TFTP + if (!strcmp(argv[1], "tftp")) + return update_tftp(value, interface, devstring); +#endif + ret = dfu_init_env_entities(interface, devstring); + if (ret) + goto done; + +#ifdef CONFIG_DFU_TIMEOUT + dfu_set_timeout(value * 1000); +#endif + + ret = CMD_RET_SUCCESS; + if (strcmp(argv[argc - 1], "list") == 0) { + dfu_show_entities(); + goto done; + } + +#ifdef CONFIG_DFU_OVER_USB + int controller_index = simple_strtoul(usb_controller, NULL, 0); + bool retry = false; + do { + ret = run_usb_dnl_gadget(controller_index, "usb_dnl_dfu"); + + if (dfu_reinit_needed) { + dfu_free_entities(); + ret = dfu_init_env_entities(interface, devstring); + if (ret) + goto done; + retry = true; + } + } while (retry); + +#endif +done: + dfu_free_entities(); + return ret; +} + +U_BOOT_CMD(dfu, CONFIG_SYS_MAXARGS, 1, do_dfu, + "Device Firmware Upgrade", + "" +#ifdef CONFIG_DFU_OVER_USB +#ifdef CONFIG_DFU_TIMEOUT + "<USB_controller> [<interface> <dev>] [<timeout>] [list]\n" +#else + "<USB_controller> [<interface> <dev>] [list]\n" +#endif + " - device firmware upgrade via <USB_controller>\n" + " on device <dev>, attached to interface\n" + " <interface>\n" +#ifdef CONFIG_DFU_TIMEOUT + " [<timeout>] - specify inactivity timeout in seconds\n" +#endif +#endif + " [list] - list available alt settings\n" +#ifdef CONFIG_DFU_OVER_TFTP +#ifdef CONFIG_DFU_OVER_USB + "dfu " +#endif + "tftp [<interface> <dev>] [<addr>]\n" + " - device firmware upgrade via TFTP\n" + " on device <dev>, attached to interface\n" + " <interface>\n" + " [<addr>] - address where FIT image has been stored\n" +#endif +); diff --git a/cmd/diag.c b/cmd/diag.c new file mode 100644 index 00000000000..4a88ab00a07 --- /dev/null +++ b/cmd/diag.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2002 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * Diagnostics support + */ +#include <command.h> +#include <post.h> +#include <linux/string.h> + +int do_diag(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + unsigned int i; + + if (argc == 1 || strcmp (argv[1], "run") != 0) { + /* List test info */ + if (argc == 1) { + puts ("Available hardware tests:\n"); + post_info (NULL); + puts ("Use 'diag [<test1> [<test2> ...]]'" + " to get more info.\n"); + puts ("Use 'diag run [<test1> [<test2> ...]]'" + " to run tests.\n"); + } else { + for (i = 1; i < argc; i++) { + if (post_info (argv[i]) != 0) + printf ("%s - no such test\n", argv[i]); + } + } + } else { + /* Run tests */ + if (argc == 2) { + post_run (NULL, POST_RAM | POST_MANUAL); + } else { + for (i = 2; i < argc; i++) { + if (post_run (argv[i], POST_RAM | POST_MANUAL) != 0) + printf ("%s - unable to execute the test\n", + argv[i]); + } + } + } + + return 0; +} +/***************************************************/ + +U_BOOT_CMD( + diag, CONFIG_SYS_MAXARGS, 0, do_diag, + "perform board diagnostics", + " - print list of available tests\n" + "diag [test1 [test2]]\n" + " - print information about specified tests\n" + "diag run - run all available tests\n" + "diag run [test1 [test2]]\n" + " - run specified tests" +); diff --git a/cmd/disk.c b/cmd/disk.c new file mode 100644 index 00000000000..2efc3ca4b1a --- /dev/null +++ b/cmd/disk.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2011 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ +#include <bootstage.h> +#include <command.h> +#include <cpu_func.h> +#include <image.h> +#include <log.h> +#include <part.h> + +int common_diskboot(struct cmd_tbl *cmdtp, const char *intf, int argc, + char *const argv[]) +{ + __maybe_unused int dev; + int part; + ulong addr = CONFIG_SYS_LOAD_ADDR; + ulong cnt; + struct disk_partition info; +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + struct legacy_img_hdr *hdr; +#endif + struct blk_desc *dev_desc; + +#if CONFIG_IS_ENABLED(FIT) + const void *fit_hdr = NULL; +#endif + + bootstage_mark(BOOTSTAGE_ID_IDE_START); + if (argc > 3) { + bootstage_error(BOOTSTAGE_ID_IDE_ADDR); + return CMD_RET_USAGE; + } + bootstage_mark(BOOTSTAGE_ID_IDE_ADDR); + + if (argc > 1) + addr = hextoul(argv[1], NULL); + + bootstage_mark(BOOTSTAGE_ID_IDE_BOOT_DEVICE); + + part = blk_get_device_part_str(intf, cmd_arg2(argc, argv), + &dev_desc, &info, 1); + if (part < 0) { + bootstage_error(BOOTSTAGE_ID_IDE_TYPE); + return 1; + } + + dev = dev_desc->devnum; + bootstage_mark(BOOTSTAGE_ID_IDE_TYPE); + + printf("\nLoading from %s device %d, partition %d: " + "Name: %.32s Type: %.32s\n", intf, dev, part, info.name, + info.type); + + debug("First Block: " LBAFU ", # of blocks: " LBAFU + ", Block Size: %ld\n", + info.start, info.size, info.blksz); + + if (blk_dread(dev_desc, info.start, 1, (ulong *)addr) != 1) { + printf("** Read error on %d:%d\n", dev, part); + bootstage_error(BOOTSTAGE_ID_IDE_PART_READ); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_IDE_PART_READ); + + switch (genimg_get_format((void *) addr)) { +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + case IMAGE_FORMAT_LEGACY: + hdr = (struct legacy_img_hdr *)addr; + + bootstage_mark(BOOTSTAGE_ID_IDE_FORMAT); + + if (!image_check_hcrc(hdr)) { + puts("\n** Bad Header Checksum **\n"); + bootstage_error(BOOTSTAGE_ID_IDE_CHECKSUM); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_IDE_CHECKSUM); + + image_print_contents(hdr); + + cnt = image_get_image_size(hdr); + break; +#endif +#if CONFIG_IS_ENABLED(FIT) + case IMAGE_FORMAT_FIT: + fit_hdr = (const void *) addr; + puts("Fit image detected...\n"); + + cnt = fit_get_size(fit_hdr); + break; +#endif + default: + bootstage_error(BOOTSTAGE_ID_IDE_FORMAT); + puts("** Unknown image type\n"); + return 1; + } + + cnt += info.blksz - 1; + cnt /= info.blksz; + cnt -= 1; + + if (blk_dread(dev_desc, info.start + 1, cnt, + (ulong *)(addr + info.blksz)) != cnt) { + printf("** Read error on %d:%d\n", dev, part); + bootstage_error(BOOTSTAGE_ID_IDE_READ); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_IDE_READ); + +#if CONFIG_IS_ENABLED(FIT) + /* This cannot be done earlier, + * we need complete FIT image in RAM first */ + if (genimg_get_format((void *) addr) == IMAGE_FORMAT_FIT) { + if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) { + bootstage_error(BOOTSTAGE_ID_IDE_FIT_READ); + puts("** Bad FIT image format\n"); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_IDE_FIT_READ_OK); + } +#endif + + flush_cache(addr, (cnt+1)*info.blksz); + + /* Loading ok, update default load address */ + image_load_addr = addr; + + return bootm_maybe_autostart(cmdtp, argv[0]); +} diff --git a/cmd/dm.c b/cmd/dm.c new file mode 100644 index 00000000000..1f212c0f030 --- /dev/null +++ b/cmd/dm.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2013 Google, Inc + * + * (C) Copyright 2012 + * Marek Vasut <marex@denx.de> + */ + +#include <command.h> +#include <dm/root.h> +#include <dm/util.h> +#include <linux/string.h> + +static int do_dm_dump_driver_compat(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + dm_dump_driver_compat(); + + return 0; +} + +static int do_dm_dump_devres(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + dm_dump_devres(); + + return 0; +} + +static int do_dm_dump_drivers(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + dm_dump_drivers(); + + return 0; +} + +#if CONFIG_IS_ENABLED(DM_STATS) +static int do_dm_dump_mem(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct dm_stats mem; + + dm_get_mem(&mem); + dm_dump_mem(&mem); + + return 0; +} +#endif /* DM_STATS */ + +static int do_dm_dump_static_driver_info(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + dm_dump_static_driver_info(); + + return 0; +} + +static int do_dm_dump_tree(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool extended = false, sort = false; + char *device = NULL; + + for (; argc > 1; argc--, argv++) { + if (argv[1][0] != '-') + break; + + if (!strcmp(argv[1], "-e")) { + extended = true; + } else if (!strcmp(argv[1], "-s")) { + sort = true; + } else { + printf("Unknown parameter: %s\n", argv[1]); + return 0; + } + } + if (argc > 1) + device = argv[1]; + + dm_dump_tree(device, extended, sort); + + return 0; +} + +static int do_dm_dump_uclass(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool extended = false; + char *uclass = NULL; + + if (argc > 1) { + if (!strcmp(argv[1], "-e")) { + extended = true; + argc--; + argv++; + } + if (argc > 1) + uclass = argv[1]; + } + + dm_dump_uclass(uclass, extended); + + return 0; +} + +#if CONFIG_IS_ENABLED(DM_STATS) +#define DM_MEM_HELP "dm mem Provide a summary of memory usage\n" +#define DM_MEM U_BOOT_SUBCMD_MKENT(mem, 1, 1, do_dm_dump_mem), +#else +#define DM_MEM_HELP +#define DM_MEM +#endif + +U_BOOT_LONGHELP(dm, + "compat Dump list of drivers with compatibility strings\n" + "dm devres Dump list of device resources for each device\n" + "dm drivers Dump list of drivers with uclass and instances\n" + DM_MEM_HELP + "dm static Dump list of drivers with static platform data\n" + "dm tree [-s][-e][name] Dump tree of driver model devices (-s=sort)\n" + "dm uclass [-e][name] Dump list of instances for each uclass"); + +U_BOOT_CMD_WITH_SUBCMDS(dm, "Driver model low level access", dm_help_text, + U_BOOT_SUBCMD_MKENT(compat, 1, 1, do_dm_dump_driver_compat), + U_BOOT_SUBCMD_MKENT(devres, 1, 1, do_dm_dump_devres), + U_BOOT_SUBCMD_MKENT(drivers, 1, 1, do_dm_dump_drivers), + DM_MEM + U_BOOT_SUBCMD_MKENT(static, 1, 1, do_dm_dump_static_driver_info), + U_BOOT_SUBCMD_MKENT(tree, 4, 1, do_dm_dump_tree), + U_BOOT_SUBCMD_MKENT(uclass, 3, 1, do_dm_dump_uclass)); diff --git a/cmd/echo.c b/cmd/echo.c new file mode 100644 index 00000000000..d1346504cfb --- /dev/null +++ b/cmd/echo.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2000-2009 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <linux/string.h> + +static int do_echo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i = 1; + bool space = false; + bool newline = true; + + if (argc > 1) { + if (!strcmp(argv[1], "-n")) { + newline = false; + ++i; + } + } + + for (; i < argc; ++i) { + if (space) { + putc(' '); + } + puts(argv[i]); + space = true; + } + + if (newline) + putc('\n'); + + return 0; +} + +U_BOOT_CMD( + echo, CONFIG_SYS_MAXARGS, 1, do_echo, + "echo args to console", + "[-n] [args..]\n" + " - echo args to console; -n suppresses newline" +); diff --git a/cmd/eeprom.c b/cmd/eeprom.c new file mode 100644 index 00000000000..cf89cfce3e4 --- /dev/null +++ b/cmd/eeprom.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000, 2001 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * Support for read and write access to EEPROM like memory devices. This + * includes regular EEPROM as well as FRAM (ferroelectic nonvolaile RAM). + * FRAM devices read and write data at bus speed. In particular, there is no + * write delay. Also, there is no limit imposed on the number of bytes that can + * be transferred with a single read or write. + * + * Use the following configuration options to ensure no unneeded performance + * degradation (typical for EEPROM) is incured for FRAM memory: + * + * #define CONFIG_SYS_I2C_FRAM + * Set CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS to 0 + * + */ + +#include <config.h> +#include <command.h> +#include <dm.h> +#include <eeprom.h> +#include <i2c.h> +#include <i2c_eeprom.h> +#include <eeprom_layout.h> +#include <vsprintf.h> +#include <linux/delay.h> + +#ifndef I2C_RXTX_LEN +#define I2C_RXTX_LEN 128 +#endif + +#define EEPROM_PAGE_SIZE (1 << CONFIG_SYS_EEPROM_PAGE_WRITE_BITS) +#define EEPROM_PAGE_OFFSET(x) ((x) & (EEPROM_PAGE_SIZE - 1)) + +#if CONFIG_IS_ENABLED(DM_I2C) +static int eeprom_i2c_bus; +#endif + +__weak int eeprom_write_enable(unsigned dev_addr, int state) +{ + return 0; +} + +void eeprom_init(int bus) +{ + /* I2C EEPROM */ +#if CONFIG_IS_ENABLED(DM_I2C) + eeprom_i2c_bus = bus; +#elif CONFIG_IS_ENABLED(SYS_I2C_LEGACY) + if (bus >= 0) + i2c_set_bus_num(bus); + i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE); +#endif +} + +/* + * for CONFIG_SYS_I2C_EEPROM_ADDR_LEN == 2 (16-bit EEPROM address) offset is + * 0x000nxxxx for EEPROM address selectors at n, offset xxxx in EEPROM. + * + * for CONFIG_SYS_I2C_EEPROM_ADDR_LEN == 1 (8-bit EEPROM page address) offset is + * 0x00000nxx for EEPROM address selectors and page number at n. + */ +static int eeprom_addr(unsigned dev_addr, unsigned offset, uchar *addr) +{ + unsigned blk_off; + int alen; + + blk_off = offset & 0xff; /* block offset */ +#if CONFIG_SYS_I2C_EEPROM_ADDR_LEN == 1 + addr[0] = offset >> 8; /* block number */ + addr[1] = blk_off; /* block offset */ + alen = 2; +#else + addr[0] = offset >> 16; /* block number */ + addr[1] = offset >> 8; /* upper address octet */ + addr[2] = blk_off; /* lower address octet */ + alen = 3; +#endif /* CONFIG_SYS_I2C_EEPROM_ADDR_LEN */ + + addr[0] |= dev_addr; /* insert device address */ + + return alen; +} + +static int eeprom_len(unsigned offset, unsigned end) +{ + unsigned len = end - offset; + + /* + * For a FRAM device there is no limit on the number of the + * bytes that can be accessed with the single read or write + * operation. + */ +#if !defined(CONFIG_SYS_I2C_FRAM) + unsigned blk_off = offset & 0xff; + unsigned maxlen = EEPROM_PAGE_SIZE - EEPROM_PAGE_OFFSET(blk_off); + + if (maxlen > I2C_RXTX_LEN) + maxlen = I2C_RXTX_LEN; + + if (len > maxlen) + len = maxlen; +#endif + + return len; +} + +static int eeprom_rw_block(unsigned offset, uchar *addr, unsigned alen, + uchar *buffer, unsigned len, bool read) +{ + int ret = 0; + +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; + + ret = i2c_get_chip_for_busnum(eeprom_i2c_bus, addr[0], + alen - 1, &dev); + if (ret) { + printf("%s: Cannot find udev for a bus %d\n", __func__, + eeprom_i2c_bus); + return CMD_RET_FAILURE; + } + + if (read) + ret = dm_i2c_read(dev, offset, buffer, len); + else + ret = dm_i2c_write(dev, offset, buffer, len); + +#else /* Non DM I2C support - will be removed */ + + if (read) + ret = i2c_read(addr[0], offset, alen - 1, buffer, len); + else + ret = i2c_write(addr[0], offset, alen - 1, buffer, len); +#endif /* CONFIG_DM_I2C */ + if (ret) + ret = CMD_RET_FAILURE; + + return ret; +} + +static int eeprom_rw(unsigned dev_addr, unsigned offset, uchar *buffer, + unsigned cnt, bool read) +{ + unsigned end = offset + cnt; + unsigned alen, len; + int rcode = 0; + uchar addr[3]; + +#if !CONFIG_IS_ENABLED(DM_I2C) && defined(CONFIG_SYS_I2C_EEPROM_BUS) + eeprom_init(CONFIG_SYS_I2C_EEPROM_BUS); +#endif + + while (offset < end) { + alen = eeprom_addr(dev_addr, offset, addr); + + len = eeprom_len(offset, end); + + rcode = eeprom_rw_block(offset, addr, alen, buffer, len, read); + + buffer += len; + offset += len; + +#if CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS > 0 + if (!read) + udelay(CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS * 1000); +#endif + } + + return rcode; +} + +int eeprom_read(unsigned dev_addr, unsigned offset, uchar *buffer, unsigned cnt) +{ + /* + * Read data until done or would cross a page boundary. + * We must write the address again when changing pages + * because the next page may be in a different device. + */ + return eeprom_rw(dev_addr, offset, buffer, cnt, 1); +} + +int eeprom_write(unsigned dev_addr, unsigned offset, + uchar *buffer, unsigned cnt) +{ + int ret; + + eeprom_write_enable(dev_addr, 1); + + /* + * Write data until done or would cross a write page boundary. + * We must write the address again when changing pages + * because the address counter only increments within a page. + */ + ret = eeprom_rw(dev_addr, offset, buffer, cnt, 0); + + eeprom_write_enable(dev_addr, 0); + return ret; +} + +static long parse_numeric_param(char *str) +{ + char *endptr; + long value = simple_strtol(str, &endptr, 16); + + return (*endptr != '\0') ? -1 : value; +} + +struct eeprom_dev_spec { +#if CONFIG_IS_ENABLED(I2C_EEPROM) + struct udevice *dev; +#endif + int i2c_bus; + ulong i2c_addr; +}; + +static void eeprom_dev_spec_init(struct eeprom_dev_spec *dev) +{ +#if CONFIG_IS_ENABLED(I2C_EEPROM) + if (!dev->dev) +#endif + eeprom_init(dev->i2c_bus); +} + +static int eeprom_dev_spec_read(struct eeprom_dev_spec *dev, + unsigned offset, uchar *buffer, unsigned cnt) +{ +#if CONFIG_IS_ENABLED(I2C_EEPROM) + if (dev->dev) + return i2c_eeprom_read(dev->dev, offset, buffer, cnt); +#endif + return eeprom_read(dev->i2c_addr, offset, buffer, cnt); +} + +static int eeprom_dev_spec_write(struct eeprom_dev_spec *dev, + unsigned offset, uchar *buffer, unsigned cnt) +{ +#if CONFIG_IS_ENABLED(I2C_EEPROM) + if (dev->dev) + return i2c_eeprom_write(dev->dev, offset, buffer, cnt); +#endif + return eeprom_write(dev->i2c_addr, offset, buffer, cnt); +} + +/** + * parse_eeprom_dev_spec - parse the eeprom device specifier + * + * @dev: pointer to eeprom device specifier + * @argc: count of command line arguments that can be used to parse + * the device specifier + * @argv: command line arguments left to parse + * + * @returns: number of arguments parsed or CMD_RET_USAGE if error + */ +static int parse_eeprom_dev_spec(struct eeprom_dev_spec *dev, int argc, + char *const argv[]) +{ +#if CONFIG_IS_ENABLED(I2C_EEPROM) + if (argc == 0) { + if (!uclass_first_device_err(UCLASS_I2C_EEPROM, &dev->dev)) + return 0; + } + + if (argc == 1) { + if (!uclass_get_device_by_name(UCLASS_I2C_EEPROM, argv[0], + &dev->dev)) + return 1; + + /* + * If we could not find the device by name and the parameter is + * not numeric (and so won't be handled later), fail. + */ + if (parse_numeric_param(argv[0]) == -1) { + printf("Can't get eeprom device: %s\n", argv[0]); + return CMD_RET_USAGE; + } + } +#endif + +#ifdef CONFIG_SYS_I2C_EEPROM_ADDR + if (argc == 0) { + dev->i2c_bus = -1; + dev->i2c_addr = CONFIG_SYS_I2C_EEPROM_ADDR; + + return 0; + } +#endif + if (argc == 1) { + dev->i2c_bus = -1; + dev->i2c_addr = parse_numeric_param(argv[0]); + + return 1; + } + + if (argc == 2) { + dev->i2c_bus = parse_numeric_param(argv[0]); + dev->i2c_addr = parse_numeric_param(argv[1]); + + return 2; + } + + return CMD_RET_USAGE; +} + +#ifdef CONFIG_CMD_EEPROM_LAYOUT + +#ifdef CONFIG_EEPROM_LAYOUT_VERSIONS +__weak int eeprom_parse_layout_version(char *str) +{ + return LAYOUT_VERSION_UNRECOGNIZED; +} +#endif + +static unsigned char eeprom_buf[CONFIG_SYS_EEPROM_SIZE]; + +#endif + +enum eeprom_action { + EEPROM_LIST, + EEPROM_READ, + EEPROM_WRITE, + EEPROM_PRINT, + EEPROM_UPDATE, + EEPROM_ACTION_INVALID, +}; + +static enum eeprom_action parse_action(char *cmd) +{ +#if CONFIG_IS_ENABLED(I2C_EEPROM) + if (!strncmp(cmd, "list", 4)) + return EEPROM_LIST; +#endif + if (!strncmp(cmd, "read", 4)) + return EEPROM_READ; + if (!strncmp(cmd, "write", 5)) + return EEPROM_WRITE; +#ifdef CONFIG_CMD_EEPROM_LAYOUT + if (!strncmp(cmd, "print", 5)) + return EEPROM_PRINT; + if (!strncmp(cmd, "update", 6)) + return EEPROM_UPDATE; +#endif + + return EEPROM_ACTION_INVALID; +} + +#if CONFIG_IS_ENABLED(I2C_EEPROM) +static int do_eeprom_list(void) +{ + struct udevice *dev; + struct uclass *uc; + int err; + + err = uclass_get(UCLASS_I2C_EEPROM, &uc); + if (err) + return CMD_RET_FAILURE; + + uclass_foreach_dev(dev, uc) + printf("%s (%s)\n", dev->name, dev->driver->name); + + return CMD_RET_SUCCESS; +} +#endif + +static int do_eeprom_rw(struct eeprom_dev_spec *dev, bool read, + ulong addr, ulong off, ulong cnt) +{ + const char *const fmt = + "\nEEPROM @0x%lX %s: addr 0x%08lx off 0x%04lx count %ld ... "; + uchar *memloc = (uchar *)addr; + int ret; + + printf(fmt, dev->i2c_addr, read ? "read" : "write", addr, off, cnt); + if (read) + ret = eeprom_dev_spec_read(dev, off, memloc, cnt); + else + ret = eeprom_dev_spec_write(dev, off, memloc, cnt); + puts("done\n"); + + return ret; +} + +#ifdef CONFIG_CMD_EEPROM_LAYOUT + +static int do_eeprom_layout(struct eeprom_dev_spec *dev, int layout_ver, + struct eeprom_layout *layout) +{ + eeprom_layout_setup(layout, eeprom_buf, CONFIG_SYS_EEPROM_SIZE, + layout_ver); + + return eeprom_dev_spec_read(dev, 0, eeprom_buf, layout->data_size); +} + +static int do_eeprom_print(struct eeprom_dev_spec *dev, int layout_ver) +{ + struct eeprom_layout layout; + int ret; + + ret = do_eeprom_layout(dev, layout_ver, &layout); + if (ret) + return ret; + + layout.print(&layout); + + return 0; +} + +static int do_eeprom_update(struct eeprom_dev_spec *dev, int layout_ver, + char *key, char *value) +{ + struct eeprom_layout layout; + int ret; + + ret = do_eeprom_layout(dev, layout_ver, &layout); + if (ret) + return ret; + + ret = layout.update(&layout, key, value); + if (ret) + return CMD_RET_FAILURE; + + return eeprom_dev_spec_write(dev, 0, layout.data, layout.data_size); +} + +#endif + +static int eeprom_action_expected_argc(enum eeprom_action action) +{ + switch (action) { + case EEPROM_LIST: + return 0; + case EEPROM_READ: + case EEPROM_WRITE: + return 3; + case EEPROM_PRINT: + return 0; + case EEPROM_UPDATE: + return 2; + default: + return CMD_RET_USAGE; + } +} + +#define NEXT_PARAM(argc, index) { (argc)--; (index)++; } +int do_eeprom(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + enum eeprom_action action = EEPROM_ACTION_INVALID; + struct eeprom_dev_spec dev; + ulong addr = 0, cnt = 0, off = 0; + int ret, index = 0; +#ifdef CONFIG_CMD_EEPROM_LAYOUT + char *field_name = ""; + char *field_value = ""; + int layout_ver = LAYOUT_VERSION_AUTODETECT; +#endif + + if (argc <= 1) + return CMD_RET_USAGE; + + NEXT_PARAM(argc, index); /* Skip program name */ + + action = parse_action(argv[index]); + NEXT_PARAM(argc, index); + + if (action == EEPROM_ACTION_INVALID) + return CMD_RET_USAGE; + +#if CONFIG_IS_ENABLED(I2C_EEPROM) + if (action == EEPROM_LIST) + return do_eeprom_list(); +#endif + +#ifdef CONFIG_EEPROM_LAYOUT_VERSIONS + if (action == EEPROM_PRINT || action == EEPROM_UPDATE) { + if (!strcmp(argv[index], "-l")) { + NEXT_PARAM(argc, index); + layout_ver = eeprom_parse_layout_version(argv[index]); + NEXT_PARAM(argc, index); + } + } +#endif + + ret = parse_eeprom_dev_spec(&dev, + argc - eeprom_action_expected_argc(action), + argv + index); + if (ret == CMD_RET_USAGE) + return ret; + + while (ret--) + NEXT_PARAM(argc, index); + + if (action == EEPROM_READ || action == EEPROM_WRITE) { + addr = parse_numeric_param(argv[index]); + NEXT_PARAM(argc, index); + off = parse_numeric_param(argv[index]); + NEXT_PARAM(argc, index); + cnt = parse_numeric_param(argv[index]); + } + +#ifdef CONFIG_CMD_EEPROM_LAYOUT + if (action == EEPROM_UPDATE) { + field_name = argv[index]; + NEXT_PARAM(argc, index); + field_value = argv[index]; + NEXT_PARAM(argc, index); + } +#endif + + eeprom_dev_spec_init(&dev); + + switch (action) { + case EEPROM_READ: + case EEPROM_WRITE: + return do_eeprom_rw(&dev, action == EEPROM_READ, + addr, off, cnt); +#ifdef CONFIG_CMD_EEPROM_LAYOUT + case EEPROM_PRINT: + return do_eeprom_print(&dev, layout_ver); + case EEPROM_UPDATE: + return do_eeprom_update(&dev, layout_ver, + field_name, field_value); +#endif + default: + return CMD_RET_USAGE; + } +} + +#ifdef CONFIG_EEPROM_LAYOUT_VERSIONS +#define EEPROM_LAYOUT_SPEC "[-l <layout_version>] " +#else +#define EEPROM_LAYOUT_SPEC "" +#endif + +#if CONFIG_IS_ENABLED(I2C_EEPROM) +# define EEPROM_DEV_SPEC "[device_specifier]" +#else +# define EEPROM_DEV_SPEC "[[bus] devaddr]" +#endif + +U_BOOT_CMD( + eeprom, 8, 1, do_eeprom, + "EEPROM sub-system", +#if CONFIG_IS_ENABLED(I2C_EEPROM) + "list\n" + "eeprom " +#endif + "read " EEPROM_DEV_SPEC " addr off cnt\n" + "eeprom write " EEPROM_DEV_SPEC " addr off cnt\n" + " - read/write `cnt' bytes from `devaddr` EEPROM at offset `off'" +#ifdef CONFIG_CMD_EEPROM_LAYOUT + "\n" + "eeprom print " EEPROM_LAYOUT_SPEC EEPROM_DEV_SPEC "\n" + " - Print layout fields and their data in human readable format\n" + "eeprom update " EEPROM_LAYOUT_SPEC EEPROM_DEV_SPEC " field_name field_value\n" + " - Update a specific eeprom field with new data.\n" + " The new data must be written in the same human readable format as shown by the print command." +#endif +#if CONFIG_IS_ENABLED(I2C_EEPROM) + "\n\n" + "DEVICE SPECIFIER - the eeprom device can be specified\n" + " [dev_name] - by device name (devices can listed with the eeprom list command)\n" + " [[bus] devaddr] - or by I2C bus and I2C device address\n" + "If no device specifier is given, the first driver-model found device is used." +#endif +#ifdef CONFIG_EEPROM_LAYOUT_VERSIONS + "\n\n" + "LAYOUT VERSIONS\n" + "The -l option can be used to force the command to interpret the EEPROM data using the chosen layout.\n" + "If the -l option is omitted, the command will auto detect the layout based on the data in the EEPROM.\n" + "The values which can be provided with the -l option are:\n" + CONFIG_EEPROM_LAYOUT_HELP_STRING"\n" +#endif +); diff --git a/cmd/efi.c b/cmd/efi.c new file mode 100644 index 00000000000..687ccb52042 --- /dev/null +++ b/cmd/efi.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2015 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <efi.h> +#include <efi_api.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <sort.h> +#include <u-boot/uuid.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const char *const type_name[] = { + "reserved", + "loader_code", + "loader_data", + "bs_code", + "bs_data", + "rt_code", + "rt_data", + "conv", + "unusable", + "acpi_reclaim", + "acpi_nvs", + "io", + "io_port", + "pal_code", +}; + +static struct attr_info { + u64 val; + const char *name; +} mem_attr[] = { + { EFI_MEMORY_UC, "uncached" }, + { EFI_MEMORY_WC, "write-coalescing" }, + { EFI_MEMORY_WT, "write-through" }, + { EFI_MEMORY_WB, "write-back" }, + { EFI_MEMORY_UCE, "uncached & exported" }, + { EFI_MEMORY_WP, "write-protect" }, + { EFI_MEMORY_RP, "read-protect" }, + { EFI_MEMORY_XP, "execute-protect" }, + { EFI_MEMORY_NV, "non-volatile" }, + { EFI_MEMORY_MORE_RELIABLE, "higher reliability" }, + { EFI_MEMORY_RO, "read-only" }, + { EFI_MEMORY_SP, "specific purpose" }, + { EFI_MEMORY_RUNTIME, "needs runtime mapping" } +}; + +/* Maximum different attribute values we can track */ +#define ATTR_SEEN_MAX 30 + +static inline bool is_boot_services(int type) +{ + return type == EFI_LOADER_CODE || type == EFI_LOADER_DATA || + type == EFI_BOOT_SERVICES_CODE || + type == EFI_BOOT_SERVICES_DATA; +} + +static int h_cmp_entry(const void *v1, const void *v2) +{ + const struct efi_mem_desc *desc1 = v1; + const struct efi_mem_desc *desc2 = v2; + int64_t diff = desc1->physical_start - desc2->physical_start; + + /* + * Manually calculate the difference to avoid sign loss in the 64-bit + * to 32-bit conversion + */ + return diff < 0 ? -1 : diff > 0 ? 1 : 0; +} + +/** + * efi_build_mem_table() - make a sorted copy of the memory table + * + * @desc_base: Pointer to EFI memory map table + * @size: Size of table in bytes + * @desc_size: Size of each @desc_base record + * @skip_bs: True to skip boot-time memory and merge it with conventional + * memory. This will significantly reduce the number of table + * entries. + * Return: pointer to the new table. It should be freed with free() by the + * caller. + */ +static void *efi_build_mem_table(struct efi_mem_desc *desc_base, int size, + int desc_size, bool skip_bs) +{ + struct efi_mem_desc *desc, *end, *base, *dest, *prev; + int count; + u64 addr; + + base = malloc(size + sizeof(*desc)); + if (!base) { + debug("%s: Cannot allocate %#x bytes\n", __func__, size); + return NULL; + } + end = (void *)desc_base + size; + count = ((ulong)end - (ulong)desc_base) / desc_size; + memcpy(base, desc_base, (ulong)end - (ulong)desc_base); + qsort(base, count, desc_size, h_cmp_entry); + prev = NULL; + addr = 0; + dest = base; + end = (struct efi_mem_desc *)((ulong)base + count * desc_size); + for (desc = base; desc < end; + desc = efi_get_next_mem_desc(desc, desc_size)) { + bool merge = true; + u32 type = desc->type; + + if (type >= EFI_MAX_MEMORY_TYPE) { + printf("Memory map contains invalid entry type %u\n", + type); + continue; + } + + if (skip_bs && is_boot_services(desc->type)) + type = EFI_CONVENTIONAL_MEMORY; + + memcpy(dest, desc, desc_size); + dest->type = type; + if (!skip_bs || !prev) + merge = false; + else if (desc->physical_start != addr) + merge = false; + else if (type != EFI_CONVENTIONAL_MEMORY) + merge = false; + else if (prev->type != EFI_CONVENTIONAL_MEMORY) + merge = false; + + if (merge) { + prev->num_pages += desc->num_pages; + } else { + prev = dest; + dest = efi_get_next_mem_desc(dest, desc_size); + } + addr = desc->physical_start + (desc->num_pages << + EFI_PAGE_SHIFT); + } + + /* Mark the end */ + dest->type = EFI_MAX_MEMORY_TYPE; + + return base; +} + +static void efi_print_mem_table(struct efi_mem_desc *desc, int desc_size, + bool skip_bs) +{ + u64 attr_seen[ATTR_SEEN_MAX]; + int attr_seen_count; + int upto, i; + u64 addr; + + printf(" # %-14s %10s %10s %10s %s\n", "Type", "Physical", + "Virtual", "Size", "Attributes"); + + /* Keep track of all the different attributes we have seen */ + attr_seen_count = 0; + addr = 0; + for (upto = 0; desc->type != EFI_MAX_MEMORY_TYPE; + upto++, desc = efi_get_next_mem_desc(desc, desc_size)) { + const char *name; + u64 size; + + if (skip_bs && is_boot_services(desc->type)) + continue; + if (desc->physical_start != addr) { + printf(" %-14s %010llx %10s %010llx\n", "<gap>", + addr, "", desc->physical_start - addr); + } + size = desc->num_pages << EFI_PAGE_SHIFT; + + name = desc->type < ARRAY_SIZE(type_name) ? + type_name[desc->type] : "<invalid>"; + printf("%2d %x:%-12s %010llx %010llx %010llx ", upto, + desc->type, name, desc->physical_start, + desc->virtual_start, size); + if (desc->attribute & EFI_MEMORY_RUNTIME) + putc('r'); + printf("%llx", desc->attribute & ~EFI_MEMORY_RUNTIME); + putc('\n'); + + for (i = 0; i < attr_seen_count; i++) { + if (attr_seen[i] == desc->attribute) + break; + } + if (i == attr_seen_count && i < ATTR_SEEN_MAX) + attr_seen[attr_seen_count++] = desc->attribute; + addr = desc->physical_start + size; + } + + printf("\nAttributes key:\n"); + for (i = 0; i < attr_seen_count; i++) { + u64 attr = attr_seen[i]; + bool first; + int j; + + printf("%c%llx: ", (attr & EFI_MEMORY_RUNTIME) ? 'r' : ' ', + attr & ~EFI_MEMORY_RUNTIME); + for (j = 0, first = true; j < ARRAY_SIZE(mem_attr); j++) { + if (attr & mem_attr[j].val) { + if (first) + first = false; + else + printf(", "); + printf("%s", mem_attr[j].name); + } + } + putc('\n'); + } + if (skip_bs) + printf("*Some areas are merged (use 'all' to see)\n"); +} + +static int do_efi_mem(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct efi_mem_desc *orig, *desc; + uint version, key; + int desc_size; + int size, ret; + bool skip_bs; + + skip_bs = !argc || *argv[0] != 'a'; + if (IS_ENABLED(CONFIG_EFI_APP)) { + ret = efi_get_mmap(&orig, &size, &key, &desc_size, &version); + if (ret) { + printf("Cannot read memory map (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + } else { + struct efi_entry_memmap *map; + + ret = efi_info_get(EFIET_MEMORY_MAP, (void **)&map, &size); + switch (ret) { + case -ENOENT: + printf("No EFI table available\n"); + goto done; + case -EPROTONOSUPPORT: + printf("Incorrect EFI table version\n"); + goto done; + } + orig = map->desc; + desc_size = map->desc_size; + version = map->version; + } + printf("EFI table at %lx, memory map %p, size %x, key %x, version %x, descr. size %#x\n", + gd->arch.table, orig, size, key, version, desc_size); + if (version != EFI_MEM_DESC_VERSION) { + printf("Incorrect memory map version\n"); + ret = -EPROTONOSUPPORT; + goto done; + } + + desc = efi_build_mem_table(orig, size, desc_size, skip_bs); + if (!desc) { + ret = -ENOMEM; + goto done; + } + + efi_print_mem_table(desc, desc_size, skip_bs); + free(desc); + if (IS_ENABLED(CONFIG_EFI_APP)) + free(orig); +done: + if (ret) + printf("Error: %d\n", ret); + + return ret ? CMD_RET_FAILURE : 0; +} + +static int do_efi_tables(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct efi_system_table *systab; + + if (IS_ENABLED(CONFIG_EFI_APP)) { + systab = efi_get_sys_table(); + if (!systab) { + printf("Cannot read system table\n"); + return CMD_RET_FAILURE; + } + } else { + int size; + int ret; + + ret = efi_info_get(EFIET_SYS_TABLE, (void **)&systab, &size); + if (ret) /* this should not happen */ + return CMD_RET_FAILURE; + } + + efi_show_tables(systab); + + return 0; +} + +static struct cmd_tbl efi_commands[] = { + U_BOOT_CMD_MKENT(mem, 1, 1, do_efi_mem, "", ""), + U_BOOT_CMD_MKENT(tables, 1, 1, do_efi_tables, "", ""), +}; + +static int do_efi(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *efi_cmd; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + efi_cmd = find_cmd_tbl(argv[1], efi_commands, ARRAY_SIZE(efi_commands)); + argc -= 2; + argv += 2; + if (!efi_cmd || argc > efi_cmd->maxargs) + return CMD_RET_USAGE; + + ret = efi_cmd->cmd(efi_cmd, flag, argc, argv); + + return cmd_process_error(efi_cmd, ret); +} + +U_BOOT_CMD( + efi, 3, 1, do_efi, + "EFI access", + "mem [all] Dump memory information [include boot services]\n" + "tables Dump tables" +); diff --git a/cmd/efi_common.c b/cmd/efi_common.c new file mode 100644 index 00000000000..d2f2b59e9e3 --- /dev/null +++ b/cmd/efi_common.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Common code for EFI commands + * + * Copyright 2023 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <efi.h> +#include <efi_api.h> +#include <u-boot/uuid.h> + +void efi_show_tables(struct efi_system_table *systab) +{ + int i; + + for (i = 0; i < systab->nr_tables; i++) { + struct efi_configuration_table *tab = &systab->tables[i]; + + printf("%p %pUl %s\n", tab->table, tab->guid.b, + uuid_guid_get_str(tab->guid.b) ?: "(unknown)"); + } +} diff --git a/cmd/eficonfig.c b/cmd/eficonfig.c new file mode 100644 index 00000000000..8ac0fb98e02 --- /dev/null +++ b/cmd/eficonfig.c @@ -0,0 +1,2507 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Menu-driven UEFI Variable maintenance + * + * Copyright (c) 2022 Masahisa Kojima, Linaro Limited + */ + +#include <ansi.h> +#include <cli.h> +#include <charset.h> +#include <efi_device_path.h> +#include <efi_loader.h> +#include <efi_load_initrd.h> +#include <efi_config.h> +#include <efi_variable.h> +#include <log.h> +#include <malloc.h> +#include <menu.h> +#include <sort.h> +#include <watchdog.h> +#include <asm/unaligned.h> +#include <linux/delay.h> + +static struct efi_simple_text_input_protocol *cin; +const char *eficonfig_menu_desc = + " Press UP/DOWN to move, ENTER to select, ESC to quit"; + +static const char *eficonfig_change_boot_order_desc = + " Press UP/DOWN to move, +/- to change orde\n" + " Press SPACE to activate or deactivate the entry\n" + " CTRL+S to save, ESC to quit"; + +static struct efi_simple_text_output_protocol *cout; +static int avail_row; + +#define EFICONFIG_DESCRIPTION_MAX 32 +#define EFICONFIG_OPTIONAL_DATA_MAX 64 +#define EFICONFIG_URI_MAX 512 +#define EFICONFIG_MENU_HEADER_ROW_NUM 3 +#define EFICONFIG_MENU_DESC_ROW_NUM 5 + +/** + * struct eficonfig_filepath_info - structure to be used to store file path + * + * @name: file or directory name + * @list: list structure + */ +struct eficonfig_filepath_info { + char *name; + struct list_head list; +}; + +/** + * struct eficonfig_boot_option - structure to be used for updating UEFI boot option + * + * @file_info: user selected file info + * @initrd_info: user selected initrd file info + * @boot_index: index of the boot option + * @description: pointer to the description string + * @optional_data: pointer to the optional_data + * @edit_completed: flag indicates edit complete + */ +struct eficonfig_boot_option { + struct eficonfig_select_file_info file_info; + struct eficonfig_select_file_info initrd_info; + struct eficonfig_select_file_info fdt_info; + unsigned int boot_index; + u16 *description; + u16 *optional_data; + bool edit_completed; +}; + +/** + * struct eficonfig_volume_entry_data - structure to be used to store volume info + * + * @file_info: pointer to file info structure + * @v: pointer to the protocol interface + * @dp: pointer to the device path + */ +struct eficonfig_volume_entry_data { + struct eficonfig_select_file_info *file_info; + struct efi_simple_file_system_protocol *v; + struct efi_device_path *dp; +}; + +/** + * struct eficonfig_file_entry_data - structure to be used to store file info + * + * @file_info: pointer to file info structure + * @is_directory: flag to identify the directory or file + * @file_name: name of directory or file + */ +struct eficonfig_file_entry_data { + struct eficonfig_select_file_info *file_info; + bool is_directory; + char *file_name; +}; + +/** + * struct eficonfig_boot_selection_data - structure to be used to select the boot option entry + * + * @boot_index: index of the boot option + * @selected: pointer to store the selected index in the BootOrder variable + */ +struct eficonfig_boot_selection_data { + u16 boot_index; + int *selected; +}; + +/** + * struct eficonfig_boot_order_data - structure to be used to update BootOrder variable + * + * @boot_index: boot option index + * @active: flag to include the boot option into BootOrder variable + */ +struct eficonfig_boot_order_data { + u32 boot_index; + bool active; +}; + +/** + * struct eficonfig_save_boot_order_data - structure to be used to change boot order + * + * @efi_menu: pointer to efimenu structure + * @selected: flag to indicate user selects "Save" entry + */ +struct eficonfig_save_boot_order_data { + struct efimenu *efi_menu; + bool selected; +}; + +/** + * struct eficonfig_menu_adjust - update start and end entry index + * + * @efi_menu: pointer to efimenu structure + * @add: flag to add or substract the index + */ +static void eficonfig_menu_adjust(struct efimenu *efi_menu, bool add) +{ + if (add) + ++efi_menu->active; + else + --efi_menu->active; + + if (add && efi_menu->end < efi_menu->active) { + efi_menu->start++; + efi_menu->end++; + } else if (!add && efi_menu->start > efi_menu->active) { + efi_menu->start--; + efi_menu->end--; + } +} +#define eficonfig_menu_up(_a) eficonfig_menu_adjust(_a, false) +#define eficonfig_menu_down(_a) eficonfig_menu_adjust(_a, true) + +/** + * eficonfig_print_msg() - print message + * + * display the message to the user, user proceeds the screen + * with any key press. + * + * @items: pointer to the structure of each menu entry + * @count: the number of menu entry + * @menu_header: pointer to the menu header string + * Return: status code + */ +void eficonfig_print_msg(char *msg) +{ + /* Flush input */ + while (tstc()) + getchar(); + + printf(ANSI_CURSOR_HIDE + ANSI_CLEAR_CONSOLE + ANSI_CURSOR_POSITION + "%s\n\n Press any key to continue", 3, 4, msg); + + getchar(); +} + +/** + * eficonfig_print_entry() - print each menu entry + * + * @data: pointer to the data associated with each menu entry + */ +void eficonfig_print_entry(void *data) +{ + struct eficonfig_entry *entry = data; + bool reverse = (entry->efi_menu->active == entry->num); + + if (entry->efi_menu->start > entry->num || entry->efi_menu->end < entry->num) + return; + + printf(ANSI_CURSOR_POSITION, (entry->num - entry->efi_menu->start) + + EFICONFIG_MENU_HEADER_ROW_NUM + 1, 7); + + if (reverse) + puts(ANSI_COLOR_REVERSE); + + printf(ANSI_CLEAR_LINE "%s", entry->title); + + if (reverse) + puts(ANSI_COLOR_RESET); +} + +/** + * eficonfig_display_statusline() - print status line + * + * @m: pointer to the menu structure + */ +void eficonfig_display_statusline(struct menu *m) +{ + struct eficonfig_entry *entry; + + if (menu_default_choice(m, (void *)&entry) < 0) + return; + + printf(ANSI_CURSOR_POSITION + "\n%s\n" + ANSI_CURSOR_POSITION ANSI_CLEAR_LINE ANSI_CURSOR_POSITION + "%s" + ANSI_CLEAR_LINE_TO_END, + 1, 1, entry->efi_menu->menu_header, avail_row + 4, 1, + avail_row + 5, 1, entry->efi_menu->menu_desc); +} + +/** + * eficonfig_choice_entry() - user key input handler + * + * @data: pointer to the efimenu structure + * Return: key string to identify the selected entry + */ +char *eficonfig_choice_entry(void *data) +{ + struct cli_ch_state s_cch, *cch = &s_cch; + struct list_head *pos, *n; + struct eficonfig_entry *entry; + enum bootmenu_key key = BKEY_NONE; + struct efimenu *efi_menu = data; + + cli_ch_init(cch); + + while (1) { + key = bootmenu_loop((struct bootmenu_data *)efi_menu, cch); + + switch (key) { + case BKEY_UP: + if (efi_menu->active > 0) + eficonfig_menu_up(efi_menu); + + /* no menu key selected, regenerate menu */ + return NULL; + case BKEY_DOWN: + if (efi_menu->active < efi_menu->count - 1) + eficonfig_menu_down(efi_menu); + + /* no menu key selected, regenerate menu */ + return NULL; + case BKEY_SELECT: + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + if (entry->num == efi_menu->active) + return entry->key; + } + break; + case BKEY_QUIT: + /* Quit by choosing the last entry */ + entry = list_last_entry(&efi_menu->list, struct eficonfig_entry, list); + return entry->key; + default: + /* Pressed key is not valid, no need to regenerate the menu */ + break; + } + } +} + +/** + * eficonfig_destroy() - destroy efimenu + * + * @efi_menu: pointer to the efimenu structure + */ +void eficonfig_destroy(struct efimenu *efi_menu) +{ + struct list_head *pos, *n; + struct eficonfig_entry *entry; + + if (!efi_menu) + return; + + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + free(entry->title); + list_del(&entry->list); + free(entry); + } + free(efi_menu->menu_header); + free(efi_menu); +} + +/** + * eficonfig_process_quit() - callback function for "Quit" entry + * + * @data: pointer to the data + * Return: status code + */ +efi_status_t eficonfig_process_quit(void *data) +{ + return EFI_ABORTED; +} + +/** + * eficonfig_append_menu_entry() - append menu item + * + * @efi_menu: pointer to the efimenu structure + * @title: pointer to the entry title + * @func: callback of each entry + * @data: pointer to the data to be passed to each entry callback + * Return: status code + */ +efi_status_t eficonfig_append_menu_entry(struct efimenu *efi_menu, + char *title, eficonfig_entry_func func, + void *data) +{ + struct eficonfig_entry *entry; + + if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX) + return EFI_OUT_OF_RESOURCES; + + entry = calloc(1, sizeof(struct eficonfig_entry)); + if (!entry) + return EFI_OUT_OF_RESOURCES; + + entry->title = title; + sprintf(entry->key, "%d", efi_menu->count); + entry->efi_menu = efi_menu; + entry->func = func; + entry->data = data; + entry->num = efi_menu->count++; + list_add_tail(&entry->list, &efi_menu->list); + + return EFI_SUCCESS; +} + +/** + * eficonfig_append_quit_entry() - append quit entry + * + * @efi_menu: pointer to the efimenu structure + * Return: status code + */ +efi_status_t eficonfig_append_quit_entry(struct efimenu *efi_menu) +{ + char *title; + efi_status_t ret; + + title = strdup("Quit"); + if (!title) + return EFI_OUT_OF_RESOURCES; + + ret = eficonfig_append_menu_entry(efi_menu, title, eficonfig_process_quit, NULL); + if (ret != EFI_SUCCESS) + free(title); + + return ret; +} + +/** + * eficonfig_create_fixed_menu() - create fixed entry menu structure + * + * @items: pointer to the menu entry item + * @count: the number of menu entry + * Return: pointer to the efimenu structure + */ +void *eficonfig_create_fixed_menu(const struct eficonfig_item *items, int count) +{ + u32 i; + char *title; + efi_status_t ret; + struct efimenu *efi_menu; + const struct eficonfig_item *iter = items; + + efi_menu = calloc(1, sizeof(struct efimenu)); + if (!efi_menu) + return NULL; + + INIT_LIST_HEAD(&efi_menu->list); + for (i = 0; i < count; i++, iter++) { + title = strdup(iter->title); + if (!title) + goto out; + + ret = eficonfig_append_menu_entry(efi_menu, title, iter->func, iter->data); + if (ret != EFI_SUCCESS) { + free(title); + goto out; + } + } + + return efi_menu; +out: + eficonfig_destroy(efi_menu); + + return NULL; +} + +/** + * eficonfig_process_common() - main handler for UEFI menu + * + * Construct the structures required to show the menu, then handle + * the user input interacting with u-boot menu functions. + * + * @efi_menu: pointer to the efimenu structure + * @menu_header: pointer to the menu header string + * @menu_desc: pointer to the menu description + * @display_statusline: function pointer to draw statusline + * @item_data_print: function pointer to draw the menu item + * @item_choice: function pointer to handle the key press + * Return: status code + */ +efi_status_t eficonfig_process_common(struct efimenu *efi_menu, + char *menu_header, const char *menu_desc, + void (*display_statusline)(struct menu *), + void (*item_data_print)(void *), + char *(*item_choice)(void *)) +{ + struct menu *menu; + void *choice = NULL; + struct list_head *pos, *n; + struct eficonfig_entry *entry; + efi_status_t ret = EFI_SUCCESS; + + if (efi_menu->count > EFICONFIG_ENTRY_NUM_MAX) + return EFI_OUT_OF_RESOURCES; + + efi_menu->delay = -1; + efi_menu->active = 0; + efi_menu->start = 0; + efi_menu->end = avail_row - 1; + + if (menu_header) { + efi_menu->menu_header = strdup(menu_header); + if (!efi_menu->menu_header) + return EFI_OUT_OF_RESOURCES; + } + if (menu_desc) + efi_menu->menu_desc = menu_desc; + + menu = menu_create(NULL, 0, 1, display_statusline, item_data_print, + item_choice, NULL, efi_menu); + if (!menu) + return EFI_INVALID_PARAMETER; + + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + if (!menu_item_add(menu, entry->key, entry)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + } + + entry = list_first_entry_or_null(&efi_menu->list, struct eficonfig_entry, list); + if (entry) + menu_default_set(menu, entry->key); + + printf(ANSI_CURSOR_HIDE + ANSI_CLEAR_CONSOLE + ANSI_CURSOR_POSITION, 1, 1); + + if (menu_get_choice(menu, &choice)) { + entry = choice; + if (entry->func) + ret = entry->func(entry->data); + } +out: + menu_destroy(menu); + + printf(ANSI_CLEAR_CONSOLE + ANSI_CURSOR_POSITION + ANSI_CURSOR_SHOW, 1, 1); + + return ret; +} + +/** + * eficonfig_volume_selected() - handler of volume selection + * + * @data: pointer to the data of selected entry + * Return: status code + */ +static efi_status_t eficonfig_volume_selected(void *data) +{ + struct eficonfig_volume_entry_data *info = data; + + if (info) { + info->file_info->current_volume = info->v; + info->file_info->dp_volume = info->dp; + } + + return EFI_SUCCESS; +} + +/** + * eficonfig_create_device_path() - create device path + * + * @dp_volume: pointer to the volume + * @current_path: pointer to the file path u16 string + * Return: + * device path or NULL. Caller must free the returned value + */ +struct efi_device_path *eficonfig_create_device_path(struct efi_device_path *dp_volume, + u16 *current_path) +{ + char *p; + void *buf; + efi_uintn_t fp_size; + struct efi_device_path *dp; + struct efi_device_path_file_path *fp; + + fp_size = sizeof(struct efi_device_path) + u16_strsize(current_path); + buf = calloc(1, fp_size + sizeof(EFI_DP_END)); + if (!buf) + return NULL; + + fp = buf; + fp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE, + fp->dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH, + fp->dp.length = (u16)fp_size; + u16_strcpy(fp->str, current_path); + + p = buf; + p += fp_size; + *((struct efi_device_path *)p) = EFI_DP_END; + + dp = efi_dp_shorten(dp_volume); + if (!dp) + dp = dp_volume; + dp = efi_dp_concat(dp, &fp->dp, 0); + free(buf); + + return dp; +} + +/** + * eficonfig_create_uri_device_path() - Create an URI based device path + * @uri_str: URI string to be added to the device path + * + * Take the u16 string, convert it to a u8 string, and create a URI + * device path. This will be used for the EFI HTTP boot. + * + * Return: pointer to the URI device path on success, NULL on failure + */ +static struct efi_device_path *eficonfig_create_uri_device_path(u16 *uri_str) +{ + char *pos, *p; + u32 len = 0; + efi_uintn_t uridp_len; + struct efi_device_path_uri *uridp; + + len = utf16_utf8_strlen(uri_str); + + uridp_len = sizeof(struct efi_device_path) + len + 1; + uridp = efi_alloc(uridp_len + sizeof(EFI_DP_END)); + if (!uridp) + return NULL; + + uridp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + uridp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_URI; + uridp->dp.length = uridp_len; + p = (char *)&uridp->uri; + utf16_utf8_strcpy(&p, uri_str); + pos = (char *)uridp + uridp_len; + memcpy(pos, &EFI_DP_END, sizeof(EFI_DP_END)); + + return &uridp->dp; +} + +/** + * eficonfig_file_selected() - handler of file selection + * + * @data: pointer to the data of selected entry + * Return: status code + */ +static efi_status_t eficonfig_file_selected(void *data) +{ + u16 *tmp; + struct eficonfig_file_entry_data *info = data; + + if (!info) + return EFI_INVALID_PARAMETER; + + if (!strcmp(info->file_name, "..\\")) { + struct eficonfig_filepath_info *iter; + struct list_head *pos, *n; + int is_last; + char *filepath; + tmp = info->file_info->current_path; + + memset(info->file_info->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE); + filepath = calloc(1, EFICONFIG_FILE_PATH_MAX); + if (!filepath) + return EFI_OUT_OF_RESOURCES; + + list_for_each_safe(pos, n, &info->file_info->filepath_list) { + iter = list_entry(pos, struct eficonfig_filepath_info, list); + + is_last = list_is_last(&iter->list, &info->file_info->filepath_list); + if (is_last) { + list_del(&iter->list); + free(iter->name); + free(iter); + break; + } + strlcat(filepath, iter->name, EFICONFIG_FILE_PATH_MAX); + } + utf8_utf16_strcpy(&tmp, filepath); + } else { + size_t new_len; + struct eficonfig_filepath_info *filepath_info; + + new_len = u16_strlen(info->file_info->current_path) + + strlen(info->file_name); + if (new_len >= EFICONFIG_FILE_PATH_MAX) { + eficonfig_print_msg("File path is too long!"); + return EFI_INVALID_PARAMETER; + } + tmp = &info->file_info->current_path[u16_strlen(info->file_info->current_path)]; + utf8_utf16_strcpy(&tmp, info->file_name); + + filepath_info = calloc(1, sizeof(struct eficonfig_filepath_info)); + if (!filepath_info) + return EFI_OUT_OF_RESOURCES; + + filepath_info->name = strdup(info->file_name); + if (!filepath_info->name) { + free(filepath_info); + return EFI_OUT_OF_RESOURCES; + } + list_add_tail(&filepath_info->list, &info->file_info->filepath_list); + + if (!info->is_directory) + info->file_info->file_selected = true; + } + + return EFI_SUCCESS; +} + +/** + * eficonfig_select_volume() - construct the volume selection menu + * + * @file_info: pointer to the file selection structure + * Return: status code + */ +static efi_status_t eficonfig_select_volume(struct eficonfig_select_file_info *file_info) +{ + u32 i; + efi_status_t ret; + efi_uintn_t count; + struct efimenu *efi_menu; + struct list_head *pos, *n; + struct efi_handler *handler; + struct eficonfig_entry *entry; + struct efi_device_path *device_path; + efi_handle_t *volume_handles = NULL; + struct efi_simple_file_system_protocol *v; + + ret = efi_locate_handle_buffer_int(BY_PROTOCOL, &efi_simple_file_system_protocol_guid, + NULL, &count, (efi_handle_t **)&volume_handles); + if (ret != EFI_SUCCESS) { + eficonfig_print_msg("No block device found!"); + return ret; + } + + efi_menu = calloc(1, sizeof(struct efimenu)); + if (!efi_menu) + return EFI_OUT_OF_RESOURCES; + + INIT_LIST_HEAD(&efi_menu->list); + for (i = 0; i < count; i++) { + char *devname; + struct efi_block_io *block_io; + struct eficonfig_volume_entry_data *info; + + if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1) + break; + + ret = efi_search_protocol(volume_handles[i], + &efi_simple_file_system_protocol_guid, &handler); + if (ret != EFI_SUCCESS) + continue; + ret = efi_protocol_open(handler, (void **)&v, efi_root, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) + continue; + + ret = efi_search_protocol(volume_handles[i], &efi_guid_device_path, &handler); + if (ret != EFI_SUCCESS) + continue; + ret = efi_protocol_open(handler, (void **)&device_path, + efi_root, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) + continue; + + ret = efi_search_protocol(volume_handles[i], &efi_block_io_guid, &handler); + if (ret != EFI_SUCCESS) + continue; + ret = efi_protocol_open(handler, (void **)&block_io, + efi_root, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (ret != EFI_SUCCESS) + continue; + + info = calloc(1, sizeof(struct eficonfig_volume_entry_data)); + if (!info) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + devname = calloc(1, BOOTMENU_DEVICE_NAME_MAX); + if (!devname) { + free(info); + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + ret = efi_disk_get_device_name(volume_handles[i], devname, + BOOTMENU_DEVICE_NAME_MAX); + if (ret != EFI_SUCCESS) { + free(info); + goto out; + } + + info->v = v; + info->dp = device_path; + info->file_info = file_info; + ret = eficonfig_append_menu_entry(efi_menu, devname, eficonfig_volume_selected, + info); + if (ret != EFI_SUCCESS) { + free(info); + goto out; + } + } + + ret = eficonfig_append_quit_entry(efi_menu); + if (ret != EFI_SUCCESS) + goto out; + + ret = eficonfig_process_common(efi_menu, " ** Select Volume **", + eficonfig_menu_desc, + eficonfig_display_statusline, + eficonfig_print_entry, + eficonfig_choice_entry); + +out: + efi_free_pool(volume_handles); + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + free(entry->data); + } + eficonfig_destroy(efi_menu); + + return ret; +} + +/** + * sort_file() - sort the file name in ascii order + * + * @data1: pointer to the file entry data + * @data2: pointer to the file entry data + * Return: -1 if the data1 file name is less than data2 file name, + * 0 if both file name match, + * 1 if the data1 file name is greater thant data2 file name. + */ +static int sort_file(const void *arg1, const void *arg2) +{ + const struct eficonfig_file_entry_data *data1, *data2; + + data1 = *((const struct eficonfig_file_entry_data **)arg1); + data2 = *((const struct eficonfig_file_entry_data **)arg2); + + return strcasecmp(data1->file_name, data2->file_name); +} + +/** + * eficonfig_create_file_entry() - construct the file menu entry + * + * @efi_menu: pointer to the efimenu structure + * @count: number of the directory and file + * @tmp_infos: pointer to the entry data array + * @f: pointer to the file handle + * @buf: pointer to the buffer to store the directory information + * @file_info: pointer to the file selection structure + * Return: status code + */ +static efi_status_t +eficonfig_create_file_entry(struct efimenu *efi_menu, u32 count, + struct eficonfig_file_entry_data **tmp_infos, + struct efi_file_handle *f, struct efi_file_info *buf, + struct eficonfig_select_file_info *file_info) +{ + char *name, *p; + efi_uintn_t len; + efi_status_t ret; + u32 i, entry_num = 0; + struct eficonfig_file_entry_data *info; + + EFI_CALL(f->setpos(f, 0)); + /* Read directory and construct menu structure */ + for (i = 0; i < count; i++) { + if (entry_num >= EFICONFIG_ENTRY_NUM_MAX - 1) + break; + + len = sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE; + ret = EFI_CALL(f->read(f, &len, buf)); + if (ret != EFI_SUCCESS || len == 0) + break; + + info = calloc(1, sizeof(struct eficonfig_file_entry_data)); + if (!info) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + /* append '\\' at the end of directory name */ + name = calloc(1, utf16_utf8_strlen(buf->file_name) + 2); + if (!name) { + ret = EFI_OUT_OF_RESOURCES; + free(info); + goto out; + } + p = name; + utf16_utf8_strcpy(&p, buf->file_name); + if (buf->attribute & EFI_FILE_DIRECTORY) { + /* filter out u'.' */ + if (!u16_strcmp(buf->file_name, u".")) { + free(info); + free(name); + continue; + } + name[u16_strlen(buf->file_name)] = '\\'; + info->is_directory = true; + } + + info->file_name = name; + info->file_info = file_info; + tmp_infos[entry_num++] = info; + } + + qsort(tmp_infos, entry_num, sizeof(*tmp_infos), + (int (*)(const void *, const void *))sort_file); + + for (i = 0; i < entry_num; i++) { + ret = eficonfig_append_menu_entry(efi_menu, tmp_infos[i]->file_name, + eficonfig_file_selected, tmp_infos[i]); + if (ret != EFI_SUCCESS) + goto out; + } + +out: + return ret; +} + +/** + * eficonfig_show_file_selection() - construct the file selection menu + * + * @file_info: pointer to the file selection structure + * @root: pointer to the file handle + * Return: status code + */ +static efi_status_t eficonfig_show_file_selection(struct eficonfig_select_file_info *file_info, + struct efi_file_handle *root) +{ + u32 count = 0, i; + efi_uintn_t len; + efi_status_t ret; + struct efimenu *efi_menu; + struct efi_file_handle *f; + struct efi_file_info *buf; + struct eficonfig_file_entry_data **tmp_infos; + + buf = calloc(1, sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE); + if (!buf) + return EFI_OUT_OF_RESOURCES; + + while (!file_info->file_selected) { + efi_menu = calloc(1, sizeof(struct efimenu)); + if (!efi_menu) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + INIT_LIST_HEAD(&efi_menu->list); + + ret = EFI_CALL(root->open(root, &f, file_info->current_path, + EFI_FILE_MODE_READ, 0)); + if (ret != EFI_SUCCESS) { + eficonfig_print_msg("Reading volume failed!"); + free(efi_menu); + ret = EFI_ABORTED; + goto out; + } + + /* Count the number of directory entries */ + for (;;) { + len = sizeof(struct efi_file_info) + EFICONFIG_FILE_PATH_BUF_SIZE; + ret = EFI_CALL(f->read(f, &len, buf)); + if (ret != EFI_SUCCESS || len == 0) + break; + + count++; + } + + /* allocate array to sort the entry */ + tmp_infos = calloc(count, sizeof(*tmp_infos)); + if (!tmp_infos) { + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + + ret = eficonfig_create_file_entry(efi_menu, count, tmp_infos, + f, buf, file_info); + if (ret != EFI_SUCCESS) + goto err; + + ret = eficonfig_append_quit_entry(efi_menu); + if (ret != EFI_SUCCESS) + goto err; + + ret = eficonfig_process_common(efi_menu, " ** Select File **", + eficonfig_menu_desc, + eficonfig_display_statusline, + eficonfig_print_entry, + eficonfig_choice_entry); +err: + EFI_CALL(f->close(f)); + eficonfig_destroy(efi_menu); + + if (tmp_infos) { + for (i = 0; i < count; i++) + free(tmp_infos[i]); + } + + free(tmp_infos); + + if (ret != EFI_SUCCESS) + break; + } + +out: + free(buf); + + return ret; +} + +/** + * handle_user_input() - handle user input + * + * @buf: pointer to the buffer + * @buf_size: size of the buffer + * @cursor_col: cursor column for user input + * @msg: pointer to the string to display + * Return: status code + */ +static efi_status_t handle_user_input(u16 *buf, int buf_size, + int cursor_col, char *msg) +{ + u16 *tmp; + efi_status_t ret; + + printf(ANSI_CLEAR_CONSOLE + ANSI_CURSOR_POSITION + "%s" + ANSI_CURSOR_POSITION + " Press ENTER to complete, ESC to quit", + 0, 1, msg, 8, 1); + + /* tmp is used to accept user cancel */ + tmp = calloc(1, buf_size * sizeof(u16)); + if (!tmp) + return EFI_OUT_OF_RESOURCES; + + ret = efi_console_get_u16_string(cin, tmp, buf_size, NULL, 4, cursor_col); + if (ret == EFI_SUCCESS) + u16_strcpy(buf, tmp); + + free(tmp); + + /* to stay the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; + + return ret; +} + +/** + * eficonfig_boot_add_enter_description() - handle user input for description + * + * @data: pointer to the internal boot option structure + * Return: status code + */ +static efi_status_t eficonfig_boot_add_enter_description(void *data) +{ + struct eficonfig_boot_option *bo = data; + + return handle_user_input(bo->description, EFICONFIG_DESCRIPTION_MAX, 22, + "\n ** Edit Description **\n" + "\n" + " Enter description: "); +} + +/** + * eficonfig_boot_add_optional_data() - handle user input for optional data + * + * @data: pointer to the internal boot option structure + * Return: status code + */ +static efi_status_t eficonfig_boot_add_optional_data(void *data) +{ + struct eficonfig_boot_option *bo = data; + + return handle_user_input(bo->optional_data, EFICONFIG_OPTIONAL_DATA_MAX, 24, + "\n ** Edit Optional Data **\n" + "\n" + " enter optional data:"); +} + +/** + * eficonfig_boot_add_uri() - handle user input for HTTP Boot URI + * + * @data: pointer to the internal boot option structure + * Return: status code + */ +static efi_status_t eficonfig_boot_add_uri(void *data) +{ + struct eficonfig_select_file_info *file_info = data; + + return handle_user_input(file_info->uri, EFICONFIG_URI_MAX, 24, + "\n ** Edit URI **\n" + "\n" + " enter HTTP Boot URI:"); +} + +/** + * eficonfig_boot_edit_save() - handler to save the boot option + * + * @data: pointer to the internal boot option structure + * Return: status code + */ +static efi_status_t eficonfig_boot_edit_save(void *data) +{ + struct eficonfig_boot_option *bo = data; + + if (u16_strlen(bo->description) == 0) { + eficonfig_print_msg("Boot Description is empty!"); + bo->edit_completed = false; + return EFI_NOT_READY; + } + if (u16_strlen(bo->file_info.current_path) == 0 && + u16_strlen(bo->file_info.uri) == 0) { + eficonfig_print_msg("File is not selected!"); + bo->edit_completed = false; + return EFI_NOT_READY; + } + + bo->edit_completed = true; + + return EFI_SUCCESS; +} + +/** + * eficonfig_process_clear_file_selection() - callback function for "Clear" entry + * + * @data: pointer to the data + * Return: status code + */ +efi_status_t eficonfig_process_clear_file_selection(void *data) +{ + struct eficonfig_select_file_info *file_info = data; + + /* clear the existing file information */ + file_info->current_volume = NULL; + file_info->current_path[0] = u'\0'; + file_info->dp_volume = NULL; + + if (file_info->uri) + file_info->uri[0] = u'\0'; + + return EFI_ABORTED; +} + +static struct eficonfig_item select_boot_file_menu_items[] = { + {"Select File", eficonfig_process_select_file}, + {"Enter URI", eficonfig_boot_add_uri}, + {"Clear", eficonfig_process_clear_file_selection}, + {"Quit", eficonfig_process_quit}, +}; + +static struct eficonfig_item select_file_menu_items[] = { + {"Select File", eficonfig_process_select_file}, + {"Clear", eficonfig_process_clear_file_selection}, + {"Quit", eficonfig_process_quit}, +}; + +/** + * eficonfig_process_show_file_option() - display select file option + * + * @file_info: pointer to the file information structure + * Return: status code + */ +efi_status_t eficonfig_process_show_file_option(void *data) +{ + efi_status_t ret; + unsigned int menu_count; + struct efimenu *efi_menu; + struct eficonfig_item *menu_items; + struct eficonfig_select_file_info *file_info = data; + + menu_items = file_info->uri ? select_boot_file_menu_items : + select_file_menu_items; + + menu_count = file_info->uri ? + ARRAY_SIZE(select_boot_file_menu_items) : + ARRAY_SIZE(select_file_menu_items); + + menu_items[0].data = data; + menu_items[1].data = data; + menu_items[2].data = data; + + efi_menu = eficonfig_create_fixed_menu(menu_items, menu_count); + if (!efi_menu) + return EFI_OUT_OF_RESOURCES; + + ret = eficonfig_process_common(efi_menu, + file_info->uri ? + " ** Update File/URI **" : + " ** Update File **", + eficonfig_menu_desc, + eficonfig_display_statusline, + eficonfig_print_entry, + eficonfig_choice_entry); + if (ret != EFI_SUCCESS) /* User selects "Clear" or "Quit" */ + ret = EFI_NOT_READY; + + eficonfig_destroy(efi_menu); + + return ret; +} + +/** + * eficonfig_process_select_file() - handle user file selection + * + * @data: pointer to the data + * Return: status code + */ +efi_status_t eficonfig_process_select_file(void *data) +{ + size_t len; + efi_status_t ret; + struct list_head *pos, *n; + struct efi_file_handle *root; + struct eficonfig_filepath_info *item; + struct eficonfig_select_file_info *tmp = NULL; + struct eficonfig_select_file_info *file_info = data; + + tmp = calloc(1, sizeof(struct eficonfig_select_file_info)); + if (!tmp) + return EFI_OUT_OF_RESOURCES; + + tmp->current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE); + if (!tmp->current_path) { + free(tmp); + return EFI_OUT_OF_RESOURCES; + } + INIT_LIST_HEAD(&tmp->filepath_list); + + while (!tmp->file_selected) { + tmp->current_volume = NULL; + memset(tmp->current_path, 0, EFICONFIG_FILE_PATH_BUF_SIZE); + + ret = eficonfig_select_volume(tmp); + if (ret != EFI_SUCCESS) + goto out; + + if (!tmp->current_volume) + return EFI_INVALID_PARAMETER; + + ret = EFI_CALL(tmp->current_volume->open_volume(tmp->current_volume, &root)); + if (ret != EFI_SUCCESS) + goto out; + + ret = eficonfig_show_file_selection(tmp, root); + if (ret == EFI_ABORTED) + continue; + if (ret != EFI_SUCCESS) + goto out; + } + +out: + if (ret == EFI_SUCCESS) { + len = u16_strlen(tmp->current_path); + len = (len >= EFICONFIG_FILE_PATH_MAX) ? (EFICONFIG_FILE_PATH_MAX - 1) : len; + memcpy(file_info->current_path, tmp->current_path, len * sizeof(u16)); + file_info->current_path[len] = u'\0'; + file_info->current_volume = tmp->current_volume; + file_info->dp_volume = tmp->dp_volume; + + /* + * File being selected, set the URI string to + * null so that the file gets picked as the + * boot image. + */ + if (file_info->uri) + file_info->uri[0] = u'\0'; + } + + list_for_each_safe(pos, n, &tmp->filepath_list) { + item = list_entry(pos, struct eficonfig_filepath_info, list); + list_del(&item->list); + free(item->name); + free(item); + } + free(tmp->current_path); + free(tmp); + + /* to stay the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; + + return ret; +} + +/** + * eficonfig_set_boot_option() - set boot option + * + * @varname: pointer to variable name + * @dp: pointer to device path + * @label: pointer to label string + * @optional_data: pointer to optional data + * Return: status code + */ +static efi_status_t eficonfig_set_boot_option(u16 *varname, struct efi_device_path *dp, + efi_uintn_t dp_size, u16 *label, char *optional_data) +{ + void *p = NULL; + efi_status_t ret; + efi_uintn_t size; + struct efi_load_option lo; + + lo.file_path = dp; + lo.file_path_length = dp_size; + lo.attributes = LOAD_OPTION_ACTIVE; + lo.optional_data = optional_data; + lo.label = label; + + size = efi_serialize_load_option(&lo, (u8 **)&p); + if (!size) + return EFI_INVALID_PARAMETER; + + ret = efi_set_variable_int(varname, &efi_global_variable_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + size, p, false); + free(p); + + return ret; +} + +/** + * create_boot_option_entry() - create boot option entry + * + * @efi_menu: pointer to the efimenu structure + * @title: pointer to the entry title + * @val: pointer to boot option label + * @func: callback of each entry + * @data: pointer to the data to be passed to each entry callback + * Return: status code + */ +static efi_status_t create_boot_option_entry(struct efimenu *efi_menu, char *title, u16 *val, + eficonfig_entry_func func, void *data) +{ + u32 len; + char *p, *buf; + + len = strlen(title) + 1; + if (val) + len += utf16_utf8_strlen(val); + buf = calloc(1, len); + if (!buf) + return EFI_OUT_OF_RESOURCES; + + strcpy(buf, title); + if (val) { + p = buf + strlen(title); + utf16_utf8_strcpy(&p, val); + } + + return eficonfig_append_menu_entry(efi_menu, buf, func, data); +} + +/** + * prepare_file_selection_entry() - prepare file selection entry + * + * @efi_menu: pointer to the efimenu structure + * @title: pointer to the title string + * @file_info: pointer to the file info + * Return: status code + */ +static efi_status_t prepare_file_selection_entry(struct efimenu *efi_menu, char *title, + struct eficonfig_select_file_info *file_info) +{ + u32 len; + efi_status_t ret; + u16 *file_name = NULL, *p; + efi_handle_t handle; + char *devname; + + /* Check for URI based boot file */ + if (file_info->uri && utf16_utf8_strlen(file_info->uri)) + return create_boot_option_entry(efi_menu, title, file_info->uri, + eficonfig_process_show_file_option, + file_info); + + devname = calloc(1, EFICONFIG_VOLUME_PATH_MAX + 1); + if (!devname) + return EFI_OUT_OF_RESOURCES; + + /* get the device name only when the user already selected the file path */ + handle = efi_dp_find_obj(file_info->dp_volume, NULL, NULL); + if (handle) { + ret = efi_disk_get_device_name(handle, devname, EFICONFIG_VOLUME_PATH_MAX); + if (ret != EFI_SUCCESS) + goto out; + } + + /* + * If the preconfigured volume does not exist in the system, display the text + * converted volume device path instead of U-Boot friendly name(e.g. "usb 0:1"). + */ + if (!handle && file_info->dp_volume) { + u16 *dp_str; + char *q = devname; + + dp_str = efi_dp_str(file_info->dp_volume); + if (dp_str) + utf16_utf8_strncpy(&q, dp_str, EFICONFIG_VOLUME_PATH_MAX); + + efi_free_pool(dp_str); + } + + /* append u'/' to devname, it is just for display purpose. */ + if (file_info->current_path[0] != u'\0' && file_info->current_path[0] != u'/') + strlcat(devname, "/", EFICONFIG_VOLUME_PATH_MAX + 1); + + len = strlen(devname); + len += utf16_utf8_strlen(file_info->current_path) + 1; + file_name = calloc(1, len * sizeof(u16)); + if (!file_name) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + p = file_name; + utf8_utf16_strcpy(&p, devname); + u16_strlcat(file_name, file_info->current_path, len); + ret = create_boot_option_entry(efi_menu, title, file_name, + eficonfig_process_show_file_option, file_info); +out: + free(devname); + free(file_name); + + return ret; +} + +/** + * eficonfig_show_boot_option() - prepare menu entry for editing boot option + * + * Construct the structures to create edit boot option menu + * + * @bo: pointer to the boot option + * @header_str: pointer to the header string + * Return: status code + */ +static efi_status_t eficonfig_show_boot_option(struct eficonfig_boot_option *bo, + char *header_str) +{ + efi_status_t ret; + struct efimenu *efi_menu; + + efi_menu = calloc(1, sizeof(struct efimenu)); + if (!efi_menu) + return EFI_OUT_OF_RESOURCES; + + INIT_LIST_HEAD(&efi_menu->list); + + ret = create_boot_option_entry(efi_menu, "Description: ", bo->description, + eficonfig_boot_add_enter_description, bo); + if (ret != EFI_SUCCESS) + goto out; + + ret = prepare_file_selection_entry(efi_menu, "File: ", &bo->file_info); + if (ret != EFI_SUCCESS) + goto out; + + ret = prepare_file_selection_entry(efi_menu, "Initrd File: ", &bo->initrd_info); + if (ret != EFI_SUCCESS) + goto out; + + ret = prepare_file_selection_entry(efi_menu, "Fdt File: ", &bo->fdt_info); + if (ret != EFI_SUCCESS) + goto out; + + ret = create_boot_option_entry(efi_menu, "Optional Data: ", bo->optional_data, + eficonfig_boot_add_optional_data, bo); + if (ret != EFI_SUCCESS) + goto out; + + ret = create_boot_option_entry(efi_menu, "Save", NULL, + eficonfig_boot_edit_save, bo); + if (ret != EFI_SUCCESS) + goto out; + + ret = create_boot_option_entry(efi_menu, "Quit", NULL, + eficonfig_process_quit, NULL); + if (ret != EFI_SUCCESS) + goto out; + + ret = eficonfig_process_common(efi_menu, header_str, + eficonfig_menu_desc, + eficonfig_display_statusline, + eficonfig_print_entry, + eficonfig_choice_entry); + +out: + eficonfig_destroy(efi_menu); + + return ret; +} + +/** + * fill_dp_uri() - copy the URI string in the device path + * @dp: pointer to the URI device path + * @uri_str: URI string to be copied + * + * Copy the passed URI string to the URI device path. This + * requires utf8_utf16_strcpy() to copy the u16 string to + * the u8 array in the device path structure. + * + * Return: None + */ +static void fill_dp_uri(struct efi_device_path *dp, u16 **uri_str) +{ + u16 *p = *uri_str; + struct efi_device_path_uri *uridp; + + uridp = (struct efi_device_path_uri *)dp; + + utf8_utf16_strcpy(&p, uridp->uri); +} + +/** + * fill_file_info() - fill the file info from efi_device_path structure + * + * @dp: pointer to the device path + * @file_info: pointer to the file info structure + * @device_dp: pointer to the volume device path + */ +static void fill_file_info(struct efi_device_path *dp, + struct eficonfig_select_file_info *file_info, + struct efi_device_path *device_dp) +{ + u16 *file_str, *p; + struct efi_device_path *file_dp = NULL; + + efi_dp_split_file_path(dp, &device_dp, &file_dp); + file_info->dp_volume = device_dp; + + if (file_dp) { + file_str = efi_dp_str(file_dp); + /* + * efi_convert_device_path_to_text() automatically adds u'/' at the + * beginning of file name, remove u'/' before copying to current_path + */ + p = file_str; + if (p[0] == u'/') + p++; + + u16_strcpy(file_info->current_path, p); + efi_free_pool(file_dp); + efi_free_pool(file_str); + } +} + +/** + * eficonfig_edit_boot_option() - prepare boot option structure for editing + * + * Construct the boot option structure and copy the existing value + * + * @varname: pointer to the UEFI variable name + * @bo: pointer to the boot option + * @load_option: pointer to the load option + * @load_option_size: size of the load option + * @header_str: pointer to the header string + * Return : status code + */ +static efi_status_t eficonfig_edit_boot_option(u16 *varname, struct eficonfig_boot_option *bo, + void *load_option, efi_uintn_t load_option_size, + char *header_str) +{ + size_t len; + efi_status_t ret; + char *tmp = NULL, *p; + u16 *current_path = NULL; + struct efi_load_option lo = {0}; + efi_uintn_t dp_size; + struct efi_device_path *dp = NULL; + efi_uintn_t size = load_option_size; + struct efi_device_path *dp_volume = NULL; + struct efi_device_path *uri_dp = NULL; + struct efi_device_path *device_dp = NULL; + struct efi_device_path *initrd_dp = NULL; + struct efi_device_path *fdt_dp = NULL; + struct efi_device_path *initrd_device_dp = NULL; + struct efi_device_path *fdt_device_dp = NULL; + + const struct efi_lo_dp_prefix initrd_prefix = { + .vendor = { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR_PATH, + sizeof(initrd_prefix.vendor), + }, + EFI_INITRD_MEDIA_GUID, + }, + .end = { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(initrd_prefix.end), + } + }; + + const struct efi_lo_dp_prefix fdt_prefix = { + .vendor = { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR_PATH, + sizeof(fdt_prefix.vendor), + }, + EFI_FDT_GUID, + }, + .end = { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(initrd_prefix.end), + } + }; + + bo->file_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE); + if (!bo->file_info.current_path) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + bo->initrd_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE); + if (!bo->initrd_info.current_path) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + bo->fdt_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE); + if (!bo->fdt_info.current_path) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + bo->description = calloc(1, EFICONFIG_DESCRIPTION_MAX * sizeof(u16)); + if (!bo->description) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + bo->optional_data = calloc(1, EFICONFIG_OPTIONAL_DATA_MAX * sizeof(u16)); + if (!bo->optional_data) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + bo->file_info.uri = calloc(1, EFICONFIG_URI_MAX * sizeof(u16)); + if (!bo->file_info.uri) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + /* copy the preset value */ + if (load_option) { + ret = efi_deserialize_load_option(&lo, load_option, &size); + if (ret != EFI_SUCCESS) + goto out; + + if (!lo.label) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + /* truncate the long label string */ + if (u16_strlen(lo.label) >= EFICONFIG_DESCRIPTION_MAX) + lo.label[EFICONFIG_DESCRIPTION_MAX - 1] = u'\0'; + + u16_strcpy(bo->description, lo.label); + + /* EFI image file path is a first instance */ + if (lo.file_path && EFI_DP_TYPE(lo.file_path, MESSAGING_DEVICE, + MSG_URI)) + fill_dp_uri(lo.file_path, &bo->file_info.uri); + else if (lo.file_path) + fill_file_info(lo.file_path, &bo->file_info, device_dp); + + /* Initrd file path (optional) is placed at second instance. */ + initrd_dp = efi_dp_from_lo(&lo, &efi_lf2_initrd_guid); + if (initrd_dp) { + fill_file_info(initrd_dp, &bo->initrd_info, initrd_device_dp); + efi_free_pool(initrd_dp); + } + + /* Fdt file path (optional) is placed as third instance. */ + fdt_dp = efi_dp_from_lo(&lo, &efi_guid_fdt); + if (fdt_dp) { + fill_file_info(fdt_dp, &bo->fdt_info, fdt_device_dp); + efi_free_pool(fdt_dp); + } + + if (size > 0) + memcpy(bo->optional_data, lo.optional_data, size); + } + + while (1) { + ret = eficonfig_show_boot_option(bo, header_str); + if (ret == EFI_SUCCESS && bo->edit_completed) + break; + if (ret == EFI_NOT_READY) + continue; + if (ret != EFI_SUCCESS) + goto out; + } + + if (utf16_utf8_strlen(bo->file_info.uri)) + uri_dp = eficonfig_create_uri_device_path(bo->file_info.uri); + + if (bo->initrd_info.dp_volume) { + dp = eficonfig_create_device_path(bo->initrd_info.dp_volume, + bo->initrd_info.current_path); + if (!dp) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + initrd_dp = efi_dp_concat((const struct efi_device_path *)&initrd_prefix, + dp, 0); + efi_free_pool(dp); + } + + if (bo->fdt_info.dp_volume) { + dp = eficonfig_create_device_path(bo->fdt_info.dp_volume, + bo->fdt_info.current_path); + if (!dp) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + fdt_dp = efi_dp_concat((const struct efi_device_path *)&fdt_prefix, + dp, 0); + efi_free_pool(dp); + } + + dp_volume = bo->file_info.dp_volume; + current_path = bo->file_info.current_path; + dp = uri_dp ? + uri_dp : eficonfig_create_device_path(dp_volume, current_path); + if (!dp) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + ret = efi_load_option_dp_join(&dp, &dp_size, initrd_dp, fdt_dp); + if (ret != EFI_SUCCESS) + goto out; + + if (utf16_utf8_strlen(bo->optional_data)) { + len = utf16_utf8_strlen(bo->optional_data) + 1; + tmp = calloc(1, len); + if (!tmp) + goto out; + p = tmp; + utf16_utf8_strncpy(&p, bo->optional_data, u16_strlen(bo->optional_data)); + } + + ret = eficonfig_set_boot_option(varname, dp, dp_size, bo->description, tmp); +out: + free(tmp); + free(bo->optional_data); + free(bo->description); + free(bo->file_info.uri); + free(bo->file_info.current_path); + free(bo->initrd_info.current_path); + free(bo->fdt_info.current_path); + efi_free_pool(device_dp); + efi_free_pool(initrd_device_dp); + efi_free_pool(initrd_dp); + efi_free_pool(fdt_device_dp); + efi_free_pool(fdt_dp); + efi_free_pool(dp); + + return ret; +} + +/** + * eficonfig_process_add_boot_option() - handler to add boot option + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t eficonfig_process_add_boot_option(void *data) +{ + u16 varname[9]; + efi_status_t ret; + struct eficonfig_boot_option *bo = NULL; + + bo = calloc(1, sizeof(struct eficonfig_boot_option)); + if (!bo) + return EFI_OUT_OF_RESOURCES; + + ret = efi_bootmgr_get_unused_bootoption(varname, sizeof(varname), &bo->boot_index); + if (ret != EFI_SUCCESS) + return ret; + + ret = eficonfig_edit_boot_option(varname, bo, NULL, 0, " ** Add Boot Option ** "); + if (ret != EFI_SUCCESS) + goto out; + + ret = efi_bootmgr_append_bootorder((u16)bo->boot_index); + if (ret != EFI_SUCCESS) + goto out; + +out: + free(bo); + + /* to stay the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_SUCCESS : ret; + + return ret; +} + +/** + * eficonfig_process_boot_selected() - handler to select boot option entry + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t eficonfig_process_boot_selected(void *data) +{ + struct eficonfig_boot_selection_data *info = data; + + if (info) + *info->selected = info->boot_index; + + return EFI_SUCCESS; +} + +/** + * eficonfig_add_boot_selection_entry() - add boot option menu entry + * + * @efi_menu: pointer to store the efimenu structure + * @boot_index: boot option index to be added + * @selected: pointer to store the selected boot option index + * Return: status code + */ +static efi_status_t eficonfig_add_boot_selection_entry(struct efimenu *efi_menu, + unsigned int boot_index, + unsigned int *selected) +{ + char *buf, *p; + efi_status_t ret; + efi_uintn_t size; + void *load_option; + struct efi_load_option lo; + u16 varname[] = u"Boot####"; + struct eficonfig_boot_selection_data *info; + + efi_create_indexed_name(varname, sizeof(varname), "Boot", boot_index); + load_option = efi_get_var(varname, &efi_global_variable_guid, &size); + if (!load_option) + return EFI_SUCCESS; + + ret = efi_deserialize_load_option(&lo, load_option, &size); + if (ret != EFI_SUCCESS) { + log_warning("Invalid load option for %ls\n", varname); + free(load_option); + return ret; + } + + if (size >= sizeof(efi_guid_t) && + !guidcmp(lo.optional_data, &efi_guid_bootmenu_auto_generated)) { + /* + * auto generated entry has GUID in optional_data, + * skip auto generated entry because it will be generated + * again even if it is edited or deleted. + */ + free(load_option); + return EFI_SUCCESS; + } + + info = calloc(1, sizeof(struct eficonfig_boot_selection_data)); + if (!info) { + free(load_option); + return EFI_OUT_OF_RESOURCES; + } + + buf = calloc(1, utf16_utf8_strlen(lo.label) + 1); + if (!buf) { + free(load_option); + free(info); + return EFI_OUT_OF_RESOURCES; + } + p = buf; + utf16_utf8_strcpy(&p, lo.label); + info->boot_index = boot_index; + info->selected = selected; + ret = eficonfig_append_menu_entry(efi_menu, buf, eficonfig_process_boot_selected, info); + if (ret != EFI_SUCCESS) { + free(load_option); + free(info); + return ret; + } + free(load_option); + + return EFI_SUCCESS; +} + +/** + * eficonfig_show_boot_selection() - construct boot option menu entry + * + * @selected: pointer to store the selected boot option index + * Return: status code + */ +static efi_status_t eficonfig_show_boot_selection(unsigned int *selected) +{ + u32 i; + u16 *bootorder; + efi_status_t ret; + u16 *var_name16 = NULL; + efi_uintn_t num, size, buf_size; + struct efimenu *efi_menu; + struct list_head *pos, *n; + struct eficonfig_entry *entry; + + efi_menu = calloc(1, sizeof(struct efimenu)); + if (!efi_menu) + return EFI_OUT_OF_RESOURCES; + + bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size); + + INIT_LIST_HEAD(&efi_menu->list); + num = size / sizeof(u16); + /* list the load option in the order of BootOrder variable */ + for (i = 0; i < num; i++) { + ret = eficonfig_add_boot_selection_entry(efi_menu, bootorder[i], selected); + if (ret != EFI_SUCCESS) + goto out; + + if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1) + break; + } + + /* list the remaining load option not included in the BootOrder */ + buf_size = 128; + var_name16 = malloc(buf_size); + if (!var_name16) + return EFI_OUT_OF_RESOURCES; + + var_name16[0] = 0; + for (;;) { + int index; + efi_guid_t guid; + + ret = efi_next_variable_name(&buf_size, &var_name16, &guid); + if (ret == EFI_NOT_FOUND) + break; + if (ret != EFI_SUCCESS) + goto out; + + if (efi_varname_is_load_option(var_name16, &index)) { + /* If the index is included in the BootOrder, skip it */ + if (efi_search_bootorder(bootorder, num, index, NULL)) + continue; + + ret = eficonfig_add_boot_selection_entry(efi_menu, index, selected); + if (ret != EFI_SUCCESS) + goto out; + } + + if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 1) + break; + } + + ret = eficonfig_append_quit_entry(efi_menu); + if (ret != EFI_SUCCESS) + goto out; + + ret = eficonfig_process_common(efi_menu, " ** Select Boot Option **", + eficonfig_menu_desc, + eficonfig_display_statusline, + eficonfig_print_entry, + eficonfig_choice_entry); +out: + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + free(entry->data); + } + eficonfig_destroy(efi_menu); + + free(var_name16); + + return ret; +} + +/** + * eficonfig_process_edit_boot_option() - handler to edit boot option + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t eficonfig_process_edit_boot_option(void *data) +{ + efi_status_t ret; + efi_uintn_t size; + struct eficonfig_boot_option *bo = NULL; + + while (1) { + unsigned int selected; + void *load_option; + u16 varname[] = u"Boot####"; + + ret = eficonfig_show_boot_selection(&selected); + if (ret != EFI_SUCCESS) + break; + + bo = calloc(1, sizeof(struct eficonfig_boot_option)); + if (!bo) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + bo->boot_index = selected; + efi_create_indexed_name(varname, sizeof(varname), "Boot", selected); + load_option = efi_get_var(varname, &efi_global_variable_guid, &size); + if (!load_option) { + free(bo); + ret = EFI_NOT_FOUND; + goto out; + } + + ret = eficonfig_edit_boot_option(varname, bo, load_option, size, + " ** Edit Boot Option ** "); + + free(load_option); + free(bo); + if (ret != EFI_SUCCESS && ret != EFI_ABORTED) + break; + } +out: + /* to stay the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; + + return ret; +} + +/** + * eficonfig_print_change_boot_order_entry() - print the boot option entry + * + * @data: pointer to the data associated with each menu entry + */ +static void eficonfig_print_change_boot_order_entry(void *data) +{ + struct eficonfig_entry *entry = data; + bool reverse = (entry->efi_menu->active == entry->num); + + if (entry->efi_menu->start > entry->num || entry->efi_menu->end < entry->num) + return; + + printf(ANSI_CURSOR_POSITION ANSI_CLEAR_LINE, + (entry->num - entry->efi_menu->start) + EFICONFIG_MENU_HEADER_ROW_NUM + 1, 7); + + if (reverse) + puts(ANSI_COLOR_REVERSE); + + if (entry->num < entry->efi_menu->count - 2) { + if (((struct eficonfig_boot_order_data *)entry->data)->active) + printf("[*] "); + else + printf("[ ] "); + } + + printf("%s", entry->title); + + if (reverse) + puts(ANSI_COLOR_RESET); +} + +/** + * eficonfig_choice_change_boot_order() - user key input handler + * + * @data: pointer to the menu entry + * Return: key string to identify the selected entry + */ +char *eficonfig_choice_change_boot_order(void *data) +{ + struct cli_ch_state s_cch, *cch = &s_cch; + struct list_head *pos, *n; + struct efimenu *efi_menu = data; + enum bootmenu_key key = BKEY_NONE; + struct eficonfig_entry *entry, *tmp; + + cli_ch_init(cch); + while (1) { + key = bootmenu_loop(NULL, cch); + + switch (key) { + case BKEY_PLUS: + if (efi_menu->active > 0 && + efi_menu->active < efi_menu->count - 2) { + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + if (entry->num == efi_menu->active) + break; + } + tmp = list_entry(pos->prev, struct eficonfig_entry, list); + entry->num--; + tmp->num++; + list_del(&tmp->list); + list_add(&tmp->list, &entry->list); + + eficonfig_menu_up(efi_menu); + } + return NULL; + case BKEY_UP: + if (efi_menu->active > 0) + eficonfig_menu_up(efi_menu); + + return NULL; + case BKEY_MINUS: + if (efi_menu->active < efi_menu->count - 3) { + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + if (entry->num == efi_menu->active) + break; + } + tmp = list_entry(pos->next, struct eficonfig_entry, list); + entry->num++; + tmp->num--; + list_del(&entry->list); + list_add(&entry->list, &tmp->list); + + eficonfig_menu_down(efi_menu); + } + return NULL; + case BKEY_DOWN: + if (efi_menu->active < efi_menu->count - 1) + eficonfig_menu_down(efi_menu); + + return NULL; + case BKEY_SAVE: + /* force to select "Save" entry */ + efi_menu->active = efi_menu->count - 2; + fallthrough; + case BKEY_SELECT: + /* "Save" */ + if (efi_menu->active == efi_menu->count - 2) { + list_for_each_prev_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + if (entry->num == efi_menu->active) + break; + } + return entry->key; + } + /* "Quit" */ + if (efi_menu->active == efi_menu->count - 1) { + entry = list_last_entry(&efi_menu->list, + struct eficonfig_entry, + list); + return entry->key; + } + /* Pressed key is not valid, wait next key press */ + break; + case BKEY_SPACE: + if (efi_menu->active < efi_menu->count - 2) { + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + if (entry->num == efi_menu->active) { + struct eficonfig_boot_order_data *data = entry->data; + + data->active = !data->active; + return NULL; + } + } + } + /* Pressed key is not valid, wait next key press */ + break; + case BKEY_QUIT: + entry = list_last_entry(&efi_menu->list, + struct eficonfig_entry, list); + return entry->key; + default: + /* Pressed key is not valid, wait next key press */ + break; + } + } +} + +/** + * eficonfig_process_save_boot_order() - callback function for "Save" entry + * + * @data: pointer to the data + * Return: status code + */ +static efi_status_t eficonfig_process_save_boot_order(void *data) +{ + u32 count = 0; + efi_status_t ret; + efi_uintn_t size; + struct list_head *pos, *n; + u16 *new_bootorder; + struct efimenu *efi_menu; + struct eficonfig_entry *entry; + struct eficonfig_save_boot_order_data *save_data = data; + + efi_menu = save_data->efi_menu; + + /* + * The change boot order menu always has "Save" and "Quit" entries. + * !(efi_menu->count - 2) means there is no user defined boot option. + */ + if (!(efi_menu->count - 2)) + return EFI_SUCCESS; + + new_bootorder = calloc(1, (efi_menu->count - 2) * sizeof(u16)); + if (!new_bootorder) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + /* create new BootOrder */ + count = 0; + list_for_each_safe(pos, n, &efi_menu->list) { + struct eficonfig_boot_order_data *data; + + entry = list_entry(pos, struct eficonfig_entry, list); + /* exit the loop when iteration reaches "Save" */ + if (!strncmp(entry->title, "Save", strlen("Save"))) + break; + + data = entry->data; + if (data->active) + new_bootorder[count++] = data->boot_index; + } + + size = count * sizeof(u16); + ret = efi_set_variable_int(u"BootOrder", &efi_global_variable_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + size, new_bootorder, false); + + save_data->selected = true; +out: + free(new_bootorder); + + return ret; +} + +/** + * eficonfig_add_change_boot_order_entry() - add boot order entry + * + * @efi_menu: pointer to the efimenu structure + * @boot_index: boot option index to be added + * @active: flag to include the boot option into BootOrder + * Return: status code + */ +static efi_status_t eficonfig_add_change_boot_order_entry(struct efimenu *efi_menu, + u32 boot_index, bool active) +{ + char *title, *p; + efi_status_t ret; + efi_uintn_t size; + void *load_option; + struct efi_load_option lo; + u16 varname[] = u"Boot####"; + struct eficonfig_boot_order_data *data; + + efi_create_indexed_name(varname, sizeof(varname), "Boot", boot_index); + load_option = efi_get_var(varname, &efi_global_variable_guid, &size); + if (!load_option) + return EFI_SUCCESS; + + ret = efi_deserialize_load_option(&lo, load_option, &size); + if (ret != EFI_SUCCESS) + goto out; + + data = calloc(1, sizeof(*data)); + if (!data) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + title = calloc(1, utf16_utf8_strlen(lo.label) + 1); + if (!title) { + free(data); + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + p = title; + utf16_utf8_strcpy(&p, lo.label); + + data->boot_index = boot_index; + data->active = active; + + ret = eficonfig_append_menu_entry(efi_menu, title, NULL, data); + if (ret != EFI_SUCCESS) { + free(data); + free(title); + goto out; + } + +out: + free(load_option); + + return ret; +} + +/** + * eficonfig_create_change_boot_order_entry() - create boot order entry + * + * @efi_menu: pointer to the efimenu structure + * @bootorder: pointer to the BootOrder variable + * @num: number of BootOrder entry + * Return: status code + */ +static efi_status_t eficonfig_create_change_boot_order_entry(struct efimenu *efi_menu, + u16 *bootorder, efi_uintn_t num) +{ + u32 i; + char *title; + efi_status_t ret; + u16 *var_name16 = NULL; + efi_uintn_t size, buf_size; + struct eficonfig_save_boot_order_data *save_data; + + /* list the load option in the order of BootOrder variable */ + for (i = 0; i < num; i++) { + if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2) + break; + + ret = eficonfig_add_change_boot_order_entry(efi_menu, bootorder[i], true); + if (ret != EFI_SUCCESS) + goto out; + } + + /* list the remaining load option not included in the BootOrder */ + buf_size = 128; + var_name16 = malloc(buf_size); + if (!var_name16) + return EFI_OUT_OF_RESOURCES; + + var_name16[0] = 0; + for (;;) { + int index; + efi_guid_t guid; + + if (efi_menu->count >= EFICONFIG_ENTRY_NUM_MAX - 2) + break; + + size = buf_size; + ret = efi_next_variable_name(&buf_size, &var_name16, &guid); + if (ret == EFI_NOT_FOUND) + break; + if (ret != EFI_SUCCESS) + goto out; + + if (efi_varname_is_load_option(var_name16, &index)) { + /* If the index is included in the BootOrder, skip it */ + if (efi_search_bootorder(bootorder, num, index, NULL)) + continue; + + ret = eficonfig_add_change_boot_order_entry(efi_menu, index, false); + if (ret != EFI_SUCCESS) + goto out; + } + } + + /* add "Save" and "Quit" entries */ + title = strdup("Save"); + if (!title) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + save_data = malloc(sizeof(struct eficonfig_save_boot_order_data)); + if (!save_data) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + save_data->efi_menu = efi_menu; + save_data->selected = false; + + ret = eficonfig_append_menu_entry(efi_menu, title, + eficonfig_process_save_boot_order, + save_data); + if (ret != EFI_SUCCESS) + goto out; + + ret = eficonfig_append_quit_entry(efi_menu); + if (ret != EFI_SUCCESS) + goto out; + + efi_menu->active = 0; +out: + free(var_name16); + + return ret; +} + +/** + * eficonfig_process_change_boot_order() - handler to change boot order + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t eficonfig_process_change_boot_order(void *data) +{ + u16 *bootorder; + efi_status_t ret; + efi_uintn_t num, size; + struct list_head *pos, *n; + struct eficonfig_entry *entry; + struct efimenu *efi_menu; + + efi_menu = calloc(1, sizeof(struct efimenu)); + if (!efi_menu) + return EFI_OUT_OF_RESOURCES; + + bootorder = efi_get_var(u"BootOrder", &efi_global_variable_guid, &size); + + INIT_LIST_HEAD(&efi_menu->list); + num = size / sizeof(u16); + ret = eficonfig_create_change_boot_order_entry(efi_menu, bootorder, num); + if (ret != EFI_SUCCESS) + goto out; + + while (1) { + ret = eficonfig_process_common(efi_menu, + " ** Change Boot Order **", + eficonfig_change_boot_order_desc, + eficonfig_display_statusline, + eficonfig_print_change_boot_order_entry, + eficonfig_choice_change_boot_order); + /* exit from the menu if user selects the "Save" entry. */ + if (ret == EFI_SUCCESS && efi_menu->active == (efi_menu->count - 2)) { + list_for_each_prev_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + if (entry->num == efi_menu->active) + break; + } + if (((struct eficonfig_save_boot_order_data *)entry->data)->selected) + break; + } + if (ret != EFI_SUCCESS) + break; + } +out: + free(bootorder); + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + free(entry->data); + } + eficonfig_destroy(efi_menu); + + /* to stay the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; + + return ret; +} + +/** + * eficonfig_process_delete_boot_option() - handler to delete boot option + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t eficonfig_process_delete_boot_option(void *data) +{ + efi_status_t ret; + unsigned int selected; + + while (1) { + ret = eficonfig_show_boot_selection(&selected); + if (ret == EFI_SUCCESS) + ret = efi_bootmgr_delete_boot_option(selected); + + if (ret != EFI_SUCCESS) + break; + } + + /* to stay the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; + + return ret; +} + +/** + * eficonfig_init() - do required initialization for eficonfig command + * + * Return: status code + */ +static efi_status_t eficonfig_init(void) +{ + efi_status_t ret = EFI_SUCCESS; + static bool init; + unsigned long columns, rows; + + if (!init) { + cout = systab.con_out; + cin = systab.con_in; + + cout->query_mode(cout, cout->mode->mode, &columns, &rows); + avail_row = rows - (EFICONFIG_MENU_HEADER_ROW_NUM + + EFICONFIG_MENU_DESC_ROW_NUM); + if (avail_row <= 0) { + eficonfig_print_msg("Console size is too small!"); + return EFI_INVALID_PARAMETER; + } + /* TODO: Should we check the minimum column size? */ + } + + init = true; + + return ret; +} + +static const struct eficonfig_item maintenance_menu_items[] = { + {"Add Boot Option", eficonfig_process_add_boot_option}, + {"Edit Boot Option", eficonfig_process_edit_boot_option}, + {"Change Boot Order", eficonfig_process_change_boot_order}, + {"Delete Boot Option", eficonfig_process_delete_boot_option}, +#if (IS_ENABLED(CONFIG_EFI_SECURE_BOOT) && IS_ENABLED(CONFIG_EFI_MM_COMM_TEE)) + {"Secure Boot Configuration", eficonfig_process_secure_boot_config}, +#endif + {"Quit", eficonfig_process_quit}, +}; + +/** + * do_eficonfig() - execute `eficonfig` command + * + * @cmdtp: table entry describing command + * @flag: bitmap indicating how the command was invoked + * @argc: number of arguments + * @argv: command line arguments + * Return: status code + */ +static int do_eficonfig(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + efi_status_t ret; + struct efimenu *efi_menu; + + if (argc > 1) + return CMD_RET_USAGE; + + ret = efi_init_obj_list(); + if (ret != EFI_SUCCESS) { + log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n", + ret & ~EFI_ERROR_MASK); + + return CMD_RET_FAILURE; + } + + ret = eficonfig_init(); + if (ret != EFI_SUCCESS) + return CMD_RET_FAILURE; + + ret = efi_bootmgr_update_media_device_boot_option(); + if (ret != EFI_SUCCESS) + return ret; + + while (1) { + efi_menu = eficonfig_create_fixed_menu(maintenance_menu_items, + ARRAY_SIZE(maintenance_menu_items)); + if (!efi_menu) + return CMD_RET_FAILURE; + + ret = eficonfig_process_common(efi_menu, + " ** UEFI Maintenance Menu **", + eficonfig_menu_desc, + eficonfig_display_statusline, + eficonfig_print_entry, + eficonfig_choice_entry); + eficonfig_destroy(efi_menu); + + if (ret == EFI_ABORTED) + break; + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + eficonfig, 1, 0, do_eficonfig, + "provide menu-driven UEFI variable maintenance interface", + "" +); diff --git a/cmd/eficonfig_sbkey.c b/cmd/eficonfig_sbkey.c new file mode 100644 index 00000000000..b3325a540f9 --- /dev/null +++ b/cmd/eficonfig_sbkey.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Menu-driven UEFI Secure Boot Key Maintenance + * + * Copyright (c) 2022 Masahisa Kojima, Linaro Limited + */ + +#include <ansi.h> +#include <charset.h> +#include <hexdump.h> +#include <log.h> +#include <malloc.h> +#include <menu.h> +#include <efi_loader.h> +#include <efi_config.h> +#include <efi_variable.h> +#include <crypto/pkcs7_parser.h> + +struct eficonfig_sig_data { + struct efi_signature_list *esl; + struct efi_signature_data *esd; + struct list_head list; + u16 *varname; +}; + +enum efi_sbkey_signature_type { + SIG_TYPE_X509 = 0, + SIG_TYPE_HASH, + SIG_TYPE_CRL, + SIG_TYPE_RSA2048, +}; + +struct eficonfig_sigtype_to_str { + efi_guid_t sig_type; + char *str; + enum efi_sbkey_signature_type type; +}; + +static const struct eficonfig_sigtype_to_str sigtype_to_str[] = { + {EFI_CERT_X509_GUID, "X509", SIG_TYPE_X509}, + {EFI_CERT_SHA256_GUID, "SHA256", SIG_TYPE_HASH}, + {EFI_CERT_X509_SHA256_GUID, "X509_SHA256 CRL", SIG_TYPE_CRL}, + {EFI_CERT_X509_SHA384_GUID, "X509_SHA384 CRL", SIG_TYPE_CRL}, + {EFI_CERT_X509_SHA512_GUID, "X509_SHA512 CRL", SIG_TYPE_CRL}, + /* U-Boot does not support the following signature types */ +/* {EFI_CERT_RSA2048_GUID, "RSA2048", SIG_TYPE_RSA2048}, */ +/* {EFI_CERT_RSA2048_SHA256_GUID, "RSA2048_SHA256", SIG_TYPE_RSA2048}, */ +/* {EFI_CERT_SHA1_GUID, "SHA1", SIG_TYPE_HASH}, */ +/* {EFI_CERT_RSA2048_SHA_GUID, "RSA2048_SHA", SIG_TYPE_RSA2048 }, */ +/* {EFI_CERT_SHA224_GUID, "SHA224", SIG_TYPE_HASH}, */ +/* {EFI_CERT_SHA384_GUID, "SHA384", SIG_TYPE_HASH}, */ +/* {EFI_CERT_SHA512_GUID, "SHA512", SIG_TYPE_HASH}, */ +}; + +/** + * file_have_auth_header() - check file has EFI_VARIABLE_AUTHENTICATION_2 header + * @buf: pointer to file + * @size: file size + * Return: true if file has auth header, false otherwise + */ +static bool file_have_auth_header(void *buf, efi_uintn_t size) +{ + struct efi_variable_authentication_2 *auth = buf; + + if (auth->auth_info.hdr.wCertificateType != WIN_CERT_TYPE_EFI_GUID) + return false; + + if (guidcmp(&auth->auth_info.cert_type, &efi_guid_cert_type_pkcs7)) + return false; + + return true; +} + +/** + * file_is_null_key() - check the file is an authenticated and signed null key + * + * @auth: pointer to the file + * @size: file size + * @null_key: pointer to store the result + * Return: status code + */ +static efi_status_t file_is_null_key(struct efi_variable_authentication_2 *auth, + efi_uintn_t size, bool *null_key) +{ + efi_uintn_t auth_size = + sizeof(auth->time_stamp) + auth->auth_info.hdr.dwLength; + + if (size < auth_size) + return EFI_INVALID_PARAMETER; + + *null_key = (size == auth_size); + + return EFI_SUCCESS; +} + +/** + * eficonfig_process_enroll_key() - enroll key into signature database + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t eficonfig_process_enroll_key(void *data) +{ + u32 attr; + char *buf = NULL; + efi_uintn_t size; + efi_status_t ret; + bool null_key = false; + struct efi_file_handle *f = NULL; + struct efi_device_path *full_dp = NULL; + struct eficonfig_select_file_info file_info; + + file_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE); + if (!file_info.current_path) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + ret = eficonfig_process_select_file(&file_info); + if (ret != EFI_SUCCESS) + goto out; + + full_dp = eficonfig_create_device_path(file_info.dp_volume, file_info.current_path); + if (!full_dp) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + f = efi_file_from_path(full_dp); + if (!f) { + ret = EFI_NOT_FOUND; + goto out; + } + + size = 0; + ret = EFI_CALL(f->getinfo(f, &efi_file_info_guid, &size, NULL)); + if (ret != EFI_BUFFER_TOO_SMALL) + goto out; + + buf = malloc(size); + if (!buf) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + ret = EFI_CALL(f->getinfo(f, &efi_file_info_guid, &size, buf)); + if (ret != EFI_SUCCESS) + goto out; + + size = ((struct efi_file_info *)buf)->file_size; + free(buf); + + if (!size) { + eficonfig_print_msg("ERROR! File is empty."); + ret = EFI_INVALID_PARAMETER; + goto out; + } + + buf = malloc(size); + if (!buf) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + ret = EFI_CALL(f->read(f, &size, buf)); + if (ret != EFI_SUCCESS) { + eficonfig_print_msg("ERROR! Failed to read file."); + goto out; + } + if (!file_have_auth_header(buf, size)) { + eficonfig_print_msg("ERROR! Invalid file format. Only .auth variables is allowed."); + ret = EFI_INVALID_PARAMETER; + goto out; + } + + ret = file_is_null_key((struct efi_variable_authentication_2 *)buf, + size, &null_key); + if (ret != EFI_SUCCESS) { + eficonfig_print_msg("ERROR! Invalid file format."); + goto out; + } + + attr = EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + + /* + * PK can enroll only one certificate. + * The signed null key is used to clear KEK, db and dbx. + * EFI_VARIABLE_APPEND_WRITE attribute must not be set in these cases. + */ + if (u16_strcmp(data, u"PK") && !null_key) { + efi_uintn_t db_size = 0; + + /* check the variable exists. If exists, add APPEND_WRITE attribute */ + ret = efi_get_variable_int(data, efi_auth_var_get_guid(data), NULL, + &db_size, NULL, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) + attr |= EFI_VARIABLE_APPEND_WRITE; + } + + ret = efi_set_variable_int((u16 *)data, efi_auth_var_get_guid((u16 *)data), + attr, size, buf, false); + if (ret != EFI_SUCCESS) + eficonfig_print_msg("ERROR! Failed to update signature database"); + +out: + free(file_info.current_path); + free(buf); + efi_free_pool(full_dp); + if (f) + EFI_CALL(f->close(f)); + + /* return to the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; + + return ret; +} + +/** + * eficonfig_process_show_siglist() - show signature list content + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t eficonfig_process_show_siglist(void *data) +{ + u32 i; + struct eficonfig_sig_data *sg = data; + + puts(ANSI_CURSOR_HIDE); + puts(ANSI_CLEAR_CONSOLE); + printf(ANSI_CURSOR_POSITION, 1, 1); + + printf("\n ** Show Signature Database (%ls) **\n\n" + " Owner GUID:\n" + " %pUL\n", + sg->varname, sg->esd->signature_owner.b); + + for (i = 0; i < ARRAY_SIZE(sigtype_to_str); i++) { + if (!guidcmp(&sg->esl->signature_type, &sigtype_to_str[i].sig_type)) { + printf(" Signature Type:\n" + " %s\n", sigtype_to_str[i].str); + + switch (sigtype_to_str[i].type) { + case SIG_TYPE_X509: + { + struct x509_certificate *cert_tmp; + + cert_tmp = x509_cert_parse(sg->esd->signature_data, + sg->esl->signature_size); + printf(" Subject:\n" + " %s\n" + " Issuer:\n" + " %s\n", + cert_tmp->subject, cert_tmp->issuer); + break; + } + case SIG_TYPE_CRL: + { + u32 hash_size = sg->esl->signature_size - sizeof(efi_guid_t) - + sizeof(struct efi_time); + struct efi_time *time = + (struct efi_time *)((u8 *)sg->esd->signature_data + + hash_size); + + printf(" ToBeSignedHash:\n"); + print_hex_dump(" ", DUMP_PREFIX_NONE, 16, 1, + sg->esd->signature_data, hash_size, false); + printf(" TimeOfRevocation:\n" + " %d-%d-%d %02d:%02d:%02d\n", + time->year, time->month, time->day, + time->hour, time->minute, time->second); + break; + } + case SIG_TYPE_HASH: + { + u32 hash_size = sg->esl->signature_size - sizeof(efi_guid_t); + + printf(" Hash:\n"); + print_hex_dump(" ", DUMP_PREFIX_NONE, 16, 1, + sg->esd->signature_data, hash_size, false); + break; + } + default: + eficonfig_print_msg("ERROR! Unsupported format."); + return EFI_INVALID_PARAMETER; + } + } + } + + while (tstc()) + getchar(); + + printf("\n\n Press any key to continue"); + getchar(); + + return EFI_SUCCESS; +} + +/** + * prepare_signature_list_menu() - create the signature list menu entry + * + * @efimenu: pointer to the efimenu structure + * @varname: pointer to the variable name + * @db: pointer to the variable raw data + * @db_size: variable data size + * @func: callback of each entry + * Return: status code + */ +static efi_status_t prepare_signature_list_menu(struct efimenu *efi_menu, void *varname, + void *db, efi_uintn_t db_size, + eficonfig_entry_func func) +{ + u32 num = 0; + efi_uintn_t size; + struct eficonfig_sig_data *sg; + struct efi_signature_list *esl; + struct efi_signature_data *esd; + efi_status_t ret = EFI_SUCCESS; + + INIT_LIST_HEAD(&efi_menu->list); + + esl = db; + size = db_size; + while (size > 0) { + u32 remain; + + esd = (struct efi_signature_data *)((u8 *)esl + + (sizeof(struct efi_signature_list) + + esl->signature_header_size)); + remain = esl->signature_list_size - sizeof(struct efi_signature_list) - + esl->signature_header_size; + for (; remain > 0; remain -= esl->signature_size) { + char buf[37]; + char *title; + + if (num >= EFICONFIG_ENTRY_NUM_MAX - 1) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + sg = calloc(1, sizeof(struct eficonfig_sig_data)); + if (!sg) { + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + + snprintf(buf, sizeof(buf), "%pUL", &esd->signature_owner); + title = strdup(buf); + if (!title) { + free(sg); + ret = EFI_OUT_OF_RESOURCES; + goto err; + } + + sg->esl = esl; + sg->esd = esd; + sg->varname = varname; + ret = eficonfig_append_menu_entry(efi_menu, title, func, sg); + if (ret != EFI_SUCCESS) { + free(sg); + free(title); + goto err; + } + esd = (struct efi_signature_data *)((u8 *)esd + esl->signature_size); + num++; + } + + size -= esl->signature_list_size; + esl = (struct efi_signature_list *)((u8 *)esl + esl->signature_list_size); + } +out: + ret = eficonfig_append_quit_entry(efi_menu); +err: + return ret; +} + +/** + * enumerate_and_show_signature_database() - enumerate and show the signature database + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t enumerate_and_show_signature_database(void *varname) +{ + void *db; + char buf[50]; + efi_status_t ret; + efi_uintn_t db_size; + struct efimenu *efi_menu; + struct list_head *pos, *n; + struct eficonfig_entry *entry; + + db = efi_get_var(varname, efi_auth_var_get_guid(varname), &db_size); + if (!db) { + eficonfig_print_msg("There is no entry in the signature database."); + return EFI_NOT_FOUND; + } + + efi_menu = calloc(1, sizeof(struct efimenu)); + if (!efi_menu) { + free(db); + return EFI_OUT_OF_RESOURCES; + } + + ret = prepare_signature_list_menu(efi_menu, varname, db, db_size, + eficonfig_process_show_siglist); + if (ret != EFI_SUCCESS) + goto out; + + snprintf(buf, sizeof(buf), " ** Show Signature Database (%ls) **", (u16 *)varname); + ret = eficonfig_process_common(efi_menu, buf, eficonfig_menu_desc, + eficonfig_display_statusline, + eficonfig_print_entry, + eficonfig_choice_entry); +out: + list_for_each_safe(pos, n, &efi_menu->list) { + entry = list_entry(pos, struct eficonfig_entry, list); + free(entry->data); + } + eficonfig_destroy(efi_menu); + free(db); + + return ret; +} + +/** + * eficonfig_process_show_signature_database() - process show signature database + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t eficonfig_process_show_signature_database(void *data) +{ + efi_status_t ret; + + while (1) { + ret = enumerate_and_show_signature_database(data); + if (ret != EFI_SUCCESS && ret != EFI_NOT_READY) + break; + } + + /* return to the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; + + return ret; +} + +static struct eficonfig_item key_config_menu_items[] = { + {"Enroll New Key", eficonfig_process_enroll_key}, + {"Show Signature Database", eficonfig_process_show_signature_database}, + {"Quit", eficonfig_process_quit}, +}; + +/** + * eficonfig_process_set_secure_boot_key() - display the key configuration menu + * + * @data: pointer to the data for each entry + * Return: status code + */ +static efi_status_t eficonfig_process_set_secure_boot_key(void *data) +{ + u32 i; + efi_status_t ret; + char header_str[32]; + struct efimenu *efi_menu; + + for (i = 0; i < ARRAY_SIZE(key_config_menu_items); i++) + key_config_menu_items[i].data = data; + + snprintf(header_str, sizeof(header_str), " ** Configure %ls **", (u16 *)data); + + while (1) { + efi_menu = eficonfig_create_fixed_menu(key_config_menu_items, + ARRAY_SIZE(key_config_menu_items)); + + ret = eficonfig_process_common(efi_menu, header_str, + eficonfig_menu_desc, + eficonfig_display_statusline, + eficonfig_print_entry, + eficonfig_choice_entry); + eficonfig_destroy(efi_menu); + + if (ret == EFI_ABORTED) + break; + } + + /* return to the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; + + return ret; +} + +static const struct eficonfig_item secure_boot_menu_items[] = { + {"PK", eficonfig_process_set_secure_boot_key, u"PK"}, + {"KEK", eficonfig_process_set_secure_boot_key, u"KEK"}, + {"db", eficonfig_process_set_secure_boot_key, u"db"}, + {"dbx", eficonfig_process_set_secure_boot_key, u"dbx"}, + {"Quit", eficonfig_process_quit}, +}; + +/** + * eficonfig_process_secure_boot_config() - display the key list menu + * + * @data: pointer to the data for each entry + * Return: status code + */ +efi_status_t eficonfig_process_secure_boot_config(void *data) +{ + efi_status_t ret; + struct efimenu *efi_menu; + + while (1) { + char header_str[64]; + + snprintf(header_str, sizeof(header_str), + " ** UEFI Secure Boot Key Configuration (SecureBoot : %s) **", + (efi_secure_boot_enabled() ? "ON" : "OFF")); + + efi_menu = eficonfig_create_fixed_menu(secure_boot_menu_items, + ARRAY_SIZE(secure_boot_menu_items)); + if (!efi_menu) { + ret = EFI_OUT_OF_RESOURCES; + break; + } + + ret = eficonfig_process_common(efi_menu, header_str, + eficonfig_menu_desc, + eficonfig_display_statusline, + eficonfig_print_entry, + eficonfig_choice_entry); + + eficonfig_destroy(efi_menu); + + if (ret == EFI_ABORTED) + break; + } + + /* return to the parent menu */ + ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; + + return ret; +} diff --git a/cmd/efidebug.c b/cmd/efidebug.c new file mode 100644 index 00000000000..109496d9e95 --- /dev/null +++ b/cmd/efidebug.c @@ -0,0 +1,1700 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * UEFI Shell-like command + * + * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited + */ + +#include <charset.h> +#include <command.h> +#include <dm/device.h> +#include <efi_device_path.h> +#include <efi_dt_fixup.h> +#include <efi_load_initrd.h> +#include <efi_loader.h> +#include <efi_rng.h> +#include <efi_variable.h> +#include <exports.h> +#include <hexdump.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <net.h> +#include <part.h> +#include <search.h> +#include <linux/ctype.h> +#include <linux/err.h> + +#define BS systab.boottime + +#ifdef CONFIG_EFI_HAVE_CAPSULE_SUPPORT +/** + * do_efi_capsule_update() - process a capsule update + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "capsule update" sub-command. + * process a capsule update. + * + * efidebug capsule update [-v] <capsule address> + */ +static int do_efi_capsule_update(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + struct efi_capsule_header *capsule; + int verbose = 0; + char *endp; + efi_status_t ret; + + if (argc != 2 && argc != 3) + return CMD_RET_USAGE; + + if (argc == 3) { + if (strcmp(argv[1], "-v")) + return CMD_RET_USAGE; + + verbose = 1; + argc--; + argv++; + } + + capsule = (typeof(capsule))hextoul(argv[1], &endp); + if (endp == argv[1]) { + printf("Invalid address: %s", argv[1]); + return CMD_RET_FAILURE; + } + + if (verbose) { + printf("Capsule guid: %pUl\n", &capsule->capsule_guid); + printf("Capsule flags: 0x%x\n", capsule->flags); + printf("Capsule header size: 0x%x\n", capsule->header_size); + printf("Capsule image size: 0x%x\n", + capsule->capsule_image_size); + } + + ret = EFI_CALL(efi_update_capsule(&capsule, 1, 0)); + if (ret) { + printf("Cannot handle a capsule at %p\n", capsule); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +#ifdef CONFIG_EFI_CAPSULE_ON_DISK +static int do_efi_capsule_on_disk_update(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + efi_status_t ret; + + ret = efi_launch_capsules(); + + return ret == EFI_SUCCESS ? CMD_RET_SUCCESS : CMD_RET_FAILURE; +} +#endif + +/** + * do_efi_capsule_show() - show capsule information + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "capsule show" sub-command. + * show capsule information. + * + * efidebug capsule show <capsule address> + */ +static int do_efi_capsule_show(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + struct efi_capsule_header *capsule; + char *endp; + + if (argc != 2) + return CMD_RET_USAGE; + + capsule = (typeof(capsule))hextoul(argv[1], &endp); + if (endp == argv[1]) { + printf("Invalid address: %s", argv[1]); + return CMD_RET_FAILURE; + } + + printf("Capsule guid: %pUl\n", &capsule->capsule_guid); + printf("Capsule flags: 0x%x\n", capsule->flags); + printf("Capsule header size: 0x%x\n", capsule->header_size); + printf("Capsule image size: 0x%x\n", + capsule->capsule_image_size); + + return CMD_RET_SUCCESS; +} + +#ifdef CONFIG_EFI_ESRT + +#define EFI_ESRT_FW_TYPE_NUM 4 +char *efi_fw_type_str[EFI_ESRT_FW_TYPE_NUM] = {"unknown", "system FW", "device FW", + "UEFI driver"}; + +#define EFI_ESRT_UPDATE_STATUS_NUM 9 +char *efi_update_status_str[EFI_ESRT_UPDATE_STATUS_NUM] = {"success", "unsuccessful", + "insufficient resources", "incorrect version", "invalid format", + "auth error", "power event (AC)", "power event (batt)", + "unsatisfied dependencies"}; + +#define EFI_FW_TYPE_STR_GET(idx) (\ +EFI_ESRT_FW_TYPE_NUM > (idx) ? efi_fw_type_str[(idx)] : "error"\ +) + +#define EFI_FW_STATUS_STR_GET(idx) (\ +EFI_ESRT_UPDATE_STATUS_NUM > (idx) ? efi_update_status_str[(idx)] : "error"\ +) + +/** + * do_efi_capsule_esrt() - manage UEFI capsules + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "capsule esrt" sub-command. + * The prints the current ESRT table. + * + * efidebug capsule esrt + */ +static int do_efi_capsule_esrt(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + struct efi_system_resource_table *esrt; + + if (argc != 1) + return CMD_RET_USAGE; + + esrt = efi_get_configuration_table(&efi_esrt_guid); + if (!esrt) { + log_info("ESRT: table not present\n"); + return CMD_RET_SUCCESS; + } + + printf("========================================\n"); + printf("ESRT: fw_resource_count=%d\n", esrt->fw_resource_count); + printf("ESRT: fw_resource_count_max=%d\n", esrt->fw_resource_count_max); + printf("ESRT: fw_resource_version=%lld\n", esrt->fw_resource_version); + + for (int idx = 0; idx < esrt->fw_resource_count; idx++) { + printf("[entry %d]==============================\n", idx); + printf("ESRT: fw_class=%pUL\n", &esrt->entries[idx].fw_class); + printf("ESRT: fw_type=%s\n", EFI_FW_TYPE_STR_GET(esrt->entries[idx].fw_type)); + printf("ESRT: fw_version=%d\n", esrt->entries[idx].fw_version); + printf("ESRT: lowest_supported_fw_version=%d\n", + esrt->entries[idx].lowest_supported_fw_version); + printf("ESRT: capsule_flags=%d\n", + esrt->entries[idx].capsule_flags); + printf("ESRT: last_attempt_version=%d\n", + esrt->entries[idx].last_attempt_version); + printf("ESRT: last_attempt_status=%s\n", + EFI_FW_STATUS_STR_GET(esrt->entries[idx].last_attempt_status)); + } + printf("========================================\n"); + + return CMD_RET_SUCCESS; +} +#endif /* CONFIG_EFI_ESRT */ +/** + * do_efi_capsule_res() - show a capsule update result + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "capsule result" sub-command. + * show a capsule update result. + * If result number is not specified, CapsuleLast will be shown. + * + * efidebug capsule result [<capsule result number>] + */ +static int do_efi_capsule_res(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + int capsule_id; + char *endp; + u16 var_name16[12]; + efi_guid_t guid; + struct efi_capsule_result_variable_header *result = NULL; + efi_uintn_t size; + efi_status_t ret; + + if (argc != 1 && argc != 2) + return CMD_RET_USAGE; + + guid = efi_guid_capsule_report; + if (argc == 1) { + size = sizeof(var_name16); + ret = efi_get_variable_int(u"CapsuleLast", &guid, NULL, + &size, var_name16, NULL); + + if (ret != EFI_SUCCESS) { + if (ret == EFI_NOT_FOUND) + printf("CapsuleLast doesn't exist\n"); + else + printf("Failed to get CapsuleLast\n"); + + return CMD_RET_FAILURE; + } + printf("CapsuleLast is %ls\n", var_name16); + } else { + argc--; + argv++; + + capsule_id = hextoul(argv[0], &endp); + if (capsule_id < 0 || capsule_id > 0xffff) + return CMD_RET_USAGE; + + efi_create_indexed_name(var_name16, sizeof(var_name16), + "Capsule", capsule_id); + } + + size = 0; + ret = efi_get_variable_int(var_name16, &guid, NULL, &size, NULL, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) { + result = malloc(size); + if (!result) + return CMD_RET_FAILURE; + ret = efi_get_variable_int(var_name16, &guid, NULL, &size, + result, NULL); + } + if (ret != EFI_SUCCESS) { + free(result); + printf("Failed to get %ls\n", var_name16); + + return CMD_RET_FAILURE; + } + + printf("Result total size: 0x%x\n", result->variable_total_size); + printf("Capsule guid: %pUl\n", &result->capsule_guid); + printf("Time processed: %04d-%02d-%02d %02d:%02d:%02d\n", + result->capsule_processed.year, result->capsule_processed.month, + result->capsule_processed.day, result->capsule_processed.hour, + result->capsule_processed.minute, + result->capsule_processed.second); + printf("Capsule status: 0x%lx\n", result->capsule_status); + + free(result); + + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl cmd_efidebug_capsule_sub[] = { + U_BOOT_CMD_MKENT(update, CONFIG_SYS_MAXARGS, 1, do_efi_capsule_update, + "", ""), + U_BOOT_CMD_MKENT(show, CONFIG_SYS_MAXARGS, 1, do_efi_capsule_show, + "", ""), +#ifdef CONFIG_EFI_ESRT + U_BOOT_CMD_MKENT(esrt, CONFIG_SYS_MAXARGS, 1, do_efi_capsule_esrt, + "", ""), +#endif +#ifdef CONFIG_EFI_CAPSULE_ON_DISK + U_BOOT_CMD_MKENT(disk-update, 0, 0, do_efi_capsule_on_disk_update, + "", ""), +#endif + U_BOOT_CMD_MKENT(result, CONFIG_SYS_MAXARGS, 1, do_efi_capsule_res, + "", ""), +}; + +/** + * do_efi_capsule() - manage UEFI capsules + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "capsule" sub-command. + */ +static int do_efi_capsule(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + struct cmd_tbl *cp; + + if (argc < 2) + return CMD_RET_USAGE; + + argc--; argv++; + + cp = find_cmd_tbl(argv[0], cmd_efidebug_capsule_sub, + ARRAY_SIZE(cmd_efidebug_capsule_sub)); + if (!cp) + return CMD_RET_USAGE; + + return cp->cmd(cmdtp, flag, argc, argv); +} +#endif /* CONFIG_EFI_HAVE_CAPSULE_SUPPORT */ + +#define EFI_HANDLE_WIDTH ((int)sizeof(efi_handle_t) * 2) + +static const char spc[] = " "; +static const char sep[] = "================"; + +/** + * efi_get_driver_handle_info() - get information of UEFI driver + * + * @handle: Handle of UEFI device + * @driver_name: Driver name + * @image_path: Pointer to text of device path + * Return: 0 on success, -1 on failure + * + * Currently return no useful information as all UEFI drivers are + * built-in.. + */ +static int efi_get_driver_handle_info(efi_handle_t handle, u16 **driver_name, + u16 **image_path) +{ + struct efi_handler *handler; + struct efi_loaded_image *image; + efi_status_t ret; + + /* + * driver name + * TODO: support EFI_COMPONENT_NAME2_PROTOCOL + */ + *driver_name = NULL; + + /* image name */ + ret = efi_search_protocol(handle, &efi_guid_loaded_image, &handler); + if (ret != EFI_SUCCESS) { + *image_path = NULL; + return 0; + } + + image = handler->protocol_interface; + *image_path = efi_dp_str(image->file_path); + + return 0; +} + +/** + * do_efi_show_drivers() - show UEFI drivers + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "drivers" sub-command. + * Show all UEFI drivers and their information. + */ +static int do_efi_show_drivers(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + efi_handle_t *handles; + efi_uintn_t num, i; + u16 *driver_name, *image_path_text; + efi_status_t ret; + + ret = EFI_CALL(efi_locate_handle_buffer( + BY_PROTOCOL, &efi_guid_driver_binding_protocol, + NULL, &num, &handles)); + if (ret != EFI_SUCCESS) + return CMD_RET_FAILURE; + + if (!num) + return CMD_RET_SUCCESS; + + printf("Driver%.*s Name Image Path\n", + EFI_HANDLE_WIDTH - 6, spc); + printf("%.*s ==================== ====================\n", + EFI_HANDLE_WIDTH, sep); + for (i = 0; i < num; i++) { + if (!efi_get_driver_handle_info(handles[i], &driver_name, + &image_path_text)) { + if (image_path_text) + printf("%p %-20ls %ls\n", handles[i], + driver_name, image_path_text); + else + printf("%p %-20ls <built-in>\n", + handles[i], driver_name); + efi_free_pool(driver_name); + efi_free_pool(image_path_text); + } + } + + efi_free_pool(handles); + + return CMD_RET_SUCCESS; +} + +/** + * do_efi_show_handles() - show UEFI handles + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "dh" sub-command. + * Show all UEFI handles and their information, currently all protocols + * added to handle. + */ +static int do_efi_show_handles(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + efi_handle_t *handles; + efi_guid_t **guid; + efi_uintn_t num, count, i, j; + efi_status_t ret; + + ret = EFI_CALL(efi_locate_handle_buffer(ALL_HANDLES, NULL, NULL, + &num, &handles)); + if (ret != EFI_SUCCESS) + return CMD_RET_FAILURE; + + if (!num) + return CMD_RET_SUCCESS; + + for (i = 0; i < num; i++) { + struct efi_handler *handler; + + printf("\n%p", handles[i]); + if (handles[i]->dev) + printf(" (%s)", handles[i]->dev->name); + printf("\n"); + /* Print device path */ + ret = efi_search_protocol(handles[i], &efi_guid_device_path, + &handler); + if (ret == EFI_SUCCESS) + printf(" %pD\n", handler->protocol_interface); + ret = EFI_CALL(BS->protocols_per_handle(handles[i], &guid, + &count)); + /* Print other protocols */ + for (j = 0; j < count; j++) { + if (guidcmp(guid[j], &efi_guid_device_path)) + printf(" %pUs\n", guid[j]); + } + efi_free_pool(guid); + } + + efi_free_pool(handles); + + return CMD_RET_SUCCESS; +} + +/** + * do_efi_show_images() - show UEFI images + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "images" sub-command. + * Show all UEFI loaded images and their information. + */ +static int do_efi_show_images(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + efi_print_image_infos(NULL); + + return CMD_RET_SUCCESS; +} + +/** + * do_efi_show_defaults() - show UEFI default filename and PXE architecture + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "defaults" sub-command. + * Shows the default EFI filename and PXE architecture + */ +static int do_efi_show_defaults(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + printf("Default boot path: EFI\\BOOT\\%s\n", efi_get_basename()); + printf("PXE arch: 0x%02x\n", efi_get_pxe_arch()); + + return CMD_RET_SUCCESS; +} + +static const char * const efi_mem_type_string[] = { + [EFI_RESERVED_MEMORY_TYPE] = "RESERVED", + [EFI_LOADER_CODE] = "LOADER CODE", + [EFI_LOADER_DATA] = "LOADER DATA", + [EFI_BOOT_SERVICES_CODE] = "BOOT CODE", + [EFI_BOOT_SERVICES_DATA] = "BOOT DATA", + [EFI_RUNTIME_SERVICES_CODE] = "RUNTIME CODE", + [EFI_RUNTIME_SERVICES_DATA] = "RUNTIME DATA", + [EFI_CONVENTIONAL_MEMORY] = "CONVENTIONAL", + [EFI_UNUSABLE_MEMORY] = "UNUSABLE MEM", + [EFI_ACPI_RECLAIM_MEMORY] = "ACPI RECLAIM MEM", + [EFI_ACPI_MEMORY_NVS] = "ACPI NVS", + [EFI_MMAP_IO] = "IO", + [EFI_MMAP_IO_PORT] = "IO PORT", + [EFI_PAL_CODE] = "PAL", + [EFI_PERSISTENT_MEMORY_TYPE] = "PERSISTENT", +}; + +static const struct efi_mem_attrs { + const u64 bit; + const char *text; +} efi_mem_attrs[] = { + {EFI_MEMORY_UC, "UC"}, + {EFI_MEMORY_WC, "WC"}, + {EFI_MEMORY_WT, "WT"}, + {EFI_MEMORY_WB, "WB"}, + {EFI_MEMORY_UCE, "UCE"}, + {EFI_MEMORY_WP, "WP"}, + {EFI_MEMORY_RP, "RP"}, + {EFI_MEMORY_XP, "XP"}, + {EFI_MEMORY_NV, "NV"}, + {EFI_MEMORY_MORE_RELIABLE, "REL"}, + {EFI_MEMORY_RO, "RO"}, + {EFI_MEMORY_SP, "SP"}, + {EFI_MEMORY_CPU_CRYPTO, "CRYPT"}, + {EFI_MEMORY_HOT_PLUGGABLE, "HOTPL"}, + {EFI_MEMORY_RUNTIME, "RT"}, +}; + +/** + * print_memory_attributes() - print memory map attributes + * + * @attributes: Attribute value + * + * Print memory map attributes + */ +static void print_memory_attributes(u64 attributes) +{ + int sep, i; + + for (sep = 0, i = 0; i < ARRAY_SIZE(efi_mem_attrs); i++) + if (attributes & efi_mem_attrs[i].bit) { + if (sep) { + putc('|'); + } else { + putc(' '); + sep = 1; + } + puts(efi_mem_attrs[i].text); + } +} + +#define EFI_PHYS_ADDR_WIDTH (int)(sizeof(efi_physical_addr_t) * 2) + +/** + * do_efi_show_memmap() - show UEFI memory map + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "memmap" sub-command. + * Show UEFI memory map. + */ +static int do_efi_show_memmap(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct efi_mem_desc *memmap, *map; + efi_uintn_t map_size; + const char *type; + int i; + efi_status_t ret; + + ret = efi_get_memory_map_alloc(&map_size, &memmap); + if (ret != EFI_SUCCESS) + return CMD_RET_FAILURE; + + printf("Type Start%.*s End%.*s Attributes\n", + EFI_PHYS_ADDR_WIDTH - 5, spc, EFI_PHYS_ADDR_WIDTH - 3, spc); + printf("================ %.*s %.*s ==========\n", + EFI_PHYS_ADDR_WIDTH, sep, EFI_PHYS_ADDR_WIDTH, sep); + /* + * Coverity check: dereferencing null pointer "map." + * This is a false positive as memmap will always be + * populated by allocate_pool() above. + */ + for (i = 0, map = memmap; i < map_size / sizeof(*map); map++, i++) { + if (map->type < ARRAY_SIZE(efi_mem_type_string)) + type = efi_mem_type_string[map->type]; + else + type = "(unknown)"; + + printf("%-16s %.*llx-%.*llx", type, + EFI_PHYS_ADDR_WIDTH, + (u64)map_to_sysmem((void *)(uintptr_t) + map->physical_start), + EFI_PHYS_ADDR_WIDTH, + (u64)map_to_sysmem((void *)(uintptr_t) + (map->physical_start + + map->num_pages * EFI_PAGE_SIZE))); + + print_memory_attributes(map->attribute); + putc('\n'); + } + + efi_free_pool(memmap); + + return CMD_RET_SUCCESS; +} + +/** + * do_efi_show_tables() - show UEFI configuration tables + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "tables" sub-command. + * Show UEFI configuration tables. + */ +static int do_efi_show_tables(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + efi_show_tables(&systab); + + return CMD_RET_SUCCESS; +} + +/** + * enum efi_lo_dp_part - part of device path in load option + */ +enum efi_lo_dp_part { + /** @EFI_LO_DP_PART_BINARY: binary */ + EFI_LO_DP_PART_BINARY, + /** @EFI_LO_DP_PART_INITRD: initial RAM disk */ + EFI_LO_DP_PART_INITRD, + /** @EFI_LP_DP_PART_FDT: device-tree */ + EFI_LP_DP_PART_FDT, +}; + +/** + * create_lo_dp_part() - create a special device path for our Boot### option + * + * @dev: device + * @part: disk partition + * @file: filename + * @shortform: create short form device path + * @type: part of device path to be created + * Return: pointer to the device path or ERR_PTR + */ +static +struct efi_device_path *create_lo_dp_part(const char *dev, const char *part, + const char *file, bool shortform, + enum efi_lo_dp_part type) + +{ + struct efi_device_path *tmp_dp = NULL, *tmp_fp = NULL, *short_fp = NULL; + struct efi_device_path *dp = NULL; + const struct efi_device_path *dp_prefix; + efi_status_t ret; + const struct efi_lo_dp_prefix fdt_dp = { + .vendor = { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR_PATH, + sizeof(fdt_dp.vendor), + }, + EFI_FDT_GUID, + }, + .end = { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(fdt_dp.end), + } + }; + const struct efi_lo_dp_prefix initrd_dp = { + .vendor = { + { + DEVICE_PATH_TYPE_MEDIA_DEVICE, + DEVICE_PATH_SUB_TYPE_VENDOR_PATH, + sizeof(initrd_dp.vendor), + }, + EFI_INITRD_MEDIA_GUID, + }, + .end = { + DEVICE_PATH_TYPE_END, + DEVICE_PATH_SUB_TYPE_END, + sizeof(initrd_dp.end), + } + }; + + switch (type) { + case EFI_LO_DP_PART_INITRD: + dp_prefix = &initrd_dp.vendor.dp; + break; + case EFI_LP_DP_PART_FDT: + dp_prefix = &fdt_dp.vendor.dp; + break; + default: + dp_prefix = NULL; + break; + } + + ret = efi_dp_from_name(dev, part, file, &tmp_dp, &tmp_fp); + if (ret != EFI_SUCCESS) { + printf("Cannot create device path for \"%s %s\"\n", part, file); + goto out; + } + if (shortform) + short_fp = efi_dp_shorten(tmp_fp); + if (!short_fp) + short_fp = tmp_fp; + + dp = efi_dp_concat(dp_prefix, short_fp, 0); + +out: + efi_free_pool(tmp_dp); + efi_free_pool(tmp_fp); + return dp; +} + +/** + * efi_boot_add_uri() - set URI load option + * + * @argc: Number of arguments + * @argv: Argument array + * @var_name16: variable name buffer + * @var_name16_size: variable name buffer size + * @lo: pointer to the load option + * @file_path: buffer to set the generated device path pointer + * @fp_size: file_path size + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + */ +static int efi_boot_add_uri(int argc, char *const argv[], u16 *var_name16, + size_t var_name16_size, struct efi_load_option *lo, + struct efi_device_path **file_path, + efi_uintn_t *fp_size) +{ + int id; + char *pos; + char *endp; + u16 *label; + efi_uintn_t uridp_len; + struct efi_device_path_uri *uridp; + + if (argc < 3 || lo->label) + return CMD_RET_USAGE; + + id = (int)hextoul(argv[1], &endp); + if (*endp != '\0' || id > 0xffff) + return CMD_RET_USAGE; + + label = efi_convert_string(argv[2]); + if (!label) + return CMD_RET_FAILURE; + + if (!wget_validate_uri(argv[3])) { + printf("ERROR: invalid URI\n"); + return CMD_RET_FAILURE; + } + + efi_create_indexed_name(var_name16, var_name16_size, "Boot", id); + lo->label = label; + + uridp_len = sizeof(struct efi_device_path) + strlen(argv[3]) + 1; + uridp = efi_alloc(uridp_len + sizeof(EFI_DP_END)); + if (!uridp) { + log_err("Out of memory\n"); + return CMD_RET_FAILURE; + } + uridp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + uridp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_URI; + uridp->dp.length = uridp_len; + strcpy(uridp->uri, argv[3]); + pos = (char *)uridp + uridp_len; + memcpy(pos, &EFI_DP_END, sizeof(EFI_DP_END)); + + *file_path = &uridp->dp; + *fp_size += uridp_len + sizeof(EFI_DP_END); + + return CMD_RET_SUCCESS; +} + +/** + * do_efi_boot_add() - set UEFI load option + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "boot add" sub-command. Create or change UEFI load option. + * + * efidebug boot add -b <id> <label> <interface> <devnum>[:<part>] <file> + * -i <file> <interface2> <devnum2>[:<part>] <initrd> + * -s '<options>' + */ +static int do_efi_boot_add(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int id; + char *endp; + u16 var_name16[9]; + efi_guid_t guid; + u16 *label; + struct efi_device_path *file_path = NULL; + struct efi_device_path *initrd_dp = NULL; + struct efi_device_path *fdt_dp = NULL; + struct efi_load_option lo; + void *data = NULL; + efi_uintn_t size; + efi_uintn_t fp_size = 0; + efi_status_t ret; + int r = CMD_RET_SUCCESS; + + guid = efi_global_variable_guid; + + /* attributes */ + lo.attributes = LOAD_OPTION_ACTIVE; /* always ACTIVE */ + lo.optional_data = NULL; + lo.label = NULL; + + argc--; + argv++; /* 'add' */ + for (; argc > 0; argc--, argv++) { + int shortform; + + if (*argv[0] != '-' || strlen(argv[0]) != 2) { + r = CMD_RET_USAGE; + goto out; + } + shortform = 0; + switch (argv[0][1]) { + case 'b': + shortform = 1; + /* fallthrough */ + case 'B': + if (argc < 5 || lo.label) { + r = CMD_RET_USAGE; + goto out; + } + id = (int)hextoul(argv[1], &endp); + if (*endp != '\0' || id > 0xffff) + return CMD_RET_USAGE; + + efi_create_indexed_name(var_name16, sizeof(var_name16), + "Boot", id); + + /* label */ + label = efi_convert_string(argv[2]); + if (!label) + return CMD_RET_FAILURE; + lo.label = label; /* label will be changed below */ + + /* file path */ + file_path = create_lo_dp_part(argv[3], argv[4], argv[5], + shortform, + EFI_LO_DP_PART_BINARY); + argc -= 5; + argv += 5; + break; + case 'd': + shortform = 1; + fallthrough; + case 'D': + if (argc < 3 || fdt_dp) { + r = CMD_RET_USAGE; + goto out; + } + + fdt_dp = create_lo_dp_part(argv[1], argv[2], argv[3], + shortform, + EFI_LP_DP_PART_FDT); + if (!fdt_dp) { + printf("Cannot add a device-tree\n"); + r = CMD_RET_FAILURE; + goto out; + } + argc -= 3; + argv += 3; + break; + case 'i': + shortform = 1; + /* fallthrough */ + case 'I': + if (argc < 3 || initrd_dp) { + r = CMD_RET_USAGE; + goto out; + } + + initrd_dp = create_lo_dp_part(argv[1], argv[2], argv[3], + shortform, + EFI_LO_DP_PART_INITRD); + if (!initrd_dp) { + printf("Cannot add an initrd\n"); + r = CMD_RET_FAILURE; + goto out; + } + argc -= 3; + argv += 3; + break; + case 's': + if (argc < 1 || lo.optional_data) { + r = CMD_RET_USAGE; + goto out; + } + lo.optional_data = (const u8 *)argv[1]; + argc -= 1; + argv += 1; + break; + case 'u': + if (IS_ENABLED(CONFIG_EFI_HTTP_BOOT)) { + r = efi_boot_add_uri(argc, argv, var_name16, + sizeof(var_name16), &lo, + &file_path, &fp_size); + if (r != CMD_RET_SUCCESS) + goto out; + argc -= 3; + argv += 3; + } else{ + r = CMD_RET_USAGE; + goto out; + } + break; + default: + r = CMD_RET_USAGE; + goto out; + } + } + + if (!file_path) { + printf("Missing binary\n"); + r = CMD_RET_USAGE; + goto out; + } + + ret = efi_load_option_dp_join(&file_path, &fp_size, initrd_dp, fdt_dp); + if (ret != EFI_SUCCESS) { + printf("Cannot create final device path\n"); + r = CMD_RET_FAILURE; + goto out; + } + + lo.file_path = file_path; + lo.file_path_length = fp_size; + + size = efi_serialize_load_option(&lo, (u8 **)&data); + if (!size) { + r = CMD_RET_FAILURE; + goto out; + } + + ret = efi_set_variable_int(var_name16, &guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + size, data, false); + if (ret != EFI_SUCCESS) { + printf("Cannot set %ls\n", var_name16); + r = CMD_RET_FAILURE; + } + +out: + free(data); + efi_free_pool(initrd_dp); + efi_free_pool(fdt_dp); + efi_free_pool(file_path); + free(lo.label); + + return r; +} + +/** + * do_efi_boot_rm() - delete UEFI load options + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "boot rm" sub-command. + * Delete UEFI load options. + * + * efidebug boot rm <id> ... + */ +static int do_efi_boot_rm(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + efi_guid_t guid; + int id, i; + char *endp; + u16 var_name16[9]; + efi_status_t ret; + + if (argc == 1) + return CMD_RET_USAGE; + + guid = efi_global_variable_guid; + for (i = 1; i < argc; i++, argv++) { + id = (int)hextoul(argv[1], &endp); + if (*endp != '\0' || id > 0xffff) + return CMD_RET_FAILURE; + + efi_create_indexed_name(var_name16, sizeof(var_name16), + "Boot", id); + ret = efi_set_variable_int(var_name16, &guid, 0, 0, NULL, + false); + if (ret) { + printf("Cannot remove %ls\n", var_name16); + return CMD_RET_FAILURE; + } + } + + return CMD_RET_SUCCESS; +} + +/** + * show_efi_boot_opt_data() - dump UEFI load option + * + * @varname16: variable name + * @data: value of UEFI load option variable + * @size: size of the boot option + * + * Decode the value of UEFI load option variable and print information. + */ +static void show_efi_boot_opt_data(u16 *varname16, void *data, size_t *size) +{ + struct efi_device_path *fdt_path; + struct efi_device_path *initrd_path; + struct efi_load_option lo; + efi_status_t ret; + + ret = efi_deserialize_load_option(&lo, data, size); + if (ret != EFI_SUCCESS) { + printf("%ls: invalid load option\n", varname16); + return; + } + + printf("%ls:\nattributes: %c%c%c (0x%08x)\n", + varname16, + /* ACTIVE */ + lo.attributes & LOAD_OPTION_ACTIVE ? 'A' : '-', + /* FORCE RECONNECT */ + lo.attributes & LOAD_OPTION_FORCE_RECONNECT ? 'R' : '-', + /* HIDDEN */ + lo.attributes & LOAD_OPTION_HIDDEN ? 'H' : '-', + lo.attributes); + printf(" label: %ls\n", lo.label); + + printf(" file_path: %pD\n", lo.file_path); + + initrd_path = efi_dp_from_lo(&lo, &efi_lf2_initrd_guid); + if (initrd_path) { + printf(" initrd_path: %pD\n", initrd_path); + efi_free_pool(initrd_path); + } + + fdt_path = efi_dp_from_lo(&lo, &efi_guid_fdt); + if (fdt_path) { + printf(" device-tree path: %pD\n", fdt_path); + efi_free_pool(fdt_path); + } + + printf(" data:\n"); + print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1, + lo.optional_data, *size, true); +} + +/** + * show_efi_boot_opt() - dump UEFI load option + * + * @varname16: variable name + * + * Dump information defined by UEFI load option. + */ +static void show_efi_boot_opt(u16 *varname16) +{ + void *data; + efi_uintn_t size; + efi_status_t ret; + + size = 0; + ret = efi_get_variable_int(varname16, &efi_global_variable_guid, + NULL, &size, NULL, NULL); + if (ret == EFI_BUFFER_TOO_SMALL) { + data = malloc(size); + if (!data) { + printf("ERROR: Out of memory\n"); + return; + } + ret = efi_get_variable_int(varname16, &efi_global_variable_guid, + NULL, &size, data, NULL); + if (ret == EFI_SUCCESS) + show_efi_boot_opt_data(varname16, data, &size); + free(data); + } +} + +/** + * do_efi_boot_dump() - dump all UEFI load options + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "boot dump" sub-command. + * Dump information of all UEFI load options defined. + * + * efidebug boot dump + */ +static int do_efi_boot_dump(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + u16 *var_name16, *p; + efi_uintn_t buf_size, size; + efi_guid_t guid; + efi_status_t ret; + + if (argc > 1) + return CMD_RET_USAGE; + + buf_size = 128; + var_name16 = malloc(buf_size); + if (!var_name16) + return CMD_RET_FAILURE; + + var_name16[0] = 0; + for (;;) { + size = buf_size; + ret = efi_get_next_variable_name_int(&size, var_name16, &guid); + if (ret == EFI_NOT_FOUND) + break; + if (ret == EFI_BUFFER_TOO_SMALL) { + buf_size = size; + p = realloc(var_name16, buf_size); + if (!p) { + free(var_name16); + return CMD_RET_FAILURE; + } + var_name16 = p; + ret = efi_get_next_variable_name_int(&size, var_name16, + &guid); + } + if (ret != EFI_SUCCESS) { + free(var_name16); + return CMD_RET_FAILURE; + } + + if (efi_varname_is_load_option(var_name16, NULL)) + show_efi_boot_opt(var_name16); + } + + free(var_name16); + + return CMD_RET_SUCCESS; +} + +/** + * show_efi_boot_order() - show order of UEFI load options + * + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Show order of UEFI load options defined by BootOrder variable. + */ +static int show_efi_boot_order(void) +{ + u16 *bootorder; + efi_uintn_t size; + int num, i; + u16 var_name16[9]; + void *data; + struct efi_load_option lo; + efi_status_t ret; + + size = 0; + ret = efi_get_variable_int(u"BootOrder", &efi_global_variable_guid, + NULL, &size, NULL, NULL); + if (ret != EFI_BUFFER_TOO_SMALL) { + if (ret == EFI_NOT_FOUND) { + printf("BootOrder not defined\n"); + return CMD_RET_SUCCESS; + } else { + return CMD_RET_FAILURE; + } + } + bootorder = malloc(size); + if (!bootorder) { + printf("ERROR: Out of memory\n"); + return CMD_RET_FAILURE; + } + ret = efi_get_variable_int(u"BootOrder", &efi_global_variable_guid, + NULL, &size, bootorder, NULL); + if (ret != EFI_SUCCESS) { + ret = CMD_RET_FAILURE; + goto out; + } + + num = size / sizeof(u16); + for (i = 0; i < num; i++) { + efi_create_indexed_name(var_name16, sizeof(var_name16), + "Boot", bootorder[i]); + + size = 0; + ret = efi_get_variable_int(var_name16, + &efi_global_variable_guid, NULL, + &size, NULL, NULL); + if (ret != EFI_BUFFER_TOO_SMALL) { + printf("%2d: %ls: (not defined)\n", i + 1, var_name16); + continue; + } + + data = malloc(size); + if (!data) { + ret = CMD_RET_FAILURE; + goto out; + } + ret = efi_get_variable_int(var_name16, + &efi_global_variable_guid, NULL, + &size, data, NULL); + if (ret != EFI_SUCCESS) { + free(data); + ret = CMD_RET_FAILURE; + goto out; + } + + ret = efi_deserialize_load_option(&lo, data, &size); + if (ret != EFI_SUCCESS) { + printf("%ls: invalid load option\n", var_name16); + ret = CMD_RET_FAILURE; + goto out; + } + + printf("%2d: %ls: %ls\n", i + 1, var_name16, lo.label); + + free(data); + } +out: + free(bootorder); + + return ret; +} + +/** + * do_efi_boot_next() - manage UEFI BootNext variable + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "boot next" sub-command. + * Set BootNext variable. + * + * efidebug boot next <id> + */ +static int do_efi_boot_next(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + u16 bootnext; + efi_uintn_t size; + char *endp; + efi_guid_t guid; + efi_status_t ret; + int r = CMD_RET_SUCCESS; + + if (argc != 2) + return CMD_RET_USAGE; + + bootnext = (u16)hextoul(argv[1], &endp); + if (*endp) { + printf("invalid value: %s\n", argv[1]); + r = CMD_RET_FAILURE; + goto out; + } + + guid = efi_global_variable_guid; + size = sizeof(u16); + ret = efi_set_variable_int(u"BootNext", &guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + size, &bootnext, false); + if (ret != EFI_SUCCESS) { + printf("Cannot set BootNext\n"); + r = CMD_RET_FAILURE; + } +out: + return r; +} + +/** + * do_efi_boot_order() - manage UEFI BootOrder variable + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "boot order" sub-command. + * Show order of UEFI load options, or change it in BootOrder variable. + * + * efidebug boot order [<id> ...] + */ +static int do_efi_boot_order(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + u16 *bootorder = NULL; + efi_uintn_t size; + int id, i; + char *endp; + efi_guid_t guid; + efi_status_t ret; + int r = CMD_RET_SUCCESS; + + if (argc == 1) + return show_efi_boot_order(); + + argc--; + argv++; + + size = argc * sizeof(u16); + bootorder = malloc(size); + if (!bootorder) + return CMD_RET_FAILURE; + + for (i = 0; i < argc; i++) { + id = (int)hextoul(argv[i], &endp); + if (*endp != '\0' || id > 0xffff) { + printf("invalid value: %s\n", argv[i]); + r = CMD_RET_FAILURE; + goto out; + } + + bootorder[i] = (u16)id; + } + + guid = efi_global_variable_guid; + ret = efi_set_variable_int(u"BootOrder", &guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + size, bootorder, true); + if (ret != EFI_SUCCESS) { + printf("Cannot set BootOrder\n"); + r = CMD_RET_FAILURE; + } +out: + free(bootorder); + + return r; +} + +static struct cmd_tbl cmd_efidebug_boot_sub[] = { + U_BOOT_CMD_MKENT(add, CONFIG_SYS_MAXARGS, 1, do_efi_boot_add, "", ""), + U_BOOT_CMD_MKENT(rm, CONFIG_SYS_MAXARGS, 1, do_efi_boot_rm, "", ""), + U_BOOT_CMD_MKENT(dump, CONFIG_SYS_MAXARGS, 1, do_efi_boot_dump, "", ""), + U_BOOT_CMD_MKENT(next, CONFIG_SYS_MAXARGS, 1, do_efi_boot_next, "", ""), + U_BOOT_CMD_MKENT(order, CONFIG_SYS_MAXARGS, 1, do_efi_boot_order, + "", ""), +}; + +/** + * do_efi_boot_opt() - manage UEFI load options + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "boot" sub-command. + */ +static int do_efi_boot_opt(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct cmd_tbl *cp; + + if (argc < 2) + return CMD_RET_USAGE; + + argc--; argv++; + + cp = find_cmd_tbl(argv[0], cmd_efidebug_boot_sub, + ARRAY_SIZE(cmd_efidebug_boot_sub)); + if (!cp) + return CMD_RET_USAGE; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +/** + * do_efi_test_bootmgr() - run simple bootmgr for test + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "test bootmgr" sub-command. + * Run simple bootmgr for test. + * + * efidebug test bootmgr + */ +static __maybe_unused int do_efi_test_bootmgr(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + efi_handle_t image; + efi_uintn_t exit_data_size = 0; + u16 *exit_data = NULL; + efi_status_t ret; + void *load_options = NULL; + + ret = efi_bootmgr_load(&image, &load_options); + printf("efi_bootmgr_load() returned: %ld\n", ret & ~EFI_ERROR_MASK); + if (ret != EFI_SUCCESS) + return CMD_RET_SUCCESS; + + /* We call efi_start_image() even if error for test purpose. */ + ret = EFI_CALL(efi_start_image(image, &exit_data_size, &exit_data)); + printf("efi_start_image() returned: %ld\n", ret & ~EFI_ERROR_MASK); + if (ret && exit_data) + efi_free_pool(exit_data); + + free(load_options); + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl cmd_efidebug_test_sub[] = { +#ifdef CONFIG_EFI_BOOTMGR + U_BOOT_CMD_MKENT(bootmgr, CONFIG_SYS_MAXARGS, 1, do_efi_test_bootmgr, + "", ""), +#endif +}; + +/** + * do_efi_test() - manage UEFI load options + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + * + * Implement efidebug "test" sub-command. + */ +static int do_efi_test(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + struct cmd_tbl *cp; + + if (argc < 2) + return CMD_RET_USAGE; + + argc--; argv++; + + cp = find_cmd_tbl(argv[0], cmd_efidebug_test_sub, + ARRAY_SIZE(cmd_efidebug_test_sub)); + if (!cp) + return CMD_RET_USAGE; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +/** + * do_efi_query_info() - QueryVariableInfo EFI service + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_FAILURE on failure + * + * Implement efidebug "test" sub-command. + */ + +static int do_efi_query_info(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + efi_status_t ret; + u32 attr = 0; + u64 max_variable_storage_size; + u64 remain_variable_storage_size; + u64 max_variable_size; + int i; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-bs")) + attr |= EFI_VARIABLE_BOOTSERVICE_ACCESS; + else if (!strcmp(argv[i], "-rt")) + attr |= EFI_VARIABLE_RUNTIME_ACCESS; + else if (!strcmp(argv[i], "-nv")) + attr |= EFI_VARIABLE_NON_VOLATILE; + else if (!strcmp(argv[i], "-at")) + attr |= + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + } + + ret = efi_query_variable_info_int(attr, &max_variable_storage_size, + &remain_variable_storage_size, + &max_variable_size); + if (ret != EFI_SUCCESS) { + printf("Error: Cannot query UEFI variables, r = %lu\n", + ret & ~EFI_ERROR_MASK); + return CMD_RET_FAILURE; + } + + printf("Max storage size %llu\n", max_variable_storage_size); + printf("Remaining storage size %llu\n", remain_variable_storage_size); + printf("Max variable size %llu\n", max_variable_size); + + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl cmd_efidebug_sub[] = { + U_BOOT_CMD_MKENT(boot, CONFIG_SYS_MAXARGS, 1, do_efi_boot_opt, "", ""), +#ifdef CONFIG_EFI_HAVE_CAPSULE_SUPPORT + U_BOOT_CMD_MKENT(capsule, CONFIG_SYS_MAXARGS, 1, do_efi_capsule, + "", ""), +#endif + U_BOOT_CMD_MKENT(drivers, CONFIG_SYS_MAXARGS, 1, do_efi_show_drivers, + "", ""), + U_BOOT_CMD_MKENT(dh, CONFIG_SYS_MAXARGS, 1, do_efi_show_handles, + "", ""), + U_BOOT_CMD_MKENT(defaults, CONFIG_SYS_MAXARGS, 1, do_efi_show_defaults, + "", ""), + U_BOOT_CMD_MKENT(images, CONFIG_SYS_MAXARGS, 1, do_efi_show_images, + "", ""), + U_BOOT_CMD_MKENT(memmap, CONFIG_SYS_MAXARGS, 1, do_efi_show_memmap, + "", ""), + U_BOOT_CMD_MKENT(tables, CONFIG_SYS_MAXARGS, 1, do_efi_show_tables, + "", ""), + U_BOOT_CMD_MKENT(test, CONFIG_SYS_MAXARGS, 1, do_efi_test, + "", ""), + U_BOOT_CMD_MKENT(query, CONFIG_SYS_MAXARGS, 1, do_efi_query_info, + "", ""), +}; + +/** + * do_efidebug() - display and configure UEFI environment + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + * + * Implement efidebug command which allows us to display and + * configure UEFI environment. + */ +static int do_efidebug(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct cmd_tbl *cp; + efi_status_t r; + + if (argc < 2) + return CMD_RET_USAGE; + + argc--; argv++; + + /* Initialize UEFI drivers */ + r = efi_init_obj_list(); + if (r != EFI_SUCCESS) { + printf("Error: Cannot initialize UEFI sub-system, r = %lu\n", + r & ~EFI_ERROR_MASK); + return CMD_RET_FAILURE; + } + + cp = find_cmd_tbl(argv[0], cmd_efidebug_sub, + ARRAY_SIZE(cmd_efidebug_sub)); + if (!cp) + return CMD_RET_USAGE; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_LONGHELP(efidebug, + " - UEFI Shell-like interface to configure UEFI environment\n" + "\n" + "efidebug boot add - set UEFI BootXXXX variable\n" + " -b|-B <bootid> <label> <interface> <devnum>[:<part>] <file path>\n" + " -d|-D <interface> <devnum>[:<part>] <device-tree file path>\n" + " -i|-I <interface> <devnum>[:<part>] <initrd file path>\n" + " (-b, -d, -i for short form device path)\n" +#if (IS_ENABLED(CONFIG_EFI_HTTP_BOOT)) + " -u <bootid> <label> <uri>\n" +#endif + " -s '<optional data>'\n" + "efidebug boot rm <bootid#1> [<bootid#2> [<bootid#3> [...]]]\n" + " - delete UEFI BootXXXX variables\n" + "efidebug boot dump\n" + " - dump all UEFI BootXXXX variables\n" + "efidebug boot next <bootid>\n" + " - set UEFI BootNext variable\n" + "efidebug boot order [<bootid#1> [<bootid#2> [<bootid#3> [...]]]]\n" + " - set/show UEFI boot order\n" + "\n" +#ifdef CONFIG_EFI_HAVE_CAPSULE_SUPPORT + "efidebug capsule update [-v] <capsule address>\n" + " - process a capsule\n" + "efidebug capsule disk-update\n" + " - update a capsule from disk\n" + "efidebug capsule show <capsule address>\n" + " - show capsule information\n" + "efidebug capsule result [<capsule result var>]\n" + " - show a capsule update result\n" +#ifdef CONFIG_EFI_ESRT + "efidebug capsule esrt\n" + " - print the ESRT\n" +#endif + "\n" +#endif + "efidebug drivers\n" + " - show UEFI drivers\n" + "efidebug dh\n" + " - show UEFI handles\n" + "efidebug defaults\n" + " - show default EFI filename and PXE architecture\n" + "efidebug images\n" + " - show loaded images\n" + "efidebug memmap\n" + " - show UEFI memory map\n" + "efidebug tables\n" + " - show UEFI configuration tables\n" +#ifdef CONFIG_EFI_BOOTMGR + "efidebug test bootmgr\n" + " - run simple bootmgr for test\n" +#endif + "efidebug query [-nv][-bs][-rt][-at]\n" + " - show size of UEFI variables store\n"); + +U_BOOT_CMD( + efidebug, CONFIG_SYS_MAXARGS, 0, do_efidebug, + "Configure UEFI environment", + efidebug_help_text +); diff --git a/cmd/elf.c b/cmd/elf.c new file mode 100644 index 00000000000..53ec193aaa6 --- /dev/null +++ b/cmd/elf.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2001 William L. Pitts + * All rights reserved. + */ + +#include <command.h> +#include <cpu_func.h> +#include <elf.h> +#include <env.h> +#include <image.h> +#include <log.h> +#ifdef CONFIG_CMD_ELF_BOOTVX +#include <net.h> +#include <vxworks.h> +#endif +#ifdef CONFIG_X86 +#include <vesa.h> +#include <asm/cache.h> +#include <asm/e820.h> +#include <linux/linkage.h> +#endif + +#define BOOTLINE_BUF_LEN 128 + +/* Interpreter command to boot an arbitrary ELF image from memory */ +int do_bootelf(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ +#if CONFIG_IS_ENABLED(CMD_ELF_FDT_SETUP) + struct bootm_headers img = {0}; + unsigned long fdt_addr = 0; /* Address of the FDT */ +#endif + unsigned long addr; /* Address of the ELF image */ + unsigned long rc; /* Return value from user code */ + int rcode = CMD_RET_SUCCESS; + Bootelf_flags flags = {0}; + + /* Consume 'bootelf' */ + argc--; argv++; + + /* Check for [-p|-s] flag. */ + if (argc >= 1 && (argv[0][0] == '-' && \ + (argv[0][1] == 'p' || argv[0][1] == 's'))) { + if (argv[0][1] == 'p') + flags.phdr = 1; + log_debug("Using ELF header format %s\n", + flags.phdr ? "phdr" : "shdr"); + /* Consume flag. */ + argc--; argv++; + } + +#if CONFIG_IS_ENABLED(CMD_ELF_FDT_SETUP) + /* Check for [-d fdt_addr_r] option. */ + if ((argc >= 2) && (argv[0][0] == '-') && (argv[0][1] == 'd')) { + if (strict_strtoul(argv[1], 16, &fdt_addr) != 0) + return CMD_RET_USAGE; + /* Consume option. */ + argc -= 2; + argv += 2; + } +#endif + + /* Check for address. */ + if (argc >= 1 && strict_strtoul(argv[0], 16, &addr) != -EINVAL) { + /* Consume address */ + argc--; argv++; + } else + addr = image_load_addr; + +#if CONFIG_IS_ENABLED(CMD_ELF_FDT_SETUP) + if (fdt_addr) { + log_debug("Setting up FDT at 0x%08lx ...\n", fdt_addr); + flush(); + + fdt_set_totalsize((void *)fdt_addr, + fdt_totalsize(fdt_addr) + CONFIG_SYS_FDT_PAD); + if (image_setup_libfdt(&img, (void *)fdt_addr, false)) + return 1; + } +#endif + + if (env_get_autostart()) { + flags.autostart = 1; + log_debug("Starting application at 0x%08lx ...\n", addr); + flush(); + } + + /* + * pass address parameter as argv[0] (aka command name), + * and all remaining arguments + */ + rc = bootelf(addr, flags, argc, argv); + if (rc != 0) + rcode = CMD_RET_FAILURE; + + if (flags.autostart) + { + if (ENOEXEC == errno) + log_err("Invalid ELF image\n"); + else + log_debug("## Application terminated, rc = 0x%lx\n", rc); + } + + return rcode; +} + +#ifdef CONFIG_CMD_ELF_BOOTVX +/* + * Interpreter command to boot VxWorks from a memory image. The image can + * be either an ELF image or a raw binary. Will attempt to setup the + * bootline and other parameters correctly. + */ +int do_bootvx(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + unsigned long addr; /* Address of image */ + unsigned long bootaddr = 0; /* Address to put the bootline */ + char *bootline; /* Text of the bootline */ + char *tmp; /* Temporary char pointer */ + char build_buf[BOOTLINE_BUF_LEN]; /* Buffer for building the bootline */ + int ptr = 0; +#ifdef CONFIG_X86 + ulong base; + struct e820_info *info; + struct e820_entry *data; + struct efi_gop_info *gop; + struct vesa_mode_info *vesa = &mode_info.vesa; +#endif + + /* + * Check the loadaddr variable. + * If we don't know where the image is then we're done. + */ + if (argc < 2) + addr = image_load_addr; + else + addr = hextoul(argv[1], NULL); + +#if defined(CONFIG_CMD_NET) && !defined(CONFIG_NET_LWIP) + /* + * Check to see if we need to tftp the image ourselves + * before starting + */ + if ((argc == 2) && (strcmp(argv[1], "tftp") == 0)) { + if (net_loop(TFTPGET) <= 0) + return 1; + printf("Automatic boot of VxWorks image at address 0x%08lx ...\n", + addr); + } +#endif + + /* + * This should equate to + * NV_RAM_ADRS + NV_BOOT_OFFSET + NV_ENET_OFFSET + * from the VxWorks BSP header files. + * This will vary from board to board + */ +#if defined(CONFIG_SYS_VXWORKS_MAC_PTR) + tmp = (char *)CONFIG_SYS_VXWORKS_MAC_PTR; + eth_env_get_enetaddr("ethaddr", (uchar *)build_buf); + memcpy(tmp, build_buf, 6); +#else + puts("## Ethernet MAC address not copied to NV RAM\n"); +#endif + +#ifdef CONFIG_X86 + /* + * Get VxWorks's physical memory base address from environment, + * if we don't specify it in the environment, use a default one. + */ + base = env_get_hex("vx_phys_mem_base", VXWORKS_PHYS_MEM_BASE); + data = (struct e820_entry *)(base + E820_DATA_OFFSET); + info = (struct e820_info *)(base + E820_INFO_OFFSET); + + memset(info, 0, sizeof(struct e820_info)); + info->sign = E820_SIGNATURE; + info->entries = install_e820_map(E820MAX, data); + info->addr = (info->entries - 1) * sizeof(struct e820_entry) + + E820_DATA_OFFSET; + + /* + * Explicitly clear the bootloader image size otherwise if memory + * at this offset happens to contain some garbage data, the final + * available memory size for the kernel is insane. + */ + *(u32 *)(base + BOOT_IMAGE_SIZE_OFFSET) = 0; + + /* + * Prepare compatible framebuffer information block. + * The VESA mode has to be 32-bit RGBA. + */ + if (vesa->x_resolution && vesa->y_resolution) { + gop = (struct efi_gop_info *)(base + EFI_GOP_INFO_OFFSET); + gop->magic = EFI_GOP_INFO_MAGIC; + gop->info.version = 0; + gop->info.width = vesa->x_resolution; + gop->info.height = vesa->y_resolution; + gop->info.pixel_format = EFI_GOT_RGBA8; + gop->info.pixels_per_scanline = vesa->bytes_per_scanline / 4; + gop->fb_base = vesa->phys_base_ptr; + gop->fb_size = vesa->bytes_per_scanline * vesa->y_resolution; + } +#endif + + /* + * Use bootaddr to find the location in memory that VxWorks + * will look for the bootline string. The default value is + * (LOCAL_MEM_LOCAL_ADRS + BOOT_LINE_OFFSET) as defined by + * VxWorks BSP. For example, on PowerPC it defaults to 0x4200. + */ + tmp = env_get("bootaddr"); + if (!tmp) { +#ifdef CONFIG_X86 + bootaddr = base + X86_BOOT_LINE_OFFSET; +#else + printf("## VxWorks bootline address not specified\n"); + return 1; +#endif + } + + if (!bootaddr) + bootaddr = hextoul(tmp, NULL); + + /* + * Check to see if the bootline is defined in the 'bootargs' parameter. + * If it is not defined, we may be able to construct the info. + */ + bootline = env_get("bootargs"); + if (!bootline) { + tmp = env_get("bootdev"); + if (tmp) { + strlcpy(build_buf, tmp, BOOTLINE_BUF_LEN); + ptr = strlen(tmp); + } else { + printf("## VxWorks boot device not specified\n"); + } + + tmp = env_get("bootfile"); + if (tmp) + ptr += sprintf(build_buf + ptr, "host:%s ", tmp); + else + ptr += sprintf(build_buf + ptr, "host:vxWorks "); + + /* + * The following parameters are only needed if 'bootdev' + * is an ethernet device, otherwise they are optional. + */ + tmp = env_get("ipaddr"); + if (tmp) { + ptr += sprintf(build_buf + ptr, "e=%s", tmp); + tmp = env_get("netmask"); + if (tmp) { + u32 mask = string_to_ip(tmp).s_addr; + ptr += sprintf(build_buf + ptr, + ":%08x ", ntohl(mask)); + } else { + ptr += sprintf(build_buf + ptr, " "); + } + } + + tmp = env_get("serverip"); + if (tmp) + ptr += sprintf(build_buf + ptr, "h=%s ", tmp); + + tmp = env_get("gatewayip"); + if (tmp) + ptr += sprintf(build_buf + ptr, "g=%s ", tmp); + + tmp = env_get("hostname"); + if (tmp) + ptr += sprintf(build_buf + ptr, "tn=%s ", tmp); + + tmp = env_get("othbootargs"); + if (tmp) { + strcpy(build_buf + ptr, tmp); + ptr += strlen(tmp); + } + + bootline = build_buf; + } + + memcpy((void *)bootaddr, bootline, max(strlen(bootline), (size_t)255)); + flush_cache(bootaddr, max(strlen(bootline), (size_t)255)); + printf("## Using bootline (@ 0x%lx): %s\n", bootaddr, (char *)bootaddr); + + /* + * If the data at the load address is an elf image, then + * treat it like an elf image. Otherwise, assume that it is a + * binary image. + */ + if (valid_elf_image(addr)) + addr = load_elf_image_phdr(addr); + else + puts("## Not an ELF image, assuming binary\n"); + + printf("## Starting vxWorks at 0x%08lx ...\n", addr); + flush(); + + dcache_disable(); +#if defined(CONFIG_ARM64) && defined(CONFIG_ARMV8_PSCI) + armv8_setup_psci(); + smp_kick_all_cpus(); +#endif + +#ifdef CONFIG_X86 + /* VxWorks on x86 uses stack to pass parameters */ + ((asmlinkage void (*)(int))addr)(0); +#else + ((void (*)(int))addr)(0); +#endif + + puts("## vxWorks terminated\n"); + + return 1; +} +#endif + +U_BOOT_CMD( + bootelf, CONFIG_SYS_MAXARGS, 0, do_bootelf, + "Boot from an ELF image in memory", + "[-p|-s] " +#if CONFIG_IS_ENABLED(CMD_ELF_FDT_SETUP) + "[-d fdt_addr_r] " +#endif + "[address]\n" + "\t- load ELF image at [address] via program headers (-p)\n" + "\t or via section headers (-s)\n" +#if CONFIG_IS_ENABLED(CMD_ELF_FDT_SETUP) + "\t- setup FDT image at [fdt_addr_r] (-d)" +#endif +); + +#ifdef CONFIG_CMD_ELF_BOOTVX +U_BOOT_CMD( + bootvx, 2, 0, do_bootvx, + "Boot vxWorks from an ELF image", + " [address] - load address of vxWorks ELF image." +); +#endif diff --git a/cmd/erofs.c b/cmd/erofs.c new file mode 100644 index 00000000000..add80b8b594 --- /dev/null +++ b/cmd/erofs.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Huang Jianan <jnhuang95@gmail.com> + * + * Author: Huang Jianan <jnhuang95@gmail.com> + * + * erofs.c: implements EROFS related commands + */ + +#include <command.h> +#include <fs.h> +#include <erofs.h> + +static int do_erofs_ls(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + return do_ls(cmdtp, flag, argc, argv, FS_TYPE_EROFS); +} + +U_BOOT_CMD(erofsls, 4, 1, do_erofs_ls, + "List files in directory. Default: root (/).", + "<interface> [<dev[:part]>] [directory]\n" + " - list files from 'dev' on 'interface' in 'directory'\n" +); + +static int do_erofs_load(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + return do_load(cmdtp, flag, argc, argv, FS_TYPE_EROFS); +} + +U_BOOT_CMD(erofsload, 7, 0, do_erofs_load, + "load binary file from a EROFS filesystem", + "<interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]\n" + " - Load binary file 'filename' from 'dev' on 'interface'\n" + " to address 'addr' from EROFS filesystem.\n" + " 'pos' gives the file position to start loading from.\n" + " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n" + " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n" + " the load stops on end of file.\n" + " If either 'pos' or 'bytes' are not aligned to\n" + " ARCH_DMA_MINALIGN then a misaligned buffer warning will\n" + " be printed and performance will suffer for the load." +); diff --git a/cmd/ethsw.c b/cmd/ethsw.c new file mode 100644 index 00000000000..4bf49ac598f --- /dev/null +++ b/cmd/ethsw.c @@ -0,0 +1,1103 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2015 Freescale Semiconductor, Inc. + * + * Ethernet Switch commands + */ + +#include <command.h> +#include <env.h> +#include <errno.h> +#include <env_flags.h> +#include <ethsw.h> +#include <net.h> +#include <vsprintf.h> + +static const char *ethsw_name; + +#define ETHSW_PORT_STATS_HELP "ethsw [port <port_no>] statistics " \ +"{ [help] | [clear] } - show an l2 switch port's statistics" + +static int ethsw_port_stats_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_PORT_STATS_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +#define ETHSW_LEARN_HELP "ethsw [port <port_no>] learning " \ +"{ [help] | show | auto | disable } " \ +"- enable/disable/show learning configuration on a port" + +static int ethsw_learn_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_LEARN_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +#define ETHSW_FDB_HELP "ethsw [port <port_no>] [vlan <vid>] fdb " \ +"{ [help] | show | flush | { add | del } <mac> } " \ +"- Add/delete a mac entry in FDB; use show to see FDB entries; " \ +"if vlan <vid> is missing, VID 1 will be used" + +static int ethsw_fdb_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_FDB_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +#define ETHSW_PVID_HELP "ethsw [port <port_no>] " \ +"pvid { [help] | show | <pvid> } " \ +"- set/show PVID (ingress and egress VLAN tagging) for a port" + +static int ethsw_pvid_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_PVID_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +#define ETHSW_VLAN_HELP "ethsw [port <port_no>] vlan " \ +"{ [help] | show | add <vid> | del <vid> } " \ +"- add a VLAN to a port (VLAN members)" + +static int ethsw_vlan_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_VLAN_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +#define ETHSW_PORT_UNTAG_HELP "ethsw [port <port_no>] untagged " \ +"{ [help] | show | all | none | pvid } " \ +" - set egress tagging mode for a port" + +static int ethsw_port_untag_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_PORT_UNTAG_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +#define ETHSW_EGR_VLAN_TAG_HELP "ethsw [port <port_no>] egress tag " \ +"{ [help] | show | pvid | classified } " \ +"- Configure VID source for egress tag. " \ +"Tag's VID could be the frame's classified VID or the PVID of the port" + +static int ethsw_egr_tag_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_EGR_VLAN_TAG_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +#define ETHSW_VLAN_FDB_HELP "ethsw vlan fdb " \ +"{ [help] | show | shared | private } " \ +"- make VLAN learning shared or private" + +static int ethsw_vlan_learn_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_VLAN_FDB_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +#define ETHSW_PORT_INGR_FLTR_HELP "ethsw [port <port_no>] ingress filtering" \ +" { [help] | show | enable | disable } " \ +"- enable/disable VLAN ingress filtering on port" + +static int ethsw_ingr_fltr_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_PORT_INGR_FLTR_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +#define ETHSW_PORT_AGGR_HELP "ethsw [port <port_no>] aggr" \ +" { [help] | show | <lag_group_no> } " \ +"- get/set LAG group for a port" + +static int ethsw_port_aggr_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_PORT_AGGR_HELP"\n"); + + return CMD_RET_SUCCESS; +} + +static struct keywords_to_function { + enum ethsw_keyword_id cmd_keyword[ETHSW_MAX_CMD_PARAMS]; + int cmd_func_offset; + int (*keyword_function)(struct ethsw_command_def *parsed_cmd); +} ethsw_cmd_def[] = { + { + .cmd_keyword = { + ethsw_id_enable, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_enable), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_disable, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_disable), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_statistics, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_port_stats_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_statistics, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_stats), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_statistics, + ethsw_id_clear, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_stats_clear), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_learning, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_learn_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_learning, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_learn_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_learning, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_learn_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_learning, + ethsw_id_auto, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_learn), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_learning, + ethsw_id_disable, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_learn), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_fdb, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_fdb_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_fdb, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_fdb_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_fdb, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + fdb_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_fdb, + ethsw_id_flush, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + fdb_flush), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_fdb, + ethsw_id_add, + ethsw_id_add_del_mac, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + fdb_entry_add), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_fdb, + ethsw_id_del, + ethsw_id_add_del_mac, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + fdb_entry_del), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_pvid, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_pvid_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_pvid, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_pvid_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_pvid, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + pvid_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_pvid, + ethsw_id_pvid_no, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + pvid_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_vlan_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_vlan_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + vlan_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_add, + ethsw_id_add_del_no, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + vlan_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_del, + ethsw_id_add_del_no, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + vlan_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_untagged, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_port_untag_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_untagged, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_port_untag_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_untagged, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_untag_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_untagged, + ethsw_id_all, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_untag_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_untagged, + ethsw_id_none, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_untag_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_untagged, + ethsw_id_pvid, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_untag_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_egress, + ethsw_id_tag, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_egr_tag_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_egress, + ethsw_id_tag, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_egr_tag_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_egress, + ethsw_id_tag, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_egr_vlan_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_egress, + ethsw_id_tag, + ethsw_id_pvid, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_egr_vlan_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_egress, + ethsw_id_tag, + ethsw_id_classified, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_egr_vlan_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_fdb, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_vlan_learn_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_fdb, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_vlan_learn_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_fdb, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + vlan_learn_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_fdb, + ethsw_id_shared, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + vlan_learn_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_fdb, + ethsw_id_private, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + vlan_learn_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_ingress, + ethsw_id_filtering, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_ingr_fltr_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_ingress, + ethsw_id_filtering, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_ingr_fltr_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_ingress, + ethsw_id_filtering, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_ingr_filt_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_ingress, + ethsw_id_filtering, + ethsw_id_enable, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_ingr_filt_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_ingress, + ethsw_id_filtering, + ethsw_id_disable, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_ingr_filt_set), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_aggr, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_port_aggr_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_aggr, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_port_aggr_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_aggr, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_aggr_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_aggr, + ethsw_id_aggr_no, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_aggr_set), + .keyword_function = NULL, + }, +}; + +struct keywords_optional { + int cmd_keyword[ETHSW_MAX_CMD_PARAMS]; +} cmd_opt_def[] = { + { + .cmd_keyword = { + ethsw_id_port, + ethsw_id_port_no, + ethsw_id_key_end, + }, + }, { + .cmd_keyword = { + ethsw_id_vlan, + ethsw_id_vlan_no, + ethsw_id_key_end, + }, + }, { + .cmd_keyword = { + ethsw_id_port, + ethsw_id_port_no, + ethsw_id_vlan, + ethsw_id_vlan_no, + ethsw_id_key_end, + }, + }, +}; + +static int keyword_match_gen(enum ethsw_keyword_id key_id, int argc, char + *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd); +static int keyword_match_port(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd); +static int keyword_match_vlan(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd); +static int keyword_match_pvid(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd); +static int keyword_match_mac_addr(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd); +static int keyword_match_aggr(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd); + +/* + * Define properties for each keyword; + * keep the order synced with enum ethsw_keyword_id + */ +struct keyword_def { + const char *keyword_name; + int (*match)(enum ethsw_keyword_id key_id, int argc, char *const argv[], + int *argc_nr, struct ethsw_command_def *parsed_cmd); +} keyword[] = { + { + .keyword_name = "help", + .match = &keyword_match_gen, + }, { + .keyword_name = "show", + .match = &keyword_match_gen, + }, { + .keyword_name = "port", + .match = &keyword_match_port + }, { + .keyword_name = "enable", + .match = &keyword_match_gen, + }, { + .keyword_name = "disable", + .match = &keyword_match_gen, + }, { + .keyword_name = "statistics", + .match = &keyword_match_gen, + }, { + .keyword_name = "clear", + .match = &keyword_match_gen, + }, { + .keyword_name = "learning", + .match = &keyword_match_gen, + }, { + .keyword_name = "auto", + .match = &keyword_match_gen, + }, { + .keyword_name = "vlan", + .match = &keyword_match_vlan, + }, { + .keyword_name = "fdb", + .match = &keyword_match_gen, + }, { + .keyword_name = "add", + .match = &keyword_match_mac_addr, + }, { + .keyword_name = "del", + .match = &keyword_match_mac_addr, + }, { + .keyword_name = "flush", + .match = &keyword_match_gen, + }, { + .keyword_name = "pvid", + .match = &keyword_match_pvid, + }, { + .keyword_name = "untagged", + .match = &keyword_match_gen, + }, { + .keyword_name = "all", + .match = &keyword_match_gen, + }, { + .keyword_name = "none", + .match = &keyword_match_gen, + }, { + .keyword_name = "egress", + .match = &keyword_match_gen, + }, { + .keyword_name = "tag", + .match = &keyword_match_gen, + }, { + .keyword_name = "classified", + .match = &keyword_match_gen, + }, { + .keyword_name = "shared", + .match = &keyword_match_gen, + }, { + .keyword_name = "private", + .match = &keyword_match_gen, + }, { + .keyword_name = "ingress", + .match = &keyword_match_gen, + }, { + .keyword_name = "filtering", + .match = &keyword_match_gen, + }, { + .keyword_name = "aggr", + .match = &keyword_match_aggr, + }, +}; + +/* + * Function used by an Ethernet Switch driver to set the functions + * that must be called by the parser when an ethsw command is given + */ +int ethsw_define_functions(const struct ethsw_command_func *cmd_func) +{ + int i; + void **aux_p; + int (*cmd_func_aux)(struct ethsw_command_def *); + + if (!cmd_func->ethsw_name) + return -EINVAL; + + ethsw_name = cmd_func->ethsw_name; + + for (i = 0; i < ARRAY_SIZE(ethsw_cmd_def); i++) { + /* + * get the pointer to the function send by the Ethernet Switch + * driver that corresponds to the proper ethsw command + */ + if (ethsw_cmd_def[i].keyword_function) + continue; + + aux_p = (void *)cmd_func + ethsw_cmd_def[i].cmd_func_offset; + + cmd_func_aux = (int (*)(struct ethsw_command_def *)) *aux_p; + ethsw_cmd_def[i].keyword_function = cmd_func_aux; + } + + return 0; +} + +/* Generic function used to match a keyword only by a string */ +static int keyword_match_gen(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd) +{ + if (strcmp(argv[*argc_nr], keyword[key_id].keyword_name) == 0) { + parsed_cmd->cmd_to_keywords[*argc_nr] = key_id; + + return 1; + } + return 0; +} + +/* Function used to match the command's port */ +static int keyword_match_port(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd) +{ + unsigned long val; + + if (!keyword_match_gen(key_id, argc, argv, argc_nr, parsed_cmd)) + return 0; + + if (*argc_nr + 1 >= argc) + return 0; + + if (strict_strtoul(argv[*argc_nr + 1], 10, &val) != -EINVAL) { + parsed_cmd->port = val; + (*argc_nr)++; + parsed_cmd->cmd_to_keywords[*argc_nr] = ethsw_id_port_no; + return 1; + } + + return 0; +} + +/* Function used to match the command's vlan */ +static int keyword_match_vlan(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd) +{ + unsigned long val; + int aux; + + if (!keyword_match_gen(key_id, argc, argv, argc_nr, parsed_cmd)) + return 0; + + if (*argc_nr + 1 >= argc) + return 0; + + if (strict_strtoul(argv[*argc_nr + 1], 10, &val) != -EINVAL) { + parsed_cmd->vid = val; + (*argc_nr)++; + parsed_cmd->cmd_to_keywords[*argc_nr] = ethsw_id_vlan_no; + return 1; + } + + aux = *argc_nr + 1; + + if (keyword_match_gen(ethsw_id_add, argc, argv, &aux, parsed_cmd)) + parsed_cmd->cmd_to_keywords[*argc_nr + 1] = ethsw_id_add; + else if (keyword_match_gen(ethsw_id_del, argc, argv, &aux, parsed_cmd)) + parsed_cmd->cmd_to_keywords[*argc_nr + 1] = ethsw_id_del; + else + return 0; + + if (*argc_nr + 2 >= argc) + return 0; + + if (strict_strtoul(argv[*argc_nr + 2], 10, &val) != -EINVAL) { + parsed_cmd->vid = val; + (*argc_nr) += 2; + parsed_cmd->cmd_to_keywords[*argc_nr] = ethsw_id_add_del_no; + return 1; + } + + return 0; +} + +/* Function used to match the command's pvid */ +static int keyword_match_pvid(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd) +{ + unsigned long val; + + if (!keyword_match_gen(key_id, argc, argv, argc_nr, parsed_cmd)) + return 0; + + if (*argc_nr + 1 >= argc) + return 1; + + if (strict_strtoul(argv[*argc_nr + 1], 10, &val) != -EINVAL) { + parsed_cmd->vid = val; + (*argc_nr)++; + parsed_cmd->cmd_to_keywords[*argc_nr] = ethsw_id_pvid_no; + } + + return 1; +} + +/* Function used to match the command's MAC address */ +static int keyword_match_mac_addr(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd) +{ + if (!keyword_match_gen(key_id, argc, argv, argc_nr, parsed_cmd)) + return 0; + + if ((*argc_nr + 1 >= argc) || + !is_broadcast_ethaddr(parsed_cmd->ethaddr)) + return 1; + + if (eth_validate_ethaddr_str(argv[*argc_nr + 1])) { + printf("Invalid MAC address: %s\n", argv[*argc_nr + 1]); + return 0; + } + + string_to_enetaddr(argv[*argc_nr + 1], parsed_cmd->ethaddr); + + if (is_broadcast_ethaddr(parsed_cmd->ethaddr)) { + memset(parsed_cmd->ethaddr, 0xFF, sizeof(parsed_cmd->ethaddr)); + return 0; + } + + parsed_cmd->cmd_to_keywords[*argc_nr + 1] = ethsw_id_add_del_mac; + + return 1; +} + +/* Function used to match the command's aggregation number */ +static int keyword_match_aggr(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd) +{ + unsigned long val; + + if (!keyword_match_gen(key_id, argc, argv, argc_nr, parsed_cmd)) + return 0; + + if (*argc_nr + 1 >= argc) + return 1; + + if (strict_strtoul(argv[*argc_nr + 1], 10, &val) != -EINVAL) { + parsed_cmd->aggr_grp = val; + (*argc_nr)++; + parsed_cmd->cmd_to_keywords[*argc_nr] = ethsw_id_aggr_no; + } + + return 1; +} + +/* Finds optional keywords and modifies *argc_va to skip them */ +static void cmd_keywords_opt_check(const struct ethsw_command_def *parsed_cmd, + int *argc_val) +{ + int i; + int keyw_opt_matched; + int argc_val_max; + int const *cmd_keyw_p; + int const *cmd_keyw_opt_p; + + /* remember the best match */ + argc_val_max = *argc_val; + + /* + * check if our command's optional keywords match the optional + * keywords of an available command + */ + for (i = 0; i < ARRAY_SIZE(cmd_opt_def); i++) { + keyw_opt_matched = 0; + cmd_keyw_p = &parsed_cmd->cmd_to_keywords[keyw_opt_matched]; + cmd_keyw_opt_p = &cmd_opt_def[i].cmd_keyword[keyw_opt_matched]; + + /* + * increase the number of keywords that + * matched with a command + */ + while (keyw_opt_matched + *argc_val < + parsed_cmd->cmd_keywords_nr && + *cmd_keyw_opt_p != ethsw_id_key_end && + *(cmd_keyw_p + *argc_val) == *cmd_keyw_opt_p) { + keyw_opt_matched++; + cmd_keyw_p++; + cmd_keyw_opt_p++; + } + + /* + * if all our optional command's keywords perfectly match an + * optional pattern, then we can move to the next defined + * keywords in our command; remember the one that matched the + * greatest number of keywords + */ + if (keyw_opt_matched + *argc_val <= + parsed_cmd->cmd_keywords_nr && + *cmd_keyw_opt_p == ethsw_id_key_end && + *argc_val + keyw_opt_matched > argc_val_max) + argc_val_max = *argc_val + keyw_opt_matched; + } + + *argc_val = argc_val_max; +} + +/* + * Finds the function to call based on keywords and + * modifies *argc_va to skip them + */ +static void cmd_keywords_check(struct ethsw_command_def *parsed_cmd, + int *argc_val) +{ + int i; + int keyw_matched; + int *cmd_keyw_p; + int *cmd_keyw_def_p; + + /* + * check if our command's keywords match the + * keywords of an available command + */ + for (i = 0; i < ARRAY_SIZE(ethsw_cmd_def); i++) { + keyw_matched = 0; + cmd_keyw_p = &parsed_cmd->cmd_to_keywords[keyw_matched]; + cmd_keyw_def_p = ðsw_cmd_def[i].cmd_keyword[keyw_matched]; + + /* + * increase the number of keywords that + * matched with a command + */ + while (keyw_matched + *argc_val < parsed_cmd->cmd_keywords_nr && + *cmd_keyw_def_p != ethsw_id_key_end && + *(cmd_keyw_p + *argc_val) == *cmd_keyw_def_p) { + keyw_matched++; + cmd_keyw_p++; + cmd_keyw_def_p++; + } + + /* + * if all our command's keywords perfectly match an + * available command, then we get the function we need to call + * to configure the Ethernet Switch + */ + if (keyw_matched && keyw_matched + *argc_val == + parsed_cmd->cmd_keywords_nr && + *cmd_keyw_def_p == ethsw_id_key_end) { + *argc_val += keyw_matched; + parsed_cmd->cmd_function = + ethsw_cmd_def[i].keyword_function; + return; + } + } +} + +/* find all the keywords in the command */ +static int keywords_find(int argc, char *const argv[], + struct ethsw_command_def *parsed_cmd) +{ + int i; + int j; + int argc_val; + int rc = CMD_RET_SUCCESS; + + for (i = 1; i < argc; i++) { + for (j = 0; j < ethsw_id_count; j++) { + if (keyword[j].match(j, argc, argv, &i, parsed_cmd)) + break; + } + } + + /* if there is no keyword match for a word, the command is invalid */ + for (i = 1; i < argc; i++) + if (parsed_cmd->cmd_to_keywords[i] == ethsw_id_key_end) + rc = CMD_RET_USAGE; + + parsed_cmd->cmd_keywords_nr = argc; + argc_val = 1; + + /* get optional parameters first */ + cmd_keywords_opt_check(parsed_cmd, &argc_val); + + if (argc_val == parsed_cmd->cmd_keywords_nr) + return CMD_RET_USAGE; + + /* + * check the keywords and if a match is found, + * get the function to call + */ + cmd_keywords_check(parsed_cmd, &argc_val); + + /* error if not all commands' parameters were matched */ + if (argc_val == parsed_cmd->cmd_keywords_nr) { + if (!parsed_cmd->cmd_function) { + printf("Command not available for: %s\n", ethsw_name); + rc = CMD_RET_FAILURE; + } + } else { + rc = CMD_RET_USAGE; + } + + return rc; +} + +static void command_def_init(struct ethsw_command_def *parsed_cmd) +{ + int i; + + for (i = 0; i < ETHSW_MAX_CMD_PARAMS; i++) + parsed_cmd->cmd_to_keywords[i] = ethsw_id_key_end; + + parsed_cmd->port = ETHSW_CMD_PORT_ALL; + parsed_cmd->vid = ETHSW_CMD_VLAN_ALL; + parsed_cmd->aggr_grp = ETHSW_CMD_AGGR_GRP_NONE; + parsed_cmd->cmd_function = NULL; + + /* We initialize the MAC address with the Broadcast address */ + memset(parsed_cmd->ethaddr, 0xff, sizeof(parsed_cmd->ethaddr)); +} + +/* function to interpret commands starting with "ethsw " */ +static int do_ethsw(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct ethsw_command_def parsed_cmd; + int rc = CMD_RET_SUCCESS; + + if (argc == 1 || argc >= ETHSW_MAX_CMD_PARAMS) + return CMD_RET_USAGE; + + command_def_init(&parsed_cmd); + + rc = keywords_find(argc, argv, &parsed_cmd); + + if (rc == CMD_RET_SUCCESS) + rc = parsed_cmd.cmd_function(&parsed_cmd); + + return rc; +} + +#define ETHSW_PORT_CONF_HELP "[port <port_no>] { enable | disable | show } " \ +"- enable/disable a port; show a port's configuration" + +U_BOOT_CMD(ethsw, ETHSW_MAX_CMD_PARAMS, 0, do_ethsw, + "Ethernet l2 switch commands", + ETHSW_PORT_CONF_HELP"\n" + ETHSW_PORT_STATS_HELP"\n" + ETHSW_LEARN_HELP"\n" + ETHSW_FDB_HELP"\n" + ETHSW_PVID_HELP"\n" + ETHSW_VLAN_HELP"\n" + ETHSW_PORT_UNTAG_HELP"\n" + ETHSW_EGR_VLAN_TAG_HELP"\n" + ETHSW_VLAN_FDB_HELP"\n" + ETHSW_PORT_INGR_FLTR_HELP"\n" + ETHSW_PORT_AGGR_HELP"\n" +); diff --git a/cmd/event.c b/cmd/event.c new file mode 100644 index 00000000000..00c828757ca --- /dev/null +++ b/cmd/event.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Command-line access to events + * + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <event.h> + +static int do_event_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + event_show_spy_list(); + + return 0; +} + +U_BOOT_LONGHELP(event, + "list - list event spies"); + +U_BOOT_CMD_WITH_SUBCMDS(event, "Events", event_help_text, + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_event_list)); diff --git a/cmd/exit.c b/cmd/exit.c new file mode 100644 index 00000000000..d125ec1e31f --- /dev/null +++ b/cmd/exit.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2000-2009 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <vsprintf.h> + +static int do_exit(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int r; + + r = 0; + if (argc > 1) + r = simple_strtoul(argv[1], NULL, 10); + + return -r - 2; +} + +U_BOOT_CMD( + exit, 2, 1, do_exit, + "exit script", + "" +); diff --git a/cmd/ext2.c b/cmd/ext2.c new file mode 100644 index 00000000000..45c8b353b58 --- /dev/null +++ b/cmd/ext2.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2011 - 2012 Samsung Electronics + * EXT4 filesystem implementation in Uboot by + * Uma Shankar <uma.shankar@samsung.com> + * Manjunatha C Achar <a.manjunatha@samsung.com> + + * (C) Copyright 2004 + * esd gmbh <www.esd-electronics.com> + * Reinhard Arlt <reinhard.arlt@esd-electronics.com> + * + * made from cmd_reiserfs by + * + * (C) Copyright 2003 - 2004 + * Sysgo Real-Time Solutions, AG <www.elinos.com> + * Pavel Bartusek <pba@sysgo.com> + */ + +/* + * Ext2fs support + */ +#include <command.h> +#include <fs.h> + +static int do_ext2ls(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_ls(cmdtp, flag, argc, argv, FS_TYPE_EXT); +} + +/****************************************************************************** + * Ext2fs boot command intepreter. Derived from diskboot + */ +int do_ext2load(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return do_load(cmdtp, flag, argc, argv, FS_TYPE_EXT); +} + +U_BOOT_CMD( + ext2ls, 4, 1, do_ext2ls, + "list files in a directory (default /)", + "<interface> <dev[:part]> [directory]\n" + " - list files from 'dev' on 'interface' in a 'directory'" +); + +U_BOOT_CMD( + ext2load, 6, 0, do_ext2load, + "load binary file from a Ext2 filesystem", + "<interface> [<dev[:part]> [addr [filename [bytes [pos]]]]]\n" + " - load binary file 'filename' from 'dev' on 'interface'\n" + " to address 'addr' from ext2 filesystem." +); diff --git a/cmd/ext4.c b/cmd/ext4.c new file mode 100644 index 00000000000..40d1fe30d5e --- /dev/null +++ b/cmd/ext4.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2011 - 2012 Samsung Electronics + * EXT4 filesystem implementation in Uboot by + * Uma Shankar <uma.shankar@samsung.com> + * Manjunatha C Achar <a.manjunatha@samsung.com> + * + * Ext4fs support + * made from existing cmd_ext2.c file of Uboot + * + * (C) Copyright 2004 + * esd gmbh <www.esd-electronics.com> + * Reinhard Arlt <reinhard.arlt@esd-electronics.com> + * + * made from cmd_reiserfs by + * + * (C) Copyright 2003 - 2004 + * Sysgo Real-Time Solutions, AG <www.elinos.com> + * Pavel Bartusek <pba@sysgo.com> + */ + +/* + * Changelog: + * 0.1 - Newly created file for ext4fs support. Taken from cmd_ext2.c + * file in uboot. Added ext4fs ls load and write support. + */ + +#include <part.h> +#include <config.h> +#include <command.h> +#include <image.h> +#include <linux/ctype.h> +#include <asm/byteorder.h> +#include <ext4fs.h> +#include <linux/stat.h> +#include <malloc.h> +#include <fs.h> + +#if defined(CONFIG_CMD_USB) && defined(CONFIG_USB_STORAGE) +#include <usb.h> +#endif + +int do_ext4_size(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return do_size(cmdtp, flag, argc, argv, FS_TYPE_EXT); +} + +int do_ext4_load(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return do_load(cmdtp, flag, argc, argv, FS_TYPE_EXT); +} + +int do_ext4_ls(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return do_ls(cmdtp, flag, argc, argv, FS_TYPE_EXT); +} + +#if defined(CONFIG_CMD_EXT4_WRITE) +int do_ext4_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_save(cmdtp, flag, argc, argv, FS_TYPE_EXT); +} + +U_BOOT_CMD(ext4write, 7, 1, do_ext4_write, + "create a file in the root directory", + "<interface> <dev[:part]> <addr> <absolute filename path>\n" + " [sizebytes] [file offset]\n" + " - create a file in / directory"); + +#endif + +U_BOOT_CMD( + ext4size, 4, 0, do_ext4_size, + "determine a file's size", + "<interface> <dev[:part]> <filename>\n" + " - Find file 'filename' from 'dev' on 'interface'\n" + " and determine its size." +); + +U_BOOT_CMD(ext4ls, 4, 1, do_ext4_ls, + "list files in a directory (default /)", + "<interface> <dev[:part]> [directory]\n" + " - list files from 'dev' on 'interface' in a 'directory'"); + +U_BOOT_CMD(ext4load, 7, 0, do_ext4_load, + "load binary file from a Ext4 filesystem", + "<interface> [<dev[:part]> [addr [filename [bytes [pos]]]]]\n" + " - load binary file 'filename' from 'dev' on 'interface'\n" + " to address 'addr' from ext4 filesystem"); diff --git a/cmd/extension_board.c b/cmd/extension_board.c new file mode 100644 index 00000000000..317b260bf36 --- /dev/null +++ b/cmd/extension_board.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2021 + * Köry Maincent, Bootlin, <kory.maincent@bootlin.com> + */ + +#include <bootdev.h> +#include <command.h> +#include <dm.h> +#include <env.h> +#include <malloc.h> +#include <extension_board.h> +#include <mapmem.h> +#include <linux/libfdt.h> +#include <fdt_support.h> + +static LIST_HEAD(extension_list); + +static int extension_apply(struct extension *extension) +{ + char *overlay_cmd; + ulong extrasize, overlay_addr; + struct fdt_header *blob; + + if (!working_fdt) { + printf("No FDT memory address configured. Please configure\n" + "the FDT address via \"fdt addr <address>\" command.\n"); + return CMD_RET_FAILURE; + } + + overlay_cmd = env_get("extension_overlay_cmd"); + if (!overlay_cmd) { + printf("Environment extension_overlay_cmd is missing\n"); + return CMD_RET_FAILURE; + } + + overlay_addr = env_get_hex("extension_overlay_addr", 0); + if (!overlay_addr) { + printf("Environment extension_overlay_addr is missing\n"); + return CMD_RET_FAILURE; + } + + env_set("extension_overlay_name", extension->overlay); + if (run_command(overlay_cmd, 0) != 0) + return CMD_RET_FAILURE; + + extrasize = env_get_hex("filesize", 0); + if (!extrasize) + return CMD_RET_FAILURE; + + fdt_shrink_to_minimum(working_fdt, extrasize); + + blob = map_sysmem(overlay_addr, 0); + if (!fdt_valid(&blob)) + return CMD_RET_FAILURE; + + /* apply method prints messages on error */ + if (fdt_overlay_apply_verbose(working_fdt, blob)) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +static int do_extension_list(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int i = 0; + struct extension *extension; + + if (list_empty(&extension_list)) { + printf("No extension registered - Please run \"extension scan\"\n"); + return CMD_RET_SUCCESS; + } + + list_for_each_entry(extension, &extension_list, list) { + printf("Extension %d: %s\n", i++, extension->name); + printf("\tManufacturer: \t\t%s\n", extension->owner); + printf("\tVersion: \t\t%s\n", extension->version); + printf("\tDevicetree overlay: \t%s\n", extension->overlay); + printf("\tOther information: \t%s\n", extension->other); + } + return CMD_RET_SUCCESS; +} + +static int extension_scan(bool show) +{ + struct extension *extension, *next; + int extension_num; + + list_for_each_entry_safe(extension, next, &extension_list, list) { + list_del(&extension->list); + free(extension); + } + extension_num = extension_board_scan(&extension_list); + if (show && extension_num >= 0) + printf("Found %d extension board(s).\n", extension_num); + + /* either the number of extensions, or -ve for error */ + return extension_num; +} + +static int do_extension_scan(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int extension_num; + + extension_num = extension_scan(true); + if (extension_num < 0) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +static int do_extension_apply(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct extension *extension = NULL; + struct list_head *entry; + int i = 0, extension_id, ret; + + if (argc < 2) + return CMD_RET_USAGE; + + if (strcmp(argv[1], "all") == 0) { + ret = CMD_RET_FAILURE; + list_for_each_entry(extension, &extension_list, list) { + ret = extension_apply(extension); + if (ret != CMD_RET_SUCCESS) + break; + } + } else { + extension_id = simple_strtol(argv[1], NULL, 10); + list_for_each(entry, &extension_list) { + if (i == extension_id) { + extension = list_entry(entry, struct extension, list); + break; + } + i++; + } + + if (!extension) { + printf("Wrong extension number\n"); + return CMD_RET_FAILURE; + } + + ret = extension_apply(extension); + } + + return ret; +} + +static struct cmd_tbl cmd_extension[] = { + U_BOOT_CMD_MKENT(scan, 1, 1, do_extension_scan, "", ""), + U_BOOT_CMD_MKENT(list, 1, 0, do_extension_list, "", ""), + U_BOOT_CMD_MKENT(apply, 2, 0, do_extension_apply, "", ""), +}; + +static int do_extensionops(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cp; + + /* Drop the extension command */ + argc--; + argv++; + + cp = find_cmd_tbl(argv[0], cmd_extension, ARRAY_SIZE(cmd_extension)); + if (cp) + return cp->cmd(cmdtp, flag, argc, argv); + + return CMD_RET_USAGE; +} + +U_BOOT_CMD(extension, 3, 1, do_extensionops, + "Extension board management sub system", + "scan - scan plugged extension(s) board(s)\n" + "extension list - lists available extension(s) board(s)\n" + "extension apply <extension number|all> - applies DT overlays corresponding to extension boards\n" +); + +static int extension_bootdev_hunt(struct bootdev_hunter *info, bool show) +{ + int ret; + + ret = env_set_hex("extension_overlay_addr", + env_get_hex("fdtoverlay_addr_r", 0)); + if (ret) + return log_msg_ret("env", ret); + + ret = extension_scan(show); + if (ret < 0) + return log_msg_ret("ext", ret); + + return 0; +} + +/* extensions should have a uclass - for now we use UCLASS_SIMPLE_BUS uclass */ +BOOTDEV_HUNTER(extension_bootdev_hunter) = { + .prio = BOOTDEVP_1_PRE_SCAN, + .uclass = UCLASS_SIMPLE_BUS, + .hunt = extension_bootdev_hunt, +}; diff --git a/cmd/fastboot.c b/cmd/fastboot.c new file mode 100644 index 00000000000..be84a482b81 --- /dev/null +++ b/cmd/fastboot.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2008 - 2009 Windriver, <www.windriver.com> + * Author: Tom Rix <Tom.Rix@windriver.com> + * + * (C) Copyright 2014 Linaro, Ltd. + * Rob Herring <robh@kernel.org> + */ +#include <command.h> +#include <console.h> +#include <g_dnl.h> +#include <fastboot.h> +#include <net.h> +#include <usb.h> +#include <watchdog.h> +#include <linux/printk.h> +#include <linux/stringify.h> + +#if CONFIG_IS_ENABLED(NET) +static int do_fastboot_udp(int argc, char *const argv[], + uintptr_t buf_addr, size_t buf_size) +{ + int err; + + if (!IS_ENABLED(CONFIG_UDP_FUNCTION_FASTBOOT)) { + pr_err("Fastboot UDP not enabled\n"); + return CMD_RET_FAILURE; + } + + err = net_loop(FASTBOOT_UDP); + + if (err < 0) { + printf("fastboot udp error: %d\n", err); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_fastboot_tcp(int argc, char *const argv[], + uintptr_t buf_addr, size_t buf_size) +{ + int err; + + if (!IS_ENABLED(CONFIG_TCP_FUNCTION_FASTBOOT)) { + pr_err("Fastboot TCP not enabled\n"); + return CMD_RET_FAILURE; + } + + err = net_loop(FASTBOOT_TCP); + + if (err < 0) { + printf("fastboot tcp error: %d\n", err); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} +#endif + +static int do_fastboot_usb(int argc, char *const argv[], + uintptr_t buf_addr, size_t buf_size) +{ + int controller_index; + char *usb_controller; + struct udevice *udc; + char *endp; + int ret; + + if (!IS_ENABLED(CONFIG_USB_FUNCTION_FASTBOOT)) { + pr_err("Fastboot USB not enabled\n"); + return CMD_RET_FAILURE; + } + + if (argc < 2) + return CMD_RET_USAGE; + + usb_controller = argv[1]; + controller_index = simple_strtoul(usb_controller, &endp, 0); + if (*endp != '\0') { + pr_err("Error: Wrong USB controller index format\n"); + return CMD_RET_FAILURE; + } + + ret = udc_device_get_by_index(controller_index, &udc); + if (ret) { + pr_err("USB init failed: %d\n", ret); + return CMD_RET_FAILURE; + } + + g_dnl_clear_detach(); + ret = g_dnl_register("usb_dnl_fastboot"); + if (ret) + return ret; + + if (!g_dnl_board_usb_cable_connected()) { + puts("\rUSB cable not detected.\n" \ + "Command exit.\n"); + ret = CMD_RET_FAILURE; + goto exit; + } + + while (1) { + if (g_dnl_detach()) + break; + if (ctrlc()) + break; + schedule(); + dm_usb_gadget_handle_interrupts(udc); + } + + ret = CMD_RET_SUCCESS; + +exit: + udc_device_put(udc); + g_dnl_unregister(); + g_dnl_clear_detach(); + + return ret; +} + +static int do_fastboot(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uintptr_t buf_addr = (uintptr_t)NULL; + size_t buf_size = 0; + + if (argc < 2) + return CMD_RET_USAGE; + + while (argc > 1 && **(argv + 1) == '-') { + char *arg = *++argv; + + --argc; + while (*++arg) { + switch (*arg) { + case 'l': + if (--argc <= 0) + return CMD_RET_USAGE; + buf_addr = hextoul(*++argv, NULL); + goto NXTARG; + + case 's': + if (--argc <= 0) + return CMD_RET_USAGE; + buf_size = hextoul(*++argv, NULL); + goto NXTARG; + + default: + return CMD_RET_USAGE; + } + } +NXTARG: + ; + } + + /* Handle case when USB controller param is just '-' */ + if (argc == 1) { + pr_err("Error: Incorrect USB controller index\n"); + return CMD_RET_USAGE; + } + + fastboot_init((void *)buf_addr, buf_size); + +#if CONFIG_IS_ENABLED(NET) + if (!strcmp(argv[1], "udp")) + return do_fastboot_udp(argc, argv, buf_addr, buf_size); + if (!strcmp(argv[1], "tcp")) + return do_fastboot_tcp(argc, argv, buf_addr, buf_size); +#endif + if (!strcmp(argv[1], "usb")) { + argv++; + argc--; + } + + return do_fastboot_usb(argc, argv, buf_addr, buf_size); +} + +U_BOOT_CMD( + fastboot, CONFIG_SYS_MAXARGS, 1, do_fastboot, + "run as a fastboot usb or udp device", + "[-l addr] [-s size] usb <controller> | udp\n" + "\taddr - address of buffer used during data transfers (" + __stringify(CONFIG_FASTBOOT_BUF_ADDR) ")\n" + "\tsize - size of buffer used during data transfers (" + __stringify(CONFIG_FASTBOOT_BUF_SIZE) ")" +); diff --git a/cmd/fat.c b/cmd/fat.c new file mode 100644 index 00000000000..5b7484dc1af --- /dev/null +++ b/cmd/fat.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2002 + * Richard Jones, rjones@nexus-tech.net + */ + +/* + * Boot support + */ +#include <command.h> +#include <mapmem.h> +#include <fat.h> +#include <fs.h> +#include <part.h> +#include <asm/cache.h> + +static int do_fat_size(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return do_size(cmdtp, flag, argc, argv, FS_TYPE_FAT); +} + +U_BOOT_CMD( + fatsize, 4, 0, do_fat_size, + "determine a file's size", + "<interface> <dev[:part]> <filename>\n" + " - Find file 'filename' from 'dev' on 'interface'\n" + " and determine its size." +); + +int do_fat_fsload(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return do_load(cmdtp, flag, argc, argv, FS_TYPE_FAT); +} + +U_BOOT_CMD( + fatload, 7, 0, do_fat_fsload, + "load binary file from a dos filesystem", + "<interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]\n" + " - Load binary file 'filename' from 'dev' on 'interface'\n" + " to address 'addr' from dos filesystem.\n" + " 'pos' gives the file position to start loading from.\n" + " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n" + " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n" + " the load stops on end of file.\n" + " If either 'pos' or 'bytes' are not aligned to\n" + " ARCH_DMA_MINALIGN then a misaligned buffer warning will\n" + " be printed and performance will suffer for the load." +); + +static int do_fat_ls(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_ls(cmdtp, flag, argc, argv, FS_TYPE_FAT); +} + +U_BOOT_CMD( + fatls, 4, 1, do_fat_ls, + "list files in a directory (default /)", + "<interface> [<dev[:part]>] [directory]\n" + " - list files from 'dev' on 'interface' in a 'directory'" +); + +static int do_fat_fsinfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int dev, part; + struct blk_desc *dev_desc; + struct disk_partition info; + + if (argc < 2) { + printf("usage: fatinfo <interface> [<dev[:part]>]\n"); + return 0; + } + + part = blk_get_device_part_str(argv[1], argv[2], &dev_desc, &info, 1); + if (part < 0) + return 1; + + dev = dev_desc->devnum; + if (fat_set_blk_dev(dev_desc, &info) != 0) { + printf("\n** Unable to use %s %d:%d for fatinfo **\n", + argv[1], dev, part); + return 1; + } + return file_fat_detectfs(); +} + +U_BOOT_CMD( + fatinfo, 3, 1, do_fat_fsinfo, + "print information about filesystem", + "<interface> [<dev[:part]>]\n" + " - print information about filesystem from 'dev' on 'interface'" +); + +#ifdef CONFIG_FAT_WRITE +static int do_fat_fswrite(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_save(cmdtp, flag, argc, argv, FS_TYPE_FAT); +} + +U_BOOT_CMD( + fatwrite, 7, 0, do_fat_fswrite, + "write file into a dos filesystem", + "<interface> <dev[:part]> <addr> <filename> [<bytes> [<offset>]]\n" + " - write file 'filename' from the address 'addr' in RAM\n" + " to 'dev' on 'interface'" +); + +static int do_fat_rm(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_rm(cmdtp, flag, argc, argv, FS_TYPE_FAT); +} + +U_BOOT_CMD( + fatrm, 4, 1, do_fat_rm, + "delete a file", + "<interface> [<dev[:part]>] <filename>\n" + " - delete a file from 'dev' on 'interface'" +); + +static int do_fat_mkdir(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_mkdir(cmdtp, flag, argc, argv, FS_TYPE_FAT); +} + +U_BOOT_CMD( + fatmkdir, 4, 1, do_fat_mkdir, + "create a directory", + "<interface> [<dev[:part]>] <directory>\n" + " - create a directory in 'dev' on 'interface'" +); +#endif diff --git a/cmd/fdt.c b/cmd/fdt.c new file mode 100644 index 00000000000..a67c30b21d5 --- /dev/null +++ b/cmd/fdt.c @@ -0,0 +1,1159 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2007 + * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com + * Based on code written by: + * Pantelis Antoniou <pantelis.antoniou@gmail.com> and + * Matthew McClintock <msm@freescale.com> + */ + +#include <command.h> +#include <env.h> +#include <image.h> +#include <linux/ctype.h> +#include <linux/types.h> +#include <asm/global_data.h> +#include <linux/libfdt.h> +#include <fdt_support.h> +#include <mapmem.h> +#include <asm/io.h> + +#define MAX_LEVEL 32 /* how deeply nested we will go */ +#define SCRATCHPAD 1024 /* bytes of scratchpad memory */ + +/* + * Global data (for the gd->bd) + */ +DECLARE_GLOBAL_DATA_PTR; + +static int fdt_parse_prop(char *const*newval, int count, char *data, int *len); +static int fdt_print(const char *pathp, char *prop, int depth); +static int is_printable_string(const void *data, int len); + +/* + * The working_fdt points to our working flattened device tree. + */ +struct fdt_header *working_fdt; + +static void set_working_fdt_addr_quiet(ulong addr) +{ + void *buf; + + buf = map_sysmem(addr, 0); + working_fdt = buf; + env_set_hex("fdtaddr", addr); +} + +void set_working_fdt_addr(ulong addr) +{ + printf("Working FDT set to %lx\n", addr); + set_working_fdt_addr_quiet(addr); +} + +/* + * Get a value from the fdt and format it to be set in the environment + */ +static int fdt_value_env_set(const void *nodep, int len, + const char *var, int index) +{ + if (is_printable_string(nodep, len)) { + const char *nodec = (const char *)nodep; + int i; + + /* + * Iterate over all members in stringlist and find the one at + * offset $index. If no such index exists, indicate failure. + */ + for (i = 0; i < len; ) { + if (index-- > 0) { + i += strlen(nodec) + 1; + nodec += strlen(nodec) + 1; + continue; + } + + env_set(var, nodec); + return 0; + } + + return 1; + } else if (len == 4) { + char buf[11]; + + sprintf(buf, "0x%08X", fdt32_to_cpu(*(fdt32_t *)nodep)); + env_set(var, buf); + } else if (len % 4 == 0 && index >= 0) { + /* Needed to print integer arrays. */ + const unsigned int *nodec = (const unsigned int *)nodep; + char buf[11]; + + if (index * 4 >= len) + return 1; + + sprintf(buf, "0x%08X", fdt32_to_cpu(*(nodec + index))); + env_set(var, buf); + } else if (len % 4 == 0 && len <= 20) { + /* Needed to print things like sha1 hashes. */ + char buf[41]; + int i; + + for (i = 0; i < len; i += sizeof(unsigned int)) + sprintf(buf + (i * 2), "%08x", + *(unsigned int *)(nodep + i)); + env_set(var, buf); + } else { + printf("error: unprintable value\n"); + return 1; + } + return 0; +} + +static const char * const fdt_member_table[] = { + "magic", + "totalsize", + "off_dt_struct", + "off_dt_strings", + "off_mem_rsvmap", + "version", + "last_comp_version", + "boot_cpuid_phys", + "size_dt_strings", + "size_dt_struct", +}; + +static int fdt_get_header_value(int argc, char *const argv[]) +{ + fdt32_t *fdtp = (fdt32_t *)working_fdt; + ulong val; + int i; + + if (argv[2][0] != 'g') + return CMD_RET_FAILURE; + + for (i = 0; i < ARRAY_SIZE(fdt_member_table); i++) { + if (strcmp(fdt_member_table[i], argv[4])) + continue; + + val = fdt32_to_cpu(fdtp[i]); + env_set_hex(argv[3], val); + return CMD_RET_SUCCESS; + } + + return CMD_RET_FAILURE; +} + +/* + * Flattened Device Tree command, see the help for parameter definitions. + */ +static int do_fdt(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + if (argc < 2) + return CMD_RET_USAGE; + + /* fdt addr: Set the address of the fdt */ + if (strncmp(argv[1], "ad", 2) == 0) { + unsigned long addr; + int control = 0; + int quiet = 0; + struct fdt_header *blob; + + /* Set the address [and length] of the fdt */ + argc -= 2; + argv += 2; + while (argc > 0 && **argv == '-') { + char *arg = *argv; + + while (*++arg) { + switch (*arg) { + case 'c': + control = 1; + break; + case 'q': + quiet = 1; + break; + default: + return CMD_RET_USAGE; + } + } + argc--; + argv++; + } + if (argc == 0) { + if (control) + blob = (struct fdt_header *)gd->fdt_blob; + else + blob = working_fdt; + if (!blob || !fdt_valid(&blob)) + return 1; + printf("%s fdt: %08lx\n", + control ? "Control" : "Working", + control ? (ulong)map_to_sysmem(blob) : + env_get_hex("fdtaddr", 0)); + return 0; + } + + addr = hextoul(argv[0], NULL); + blob = map_sysmem(addr, 0); + if ((quiet && fdt_check_header(blob)) || + (!quiet && !fdt_valid(&blob))) + return 1; + if (control) { + gd->fdt_blob = blob; + } else { + if (quiet) + set_working_fdt_addr_quiet(addr); + else + set_working_fdt_addr(addr); + } + + if (argc >= 2) { + int len; + int err; + + /* Optional new length */ + len = hextoul(argv[1], NULL); + if (len < fdt_totalsize(blob)) { + if (!quiet) + printf("New length %d < existing length %d, ignoring\n", + len, fdt_totalsize(blob)); + } else { + /* Open in place with a new length */ + err = fdt_open_into(blob, blob, len); + if (!quiet && err != 0) { + printf("libfdt fdt_open_into(): %s\n", + fdt_strerror(err)); + } + } + } + + return CMD_RET_SUCCESS; + + /* + * Move the working_fdt + */ + } else if (strncmp(argv[1], "mo", 2) == 0) { + struct fdt_header *newaddr; + int len; + int err; + + if (argc < 4) + return CMD_RET_USAGE; + + /* + * Set the address and length of the fdt. + */ + working_fdt = map_sysmem(hextoul(argv[2], NULL), 0); + if (!fdt_valid(&working_fdt)) + return 1; + + newaddr = map_sysmem(hextoul(argv[3], NULL), 0); + + /* + * If the user specifies a length, use that. Otherwise use the + * current length. + */ + if (argc <= 4) { + len = fdt_totalsize(working_fdt); + } else { + len = hextoul(argv[4], NULL); + if (len < fdt_totalsize(working_fdt)) { + printf ("New length 0x%X < existing length " + "0x%X, aborting.\n", + len, fdt_totalsize(working_fdt)); + return 1; + } + } + + /* + * Copy to the new location. + */ + err = fdt_open_into(working_fdt, newaddr, len); + if (err != 0) { + printf ("libfdt fdt_open_into(): %s\n", + fdt_strerror(err)); + return 1; + } + set_working_fdt_addr(map_to_sysmem(newaddr)); + + return CMD_RET_SUCCESS; + } + + if (!working_fdt) { + puts("No FDT memory address configured. Please configure\n" + "the FDT address via \"fdt addr <address>\" command.\n" + "Aborting!\n"); + return CMD_RET_FAILURE; + } + +#ifdef CONFIG_OF_SYSTEM_SETUP + /* Call the board-specific fixup routine */ + if (strncmp(argv[1], "sys", 3) == 0) { + int err = ft_system_setup(working_fdt, gd->bd); + + if (err) { + printf("Failed to add system information to FDT: %s\n", + fdt_strerror(err)); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; + } +#endif + /* + * Make a new node + */ + if (strncmp(argv[1], "mk", 2) == 0) { + char *pathp; /* path */ + char *nodep; /* new node to add */ + int nodeoffset; /* node offset from libfdt */ + int err; + + /* + * Parameters: Node path, new node to be appended to the path. + */ + if (argc < 4) + return CMD_RET_USAGE; + + pathp = argv[2]; + nodep = argv[3]; + + nodeoffset = fdt_path_offset (working_fdt, pathp); + if (nodeoffset < 0) { + /* + * Not found or something else bad happened. + */ + printf ("libfdt fdt_path_offset() returned %s\n", + fdt_strerror(nodeoffset)); + return 1; + } + err = fdt_add_subnode(working_fdt, nodeoffset, nodep); + if (err < 0) { + printf ("libfdt fdt_add_subnode(): %s\n", + fdt_strerror(err)); + return 1; + } + + /* + * Set the value of a property in the working_fdt. + */ + } else if (strncmp(argv[1], "se", 2) == 0) { + char *pathp; /* path */ + char *prop; /* property */ + int nodeoffset; /* node offset from libfdt */ + static char data[SCRATCHPAD] __aligned(4);/* property storage */ + const void *ptmp; + int len; /* new length of the property */ + int ret; /* return value */ + + /* + * Parameters: Node path, property, optional value. + */ + if (argc < 4) + return CMD_RET_USAGE; + + pathp = argv[2]; + prop = argv[3]; + + nodeoffset = fdt_path_offset (working_fdt, pathp); + if (nodeoffset < 0) { + /* + * Not found or something else bad happened. + */ + printf ("libfdt fdt_path_offset() returned %s\n", + fdt_strerror(nodeoffset)); + return 1; + } + + if (argc == 4) { + len = 0; + } else { + ptmp = fdt_getprop(working_fdt, nodeoffset, prop, &len); + if (len > SCRATCHPAD) { + printf("prop (%d) doesn't fit in scratchpad!\n", + len); + return 1; + } + if (ptmp != NULL) + memcpy(data, ptmp, len); + + ret = fdt_parse_prop(&argv[4], argc - 4, data, &len); + if (ret != 0) + return ret; + } + + ret = fdt_setprop(working_fdt, nodeoffset, prop, data, len); + if (ret < 0) { + printf ("libfdt fdt_setprop(): %s\n", fdt_strerror(ret)); + return 1; + } + + /******************************************************************** + * Get the value of a property in the working_fdt. + ********************************************************************/ + } else if (argv[1][0] == 'g') { + char *subcmd; /* sub-command */ + char *pathp; /* path */ + char *prop; /* property */ + char *var; /* variable to store result */ + int nodeoffset; /* node offset from libfdt */ + const void *nodep; /* property node pointer */ + int len = 0; /* new length of the property */ + + /* + * Parameters: Node path, property, optional value. + */ + if (argc < 5) + return CMD_RET_USAGE; + + subcmd = argv[2]; + + if (argc < 6 && subcmd[0] != 's') + return CMD_RET_USAGE; + + var = argv[3]; + pathp = argv[4]; + prop = argv[5]; + + nodeoffset = fdt_path_offset(working_fdt, pathp); + if (nodeoffset < 0) { + /* + * Not found or something else bad happened. + */ + printf("libfdt fdt_path_offset() returned %s\n", + fdt_strerror(nodeoffset)); + return 1; + } + + if (subcmd[0] == 'n' || (subcmd[0] == 's' && argc == 5)) { + int req_index = -1; + int startDepth = fdt_node_depth( + working_fdt, nodeoffset); + int curDepth = startDepth; + int cur_index = -1; + int nextNodeOffset = fdt_next_node( + working_fdt, nodeoffset, &curDepth); + + if (subcmd[0] == 'n') + req_index = hextoul(argv[5], NULL); + + while (curDepth > startDepth) { + if (curDepth == startDepth + 1) + cur_index++; + if (subcmd[0] == 'n' && + cur_index == req_index) { + const char *node_name; + + node_name = fdt_get_name(working_fdt, + nextNodeOffset, + NULL); + env_set(var, node_name); + return 0; + } + nextNodeOffset = fdt_next_node( + working_fdt, nextNodeOffset, &curDepth); + if (nextNodeOffset < 0) + break; + } + if (subcmd[0] == 's') { + /* get the num nodes at this level */ + env_set_ulong(var, cur_index + 1); + } else { + /* node index not found */ + printf("libfdt node not found\n"); + return 1; + } + } else { + nodep = fdt_getprop( + working_fdt, nodeoffset, prop, &len); + if (nodep && len >= 0) { + if (subcmd[0] == 'v') { + int index = -1; + int ret; + + if (len == 0) { + /* no property value */ + env_set(var, ""); + return 0; + } + + if (argc == 7) + index = simple_strtoul(argv[6], NULL, 10); + + ret = fdt_value_env_set(nodep, len, + var, index); + if (ret != 0) + return ret; + } else if (subcmd[0] == 'a') { + env_set_hex(var, (ulong)map_to_sysmem(nodep)); + } else if (subcmd[0] == 's') { + env_set_hex(var, len); + } else + return CMD_RET_USAGE; + return 0; + } else { + printf("libfdt fdt_getprop(): %s\n", + fdt_strerror(len)); + return 1; + } + } + + /* + * Print (recursive) / List (single level) + */ + } else if ((argv[1][0] == 'p') || (argv[1][0] == 'l')) { + int depth = MAX_LEVEL; /* how deep to print */ + char *pathp; /* path */ + char *prop; /* property */ + int ret; /* return value */ + static char root[2] = "/"; + + /* + * list is an alias for print, but limited to 1 level + */ + if (argv[1][0] == 'l') { + depth = 1; + } + + /* + * Get the starting path. The root node is an oddball, + * the offset is zero and has no name. + */ + if (argc == 2) + pathp = root; + else + pathp = argv[2]; + if (argc > 3) + prop = argv[3]; + else + prop = NULL; + + ret = fdt_print(pathp, prop, depth); + if (ret != 0) + return ret; + + /* + * Remove a property/node + */ + } else if (strncmp(argv[1], "rm", 2) == 0) { + int nodeoffset; /* node offset from libfdt */ + int err; + + /* + * Get the path. The root node is an oddball, the offset + * is zero and has no name. + */ + nodeoffset = fdt_path_offset (working_fdt, argv[2]); + if (nodeoffset < 0) { + /* + * Not found or something else bad happened. + */ + printf ("libfdt fdt_path_offset() returned %s\n", + fdt_strerror(nodeoffset)); + return 1; + } + /* + * Do the delete. A fourth parameter means delete a property, + * otherwise delete the node. + */ + if (argc > 3) { + err = fdt_delprop(working_fdt, nodeoffset, argv[3]); + if (err < 0) { + printf("libfdt fdt_delprop(): %s\n", + fdt_strerror(err)); + return CMD_RET_FAILURE; + } + } else { + err = fdt_del_node(working_fdt, nodeoffset); + if (err < 0) { + printf("libfdt fdt_del_node(): %s\n", + fdt_strerror(err)); + return CMD_RET_FAILURE; + } + } + + /* + * Display header info + */ + } else if (argv[1][0] == 'h') { + if (argc == 5) + return fdt_get_header_value(argc, argv); + + u32 version = fdt_version(working_fdt); + printf("magic:\t\t\t0x%x\n", fdt_magic(working_fdt)); + printf("totalsize:\t\t0x%x (%d)\n", fdt_totalsize(working_fdt), + fdt_totalsize(working_fdt)); + printf("off_dt_struct:\t\t0x%x\n", + fdt_off_dt_struct(working_fdt)); + printf("off_dt_strings:\t\t0x%x\n", + fdt_off_dt_strings(working_fdt)); + printf("off_mem_rsvmap:\t\t0x%x\n", + fdt_off_mem_rsvmap(working_fdt)); + printf("version:\t\t%d\n", version); + printf("last_comp_version:\t%d\n", + fdt_last_comp_version(working_fdt)); + if (version >= 2) + printf("boot_cpuid_phys:\t0x%x\n", + fdt_boot_cpuid_phys(working_fdt)); + if (version >= 3) + printf("size_dt_strings:\t0x%x\n", + fdt_size_dt_strings(working_fdt)); + if (version >= 17) + printf("size_dt_struct:\t\t0x%x\n", + fdt_size_dt_struct(working_fdt)); + printf("number mem_rsv:\t\t0x%x\n", + fdt_num_mem_rsv(working_fdt)); + printf("\n"); + + /* + * Set boot cpu id + */ + } else if (strncmp(argv[1], "boo", 3) == 0) { + unsigned long tmp; + + if (argc != 3) + return CMD_RET_USAGE; + + tmp = hextoul(argv[2], NULL); + fdt_set_boot_cpuid_phys(working_fdt, tmp); + + /* + * memory command + */ + } else if (strncmp(argv[1], "me", 2) == 0) { + uint64_t addr, size; + int err; + + if (argc != 4) + return CMD_RET_USAGE; + + addr = simple_strtoull(argv[2], NULL, 16); + size = simple_strtoull(argv[3], NULL, 16); + err = fdt_fixup_memory(working_fdt, addr, size); + if (err < 0) + return err; + + /* + * mem reserve commands + */ + } else if (strncmp(argv[1], "rs", 2) == 0) { + if (argv[2][0] == 'p') { + uint64_t addr, size; + int total = fdt_num_mem_rsv(working_fdt); + int j, err; + printf("index\t\t start\t\t size\n"); + printf("-------------------------------" + "-----------------\n"); + for (j = 0; j < total; j++) { + err = fdt_get_mem_rsv(working_fdt, j, &addr, &size); + if (err < 0) { + printf("libfdt fdt_get_mem_rsv(): %s\n", + fdt_strerror(err)); + return err; + } + printf(" %x\t%08x%08x\t%08x%08x\n", j, + (u32)(addr >> 32), + (u32)(addr & 0xffffffff), + (u32)(size >> 32), + (u32)(size & 0xffffffff)); + } + } else if (argv[2][0] == 'a') { + uint64_t addr, size; + int err; + addr = simple_strtoull(argv[3], NULL, 16); + size = simple_strtoull(argv[4], NULL, 16); + err = fdt_add_mem_rsv(working_fdt, addr, size); + + if (err < 0) { + printf("libfdt fdt_add_mem_rsv(): %s\n", + fdt_strerror(err)); + return CMD_RET_FAILURE; + } + } else if (argv[2][0] == 'd') { + unsigned long idx = hextoul(argv[3], NULL); + int err = fdt_del_mem_rsv(working_fdt, idx); + + if (err < 0) { + printf("libfdt fdt_del_mem_rsv(): %s\n", + fdt_strerror(err)); + return CMD_RET_FAILURE; + } + } else { + /* Unrecognized command */ + return CMD_RET_USAGE; + } + } +#ifdef CONFIG_OF_BOARD_SETUP + /* Call the board-specific fixup routine */ + else if (strncmp(argv[1], "boa", 3) == 0) { + int err = ft_board_setup(working_fdt, gd->bd); + + if (err) { + printf("Failed to update board information in FDT: %s\n", + fdt_strerror(err)); + return CMD_RET_FAILURE; + } + + if (IS_ENABLED(CONFIG_OF_BOARD_SETUP_EXTENDED)) + ft_board_setup_ex(working_fdt, gd->bd); + } +#endif + /* Create a chosen node */ + else if (strncmp(argv[1], "cho", 3) == 0) { + unsigned long initrd_start = 0, initrd_end = 0; + + if ((argc != 2) && (argc != 4)) + return CMD_RET_USAGE; + + if (argc == 4) { + initrd_start = hextoul(argv[2], NULL); + initrd_end = initrd_start + hextoul(argv[3], NULL) - 1; + } + + fdt_chosen(working_fdt); + fdt_initrd(working_fdt, initrd_start, initrd_end); + +#if defined(CONFIG_FIT_SIGNATURE) + } else if (strncmp(argv[1], "che", 3) == 0) { + int cfg_noffset; + int ret; + unsigned long addr; + struct fdt_header *blob; + + if (!working_fdt) + return CMD_RET_FAILURE; + + if (argc > 2) { + addr = hextoul(argv[2], NULL); + blob = map_sysmem(addr, 0); + } else { + blob = (struct fdt_header *)gd->fdt_blob; + } + if (!fdt_valid(&blob)) + return 1; + + gd->fdt_blob = blob; + cfg_noffset = fit_conf_get_node(working_fdt, NULL); + if (cfg_noffset < 0) { + printf("Could not find configuration node: %s\n", + fdt_strerror(cfg_noffset)); + return CMD_RET_FAILURE; + } + + ret = fit_config_verify(working_fdt, cfg_noffset); + if (ret == 0) + return CMD_RET_SUCCESS; + else + return CMD_RET_FAILURE; +#endif + + } +#ifdef CONFIG_OF_LIBFDT_OVERLAY + /* apply an overlay */ + else if (strncmp(argv[1], "ap", 2) == 0) { + unsigned long addr; + struct fdt_header *blob; + int ret; + + if (argc != 3) + return CMD_RET_USAGE; + + if (!working_fdt) + return CMD_RET_FAILURE; + + addr = hextoul(argv[2], NULL); + blob = map_sysmem(addr, 0); + if (!fdt_valid(&blob)) + return CMD_RET_FAILURE; + + /* apply method prints messages on error */ + ret = fdt_overlay_apply_verbose(working_fdt, blob); + if (ret) + return CMD_RET_FAILURE; + } +#endif + /* resize the fdt */ + else if (strncmp(argv[1], "re", 2) == 0) { + uint extrasize; + if (argc > 2) + extrasize = hextoul(argv[2], NULL); + else + extrasize = 0; + fdt_shrink_to_minimum(working_fdt, extrasize); + } + else { + /* Unrecognized command */ + return CMD_RET_USAGE; + } + + return 0; +} + +/****************************************************************************/ + +/* + * Parse the user's input, partially heuristic. Valid formats: + * <0x00112233 4 05> - an array of cells. Numbers follow standard + * C conventions. + * [00 11 22 .. nn] - byte stream + * "string" - If the the value doesn't start with "<" or "[", it is + * treated as a string. Note that the quotes are + * stripped by the parser before we get the string. + * newval: An array of strings containing the new property as specified + * on the command line + * count: The number of strings in the array + * data: A bytestream to be placed in the property + * len: The length of the resulting bytestream + */ +static int fdt_parse_prop(char * const *newval, int count, char *data, int *len) +{ + char *cp; /* temporary char pointer */ + char *newp; /* temporary newval char pointer */ + unsigned long tmp; /* holds converted values */ + int stridx = 0; + + *len = 0; + newp = newval[0]; + + /* An array of cells */ + if (*newp == '<') { + newp++; + while ((*newp != '>') && (stridx < count)) { + /* + * Keep searching until we find that last ">" + * That way users don't have to escape the spaces + */ + if (*newp == '\0') { + newp = newval[++stridx]; + continue; + } + + cp = newp; + tmp = simple_strtoul(cp, &newp, 0); + if (*cp != '?') + *(fdt32_t *)data = cpu_to_fdt32(tmp); + else + newp++; + + data += 4; + *len += 4; + + /* If the ptr didn't advance, something went wrong */ + if ((newp - cp) <= 0) { + printf("Sorry, I could not convert \"%s\"\n", + cp); + return 1; + } + + while (*newp == ' ') + newp++; + } + + if (*newp != '>') { + printf("Unexpected character '%c'\n", *newp); + return 1; + } + } else if (*newp == '[') { + /* + * Byte stream. Convert the values. + */ + newp++; + while ((stridx < count) && (*newp != ']')) { + while (*newp == ' ') + newp++; + if (*newp == '\0') { + newp = newval[++stridx]; + continue; + } + if (!isxdigit(*newp)) + break; + tmp = hextoul(newp, &newp); + *data++ = tmp & 0xFF; + *len = *len + 1; + } + if (*newp != ']') { + printf("Unexpected character '%c'\n", *newp); + return 1; + } + } else { + /* + * Assume it is one or more strings. Copy it into our + * data area for convenience (including the + * terminating '\0's). + */ + while (stridx < count) { + size_t length = strlen(newp) + 1; + strcpy(data, newp); + data += length; + *len += length; + newp = newval[++stridx]; + } + } + return 0; +} + +/****************************************************************************/ + +/* + * Heuristic to guess if this is a string or concatenated strings. + */ + +static int is_printable_string(const void *data, int len) +{ + const char *s = data; + const char *ss, *se; + + /* zero length is not */ + if (len == 0) + return 0; + + /* must terminate with zero */ + if (s[len - 1] != '\0') + return 0; + + se = s + len; + + while (s < se) { + ss = s; + while (s < se && *s && isprint((unsigned char)*s)) + s++; + + /* not zero, or not done yet */ + if (*s != '\0' || s == ss) + return 0; + + s++; + } + + return 1; +} + +/* + * Print the property in the best format, a heuristic guess. Print as + * a string, concatenated strings, a byte, word, double word, or (if all + * else fails) it is printed as a stream of bytes. + */ +static void print_data(const void *data, int len) +{ + int j; + const char *env_max_dump; + ulong max_dump = ULONG_MAX; + + /* no data, don't print */ + if (len == 0) + return; + + env_max_dump = env_get("fdt_max_dump"); + if (env_max_dump) + max_dump = hextoul(env_max_dump, NULL); + + /* + * It is a string, but it may have multiple strings (embedded '\0's). + */ + if (is_printable_string(data, len)) { + puts("\""); + j = 0; + while (j < len) { + if (j > 0) + puts("\", \""); + puts(data); + j += strlen(data) + 1; + data += strlen(data) + 1; + } + puts("\""); + return; + } + + if ((len %4) == 0) { + if (len > max_dump) + printf("* 0x%p [0x%08x]", data, len); + else { + const __be32 *p; + + printf("<"); + for (j = 0, p = data; j < len/4; j++) + printf("0x%08x%s", fdt32_to_cpu(p[j]), + j < (len/4 - 1) ? " " : ""); + printf(">"); + } + } else { /* anything else... hexdump */ + if (len > max_dump) + printf("* 0x%p [0x%08x]", data, len); + else { + const u8 *s; + + printf("["); + for (j = 0, s = data; j < len; j++) + printf("%02x%s", s[j], j < len - 1 ? " " : ""); + printf("]"); + } + } +} + +/****************************************************************************/ + +/* + * Recursively print (a portion of) the working_fdt. The depth parameter + * determines how deeply nested the fdt is printed. + */ +static int fdt_print(const char *pathp, char *prop, int depth) +{ + static char tabs[MAX_LEVEL+1] = + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t" + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + const void *nodep; /* property node pointer */ + int nodeoffset; /* node offset from libfdt */ + int nextoffset; /* next node offset from libfdt */ + uint32_t tag; /* tag */ + int len; /* length of the property */ + int level = 0; /* keep track of nesting level */ + const struct fdt_property *fdt_prop; + + nodeoffset = fdt_path_offset (working_fdt, pathp); + if (nodeoffset < 0) { + /* + * Not found or something else bad happened. + */ + printf ("libfdt fdt_path_offset() returned %s\n", + fdt_strerror(nodeoffset)); + return 1; + } + /* + * The user passed in a property as well as node path. + * Print only the given property and then return. + */ + if (prop) { + nodep = fdt_getprop (working_fdt, nodeoffset, prop, &len); + if (len == 0) { + /* no property value */ + printf("%s %s\n", pathp, prop); + return 0; + } else if (nodep && len > 0) { + printf("%s = ", prop); + print_data (nodep, len); + printf("\n"); + return 0; + } else { + printf ("libfdt fdt_getprop(): %s\n", + fdt_strerror(len)); + return 1; + } + } + + /* + * The user passed in a node path and no property, + * print the node and all subnodes. + */ + while(level >= 0) { + tag = fdt_next_tag(working_fdt, nodeoffset, &nextoffset); + switch(tag) { + case FDT_BEGIN_NODE: + pathp = fdt_get_name(working_fdt, nodeoffset, NULL); + if (level <= depth) { + if (pathp == NULL) + pathp = "/* NULL pointer error */"; + if (*pathp == '\0') + pathp = "/"; /* root is nameless */ + printf("%s%s {\n", + &tabs[MAX_LEVEL - level], pathp); + } + level++; + if (level >= MAX_LEVEL) { + printf("Nested too deep, aborting.\n"); + return 1; + } + break; + case FDT_END_NODE: + level--; + if (level <= depth) + printf("%s};\n", &tabs[MAX_LEVEL - level]); + if (level == 0) { + level = -1; /* exit the loop */ + } + break; + case FDT_PROP: + fdt_prop = fdt_offset_ptr(working_fdt, nodeoffset, + sizeof(*fdt_prop)); + pathp = fdt_string(working_fdt, + fdt32_to_cpu(fdt_prop->nameoff)); + len = fdt32_to_cpu(fdt_prop->len); + nodep = fdt_prop->data; + if (len < 0) { + printf ("libfdt fdt_getprop(): %s\n", + fdt_strerror(len)); + return 1; + } else if (len == 0) { + /* the property has no value */ + if (level <= depth) + printf("%s%s;\n", + &tabs[MAX_LEVEL - level], + pathp); + } else { + if (level <= depth) { + printf("%s%s = ", + &tabs[MAX_LEVEL - level], + pathp); + print_data (nodep, len); + printf(";\n"); + } + } + break; + case FDT_NOP: + printf("%s/* NOP */\n", &tabs[MAX_LEVEL - level]); + break; + case FDT_END: + return 1; + default: + if (level <= depth) + printf("Unknown tag 0x%08X\n", tag); + return 1; + } + nodeoffset = nextoffset; + } + return 0; +} + +/********************************************************************/ +U_BOOT_LONGHELP(fdt, + "addr [-c] [-q] <addr> [<size>] - Set the [control] fdt location to <addr>\n" +#ifdef CONFIG_OF_LIBFDT_OVERLAY + "fdt apply <addr> - Apply overlay to the DT\n" +#endif +#ifdef CONFIG_OF_BOARD_SETUP + "fdt boardsetup - Do board-specific set up\n" +#endif +#ifdef CONFIG_OF_SYSTEM_SETUP + "fdt systemsetup - Do system-specific set up\n" +#endif + "fdt move <fdt> <newaddr> <length> - Copy the fdt to <addr> and make it active\n" + "fdt resize [<extrasize>] - Resize fdt to size + padding to 4k addr + some optional <extrasize> if needed\n" + "fdt print <path> [<prop>] - Recursive print starting at <path>\n" + "fdt list <path> [<prop>] - Print one level starting at <path>\n" + "fdt get value <var> <path> <prop> [<index>] - Get <property> and store in <var>\n" + " In case of stringlist property, use optional <index>\n" + " to select string within the stringlist. Default is 0.\n" + "fdt get name <var> <path> <index> - Get name of node <index> and store in <var>\n" + "fdt get addr <var> <path> <prop> - Get start address of <property> and store in <var>\n" + "fdt get size <var> <path> [<prop>] - Get size of [<property>] or num nodes and store in <var>\n" + "fdt set <path> <prop> [<val>] - Set <property> [to <val>]\n" + "fdt mknode <path> <node> - Create a new node after <path>\n" + "fdt rm <path> [<prop>] - Delete the node or <property>\n" + "fdt header [get <var> <member>] - Display header info\n" + " get - get header member <member> and store it in <var>\n" + "fdt bootcpu <id> - Set boot cpuid\n" + "fdt memory <addr> <size> - Add/Update memory node\n" + "fdt rsvmem print - Show current mem reserves\n" + "fdt rsvmem add <addr> <size> - Add a mem reserve\n" + "fdt rsvmem delete <index> - Delete a mem reserves\n" + "fdt chosen [<start> <size>] - Add/update the /chosen branch in the tree\n" + " <start>/<size> - initrd start addr/size\n" +#if defined(CONFIG_FIT_SIGNATURE) + "fdt checksign [<addr>] - check FIT signature\n" + " <addr> - address of key blob\n" + " default gd->fdt_blob\n" +#endif + "NOTE: Dereference aliases by omitting the leading '/', " + "e.g. fdt print ethernet0."); + +U_BOOT_CMD( + fdt, 255, 0, do_fdt, + "flattened device tree utility commands", fdt_help_text +); diff --git a/cmd/flash.c b/cmd/flash.c new file mode 100644 index 00000000000..76aa387ba59 --- /dev/null +++ b/cmd/flash.c @@ -0,0 +1,700 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * FLASH support + */ +#include <command.h> +#include <log.h> +#include <vsprintf.h> +#include <linux/string.h> +#include <u-boot/uuid.h> + +#if defined(CONFIG_CMD_MTDPARTS) +#include <jffs2/jffs2.h> + +/* partition handling routines */ +int mtdparts_init(void); +int mtd_id_parse(const char *id, const char **ret_id, u8 *dev_type, u8 *dev_num); +int find_dev_and_part(const char *id, struct mtd_device **dev, + u8 *part_num, struct part_info **part); +#endif + +#ifdef CONFIG_MTD_NOR_FLASH +#include <flash.h> +#include <mtd/cfi_flash.h> + +/* + * The user interface starts numbering for Flash banks with 1 + * for historical reasons. + */ + +/* + * this routine looks for an abbreviated flash range specification. + * the syntax is B:SF[-SL], where B is the bank number, SF is the first + * sector to erase, and SL is the last sector to erase (defaults to SF). + * bank numbers start at 1 to be consistent with other specs, sector numbers + * start at zero. + * + * returns: 1 - correct spec; *pinfo, *psf and *psl are + * set appropriately + * 0 - doesn't look like an abbreviated spec + * -1 - looks like an abbreviated spec, but got + * a parsing error, a number out of range, + * or an invalid flash bank. + */ +static int +abbrev_spec(char *str, flash_info_t **pinfo, int *psf, int *psl) +{ + flash_info_t *fp; + int bank, first, last; + char *p, *ep; + + p = strchr(str, ':'); + if (!p) + return 0; + *p++ = '\0'; + + bank = dectoul(str, &ep); + if (ep == str || *ep != '\0' || + bank < 1 || bank > CFI_FLASH_BANKS) + return -1; + + fp = &flash_info[bank - 1]; + if (fp->flash_id == FLASH_UNKNOWN) + return -1; + + str = p; + p = strchr(str, '-'); + if (p) + *p++ = '\0'; + + first = dectoul(str, &ep); + if (ep == str || *ep != '\0' || first >= fp->sector_count) + return -1; + + if (p) { + last = dectoul(p, &ep); + if (ep == p || *ep != '\0' || + last < first || last >= fp->sector_count) + return -1; + } else { + last = first; + } + + *pinfo = fp; + *psf = first; + *psl = last; + + return 1; +} + +/* + * Take *addr in Flash and adjust it to fall on the end of its sector + */ +int flash_sect_roundb(ulong *addr) +{ + flash_info_t *info; + ulong bank, sector_end_addr; + char found; + int i; + + /* find the end addr of the sector where the *addr is */ + found = 0; + for (bank = 0; bank < CFI_FLASH_BANKS && !found; ++bank) { + info = &flash_info[bank]; + for (i = 0; i < info->sector_count && !found; ++i) { + /* get the end address of the sector */ + if (i == info->sector_count - 1) { + sector_end_addr = info->start[0] + + info->size - 1; + } else { + sector_end_addr = info->start[i + 1] - 1; + } + + if (*addr <= sector_end_addr && *addr >= info->start[i]) { + found = 1; + /* adjust *addr if necessary */ + if (*addr < sector_end_addr) + *addr = sector_end_addr; + } /* sector */ + } /* bank */ + } + if (!found) { + /* error, address not in flash */ + printf("Error: end address (0x%08lx) not in flash!\n", *addr); + return 1; + } + + return 0; +} + +/* + * This function computes the start and end addresses for both + * erase and protect commands. The range of the addresses on which + * either of the commands is to operate can be given in two forms: + * 1. <cmd> start end - operate on <'start', 'end') + * 2. <cmd> start +length - operate on <'start', start + length) + * If the second form is used and the end address doesn't fall on the + * sector boundary, than it will be adjusted to the next sector boundary. + * If it isn't in the flash, the function will fail (return -1). + * Input: + * arg1, arg2: address specification (i.e. both command arguments) + * Output: + * addr_first, addr_last: computed address range + * Return: + * 1: success + * -1: failure (bad format, bad address). + */ +static int +addr_spec(char *arg1, char *arg2, ulong *addr_first, ulong *addr_last) +{ + char *ep; + char len_used; /* indicates if the "start +length" form used */ + + *addr_first = hextoul(arg1, &ep); + if (ep == arg1 || *ep != '\0') + return -1; + + len_used = 0; + if (arg2 && *arg2 == '+') { + len_used = 1; + ++arg2; + } + + *addr_last = hextoul(arg2, &ep); + if (ep == arg2 || *ep != '\0') + return -1; + + if (len_used) { + /* + * *addr_last has the length, compute correct *addr_last + * XXX watch out for the integer overflow! Right now it is + * checked for in both the callers. + */ + *addr_last = *addr_first + *addr_last - 1; + + /* + * It may happen that *addr_last doesn't fall on the sector + * boundary. We want to round such an address to the next + * sector boundary, so that the commands don't fail later on. + */ + + if (flash_sect_roundb(addr_last) > 0) + return -1; + } /* "start +length" from used */ + + return 1; +} + +static int +flash_fill_sect_ranges(ulong addr_first, ulong addr_last, + int *s_first, int *s_last, + int *s_count) +{ + flash_info_t *info; + ulong bank; + int rcode = 0; + + *s_count = 0; + + for (bank = 0; bank < CFI_FLASH_BANKS; ++bank) { + s_first[bank] = -1; /* first sector to erase */ + s_last[bank] = -1; /* last sector to erase */ + } + + for (bank = 0, info = &flash_info[0]; + (bank < CFI_FLASH_BANKS) && (addr_first <= addr_last); + ++bank, ++info) { + ulong b_end; + int sect; + short s_end; + + if (info->flash_id == FLASH_UNKNOWN) + continue; + + b_end = info->start[0] + info->size - 1; /* bank end addr */ + s_end = info->sector_count - 1; /* last sector */ + + for (sect = 0; sect < info->sector_count; ++sect) { + ulong end; /* last address in current sect */ + + end = (sect == s_end) ? b_end : info->start[sect + 1] - 1; + + if (addr_first > end) + continue; + if (addr_last < info->start[sect]) + continue; + + if (addr_first == info->start[sect]) + s_first[bank] = sect; + + if (addr_last == end) + s_last[bank] = sect; + } + if (s_first[bank] >= 0) { + if (s_last[bank] < 0) { + if (addr_last > b_end) { + s_last[bank] = s_end; + } else { + puts("Error: end address not on sector boundary\n"); + rcode = 1; + break; + } + } + if (s_last[bank] < s_first[bank]) { + puts("Error: end sector precedes start sector\n"); + rcode = 1; + break; + } + sect = s_last[bank]; + addr_first = (sect == s_end) ? b_end + 1 : info->start[sect + 1]; + (*s_count) += s_last[bank] - s_first[bank] + 1; + } else if (addr_first >= info->start[0] && addr_first < b_end) { + puts("Error: start address not on sector boundary\n"); + rcode = 1; + break; + } else if (s_last[bank] >= 0) { + puts("Error: cannot span across banks when they are" + " mapped in reverse order\n"); + rcode = 1; + break; + } + } + + return rcode; +} +#endif /* CONFIG_MTD_NOR_FLASH */ + +static int do_flinfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ +#ifdef CONFIG_MTD_NOR_FLASH + ulong bank; +#endif + +#ifdef CONFIG_MTD_NOR_FLASH + if (argc == 1) { /* print info for all FLASH banks */ + for (bank = 0; bank < CFI_FLASH_BANKS; ++bank) { + printf("\nBank # %ld: ", bank + 1); + + flash_print_info(&flash_info[bank]); + } + return 0; + } + + bank = hextoul(argv[1], NULL); + if (bank < 1 || bank > CFI_FLASH_BANKS) { + printf("Only FLASH Banks # 1 ... # %d supported\n", + CFI_FLASH_BANKS); + return 1; + } + printf("\nBank # %ld: ", bank); + flash_print_info(&flash_info[bank - 1]); +#endif /* CONFIG_MTD_NOR_FLASH */ + return 0; +} + +static int do_flerase(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ +#ifdef CONFIG_MTD_NOR_FLASH + flash_info_t *info = NULL; + ulong bank, addr_first, addr_last; + int n, sect_first = 0, sect_last = 0; +#if defined(CONFIG_CMD_MTDPARTS) + struct mtd_device *dev; + struct part_info *part; + u8 dev_type, dev_num, pnum; +#endif + int rcode = 0; + + if (argc < 2) + return CMD_RET_USAGE; + + if (strcmp(argv[1], "all") == 0) { + for (bank = 1; bank <= CFI_FLASH_BANKS; ++bank) { + printf("Erase Flash Bank # %ld ", bank); + info = &flash_info[bank - 1]; + rcode = flash_erase(info, 0, info->sector_count - 1); + } + return rcode; + } + + n = abbrev_spec(argv[1], &info, §_first, §_last); + if (n) { + if (n < 0) { + puts("Bad sector specification\n"); + return 1; + } + printf("Erase Flash Sectors %d-%d in Bank # %zu ", + sect_first, sect_last, (info - flash_info) + 1); + rcode = flash_erase(info, sect_first, sect_last); + return rcode; + } + +#if defined(CONFIG_CMD_MTDPARTS) + /* erase <part-id> - erase partition */ + if (argc == 2 && mtd_id_parse(argv[1], NULL, &dev_type, &dev_num) == 0) { + mtdparts_init(); + if (find_dev_and_part(argv[1], &dev, &pnum, &part) == 0) { + if (dev->id->type == MTD_DEV_TYPE_NOR) { + bank = dev->id->num; + info = &flash_info[bank]; + addr_first = part->offset + info->start[0]; + addr_last = addr_first + part->size - 1; + + printf("Erase Flash Partition %s, bank %ld, 0x%08lx - 0x%08lx ", + argv[1], bank, addr_first, + addr_last); + + rcode = flash_sect_erase(addr_first, addr_last); + return rcode; + } + + printf("cannot erase, not a NOR device\n"); + return 1; + } + } +#endif + + if (argc != 3) + return CMD_RET_USAGE; + + if (strcmp(argv[1], "bank") == 0) { + bank = hextoul(argv[2], NULL); + if (bank < 1 || bank > CFI_FLASH_BANKS) { + printf("Only FLASH Banks # 1 ... # %d supported\n", + CFI_FLASH_BANKS); + return 1; + } + printf("Erase Flash Bank # %ld ", bank); + info = &flash_info[bank - 1]; + rcode = flash_erase(info, 0, info->sector_count - 1); + return rcode; + } + + if (addr_spec(argv[1], argv[2], &addr_first, &addr_last) < 0) { + printf("Bad address format\n"); + return 1; + } + + if (addr_first >= addr_last) + return CMD_RET_USAGE; + + rcode = flash_sect_erase(addr_first, addr_last); + return rcode; +#else + return 0; +#endif /* CONFIG_MTD_NOR_FLASH */ +} + +#ifdef CONFIG_MTD_NOR_FLASH +int flash_sect_erase(ulong addr_first, ulong addr_last) +{ + flash_info_t *info; + ulong bank; + int s_first[CFI_FLASH_BANKS], s_last[CFI_FLASH_BANKS]; + int erased = 0; + int planned; + int rcode = 0; + + rcode = flash_fill_sect_ranges(addr_first, addr_last, s_first, s_last, &planned); + + if (planned && rcode == 0) { + for (bank = 0, info = &flash_info[0]; + bank < CFI_FLASH_BANKS && rcode == 0; + ++bank, ++info) { + if (s_first[bank] >= 0) { + erased += s_last[bank] - s_first[bank] + 1; + debug("Erase Flash from 0x%08lx to 0x%08lx in Bank # %ld ", + info->start[s_first[bank]], + (s_last[bank] == info->sector_count) ? + info->start[0] + info->size - 1 : + info->start[s_last[bank] + 1] - 1, + bank + 1); + rcode = flash_erase(info, s_first[bank], + s_last[bank]); + } + } + if (rcode == 0) + printf("Erased %d sectors\n", erased); + } else if (rcode == 0) { + puts("Error: start and/or end address not on sector boundary\n"); + rcode = 1; + } + return rcode; +} +#endif /* CONFIG_MTD_NOR_FLASH */ + +static int do_protect(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int rcode = 0; +#ifdef CONFIG_MTD_NOR_FLASH + flash_info_t *info = NULL; + ulong bank; + int i, n, sect_first = 0, sect_last = 0; +#if defined(CONFIG_CMD_MTDPARTS) + struct mtd_device *dev; + struct part_info *part; + u8 dev_type, dev_num, pnum; +#endif +#endif /* CONFIG_MTD_NOR_FLASH */ +#if defined(CONFIG_MTD_NOR_FLASH) + int p; + ulong addr_first, addr_last; +#endif + + if (argc < 3) + return CMD_RET_USAGE; + +#if defined(CONFIG_MTD_NOR_FLASH) + if (strcmp(argv[1], "off") == 0) + p = 0; + else if (strcmp(argv[1], "on") == 0) + p = 1; + else + return CMD_RET_USAGE; +#endif + +#ifdef CONFIG_MTD_NOR_FLASH + if (strcmp(argv[2], "all") == 0) { + for (bank = 1; bank <= CFI_FLASH_BANKS; ++bank) { + info = &flash_info[bank - 1]; + if (info->flash_id == FLASH_UNKNOWN) + continue; + + printf("%sProtect Flash Bank # %ld\n", + p ? "" : "Un-", bank); + + for (i = 0; i < info->sector_count; ++i) { +#if defined(CONFIG_SYS_FLASH_PROTECTION) + if (flash_real_protect(info, i, p)) + rcode = 1; + putc('.'); +#else + info->protect[i] = p; +#endif /* CONFIG_SYS_FLASH_PROTECTION */ + } +#if defined(CONFIG_SYS_FLASH_PROTECTION) + if (!rcode) + puts(" done\n"); +#endif /* CONFIG_SYS_FLASH_PROTECTION */ + } + return rcode; + } + n = abbrev_spec(argv[2], &info, §_first, §_last); + if (n) { + if (n < 0) { + puts("Bad sector specification\n"); + return 1; + } + printf("%sProtect Flash Sectors %d-%d in Bank # %zu\n", + p ? "" : "Un-", sect_first, sect_last, + (info - flash_info) + 1); + for (i = sect_first; i <= sect_last; i++) { +#if defined(CONFIG_SYS_FLASH_PROTECTION) + if (flash_real_protect(info, i, p)) + rcode = 1; + putc('.'); +#else + info->protect[i] = p; +#endif /* CONFIG_SYS_FLASH_PROTECTION */ + } + +#if defined(CONFIG_SYS_FLASH_PROTECTION) + if (!rcode) + puts(" done\n"); +#endif /* CONFIG_SYS_FLASH_PROTECTION */ + + return rcode; + } + +#if defined(CONFIG_CMD_MTDPARTS) + /* protect on/off <part-id> */ + if (argc == 3 && mtd_id_parse(argv[2], NULL, &dev_type, &dev_num) == 0) { + mtdparts_init(); + if (find_dev_and_part(argv[2], &dev, &pnum, &part) == 0) { + if (dev->id->type == MTD_DEV_TYPE_NOR) { + bank = dev->id->num; + info = &flash_info[bank]; + addr_first = part->offset + info->start[0]; + addr_last = addr_first + part->size - 1; + + printf("%sProtect Flash Partition %s, " + "bank %ld, 0x%08lx - 0x%08lx\n", + p ? "" : "Un", argv[1], + bank, addr_first, addr_last); + + rcode = flash_sect_protect(p, addr_first, + addr_last); + return rcode; + } + + printf("cannot %sprotect, not a NOR device\n", + p ? "" : "un"); + return 1; + } + } +#endif + + if (argc != 4) + return CMD_RET_USAGE; + + if (strcmp(argv[2], "bank") == 0) { + bank = hextoul(argv[3], NULL); + if (bank < 1 || bank > CFI_FLASH_BANKS) { + printf("Only FLASH Banks # 1 ... # %d supported\n", + CFI_FLASH_BANKS); + return 1; + } + printf("%sProtect Flash Bank # %ld\n", + p ? "" : "Un-", bank); + info = &flash_info[bank - 1]; + + if (info->flash_id == FLASH_UNKNOWN) { + puts("missing or unknown FLASH type\n"); + return 1; + } + for (i = 0; i < info->sector_count; ++i) { +#if defined(CONFIG_SYS_FLASH_PROTECTION) + if (flash_real_protect(info, i, p)) + rcode = 1; + putc('.'); +#else + info->protect[i] = p; +#endif /* CONFIG_SYS_FLASH_PROTECTION */ + } + +#if defined(CONFIG_SYS_FLASH_PROTECTION) + if (!rcode) + puts(" done\n"); +#endif /* CONFIG_SYS_FLASH_PROTECTION */ + + return rcode; + } + + if (addr_spec(argv[2], argv[3], &addr_first, &addr_last) < 0) { + printf("Bad address format\n"); + return 1; + } + + if (addr_first >= addr_last) + return CMD_RET_USAGE; + + rcode = flash_sect_protect(p, addr_first, addr_last); +#endif /* CONFIG_MTD_NOR_FLASH */ + return rcode; +} + +#ifdef CONFIG_MTD_NOR_FLASH +int flash_sect_protect(int p, ulong addr_first, ulong addr_last) +{ + flash_info_t *info; + ulong bank; + int s_first[CFI_FLASH_BANKS], s_last[CFI_FLASH_BANKS]; + int protected, i; + int planned; + int rcode; + + rcode = flash_fill_sect_ranges(addr_first, addr_last, s_first, s_last, &planned); + + protected = 0; + + if (planned && rcode == 0) { + for (bank = 0, info = &flash_info[0]; + bank < CFI_FLASH_BANKS; ++bank, ++info) { + if (info->flash_id == FLASH_UNKNOWN) + continue; + + if (s_first[bank] >= 0 && s_first[bank] <= s_last[bank]) { + debug("%sProtecting sectors %d..%d in bank %ld\n", + p ? "" : "Un-", s_first[bank], + s_last[bank], bank + 1); + protected += s_last[bank] - s_first[bank] + 1; + for (i = s_first[bank]; i <= s_last[bank]; ++i) { +#if defined(CONFIG_SYS_FLASH_PROTECTION) + if (flash_real_protect(info, i, p)) + rcode = 1; + putc('.'); +#else + info->protect[i] = p; +#endif /* CONFIG_SYS_FLASH_PROTECTION */ + } + } + } +#if defined(CONFIG_SYS_FLASH_PROTECTION) + puts(" done\n"); +#endif /* CONFIG_SYS_FLASH_PROTECTION */ + + printf("%sProtected %d sectors\n", + p ? "" : "Un-", protected); + } else if (rcode == 0) { + puts("Error: start and/or end address not on sector boundary\n"); + rcode = 1; + } + return rcode; +} +#endif /* CONFIG_MTD_NOR_FLASH */ + +/**************************************************/ +#if defined(CONFIG_CMD_MTDPARTS) +# define TMP_ERASE "erase <part-id>\n - erase partition\n" +# define TMP_PROT_ON "protect on <part-id>\n - protect partition\n" +# define TMP_PROT_OFF "protect off <part-id>\n - make partition writable\n" +#else +# define TMP_ERASE /* empty */ +# define TMP_PROT_ON /* empty */ +# define TMP_PROT_OFF /* empty */ +#endif + +U_BOOT_CMD( + flinfo, 2, 1, do_flinfo, + "print FLASH memory information", + "\n - print information for all FLASH memory banks\n" + "flinfo N\n - print information for FLASH memory bank # N" +); + +U_BOOT_CMD( + erase, 3, 0, do_flerase, + "erase FLASH memory", + "start end\n" + " - erase FLASH from addr 'start' to addr 'end'\n" + "erase start +len\n" + " - erase FLASH from addr 'start' to the end of sect w/addr 'start'+'len'-1\n" + "erase N:SF[-SL]\n - erase sectors SF-SL in FLASH bank # N\n" + "erase bank N\n - erase FLASH bank # N\n" + TMP_ERASE + "erase all\n - erase all FLASH banks" +); + +U_BOOT_CMD( + protect, 4, 0, do_protect, + "enable or disable FLASH write protection", + "on start end\n" + " - protect FLASH from addr 'start' to addr 'end'\n" + "protect on start +len\n" + " - protect FLASH from addr 'start' to end of sect w/addr 'start'+'len'-1\n" + "protect on N:SF[-SL]\n" + " - protect sectors SF-SL in FLASH bank # N\n" + "protect on bank N\n - protect FLASH bank # N\n" + TMP_PROT_ON + "protect on all\n - protect all FLASH banks\n" + "protect off start end\n" + " - make FLASH from addr 'start' to addr 'end' writable\n" + "protect off start +len\n" + " - make FLASH from addr 'start' to end of sect w/addr 'start'+'len'-1 wrtable\n" + "protect off N:SF[-SL]\n" + " - make sectors SF-SL writable in FLASH bank # N\n" + "protect off bank N\n - make FLASH bank # N writable\n" + TMP_PROT_OFF + "protect off all\n - make all FLASH banks writable" +); + +#undef TMP_ERASE +#undef TMP_PROT_ON +#undef TMP_PROT_OFF diff --git a/cmd/font.c b/cmd/font.c new file mode 100644 index 00000000000..36e41203654 --- /dev/null +++ b/cmd/font.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * video commands + * + * Copyright 2022 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <dm.h> +#include <video.h> +#include <video_console.h> + +static int do_font_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + + if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) + return CMD_RET_FAILURE; + vidconsole_list_fonts(dev); + + return 0; +} + +static int do_font_select(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + const char *name; + uint size = 0; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) + return CMD_RET_FAILURE; + name = argv[1]; + if (argc == 3) + size = dectoul(argv[2], NULL); + ret = vidconsole_select_font(dev, name, size); + if (ret) { + printf("Failed (error %d)\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} +static int do_font_size(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *font_name; + struct udevice *dev; + uint size; + int ret; + + if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) + return CMD_RET_FAILURE; + ret = vidconsole_get_font_size(dev, &font_name, &size); + if (ret) { + printf("Failed (error %d)\n", ret); + return CMD_RET_FAILURE; + } + + if (argc < 2) { + printf("%d\n", size); + } else { + size = dectoul(argv[1], NULL); + + ret = vidconsole_select_font(dev, font_name, size); + if (ret) { + printf("Failed (error %d)\n", ret); + return CMD_RET_FAILURE; + } + } + + return 0; +} + +U_BOOT_LONGHELP(font, + "list - list available fonts\n" + "font select <name> [<size>] - select font to use\n" + "font size <size> - select font size to"); + +U_BOOT_CMD_WITH_SUBCMDS(font, "Fonts", font_help_text, + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_font_list), + U_BOOT_SUBCMD_MKENT(select, 3, 1, do_font_select), + U_BOOT_SUBCMD_MKENT(size, 2, 1, do_font_size)); diff --git a/cmd/fpga.c b/cmd/fpga.c new file mode 100644 index 00000000000..d51c380d7b3 --- /dev/null +++ b/cmd/fpga.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000, 2001 + * Rich Ireland, Enterasys Networks, rireland@enterasys.com. + */ + +/* + * FPGA support + */ +#include <command.h> +#include <env.h> +#include <fpga.h> +#include <fs.h> +#include <gzip.h> +#include <image.h> +#include <log.h> +#include <malloc.h> + +static long do_fpga_get_device(char *arg) +{ + long dev = FPGA_INVALID_DEVICE; + char *devstr = env_get("fpga"); + + if (devstr) + /* Should be strtol to handle -1 cases */ + dev = simple_strtol(devstr, NULL, 16); + + if (dev == FPGA_INVALID_DEVICE && arg) + dev = simple_strtol(arg, NULL, 16); + + log_debug("device = %ld\n", dev); + + return dev; +} + +static int do_fpga_check_params(long *dev, long *fpga_data, size_t *data_size, + struct cmd_tbl *cmdtp, int argc, + char *const argv[]) +{ + size_t local_data_size; + long local_fpga_data; + + log_debug("%d, %d\n", argc, cmdtp->maxargs); + + if (argc != cmdtp->maxargs) { + log_err("Incorrect number of parameters passed\n"); + return CMD_RET_FAILURE; + } + + *dev = do_fpga_get_device(argv[0]); + + local_fpga_data = simple_strtol(argv[1], NULL, 16); + if (!local_fpga_data) { + log_err("Zero fpga_data address\n"); + return CMD_RET_FAILURE; + } + *fpga_data = local_fpga_data; + + local_data_size = hextoul(argv[2], NULL); + if (!local_data_size) { + log_err("Zero size\n"); + return CMD_RET_FAILURE; + } + *data_size = local_data_size; + + return 0; +} + +#if defined(CONFIG_CMD_FPGA_LOAD_SECURE) +static int do_fpga_loads(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct fpga_secure_info fpga_sec_info; + const int pos_userkey = 5; + size_t data_size = 0; + long fpga_data, dev; + int ret; + + memset(&fpga_sec_info, 0, sizeof(fpga_sec_info)); + + if (argc < pos_userkey) { + log_err("Too few parameters passed\n"); + return CMD_RET_FAILURE; + } + + if (argc == pos_userkey + 1) + fpga_sec_info.userkey_addr = (u8 *)(uintptr_t) + simple_strtoull(argv[pos_userkey], + NULL, 16); + else + /* + * If 6th parameter is not passed then do_fpga_check_params + * will get 5 instead of expected 6 which means that function + * return CMD_RET_FAILURE. Increase number of params +1 to pass + * this. + */ + argc++; + + ret = do_fpga_check_params(&dev, &fpga_data, &data_size, + cmdtp, argc, argv); + if (ret) + return ret; + + fpga_sec_info.encflag = (u8)hextoul(argv[4], NULL); + fpga_sec_info.authflag = (u8)hextoul(argv[3], NULL); + + if (fpga_sec_info.authflag >= FPGA_NO_ENC_OR_NO_AUTH && + fpga_sec_info.encflag >= FPGA_NO_ENC_OR_NO_AUTH) { + log_err("Use <fpga load> for NonSecure bitstream\n"); + return CMD_RET_FAILURE; + } + + if (fpga_sec_info.encflag == FPGA_ENC_USR_KEY && + !fpga_sec_info.userkey_addr) { + log_err("User key not provided\n"); + return CMD_RET_FAILURE; + } + + return fpga_loads(dev, (void *)fpga_data, data_size, &fpga_sec_info); +} +#endif + +#if defined(CONFIG_CMD_FPGA_LOADFS) +static int do_fpga_loadfs(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + size_t data_size = 0; + long fpga_data, dev; + int ret; + fpga_fs_info fpga_fsinfo; + + ret = do_fpga_check_params(&dev, &fpga_data, &data_size, + cmdtp, argc, argv); + if (ret) + return ret; + + fpga_fsinfo.fstype = FS_TYPE_ANY; + fpga_fsinfo.blocksize = (unsigned int)hextoul(argv[3], NULL); + fpga_fsinfo.interface = argv[4]; + fpga_fsinfo.dev_part = argv[5]; + fpga_fsinfo.filename = argv[6]; + + return fpga_fsload(dev, (void *)fpga_data, data_size, &fpga_fsinfo); +} +#endif + +static int do_fpga_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + long dev = do_fpga_get_device(argv[0]); + + return fpga_info(dev); +} + +static int do_fpga_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + size_t data_size = 0; + long fpga_data, dev; + int ret; + + ret = do_fpga_check_params(&dev, &fpga_data, &data_size, + cmdtp, argc, argv); + if (ret) + return ret; + + return fpga_dump(dev, (void *)fpga_data, data_size); +} + +static int do_fpga_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + size_t data_size = 0; + long fpga_data, dev; + int ret; + + ret = do_fpga_check_params(&dev, &fpga_data, &data_size, + cmdtp, argc, argv); + if (ret) + return ret; + + return fpga_load(dev, (void *)fpga_data, data_size, BIT_FULL, 0); +} + +#if defined(CONFIG_CMD_FPGA_LOADB) +static int do_fpga_loadb(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + size_t data_size = 0; + long fpga_data, dev; + int ret; + + ret = do_fpga_check_params(&dev, &fpga_data, &data_size, + cmdtp, argc, argv); + if (ret) + return ret; + + return fpga_loadbitstream(dev, (void *)fpga_data, data_size, BIT_FULL); +} +#endif +#if defined(CONFIG_CMD_FPGA_LOADP) +static int do_fpga_loadp(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + size_t data_size = 0; + long fpga_data, dev; + int ret; + + ret = do_fpga_check_params(&dev, &fpga_data, &data_size, + cmdtp, argc, argv); + if (ret) + return ret; + + return fpga_load(dev, (void *)fpga_data, data_size, BIT_PARTIAL, 0); +} +#endif + +#if defined(CONFIG_CMD_FPGA_LOADBP) +static int do_fpga_loadbp(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + size_t data_size = 0; + long fpga_data, dev; + int ret; + + ret = do_fpga_check_params(&dev, &fpga_data, &data_size, + cmdtp, argc, argv); + if (ret) + return ret; + + return fpga_loadbitstream(dev, (void *)fpga_data, data_size, + BIT_PARTIAL); +} +#endif + +#if defined(CONFIG_CMD_FPGA_LOADMK) +static int do_fpga_loadmk(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + size_t data_size = 0; + void *fpga_data = NULL; +#if defined(CONFIG_FIT) + const char *fit_uname = NULL; + ulong fit_addr; +#endif + ulong dev = do_fpga_get_device(argv[0]); + char *datastr = env_get("fpgadata"); + + log_debug("argc %x, dev %lx, datastr %s\n", argc, dev, datastr); + + if (dev == FPGA_INVALID_DEVICE) { + log_err("Invalid fpga device\n"); + return CMD_RET_FAILURE; + } + + if (argc == 0 && !datastr) { + log_err("No datastr passed\n"); + return CMD_RET_FAILURE; + } + + if (argc == 2) { + datastr = argv[1]; + log_debug("Full command with two args\n"); + } else if (argc == 1 && !datastr) { + log_debug("Dev is setup - fpgadata passed\n"); + datastr = argv[0]; + } + +#if defined(CONFIG_FIT) + if (fit_parse_subimage(datastr, (ulong)fpga_data, + &fit_addr, &fit_uname)) { + fpga_data = (void *)fit_addr; + log_debug("* fpga: subimage '%s' from FIT image ", + fit_uname); + log_debug("at 0x%08lx\n", fit_addr); + } else +#endif + { + fpga_data = (void *)hextoul(datastr, NULL); + log_debug("* fpga: cmdline image address = 0x%08lx\n", + (ulong)fpga_data); + } + log_debug("fpga_data = 0x%lx\n", (ulong)fpga_data); + if (!fpga_data) { + log_err("Zero fpga_data address\n"); + return CMD_RET_FAILURE; + } + + switch (genimg_get_format(fpga_data)) { +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + case IMAGE_FORMAT_LEGACY: + { + struct legacy_img_hdr *hdr = (struct legacy_img_hdr *)fpga_data; + ulong data; + u8 comp; + + comp = image_get_comp(hdr); + if (comp == IH_COMP_GZIP) { +#if defined(CONFIG_GZIP) + ulong image_buf = image_get_data(hdr); + ulong image_size = ~0UL; + + data = image_get_load(hdr); + + if (gunzip((void *)data, ~0U, (void *)image_buf, + &image_size) != 0) { + log_err("Gunzip error\n"); + return CMD_RET_FAILURE; + } + data_size = image_size; +#else + log_err("Gunzip image is not supported\n"); + return CMD_RET_FAILURE; +#endif + } else { + data = (ulong)image_get_data(hdr); + data_size = image_get_data_size(hdr); + } + return fpga_load(dev, (void *)data, data_size, + BIT_FULL, 0); + } +#endif +#if defined(CONFIG_FIT) + case IMAGE_FORMAT_FIT: + { + const void *fit_hdr = (const void *)fpga_data; + int err; + const void *fit_data; + + if (!fit_uname) { + log_err("No FIT subimage unit name\n"); + return CMD_RET_FAILURE; + } + + if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) { + log_err("Bad FIT image format\n"); + return CMD_RET_FAILURE; + } + + err = fit_get_data_node(fit_hdr, fit_uname, &fit_data, + &data_size); + if (err) { + printf("Could not load '%s' subimage (err %d)\n", + fit_uname, err); + return CMD_RET_FAILURE; + } + + return fpga_load(dev, fit_data, data_size, BIT_FULL, 0); + } +#endif + default: + log_err("Unknown image type\n"); + return CMD_RET_FAILURE; + } +} +#endif + +static struct cmd_tbl fpga_commands[] = { + U_BOOT_CMD_MKENT(info, 1, 1, do_fpga_info, "", ""), + U_BOOT_CMD_MKENT(dump, 3, 1, do_fpga_dump, "", ""), + U_BOOT_CMD_MKENT(load, 3, 1, do_fpga_load, "", ""), +#if defined(CONFIG_CMD_FPGA_LOADB) + U_BOOT_CMD_MKENT(loadb, 3, 1, do_fpga_loadb, "", ""), +#endif +#if defined(CONFIG_CMD_FPGA_LOADP) + U_BOOT_CMD_MKENT(loadp, 3, 1, do_fpga_loadp, "", ""), +#endif +#if defined(CONFIG_CMD_FPGA_LOADBP) + U_BOOT_CMD_MKENT(loadbp, 3, 1, do_fpga_loadbp, "", ""), +#endif +#if defined(CONFIG_CMD_FPGA_LOADFS) + U_BOOT_CMD_MKENT(loadfs, 7, 1, do_fpga_loadfs, "", ""), +#endif +#if defined(CONFIG_CMD_FPGA_LOADMK) + U_BOOT_CMD_MKENT(loadmk, 2, 1, do_fpga_loadmk, "", ""), +#endif +#if defined(CONFIG_CMD_FPGA_LOAD_SECURE) + U_BOOT_CMD_MKENT(loads, 6, 1, do_fpga_loads, "", ""), +#endif +}; + +static int do_fpga_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *fpga_cmd; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + fpga_cmd = find_cmd_tbl(argv[1], fpga_commands, + ARRAY_SIZE(fpga_commands)); + if (!fpga_cmd) { + log_err("Non existing command\n"); + return CMD_RET_FAILURE; + } + + argc -= 2; + argv += 2; + + if (argc > fpga_cmd->maxargs) { + log_err("Too many parameters passed\n"); + return CMD_RET_FAILURE; + } + + ret = fpga_cmd->cmd(fpga_cmd, flag, argc, argv); + + return cmd_process_error(fpga_cmd, ret); +} + +#if defined(CONFIG_CMD_FPGA_LOADFS) || defined(CONFIG_CMD_FPGA_LOAD_SECURE) +U_BOOT_CMD(fpga, 9, 1, do_fpga_wrapper, +#else +U_BOOT_CMD(fpga, 6, 1, do_fpga_wrapper, +#endif + "loadable FPGA image support", + "info [dev] List known device information\n" + "fpga dump <dev> <address> <size> Load device to memory buffer\n" + "fpga load <dev> <address> <size> Load device from memory buffer\n" +#if defined(CONFIG_CMD_FPGA_LOADP) + "fpga loadb <dev> <address> <size> Load device from bitstream buffer\n" +#endif +#if defined(CONFIG_CMD_FPGA_LOADP) + "fpga loadp <dev> <address> <size> Load device from memory buffer\n" + " with partial bitstream\n" +#endif +#if defined(CONFIG_CMD_FPGA_LOADBP) + "fpga loadbp <dev> <address> <size> Load device from bitstream buffer\n" + " with partial bitstream\n" +#endif +#if defined(CONFIG_CMD_FPGA_LOADFS) + "fpga loadfs <dev> <address> <size> <blocksize> <interface> [<dev[:part]>] <filename>\n" + " Load device from filesystem (FAT by default)\n" +#endif +#if defined(CONFIG_CMD_FPGA_LOADMK) + "fpga loadmk <dev> <address> Load device generated with mkimage\n" +#if defined(CONFIG_FIT) + " NOTE: loadmk operating on FIT must include subimage unit\n" + " name in the form of addr:<subimg_uname>\n" +#endif +#endif +#if defined(CONFIG_CMD_FPGA_LOAD_SECURE) + "fpga loads <dev> <address> <size> <authflag> <encflag> [Userkey address]\n" + " Load device from memory buffer with secure bistream\n" + " (authenticated/encrypted/both)\n" + " -authflag: 0 for OCM, 1 for DDR, 2 for no authentication\n" + " (specifies where to perform authentication)\n" + " -encflag: 0 for device key, 1 for user key, 2 for no encryption\n" + " -Userkey address: address where user key is stored\n" + " NOTE: secure bitstream has to be created using Xilinx bootgen tool\n" +#endif +); diff --git a/cmd/fpgad.c b/cmd/fpgad.c new file mode 100644 index 00000000000..b4bfaa12165 --- /dev/null +++ b/cmd/fpgad.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2013 + * Dirk Eibach, Guntermann & Drunck GmbH, dirk.eibach@gdsys.cc + * + * based on cmd_mem.c + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <console.h> +#include <display_options.h> +#include <vsprintf.h> + +#include <gdsys_fpga.h> + +static uint dp_last_fpga; +static uint dp_last_addr; +static uint dp_last_length = 0x40; + +/* + * FPGA Memory Display + * + * Syntax: + * fpgad {fpga} {addr} {len} + */ +#define DISP_LINE_LEN 16 +int do_fpga_md(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + unsigned int k; + unsigned int fpga; + ulong addr, length; + int rc = 0; + u16 linebuf[DISP_LINE_LEN/sizeof(u16)]; + ulong nbytes; + + /* + * We use the last specified parameters, unless new ones are + * entered. + */ + fpga = dp_last_fpga; + addr = dp_last_addr; + length = dp_last_length; + + if (argc < 3) + return CMD_RET_USAGE; + + if ((flag & CMD_FLAG_REPEAT) == 0) { + /* + * FPGA is specified since argc > 2 + */ + fpga = hextoul(argv[1], NULL); + + /* + * Address is specified since argc > 2 + */ + addr = hextoul(argv[2], NULL); + + /* + * If another parameter, it is the length to display. + * Length is the number of objects, not number of bytes. + */ + if (argc > 3) + length = hextoul(argv[3], NULL); + } + + nbytes = length * sizeof(u16); + do { + ulong linebytes = (nbytes > DISP_LINE_LEN) ? + DISP_LINE_LEN : nbytes; + + for (k = 0; k < linebytes / sizeof(u16); ++k) + fpga_get_reg(fpga, + (u16 *)fpga_ptr[fpga] + addr + / sizeof(u16) + k, + addr + k * sizeof(u16), + &linebuf[k]); + print_buffer(addr, (void *)linebuf, sizeof(u16), + linebytes / sizeof(u16), + DISP_LINE_LEN / sizeof(u16)); + + nbytes -= linebytes; + addr += linebytes; + if (ctrlc()) { + rc = 1; + break; + } + } while (nbytes > 0); + + dp_last_fpga = fpga; + dp_last_addr = addr; + dp_last_length = length; + return rc; +} + +U_BOOT_CMD( + fpgad, 4, 1, do_fpga_md, + "fpga register display", + "fpga address [# of objects]" +); diff --git a/cmd/fs.c b/cmd/fs.c new file mode 100644 index 00000000000..7f1ab8f0fd2 --- /dev/null +++ b/cmd/fs.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * Inspired by cmd_ext_common.c, cmd_fat.c. + */ + +#include <command.h> +#include <fs.h> + +static int do_size_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_size(cmdtp, flag, argc, argv, FS_TYPE_ANY); +} + +U_BOOT_CMD( + size, 4, 0, do_size_wrapper, + "determine a file's size", + "<interface> <dev[:part]> <filename>\n" + " - Find file 'filename' from 'dev' on 'interface'\n" + " determine its size, and store in the 'filesize' variable." +); + +static int do_load_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_load(cmdtp, flag, argc, argv, FS_TYPE_ANY); +} + +U_BOOT_CMD( + load, 7, 0, do_load_wrapper, + "load binary file from a filesystem", + "<interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]\n" + " - Load binary file 'filename' from partition 'part' on device\n" + " type 'interface' instance 'dev' to address 'addr' in memory.\n" + " 'bytes' gives the size to load in bytes.\n" + " If 'bytes' is 0 or omitted, the file is read until the end.\n" + " 'pos' gives the file byte position to start reading from.\n" + " If 'pos' is 0 or omitted, the file is read from the start." +); + +static int do_save_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_save(cmdtp, flag, argc, argv, FS_TYPE_ANY); +} + +U_BOOT_CMD( + save, 7, 0, do_save_wrapper, + "save file to a filesystem", + "<interface> <dev[:part]> <addr> <filename> bytes [pos]\n" + " - Save binary file 'filename' to partition 'part' on device\n" + " type 'interface' instance 'dev' from addr 'addr' in memory.\n" + " 'bytes' gives the size to save in bytes and is mandatory.\n" + " 'pos' gives the file byte position to start writing to.\n" + " If 'pos' is 0 or omitted, the file is written from the start." +); + +static int do_ls_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_ls(cmdtp, flag, argc, argv, FS_TYPE_ANY); +} + +U_BOOT_CMD( + ls, 4, 1, do_ls_wrapper, + "list files in a directory (default /)", + "<interface> [<dev[:part]> [directory]]\n" + " - List files in directory 'directory' of partition 'part' on\n" + " device type 'interface' instance 'dev'." +); + +static int do_ln_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_ln(cmdtp, flag, argc, argv, FS_TYPE_ANY); +} + +U_BOOT_CMD( + ln, 5, 1, do_ln_wrapper, + "Create a symbolic link", + "<interface> <dev[:part]> target linkname\n" + " - create a symbolic link to 'target' with the name 'linkname' on\n" + " device type 'interface' instance 'dev'." +); + +static int do_mkdir_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_mkdir(cmdtp, flag, argc, argv, FS_TYPE_ANY); +} + +U_BOOT_CMD( + mkdir, 4, 1, do_mkdir_wrapper, + "create a directory", + "<interface> [<dev[:part]>] <directory>\n" + " - Create a directory 'directory' of partition 'part' on\n" + " device type 'interface' instance 'dev'." +); + +static int do_rm_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_rm(cmdtp, flag, argc, argv, FS_TYPE_ANY); +} + +U_BOOT_CMD( + rm, 4, 1, do_rm_wrapper, + "delete a file", + "<interface> [<dev[:part]>] <filename>\n" + " - delete a file with the name 'filename' on\n" + " device type 'interface' instance 'dev'." +); + +static int do_fstype_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_fs_type(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD( + fstype, 4, 1, do_fstype_wrapper, + "Look up a filesystem type", + "<interface> <dev>:<part>\n" + "- print filesystem type\n" + "fstype <interface> <dev>:<part> <varname>\n" + "- set environment variable to filesystem type\n" +); + +static int do_fstypes_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + return do_fs_types(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD( + fstypes, 1, 1, do_fstypes_wrapper, + "List supported filesystem types", "" +); + +static int do_mv_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_mv(cmdtp, flag, argc, argv, FS_TYPE_ANY); +} + +U_BOOT_CMD( + mv, 5, 1, do_mv_wrapper, + "rename/move a file/directory", + "<interface> [<dev[:part]>] <old_path> <new_path>\n" + " - renames/moves a file/directory in 'dev' on 'interface' from\n" + " 'old_path' to 'new_path'" +); diff --git a/cmd/fs_uuid.c b/cmd/fs_uuid.c new file mode 100644 index 00000000000..5f7770d09ac --- /dev/null +++ b/cmd/fs_uuid.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cmd_fs_uuid.c -- fsuuid command + * + * Copyright (C) 2014, Bachmann electronic GmbH + */ + +#include <command.h> +#include <fs.h> + +static int do_fs_uuid_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_fs_uuid(cmdtp, flag, argc, argv, FS_TYPE_ANY); +} + +U_BOOT_CMD( + fsuuid, 4, 1, do_fs_uuid_wrapper, + "Look up a filesystem UUID", + "<interface> <dev>:<part>\n" + " - print filesystem UUID\n" + "fsuuid <interface> <dev>:<part> <varname>\n" + " - set environment variable to filesystem UUID\n" +); diff --git a/cmd/fuse.c b/cmd/fuse.c new file mode 100644 index 00000000000..e2206cdf0d5 --- /dev/null +++ b/cmd/fuse.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2009-2013 ADVANSEE + * Benoît Thébaudeau <benoit.thebaudeau@advansee.com> + * + * Based on the mpc512x iim code: + * Copyright 2008 Silicon Turnkey Express, Inc. + * Martha Marx <mmarx@silicontkx.com> + */ + +#include <command.h> +#include <console.h> +#include <fuse.h> +#include <mapmem.h> +#include <vsprintf.h> +#include <linux/errno.h> +#include <linux/string.h> + +static int confirm_prog(void) +{ + puts("Warning: Programming fuses is an irreversible operation!\n" + " This may brick your system.\n" + " Use this command only if you are sure of " + "what you are doing!\n" + "\nReally perform this fuse programming? <y/N>\n"); + + if (confirm_yesno()) + return 1; + + puts("Fuse programming aborted\n"); + return 0; +} + +static int do_fuse(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *op = cmd_arg1(argc, argv); + int confirmed = argc >= 3 && !strcmp(argv[2], "-y"); + u32 bank, word, cnt, val, cmp; + ulong addr; + void *buf, *start; + int ret, i; + + argc -= 2 + confirmed; + argv += 2 + confirmed; + + if (IS_ENABLED(CONFIG_CMD_FUSE_WRITEBUFF) && !strcmp(op, "writebuff")) { + if (argc == 1) + addr = simple_strtoul(argv[0], NULL, 16); + else + return CMD_RET_USAGE; + } else { + if (argc < 2) + return CMD_RET_USAGE; + + bank = simple_strtoul(argv[0], NULL, 0); + word = simple_strtoul(argv[1], NULL, 0); + } + + if (!strcmp(op, "read")) { + if (argc == 2) + cnt = 1; + else if (argc == 3) + cnt = simple_strtoul(argv[2], NULL, 0); + else + return CMD_RET_USAGE; + + printf("Reading bank %u:\n", bank); + for (i = 0; i < cnt; i++, word++) { + if (!(i % 4)) + printf("\nWord 0x%.8x:", word); + + ret = fuse_read(bank, word, &val); + if (ret) + goto err; + + printf(" %.8x", val); + } + putc('\n'); + } else if (!strcmp(op, "readm")) { + if (argc == 3) + cnt = 1; + else if (argc == 4) + cnt = simple_strtoul(argv[3], NULL, 0); + else + return CMD_RET_USAGE; + + addr = simple_strtoul(argv[2], NULL, 16); + + start = map_sysmem(addr, 4); + buf = start; + + printf("Reading bank %u len %u to 0x%lx\n", bank, cnt, addr); + for (i = 0; i < cnt; i++, word++) { + ret = fuse_read(bank, word, &val); + if (ret) + goto err; + + *((u32 *)buf) = val; + buf += 4; + } + + unmap_sysmem(start); + } else if (!strcmp(op, "cmp")) { + if (argc == 3) + cmp = simple_strtoul(argv[2], NULL, 0); + else + return CMD_RET_USAGE; + + printf("Comparing bank %u:\n", bank); + printf("\nWord 0x%.8x:", word); + printf("\nValue 0x%.8x:", cmp); + + ret = fuse_read(bank, word, &val); + if (ret) + goto err; + + printf("0x%.8x\n", val); + if (val != cmp) { + printf("failed\n"); + return CMD_RET_FAILURE; + } + printf("passed\n"); + } else if (!strcmp(op, "sense")) { + if (argc == 2) + cnt = 1; + else if (argc == 3) + cnt = simple_strtoul(argv[2], NULL, 0); + else + return CMD_RET_USAGE; + + printf("Sensing bank %u:\n", bank); + for (i = 0; i < cnt; i++, word++) { + if (!(i % 4)) + printf("\nWord 0x%.8x:", word); + + ret = fuse_sense(bank, word, &val); + if (ret) + goto err; + + printf(" %.8x", val); + } + putc('\n'); + } else if (!strcmp(op, "prog")) { + if (argc < 3) + return CMD_RET_USAGE; + + for (i = 2; i < argc; i++, word++) { + val = simple_strtoul(argv[i], NULL, 16); + + printf("Programming bank %u word 0x%.8x to 0x%.8x...\n", + bank, word, val); + if (!confirmed && !confirm_prog()) + return CMD_RET_FAILURE; + ret = fuse_prog(bank, word, val); + if (ret) + goto err; + } + } else if (!strcmp(op, "override")) { + if (argc < 3) + return CMD_RET_USAGE; + + for (i = 2; i < argc; i++, word++) { + val = simple_strtoul(argv[i], NULL, 16); + + printf("Overriding bank %u word 0x%.8x with " + "0x%.8x...\n", bank, word, val); + ret = fuse_override(bank, word, val); + if (ret) + goto err; + } + } else if (IS_ENABLED(CONFIG_CMD_FUSE_WRITEBUFF) && !strcmp(op, "writebuff")) { + printf("Programming fuses using a structured buffer in memory " + "starting at addr 0x%lx\n", addr); + if (!confirmed && !confirm_prog()) + return CMD_RET_FAILURE; + + ret = fuse_writebuff(addr); + if (ret) + goto err; + } else { + return CMD_RET_USAGE; + } + + return 0; + +err: + puts("ERROR\n"); + return CMD_RET_FAILURE; +} + +U_BOOT_CMD( + fuse, CONFIG_SYS_MAXARGS, 0, do_fuse, + "Fuse sub-system", + "read <bank> <word> [<cnt>] - read 1 or 'cnt' fuse words,\n" + " starting at 'word'\n" + "fuse cmp <bank> <word> <hexval> - compare 'hexval' to fuse\n" + " at 'word'\n" + "fuse readm <bank> <word> <addr> [<cnt>] - read 1 or 'cnt' fuse words,\n" + " starting at 'word' into memory at 'addr'\n" + "fuse sense <bank> <word> [<cnt>] - sense 1 or 'cnt' fuse words,\n" + " starting at 'word'\n" + "fuse prog [-y] <bank> <word> <hexval> [<hexval>...] - program 1 or\n" + " several fuse words, starting at 'word' (PERMANENT)\n" + "fuse override <bank> <word> <hexval> [<hexval>...] - override 1 or\n" + " several fuse words, starting at 'word'\n" +#ifdef CONFIG_CMD_FUSE_WRITEBUFF + "fuse writebuff [-y] <addr> - program fuse data\n" + " using a structured buffer in memory starting at 'addr'\n" +#endif /* CONFIG_CMD_FUSE_WRITEBUFF */ +); diff --git a/cmd/fwu_mdata.c b/cmd/fwu_mdata.c new file mode 100644 index 00000000000..5b5a2e4d1cd --- /dev/null +++ b/cmd/fwu_mdata.c @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2022, Linaro Limited + */ + +#include <command.h> +#include <dm.h> +#include <fwu.h> +#include <fwu_mdata.h> +#include <hexdump.h> +#include <log.h> +#include <stdio.h> +#include <stdlib.h> + +#include <linux/types.h> + +static void print_mdata(struct fwu_data *data) +{ + int i, j; + struct fwu_image_entry *img_entry; + struct fwu_image_bank_info *img_info; + + printf("\tFWU Metadata\n"); + printf("crc32: %#x\n", data->crc32); + printf("version: %#x\n", data->version); + printf("size: %#x\n", data->metadata_size); + printf("active_index: %#x\n", data->active_index); + printf("previous_active_index: %#x\n", data->previous_active_index); + + if (data->version == 2) { + for (i = 0; i < 4; i++) + printf("bank_state[%d]: %#x\n", + i, data->bank_state[i]); + } + + printf("\tImage Info\n"); + for (i = 0; i < CONFIG_FWU_NUM_IMAGES_PER_BANK; i++) { + img_entry = &data->fwu_images[i]; + printf("\nImage Type Guid: %pUL\n", + &img_entry->image_type_guid); + printf("Location Guid: %pUL\n", &img_entry->location_guid); + for (j = 0; j < CONFIG_FWU_NUM_BANKS; j++) { + img_info = &img_entry->img_bank_info[j]; + printf("Image Guid: %pUL\n", &img_info->image_guid); + printf("Image Acceptance: %s\n", + img_info->accepted == 0x1 ? "yes" : "no"); + } + } + + if (data->version == 2) { + struct fwu_mdata *mdata = data->fwu_mdata; + struct fwu_fw_store_desc *desc; + void *end; + u32 diff; + + /* + * fwu_mdata defines only header that's why taking it as array + * which exactly point to image description location + */ + desc = (struct fwu_fw_store_desc *)&mdata[1]; + + /* Number of entries is taken from for loop - variable i */ + end = &desc->img_entry[i]; + debug("mdata %p, desc %p, end %p\n", mdata, desc, end); + + diff = data->metadata_size - ((void *)end - (void *)mdata); + if (diff) { + printf("Custom fields covered by CRC len: 0x%x\n", diff); + print_hex_dump_bytes("CUSTOM ", DUMP_PREFIX_OFFSET, + end, diff); + } + } +} + +int do_fwu_mdata_read(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + struct fwu_data *data = fwu_get_data(); + + print_mdata(data); + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + fwu_mdata_read, 1, 1, do_fwu_mdata_read, + "Read and print FWU metadata", + "" +); diff --git a/cmd/gettime.c b/cmd/gettime.c new file mode 100644 index 00000000000..fc307efce8c --- /dev/null +++ b/cmd/gettime.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * (C) Copyright 2001 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * Get Timer overflows after 2^32 / CONFIG_SYS_HZ (32Khz) = 131072 sec + */ +#include <command.h> +#include <time.h> + +static int do_gettime(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned long int val = get_timer(0); + +#ifdef CONFIG_SYS_HZ + printf("Timer val: %lu\n", val); + printf("Seconds : %lu\n", val / CONFIG_SYS_HZ); + printf("Remainder : %lu\n", val % CONFIG_SYS_HZ); + printf("sys_hz = %lu\n", (unsigned long int)CONFIG_SYS_HZ); +#else + printf("CONFIG_SYS_HZ not defined"); + printf("Timer Val %lu", val); +#endif + + return 0; +} + +U_BOOT_CMD( + gettime, 1, 1, do_gettime, + "get timer val elapsed", + "get time elapsed from uboot start" +); diff --git a/cmd/gpio.c b/cmd/gpio.c new file mode 100644 index 00000000000..7a43dc6ab18 --- /dev/null +++ b/cmd/gpio.c @@ -0,0 +1,317 @@ +/* + * Control GPIO pins on the fly + * + * Copyright (c) 2008-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <command.h> +#include <errno.h> +#include <dm.h> +#include <log.h> +#include <malloc.h> +#ifdef CONFIG_CMD_GPIO_READ +#include <env.h> +#endif +#include <asm/gpio.h> +#include <linux/err.h> +#include <dm/device_compat.h> + +__weak int name_to_gpio(const char *name) +{ + return dectoul(name, NULL); +} + +enum gpio_cmd { + GPIOC_INPUT, + GPIOC_SET, + GPIOC_CLEAR, + GPIOC_TOGGLE, +#ifdef CONFIG_CMD_GPIO_READ + GPIOC_READ, +#endif +}; + +#if defined(CONFIG_DM_GPIO) && !defined(gpio_status) + +/* A few flags used by show_gpio() */ +enum { + FLAG_SHOW_ALL = 1 << 0, + FLAG_SHOW_BANK = 1 << 1, + FLAG_SHOW_NEWLINE = 1 << 2, +}; + +static void gpio_get_description(struct udevice *dev, const char *bank_name, + int offset, int *flagsp, bool show_all) +{ + char buf[80]; + int ret; + + ret = gpio_get_function(dev, offset, NULL); + if (ret < 0) + goto err; + if (!show_all && !(*flagsp & FLAG_SHOW_ALL) && ret == GPIOF_UNUSED) + return; + if ((*flagsp & FLAG_SHOW_BANK) && bank_name) { + if (*flagsp & FLAG_SHOW_NEWLINE) { + putc('\n'); + *flagsp &= ~FLAG_SHOW_NEWLINE; + } + printf("Bank %s:\n", bank_name); + *flagsp &= ~FLAG_SHOW_BANK; + } + + ret = gpio_get_status(dev, offset, buf, sizeof(buf)); + if (ret) + goto err; + + printf("%s\n", buf); + return; +err: + if (ret != -ENOENT) + printf("Error %d\n", ret); +} + +static int do_gpio_status(bool all, const char *gpio_name) +{ + struct udevice *dev; + int banklen; + int flags; + int ret, err = 0; + + flags = 0; + if (gpio_name && !*gpio_name) + gpio_name = NULL; + for (ret = uclass_first_device_check(UCLASS_GPIO, &dev); + dev; + ret = uclass_next_device_check(&dev)) { + const char *bank_name; + int num_bits; + + if (ret) { + printf("GPIO device %s probe error %i\n", + dev->name, ret); + err = ret; + continue; + } + + flags |= FLAG_SHOW_BANK; + if (all) + flags |= FLAG_SHOW_ALL; + bank_name = gpio_get_bank_info(dev, &num_bits); + if (!num_bits) { + debug("GPIO device %s has no bits\n", dev->name); + continue; + } + banklen = bank_name ? strlen(bank_name) : 0; + + if (!gpio_name || !bank_name || + !strncasecmp(gpio_name, bank_name, banklen)) { + const char *p; + int offset; + + p = gpio_name + banklen; + if (gpio_name && *p) { + offset = dectoul(p, NULL); + gpio_get_description(dev, bank_name, offset, + &flags, true); + } else { + for (offset = 0; offset < num_bits; offset++) { + gpio_get_description(dev, bank_name, + offset, &flags, false); + } + } + } + /* Add a newline between bank names */ + if (!(flags & FLAG_SHOW_BANK)) + flags |= FLAG_SHOW_NEWLINE; + } + + return err; +} +#endif + +static int do_gpio(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned int gpio; + enum gpio_cmd sub_cmd; + int value; + const char *str_cmd, *str_gpio = NULL; +#ifdef CONFIG_CMD_GPIO_READ + const char *str_var = NULL; +#endif + int ret; +#ifdef CONFIG_DM_GPIO + bool all = false; +#endif + + if (argc < 2) + show_usage: + return CMD_RET_USAGE; + str_cmd = argv[1]; + argc -= 2; + argv += 2; +#ifdef CONFIG_DM_GPIO + if (argc > 0 && !strncmp(str_cmd, "status", 2) && !strcmp(*argv, "-a")) { + all = true; + argc--; + argv++; + } +#endif +#ifdef CONFIG_CMD_GPIO_READ + if (argc > 0 && !strncmp(str_cmd, "read", 2)) { + if (argc < 2) + goto show_usage; + str_var = *argv; + argc--; + argv++; + } +#endif + if (argc > 0) + str_gpio = *argv; + if (!strncmp(str_cmd, "status", 2)) { + /* Support deprecated gpio_status() */ +#ifdef gpio_status + gpio_status(); + return 0; +#elif defined(CONFIG_DM_GPIO) + return cmd_process_error(cmdtp, do_gpio_status(all, str_gpio)); +#else + goto show_usage; +#endif + } + + if (!str_gpio) + goto show_usage; + + /* parse the behavior */ + switch (*str_cmd) { + case 'i': + sub_cmd = GPIOC_INPUT; + break; + case 's': + sub_cmd = GPIOC_SET; + break; + case 'c': + sub_cmd = GPIOC_CLEAR; + break; + case 't': + sub_cmd = GPIOC_TOGGLE; + break; +#ifdef CONFIG_CMD_GPIO_READ + case 'r': + sub_cmd = GPIOC_READ; + break; +#endif + default: + goto show_usage; + } + +#if defined(CONFIG_DM_GPIO) + /* + * TODO(sjg@chromium.org): For now we must fit into the existing GPIO + * framework, so we look up the name here and convert it to a GPIO number. + * Once all GPIO drivers are converted to driver model, we can change the + * code here to use the GPIO uclass interface instead of the numbered + * GPIO compatibility layer. + */ + ret = gpio_lookup_name(str_gpio, NULL, NULL, &gpio); + if (ret) { + printf("GPIO: '%s' not found\n", str_gpio); + return cmd_process_error(cmdtp, ret); + } +#else + /* turn the gpio name into a gpio number */ + gpio = name_to_gpio(str_gpio); + if (gpio < 0) + goto show_usage; +#endif + /* grab the pin before we tweak it */ + ret = gpio_request(gpio, "cmd_gpio"); + if (ret && ret != -EBUSY) { + printf("gpio: requesting pin %u failed\n", gpio); + return -1; + } + + /* finally, let's do it: set direction and exec command */ + if (sub_cmd == GPIOC_INPUT +#ifdef CONFIG_CMD_GPIO_READ + || sub_cmd == GPIOC_READ +#endif + ) { + gpio_direction_input(gpio); + value = gpio_get_value(gpio); + } else { + switch (sub_cmd) { + case GPIOC_SET: + value = 1; + break; + case GPIOC_CLEAR: + value = 0; + break; + case GPIOC_TOGGLE: + value = gpio_get_value(gpio); + if (!IS_ERR_VALUE(value)) + value = !value; + break; + default: + goto show_usage; + } + gpio_direction_output(gpio, value); + } + printf("gpio: pin %s (gpio %u) value is ", str_gpio, gpio); + + if (IS_ERR_VALUE(value)) { + printf("unknown (ret=%d)\n", value); + goto err; + } else { + printf("%d\n", value); +#ifdef CONFIG_CMD_GPIO_READ + if (sub_cmd == GPIOC_READ) + env_set_ulong(str_var, (ulong)value); +#endif + } + + if (sub_cmd != GPIOC_INPUT && !IS_ERR_VALUE(value) +#ifdef CONFIG_CMD_GPIO_READ + && sub_cmd != GPIOC_READ +#endif + ) { + int nval = gpio_get_value(gpio); + + if (IS_ERR_VALUE(nval)) { + printf(" Warning: no access to GPIO output value\n"); + goto err; + } else if (nval != value) { + printf(" Warning: value of pin is still %d\n", nval); + goto err; + } + } + + if (ret != -EBUSY) + gpio_free(gpio); + + /* + * Whilst wrong, the legacy gpio input command returns the pin + * value, or CMD_RET_FAILURE (which is indistinguishable from a + * valid pin value). + */ + return (sub_cmd == GPIOC_INPUT) ? value : CMD_RET_SUCCESS; + +err: + if (ret != -EBUSY) + gpio_free(gpio); + return CMD_RET_FAILURE; +} + +U_BOOT_CMD(gpio, 4, 0, do_gpio, + "query and control gpio pins", + "<input|set|clear|toggle> <pin>\n" + " - input/set/clear/toggle the specified pin\n" +#ifdef CONFIG_CMD_GPIO_READ + "gpio read <name> <pin>\n" + " - set environment variable 'name' to the specified pin\n" +#endif + "gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs"); diff --git a/cmd/gpt.c b/cmd/gpt.c new file mode 100644 index 00000000000..e18e5036a06 --- /dev/null +++ b/cmd/gpt.c @@ -0,0 +1,1240 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cmd_gpt.c -- GPT (GUID Partition Table) handling command + * + * Copyright (C) 2015 + * Lukasz Majewski <l.majewski@majess.pl> + * + * Copyright (C) 2012 Samsung Electronics + * author: Lukasz Majewski <l.majewski@samsung.com> + * author: Piotr Wilczek <p.wilczek@samsung.com> + */ + +#include <blk.h> +#include <env.h> +#include <log.h> +#include <malloc.h> +#include <command.h> +#include <part.h> +#include <part_efi.h> +#include <part.h> +#include <exports.h> +#include <u-boot/uuid.h> +#include <linux/ctype.h> +#include <div64.h> +#include <memalign.h> +#include <linux/compat.h> +#include <linux/err.h> +#include <linux/sizes.h> +#include <stdlib.h> + +static LIST_HEAD(disk_partitions); + +/** + * extract_env(): Expand env name from string format '&{env_name}' + * and return pointer to the env (if the env is set) + * + * @param str - pointer to string + * @param env - pointer to pointer to extracted env + * + * Return: - zero on successful expand and env is set + */ +static int extract_env(const char *str, char **env) +{ + int ret = -1; + char *e, *s; +#ifdef CONFIG_RANDOM_UUID + char uuid_str[UUID_STR_LEN + 1]; +#endif + + if (!str || strlen(str) < 4) + return -1; + + if (!((strncmp(str, "${", 2) == 0) && (str[strlen(str) - 1] == '}'))) + return -1; + + s = strdup(str); + if (s == NULL) + return -1; + + memset(s + strlen(s) - 1, '\0', 1); + memmove(s, s + 2, strlen(s) - 1); + + e = env_get(s); + if (e == NULL) { +#ifdef CONFIG_RANDOM_UUID + debug("%s unset. ", str); + gen_rand_uuid_str(uuid_str, UUID_STR_FORMAT_GUID); + env_set(s, uuid_str); + + e = env_get(s); + if (e) { + debug("Set to random.\n"); + ret = 0; + } else { + debug("Can't get random UUID.\n"); + } +#else + debug("%s unset.\n", str); +#endif + } else { + debug("%s get from environment.\n", str); + ret = 0; + } + + *env = e; + free(s); + + return ret; +} + +/** + * extract_val(): Extract value from a key=value pair list (comma separated). + * Only value for the given key is returend. + * Function allocates memory for the value, remember to free! + * + * @param str - pointer to string with key=values pairs + * @param key - pointer to the key to search for + * + * Return: - pointer to allocated string with the value + */ +static char *extract_val(const char *str, const char *key) +{ + char *v, *k; + char *s, *strcopy; + char *new = NULL; + + strcopy = strdup(str); + if (strcopy == NULL) + return NULL; + + s = strcopy; + while (s) { + v = strsep(&s, ","); + if (!v) + break; + k = strsep(&v, "="); + if (!k) + break; + k += strspn(k, " \t"); + if (strcmp(k, key) == 0) { + new = strdup(v); + break; + } + } + + free(strcopy); + + return new; +} + +/** + * found_key(): Found key without value in parameter list (comma separated). + * + * @param str - pointer to string with key + * @param key - pointer to the key to search for + * + * Return: - true on found key + */ +static bool found_key(const char *str, const char *key) +{ + char *k; + char *s, *strcopy; + bool result = false; + + strcopy = strdup(str); + if (!strcopy) + return NULL; + + s = strcopy; + while (s) { + k = strsep(&s, ","); + if (!k) + break; + k += strspn(k, " \t"); + if (strcmp(k, key) == 0) { + result = true; + break; + } + } + + free(strcopy); + + return result; +} + +/** + * calc_parts_list_len() - get size of partition table description + * + * @numparts: number of partitions + * Return: string size including terminating NUL + */ +static int calc_parts_list_len(int numparts) +{ + /* number of hexadecimal digits of the lbaint_t representation */ + const int lbaint_size = 2 * sizeof(lbaint_t); + int partlistlen; + + /* media description including terminating NUL */ + partlistlen = strlen("uuid_disk=;") + UUID_STR_LEN + 1; + /* per-partition descriptions; numparts */ + partlistlen += numparts * (strlen("name=,") + PART_NAME_LEN); + /* see part.h for definition of struct disk_partition */ + partlistlen += numparts * (strlen("start=0x,") + lbaint_size); + partlistlen += numparts * (strlen("size=0x,") + lbaint_size); + if (IS_ENABLED(CONFIG_PARTITION_UUIDS)) + partlistlen += numparts * (strlen("uuid=,") + UUID_STR_LEN); + if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) + partlistlen += numparts * (strlen("type=;") + UUID_STR_LEN); + debug("Length of partitions_list is %d for %d partitions\n", + partlistlen, numparts); + return partlistlen; +} + +#ifdef CONFIG_CMD_GPT_RENAME +static void del_gpt_info(void) +{ + struct list_head *pos = &disk_partitions; + struct disk_part *curr; + while (!list_empty(pos)) { + curr = list_entry(pos->next, struct disk_part, list); + list_del(pos->next); + free(curr); + } +} + +static struct disk_part *allocate_disk_part(struct disk_partition *info, + int partnum) +{ + struct disk_part *newpart; + newpart = calloc(1, sizeof(struct disk_part)); + if (!newpart) + return ERR_PTR(-ENOMEM); + + newpart->gpt_part_info.start = info->start; + newpart->gpt_part_info.size = info->size; + newpart->gpt_part_info.blksz = info->blksz; + strncpy((char *)newpart->gpt_part_info.name, (const char *)info->name, + PART_NAME_LEN); + newpart->gpt_part_info.name[PART_NAME_LEN - 1] = '\0'; + strncpy((char *)newpart->gpt_part_info.type, (const char *)info->type, + PART_TYPE_LEN); + newpart->gpt_part_info.type[PART_TYPE_LEN - 1] = '\0'; + newpart->gpt_part_info.bootable = info->bootable; + if (IS_ENABLED(CONFIG_PARTITION_UUIDS)) + disk_partition_set_uuid(&newpart->gpt_part_info, + disk_partition_uuid(info)); + if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) + disk_partition_set_type_guid(&newpart->gpt_part_info, + disk_partition_type_guid(info)); + newpart->partnum = partnum; + + return newpart; +} + +static void prettyprint_part_size(char *sizestr, lbaint_t partsize, + lbaint_t blksize) +{ + unsigned long long partbytes, partmegabytes; + + partbytes = partsize * blksize; + partmegabytes = lldiv(partbytes, SZ_1M); + snprintf(sizestr, 16, "%lluMiB", partmegabytes); +} + +static void print_gpt_info(void) +{ + struct list_head *pos; + struct disk_part *curr; + char partstartstr[16]; + char partsizestr[16]; + + list_for_each(pos, &disk_partitions) { + curr = list_entry(pos, struct disk_part, list); + prettyprint_part_size(partstartstr, curr->gpt_part_info.start, + curr->gpt_part_info.blksz); + prettyprint_part_size(partsizestr, curr->gpt_part_info.size, + curr->gpt_part_info.blksz); + + printf("Partition %d:\n", curr->partnum); + printf("Start %s, size %s\n", partstartstr, partsizestr); + printf("Block size %lu, name %s\n", curr->gpt_part_info.blksz, + curr->gpt_part_info.name); + printf("Type %s, bootable %d\n", curr->gpt_part_info.type, + curr->gpt_part_info.bootable & PART_BOOTABLE); + if (CONFIG_IS_ENABLED(PARTITION_UUIDS)) + printf("UUID %s\n", + disk_partition_uuid(&curr->gpt_part_info)); + if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) + printf("Type GUID %s\n", + disk_partition_type_guid(&curr->gpt_part_info)); + printf("\n"); + } +} + +/* + * create the string that upstream 'gpt write' command will accept as an + * argument + * + * From doc/README.gpt, Format of partitions layout: + * "uuid_disk=...;name=u-boot,size=60MiB,uuid=...; + * name=kernel,size=60MiB,uuid=...;" + * The fields 'name' and 'size' are mandatory for every partition. + * The field 'start' is optional. The fields 'uuid' and 'uuid_disk' + * are optional if CONFIG_RANDOM_UUID is enabled. + */ +static int create_gpt_partitions_list(int numparts, const char *guid, + char *partitions_list) +{ + struct list_head *pos; + struct disk_part *curr; + char partstr[PART_NAME_LEN + 1]; + + if (!partitions_list) + return -EINVAL; + + strcpy(partitions_list, "uuid_disk="); + strncat(partitions_list, guid, UUID_STR_LEN + 1); + strcat(partitions_list, ";"); + + list_for_each(pos, &disk_partitions) { + curr = list_entry(pos, struct disk_part, list); + strcat(partitions_list, "name="); + strncat(partitions_list, (const char *)curr->gpt_part_info.name, + PART_NAME_LEN + 1); + sprintf(partstr, ",start=0x%llx", + (unsigned long long)curr->gpt_part_info.start * + curr->gpt_part_info.blksz); + /* one extra byte for NULL */ + strncat(partitions_list, partstr, PART_NAME_LEN + 1); + sprintf(partstr, ",size=0x%llx", + (unsigned long long)curr->gpt_part_info.size * + curr->gpt_part_info.blksz); + strncat(partitions_list, partstr, PART_NAME_LEN + 1); + + if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) { + strcat(partitions_list, ",type="); + strncat(partitions_list, + disk_partition_type_guid(&curr->gpt_part_info), + UUID_STR_LEN + 1); + } + if (CONFIG_IS_ENABLED(PARTITION_UUIDS)) { + strcat(partitions_list, ",uuid="); + strncat(partitions_list, + disk_partition_uuid(&curr->gpt_part_info), + UUID_STR_LEN + 1); + } + if (curr->gpt_part_info.bootable & PART_BOOTABLE) + strcat(partitions_list, ",bootable"); + strcat(partitions_list, ";"); + } + return 0; +} + +/* + * read partition info into disk_partitions list where + * it can be printed or modified + */ +static int get_gpt_info(struct blk_desc *dev_desc) +{ + /* start partition numbering at 1, as U-Boot does */ + int valid_parts = 0, p, ret; + struct disk_partition info; + struct disk_part *new_disk_part; + + /* + * Always re-read partition info from device, in case + * it has changed + */ + INIT_LIST_HEAD(&disk_partitions); + + for (p = 1; p <= MAX_SEARCH_PARTITIONS; p++) { + ret = part_get_info(dev_desc, p, &info); + if (ret) + continue; + + /* Add 1 here because counter is zero-based but p1 is + the first partition */ + new_disk_part = allocate_disk_part(&info, valid_parts+1); + if (IS_ERR(new_disk_part)) + goto out; + + list_add_tail(&new_disk_part->list, &disk_partitions); + valid_parts++; + } + if (valid_parts == 0) { + printf("** No valid partitions found **\n"); + goto out; + } + return valid_parts; + out: + if (valid_parts >= 1) + del_gpt_info(); + return -ENODEV; +} + +/* a wrapper to test get_gpt_info */ +static int do_get_gpt_info(struct blk_desc *dev_desc, char * const namestr) +{ + int numparts; + + numparts = get_gpt_info(dev_desc); + + if (numparts > 0) { + if (namestr) { + char disk_guid[UUID_STR_LEN + 1]; + char *partitions_list; + int partlistlen; + int ret = -1; + + ret = get_disk_guid(dev_desc, disk_guid); + if (ret < 0) + return ret; + + partlistlen = calc_parts_list_len(numparts); + partitions_list = malloc(partlistlen); + if (!partitions_list) { + del_gpt_info(); + return -ENOMEM; + } + memset(partitions_list, '\0', partlistlen); + + ret = create_gpt_partitions_list(numparts, disk_guid, + partitions_list); + if (ret < 0) + printf("Error: Could not create partition list string!\n"); + else + env_set(namestr, partitions_list); + + free(partitions_list); + } else { + print_gpt_info(); + } + del_gpt_info(); + return 0; + } + return numparts; +} +#endif + +/** + * set_gpt_info(): Fill partition information from string + * function allocates memory, remember to free! + * + * @param dev_desc - pointer block device descriptor + * @param str_part - pointer to string with partition information + * @param str_disk_guid - pointer to pointer to allocated string with disk guid + * @param partitions - pointer to pointer to allocated partitions array + * @param parts_count - number of partitions + * + * Return: - zero on success, otherwise error + * + */ +static int set_gpt_info(struct blk_desc *dev_desc, + const char *str_part, + char **str_disk_guid, + struct disk_partition **partitions, + u8 *parts_count) +{ + char *tok, *str, *s; + int i; + char *val, *p; + int p_count; + struct disk_partition *parts; + int errno = 0; + uint64_t size_ll, start_ll; + lbaint_t offset = 0; + int max_str_part = calc_parts_list_len(MAX_SEARCH_PARTITIONS); + + debug("%s: lba num: 0x%x %d\n", __func__, + (unsigned int)dev_desc->lba, (unsigned int)dev_desc->lba); + + if (str_part == NULL) + return -1; + + str = strdup(str_part); + if (str == NULL) + return -ENOMEM; + + /* extract disk guid */ + s = str; + val = extract_val(str, "uuid_disk"); + if (!val) { +#ifdef CONFIG_RANDOM_UUID + *str_disk_guid = malloc(UUID_STR_LEN + 1); + if (*str_disk_guid == NULL) + return -ENOMEM; + gen_rand_uuid_str(*str_disk_guid, UUID_STR_FORMAT_STD); +#else + free(str); + return -2; +#endif + } else { + val = strsep(&val, ";"); + if (extract_env(val, &p)) + p = val; + *str_disk_guid = strdup(p); + free(val); + /* Move s to first partition */ + strsep(&s, ";"); + } + if (s == NULL) { + printf("Error: is the partitions string NULL-terminated?\n"); + return -EINVAL; + } + if (strnlen(s, max_str_part) == 0) + return -3; + + i = strnlen(s, max_str_part) - 1; + if (s[i] == ';') + s[i] = '\0'; + + /* calculate expected number of partitions */ + p_count = 1; + p = s; + while (*p) { + if (*p++ == ';') + p_count++; + } + + /* allocate memory for partitions */ + parts = calloc(sizeof(struct disk_partition), p_count); + if (parts == NULL) + return -ENOMEM; + + /* retrieve partitions data from string */ + for (i = 0; i < p_count; i++) { + tok = strsep(&s, ";"); + + if (tok == NULL) + break; + + /* uuid */ + val = extract_val(tok, "uuid"); + if (!val) { + /* 'uuid' is optional if random uuid's are enabled */ +#ifdef CONFIG_RANDOM_UUID + gen_rand_uuid_str(parts[i].uuid, UUID_STR_FORMAT_STD); +#else + errno = -4; + goto err; +#endif + } else { + if (extract_env(val, &p)) + p = val; + if (strnlen(p, max_str_part) >= sizeof(parts[i].uuid)) { + printf("Wrong uuid format for partition %d\n", i); + errno = -4; + goto err; + } + strncpy((char *)parts[i].uuid, p, max_str_part); + free(val); + } +#ifdef CONFIG_PARTITION_TYPE_GUID + /* guid */ + val = extract_val(tok, "type"); + if (val) { + /* 'type' is optional */ + if (extract_env(val, &p)) + p = val; + if (strnlen(p, max_str_part) >= sizeof(parts[i].type_guid)) { + printf("Wrong type guid format for partition %d\n", + i); + errno = -4; + goto err; + } + strncpy((char *)parts[i].type_guid, p, max_str_part); + free(val); + } +#endif + /* name */ + val = extract_val(tok, "name"); + if (!val) { /* name is mandatory */ + errno = -4; + goto err; + } + if (extract_env(val, &p)) + p = val; + if (strnlen(p, max_str_part) >= sizeof(parts[i].name)) { + errno = -4; + goto err; + } + strncpy((char *)parts[i].name, p, max_str_part); + free(val); + + /* size */ + val = extract_val(tok, "size"); + if (!val) { /* 'size' is mandatory */ + errno = -4; + goto err; + } + if (extract_env(val, &p)) + p = val; + if ((strcmp(p, "-") == 0)) { + /* Let part efi module to auto extend the size */ + parts[i].size = 0; + } else { + size_ll = ustrtoull(p, &p, 0); + parts[i].size = lldiv(size_ll, dev_desc->blksz); + } + + free(val); + + /* start address */ + val = extract_val(tok, "start"); + if (val) { /* start address is optional */ + if (extract_env(val, &p)) + p = val; + start_ll = ustrtoull(p, &p, 0); + parts[i].start = lldiv(start_ll, dev_desc->blksz); + free(val); + } + + offset += parts[i].size + parts[i].start; + + /* bootable */ + if (found_key(tok, "bootable")) + parts[i].bootable = PART_BOOTABLE; + } + + *parts_count = p_count; + *partitions = parts; + free(str); + + return 0; +err: + free(str); + free(*str_disk_guid); + free(parts); + + return errno; +} + +static int gpt_repair(struct blk_desc *blk_dev_desc) +{ + int ret = 0; + + ret = gpt_repair_headers(blk_dev_desc); + + return ret; +} + +static int gpt_default(struct blk_desc *blk_dev_desc, const char *str_part) +{ + int ret; + char *str_disk_guid; + u8 part_count = 0; + struct disk_partition *partitions = NULL; + + /* fill partitions */ + ret = set_gpt_info(blk_dev_desc, str_part, + &str_disk_guid, &partitions, &part_count); + if (ret) { + if (ret == -1) + printf("No partition list provided\n"); + if (ret == -2) + printf("Missing disk guid\n"); + if ((ret == -3) || (ret == -4)) + printf("Partition list incomplete\n"); + return -1; + } + + /* save partitions layout to disk */ + ret = gpt_restore(blk_dev_desc, str_disk_guid, partitions, part_count); + free(str_disk_guid); + free(partitions); + + /* initialize partition table */ + if (blk_enabled()) + part_init(blk_dev_desc); + + return ret; +} + +static int gpt_verify(struct blk_desc *blk_dev_desc, const char *str_part) +{ + ALLOC_CACHE_ALIGN_BUFFER_PAD(gpt_header, gpt_head, 1, + blk_dev_desc->blksz); + struct disk_partition *partitions = NULL; + gpt_entry *gpt_pte = NULL; + char *str_disk_guid; + u8 part_count = 0; + int ret = 0; + + /* fill partitions */ + ret = set_gpt_info(blk_dev_desc, str_part, + &str_disk_guid, &partitions, &part_count); + if (ret) { + if (ret == -1) { + printf("No partition list provided - only basic check\n"); + ret = gpt_verify_headers(blk_dev_desc, gpt_head, + &gpt_pte); + goto out; + } + if (ret == -2) + printf("Missing disk guid\n"); + if ((ret == -3) || (ret == -4)) + printf("Partition list incomplete\n"); + return -1; + } + + /* Check partition layout with provided pattern */ + ret = gpt_verify_partitions(blk_dev_desc, partitions, part_count, + gpt_head, &gpt_pte); + free(str_disk_guid); + free(partitions); + out: + if (!ret) + free(gpt_pte); + return ret; +} + +/** + * gpt_enumerate() - Enumerate partition names into environment variable. + * + * Enumerate partition names. Partition names are stored in gpt_partition_list + * environment variable. Each partition name is delimited by space. + * + * @desc: block device descriptor + * + * @Return: '0' on success and -ve error on failure + */ +static int gpt_enumerate(struct blk_desc *desc) +{ + struct part_driver *first_drv, *part_drv; + int str_len = 0, tmp_len; + char part_list[2048]; + int n_drvs; + char *ptr; + + part_list[0] = 0; + n_drvs = part_driver_get_count(); + if (!n_drvs) { + printf("Failed to get partition driver count\n"); + return -ENOENT; + } + + first_drv = part_driver_get_first(); + for (part_drv = first_drv; part_drv != first_drv + n_drvs; part_drv++) { + struct disk_partition pinfo; + int ret; + int i; + + if (part_drv->test(desc)) + continue; + + for (i = 1; i < part_drv->max_entries; i++) { + ret = part_drv->get_info(desc, i, &pinfo); + if (ret) + continue; + + ptr = &part_list[str_len]; + tmp_len = strlen((const char *)pinfo.name); + str_len += tmp_len; + /* +1 for space */ + str_len++; + if (str_len > sizeof(part_list)) { + printf("Error insufficient memory\n"); + return -ENOMEM; + } + strcpy(ptr, (const char *)pinfo.name); + /* One byte for space(" ") delimiter */ + ptr[tmp_len] = ' '; + } + if (*part_list) + part_list[strlen(part_list) - 1] = 0; + break; + } + debug("setenv gpt_partition_list %s\n", part_list); + + return env_set("gpt_partition_list", part_list); +} + +/** + * gpt_setenv_part_variables() - setup partition environmental variables + * + * Setup the gpt_partition_name, gpt_partition_entry, gpt_partition_addr + * and gpt_partition_size, gpt_partition_bootable environment variables. + * + * @pinfo: pointer to disk partition + * @i: partition entry + * + * @Return: '0' on success and -ENOENT on failure + */ +static int gpt_setenv_part_variables(struct disk_partition *pinfo, int i) +{ + int ret; + + ret = env_set_hex("gpt_partition_addr", pinfo->start); + if (ret) + goto fail; + + ret = env_set_hex("gpt_partition_size", pinfo->size); + if (ret) + goto fail; + + ret = env_set_hex("gpt_partition_entry", i); + if (ret) + goto fail; + + ret = env_set("gpt_partition_name", (const char *)pinfo->name); + if (ret) + goto fail; + + ret = env_set_ulong("gpt_partition_bootable", !!(pinfo->bootable & PART_BOOTABLE)); + if (ret) + goto fail; + + return 0; + +fail: + return -ENOENT; +} + +/** + * gpt_setenv() - Dynamically setup environment variables. + * + * Dynamically setup environment variables for name, index, offset and size + * for partition in GPT table after running "gpt setenv" for a partition name. + * + * @desc: block device descriptor + * @name: partition name + * + * @Return: '0' on success and -ve err on failure + */ +static int gpt_setenv(struct blk_desc *desc, const char *name) +{ + struct part_driver *first_drv, *part_drv; + int n_drvs; + int ret = -1; + + n_drvs = part_driver_get_count(); + if (!n_drvs) { + printf("Failed to get partition driver count\n"); + goto fail; + } + + first_drv = part_driver_get_first(); + for (part_drv = first_drv; part_drv != first_drv + n_drvs; part_drv++) { + struct disk_partition pinfo; + int i; + + for (i = 1; i < part_drv->max_entries; i++) { + ret = part_drv->get_info(desc, i, &pinfo); + if (ret) + continue; + + if (!strcmp(name, (const char *)pinfo.name)) { + /* match found, setup environment variables */ + ret = gpt_setenv_part_variables(&pinfo, i); + if (ret) + goto fail; + + return 0; + } + } + } + +fail: + return ret; +} + +static int do_disk_guid(struct blk_desc *dev_desc, char * const namestr) +{ + int ret; + char disk_guid[UUID_STR_LEN + 1]; + + ret = get_disk_guid(dev_desc, disk_guid); + if (ret < 0) + return CMD_RET_FAILURE; + + if (namestr) + env_set(namestr, disk_guid); + else + printf("%s\n", disk_guid); + + return ret; +} + +#ifdef CONFIG_CMD_GPT_RENAME +static int do_rename_gpt_parts(struct blk_desc *dev_desc, char *subcomm, + char *name1, char *name2) +{ + struct list_head *pos; + struct disk_part *curr; + struct disk_partition *new_partitions = NULL; + char disk_guid[UUID_STR_LEN + 1]; + char *partitions_list, *str_disk_guid = NULL; + u8 part_count = 0; + int partlistlen, ret, numparts = 0, partnum, i = 1, ctr1 = 0, ctr2 = 0; + + if (!subcomm || !name1 || !name2 || + (strcmp(subcomm, "swap") && strcmp(subcomm, "rename") && + strcmp(subcomm, "transpose"))) + return -EINVAL; + + ret = get_disk_guid(dev_desc, disk_guid); + if (ret < 0) + return ret; + /* + * Allocates disk_partitions, requiring matching call to del_gpt_info() + * if successful. + */ + numparts = get_gpt_info(dev_desc); + if (numparts <= 0) + return numparts ? numparts : -ENODEV; + + partlistlen = calc_parts_list_len(numparts); + partitions_list = malloc(partlistlen); + if (!partitions_list) { + del_gpt_info(); + return -ENOMEM; + } + memset(partitions_list, '\0', partlistlen); + + ret = create_gpt_partitions_list(numparts, disk_guid, partitions_list); + if (ret < 0) { + free(partitions_list); + return ret; + } + /* + * Uncomment the following line to print a string that 'gpt write' + * or 'gpt verify' will accept as input. + */ + debug("OLD partitions_list is %s with %u chars\n", partitions_list, + (unsigned)strlen(partitions_list)); + + /* set_gpt_info allocates new_partitions and str_disk_guid */ + ret = set_gpt_info(dev_desc, partitions_list, &str_disk_guid, + &new_partitions, &part_count); + if (ret < 0) + goto out; + + if (!strcmp(subcomm, "swap")) { + if ((strlen(name1) >= PART_NAME_LEN) || (strlen(name2) >= PART_NAME_LEN)) { + printf("Names longer than %d characters are truncated.\n", + PART_NAME_LEN - 1); + ret = -EINVAL; + goto out; + } + list_for_each(pos, &disk_partitions) { + curr = list_entry(pos, struct disk_part, list); + if (!strcmp((char *)curr->gpt_part_info.name, name1)) { + strcpy((char *)curr->gpt_part_info.name, name2); + ctr1++; + } else if (!strcmp((char *)curr->gpt_part_info.name, name2)) { + strcpy((char *)curr->gpt_part_info.name, name1); + ctr2++; + } + } + if ((ctr1 + ctr2 < 2) || (ctr1 != ctr2)) { + printf("Cannot swap partition names except in pairs.\n"); + ret = -EINVAL; + goto out; + } + } else if (!strcmp(subcomm, "transpose")) { + int idx1, idx2; + struct disk_partition* first = NULL; + struct disk_partition* second= NULL; + struct disk_partition tmp_part; + + idx1 = simple_strtoul(name1, NULL, 10); + idx2 = simple_strtoul(name2, NULL, 10); + if (idx1 == idx2) { + printf("Cannot swap partition with itself\n"); + ret = -EINVAL; + goto out; + } + + list_for_each(pos, &disk_partitions) { + curr = list_entry(pos, struct disk_part, list); + if (curr->partnum == idx1) + first = &curr->gpt_part_info; + else if (curr->partnum == idx2) + second = &curr->gpt_part_info; + } + if (!first) { + printf("Illegal partition number %s\n", name1); + ret = -EINVAL; + goto out; + } + if (!second) { + printf("Illegal partition number %s\n", name2); + ret = -EINVAL; + goto out; + } + + tmp_part = *first; + *first = *second; + *second = tmp_part; + } else { /* rename */ + if (strlen(name2) >= PART_NAME_LEN) { + printf("Names longer than %d characters are truncated.\n", + PART_NAME_LEN - 1); + ret = -EINVAL; + goto out; + } + partnum = (int)simple_strtol(name1, NULL, 10); + if ((partnum < 0) || (partnum > numparts)) { + printf("Illegal partition number %s\n", name1); + ret = -EINVAL; + goto out; + } + ret = part_get_info(dev_desc, partnum, new_partitions); + if (ret < 0) + goto out; + + /* U-Boot partition numbering starts at 1 */ + list_for_each(pos, &disk_partitions) { + curr = list_entry(pos, struct disk_part, list); + if (i == partnum) { + strcpy((char *)curr->gpt_part_info.name, name2); + break; + } + i++; + } + } + + ret = create_gpt_partitions_list(numparts, disk_guid, partitions_list); + if (ret < 0) + goto out; + debug("NEW partitions_list is %s with %u chars\n", partitions_list, + (unsigned)strlen(partitions_list)); + + ret = set_gpt_info(dev_desc, partitions_list, &str_disk_guid, + &new_partitions, &part_count); + /* + * Even though valid pointers are here passed into set_gpt_info(), + * it mallocs again, and there's no way to tell which failed. + */ + if (ret < 0) + goto out; + + debug("Writing new partition table\n"); + ret = gpt_restore(dev_desc, disk_guid, new_partitions, numparts); + if (ret < 0) { + printf("Writing new partition table failed\n"); + goto out; + } + + debug("Reading back new partition table\n"); + /* + * Empty the existing disk_partitions list, as otherwise the memory in + * the original list is unreachable. + */ + del_gpt_info(); + numparts = get_gpt_info(dev_desc); + if (numparts <= 0) { + ret = numparts ? numparts : -ENODEV; + goto out; + } + printf("new partition table with %d partitions is:\n", numparts); + print_gpt_info(); + out: + del_gpt_info(); +#ifdef CONFIG_RANDOM_UUID + free(str_disk_guid); +#endif + free(new_partitions); + free(partitions_list); + return ret; +} + +/** + * gpt_set_bootable() - Set bootable flags for partitions + * + * Sets the bootable flag for any partition names in the comma separated list of + * partition names. Any partitions not in the list have their bootable flag + * cleared + * + * @desc: block device descriptor + * @name: Comma separated list of partition names + * + * @Return: '0' on success and -ve error on failure + */ +static int gpt_set_bootable(struct blk_desc *blk_dev_desc, char *const part_list) +{ + char *name; + char disk_guid[UUID_STR_LEN + 1]; + struct list_head *pos; + struct disk_part *curr; + struct disk_partition *partitions = NULL; + int part_count = 0; + int ret = get_disk_guid(blk_dev_desc, disk_guid); + + if (ret < 0) + return ret; + + ret = get_gpt_info(blk_dev_desc); + if (ret <= 0) + goto out; + + part_count = ret; + partitions = malloc(sizeof(*partitions) * part_count); + if (!partitions) { + ret = -ENOMEM; + goto out; + } + + /* Copy partitions and clear bootable flag */ + part_count = 0; + list_for_each(pos, &disk_partitions) { + curr = list_entry(pos, struct disk_part, list); + partitions[part_count] = curr->gpt_part_info; + partitions[part_count].bootable &= ~PART_BOOTABLE; + part_count++; + } + + name = strtok(part_list, ","); + while (name) { + bool found = false; + + for (int i = 0; i < part_count; i++) { + if (strcmp((char *)partitions[i].name, name) == 0) { + partitions[i].bootable |= PART_BOOTABLE; + found = true; + } + } + + if (!found) { + printf("Warning: No partition matching '%s' found\n", + name); + } + + name = strtok(NULL, ","); + } + + ret = gpt_restore(blk_dev_desc, disk_guid, partitions, part_count); + +out: + del_gpt_info(); + + if (partitions) + free(partitions); + + return ret; +} +#endif + +/** + * do_gpt(): Perform GPT operations + * + * @param cmdtp - command name + * @param flag + * @param argc + * @param argv + * + * Return: zero on success; otherwise error + */ +static int do_gpt(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int ret = CMD_RET_SUCCESS; + int dev = 0; + char *ep; + struct blk_desc *blk_dev_desc = NULL; + +#ifndef CONFIG_CMD_GPT_RENAME + if (argc < 4 || argc > 5) +#else + if (argc < 4 || argc > 6) +#endif + return CMD_RET_USAGE; + + dev = (int)dectoul(argv[3], &ep); + if (!ep || ep[0] != '\0') { + printf("'%s' is not a number\n", argv[3]); + return CMD_RET_USAGE; + } + blk_dev_desc = blk_get_dev(argv[2], dev); + if (!blk_dev_desc) { + printf("%s: %s dev %d NOT available\n", + __func__, argv[2], dev); + return CMD_RET_FAILURE; + } + + if (strcmp(argv[1], "repair") == 0) { + printf("Repairing GPT: "); + ret = gpt_repair(blk_dev_desc); + } else if ((strcmp(argv[1], "write") == 0) && (argc == 5)) { + printf("Writing GPT: "); + ret = gpt_default(blk_dev_desc, argv[4]); + } else if ((strcmp(argv[1], "verify") == 0)) { + ret = gpt_verify(blk_dev_desc, argv[4]); + printf("Verify GPT: "); + } else if ((strcmp(argv[1], "setenv") == 0)) { + ret = gpt_setenv(blk_dev_desc, argv[4]); + } else if ((strcmp(argv[1], "enumerate") == 0)) { + ret = gpt_enumerate(blk_dev_desc); + } else if (strcmp(argv[1], "guid") == 0) { + ret = do_disk_guid(blk_dev_desc, argv[4]); +#ifdef CONFIG_CMD_GPT_RENAME + } else if (strcmp(argv[1], "read") == 0) { + ret = do_get_gpt_info(blk_dev_desc, (argc == 5) ? argv[4] : NULL); + } else if ((strcmp(argv[1], "swap") == 0) || + (strcmp(argv[1], "rename") == 0) || + (strcmp(argv[1], "transpose") == 0)) { + ret = do_rename_gpt_parts(blk_dev_desc, argv[1], argv[4], argv[5]); + } else if ((strcmp(argv[1], "set-bootable") == 0)) { + ret = gpt_set_bootable(blk_dev_desc, argv[4]); +#endif + } else { + return CMD_RET_USAGE; + } + + if (ret) { + printf("error!\n"); + return CMD_RET_FAILURE; + } + + printf("success!\n"); + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(gpt, CONFIG_SYS_MAXARGS, 1, do_gpt, + "GUID Partition Table", + "<command> <interface> <dev> <partitions_list>\n" + " - GUID partition table restoration and validity check\n" + " Restore or verify GPT information on a device connected\n" + " to interface\n" + " Example usage:\n" + " gpt repair mmc 0\n" + " - repair the GPT on the device\n" + " gpt write mmc 0 $partitions\n" + " - write the GPT to device\n" + " gpt verify mmc 0 $partitions\n" + " - verify the GPT on device against $partitions\n" + " gpt setenv mmc 0 $name\n" + " - setup environment variables for partition $name:\n" + " gpt_partition_addr, gpt_partition_size,\n" + " gpt_partition_name, gpt_partition_entry,\n" + " gpt_partition_bootable\n" + " gpt enumerate mmc 0\n" + " - store list of partitions to gpt_partition_list environment variable\n" + " gpt guid <interface> <dev>\n" + " - print disk GUID\n" + " gpt guid <interface> <dev> <varname>\n" + " - set environment variable to disk GUID\n" + " Example usage:\n" + " gpt guid mmc 0\n" + " gpt guid mmc 0 varname\n" +#ifdef CONFIG_CMD_GPT_RENAME + "gpt partition renaming commands:\n" + " gpt read <interface> <dev> [<varname>]\n" + " - read GPT into a data structure for manipulation\n" + " - read GPT partitions into environment variable\n" + " gpt swap <interface> <dev> <name1> <name2>\n" + " - change all partitions named name1 to name2\n" + " and vice-versa\n" + " gpt transpose <interface> <dev> <part1> <part2>\n" + " - Swap the order of the entries for part1 and part2 in the partition table\n" + " gpt rename <interface> <dev> <part> <name>\n" + " - rename the specified partition\n" + " gpt set-bootable <interface> <dev> <list>\n" + " - make partition names in list bootable\n" + " Example usage:\n" + " gpt swap mmc 0 foo bar\n" + " gpt rename mmc 0 3 foo\n" + " gpt set-bootable mmc 0 boot_a,boot_b\n" + " gpt transpose mmc 0 1 2\n" +#endif +); diff --git a/cmd/hash.c b/cmd/hash.c new file mode 100644 index 00000000000..96d0e443a5b --- /dev/null +++ b/cmd/hash.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2012 The Chromium OS Authors. + * + * (C) Copyright 2011 + * Joe Hershberger, National Instruments, joe.hershberger@ni.com + * + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <env.h> +#include <hash.h> +#include <linux/ctype.h> + +#if IS_ENABLED(CONFIG_HASH_VERIFY) +#define HARGS 6 +#else +#define HARGS 5 +#endif + +static int do_hash(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *s; + int flags = HASH_FLAG_ENV; + + if (argc < 4) + return CMD_RET_USAGE; + +#if IS_ENABLED(CONFIG_HASH_VERIFY) + if (!strcmp(argv[1], "-v")) { + flags |= HASH_FLAG_VERIFY; + argc--; + argv++; + } +#endif + /* Move forward to 'algorithm' parameter */ + argc--; + argv++; + for (s = *argv; *s; s++) + *s = tolower(*s); + return hash_command(*argv, flags, cmdtp, flag, argc - 1, argv + 1); +} + +U_BOOT_CMD( + hash, HARGS, 1, do_hash, + "compute hash message digest", + "algorithm address count [[*]hash_dest]\n" + " - compute message digest [save to env var / *address]" +#if IS_ENABLED(CONFIG_HASH_VERIFY) + "\nhash -v algorithm address count [*]hash\n" + " - verify message digest of memory area to immediate value, \n" + " env var or *address" +#endif +); diff --git a/cmd/help.c b/cmd/help.c new file mode 100644 index 00000000000..1be83ba607d --- /dev/null +++ b/cmd/help.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2000-2009 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> + +static int do_help(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *start = ll_entry_start(struct cmd_tbl, cmd); + const int len = ll_entry_count(struct cmd_tbl, cmd); + return _do_help(start, len, cmdtp, flag, argc, argv); +} + +U_BOOT_CMD( + help, CONFIG_SYS_MAXARGS, 1, do_help, + "print command description/usage", + "\n" + " - print brief description of all commands\n" + "help command ...\n" + " - print detailed usage of 'command'" +); + +/* + * This does not use the U_BOOT_CMD macro as ? can't be used in symbol names + * nor can we rely on the CONFIG_SYS_LONGHELP helper macro + */ +ll_entry_declare(struct cmd_tbl, question_mark, cmd) = { + "?", CONFIG_SYS_MAXARGS, cmd_always_repeatable, do_help, + "alias for 'help'", +#ifdef CONFIG_SYS_LONGHELP + "" +#endif /* CONFIG_SYS_LONGHELP */ +}; diff --git a/cmd/history.c b/cmd/history.c new file mode 100644 index 00000000000..8972986ca9d --- /dev/null +++ b/cmd/history.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2023 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <cli.h> + +static int do_history(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + cread_print_hist_list(); + + return 0; +} + +U_BOOT_CMD( + history, CONFIG_SYS_MAXARGS, 1, do_history, + "print command history", + "" +); diff --git a/cmd/host.c b/cmd/host.c new file mode 100644 index 00000000000..e03576b4d2d --- /dev/null +++ b/cmd/host.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2012, Google Inc. + */ + +#include <command.h> +#include <dm.h> +#include <fs.h> +#include <part.h> +#include <sandbox_host.h> +#include <dm/device_compat.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> +#include <linux/errno.h> +#include <linux/log2.h> + +static int do_host_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_load(cmdtp, flag, argc, argv, FS_TYPE_SANDBOX); +} + +static int do_host_ls(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_ls(cmdtp, flag, argc, argv, FS_TYPE_SANDBOX); +} + +static int do_host_size(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_size(cmdtp, flag, argc, argv, FS_TYPE_SANDBOX); +} + +static int do_host_save(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_save(cmdtp, flag, argc, argv, FS_TYPE_SANDBOX); +} + +static int do_host_bind(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool removable = false; + struct udevice *dev; + const char *label; + char *file; + unsigned long blksz = DEFAULT_BLKSZ; + int ret; + + /* Skip 'bind' */ + argc--; + argv++; + if (argc < 2) + return CMD_RET_USAGE; + + if (!strcmp(argv[0], "-r")) { + removable = true; + argc--; + argv++; + } + + if (argc < 2 || argc > 3) + return CMD_RET_USAGE; + label = argv[0]; + file = argv[1]; + if (argc > 2) { + blksz = dectoul(argv[2], NULL); + if (blksz < DEFAULT_BLKSZ || !is_power_of_2(blksz)) { + printf("blksz must be >= 512 and power of 2\n"); + return CMD_RET_FAILURE; + } + } + + ret = host_create_attach_file(label, file, removable, blksz, &dev); + if (ret) { + printf("Cannot create device / bind file\n"); + return CMD_RET_FAILURE; + } + + return 0; +} + +/** + * parse_host_label() - Parse a device label or sequence number + * + * This shows an error if it returns NULL + * + * @label: String containing the label or sequence number + * Returns: Associated device, or NULL if not found + */ +static struct udevice *parse_host_label(const char *label) +{ + struct udevice *dev; + + dev = host_find_by_label(label); + if (!dev) { + int devnum; + char *ep; + + devnum = hextoul(label, &ep); + if (*ep || + uclass_find_device_by_seq(UCLASS_HOST, devnum, &dev)) { + printf("No such device '%s'\n", label); + return NULL; + } + } + + return dev; +} + +static int do_host_unbind(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + const char *label; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + label = argv[1]; + dev = parse_host_label(label); + if (!dev) + return CMD_RET_FAILURE; + + ret = host_detach_file(dev); + if (ret) { + printf("Cannot detach file (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + + ret = device_unbind(dev); + if (ret) { + printf("Cannot attach file\n"); + ret = device_unbind(dev); + if (ret) + printf("Cannot unbind device '%s'\n", dev->name); + return CMD_RET_FAILURE; + } + + return 0; +} + +static void show_host_dev(struct udevice *dev) +{ + struct host_sb_plat *plat = dev_get_plat(dev); + struct blk_desc *desc; + struct udevice *blk; + int ret; + + printf("%3d ", dev_seq(dev)); + if (!plat->fd) { + printf("Not bound to a backing file\n"); + return; + } + ret = blk_get_from_parent(dev, &blk); + if (ret) /* cannot happen */ + return; + + desc = dev_get_uclass_plat(blk); + printf("%12lu %6lu %-15s %s\n", (unsigned long)desc->lba, desc->blksz, + plat->label, plat->filename); +} + +static int do_host_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + + if (argc < 1) + return CMD_RET_USAGE; + + dev = NULL; + if (argc >= 2) { + dev = parse_host_label(argv[1]); + if (!dev) + return CMD_RET_FAILURE; + } + + printf("%3s %12s %6s %-15s %s\n", + "dev", "blocks", "blksz", "label", "path"); + if (dev) { + show_host_dev(dev); + } else { + struct uclass *uc; + + uclass_id_foreach_dev(UCLASS_HOST, dev, uc) + show_host_dev(dev); + } + + return 0; +} + +static int do_host_dev(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + const char *label; + + if (argc < 1 || argc > 3) + return CMD_RET_USAGE; + + if (argc == 1) { + struct host_sb_plat *plat; + + dev = host_get_cur_dev(); + if (!dev) { + printf("No current host device\n"); + return CMD_RET_FAILURE; + } + plat = dev_get_plat(dev); + printf("Current host device: %d: %s\n", dev_seq(dev), + plat->label); + return 0; + } + + label = argv[1]; + dev = parse_host_label(argv[1]); + if (!dev) + return CMD_RET_FAILURE; + + host_set_cur_dev(dev); + + return 0; +} + +static struct cmd_tbl cmd_host_sub[] = { + U_BOOT_CMD_MKENT(load, 7, 0, do_host_load, "", ""), + U_BOOT_CMD_MKENT(ls, 3, 0, do_host_ls, "", ""), + U_BOOT_CMD_MKENT(save, 6, 0, do_host_save, "", ""), + U_BOOT_CMD_MKENT(size, 3, 0, do_host_size, "", ""), + U_BOOT_CMD_MKENT(bind, 4, 0, do_host_bind, "", ""), + U_BOOT_CMD_MKENT(unbind, 4, 0, do_host_unbind, "", ""), + U_BOOT_CMD_MKENT(info, 3, 0, do_host_info, "", ""), + U_BOOT_CMD_MKENT(dev, 0, 1, do_host_dev, "", ""), +}; + +static int do_host(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c; + + /* Skip past 'host' */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], cmd_host_sub, ARRAY_SIZE(cmd_host_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + host, 8, 1, do_host, + "Miscellaneous host commands", + "load hostfs - <addr> <filename> [<bytes> <offset>] - " + "load a file from host\n" + "host ls hostfs - <filename> - list files on host\n" + "host save hostfs - <addr> <filename> <bytes> [<offset>] - " + "save a file to host\n" + "host size hostfs - <filename> - determine size of file on host\n" + "host bind [-r] <label> <filename> [<blksz>] - bind \"host\" device to file,\n" + " and optionally set the device's logical block size\n" + " -r = mark as removable\n" + "host unbind <label> - unbind file from \"host\" device\n" + "host info [<label>] - show device binding & info\n" + "host dev [<label>] - set or retrieve the current host device\n" + "host commands use the \"hostfs\" device. The \"host\" device is used\n" + "with standard IO commands such as fatls or ext2load" +); diff --git a/cmd/i2c.c b/cmd/i2c.c new file mode 100644 index 00000000000..e021067e68a --- /dev/null +++ b/cmd/i2c.c @@ -0,0 +1,1984 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2009 + * Sergey Kubushyn, himself, ksi@koi8.net + * + * Changes for unified multibus/multiadapter I2C support. + * + * (C) Copyright 2001 + * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com. + */ + +/* + * I2C Functions similar to the standard memory functions. + * + * There are several parameters in many of the commands that bear further + * explanations: + * + * {i2c_chip} is the I2C chip address (the first byte sent on the bus). + * Each I2C chip on the bus has a unique address. On the I2C data bus, + * the address is the upper seven bits and the LSB is the "read/write" + * bit. Note that the {i2c_chip} address specified on the command + * line is not shifted up: e.g. a typical EEPROM memory chip may have + * an I2C address of 0x50, but the data put on the bus will be 0xA0 + * for write and 0xA1 for read. This "non shifted" address notation + * matches at least half of the data sheets :-/. + * + * {addr} is the address (or offset) within the chip. Small memory + * chips have 8 bit addresses. Large memory chips have 16 bit + * addresses. Other memory chips have 9, 10, or 11 bit addresses. + * Many non-memory chips have multiple registers and {addr} is used + * as the register index. Some non-memory chips have only one register + * and therefore don't need any {addr} parameter. + * + * The default {addr} parameter is one byte (.1) which works well for + * memories and registers with 8 bits of address space. + * + * You can specify the length of the {addr} field with the optional .0, + * .1, or .2 modifier (similar to the .b, .w, .l modifier). If you are + * manipulating a single register device which doesn't use an address + * field, use "0.0" for the address and the ".0" length field will + * suppress the address in the I2C data stream. This also works for + * successive reads using the I2C auto-incrementing memory pointer. + * + * If you are manipulating a large memory with 2-byte addresses, use + * the .2 address modifier, e.g. 210.2 addresses location 528 (decimal). + * + * Then there are the unfortunate memory chips that spill the most + * significant 1, 2, or 3 bits of address into the chip address byte. + * This effectively makes one chip (logically) look like 2, 4, or + * 8 chips. This is handled (awkwardly) by #defining + * CONFIG_SYS_I2C_EEPROM_ADDR_OVERFLOW and using the .1 modifier on the + * {addr} field (since .1 is the default, it doesn't actually have to + * be specified). Examples: given a memory chip at I2C chip address + * 0x50, the following would happen... + * i2c md 50 0 10 display 16 bytes starting at 0x000 + * On the bus: <S> A0 00 <E> <S> A1 <rd> ... <rd> + * i2c md 50 100 10 display 16 bytes starting at 0x100 + * On the bus: <S> A2 00 <E> <S> A3 <rd> ... <rd> + * i2c md 50 210 10 display 16 bytes starting at 0x210 + * On the bus: <S> A4 10 <E> <S> A5 <rd> ... <rd> + * This is awfully ugly. It would be nice if someone would think up + * a better way of handling this. + * + * Adapted from cmd_mem.c which is copyright Wolfgang Denk (wd@denx.de). + */ + +#include <bootretry.h> +#include <cli.h> +#include <command.h> +#include <console.h> +#include <dm.h> +#include <edid.h> +#include <errno.h> +#include <i2c.h> +#include <log.h> +#include <malloc.h> +#include <asm/byteorder.h> +#include <linux/compiler.h> +#include <linux/delay.h> +#include <u-boot/crc.h> + +/* Display values from last command. + * Memory modify remembered values are different from display memory. + */ +static uint i2c_dp_last_chip; +static uint i2c_dp_last_addr; +static uint i2c_dp_last_alen; +static uint i2c_dp_last_length = 0x10; + +static uint i2c_mm_last_chip; +static uint i2c_mm_last_addr; +static uint i2c_mm_last_alen; + +/* If only one I2C bus is present, the list of devices to ignore when + * the probe command is issued is represented by a 1D array of addresses. + * When multiple buses are present, the list is an array of bus-address + * pairs. The following macros take care of this */ + +#if defined(CFG_SYS_I2C_NOPROBES) +#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY) +static struct +{ + uchar bus; + uchar addr; +} i2c_no_probes[] = CFG_SYS_I2C_NOPROBES; +#define GET_BUS_NUM i2c_get_bus_num() +#define COMPARE_BUS(b,i) (i2c_no_probes[(i)].bus == (b)) +#define COMPARE_ADDR(a,i) (i2c_no_probes[(i)].addr == (a)) +#define NO_PROBE_ADDR(i) i2c_no_probes[(i)].addr +#else /* single bus */ +static uchar i2c_no_probes[] = CFG_SYS_I2C_NOPROBES; +#define GET_BUS_NUM 0 +#define COMPARE_BUS(b,i) ((b) == 0) /* Make compiler happy */ +#define COMPARE_ADDR(a,i) (i2c_no_probes[(i)] == (a)) +#define NO_PROBE_ADDR(i) i2c_no_probes[(i)] +#endif /* CONFIG_IS_ENABLED(SYS_I2C_LEGACY) */ +#endif + +#define DISP_LINE_LEN 16 + +/* + * Default for driver model is to use the chip's existing address length. + * For legacy code, this is not stored, so we need to use a suitable + * default. + */ +#if CONFIG_IS_ENABLED(DM_I2C) +#define DEFAULT_ADDR_LEN (-1) +#else +#define DEFAULT_ADDR_LEN 1 +#endif + +#if CONFIG_IS_ENABLED(DM_I2C) +static struct udevice *i2c_cur_bus; + +static int cmd_i2c_set_bus_num(unsigned int busnum) +{ + struct udevice *bus; + int ret; + + ret = uclass_get_device_by_seq(UCLASS_I2C, busnum, &bus); + if (ret) { + debug("%s: No bus %d\n", __func__, busnum); + return ret; + } + i2c_cur_bus = bus; + + return 0; +} + +static int i2c_get_cur_bus(struct udevice **busp) +{ +#ifdef CONFIG_I2C_SET_DEFAULT_BUS_NUM + if (!i2c_cur_bus) { + if (cmd_i2c_set_bus_num(CONFIG_I2C_DEFAULT_BUS_NUMBER)) { + printf("Default I2C bus %d not found\n", + CONFIG_I2C_DEFAULT_BUS_NUMBER); + return -ENODEV; + } + } +#endif + + if (!i2c_cur_bus) { + puts("No I2C bus selected\n"); + return -ENODEV; + } + *busp = i2c_cur_bus; + + return 0; +} + +static int i2c_get_cur_bus_chip(uint chip_addr, struct udevice **devp) +{ + struct udevice *bus; + int ret; + + ret = i2c_get_cur_bus(&bus); + if (ret) + return ret; + + return i2c_get_chip(bus, chip_addr, 1, devp); +} + +#endif + +/** + * i2c_init_board() - Board-specific I2C bus init + * + * This function is the default no-op implementation of I2C bus + * initialization. This function can be overridden by board-specific + * implementation if needed. + */ +__weak +void i2c_init_board(void) +{ +} + +/** + * get_alen() - Small parser helper function to get address length + * + * Returns the address length. + */ +static uint get_alen(char *arg, int default_len) +{ + int j; + int alen; + + alen = default_len; + for (j = 0; j < 8; j++) { + if (arg[j] == '.') { + alen = arg[j+1] - '0'; + break; + } else if (arg[j] == '\0') + break; + } + return alen; +} + +enum i2c_err_op { + I2C_ERR_READ, + I2C_ERR_WRITE, +}; + +static int i2c_report_err(int ret, enum i2c_err_op op) +{ + printf("Error %s the chip: %d\n", + op == I2C_ERR_READ ? "reading" : "writing", ret); + + return CMD_RET_FAILURE; +} + +/** + * do_i2c_read() - Handle the "i2c read" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + * + * Syntax: + * i2c read {i2c_chip} {devaddr}{.0, .1, .2} {len} {memaddr} + */ +static int do_i2c_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint chip; + uint devaddr, length; + int alen; + u_char *memaddr; + int ret; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; +#endif + + if (argc != 5) + return CMD_RET_USAGE; + + /* + * I2C chip address + */ + chip = hextoul(argv[1], NULL); + + /* + * I2C data address within the chip. This can be 1 or + * 2 bytes long. Some day it might be 3 bytes long :-). + */ + devaddr = hextoul(argv[2], NULL); + alen = get_alen(argv[2], DEFAULT_ADDR_LEN); + if (alen > 3) + return CMD_RET_USAGE; + + /* + * Length is the number of objects, not number of bytes. + */ + length = hextoul(argv[3], NULL); + + /* + * memaddr is the address where to store things in memory + */ + memaddr = (u_char *)hextoul(argv[4], NULL); + +#if CONFIG_IS_ENABLED(DM_I2C) + ret = i2c_get_cur_bus_chip(chip, &dev); + if (!ret && alen != -1) + ret = i2c_set_chip_offset_len(dev, alen); + if (!ret) + ret = dm_i2c_read(dev, devaddr, memaddr, length); +#else + ret = i2c_read(chip, devaddr, alen, memaddr, length); +#endif + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); + + return 0; +} + +static int do_i2c_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint chip; + uint devaddr; + int length; + int alen; + u_char *memaddr; + int ret; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; + struct dm_i2c_chip *i2c_chip; +#endif + + if ((argc < 5) || (argc > 6)) + return cmd_usage(cmdtp); + + /* + * memaddr is the address where to store things in memory + */ + memaddr = (u_char *)hextoul(argv[1], NULL); + + /* + * I2C chip address + */ + chip = hextoul(argv[2], NULL); + + /* + * I2C data address within the chip. This can be 1 or + * 2 bytes long. Some day it might be 3 bytes long :-). + */ + devaddr = hextoul(argv[3], NULL); + alen = get_alen(argv[3], DEFAULT_ADDR_LEN); + if (alen > 3) + return cmd_usage(cmdtp); + + /* + * Length is the number of bytes. + */ + length = hextoul(argv[4], NULL); + +#if CONFIG_IS_ENABLED(DM_I2C) + ret = i2c_get_cur_bus_chip(chip, &dev); + if (!ret && alen != -1) + ret = i2c_set_chip_offset_len(dev, alen); + if (ret) + return i2c_report_err(ret, I2C_ERR_WRITE); + i2c_chip = dev_get_parent_plat(dev); + if (!i2c_chip) + return i2c_report_err(ret, I2C_ERR_WRITE); +#endif + + if (argc == 6 && !strcmp(argv[5], "-s")) { + /* + * Write all bytes in a single I2C transaction. If the target + * device is an EEPROM, it is your responsibility to not cross + * a page boundary. No write delay upon completion, take this + * into account if linking commands. + */ +#if CONFIG_IS_ENABLED(DM_I2C) + i2c_chip->flags &= ~DM_I2C_CHIP_WR_ADDRESS; + ret = dm_i2c_write(dev, devaddr, memaddr, length); +#else + ret = i2c_write(chip, devaddr, alen, memaddr, length); +#endif + if (ret) + return i2c_report_err(ret, I2C_ERR_WRITE); + } else { + /* + * Repeated addressing - perform <length> separate + * write transactions of one byte each + */ + while (length-- > 0) { +#if CONFIG_IS_ENABLED(DM_I2C) + i2c_chip->flags |= DM_I2C_CHIP_WR_ADDRESS; + ret = dm_i2c_write(dev, devaddr++, memaddr++, 1); +#else + ret = i2c_write(chip, devaddr++, alen, memaddr++, 1); +#endif + if (ret) + return i2c_report_err(ret, I2C_ERR_WRITE); +/* + * No write delay with FRAM devices. + */ +#if !defined(CONFIG_SYS_I2C_FRAM) + udelay(11000); +#endif + } + } + return 0; +} + +#if CONFIG_IS_ENABLED(DM_I2C) +static int do_i2c_flags(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + uint flags; + int chip; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + chip = hextoul(argv[1], NULL); + ret = i2c_get_cur_bus_chip(chip, &dev); + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); + + if (argc > 2) { + flags = hextoul(argv[2], NULL); + ret = i2c_set_chip_flags(dev, flags); + } else { + ret = i2c_get_chip_flags(dev, &flags); + if (!ret) + printf("%x\n", flags); + } + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); + + return 0; +} + +static int do_i2c_olen(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + uint olen; + int chip; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + chip = hextoul(argv[1], NULL); + ret = i2c_get_cur_bus_chip(chip, &dev); + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); + + if (argc > 2) { + olen = hextoul(argv[2], NULL); + ret = i2c_set_chip_offset_len(dev, olen); + } else { + ret = i2c_get_chip_offset_len(dev); + if (ret >= 0) { + printf("%x\n", ret); + ret = 0; + } + } + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); + + return 0; +} +#endif + +/** + * do_i2c_md() - Handle the "i2c md" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + * + * Syntax: + * i2c md {i2c_chip} {addr}{.0, .1, .2} {len} + */ +static int do_i2c_md(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint chip; + uint addr, length; + int alen; + int j; + uint nbytes, linebytes; + int ret; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; +#endif + + /* We use the last specified parameters, unless new ones are + * entered. + */ + chip = i2c_dp_last_chip; + addr = i2c_dp_last_addr; + alen = i2c_dp_last_alen; + length = i2c_dp_last_length; + + if (argc < 3) + return CMD_RET_USAGE; + + if ((flag & CMD_FLAG_REPEAT) == 0) { + /* + * New command specified. + */ + + /* + * I2C chip address + */ + chip = hextoul(argv[1], NULL); + + /* + * I2C data address within the chip. This can be 1 or + * 2 bytes long. Some day it might be 3 bytes long :-). + */ + addr = hextoul(argv[2], NULL); + alen = get_alen(argv[2], DEFAULT_ADDR_LEN); + if (alen > 3) + return CMD_RET_USAGE; + + /* + * If another parameter, it is the length to display. + * Length is the number of objects, not number of bytes. + */ + if (argc > 3) + length = hextoul(argv[3], NULL); + } + +#if CONFIG_IS_ENABLED(DM_I2C) + ret = i2c_get_cur_bus_chip(chip, &dev); + if (!ret && alen != -1) + ret = i2c_set_chip_offset_len(dev, alen); + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); +#endif + + /* + * Print the lines. + * + * We buffer all read data, so we can make sure data is read only + * once. + */ + nbytes = length; + do { + unsigned char linebuf[DISP_LINE_LEN]; + unsigned char *cp; + + linebytes = (nbytes > DISP_LINE_LEN) ? DISP_LINE_LEN : nbytes; + +#if CONFIG_IS_ENABLED(DM_I2C) + ret = dm_i2c_read(dev, addr, linebuf, linebytes); +#else + ret = i2c_read(chip, addr, alen, linebuf, linebytes); +#endif + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); + else { + printf("%04x:", addr); + cp = linebuf; + for (j=0; j<linebytes; j++) { + printf(" %02x", *cp++); + addr++; + } + puts (" "); + cp = linebuf; + for (j=0; j<linebytes; j++) { + if ((*cp < 0x20) || (*cp > 0x7e)) + puts ("."); + else + printf("%c", *cp); + cp++; + } + putc ('\n'); + } + nbytes -= linebytes; + } while (nbytes > 0); + + i2c_dp_last_chip = chip; + i2c_dp_last_addr = addr; + i2c_dp_last_alen = alen; + i2c_dp_last_length = length; + + return 0; +} + +/** + * do_i2c_mw() - Handle the "i2c mw" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + * + * Syntax: + * i2c mw {i2c_chip} {addr}{.0, .1, .2} {data} [{count}] + */ +static int do_i2c_mw(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint chip; + ulong addr; + int alen; + uchar byte; + int count; + int ret; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; +#endif + + if ((argc < 4) || (argc > 5)) + return CMD_RET_USAGE; + + /* + * Chip is always specified. + */ + chip = hextoul(argv[1], NULL); + + /* + * Address is always specified. + */ + addr = hextoul(argv[2], NULL); + alen = get_alen(argv[2], DEFAULT_ADDR_LEN); + if (alen > 3) + return CMD_RET_USAGE; + +#if CONFIG_IS_ENABLED(DM_I2C) + ret = i2c_get_cur_bus_chip(chip, &dev); + if (!ret && alen != -1) + ret = i2c_set_chip_offset_len(dev, alen); + if (ret) + return i2c_report_err(ret, I2C_ERR_WRITE); +#endif + /* + * Value to write is always specified. + */ + byte = hextoul(argv[3], NULL); + + /* + * Optional count + */ + if (argc == 5) + count = hextoul(argv[4], NULL); + else + count = 1; + + while (count-- > 0) { +#if CONFIG_IS_ENABLED(DM_I2C) + ret = dm_i2c_write(dev, addr++, &byte, 1); +#else + ret = i2c_write(chip, addr++, alen, &byte, 1); +#endif + if (ret) + return i2c_report_err(ret, I2C_ERR_WRITE); + /* + * Wait for the write to complete. The write can take + * up to 10mSec (we allow a little more time). + */ +/* + * No write delay with FRAM devices. + */ +#if !defined(CONFIG_SYS_I2C_FRAM) + udelay(11000); +#endif + } + + return 0; +} + +/** + * do_i2c_crc() - Handle the "i2c crc32" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Calculate a CRC on memory + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + * + * Syntax: + * i2c crc32 {i2c_chip} {addr}{.0, .1, .2} {count} + */ +static int do_i2c_crc(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint chip; + ulong addr; + int alen; + int count; + uchar byte; + ulong crc; + ulong err; + int ret = 0; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; +#endif + + if (argc < 4) + return CMD_RET_USAGE; + + /* + * Chip is always specified. + */ + chip = hextoul(argv[1], NULL); + + /* + * Address is always specified. + */ + addr = hextoul(argv[2], NULL); + alen = get_alen(argv[2], DEFAULT_ADDR_LEN); + if (alen > 3) + return CMD_RET_USAGE; + +#if CONFIG_IS_ENABLED(DM_I2C) + ret = i2c_get_cur_bus_chip(chip, &dev); + if (!ret && alen != -1) + ret = i2c_set_chip_offset_len(dev, alen); + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); +#endif + /* + * Count is always specified + */ + count = hextoul(argv[3], NULL); + + printf ("CRC32 for %08lx ... %08lx ==> ", addr, addr + count - 1); + /* + * CRC a byte at a time. This is going to be slooow, but hey, the + * memories are small and slow too so hopefully nobody notices. + */ + crc = 0; + err = 0; + while (count-- > 0) { +#if CONFIG_IS_ENABLED(DM_I2C) + ret = dm_i2c_read(dev, addr, &byte, 1); +#else + ret = i2c_read(chip, addr, alen, &byte, 1); +#endif + if (ret) + err++; + crc = crc32(crc, &byte, 1); + addr++; + } + if (err > 0) + i2c_report_err(ret, I2C_ERR_READ); + else + printf ("%08lx\n", crc); + + return 0; +} + +/** + * mod_i2c_mem() - Handle the "i2c mm" and "i2c nm" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Modify memory. + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + * + * Syntax: + * i2c mm{.b, .w, .l} {i2c_chip} {addr}{.0, .1, .2} + * i2c nm{.b, .w, .l} {i2c_chip} {addr}{.0, .1, .2} + */ +static int mod_i2c_mem(struct cmd_tbl *cmdtp, int incrflag, int flag, int argc, + char *const argv[]) +{ + uint chip; + ulong addr; + int alen; + ulong data; + int size = 1; + int nbytes; + int ret; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; +#endif + + if (argc != 3) + return CMD_RET_USAGE; + + bootretry_reset_cmd_timeout(); /* got a good command to get here */ + /* + * We use the last specified parameters, unless new ones are + * entered. + */ + chip = i2c_mm_last_chip; + addr = i2c_mm_last_addr; + alen = i2c_mm_last_alen; + + if ((flag & CMD_FLAG_REPEAT) == 0) { + /* + * New command specified. Check for a size specification. + * Defaults to byte if no or incorrect specification. + */ + size = cmd_get_data_size(argv[0], 1); + + /* + * Chip is always specified. + */ + chip = hextoul(argv[1], NULL); + + /* + * Address is always specified. + */ + addr = hextoul(argv[2], NULL); + alen = get_alen(argv[2], DEFAULT_ADDR_LEN); + if (alen > 3) + return CMD_RET_USAGE; + } + +#if CONFIG_IS_ENABLED(DM_I2C) + ret = i2c_get_cur_bus_chip(chip, &dev); + if (!ret && alen != -1) + ret = i2c_set_chip_offset_len(dev, alen); + if (ret) + return i2c_report_err(ret, I2C_ERR_WRITE); +#endif + + /* + * Print the address, followed by value. Then accept input for + * the next value. A non-converted value exits. + */ + do { + printf("%08lx:", addr); +#if CONFIG_IS_ENABLED(DM_I2C) + ret = dm_i2c_read(dev, addr, (uchar *)&data, size); +#else + ret = i2c_read(chip, addr, alen, (uchar *)&data, size); +#endif + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); + + data = cpu_to_be32(data); + if (size == 1) + printf(" %02lx", (data >> 24) & 0x000000FF); + else if (size == 2) + printf(" %04lx", (data >> 16) & 0x0000FFFF); + else + printf(" %08lx", data); + + nbytes = cli_readline(" ? "); + if (nbytes == 0) { + /* + * <CR> pressed as only input, don't modify current + * location and move to next. + */ + if (incrflag) + addr += size; + nbytes = size; + /* good enough to not time out */ + bootretry_reset_cmd_timeout(); + } +#ifdef CONFIG_BOOT_RETRY_TIME + else if (nbytes == -2) + break; /* timed out, exit the command */ +#endif + else { + char *endp; + + data = hextoul(console_buffer, &endp); + if (size == 1) + data = data << 24; + else if (size == 2) + data = data << 16; + data = be32_to_cpu(data); + nbytes = endp - console_buffer; + if (nbytes) { + /* + * good enough to not time out + */ + bootretry_reset_cmd_timeout(); +#if CONFIG_IS_ENABLED(DM_I2C) + ret = dm_i2c_write(dev, addr, (uchar *)&data, + size); +#else + ret = i2c_write(chip, addr, alen, + (uchar *)&data, size); +#endif + if (ret) + return i2c_report_err(ret, + I2C_ERR_WRITE); +#if CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS > 0 + udelay(CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS * 1000); +#endif + if (incrflag) + addr += size; + } + } + } while (nbytes); + + i2c_mm_last_chip = chip; + i2c_mm_last_addr = addr; + i2c_mm_last_alen = alen; + + return 0; +} + +/** + * do_i2c_probe() - Handle the "i2c probe" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + * + * Syntax: + * i2c probe {addr} + * + * Returns zero (success) if one or more I2C devices was found + */ +static int do_i2c_probe(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int j; + int addr = -1; + int found = 0; +#if defined(CFG_SYS_I2C_NOPROBES) + int k, skip; + unsigned int bus = GET_BUS_NUM; +#endif /* NOPROBES */ + int ret; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *cur_bus, *dev; + + if (i2c_get_cur_bus(&cur_bus)) + return CMD_RET_FAILURE; +#endif + + if (argc == 2) + addr = simple_strtol(argv[1], 0, 16); + + puts ("Valid chip addresses:"); + for (j = 0; j < 128; j++) { + if ((0 <= addr) && (j != addr)) + continue; + +#if defined(CFG_SYS_I2C_NOPROBES) + skip = 0; + for (k = 0; k < ARRAY_SIZE(i2c_no_probes); k++) { + if (COMPARE_BUS(bus, k) && COMPARE_ADDR(j, k)) { + skip = 1; + break; + } + } + if (skip) + continue; +#endif +#if CONFIG_IS_ENABLED(DM_I2C) + ret = dm_i2c_probe(cur_bus, j, 0, &dev); +#else + ret = i2c_probe(j); +#endif + if (ret == 0) { + printf(" %02X", j); + found++; + } + } + putc ('\n'); + +#if defined(CFG_SYS_I2C_NOPROBES) + puts ("Excluded chip addresses:"); + for (k = 0; k < ARRAY_SIZE(i2c_no_probes); k++) { + if (COMPARE_BUS(bus,k)) + printf(" %02X", NO_PROBE_ADDR(k)); + } + putc ('\n'); +#endif + + return (0 == found); +} + +/** + * do_i2c_loop() - Handle the "i2c loop" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + * + * Syntax: + * i2c loop {i2c_chip} {addr}{.0, .1, .2} [{length}] [{delay}] + * {length} - Number of bytes to read + * {delay} - A DECIMAL number and defaults to 1000 uSec + */ +static int do_i2c_loop(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint chip; + int alen; + uint addr; + uint length; + u_char bytes[16]; + int delay; + int ret; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; +#endif + + if (argc < 3) + return CMD_RET_USAGE; + + /* + * Chip is always specified. + */ + chip = hextoul(argv[1], NULL); + + /* + * Address is always specified. + */ + addr = hextoul(argv[2], NULL); + alen = get_alen(argv[2], DEFAULT_ADDR_LEN); + if (alen > 3) + return CMD_RET_USAGE; +#if CONFIG_IS_ENABLED(DM_I2C) + ret = i2c_get_cur_bus_chip(chip, &dev); + if (!ret && alen != -1) + ret = i2c_set_chip_offset_len(dev, alen); + if (ret) + return i2c_report_err(ret, I2C_ERR_WRITE); +#endif + + /* + * Length is the number of objects, not number of bytes. + */ + length = 1; + length = hextoul(argv[3], NULL); + if (length > sizeof(bytes)) + length = sizeof(bytes); + + /* + * The delay time (uSec) is optional. + */ + delay = 1000; + if (argc > 3) + delay = dectoul(argv[4], NULL); + /* + * Run the loop... + */ + while (1) { +#if CONFIG_IS_ENABLED(DM_I2C) + ret = dm_i2c_read(dev, addr, bytes, length); +#else + ret = i2c_read(chip, addr, alen, bytes, length); +#endif + if (ret) + i2c_report_err(ret, I2C_ERR_READ); + udelay(delay); + } + + /* NOTREACHED */ + return 0; +} + +/* + * The SDRAM command is separately configured because many + * (most?) embedded boards don't use SDRAM DIMMs. + * + * FIXME: Document and probably move elsewhere! + */ +#if defined(CONFIG_CMD_SDRAM) +static void print_ddr2_tcyc (u_char const b) +{ + printf ("%d.", (b >> 4) & 0x0F); + switch (b & 0x0F) { + case 0x0: + case 0x1: + case 0x2: + case 0x3: + case 0x4: + case 0x5: + case 0x6: + case 0x7: + case 0x8: + case 0x9: + printf ("%d ns\n", b & 0x0F); + break; + case 0xA: + puts ("25 ns\n"); + break; + case 0xB: + puts ("33 ns\n"); + break; + case 0xC: + puts ("66 ns\n"); + break; + case 0xD: + puts ("75 ns\n"); + break; + default: + puts ("?? ns\n"); + break; + } +} + +static void decode_bits (u_char const b, char const *str[], int const do_once) +{ + u_char mask; + + for (mask = 0x80; mask != 0x00; mask >>= 1, ++str) { + if (b & mask) { + puts (*str); + if (do_once) + return; + } + } +} + +/* + * Syntax: + * i2c sdram {i2c_chip} + */ +static int do_sdram(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum { unknown, EDO, SDRAM, DDR, DDR2, DDR3, DDR4 } type; + + uint chip; + u_char data[128]; + u_char cksum; + int j, ret; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; +#endif + + static const char *decode_CAS_DDR2[] = { + " TBD", " 6", " 5", " 4", " 3", " 2", " TBD", " TBD" + }; + + static const char *decode_CAS_default[] = { + " TBD", " 7", " 6", " 5", " 4", " 3", " 2", " 1" + }; + + static const char *decode_CS_WE_default[] = { + " TBD", " 6", " 5", " 4", " 3", " 2", " 1", " 0" + }; + + static const char *decode_byte21_default[] = { + " TBD (bit 7)\n", + " Redundant row address\n", + " Differential clock input\n", + " Registerd DQMB inputs\n", + " Buffered DQMB inputs\n", + " On-card PLL\n", + " Registered address/control lines\n", + " Buffered address/control lines\n" + }; + + static const char *decode_byte22_DDR2[] = { + " TBD (bit 7)\n", + " TBD (bit 6)\n", + " TBD (bit 5)\n", + " TBD (bit 4)\n", + " TBD (bit 3)\n", + " Supports partial array self refresh\n", + " Supports 50 ohm ODT\n", + " Supports weak driver\n" + }; + + static const char *decode_row_density_DDR2[] = { + "512 MiB", "256 MiB", "128 MiB", "16 GiB", + "8 GiB", "4 GiB", "2 GiB", "1 GiB" + }; + + static const char *decode_row_density_default[] = { + "512 MiB", "256 MiB", "128 MiB", "64 MiB", + "32 MiB", "16 MiB", "8 MiB", "4 MiB" + }; + + if (argc < 2) + return CMD_RET_USAGE; + + /* + * Chip is always specified. + */ + chip = hextoul(argv[1], NULL); + +#if CONFIG_IS_ENABLED(DM_I2C) + ret = i2c_get_cur_bus_chip(chip, &dev); + if (!ret) + ret = dm_i2c_read(dev, 0, data, sizeof(data)); +#else + ret = i2c_read(chip, 0, 1, data, sizeof(data)); +#endif + if (ret) { + puts ("No SDRAM Serial Presence Detect found.\n"); + return 1; + } + + cksum = 0; + for (j = 0; j < 63; j++) { + cksum += data[j]; + } + if (cksum != data[63]) { + printf ("WARNING: Configuration data checksum failure:\n" + " is 0x%02x, calculated 0x%02x\n", data[63], cksum); + } + printf ("SPD data revision %d.%d\n", + (data[62] >> 4) & 0x0F, data[62] & 0x0F); + printf ("Bytes used 0x%02X\n", data[0]); + printf ("Serial memory size 0x%02X\n", 1 << data[1]); + + puts ("Memory type "); + switch (data[2]) { + case 2: + type = EDO; + puts ("EDO\n"); + break; + case 4: + type = SDRAM; + puts ("SDRAM\n"); + break; + case 7: + type = DDR; + puts("DDR\n"); + break; + case 8: + type = DDR2; + puts ("DDR2\n"); + break; + case 11: + type = DDR3; + puts("DDR3\n"); + break; + case 12: + type = DDR4; + puts("DDR4\n"); + break; + default: + type = unknown; + puts ("unknown\n"); + break; + } + + puts ("Row address bits "); + if ((data[3] & 0x00F0) == 0) + printf ("%d\n", data[3] & 0x0F); + else + printf ("%d/%d\n", data[3] & 0x0F, (data[3] >> 4) & 0x0F); + + puts ("Column address bits "); + if ((data[4] & 0x00F0) == 0) + printf ("%d\n", data[4] & 0x0F); + else + printf ("%d/%d\n", data[4] & 0x0F, (data[4] >> 4) & 0x0F); + + switch (type) { + case DDR2: + printf ("Number of ranks %d\n", + (data[5] & 0x07) + 1); + break; + default: + printf ("Module rows %d\n", data[5]); + break; + } + + switch (type) { + case DDR2: + printf ("Module data width %d bits\n", data[6]); + break; + default: + printf ("Module data width %d bits\n", + (data[7] << 8) | data[6]); + break; + } + + puts ("Interface signal levels "); + switch(data[8]) { + case 0: puts ("TTL 5.0 V\n"); break; + case 1: puts ("LVTTL\n"); break; + case 2: puts ("HSTL 1.5 V\n"); break; + case 3: puts ("SSTL 3.3 V\n"); break; + case 4: puts ("SSTL 2.5 V\n"); break; + case 5: puts ("SSTL 1.8 V\n"); break; + default: puts ("unknown\n"); break; + } + + switch (type) { + case DDR2: + printf ("SDRAM cycle time "); + print_ddr2_tcyc (data[9]); + break; + default: + printf ("SDRAM cycle time %d.%d ns\n", + (data[9] >> 4) & 0x0F, data[9] & 0x0F); + break; + } + + switch (type) { + case DDR2: + printf ("SDRAM access time 0.%d%d ns\n", + (data[10] >> 4) & 0x0F, data[10] & 0x0F); + break; + default: + printf ("SDRAM access time %d.%d ns\n", + (data[10] >> 4) & 0x0F, data[10] & 0x0F); + break; + } + + puts ("EDC configuration "); + switch (data[11]) { + case 0: puts ("None\n"); break; + case 1: puts ("Parity\n"); break; + case 2: puts ("ECC\n"); break; + default: puts ("unknown\n"); break; + } + + if ((data[12] & 0x80) == 0) + puts ("No self refresh, rate "); + else + puts ("Self refresh, rate "); + + switch(data[12] & 0x7F) { + case 0: puts ("15.625 us\n"); break; + case 1: puts ("3.9 us\n"); break; + case 2: puts ("7.8 us\n"); break; + case 3: puts ("31.3 us\n"); break; + case 4: puts ("62.5 us\n"); break; + case 5: puts ("125 us\n"); break; + default: puts ("unknown\n"); break; + } + + switch (type) { + case DDR2: + printf ("SDRAM width (primary) %d\n", data[13]); + break; + default: + printf ("SDRAM width (primary) %d\n", data[13] & 0x7F); + if ((data[13] & 0x80) != 0) { + printf (" (second bank) %d\n", + 2 * (data[13] & 0x7F)); + } + break; + } + + switch (type) { + case DDR2: + if (data[14] != 0) + printf ("EDC width %d\n", data[14]); + break; + default: + if (data[14] != 0) { + printf ("EDC width %d\n", + data[14] & 0x7F); + + if ((data[14] & 0x80) != 0) { + printf (" (second bank) %d\n", + 2 * (data[14] & 0x7F)); + } + } + break; + } + + if (DDR2 != type) { + printf ("Min clock delay, back-to-back random column addresses " + "%d\n", data[15]); + } + + puts ("Burst length(s) "); + if (data[16] & 0x80) puts (" Page"); + if (data[16] & 0x08) puts (" 8"); + if (data[16] & 0x04) puts (" 4"); + if (data[16] & 0x02) puts (" 2"); + if (data[16] & 0x01) puts (" 1"); + putc ('\n'); + printf ("Number of banks %d\n", data[17]); + + switch (type) { + case DDR2: + puts ("CAS latency(s) "); + decode_bits (data[18], decode_CAS_DDR2, 0); + putc ('\n'); + break; + default: + puts ("CAS latency(s) "); + decode_bits (data[18], decode_CAS_default, 0); + putc ('\n'); + break; + } + + if (DDR2 != type) { + puts ("CS latency(s) "); + decode_bits (data[19], decode_CS_WE_default, 0); + putc ('\n'); + } + + if (DDR2 != type) { + puts ("WE latency(s) "); + decode_bits (data[20], decode_CS_WE_default, 0); + putc ('\n'); + } + + switch (type) { + case DDR2: + puts ("Module attributes:\n"); + if (data[21] & 0x80) + puts (" TBD (bit 7)\n"); + if (data[21] & 0x40) + puts (" Analysis probe installed\n"); + if (data[21] & 0x20) + puts (" TBD (bit 5)\n"); + if (data[21] & 0x10) + puts (" FET switch external enable\n"); + printf (" %d PLLs on DIMM\n", (data[21] >> 2) & 0x03); + if (data[20] & 0x11) { + printf (" %d active registers on DIMM\n", + (data[21] & 0x03) + 1); + } + break; + default: + puts ("Module attributes:\n"); + if (!data[21]) + puts (" (none)\n"); + else + decode_bits (data[21], decode_byte21_default, 0); + break; + } + + switch (type) { + case DDR2: + decode_bits (data[22], decode_byte22_DDR2, 0); + break; + default: + puts ("Device attributes:\n"); + if (data[22] & 0x80) puts (" TBD (bit 7)\n"); + if (data[22] & 0x40) puts (" TBD (bit 6)\n"); + if (data[22] & 0x20) puts (" Upper Vcc tolerance 5%\n"); + else puts (" Upper Vcc tolerance 10%\n"); + if (data[22] & 0x10) puts (" Lower Vcc tolerance 5%\n"); + else puts (" Lower Vcc tolerance 10%\n"); + if (data[22] & 0x08) puts (" Supports write1/read burst\n"); + if (data[22] & 0x04) puts (" Supports precharge all\n"); + if (data[22] & 0x02) puts (" Supports auto precharge\n"); + if (data[22] & 0x01) puts (" Supports early RAS# precharge\n"); + break; + } + + switch (type) { + case DDR2: + printf ("SDRAM cycle time (2nd highest CAS latency) "); + print_ddr2_tcyc (data[23]); + break; + default: + printf ("SDRAM cycle time (2nd highest CAS latency) %d." + "%d ns\n", (data[23] >> 4) & 0x0F, data[23] & 0x0F); + break; + } + + switch (type) { + case DDR2: + printf ("SDRAM access from clock (2nd highest CAS latency) 0." + "%d%d ns\n", (data[24] >> 4) & 0x0F, data[24] & 0x0F); + break; + default: + printf ("SDRAM access from clock (2nd highest CAS latency) %d." + "%d ns\n", (data[24] >> 4) & 0x0F, data[24] & 0x0F); + break; + } + + switch (type) { + case DDR2: + printf ("SDRAM cycle time (3rd highest CAS latency) "); + print_ddr2_tcyc (data[25]); + break; + default: + printf ("SDRAM cycle time (3rd highest CAS latency) %d." + "%d ns\n", (data[25] >> 4) & 0x0F, data[25] & 0x0F); + break; + } + + switch (type) { + case DDR2: + printf ("SDRAM access from clock (3rd highest CAS latency) 0." + "%d%d ns\n", (data[26] >> 4) & 0x0F, data[26] & 0x0F); + break; + default: + printf ("SDRAM access from clock (3rd highest CAS latency) %d." + "%d ns\n", (data[26] >> 4) & 0x0F, data[26] & 0x0F); + break; + } + + switch (type) { + case DDR2: + printf ("Minimum row precharge %d.%02d ns\n", + (data[27] >> 2) & 0x3F, 25 * (data[27] & 0x03)); + break; + default: + printf ("Minimum row precharge %d ns\n", data[27]); + break; + } + + switch (type) { + case DDR2: + printf ("Row active to row active min %d.%02d ns\n", + (data[28] >> 2) & 0x3F, 25 * (data[28] & 0x03)); + break; + default: + printf ("Row active to row active min %d ns\n", data[28]); + break; + } + + switch (type) { + case DDR2: + printf ("RAS to CAS delay min %d.%02d ns\n", + (data[29] >> 2) & 0x3F, 25 * (data[29] & 0x03)); + break; + default: + printf ("RAS to CAS delay min %d ns\n", data[29]); + break; + } + + printf ("Minimum RAS pulse width %d ns\n", data[30]); + + switch (type) { + case DDR2: + puts ("Density of each row "); + decode_bits (data[31], decode_row_density_DDR2, 1); + putc ('\n'); + break; + default: + puts ("Density of each row "); + decode_bits (data[31], decode_row_density_default, 1); + putc ('\n'); + break; + } + + switch (type) { + case DDR2: + puts ("Command and Address setup "); + if (data[32] >= 0xA0) { + printf ("1.%d%d ns\n", + ((data[32] >> 4) & 0x0F) - 10, data[32] & 0x0F); + } else { + printf ("0.%d%d ns\n", + ((data[32] >> 4) & 0x0F), data[32] & 0x0F); + } + break; + default: + printf ("Command and Address setup %c%d.%d ns\n", + (data[32] & 0x80) ? '-' : '+', + (data[32] >> 4) & 0x07, data[32] & 0x0F); + break; + } + + switch (type) { + case DDR2: + puts ("Command and Address hold "); + if (data[33] >= 0xA0) { + printf ("1.%d%d ns\n", + ((data[33] >> 4) & 0x0F) - 10, data[33] & 0x0F); + } else { + printf ("0.%d%d ns\n", + ((data[33] >> 4) & 0x0F), data[33] & 0x0F); + } + break; + default: + printf ("Command and Address hold %c%d.%d ns\n", + (data[33] & 0x80) ? '-' : '+', + (data[33] >> 4) & 0x07, data[33] & 0x0F); + break; + } + + switch (type) { + case DDR2: + printf ("Data signal input setup 0.%d%d ns\n", + (data[34] >> 4) & 0x0F, data[34] & 0x0F); + break; + default: + printf ("Data signal input setup %c%d.%d ns\n", + (data[34] & 0x80) ? '-' : '+', + (data[34] >> 4) & 0x07, data[34] & 0x0F); + break; + } + + switch (type) { + case DDR2: + printf ("Data signal input hold 0.%d%d ns\n", + (data[35] >> 4) & 0x0F, data[35] & 0x0F); + break; + default: + printf ("Data signal input hold %c%d.%d ns\n", + (data[35] & 0x80) ? '-' : '+', + (data[35] >> 4) & 0x07, data[35] & 0x0F); + break; + } + + puts ("Manufacturer's JEDEC ID "); + for (j = 64; j <= 71; j++) + printf ("%02X ", data[j]); + putc ('\n'); + printf ("Manufacturing Location %02X\n", data[72]); + puts ("Manufacturer's Part Number "); + for (j = 73; j <= 90; j++) + printf ("%02X ", data[j]); + putc ('\n'); + printf ("Revision Code %02X %02X\n", data[91], data[92]); + printf ("Manufacturing Date %02X %02X\n", data[93], data[94]); + puts ("Assembly Serial Number "); + for (j = 95; j <= 98; j++) + printf ("%02X ", data[j]); + putc ('\n'); + + if (DDR2 != type) { + printf ("Speed rating PC%d\n", + data[126] == 0x66 ? 66 : data[126]); + } + return 0; +} +#endif + +/* + * Syntax: + * i2c edid {i2c_chip} + */ +#if defined(CONFIG_I2C_EDID) +int do_edid(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + uint chip; + struct edid1_info edid; + int ret; +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *dev; +#endif + + if (argc < 2) { + cmd_usage(cmdtp); + return 1; + } + + chip = hextoul(argv[1], NULL); +#if CONFIG_IS_ENABLED(DM_I2C) + ret = i2c_get_cur_bus_chip(chip, &dev); + if (!ret) + ret = dm_i2c_read(dev, 0, (uchar *)&edid, sizeof(edid)); +#else + ret = i2c_read(chip, 0, 1, (uchar *)&edid, sizeof(edid)); +#endif + if (ret) + return i2c_report_err(ret, I2C_ERR_READ); + + if (edid_check_info(&edid)) { + puts("Content isn't valid EDID.\n"); + return 1; + } + + edid_print_info(&edid); + return 0; + +} +#endif /* CONFIG_I2C_EDID */ + +#if CONFIG_IS_ENABLED(DM_I2C) +static void show_bus(struct udevice *bus) +{ + struct udevice *dev; + + printf("Bus %d:\t%s", dev_seq(bus), bus->name); + if (device_active(bus)) + printf(" (active %d)", dev_seq(bus)); + printf("\n"); + for (device_find_first_child(bus, &dev); + dev; + device_find_next_child(&dev)) { + struct dm_i2c_chip *chip = dev_get_parent_plat(dev); + + printf(" %02x: %s, offset len %x, flags %x\n", + chip->chip_addr, dev->name, chip->offset_len, + chip->flags); + } +} +#endif + +/** + * do_i2c_show_bus() - Handle the "i2c bus" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero always. + */ +#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY) || CONFIG_IS_ENABLED(DM_I2C) +static int do_i2c_show_bus(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc == 1) { + /* show all busses */ +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *bus; + struct uclass *uc; + int ret; + + ret = uclass_get(UCLASS_I2C, &uc); + if (ret) + return CMD_RET_FAILURE; + uclass_foreach_dev(bus, uc) + show_bus(bus); +#else + int i; + + for (i = 0; i < CFG_SYS_NUM_I2C_BUSES; i++) { + printf("Bus %d:\t%s", i, I2C_ADAP_NR(i)->name); + printf("\n"); + } +#endif + } else { + int i; + + /* show specific bus */ + i = dectoul(argv[1], NULL); +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *bus; + int ret; + + ret = uclass_get_device_by_seq(UCLASS_I2C, i, &bus); + if (ret) { + printf("Invalid bus %d: err=%d\n", i, ret); + return CMD_RET_FAILURE; + } + show_bus(bus); +#else + if (i >= CFG_SYS_NUM_I2C_BUSES) { + printf("Invalid bus %d\n", i); + return -1; + } + printf("Bus %d:\t%s", i, I2C_ADAP_NR(i)->name); + printf("\n"); +#endif + } + + return 0; +} +#endif + +/** + * do_i2c_bus_num() - Handle the "i2c dev" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY) || CONFIG_IS_ENABLED(DM_I2C) +static int do_i2c_bus_num(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret = 0; + int bus_no; + + if (argc == 1) { + /* querying current setting */ +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *bus; + + if (!i2c_get_cur_bus(&bus)) + bus_no = dev_seq(bus); + else + bus_no = -1; +#else + bus_no = i2c_get_bus_num(); +#endif + printf("Current bus is %d\n", bus_no); + } else { + bus_no = dectoul(argv[1], NULL); +#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY) + if (bus_no >= CFG_SYS_NUM_I2C_BUSES) { + printf("Invalid bus %d\n", bus_no); + return -1; + } +#endif + printf("Setting bus to %d\n", bus_no); +#if CONFIG_IS_ENABLED(DM_I2C) + ret = cmd_i2c_set_bus_num(bus_no); +#else + ret = i2c_set_bus_num(bus_no); +#endif + if (ret) + printf("Failure changing bus number (%d)\n", ret); + } + + return ret ? CMD_RET_FAILURE : 0; +} +#endif /* CONFIG_IS_ENABLED(SYS_I2C_LEGACY) */ + +/** + * do_i2c_bus_speed() - Handle the "i2c speed" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +static int do_i2c_bus_speed(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int speed, ret=0; + +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *bus; + + if (i2c_get_cur_bus(&bus)) + return 1; +#endif + if (argc == 1) { +#if CONFIG_IS_ENABLED(DM_I2C) + speed = dm_i2c_get_bus_speed(bus); +#else + speed = i2c_get_bus_speed(); +#endif + /* querying current speed */ + printf("Current bus speed=%d\n", speed); + } else { + speed = dectoul(argv[1], NULL); + printf("Setting bus speed to %d Hz\n", speed); +#if CONFIG_IS_ENABLED(DM_I2C) + ret = dm_i2c_set_bus_speed(bus, speed); +#else + ret = i2c_set_bus_speed(speed); +#endif + if (ret) + printf("Failure changing bus speed (%d)\n", ret); + } + + return ret ? CMD_RET_FAILURE : 0; +} + +/** + * do_i2c_mm() - Handle the "i2c mm" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +static int do_i2c_mm(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return mod_i2c_mem (cmdtp, 1, flag, argc, argv); +} + +/** + * do_i2c_nm() - Handle the "i2c nm" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +static int do_i2c_nm(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return mod_i2c_mem (cmdtp, 0, flag, argc, argv); +} + +/** + * do_i2c_reset() - Handle the "i2c reset" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero always. + */ +static int do_i2c_reset(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ +#if CONFIG_IS_ENABLED(DM_I2C) + struct udevice *bus; + + if (i2c_get_cur_bus(&bus)) + return CMD_RET_FAILURE; + if (i2c_deblock(bus)) { + printf("Error: Not supported by the driver\n"); + return CMD_RET_FAILURE; + } +#elif CONFIG_IS_ENABLED(SYS_I2C_LEGACY) + i2c_init(I2C_ADAP->speed, I2C_ADAP->slaveaddr); +#endif + return 0; +} + +static struct cmd_tbl cmd_i2c_sub[] = { +#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY) || CONFIG_IS_ENABLED(DM_I2C) + U_BOOT_CMD_MKENT(bus, 1, 1, do_i2c_show_bus, "", ""), +#endif + U_BOOT_CMD_MKENT(crc32, 3, 1, do_i2c_crc, "", ""), +#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY) || CONFIG_IS_ENABLED(DM_I2C) + U_BOOT_CMD_MKENT(dev, 1, 1, do_i2c_bus_num, "", ""), +#endif +#if defined(CONFIG_I2C_EDID) + U_BOOT_CMD_MKENT(edid, 1, 1, do_edid, "", ""), +#endif /* CONFIG_I2C_EDID */ + U_BOOT_CMD_MKENT(loop, 3, 1, do_i2c_loop, "", ""), + U_BOOT_CMD_MKENT(md, 3, 1, do_i2c_md, "", ""), + U_BOOT_CMD_MKENT(mm, 2, 1, do_i2c_mm, "", ""), + U_BOOT_CMD_MKENT(mw, 3, 1, do_i2c_mw, "", ""), + U_BOOT_CMD_MKENT(nm, 2, 1, do_i2c_nm, "", ""), + U_BOOT_CMD_MKENT(probe, 0, 1, do_i2c_probe, "", ""), + U_BOOT_CMD_MKENT(read, 5, 1, do_i2c_read, "", ""), + U_BOOT_CMD_MKENT(write, 6, 0, do_i2c_write, "", ""), +#if CONFIG_IS_ENABLED(DM_I2C) + U_BOOT_CMD_MKENT(flags, 2, 1, do_i2c_flags, "", ""), + U_BOOT_CMD_MKENT(olen, 2, 1, do_i2c_olen, "", ""), +#endif + U_BOOT_CMD_MKENT(reset, 0, 1, do_i2c_reset, "", ""), +#if defined(CONFIG_CMD_SDRAM) + U_BOOT_CMD_MKENT(sdram, 1, 1, do_sdram, "", ""), +#endif + U_BOOT_CMD_MKENT(speed, 1, 1, do_i2c_bus_speed, "", ""), +}; + +/** + * do_i2c() - Handle the "i2c" command-line command + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Returns zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +static int do_i2c(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *c; + + if (argc < 2) + return CMD_RET_USAGE; + + /* Strip off leading 'i2c' command argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], &cmd_i2c_sub[0], ARRAY_SIZE(cmd_i2c_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +/***************************************************/ +U_BOOT_LONGHELP(i2c, +#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY) || CONFIG_IS_ENABLED(DM_I2C) + "bus [muxtype:muxaddr:muxchannel] - show I2C bus info\n" + "i2c " /* That's the prefix for the crc32 command below. */ +#endif + "crc32 chip address[.0, .1, .2] count - compute CRC32 checksum\n" +#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY) || CONFIG_IS_ENABLED(DM_I2C) + "i2c dev [dev] - show or set current I2C bus\n" +#endif +#if defined(CONFIG_I2C_EDID) + "i2c edid chip - print EDID configuration information\n" +#endif /* CONFIG_I2C_EDID */ + "i2c loop chip address[.0, .1, .2] [# of objects] - looping read of device\n" + "i2c md chip address[.0, .1, .2] [# of objects] - read from I2C device\n" + "i2c mm chip address[.0, .1, .2] - write to I2C device (auto-incrementing)\n" + "i2c mw chip address[.0, .1, .2] value [count] - write to I2C device (fill)\n" + "i2c nm chip address[.0, .1, .2] - write to I2C device (constant address)\n" + "i2c probe [address] - test for and show device(s) on the I2C bus\n" + "i2c read chip address[.0, .1, .2] length memaddress - read to memory\n" + "i2c write memaddress chip address[.0, .1, .2] length [-s] - write memory\n" + " to I2C; the -s option selects bulk write in a single transaction\n" +#if CONFIG_IS_ENABLED(DM_I2C) + "i2c flags chip [flags] - set or get chip flags\n" + "i2c olen chip [offset_length] - set or get chip offset length\n" +#endif + "i2c reset - re-init the I2C Controller\n" +#if defined(CONFIG_CMD_SDRAM) + "i2c sdram chip - print SDRAM configuration information\n" +#endif + "i2c speed [speed] - show or set I2C bus speed"); + +U_BOOT_CMD( + i2c, 7, 1, do_i2c, + "I2C sub-system", + i2c_help_text +); diff --git a/cmd/i3c.c b/cmd/i3c.c new file mode 100644 index 00000000000..08957f4d447 --- /dev/null +++ b/cmd/i3c.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Altera Corporation <www.altera.com> + */ + +#include <bootretry.h> +#include <cli.h> +#include <command.h> +#include <console.h> +#include <dm.h> +#include <dw-i3c.h> +#include <edid.h> +#include <errno.h> +#include <hexdump.h> +#include <log.h> +#include <malloc.h> +#include <asm/byteorder.h> +#include <linux/compiler.h> +#include <linux/delay.h> +#include <u-boot/crc.h> +#include <linux/i3c/master.h> +#include <linux/printk.h> +#include <linux/types.h> + +static struct udevice *currdev; +static struct udevice *prevdev; +static struct dw_i3c_master *master; + +static void low_to_high_bytes(void *data, size_t size) +{ + u8 *byte_data = data; + size_t start = 0; + size_t end = size - 1; + + while (start < end) { + u8 temp = byte_data[start]; + + byte_data[start] = byte_data[end]; + byte_data[end] = temp; + start++; + end--; + } +} + +static int handle_i3c_select(const char *name) +{ + struct uclass *uc; + struct udevice *dev_list; + int ret = uclass_get_device_by_name(UCLASS_I3C, name, &currdev); + + if (ret) { + currdev = prevdev; + if (!currdev) { + ret = uclass_get(UCLASS_I3C, &uc); + if (ret) + return CMD_RET_FAILURE; + + uclass_foreach_dev(dev_list, uc) + printf("%s (%s)\n", dev_list->name, dev_list->driver->name); + + printf("i3c: Host controller not initialized: %s\n", name); + return CMD_RET_FAILURE; + } + } else { + master = dev_get_priv(currdev); + printf("i3c: Current controller: %s\n", currdev->name); + prevdev = currdev; + } + + return CMD_RET_SUCCESS; +} + +static int handle_i3c_list(void) +{ + struct uclass *uc; + struct udevice *dev_list; + int ret = uclass_get(UCLASS_I3C, &uc); + + if (ret) + return CMD_RET_FAILURE; + + uclass_foreach_dev(dev_list, uc) + printf("%s (%s)\n", dev_list->name, dev_list->driver->name); + + return CMD_RET_SUCCESS; +} + +static int handle_i3c_current(void) +{ + if (!currdev) + printf("i3c: No current controller selected\n"); + else + printf("i3c: Current controller: %s\n", currdev->name); + + return CMD_RET_SUCCESS; +} + +static int handle_i3c_device_list(void) +{ + if (!master) { + printf("i3c: No controller active\n"); + return CMD_RET_FAILURE; + } + + for (int i = 0; i < master->num_i3cdevs; i++) { + struct i3c_device_info *info = &master->i3cdev[i]->info; + + printf("Device %d:\n", i); + printf(" Static Address : 0x%02X\n", info->static_addr); + printf(" Dynamic Address : 0x%X\n", info->dyn_addr); + printf(" PID : %016llx\n", info->pid); + printf(" BCR : 0x%X\n", info->bcr); + printf(" DCR : 0x%X\n", info->dcr); + printf(" Max Read DS : 0x%X\n", info->max_read_ds); + printf(" Max Write DS : 0x%X\n", info->max_write_ds); + printf("\n"); + } + + return CMD_RET_SUCCESS; +} + +static int handle_i3c_write(int argc, char *const argv[]) +{ + u32 mem_addr, num_bytes, dev_num_val; + u8 device_num; + u8 *data; + int ret; + + if (argc < 5) + return CMD_RET_USAGE; + + if (!currdev) { + printf("i3c: No I3C controller selected\n"); + return CMD_RET_FAILURE; + } + + mem_addr = hextoul(argv[2], NULL); + num_bytes = hextoul(argv[3], NULL); + dev_num_val = hextoul(argv[4], NULL); + + if (num_bytes == 0 || num_bytes > 4) { + printf("i3c: Length must be between 1 and 4\n"); + return CMD_RET_USAGE; + } + + if (dev_num_val > 0xFF) { + printf("i3c: Device number 0x%x exceeds valid u8 range\n", dev_num_val); + return CMD_RET_USAGE; + } + + device_num = dev_num_val; + data = malloc(num_bytes); + + if (!data) { + printf("i3c: Memory allocation failed\n"); + return -ENOMEM; + } + + memcpy(data, (void *)(uintptr_t)mem_addr, num_bytes); + low_to_high_bytes(data, num_bytes); + + ret = dm_i3c_write(currdev, device_num, data, num_bytes); + + if (ret) + printf("i3c: Write failed: %d\n", ret); + + free(data); + return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static int handle_i3c_read(int argc, char *const argv[]) +{ + u32 mem_addr, read_len, dev_num_val; + u8 device_num; + u8 *rdata; + int ret; + + if (argc < 5) + return CMD_RET_USAGE; + + if (!currdev) { + printf("i3c: No I3C controller selected\n"); + return CMD_RET_FAILURE; + } + + mem_addr = hextoul(argv[2], NULL); + read_len = hextoul(argv[3], NULL); + dev_num_val = hextoul(argv[4], NULL); + + if (read_len == 0) { + printf("i3c: Read length must be greater than 0\n"); + return CMD_RET_USAGE; + } + + if (dev_num_val > 0xFF) { + printf("i3c: Device number 0x%x exceeds valid u8 range\n", dev_num_val); + return CMD_RET_USAGE; + } + + device_num = dev_num_val; + rdata = malloc(read_len); + + if (!rdata) { + printf("i3c: Memory allocation failed\n"); + return -ENOMEM; + } + + ret = dm_i3c_read(currdev, device_num, rdata, read_len); + + if (ret) { + printf("i3c: Read failed: %d\n", ret); + free(rdata); + return CMD_RET_FAILURE; + } + + memcpy((void *)(uintptr_t)mem_addr, rdata, read_len); + print_hex_dump("i3c read: ", DUMP_PREFIX_OFFSET, 16, 1, + (void *)(uintptr_t)mem_addr, read_len, false); + + free(rdata); + return CMD_RET_SUCCESS; +} + +static bool is_i3c_subcommand(const char *cmd) +{ + return !strcmp(cmd, "write") || + !strcmp(cmd, "read") || + !strcmp(cmd, "device_list") || + !strcmp(cmd, "list") || + !strcmp(cmd, "current"); +} + +static int do_i3c(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + if (argc < 2) + return CMD_RET_USAGE; + + const char *subcmd = argv[1]; + + if (!is_i3c_subcommand(subcmd)) + return handle_i3c_select(subcmd); + + if (!currdev) { + printf("i3c: No I3C controller selected\n"); + return CMD_RET_FAILURE; + } + + if (!strcmp(subcmd, "list")) + return handle_i3c_list(); + else if (!strcmp(subcmd, "current")) + return handle_i3c_current(); + else if (!strcmp(subcmd, "device_list")) + return handle_i3c_device_list(); + else if (!strcmp(subcmd, "write")) + return handle_i3c_write(argc, argv); + else if (!strcmp(subcmd, "read")) + return handle_i3c_read(argc, argv); + + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + i3c, 5, 1, do_i3c, + "access the system i3c", + "i3c write <mem_addr> <length> <device_number> - write from memory to device\n" + "i3c read <mem_addr> <length> <device_number> - read from device to memory\n" + "i3c device_list - List valid target devices\n" + "i3c <host_controller> - Select i3c controller\n" + "i3c list - List all available i3c controllers\n" + "i3c current - Show current i3c controller" +); diff --git a/cmd/ide.c b/cmd/ide.c new file mode 100644 index 00000000000..ed30f946866 --- /dev/null +++ b/cmd/ide.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2011 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * IDE support + */ + +#include <blk.h> +#include <dm.h> +#include <config.h> +#include <watchdog.h> +#include <command.h> +#include <image.h> +#include <asm/byteorder.h> +#include <asm/io.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> + +#include <ata.h> + +#ifdef CONFIG_LED_STATUS +# include <status_led.h> +#endif + +/* Current I/O Device */ +static int curr_device; + +int do_ide(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + if (argc == 2) { + if (strncmp(argv[1], "res", 3) == 0) { + struct udevice *dev; + int ret; + + puts("\nReset IDE: "); + ret = uclass_find_first_device(UCLASS_IDE, &dev); + ret = device_remove(dev, DM_REMOVE_NORMAL); + if (!ret) + ret = device_chld_unbind(dev, NULL); + if (ret) { + printf("Cannot remove IDE (err=%dE)\n", ret); + return CMD_RET_FAILURE; + } + + ret = uclass_first_device_err(UCLASS_IDE, &dev); + if (ret) { + printf("Init failed (err=%dE)\n", ret); + return CMD_RET_FAILURE; + } + + return 0; + } + } + + return blk_common_cmd(argc, argv, UCLASS_IDE, &curr_device); +} + +int do_diskboot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return common_diskboot(cmdtp, "ide", argc, argv); +} + +U_BOOT_CMD(ide, 5, 1, do_ide, + "IDE sub-system", + "reset - reset IDE controller\n" + "ide info - show available IDE devices\n" + "ide device [dev] - show or set current device\n" + "ide part [dev] - print partition table of one or all IDE devices\n" + "ide read addr blk# cnt\n" + "ide write addr blk# cnt - read/write `cnt'" + " blocks starting at block `blk#'\n" + " to/from memory address `addr'"); + +U_BOOT_CMD(diskboot, 3, 1, do_diskboot, + "boot from IDE device", "loadAddr dev:part"); diff --git a/cmd/ini.c b/cmd/ini.c new file mode 100644 index 00000000000..96399017691 --- /dev/null +++ b/cmd/ini.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * inih -- simple .INI file parser + * + * Copyright (c) 2009, Brush Technology + * Copyright (c) 2012: + * Joe Hershberger, National Instruments, joe.hershberger@ni.com + * All rights reserved. + * + * Go to the project home page for more info: + * http://code.google.com/p/inih/ + */ + +#include <command.h> +#include <env.h> +#include <vsprintf.h> +#include <linux/ctype.h> +#include <linux/string.h> + +#ifdef CONFIG_INI_MAX_LINE +#define MAX_LINE CONFIG_INI_MAX_LINE +#else +#define MAX_LINE 200 +#endif + +#ifdef CONFIG_INI_MAX_SECTION +#define MAX_SECTION CONFIG_INI_MAX_SECTION +#else +#define MAX_SECTION 50 +#endif + +#ifdef CONFIG_INI_MAX_NAME +#define MAX_NAME CONFIG_INI_MAX_NAME +#else +#define MAX_NAME 50 +#endif + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char *rstrip(char *s) +{ + char *p = s + strlen(s); + + while (p > s && isspace(*--p)) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char *lskip(const char *s) +{ + while (*s && isspace(*s)) + s++; + return (char *)s; +} + +/* Return pointer to first char c or ';' comment in given string, or pointer to + null at end of string if neither found. ';' must be prefixed by a whitespace + character to register as a comment. */ +static char *find_char_or_comment(const char *s, char c) +{ + int was_whitespace = 0; + + while (*s && *s != c && !(was_whitespace && *s == ';')) { + was_whitespace = isspace(*s); + s++; + } + return (char *)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char *strncpy0(char *dest, const char *src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* Emulate the behavior of fgets but on memory */ +static char *memgets(char *str, int num, char **mem, size_t *memsize) +{ + char *end; + int len; + int newline = 1; + + end = memchr(*mem, '\n', *memsize); + if (end == NULL) { + if (*memsize == 0) + return NULL; + end = *mem + *memsize; + newline = 0; + } + len = min((int)(end - *mem) + newline, num); + memcpy(str, *mem, len); + if (len < num) + str[len] = '\0'; + + /* prepare the mem vars for the next call */ + *memsize -= (end - *mem) + newline; + *mem += (end - *mem) + newline; + + return str; +} + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's ConfigParser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error). +*/ +static int ini_parse(char *filestart, size_t filelen, + int (*handler)(void *, char *, char *, char *), void *user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ + char line[MAX_LINE]; + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char *curmem = filestart; + char *start; + char *end; + char *name; + char *value; + size_t memleft = filelen; + int lineno = 0; + int error = 0; + + /* Scan through file line by line */ + while (memgets(line, sizeof(line), &curmem, &memleft) != NULL) { + lineno++; + start = lskip(rstrip(line)); + + if (*start == ';' || *start == '#') { + /* + * Per Python ConfigParser, allow '#' comments at start + * of line + */ + } +#if CONFIG_INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* + * Non-blank line with leading whitespace, treat as + * continuation of previous name's value (as per Python + * ConfigParser). + */ + if (!handler(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_char_or_comment(start + 1, ']'); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } else if (*start && *start != ';') { + /* Not a comment, must be a name[=:]value pair */ + end = find_char_or_comment(start, '='); + if (*end != '=') + end = find_char_or_comment(start, ':'); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); + end = find_char_or_comment(value, '\0'); + if (*end == ';') + *end = '\0'; + rstrip(value); + /* Strip double-quotes */ + if (value[0] == '"' && + value[strlen(value)-1] == '"') { + value[strlen(value)-1] = '\0'; + value += 1; + } + + /* + * Valid name[=:]value pair found, call handler + */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!handler(user, section, name, value) && + !error) + error = lineno; + } else if (!error) + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + + return error; +} + +static int ini_handler(void *user, char *section, char *name, char *value) +{ + char *requested_section = (char *)user; +#ifdef CONFIG_INI_CASE_INSENSITIVE + int i; + + for (i = 0; i < strlen(requested_section); i++) + requested_section[i] = tolower(requested_section[i]); + for (i = 0; i < strlen(section); i++) + section[i] = tolower(section[i]); +#endif + + if (!strcmp(section, requested_section)) { +#ifdef CONFIG_INI_CASE_INSENSITIVE + for (i = 0; i < strlen(name); i++) + name[i] = tolower(name[i]); + for (i = 0; i < strlen(value); i++) + value[i] = tolower(value[i]); +#endif + env_set(name, value); + printf("ini: Imported %s as %s\n", name, value); + } + + /* success */ + return 1; +} + +static int do_ini(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + const char *section; + char *file_address; + size_t file_size; + + if (argc == 1) + return CMD_RET_USAGE; + + section = argv[1]; + file_address = (char *)hextoul(argc < 3 ? env_get("loadaddr") : argv[2], + NULL); + file_size = (size_t)hextoul(argc < 4 ? env_get("filesize") : argv[3], + NULL); + + return ini_parse(file_address, file_size, ini_handler, (void *)section); +} + +U_BOOT_CMD( + ini, 4, 0, do_ini, + "parse an ini file in memory and merge the specified section into the env", + "section [[file-address] file-size]" +); diff --git a/cmd/io.c b/cmd/io.c new file mode 100644 index 00000000000..617373d3cb7 --- /dev/null +++ b/cmd/io.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2012 The Chromium OS Authors. + */ + +/* + * IO space access commands. + */ + +#include <command.h> +#include <display_options.h> +#include <vsprintf.h> +#include <asm/io.h> + +/* Display values from last command */ +static ulong last_addr, last_size; +static ulong last_length = 0x40; +static ulong base_address; + +#define DISP_LINE_LEN 16 + +/* + * IO Display + * + * Syntax: + * iod{.b, .w, .l} {addr} + */ +int do_io_iod(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + ulong addr, length, bytes; + u8 buf[DISP_LINE_LEN]; + int size, todo; + + /* + * We use the last specified parameters, unless new ones are + * entered. + */ + addr = last_addr; + size = last_size; + length = last_length; + + if (argc < 2) + return CMD_RET_USAGE; + + if ((flag & CMD_FLAG_REPEAT) == 0) { + /* + * New command specified. Check for a size specification. + * Defaults to long if no or incorrect specification. + */ + size = cmd_get_data_size(argv[0], 4); + if (size < 0) + return 1; + + /* Address is specified since argc > 1 */ + addr = hextoul(argv[1], NULL); + addr += base_address; + + /* + * If another parameter, it is the length to display. + * Length is the number of objects, not number of bytes. + */ + if (argc > 2) + length = hextoul(argv[2], NULL); + } + + bytes = size * length; + + /* Print the lines */ + for (; bytes > 0; addr += todo) { + u8 *ptr = buf; + int i; + + todo = min(bytes, (ulong)DISP_LINE_LEN); + for (i = 0; i < todo; i += size, ptr += size) { + if (size == 4) + *(u32 *)ptr = inl(addr + i); + else if (size == 2) + *(u16 *)ptr = inw(addr + i); + else + *ptr = inb(addr + i); + } + print_buffer(addr, buf, size, todo / size, + DISP_LINE_LEN / size); + bytes -= todo; + } + + last_addr = addr; + last_length = length; + last_size = size; + + return 0; +} + +int do_io_iow(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + ulong addr, val; + int size; + + if (argc != 3) + return CMD_RET_USAGE; + + size = cmd_get_data_size(argv[0], 4); + if (size < 0) + return 1; + + addr = hextoul(argv[1], NULL); + val = hextoul(argv[2], NULL); + + if (size == 4) + outl((u32) val, addr); + else if (size == 2) + outw((u16) val, addr); + else + outb((u8) val, addr); + + return 0; +} + +/**************************************************/ +U_BOOT_CMD(iod, 3, 1, do_io_iod, + "IO space display", "[.b, .w, .l] address"); + +U_BOOT_CMD(iow, 3, 0, do_io_iow, + "IO space modify", + "[.b, .w, .l] address value"); diff --git a/cmd/iotrace.c b/cmd/iotrace.c new file mode 100644 index 00000000000..0a041ed8652 --- /dev/null +++ b/cmd/iotrace.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2014 Google, Inc + */ + +#include <command.h> +#include <iotrace.h> +#include <vsprintf.h> + +static void do_print_stats(void) +{ + ulong start, size, needed_size, offset, count; + + printf("iotrace is %sabled\n", iotrace_get_enabled() ? "en" : "dis"); + iotrace_get_buffer(&start, &size, &needed_size, &offset, &count); + printf("Start: %08lx\n", start); + printf("Actual Size: %08lx\n", size); + printf("Needed Size: %08lx\n", needed_size); + iotrace_get_region(&start, &size); + printf("Region: %08lx\n", start); + printf("Size: %08lx\n", size); + printf("Offset: %08lx\n", offset); + printf("Output: %08lx\n", start + offset); + printf("Count: %08lx\n", count); + printf("CRC32: %08lx\n", (ulong)iotrace_get_checksum()); +} + +static void do_print_trace(void) +{ + ulong start, size, needed_size, offset, count; + + struct iotrace_record *cur_record; + + iotrace_get_buffer(&start, &size, &needed_size, &offset, &count); + + if (!start || !size || !count) + return; + + printf("Timestamp Value Address\n"); + + cur_record = (struct iotrace_record *)start; + for (int i = 0; i < count; i++) { + if (cur_record->flags & IOT_WRITE) + printf("%08llu: 0x%08lx --> 0x%08llx\n", + cur_record->timestamp, + cur_record->value, + (unsigned long long)cur_record->addr); + else + printf("%08llu: 0x%08lx <-- 0x%08llx\n", + cur_record->timestamp, + cur_record->value, + (unsigned long long)cur_record->addr); + + cur_record++; + } +} + +static int do_set_buffer(int argc, char *const argv[]) +{ + ulong addr = 0, size = 0; + + if (argc == 2) { + addr = hextoul(*argv++, NULL); + size = hextoul(*argv++, NULL); + } else if (argc != 0) { + return CMD_RET_USAGE; + } + + iotrace_set_buffer(addr, size); + + return 0; +} + +static int do_set_region(int argc, char *const argv[]) +{ + ulong addr = 0, size = 0; + + if (argc == 2) { + addr = hextoul(*argv++, NULL); + size = hextoul(*argv++, NULL); + } else if (argc != 0) { + return CMD_RET_USAGE; + } + + iotrace_set_region(addr, size); + + return 0; +} + +int do_iotrace(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + const char *cmd = argc < 2 ? NULL : argv[1]; + + if (!cmd) + return cmd_usage(cmdtp); + switch (*cmd) { + case 'b': + return do_set_buffer(argc - 2, argv + 2); + case 'l': + return do_set_region(argc - 2, argv + 2); + case 'p': + iotrace_set_enabled(0); + break; + case 'r': + iotrace_set_enabled(1); + break; + case 's': + do_print_stats(); + break; + case 'd': + do_print_trace(); + break; + default: + return CMD_RET_USAGE; + } + + return 0; +} + +U_BOOT_CMD( + iotrace, 4, 1, do_iotrace, + "iotrace utility commands", + "stats - display iotrace stats\n" + "iotrace buffer <address> <size> - set iotrace buffer\n" + "iotrace limit <address> <size> - set iotrace region limit\n" + "iotrace pause - pause tracing\n" + "iotrace resume - resume tracing\n" + "iotrace dump - dump iotrace buffer" +); diff --git a/cmd/irq.c b/cmd/irq.c new file mode 100644 index 00000000000..58483d04de8 --- /dev/null +++ b/cmd/irq.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + */ + +#include <config.h> +#include <command.h> +#include <irq_func.h> +#include <linux/string.h> + +static int do_interrupts(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + + if (argc != 2) + return CMD_RET_USAGE; + + /* on */ + if (strncmp(argv[1], "on", 2) == 0) + enable_interrupts(); + else + disable_interrupts(); + + return 0; +} + +U_BOOT_CMD( + interrupts, 5, 0, do_interrupts, + "enable or disable interrupts", + "[on, off]" +); + +U_BOOT_CMD( + irqinfo, 1, 1, do_irqinfo, + "print information about IRQs", + "" +); diff --git a/cmd/itest.c b/cmd/itest.c new file mode 100644 index 00000000000..b79512a505d --- /dev/null +++ b/cmd/itest.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2003 + * Tait Electronics Limited, Christchurch, New Zealand + */ + +/* + * This file provides a shell like 'test' function to return + * true/false from an integer or string compare of two memory + * locations or a location and a scalar/literal. + * A few parts were lifted from bash 'test' command + */ + +#include <config.h> +#include <command.h> +#include <env.h> +#include <mapmem.h> +#include <vsprintf.h> + +#include <asm/io.h> + +#define EQ 0 +#define NE 1 +#define LT 2 +#define GT 3 +#define LE 4 +#define GE 5 + +struct op_tbl_s { + char *op; /* operator string */ + int opcode; /* internal representation of opcode */ +}; + +typedef struct op_tbl_s op_tbl_t; + +static const op_tbl_t op_table [] = { + { "-lt", LT }, + { "<" , LT }, + { "-gt", GT }, + { ">" , GT }, + { "-eq", EQ }, + { "==" , EQ }, + { "-ne", NE }, + { "!=" , NE }, + { "<>" , NE }, + { "-ge", GE }, + { ">=" , GE }, + { "-le", LE }, + { "<=" , LE }, +}; + +static long evalexp(char *s, int w) +{ + long l = 0; + unsigned long addr; + void *buf; + + /* if the parameter starts with a * then assume is a pointer to the value we want */ + if (s[0] == '*') { + addr = hextoul(&s[1], NULL); + buf = map_physmem(addr, w, MAP_WRBACK); + if (!buf && addr) { + puts("Failed to map physical memory\n"); + return 0; + } + switch (w) { + case 1: + l = (long)(*(u8 *)buf); + break; + case 2: + l = (long)(*(u16 *)buf); + break; + case 4: + l = (long)(*(u32 *)buf); + break; +#ifdef CONFIG_PHYS_64BIT + case 8: + l = (long)(*(unsigned long *)buf); + break; +#endif + } + unmap_physmem(buf, w); + return l; + } else { + l = hextoul(s, NULL); + } + + /* avoid overflow on mask calculus */ + return (w >= sizeof(long)) ? l : (l & ((1UL << (w * 8)) - 1)); +} + +static char * evalstr(char *s) +{ + /* if the parameter starts with a * then assume a string pointer else its a literal */ + if (s[0] == '*') { + return (char *)hextoul(&s[1], NULL); + } else if (s[0] == '$') { + int i = 2; + + if (s[1] != '{') + return NULL; + + while (s[i] != '}') { + if (s[i] == 0) + return NULL; + i++; + } + s[i] = 0; + return env_get((const char *)&s[2]); + } else { + return s; + } +} + +static int stringcomp(char *s, char *t, int op) +{ + int p; + char *l, *r; + + l = evalstr(s); + r = evalstr(t); + + p = strcmp(l, r); + switch (op) { + case EQ: return (p == 0); + case NE: return (p != 0); + case LT: return (p < 0); + case GT: return (p > 0); + case LE: return (p <= 0); + case GE: return (p >= 0); + } + return (0); +} + +static int arithcomp (char *s, char *t, int op, int w) +{ + long l, r; + + l = evalexp (s, w); + r = evalexp (t, w); + + switch (op) { + case EQ: return (l == r); + case NE: return (l != r); + case LT: return (l < r); + case GT: return (l > r); + case LE: return (l <= r); + case GE: return (l >= r); + } + return (0); +} + +static int binary_test(char *op, char *arg1, char *arg2, int w) +{ + int len, i; + const op_tbl_t *optp; + + len = strlen(op); + + for (optp = (op_tbl_t *)&op_table, i = 0; + i < ARRAY_SIZE(op_table); + optp++, i++) { + + if ((strncmp (op, optp->op, len) == 0) && (len == strlen (optp->op))) { + if (w == 0) { + return (stringcomp(arg1, arg2, optp->opcode)); + } else { + return (arithcomp (arg1, arg2, optp->opcode, w)); + } + } + } + + printf("Unknown operator '%s'\n", op); + return 0; /* op code not found */ +} + +/* command line interface to the shell test */ +static int do_itest(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int value, w; + + /* Validate arguments */ + if ((argc != 4)) + return CMD_RET_USAGE; + + /* Check for a data width specification. + * Defaults to long (4) if no specification. + * Uses -2 as 'width' for .s (string) so as not to upset existing code + */ + switch (w = cmd_get_data_size(argv[0], 4)) { + case 1: + case 2: + case 4: +#ifdef CONFIG_PHYS_64BIT + case 8: +#endif + value = binary_test (argv[2], argv[1], argv[3], w); + break; + case CMD_DATA_SIZE_STR: + value = binary_test (argv[2], argv[1], argv[3], 0); + break; + case CMD_DATA_SIZE_ERR: + default: + puts("Invalid data width specifier\n"); + value = 0; + break; + } + + return !value; +} + +U_BOOT_CMD( + itest, 4, 0, do_itest, + "return true/false on integer compare", +#ifdef CONFIG_PHYS_64BIT + "[.b, .w, .l, .q, .s] [*]value1 <op> [*]value2" +#else + "[.b, .w, .l, .s] [*]value1 <op> [*]value2" +#endif +); diff --git a/cmd/jffs2.c b/cmd/jffs2.c new file mode 100644 index 00000000000..89d336f5958 --- /dev/null +++ b/cmd/jffs2.c @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2002 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * (C) Copyright 2002 + * Robert Schwebel, Pengutronix, <r.schwebel@pengutronix.de> + * + * (C) Copyright 2003 + * Kai-Uwe Bloem, Auerswald GmbH & Co KG, <linux-development@auerswald.de> + * + * (C) Copyright 2005 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * Added support for reading flash partition table from environment. + * Parsing routines are based on driver/mtd/cmdline.c from the linux 2.4 + * kernel tree. + * + * $Id: cmdlinepart.c,v 1.17 2004/11/26 11:18:47 lavinen Exp $ + * Copyright 2002 SYSGO Real-Time Solutions GmbH + */ + +/* + * Three environment variables are used by the parsing routines: + * + * 'partition' - keeps current partition identifier + * + * partition := <part-id> + * <part-id> := <dev-id>,part_num + * + * + * 'mtdids' - linux kernel mtd device id <-> u-boot device id mapping + * + * mtdids=<idmap>[,<idmap>,...] + * + * <idmap> := <dev-id>=<mtd-id> + * <dev-id> := 'nand'|'nor'|'onenand'<dev-num> + * <dev-num> := mtd device number, 0... + * <mtd-id> := unique device tag used by linux kernel to find mtd device (mtd->name) + * + * + * 'mtdparts' - partition list + * + * mtdparts=mtdparts=<mtd-def>[;<mtd-def>...] + * + * <mtd-def> := <mtd-id>:<part-def>[,<part-def>...] + * <mtd-id> := unique device tag used by linux kernel to find mtd device (mtd->name) + * <part-def> := <size>[@<offset>][<name>][<ro-flag>] + * <size> := standard linux memsize OR '-' to denote all remaining space + * <offset> := partition start offset within the device + * <name> := '(' NAME ')' + * <ro-flag> := when set to 'ro' makes partition read-only (not used, passed to kernel) + * + * Notes: + * - each <mtd-id> used in mtdparts must albo exist in 'mtddis' mapping + * - if the above variables are not set defaults for a given target are used + * + * Examples: + * + * 1 NOR Flash, with 1 single writable partition: + * mtdids=nor0=edb7312-nor + * mtdparts=mtdparts=edb7312-nor:- + * + * 1 NOR Flash with 2 partitions, 1 NAND with one + * mtdids=nor0=edb7312-nor,nand0=edb7312-nand + * mtdparts=mtdparts=edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home) + * + */ + +/* + * JFFS2/CRAMFS support + */ +#include <command.h> +#include <env.h> +#if defined(CONFIG_CMD_FLASH) +#include <flash.h> +#endif +#include <image.h> +#include <malloc.h> +#include <jffs2/jffs2.h> +#include <linux/bug.h> +#include <linux/list.h> +#include <linux/ctype.h> +#include <cramfs/cramfs_fs.h> + +#if defined(CONFIG_CMD_NAND) +#include <linux/mtd/rawnand.h> +#include <nand.h> +#endif + +#if defined(CONFIG_CMD_ONENAND) +#include <linux/mtd/mtd.h> +#include <linux/mtd/onenand.h> +#include <onenand_uboot.h> +#endif + +/* enable/disable debugging messages */ +#define DEBUG_JFFS +#undef DEBUG_JFFS + +#ifdef DEBUG_JFFS +# define DEBUGF(fmt, args...) printf(fmt ,##args) +#else +# define DEBUGF(fmt, args...) +#endif + +/* special size referring to all the remaining space in a partition */ +#define SIZE_REMAINING 0xFFFFFFFF + +/* special offset value, it is used when not provided by user + * + * this value is used temporarily during parsing, later such offests + * are recalculated */ +#define OFFSET_NOT_SPECIFIED 0xFFFFFFFF + +/* minimum partition size */ +#define MIN_PART_SIZE 4096 + +/* this flag needs to be set in part_info struct mask_flags + * field for read-only partitions */ +#define MTD_WRITEABLE_CMD 1 + +/* current active device and partition number */ +#ifdef CONFIG_CMD_MTDPARTS +/* Use the ones declared in cmd_mtdparts.c */ +extern struct mtd_device *current_mtd_dev; +extern u8 current_mtd_partnum; +#else +/* Use local ones */ +struct mtd_device *current_mtd_dev = NULL; +u8 current_mtd_partnum = 0; +#endif + +#if defined(CONFIG_CMD_CRAMFS) +extern int cramfs_check (struct part_info *info); +extern int cramfs_load (char *loadoffset, struct part_info *info, char *filename); +extern int cramfs_ls (struct part_info *info, char *filename); +extern int cramfs_info (struct part_info *info); +#else +/* defining empty macros for function names is ugly but avoids ifdef clutter + * all over the code */ +#define cramfs_check(x) (0) +#define cramfs_load(x,y,z) (-1) +#define cramfs_ls(x,y) (0) +#define cramfs_info(x) (0) +#endif + +#ifndef CONFIG_CMD_MTDPARTS +/** + * Check device number to be within valid range for given device type. + * + * @param dev device to validate + * Return: 0 if device is valid, 1 otherwise + */ +static int mtd_device_validate(u8 type, u8 num, u32 *size) +{ + if (type == MTD_DEV_TYPE_NOR) { +#if defined(CONFIG_CMD_FLASH) + if (num < CONFIG_SYS_MAX_FLASH_BANKS) { + *size = flash_info[num].size; + + return 0; + } + + printf("no such FLASH device: %s%d (valid range 0 ... %d\n", + MTD_DEV_TYPE(type), num, CONFIG_SYS_MAX_FLASH_BANKS - 1); +#else + printf("support for FLASH devices not present\n"); +#endif + } else if (type == MTD_DEV_TYPE_NAND) { +#if defined(CONFIG_JFFS2_NAND) && defined(CONFIG_CMD_NAND) + struct mtd_info *mtd = get_nand_dev_by_index(num); + if (mtd) { + *size = mtd->size; + return 0; + } + + printf("no such NAND device: %s%d (valid range 0 ... %d)\n", + MTD_DEV_TYPE(type), num, CONFIG_SYS_MAX_NAND_DEVICE - 1); +#else + printf("support for NAND devices not present\n"); +#endif + } else if (type == MTD_DEV_TYPE_ONENAND) { +#if defined(CONFIG_CMD_ONENAND) + *size = onenand_mtd.size; + return 0; +#else + printf("support for OneNAND devices not present\n"); +#endif + } else + printf("Unknown defice type %d\n", type); + + return 1; +} + +/** + * Parse device id string <dev-id> := 'nand'|'nor'|'onenand'<dev-num>, + * return device type and number. + * + * @param id string describing device id + * @param ret_id output pointer to next char after parse completes (output) + * @param dev_type parsed device type (output) + * @param dev_num parsed device number (output) + * Return: 0 on success, 1 otherwise + */ +static int mtd_id_parse(const char *id, const char **ret_id, u8 *dev_type, u8 *dev_num) +{ + const char *p = id; + + *dev_type = 0; + if (strncmp(p, "nand", 4) == 0) { + *dev_type = MTD_DEV_TYPE_NAND; + p += 4; + } else if (strncmp(p, "nor", 3) == 0) { + *dev_type = MTD_DEV_TYPE_NOR; + p += 3; + } else if (strncmp(p, "onenand", 7) == 0) { + *dev_type = MTD_DEV_TYPE_ONENAND; + p += 7; + } else { + printf("incorrect device type in %s\n", id); + return 1; + } + + if (!isdigit(*p)) { + printf("incorrect device number in %s\n", id); + return 1; + } + + *dev_num = simple_strtoul(p, (char **)&p, 0); + if (ret_id) + *ret_id = p; + return 0; +} + +/* + * 'Static' version of command line mtdparts_init() routine. Single partition on + * a single device configuration. + */ + +/** + * Calculate sector size. + * + * Return: sector size + */ +static inline u32 get_part_sector_size_nand(struct mtdids *id) +{ +#if defined(CONFIG_JFFS2_NAND) && defined(CONFIG_CMD_NAND) + struct mtd_info *mtd; + + mtd = get_nand_dev_by_index(id->num); + + return mtd->erasesize; +#else + BUG(); + return 0; +#endif +} + +static inline u32 get_part_sector_size_nor(struct mtdids *id, struct part_info *part) +{ +#if defined(CONFIG_CMD_FLASH) + u32 end_phys, start_phys, sector_size = 0, size = 0; + int i; + flash_info_t *flash; + + flash = &flash_info[id->num]; + + start_phys = flash->start[0] + part->offset; + end_phys = start_phys + part->size - 1; + + for (i = 0; i < flash->sector_count; i++) { + if (flash->start[i] >= end_phys) + break; + + if (flash->start[i] >= start_phys) { + if (i == flash->sector_count - 1) { + size = flash->start[0] + flash->size - flash->start[i]; + } else { + size = flash->start[i+1] - flash->start[i]; + } + + if (sector_size < size) + sector_size = size; + } + } + + return sector_size; +#else + BUG(); + return 0; +#endif +} + +static inline u32 get_part_sector_size_onenand(void) +{ +#if defined(CONFIG_CMD_ONENAND) + struct mtd_info *mtd; + + mtd = &onenand_mtd; + + return mtd->erasesize; +#else + BUG(); + return 0; +#endif +} + +static inline u32 get_part_sector_size(struct mtdids *id, struct part_info *part) +{ + if (id->type == MTD_DEV_TYPE_NAND) + return get_part_sector_size_nand(id); + else if (id->type == MTD_DEV_TYPE_NOR) + return get_part_sector_size_nor(id, part); + else if (id->type == MTD_DEV_TYPE_ONENAND) + return get_part_sector_size_onenand(); + else + DEBUGF("Error: Unknown device type.\n"); + + return 0; +} + +/** + * Parse and initialize global mtdids mapping and create global + * device/partition list. + * + * 'Static' version of command line mtdparts_init() routine. Single partition on + * a single device configuration. + * + * Return: 0 on success, 1 otherwise + */ +int mtdparts_init(void) +{ + static int initialized = 0; + u32 size; + char *dev_name; + + DEBUGF("\n---mtdparts_init---\n"); + if (!initialized) { + struct mtdids *id; + struct part_info *part; + + initialized = 1; + current_mtd_dev = (struct mtd_device *) + malloc(sizeof(struct mtd_device) + + sizeof(struct part_info) + + sizeof(struct mtdids)); + if (!current_mtd_dev) { + printf("out of memory\n"); + return 1; + } + memset(current_mtd_dev, 0, sizeof(struct mtd_device) + + sizeof(struct part_info) + sizeof(struct mtdids)); + + id = (struct mtdids *)(current_mtd_dev + 1); + part = (struct part_info *)(id + 1); + + /* id */ + id->mtd_id = "single part"; + + dev_name = CONFIG_JFFS2_DEV; + + if ((mtd_id_parse(dev_name, NULL, &id->type, &id->num) != 0) || + (mtd_device_validate(id->type, id->num, &size) != 0)) { + printf("incorrect device: %s%d\n", MTD_DEV_TYPE(id->type), id->num); + free(current_mtd_dev); + return 1; + } + id->size = size; + INIT_LIST_HEAD(&id->link); + + DEBUGF("dev id: type = %d, num = %d, size = 0x%08lx, mtd_id = %s\n", + id->type, id->num, id->size, id->mtd_id); + + /* partition */ + part->name = "static"; + part->auto_name = 0; + + part->size = CONFIG_JFFS2_PART_SIZE; + + part->offset = CONFIG_JFFS2_PART_OFFSET; + + part->dev = current_mtd_dev; + INIT_LIST_HEAD(&part->link); + + /* recalculate size if needed */ + if (part->size == SIZE_REMAINING) + part->size = id->size - part->offset; + + part->sector_size = get_part_sector_size(id, part); + + DEBUGF("part : name = %s, size = 0x%08lx, offset = 0x%08lx\n", + part->name, part->size, part->offset); + + /* device */ + current_mtd_dev->id = id; + INIT_LIST_HEAD(¤t_mtd_dev->link); + current_mtd_dev->num_parts = 1; + INIT_LIST_HEAD(¤t_mtd_dev->parts); + list_add(&part->link, ¤t_mtd_dev->parts); + } + + return 0; +} +#endif /* #ifndef CONFIG_CMD_MTDPARTS */ + +/** + * Return pointer to the partition of a requested number from a requested + * device. + * + * @param dev device that is to be searched for a partition + * @param part_num requested partition number + * Return: pointer to the part_info, NULL otherwise + */ +static struct part_info* jffs2_part_info(struct mtd_device *dev, unsigned int part_num) +{ + struct list_head *entry; + struct part_info *part; + int num; + + if (!dev) + return NULL; + + DEBUGF("\n--- jffs2_part_info: partition number %d for device %s%d (%s)\n", + part_num, MTD_DEV_TYPE(dev->id->type), + dev->id->num, dev->id->mtd_id); + + if (part_num >= dev->num_parts) { + printf("invalid partition number %d for device %s%d (%s)\n", + part_num, MTD_DEV_TYPE(dev->id->type), + dev->id->num, dev->id->mtd_id); + return NULL; + } + + /* locate partition number, return it */ + num = 0; + list_for_each(entry, &dev->parts) { + part = list_entry(entry, struct part_info, link); + + if (part_num == num++) { + return part; + } + } + + return NULL; +} + +/***************************************************/ +/* U-Boot commands */ +/***************************************************/ + +/** + * Routine implementing fsload u-boot command. This routine tries to load + * a requested file from jffs2/cramfs filesystem on a current partition. + * + * @param cmdtp command internal data + * @param flag command flag + * @param argc number of arguments supplied to the command + * @param argv arguments list + * Return: 0 on success, 1 otherwise + */ +int do_jffs2_fsload(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *fsname; + char *filename; + int size; + struct part_info *part; + ulong offset = image_load_addr; + + /* pre-set Boot file name */ + filename = env_get("bootfile"); + if (!filename) + filename = "uImage"; + + if (argc == 2) { + filename = argv[1]; + } + if (argc == 3) { + offset = hextoul(argv[1], NULL); + image_load_addr = offset; + filename = argv[2]; + } + + /* make sure we are in sync with env variables */ + if (mtdparts_init() !=0) + return 1; + + if ((part = jffs2_part_info(current_mtd_dev, current_mtd_partnum))){ + + /* check partition type for cramfs */ + fsname = (cramfs_check(part) ? "CRAMFS" : "JFFS2"); + printf("### %s loading '%s' to 0x%lx\n", fsname, filename, offset); + + if (cramfs_check(part)) { + size = cramfs_load ((char *) offset, part, filename); + } else { + /* if this is not cramfs assume jffs2 */ + size = jffs2_1pass_load((char *)offset, part, filename); + } + + if (size > 0) { + printf("### %s load complete: %d bytes loaded to 0x%lx\n", + fsname, size, offset); + env_set_hex("filesize", size); + } else { + printf("### %s LOAD ERROR<%x> for %s!\n", fsname, size, filename); + } + + return !(size > 0); + } + return 1; +} + +/** + * Routine implementing u-boot ls command which lists content of a given + * directory on a current partition. + * + * @param cmdtp command internal data + * @param flag command flag + * @param argc number of arguments supplied to the command + * @param argv arguments list + * Return: 0 on success, 1 otherwise + */ +int do_jffs2_ls(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *filename = "/"; + int ret; + struct part_info *part; + + if (argc == 2) + filename = argv[1]; + + /* make sure we are in sync with env variables */ + if (mtdparts_init() !=0) + return 1; + + if ((part = jffs2_part_info(current_mtd_dev, current_mtd_partnum))){ + + /* check partition type for cramfs */ + if (cramfs_check(part)) { + ret = cramfs_ls (part, filename); + } else { + /* if this is not cramfs assume jffs2 */ + ret = jffs2_1pass_ls(part, filename); + } + + return ret ? 0 : 1; + } + return 1; +} + +/** + * Routine implementing u-boot fsinfo command. This routine prints out + * miscellaneous filesystem informations/statistics. + * + * @param cmdtp command internal data + * @param flag command flag + * @param argc number of arguments supplied to the command + * @param argv arguments list + * Return: 0 on success, 1 otherwise + */ +int do_jffs2_fsinfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct part_info *part; + char *fsname; + int ret; + + /* make sure we are in sync with env variables */ + if (mtdparts_init() !=0) + return 1; + + if ((part = jffs2_part_info(current_mtd_dev, current_mtd_partnum))){ + + /* check partition type for cramfs */ + fsname = (cramfs_check(part) ? "CRAMFS" : "JFFS2"); + printf("### filesystem type is %s\n", fsname); + + if (cramfs_check(part)) { + ret = cramfs_info (part); + } else { + /* if this is not cramfs assume jffs2 */ + ret = jffs2_1pass_info(part); + } + + return ret ? 0 : 1; + } + return 1; +} + +/***************************************************/ +U_BOOT_CMD( + fsload, 3, 0, do_jffs2_fsload, + "load binary file from a filesystem image", + "[ off ] [ filename ]\n" + " - load binary file from flash bank\n" + " with offset 'off'" +); +U_BOOT_CMD( + fsls, 2, 1, do_jffs2_ls, + "list files in a directory (default /)", + "[ directory ]" +); + +U_BOOT_CMD( + fsinfo, 1, 1, do_jffs2_fsinfo, + "print information about filesystems", + "" +); +/***************************************************/ diff --git a/cmd/kaslrseed.c b/cmd/kaslrseed.c new file mode 100644 index 00000000000..2ad983a11f9 --- /dev/null +++ b/cmd/kaslrseed.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'kaslrseed' command takes bytes from the hardware random number + * generator and uses them to set the kaslr-seed value in the chosen node. + * + * Copyright (c) 2021, Chris Morgan <macromorgan@hotmail.com> + */ + +#include <command.h> +#include <dm.h> +#include <hexdump.h> +#include <malloc.h> +#include <rng.h> +#include <fdt_support.h> + +static int do_kaslr_seed(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int err = CMD_RET_SUCCESS; + + printf("Notice: a /chosen/kaslr-seed is automatically added to the device-tree when booted via booti/bootm/bootz therefore using this command is likely no longer needed\n"); + + if (!working_fdt) { + printf("No FDT memory address configured. Please configure\n" + "the FDT address via \"fdt addr <address>\" command.\n" + "Aborting!\n"); + err = CMD_RET_FAILURE; + } else { + if (fdt_kaslrseed(working_fdt, true) < 0) + err = CMD_RET_FAILURE; + } + + return cmd_process_error(cmdtp, err); +} + +U_BOOT_LONGHELP(kaslrseed, + "\n" + " - append random bytes to chosen kaslr-seed node\n"); + +U_BOOT_CMD( + kaslrseed, 1, 0, do_kaslr_seed, + "feed bytes from the hardware random number generator to the kaslr-seed", + kaslrseed_help_text +); diff --git a/cmd/led.c b/cmd/led.c new file mode 100644 index 00000000000..91fb856ee59 --- /dev/null +++ b/cmd/led.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <dm.h> +#include <led.h> +#include <dm/uclass-internal.h> + +#define LED_TOGGLE LEDST_COUNT + +static const char *const state_label[] = { + [LEDST_OFF] = "off", + [LEDST_ON] = "on", + [LEDST_TOGGLE] = "toggle", + [LEDST_BLINK] = "blink", +}; + +enum led_state_t get_led_cmd(char *var) +{ + int i; + + for (i = 0; i < LEDST_COUNT; i++) { + if (!strncmp(var, state_label[i], strlen(var))) + return i; + } + + return -1; +} + +static int show_led_state(struct udevice *dev) +{ + int ret; + + ret = led_get_state(dev); + if (ret >= LEDST_COUNT) + ret = -EINVAL; + if (ret >= 0) + printf("%s\n", state_label[ret]); + + return ret; +} + +static int list_leds(void) +{ + struct udevice *dev; + int ret; + + for (uclass_find_first_device(UCLASS_LED, &dev); + dev; + uclass_find_next_device(&dev)) { + struct led_uc_plat *plat = dev_get_uclass_plat(dev); + + if (!plat->label) + continue; + printf("%-15s ", plat->label); + if (device_active(dev)) { + ret = show_led_state(dev); + if (ret < 0) + printf("Error %d\n", ret); + } else { + printf("<inactive>\n"); + } + } + + return 0; +} + +int do_led(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + enum led_state_t cmd; + const char *led_label; + struct udevice *dev; + int freq_ms = 0; + int ret; + + /* Validate arguments */ + if (argc < 2) + return CMD_RET_USAGE; + led_label = argv[1]; + if (strncmp(led_label, "list", 4) == 0) + return list_leds(); + + cmd = argc > 2 ? get_led_cmd(argv[2]) : LEDST_COUNT; + if (cmd == LEDST_BLINK) { + if (argc < 4) + return CMD_RET_USAGE; + freq_ms = dectoul(argv[3], NULL); + } + ret = led_get_by_label(led_label, &dev); + if (ret) { + printf("LED '%s' not found (err=%d)\n", led_label, ret); + return CMD_RET_FAILURE; + } + switch (cmd) { + case LEDST_OFF: + case LEDST_ON: + case LEDST_TOGGLE: + ret = led_set_state(dev, cmd); + break; + case LEDST_BLINK: + ret = led_set_period(dev, freq_ms); + if (!ret) + ret = led_set_state(dev, LEDST_BLINK); + break; + case LEDST_COUNT: + printf("LED '%s': ", led_label); + ret = show_led_state(dev); + break; + } + if (ret < 0) { + printf("LED '%s' operation failed (err=%d)\n", led_label, ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +#if defined(CONFIG_LED_BLINK) || defined(CONFIG_LED_SW_BLINK) +#define BLINK "|blink [blink-freq in ms]" +#else +#define BLINK "" +#endif + +U_BOOT_CMD( + led, 4, 1, do_led, + "manage LEDs", + "<led_label> on|off|toggle" BLINK "\tChange LED state\n" + "led <led_label>\tGet LED state\n" + "led list\t\tshow a list of LEDs" +); diff --git a/cmd/legacy-mtd-utils.c b/cmd/legacy-mtd-utils.c new file mode 100644 index 00000000000..34a6da01947 --- /dev/null +++ b/cmd/legacy-mtd-utils.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <jffs2/jffs2.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/string.h> + +static int get_part(const char *partname, int *idx, loff_t *off, loff_t *size, + loff_t *maxsize, int devtype) +{ +#ifdef CONFIG_CMD_MTDPARTS + struct mtd_device *dev; + struct part_info *part; + u8 pnum; + int ret; + + ret = mtdparts_init(); + if (ret) + return ret; + + ret = find_dev_and_part(partname, &dev, &pnum, &part); + if (ret) + return ret; + + if (dev->id->type != devtype) { + printf("not same typ %d != %d\n", dev->id->type, devtype); + return -1; + } + + *off = part->offset; + *size = part->size; + *maxsize = part->size; + *idx = dev->id->num; + + return 0; +#else + puts("mtdparts support missing.\n"); + return -1; +#endif +} + +int mtd_arg_off(const char *arg, int *idx, loff_t *off, loff_t *size, + loff_t *maxsize, int devtype, uint64_t chipsize) +{ + if (!str2off(arg, off)) + return get_part(arg, idx, off, size, maxsize, devtype); + + if (*off >= chipsize) { + puts("Offset exceeds device limit\n"); + return -1; + } + + *maxsize = chipsize - *off; + *size = *maxsize; + return 0; +} + +int mtd_arg_off_size(int argc, char *const argv[], int *idx, loff_t *off, + loff_t *size, loff_t *maxsize, int devtype, + uint64_t chipsize) +{ + int ret; + + if (argc == 0) { + *off = 0; + *size = chipsize; + *maxsize = *size; + goto print; + } + + ret = mtd_arg_off(argv[0], idx, off, size, maxsize, devtype, + chipsize); + if (ret) + return ret; + + if (argc == 1) + goto print; + + if (!str2off(argv[1], size)) { + printf("'%s' is not a number\n", argv[1]); + return -1; + } + + if (*size > *maxsize) { + puts("Size exceeds partition or device limit\n"); + return -1; + } + + if (*size == 0) { + debug("ERROR: Invalid size 0\n"); + return -1; + } + +print: + printf("device %d ", *idx); + if (*size == chipsize) + puts("whole chip\n"); + else + printf("offset 0x%llx, size 0x%llx\n", + (unsigned long long)*off, (unsigned long long)*size); + return 0; +} diff --git a/cmd/legacy-mtd-utils.h b/cmd/legacy-mtd-utils.h new file mode 100644 index 00000000000..ac441d5558a --- /dev/null +++ b/cmd/legacy-mtd-utils.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef __LEGACY_MTD_UTILS_H +#define __LEGACY_MTD_UTILS_H + +int mtd_arg_off(const char *arg, int *idx, loff_t *off, loff_t *size, + loff_t *maxsize, int devtype, uint64_t chipsize); +int mtd_arg_off_size(int argc, char *const argv[], int *idx, loff_t *off, + loff_t *size, loff_t *maxsize, int devtype, + uint64_t chipsize); + +#endif /* LEGACY_MTD_UTILS_H */ diff --git a/cmd/legacy_led.c b/cmd/legacy_led.c new file mode 100644 index 00000000000..db312ae6e2d --- /dev/null +++ b/cmd/legacy_led.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2010 + * Jason Kridner <jkridner@beagleboard.org> + * + * Based on cmd_led.c patch from: + * http://www.mail-archive.com/u-boot@lists.denx.de/msg06873.html + * (C) Copyright 2008 + * Ulf Samuelsson <ulf.samuelsson@atmel.com> + */ + +#include <command.h> +#include <status_led.h> +#include <vsprintf.h> +#include <linux/string.h> + +struct led_tbl_s { + char *string; /* String for use in the command */ + led_id_t mask; /* Mask used for calling __led_set() */ + void (*off)(void); /* Optional function for turning LED off */ + void (*on)(void); /* Optional function for turning LED on */ + void (*toggle)(void);/* Optional function for toggling LED */ +}; + +typedef struct led_tbl_s led_tbl_t; + +static const led_tbl_t led_commands[] = { +#ifdef CONFIG_LED_STATUS_BOARD_SPECIFIC +#ifdef CONFIG_LED_STATUS0 + { "0", CONFIG_LED_STATUS_BIT, NULL, NULL, NULL }, +#endif +#ifdef CONFIG_LED_STATUS1 + { "1", CONFIG_LED_STATUS_BIT1, NULL, NULL, NULL }, +#endif +#ifdef CONFIG_LED_STATUS2 + { "2", CONFIG_LED_STATUS_BIT2, NULL, NULL, NULL }, +#endif +#ifdef CONFIG_LED_STATUS3 + { "3", CONFIG_LED_STATUS_BIT3, NULL, NULL, NULL }, +#endif +#ifdef CONFIG_LED_STATUS4 + { "4", CONFIG_LED_STATUS_BIT4, NULL, NULL, NULL }, +#endif +#ifdef CONFIG_LED_STATUS5 + { "5", CONFIG_LED_STATUS_BIT5, NULL, NULL, NULL }, +#endif +#endif +#ifdef CONFIG_LED_STATUS_GREEN + { "green", CONFIG_LED_STATUS_GREEN, green_led_off, green_led_on, NULL }, +#endif +#ifdef CONFIG_LED_STATUS_YELLOW + { "yellow", CONFIG_LED_STATUS_YELLOW, yellow_led_off, yellow_led_on, + NULL }, +#endif +#ifdef CONFIG_LED_STATUS_RED + { "red", CONFIG_LED_STATUS_RED, red_led_off, red_led_on, NULL }, +#endif +#ifdef CONFIG_LED_STATUS_BLUE + { "blue", CONFIG_LED_STATUS_BLUE, blue_led_off, blue_led_on, NULL }, +#endif + { NULL, 0, NULL, NULL, NULL } +}; + +enum led_cmd { LED_ON, LED_OFF, LED_TOGGLE, LED_BLINK }; + +enum led_cmd get_led_cmd(char *var) +{ + if (strcmp(var, "off") == 0) + return LED_OFF; + if (strcmp(var, "on") == 0) + return LED_ON; + if (strcmp(var, "toggle") == 0) + return LED_TOGGLE; + if (strcmp(var, "blink") == 0) + return LED_BLINK; + + return -1; +} + +/* + * LED drivers providing a blinking LED functionality, like the + * PCA9551, can override this empty weak function + */ +void __weak __led_blink(led_id_t mask, int freq) +{ +} + +int do_legacy_led(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int i, match = 0; + enum led_cmd cmd; + int freq; + + /* Validate arguments */ + if ((argc < 3) || (argc > 4)) + return CMD_RET_USAGE; + + cmd = get_led_cmd(argv[2]); + if (cmd < 0) { + return CMD_RET_USAGE; + } + + for (i = 0; led_commands[i].string; i++) { + if ((strcmp("all", argv[1]) == 0) || + (strcmp(led_commands[i].string, argv[1]) == 0)) { + match = 1; + switch (cmd) { + case LED_ON: + if (led_commands[i].on) + led_commands[i].on(); + else + __led_set(led_commands[i].mask, + CONFIG_LED_STATUS_ON); + break; + case LED_OFF: + if (led_commands[i].off) + led_commands[i].off(); + else + __led_set(led_commands[i].mask, + CONFIG_LED_STATUS_OFF); + break; + case LED_TOGGLE: + if (led_commands[i].toggle) + led_commands[i].toggle(); + else + __led_toggle(led_commands[i].mask); + break; + case LED_BLINK: + if (argc != 4) + return CMD_RET_USAGE; + + freq = dectoul(argv[3], NULL); + __led_blink(led_commands[i].mask, freq); + } + /* Need to set only 1 led if led_name wasn't 'all' */ + if (strcmp("all", argv[1]) != 0) + break; + } + } + + /* If we ran out of matches, print Usage */ + if (!match) { + return CMD_RET_USAGE; + } + + return 0; +} + +U_BOOT_CMD( + led, 4, 1, do_legacy_led, + "[" +#ifdef CONFIG_LED_STATUS_BOARD_SPECIFIC +#ifdef CONFIG_LED_STATUS0 + "0|" +#endif +#ifdef CONFIG_LED_STATUS1 + "1|" +#endif +#ifdef CONFIG_LED_STATUS2 + "2|" +#endif +#ifdef CONFIG_LED_STATUS3 + "3|" +#endif +#ifdef CONFIG_LED_STATUS4 + "4|" +#endif +#ifdef CONFIG_LED_STATUS5 + "5|" +#endif +#endif +#ifdef CONFIG_LED_STATUS_GREEN + "green|" +#endif +#ifdef CONFIG_LED_STATUS_YELLOW + "yellow|" +#endif +#ifdef CONFIG_LED_STATUS_RED + "red|" +#endif +#ifdef CONFIG_LED_STATUS_BLUE + "blue|" +#endif + "all] [on|off|toggle|blink] [blink-freq in ms]", + "[led_name] [on|off|toggle|blink] sets or clears led(s)" +); diff --git a/cmd/license.c b/cmd/license.c new file mode 100644 index 00000000000..161663ff29c --- /dev/null +++ b/cmd/license.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2007 by OpenMoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + */ + +#include <command.h> +#include <gzip.h> +#include <malloc.h> + +#include "license_data_gz.h" +#include "license_data_size.h" + +static int do_license(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *dst; + unsigned long len = data_size; + int ret = CMD_RET_SUCCESS; + + dst = malloc(data_size + 1); + if (!dst) + return CMD_RET_FAILURE; + + ret = gunzip(dst, data_size, (unsigned char *)data_gz, &len); + if (ret) { + printf("Error uncompressing license text\n"); + ret = CMD_RET_FAILURE; + goto free; + } + + dst[data_size] = 0; + puts(dst); + +free: + free(dst); + + return ret; +} + +U_BOOT_CMD( + license, 1, 1, do_license, + "print GPL license text", + "" +); diff --git a/cmd/load.c b/cmd/load.c new file mode 100644 index 00000000000..159767aa7f7 --- /dev/null +++ b/cmd/load.c @@ -0,0 +1,1193 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2004 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * Serial up- and download support + */ +#include <command.h> +#include <console.h> +#include <cpu_func.h> +#include <efi_loader.h> +#include <env.h> +#include <exports.h> +#ifdef CONFIG_MTD_NOR_FLASH +#include <flash.h> +#endif +#include <image.h> +#include <lmb.h> +#include <mapmem.h> +#include <net.h> +#include <s_record.h> +#include <serial.h> +#include <xyzModem.h> +#include <asm/cache.h> +#include <asm/global_data.h> +#include <linux/delay.h> + +DECLARE_GLOBAL_DATA_PTR; + +#if defined(CONFIG_CMD_LOADB) +static ulong load_serial_ymodem(ulong offset, int mode); +#endif + +#if defined(CONFIG_CMD_LOADS) +static ulong load_serial(long offset); +static int read_record(char *buf, ulong len); +# if defined(CONFIG_CMD_SAVES) +static int save_serial(ulong offset, ulong size); +static int write_record(char *buf); +#endif + +static int do_echo = 1; +#endif + +/* -------------------------------------------------------------------- */ + +#if defined(CONFIG_CMD_LOADS) +static int do_load_serial(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + long offset = 0; + ulong addr; + int i; + char *env_echo; + int rcode = 0; +#ifdef CONFIG_SYS_LOADS_BAUD_CHANGE + int load_baudrate, current_baudrate; + + load_baudrate = current_baudrate = gd->baudrate; +#endif + + env_echo = env_get("loads_echo"); + if (env_echo && *env_echo == '1') + do_echo = 1; + else + do_echo = 0; + +#ifdef CONFIG_SYS_LOADS_BAUD_CHANGE + if (argc >= 2) { + offset = simple_strtol(argv[1], NULL, 16); + } + if (argc == 3) { + load_baudrate = (int)dectoul(argv[2], NULL); + + /* default to current baudrate */ + if (load_baudrate == 0) + load_baudrate = current_baudrate; + } + if (load_baudrate != current_baudrate) { + printf("## Switch baudrate to %d bps and press ENTER ...\n", + load_baudrate); + udelay(50000); + flush(); + gd->baudrate = load_baudrate; + serial_setbrg(); + udelay(50000); + for (;;) { + if (getchar() == '\r') + break; + } + } +#else /* ! CONFIG_SYS_LOADS_BAUD_CHANGE */ + if (argc == 2) { + offset = simple_strtol(argv[1], NULL, 16); + } +#endif /* CONFIG_SYS_LOADS_BAUD_CHANGE */ + + printf("## Ready for S-Record download ...\n"); + + addr = load_serial(offset); + + /* + * Gather any trailing characters (for instance, the ^D which + * is sent by 'cu' after sending a file), and give the + * box some time (100 * 1 ms) + */ + for (i=0; i<100; ++i) { + if (tstc()) { + getchar(); + } + udelay(1000); + } + + if (addr == ~0) { + printf("## S-Record download aborted\n"); + rcode = 1; + } else { + printf("## Start Addr = 0x%08lX\n", addr); + image_load_addr = addr; + } + +#ifdef CONFIG_SYS_LOADS_BAUD_CHANGE + if (load_baudrate != current_baudrate) { + printf("## Switch baudrate to %d bps and press ESC ...\n", + current_baudrate); + udelay(50000); + flush(); + gd->baudrate = current_baudrate; + serial_setbrg(); + udelay(50000); + for (;;) { + if (getchar() == 0x1B) /* ESC */ + break; + } + } +#endif + return rcode; +} + +static ulong load_serial(long offset) +{ + char record[SREC_MAXRECLEN + 1]; /* buffer for one S-Record */ + char binbuf[SREC_MAXBINLEN]; /* buffer for binary data */ + int binlen; /* no. of data bytes in S-Rec. */ + int type; /* return code for record type */ + ulong addr; /* load address from S-Record */ + ulong size; /* number of bytes transferred */ + ulong store_addr; + ulong start_addr = ~0; + ulong end_addr = 0; + int line_count = 0; + long ret; + + while (read_record(record, SREC_MAXRECLEN + 1) >= 0) { + type = srec_decode(record, &binlen, &addr, binbuf); + + if (type < 0) { + return (~0); /* Invalid S-Record */ + } + + switch (type) { + case SREC_DATA2: + case SREC_DATA3: + case SREC_DATA4: + store_addr = addr + offset; +#ifdef CONFIG_MTD_NOR_FLASH + if (addr2info(store_addr)) { + int rc; + + rc = flash_write((char *)binbuf,store_addr,binlen); + if (rc != 0) { + flash_perror(rc); + return (~0); + } + } else +#endif + { + void *dst; + phys_addr_t dst_addr; + + dst_addr = (phys_addr_t)store_addr; + ret = lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &dst_addr, + binlen, LMB_NONE); + if (ret) { + printf("\nCannot overwrite reserved area (%08lx..%08lx)\n", + store_addr, store_addr + binlen); + return ret; + } + dst = map_sysmem(dst_addr, binlen); + memcpy(dst, binbuf, binlen); + unmap_sysmem(dst); + lmb_free(dst_addr, binlen, LMB_NONE); + } + if ((store_addr) < start_addr) + start_addr = store_addr; + if ((store_addr + binlen - 1) > end_addr) + end_addr = store_addr + binlen - 1; + break; + case SREC_END2: + case SREC_END3: + case SREC_END4: + udelay(10000); + size = end_addr - start_addr + 1; + printf("\n" + "## First Load Addr = 0x%08lX\n" + "## Last Load Addr = 0x%08lX\n" + "## Total Size = 0x%08lX = %ld Bytes\n", + start_addr, end_addr, size, size + ); + flush_cache(start_addr, size); + env_set_hex("filesize", size); + return (addr); + case SREC_START: + break; + default: + break; + } + if (!do_echo) { /* print a '.' every 100 lines */ + if ((++line_count % 100) == 0) + putc('.'); + } + } + + return (~0); /* Download aborted */ +} + +static int read_record(char *buf, ulong len) +{ + char *p; + int c; + + --len; /* always leave room for terminating '\0' byte */ + + for (p=buf; p < buf+len; ++p) { + c = getchar(); /* read character */ + if (do_echo) + putc(c); /* ... and echo it */ + + switch (c) { + case '\r': + case '\n': + *p = '\0'; + return (p - buf); + case '\0': + case 0x03: /* ^C - Control C */ + return (-1); + default: + *p = c; + } + + /* Check for the console hangup (if any different from serial) */ + if (gd->jt->getc != getchar) { + if (ctrlc()) + return (-1); + } + } + + /* line too long - truncate */ + *p = '\0'; + return (p - buf); +} + +#if defined(CONFIG_CMD_SAVES) + +int do_save_serial(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong offset = 0; + ulong size = 0; +#ifdef CONFIG_SYS_LOADS_BAUD_CHANGE + int save_baudrate, current_baudrate; + + save_baudrate = current_baudrate = gd->baudrate; +#endif + + if (argc >= 2) { + offset = hextoul(argv[1], NULL); + } +#ifdef CONFIG_SYS_LOADS_BAUD_CHANGE + if (argc >= 3) { + size = hextoul(argv[2], NULL); + } + if (argc == 4) { + save_baudrate = (int)dectoul(argv[3], NULL); + + /* default to current baudrate */ + if (save_baudrate == 0) + save_baudrate = current_baudrate; + } + if (save_baudrate != current_baudrate) { + printf("## Switch baudrate to %d bps and press ENTER ...\n", + save_baudrate); + udelay(50000); + gd->baudrate = save_baudrate; + serial_setbrg(); + udelay(50000); + for (;;) { + if (getchar() == '\r') + break; + } + } +#else /* ! CONFIG_SYS_LOADS_BAUD_CHANGE */ + if (argc == 3) { + size = hextoul(argv[2], NULL); + } +#endif /* CONFIG_SYS_LOADS_BAUD_CHANGE */ + + printf("## Ready for S-Record upload, press ENTER to proceed ...\n"); + for (;;) { + if (getchar() == '\r') + break; + } + if (save_serial(offset, size)) { + printf("## S-Record upload aborted\n"); + } else { + printf("## S-Record upload complete\n"); + } +#ifdef CONFIG_SYS_LOADS_BAUD_CHANGE + if (save_baudrate != current_baudrate) { + printf("## Switch baudrate to %d bps and press ESC ...\n", + (int)current_baudrate); + udelay(50000); + flush(); + gd->baudrate = current_baudrate; + serial_setbrg(); + udelay(50000); + for (;;) { + if (getchar() == 0x1B) /* ESC */ + break; + } + } +#endif + return 0; +} + +#define SREC3_START "S0030000FC\n" +#define SREC3_FORMAT "S3%02X%08lX%s%02X\n" +#define SREC3_END "S70500000000FA\n" +#define SREC_BYTES_PER_RECORD 16 + +static int save_serial(ulong address, ulong count) +{ + int i, c, reclen, checksum, length; + char *hex = "0123456789ABCDEF"; + char record[2*SREC_BYTES_PER_RECORD+16]; /* buffer for one S-Record */ + char data[2*SREC_BYTES_PER_RECORD+1]; /* buffer for hex data */ + + reclen = 0; + checksum = 0; + + if(write_record(SREC3_START)) /* write the header */ + return (-1); + do { + volatile uchar *src; + + src = map_sysmem(address, count); + if (count) { /* collect hex data in the buffer */ + c = src[reclen]; /* get one byte */ + checksum += c; /* accumulate checksum */ + data[2*reclen] = hex[(c>>4)&0x0f]; + data[2*reclen+1] = hex[c & 0x0f]; + data[2*reclen+2] = '\0'; + ++reclen; + --count; + } + unmap_sysmem((void *)src); + if(reclen == SREC_BYTES_PER_RECORD || count == 0) { + /* enough data collected for one record: dump it */ + if(reclen) { /* build & write a data record: */ + /* address + data + checksum */ + length = 4 + reclen + 1; + + /* accumulate length bytes into checksum */ + for(i = 0; i < 2; i++) + checksum += (length >> (8*i)) & 0xff; + + /* accumulate address bytes into checksum: */ + for(i = 0; i < 4; i++) + checksum += (address >> (8*i)) & 0xff; + + /* make proper checksum byte: */ + checksum = ~checksum & 0xff; + + /* output one record: */ + sprintf(record, SREC3_FORMAT, length, address, data, checksum); + if(write_record(record)) + return (-1); + } + address += reclen; /* increment address */ + checksum = 0; + reclen = 0; + } + } + while(count); + if(write_record(SREC3_END)) /* write the final record */ + return (-1); + return(0); +} + +static int write_record(char *buf) +{ + char c; + + while((c = *buf++)) + putc(c); + + /* Check for the console hangup (if any different from serial) */ + + if (ctrlc()) { + return (-1); + } + return (0); +} +# endif + +#endif + +#if defined(CONFIG_CMD_LOADB) +/* + * loadb command (load binary) included + */ +#define XON_CHAR 17 +#define XOFF_CHAR 19 +#define START_CHAR 0x01 +#define ETX_CHAR 0x03 +#define END_CHAR 0x0D +#define SPACE 0x20 +#define K_ESCAPE 0x23 +#define SEND_TYPE 'S' +#define DATA_TYPE 'D' +#define ACK_TYPE 'Y' +#define NACK_TYPE 'N' +#define BREAK_TYPE 'B' +#define tochar(x) ((char) (((x) + SPACE) & 0xff)) +#define untochar(x) ((int) (((x) - SPACE) & 0xff)) + +static void set_kerm_bin_mode(unsigned long *); +static int k_recv(void); +static ulong load_serial_bin(ulong offset); + +static char his_eol; /* character he needs at end of packet */ +static int his_pad_count; /* number of pad chars he needs */ +static char his_pad_char; /* pad chars he needs */ +static char his_quote; /* quote chars he'll use */ + +static int do_load_serial_bin(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong offset = 0; + ulong addr; + int load_baudrate, current_baudrate; + int rcode = 0; + char *s; + + /* pre-set offset from CONFIG_SYS_LOAD_ADDR */ + offset = CONFIG_SYS_LOAD_ADDR; + + /* pre-set offset from $loadaddr */ + s = env_get("loadaddr"); + if (s) + offset = hextoul(s, NULL); + + load_baudrate = current_baudrate = gd->baudrate; + + if (argc >= 2) { + offset = hextoul(argv[1], NULL); + } + if (argc == 3) { + load_baudrate = (int)dectoul(argv[2], NULL); + + /* default to current baudrate */ + if (load_baudrate == 0) + load_baudrate = current_baudrate; + } + + if (load_baudrate != current_baudrate) { + printf("## Switch baudrate to %d bps and press ENTER ...\n", + load_baudrate); + udelay(50000); + flush(); + gd->baudrate = load_baudrate; + serial_setbrg(); + udelay(50000); + for (;;) { + if (getchar() == '\r') + break; + } + } + + if (strcmp(argv[0],"loady")==0) { + printf("## Ready for binary (ymodem) download " + "to 0x%08lX at %d bps...\n", + offset, + load_baudrate); + + addr = load_serial_ymodem(offset, xyzModem_ymodem); + + if (addr == ~0) { + image_load_addr = 0; + printf("## Binary (ymodem) download aborted\n"); + rcode = 1; + } else { + printf("## Start Addr = 0x%08lX\n", addr); + image_load_addr = addr; + } + } else if (strcmp(argv[0],"loadx")==0) { + printf("## Ready for binary (xmodem) download " + "to 0x%08lX at %d bps...\n", + offset, + load_baudrate); + + addr = load_serial_ymodem(offset, xyzModem_xmodem); + + if (addr == ~0) { + image_load_addr = 0; + printf("## Binary (xmodem) download aborted\n"); + rcode = 1; + } else { + printf("## Start Addr = 0x%08lX\n", addr); + image_load_addr = addr; + } + } else { + + printf("## Ready for binary (kermit) download " + "to 0x%08lX at %d bps...\n", + offset, + load_baudrate); + addr = load_serial_bin(offset); + + if (addr == ~0) { + image_load_addr = 0; + printf("## Binary (kermit) download aborted\n"); + rcode = 1; + } else { + printf("## Start Addr = 0x%08lX\n", addr); + image_load_addr = addr; + } + } + if (load_baudrate != current_baudrate) { + printf("## Switch baudrate to %d bps and press ESC ...\n", + current_baudrate); + udelay(50000); + flush(); + gd->baudrate = current_baudrate; + serial_setbrg(); + udelay(50000); + for (;;) { + if (getchar() == 0x1B) /* ESC */ + break; + } + } + + return rcode; +} + +static ulong load_serial_bin(ulong offset) +{ + int size, i; + + set_kerm_bin_mode((ulong *) offset); + size = k_recv(); + + /* + * Gather any trailing characters (for instance, the ^D which + * is sent by 'cu' after sending a file), and give the + * box some time (100 * 1 ms) + */ + for (i=0; i<100; ++i) { + if (tstc()) { + getchar(); + } + udelay(1000); + } + + if (size == 0) + return ~0; /* Download aborted */ + + flush_cache(offset, size); + + printf("## Total Size = 0x%08x = %d Bytes\n", size, size); + env_set_hex("filesize", size); + + return offset; +} + +static void send_pad(void) +{ + int count = his_pad_count; + + while (count-- > 0) + putc(his_pad_char); +} + +/* converts escaped kermit char to binary char */ +static char ktrans(char in) +{ + if ((in & 0x60) == 0x40) { + return (char) (in & ~0x40); + } else if ((in & 0x7f) == 0x3f) { + return (char) (in | 0x40); + } else + return in; +} + +static int chk1(char *buffer) +{ + int total = 0; + + while (*buffer) { + total += *buffer++; + } + return (int) ((total + ((total >> 6) & 0x03)) & 0x3f); +} + +static void s1_sendpacket(char *packet) +{ + send_pad(); + while (*packet) { + putc(*packet++); + } +} + +static char a_b[24]; +static void send_ack(int n) +{ + a_b[0] = START_CHAR; + a_b[1] = tochar(3); + a_b[2] = tochar(n); + a_b[3] = ACK_TYPE; + a_b[4] = '\0'; + a_b[4] = tochar(chk1(&a_b[1])); + a_b[5] = his_eol; + a_b[6] = '\0'; + s1_sendpacket(a_b); +} + +static void send_nack(int n) +{ + a_b[0] = START_CHAR; + a_b[1] = tochar(3); + a_b[2] = tochar(n); + a_b[3] = NACK_TYPE; + a_b[4] = '\0'; + a_b[4] = tochar(chk1(&a_b[1])); + a_b[5] = his_eol; + a_b[6] = '\0'; + s1_sendpacket(a_b); +} + +static void (*os_data_init)(void); +static void (*os_data_char)(char new_char); +static int os_data_state, os_data_state_saved; +static char *os_data_addr, *os_data_addr_saved; +static char *bin_start_address; + +static void bin_data_init(void) +{ + os_data_state = 0; + os_data_addr = bin_start_address; +} + +static void os_data_save(void) +{ + os_data_state_saved = os_data_state; + os_data_addr_saved = os_data_addr; +} + +static void os_data_restore(void) +{ + os_data_state = os_data_state_saved; + os_data_addr = os_data_addr_saved; +} + +static void bin_data_char(char new_char) +{ + switch (os_data_state) { + case 0: /* data */ + *os_data_addr++ = new_char; + break; + } +} + +static void set_kerm_bin_mode(unsigned long *addr) +{ + bin_start_address = (char *) addr; + os_data_init = bin_data_init; + os_data_char = bin_data_char; +} + +/* k_data_* simply handles the kermit escape translations */ +static int k_data_escape, k_data_escape_saved; +static void k_data_init(void) +{ + k_data_escape = 0; + os_data_init(); +} + +static void k_data_save(void) +{ + k_data_escape_saved = k_data_escape; + os_data_save(); +} + +static void k_data_restore(void) +{ + k_data_escape = k_data_escape_saved; + os_data_restore(); +} + +static void k_data_char(char new_char) +{ + if (k_data_escape) { + /* last char was escape - translate this character */ + os_data_char(ktrans(new_char)); + k_data_escape = 0; + } else { + if (new_char == his_quote) { + /* this char is escape - remember */ + k_data_escape = 1; + } else { + /* otherwise send this char as-is */ + os_data_char(new_char); + } + } +} + +#define SEND_DATA_SIZE 20 +static char send_parms[SEND_DATA_SIZE]; +static char *send_ptr; + +/* handle_send_packet interprits the protocol info and builds and + sends an appropriate ack for what we can do */ +static void handle_send_packet(int n) +{ + int length = 3; + int bytes; + + /* initialize some protocol parameters */ + his_eol = END_CHAR; /* default end of line character */ + his_pad_count = 0; + his_pad_char = '\0'; + his_quote = K_ESCAPE; + + /* ignore last character if it filled the buffer */ + if (send_ptr == &send_parms[SEND_DATA_SIZE - 1]) + --send_ptr; + bytes = send_ptr - send_parms; /* how many bytes we'll process */ + do { + if (bytes-- <= 0) + break; + /* handle MAXL - max length */ + /* ignore what he says - most I'll take (here) is 94 */ + a_b[++length] = tochar(94); + if (bytes-- <= 0) + break; + /* handle TIME - time you should wait for my packets */ + /* ignore what he says - don't wait for my ack longer than 1 second */ + a_b[++length] = tochar(1); + if (bytes-- <= 0) + break; + /* handle NPAD - number of pad chars I need */ + /* remember what he says - I need none */ + his_pad_count = untochar(send_parms[2]); + a_b[++length] = tochar(0); + if (bytes-- <= 0) + break; + /* handle PADC - pad chars I need */ + /* remember what he says - I need none */ + his_pad_char = ktrans(send_parms[3]); + a_b[++length] = 0x40; /* He should ignore this */ + if (bytes-- <= 0) + break; + /* handle EOL - end of line he needs */ + /* remember what he says - I need CR */ + his_eol = untochar(send_parms[4]); + a_b[++length] = tochar(END_CHAR); + if (bytes-- <= 0) + break; + /* handle QCTL - quote control char he'll use */ + /* remember what he says - I'll use '#' */ + his_quote = send_parms[5]; + a_b[++length] = '#'; + if (bytes-- <= 0) + break; + /* handle QBIN - 8-th bit prefixing */ + /* ignore what he says - I refuse */ + a_b[++length] = 'N'; + if (bytes-- <= 0) + break; + /* handle CHKT - the clock check type */ + /* ignore what he says - I do type 1 (for now) */ + a_b[++length] = '1'; + if (bytes-- <= 0) + break; + /* handle REPT - the repeat prefix */ + /* ignore what he says - I refuse (for now) */ + a_b[++length] = 'N'; + if (bytes-- <= 0) + break; + /* handle CAPAS - the capabilities mask */ + /* ignore what he says - I only do long packets - I don't do windows */ + a_b[++length] = tochar(2); /* only long packets */ + a_b[++length] = tochar(0); /* no windows */ + a_b[++length] = tochar(94); /* large packet msb */ + a_b[++length] = tochar(94); /* large packet lsb */ + } while (0); + + a_b[0] = START_CHAR; + a_b[1] = tochar(length); + a_b[2] = tochar(n); + a_b[3] = ACK_TYPE; + a_b[++length] = '\0'; + a_b[length] = tochar(chk1(&a_b[1])); + a_b[++length] = his_eol; + a_b[++length] = '\0'; + s1_sendpacket(a_b); +} + +/* k_recv receives a OS Open image file over kermit line */ +static int k_recv(void) +{ + int new_char; + char k_state, k_state_saved; + int sum; + int done; + int length; + int n, last_n; + int len_lo, len_hi; + + /* initialize some protocol parameters */ + his_eol = END_CHAR; /* default end of line character */ + his_pad_count = 0; + his_pad_char = '\0'; + his_quote = K_ESCAPE; + + /* initialize the k_recv and k_data state machine */ + done = 0; + k_state = 0; + k_data_init(); + k_state_saved = k_state; + k_data_save(); + n = 0; /* just to get rid of a warning */ + last_n = -1; + + /* expect this "type" sequence (but don't check): + S: send initiate + F: file header + D: data (multiple) + Z: end of file + B: break transmission + */ + + /* enter main loop */ + while (!done) { + /* set the send packet pointer to begining of send packet parms */ + send_ptr = send_parms; + + /* With each packet, start summing the bytes starting with the length. + Save the current sequence number. + Note the type of the packet. + If a character less than SPACE (0x20) is received - error. + */ + +#if 0 + /* OLD CODE, Prior to checking sequence numbers */ + /* first have all state machines save current states */ + k_state_saved = k_state; + k_data_save (); +#endif + + /* get a packet */ + /* wait for the starting character or ^C */ + for (;;) { + switch (getchar()) { + case START_CHAR: /* start packet */ + goto START; + case ETX_CHAR: /* ^C waiting for packet */ + return (0); + default: + ; + } + } +START: + /* get length of packet */ + sum = 0; + new_char = getchar(); + if ((new_char & 0xE0) == 0) + goto packet_error; + sum += new_char & 0xff; + length = untochar(new_char); + /* get sequence number */ + new_char = getchar(); + if ((new_char & 0xE0) == 0) + goto packet_error; + sum += new_char & 0xff; + n = untochar(new_char); + --length; + + /* NEW CODE - check sequence numbers for retried packets */ + /* Note - this new code assumes that the sequence number is correctly + * received. Handling an invalid sequence number adds another layer + * of complexity that may not be needed - yet! At this time, I'm hoping + * that I don't need to buffer the incoming data packets and can write + * the data into memory in real time. + */ + if (n == last_n) { + /* same sequence number, restore the previous state */ + k_state = k_state_saved; + k_data_restore(); + } else { + /* new sequence number, checkpoint the download */ + last_n = n; + k_state_saved = k_state; + k_data_save(); + } + /* END NEW CODE */ + + /* get packet type */ + new_char = getchar(); + if ((new_char & 0xE0) == 0) + goto packet_error; + sum += new_char & 0xff; + k_state = new_char; + --length; + /* check for extended length */ + if (length == -2) { + /* (length byte was 0, decremented twice) */ + /* get the two length bytes */ + new_char = getchar(); + if ((new_char & 0xE0) == 0) + goto packet_error; + sum += new_char & 0xff; + len_hi = untochar(new_char); + new_char = getchar(); + if ((new_char & 0xE0) == 0) + goto packet_error; + sum += new_char & 0xff; + len_lo = untochar(new_char); + length = len_hi * 95 + len_lo; + /* check header checksum */ + new_char = getchar(); + if ((new_char & 0xE0) == 0) + goto packet_error; + if (new_char != tochar((sum + ((sum >> 6) & 0x03)) & 0x3f)) + goto packet_error; + sum += new_char & 0xff; +/* --length; */ /* new length includes only data and block check to come */ + } + /* bring in rest of packet */ + while (length > 1) { + new_char = getchar(); + if ((new_char & 0xE0) == 0) + goto packet_error; + sum += new_char & 0xff; + --length; + if (k_state == DATA_TYPE) { + /* pass on the data if this is a data packet */ + k_data_char (new_char); + } else if (k_state == SEND_TYPE) { + /* save send pack in buffer as is */ + *send_ptr++ = new_char; + /* if too much data, back off the pointer */ + if (send_ptr >= &send_parms[SEND_DATA_SIZE]) + --send_ptr; + } + } + /* get and validate checksum character */ + new_char = getchar(); + if ((new_char & 0xE0) == 0) + goto packet_error; + if (new_char != tochar((sum + ((sum >> 6) & 0x03)) & 0x3f)) + goto packet_error; + /* get END_CHAR */ + new_char = getchar(); + if (new_char != END_CHAR) { + packet_error: + /* restore state machines */ + k_state = k_state_saved; + k_data_restore(); + /* send a negative acknowledge packet in */ + send_nack(n); + } else if (k_state == SEND_TYPE) { + /* crack the protocol parms, build an appropriate ack packet */ + handle_send_packet(n); + } else { + /* send simple acknowledge packet in */ + send_ack(n); + /* quit if end of transmission */ + if (k_state == BREAK_TYPE) + done = 1; + } + } + return ((ulong) os_data_addr - (ulong) bin_start_address); +} + +static int getcxmodem(void) { + if (tstc()) + return (getchar()); + return -1; +} +static ulong load_serial_ymodem(ulong offset, int mode) +{ + int size; + int err; + int res; + connection_info_t info; + char ymodemBuf[1024]; + ulong store_addr = ~0; + ulong addr = 0; + + size = 0; + info.mode = mode; + res = xyzModem_stream_open(&info, &err); + if (!res) { + + err = 0; + while ((res = + xyzModem_stream_read(ymodemBuf, 1024, &err)) > 0) { + store_addr = addr + offset; + size += res; + addr += res; +#ifdef CONFIG_MTD_NOR_FLASH + if (addr2info(store_addr)) { + int rc; + + rc = flash_write((char *) ymodemBuf, + store_addr, res); + if (rc != 0) { + xyzModem_stream_terminate(true, &getcxmodem); + xyzModem_stream_close(&err); + printf("\n"); + flash_perror(rc); + return (~0); + } + } else +#endif + { + memcpy((char *)(store_addr), ymodemBuf, + res); + } + + } + if (err) { + xyzModem_stream_terminate((err == xyzModem_cancel) ? false : true, &getcxmodem); + xyzModem_stream_close(&err); + printf("\n%s\n", xyzModem_error(err)); + return (~0); /* Download aborted */ + } + + if (IS_ENABLED(CONFIG_CMD_BOOTEFI)) + efi_set_bootdev("Uart", "", "", + map_sysmem(offset, 0), size); + + } else { + printf("\n%s\n", xyzModem_error(err)); + return (~0); /* Download aborted */ + } + + xyzModem_stream_terminate(false, &getcxmodem); + xyzModem_stream_close(&err); + + flush_cache(offset, ALIGN(size, ARCH_DMA_MINALIGN)); + + printf("## Total Size = 0x%08x = %d Bytes\n", size, size); + env_set_hex("filesize", size); + + return offset; +} + +#endif + +#if defined(CONFIG_CMD_LOADM) +static int do_load_memory_bin(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr, dest, size; + void *src, *dst; + + if (argc != 4) + return CMD_RET_USAGE; + + addr = simple_strtoul(argv[1], NULL, 16); + + dest = simple_strtoul(argv[2], NULL, 16); + + size = simple_strtoul(argv[3], NULL, 16); + + if (!size) { + printf("loadm: can not load zero bytes\n"); + return 1; + } + + src = map_sysmem(addr, size); + dst = map_sysmem(dest, size); + + memcpy(dst, src, size); + + unmap_sysmem(src); + unmap_sysmem(dst); + + if (IS_ENABLED(CONFIG_CMD_BOOTEFI)) + efi_set_bootdev("Mem", "", "", map_sysmem(dest, 0), size); + + printf("loaded bin to memory: size: %lu\n", size); + + return 0; +} +#endif + +/* -------------------------------------------------------------------- */ + +#if defined(CONFIG_CMD_LOADS) + +#ifdef CONFIG_SYS_LOADS_BAUD_CHANGE +U_BOOT_CMD( + loads, 3, 0, do_load_serial, + "load S-Record file over serial line", + "[ off ] [ baud ]\n" + " - load S-Record file over serial line" + " with offset 'off' and baudrate 'baud'" +); + +#else /* ! CONFIG_SYS_LOADS_BAUD_CHANGE */ +U_BOOT_CMD( + loads, 2, 0, do_load_serial, + "load S-Record file over serial line", + "[ off ]\n" + " - load S-Record file over serial line with offset 'off'" +); +#endif /* CONFIG_SYS_LOADS_BAUD_CHANGE */ + +/* + * SAVES always requires LOADS support, but not vice versa + */ + +#if defined(CONFIG_CMD_SAVES) +#ifdef CONFIG_SYS_LOADS_BAUD_CHANGE +U_BOOT_CMD( + saves, 4, 0, do_save_serial, + "save S-Record file over serial line", + "[ off ] [size] [ baud ]\n" + " - save S-Record file over serial line" + " with offset 'off', size 'size' and baudrate 'baud'" +); +#else /* ! CONFIG_SYS_LOADS_BAUD_CHANGE */ +U_BOOT_CMD( + saves, 3, 0, do_save_serial, + "save S-Record file over serial line", + "[ off ] [size]\n" + " - save S-Record file over serial line with offset 'off' and size 'size'" +); +#endif /* CONFIG_SYS_LOADS_BAUD_CHANGE */ +#endif /* CONFIG_CMD_SAVES */ +#endif /* CONFIG_CMD_LOADS */ + +#if defined(CONFIG_CMD_LOADB) +U_BOOT_CMD( + loadb, 3, 0, do_load_serial_bin, + "load binary file over serial line (kermit mode)", + "[ addr [ baud ] ]\n" + " - load binary file over serial line" + " at address 'addr' with baudrate 'baud'" +); + +U_BOOT_CMD( + loadx, 3, 0, do_load_serial_bin, + "load binary file over serial line (xmodem mode)", + "[ addr [ baud ] ]\n" + " - load binary file over serial line" + " at address 'addr' with baudrate 'baud'" +); + +U_BOOT_CMD( + loady, 3, 0, do_load_serial_bin, + "load binary file over serial line (ymodem mode)", + "[ addr [ baud ] ]\n" + " - load binary file over serial line" + " at address 'addr' with baudrate 'baud'" +); + +#endif /* CONFIG_CMD_LOADB */ + +#if defined(CONFIG_CMD_LOADM) +U_BOOT_CMD( + loadm, 4, 0, do_load_memory_bin, + "load binary blob from source address to destination address", + "[src_addr] [dst_addr] [size]\n" + " - load a binary blob from one memory location to other" + " from src_addr to dst_addr by size bytes" +); +#endif /* CONFIG_CMD_LOADM */ diff --git a/cmd/log.c b/cmd/log.c new file mode 100644 index 00000000000..64add6d8b5a --- /dev/null +++ b/cmd/log.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <dm.h> +#include <getopt.h> +#include <log.h> +#include <malloc.h> +#include <asm/global_data.h> + +static char log_fmt_chars[LOGF_COUNT] = "clFLfm"; + +static enum log_level_t parse_log_level(char *const arg) +{ + enum log_level_t ret; + ulong level; + + if (!strict_strtoul(arg, 10, &level)) { + if (level > _LOG_MAX_LEVEL) { + printf("Only log levels <= %d are supported\n", + _LOG_MAX_LEVEL); + return LOGL_NONE; + } + return level; + } + + ret = log_get_level_by_name(arg); + if (ret == LOGL_NONE) + printf("Unknown log level \"%s\"\n", arg); + return ret; +} + +static int do_log_level(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum log_level_t log_level; + + if (argc > 1) { + log_level = parse_log_level(argv[1]); + + if (log_level == LOGL_NONE) + return CMD_RET_FAILURE; + gd->default_log_level = log_level; + } else { + for (log_level = LOGL_FIRST; log_level <= _LOG_MAX_LEVEL; + log_level++) + printf("%s%s\n", log_get_level_name(log_level), + log_level == gd->default_log_level ? + " (default)" : ""); + } + + return CMD_RET_SUCCESS; +} + +static int do_log_categories(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum log_category_t cat; + const char *name; + + for (cat = LOGC_FIRST; cat < LOGC_COUNT; cat++) { + name = log_get_cat_name(cat); + /* + * Invalid category names (e.g. <invalid> or <missing>) begin + * with '<'. + */ + if (name[0] == '<') + continue; + printf("%s\n", name); + } + + return CMD_RET_SUCCESS; +} + +static int do_log_drivers(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct log_device *ldev; + + list_for_each_entry(ldev, &gd->log_head, sibling_node) + printf("%s\n", ldev->drv->name); + + return CMD_RET_SUCCESS; +} + +static int do_log_filter_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int opt; + const char *drv_name = "console"; + struct getopt_state gs; + struct log_filter *filt; + struct log_device *ldev; + + getopt_init_state(&gs); + while ((opt = getopt(&gs, argc, argv, "d:")) > 0) { + switch (opt) { + case 'd': + drv_name = gs.arg; + break; + default: + return CMD_RET_USAGE; + } + } + + if (gs.index != argc) + return CMD_RET_USAGE; + + ldev = log_device_find_by_name(drv_name); + if (!ldev) { + printf("Could not find log device for \"%s\"\n", drv_name); + return CMD_RET_FAILURE; + } + + list_for_each_entry(filt, &ldev->filter_head, sibling_node) { + printf("%-3d: %s %s %s\n", filt->filter_num, + filt->flags & LOGFF_DENY ? "DENY" : "ALLOW", + filt->flags & LOGFF_LEVEL_MIN ? ">=" : "<=", + log_get_level_name(filt->level)); + + if (filt->flags & LOGFF_HAS_CAT) { + printf(" Categories:"); + for (int i = 0; + i < LOGF_MAX_CATEGORIES && + filt->cat_list[i] != LOGC_END; + ++i) { + printf(" %s", + log_get_cat_name(filt->cat_list[i])); + } + printf("\n"); + } + if (filt->file_list) + printf(" Files: %s\n", filt->file_list); + if (filt->func_list) + printf(" Functions: %s\n", filt->func_list); + } + + return CMD_RET_SUCCESS; +} + +static int do_log_filter_add(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool level_set = false; + bool print_num = false; + bool type_set = false; + char *file_list = NULL; + char *func_list = NULL; + const char *drv_name = "console"; + int opt, err; + int cat_count = 0; + int flags = 0; + enum log_category_t cat_list[LOGF_MAX_CATEGORIES + 1]; + enum log_level_t level = LOGL_MAX; + struct getopt_state gs; + + getopt_init_state(&gs); + while ((opt = getopt(&gs, argc, argv, "Ac:d:Df:F:l:L:p")) > 0) { + switch (opt) { + case 'A': +#define do_type() do { \ + if (type_set) { \ + printf("Allow or deny set twice\n"); \ + return CMD_RET_USAGE; \ + } \ + type_set = true; \ +} while (0) + do_type(); + break; + case 'c': { + enum log_category_t cat; + + if (cat_count >= LOGF_MAX_CATEGORIES) { + printf("Too many categories\n"); + return CMD_RET_FAILURE; + } + + cat = log_get_cat_by_name(gs.arg); + if (cat == LOGC_NONE) { + printf("Unknown category \"%s\"\n", gs.arg); + return CMD_RET_FAILURE; + } + + cat_list[cat_count++] = cat; + break; + } + case 'd': + drv_name = gs.arg; + break; + case 'D': + do_type(); + flags |= LOGFF_DENY; + break; + case 'f': + file_list = gs.arg; + break; + case 'F': + func_list = gs.arg; + break; + case 'l': +#define do_level() do { \ + if (level_set) { \ + printf("Log level set twice\n"); \ + return CMD_RET_USAGE; \ + } \ + level = parse_log_level(gs.arg); \ + if (level == LOGL_NONE) \ + return CMD_RET_FAILURE; \ + level_set = true; \ +} while (0) + do_level(); + break; + case 'L': + do_level(); + flags |= LOGFF_LEVEL_MIN; + break; + case 'p': + print_num = true; + break; + default: + return CMD_RET_USAGE; + } + } + + if (gs.index != argc) + return CMD_RET_USAGE; + + cat_list[cat_count] = LOGC_END; + err = log_add_filter_flags(drv_name, cat_count ? cat_list : NULL, level, + file_list, func_list, flags); + if (err < 0) { + printf("Could not add filter (err = %d)\n", err); + return CMD_RET_FAILURE; + } else if (print_num) { + printf("%d\n", err); + } + + return CMD_RET_SUCCESS; +} + +static int do_log_filter_remove(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool all = false; + int opt, err; + ulong filter_num; + const char *drv_name = "console"; + struct getopt_state gs; + + getopt_init_state(&gs); + while ((opt = getopt(&gs, argc, argv, "ad:")) > 0) { + switch (opt) { + case 'a': + all = true; + break; + case 'd': + drv_name = gs.arg; + break; + default: + return CMD_RET_USAGE; + } + } + + if (all) { + struct log_filter *filt, *tmp_filt; + struct log_device *ldev; + + if (gs.index != argc) + return CMD_RET_USAGE; + + ldev = log_device_find_by_name(drv_name); + if (!ldev) { + printf("Could not find log device for \"%s\"\n", + drv_name); + return CMD_RET_FAILURE; + } + + list_for_each_entry_safe(filt, tmp_filt, &ldev->filter_head, + sibling_node) { + list_del(&filt->sibling_node); + free(filt); + } + } else { + if (gs.index + 1 != argc) + return CMD_RET_USAGE; + + if (strict_strtoul(argv[gs.index], 10, &filter_num)) { + printf("Invalid filter number \"%s\"\n", argv[gs.index]); + return CMD_RET_FAILURE; + } + + err = log_remove_filter(drv_name, filter_num); + if (err) { + printf("Could not remove filter (err = %d)\n", err); + return CMD_RET_FAILURE; + } + } + + return CMD_RET_SUCCESS; +} + +static int do_log_format(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i; + + if (argc > 1) { + const char *str = argv[1]; + + if (!strcmp(str, "default")) { + gd->log_fmt = log_get_default_format(); + } else if (!strcmp(str, "all")) { + gd->log_fmt = LOGF_ALL; + } else { + gd->log_fmt = 0; + for (; *str; str++) { + char *ptr = strchr(log_fmt_chars, *str); + + if (!ptr) { + printf("Invalid log char '%c'\n", *str); + return CMD_RET_FAILURE; + } + gd->log_fmt |= 1 << (ptr - log_fmt_chars); + } + } + } else { + printf("Log format: "); + for (i = 0; i < LOGF_COUNT; i++) { + if (gd->log_fmt & (1 << i)) + printf("%c", log_fmt_chars[i]); + } + printf("\n"); + } + + return 0; +} + +static int do_log_rec(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum log_category_t cat; + enum log_level_t level; + const char *file; + uint line; + const char *func; + const char *msg; + char *end; + + if (argc < 7) + return CMD_RET_USAGE; + cat = log_get_cat_by_name(argv[1]); + level = dectoul(argv[2], &end); + if (end == argv[2]) { + level = log_get_level_by_name(argv[2]); + + if (level == LOGL_NONE) { + printf("Invalid log level '%s'\n", argv[2]); + return CMD_RET_USAGE; + } + } + if (level >= LOGL_MAX) { + printf("Invalid log level %u\n", level); + return CMD_RET_USAGE; + } + file = argv[3]; + line = dectoul(argv[4], NULL); + func = argv[5]; + msg = argv[6]; + if (_log(cat, level, file, line, func, "%s\n", msg)) + return CMD_RET_FAILURE; + + return 0; +} + +U_BOOT_LONGHELP(log, + "level [<level>] - get/set log level\n" + "categories - list log categories\n" + "drivers - list log drivers\n" + "log filter-list [OPTIONS] - list all filters for a log driver\n" + "\t-d <driver> - Specify the log driver to list filters from; defaults\n" + "\t to console\n" + "log filter-add [OPTIONS] - add a new filter to a driver\n" + "\t-A - Allow messages matching this filter; mutually exclusive with -D\n" + "\t This is the default.\n" + "\t-c <category> - Category to match; may be specified multiple times\n" + "\t-d <driver> - Specify the log driver to add the filter to; defaults\n" + "\t to console\n" + "\t-D - Deny messages matching this filter; mutually exclusive with -A\n" + "\t-f <file_list> - A comma-separated list of files to match\n" + "\t-F <func_list> - A comma-separated list of functions to match\n" + "\t-l <level> - Match log levels less than or equal to <level>;\n" + "\t mutually-exclusive with -L\n" + "\t-L <level> - Match log levels greather than or equal to <level>;\n" + "\t mutually-exclusive with -l\n" + "\t-p - Print the filter number on success\n" + "log filter-remove [OPTIONS] [<num>] - Remove filter number <num>\n" + "\t-a - Remove ALL filters\n" + "\t-d <driver> - Specify the log driver to remove the filter from;\n" + "\t defaults to console\n" + "log format <fmt> - set log output format. <fmt> is a string where\n" + "\teach letter indicates something that should be displayed:\n" + "\tc=category, l=level, F=file, L=line number, f=function, m=msg\n" + "\tor 'default', or 'all' for all\n" + "log rec <category> <level> <file> <line> <func> <message> - " + "output a log record"); + +U_BOOT_CMD_WITH_SUBCMDS(log, "log system", log_help_text, + U_BOOT_SUBCMD_MKENT(level, 2, 1, do_log_level), + U_BOOT_SUBCMD_MKENT(categories, 1, 1, do_log_categories), + U_BOOT_SUBCMD_MKENT(drivers, 1, 1, do_log_drivers), + U_BOOT_SUBCMD_MKENT(filter-list, 3, 1, do_log_filter_list), + U_BOOT_SUBCMD_MKENT(filter-add, CONFIG_SYS_MAXARGS, 1, + do_log_filter_add), + U_BOOT_SUBCMD_MKENT(filter-remove, 4, 1, do_log_filter_remove), + U_BOOT_SUBCMD_MKENT(format, 2, 1, do_log_format), + U_BOOT_SUBCMD_MKENT(rec, 7, 1, do_log_rec), +); diff --git a/cmd/lsblk.c b/cmd/lsblk.c new file mode 100644 index 00000000000..7c00bfdc7a0 --- /dev/null +++ b/cmd/lsblk.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2020 + * Niel Fourie, DENX Software Engineering, lusus@denx.de. + */ + +#include <blk.h> +#include <command.h> +#include <dm.h> + +static int do_lsblk(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + struct driver *d = ll_entry_start(struct driver, driver); + const int n_ents = ll_entry_count(struct driver, driver); + struct driver *entry; + struct udevice *udev; + struct uclass *uc; + struct blk_desc *desc; + int ret, i; + + ret = uclass_get(UCLASS_BLK, &uc); + if (ret) { + puts("Could not get BLK uclass.\n"); + return CMD_RET_FAILURE; + } + puts("Block Driver Devices\n"); + puts("-----------------------------\n"); + for (entry = d; entry < d + n_ents; entry++) { + if (entry->id != UCLASS_BLK) + continue; + i = 0; + printf("%-20.20s", entry->name); + uclass_foreach_dev(udev, uc) { + if (udev->driver != entry) + continue; + desc = dev_get_uclass_plat(udev); + printf("%c %s %u", i ? ',' : ':', + blk_get_uclass_name(desc->uclass_id), + desc->devnum); + i++; + } + if (!i) + puts(": <none>"); + puts("\n"); + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(lsblk, 1, 0, do_lsblk, "list block drivers and devices", + "- display list of block device drivers and attached block devices" +); diff --git a/cmd/lwip/Makefile b/cmd/lwip/Makefile new file mode 100644 index 00000000000..a7f8976af3f --- /dev/null +++ b/cmd/lwip/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_CMD_DHCP) += dhcp.o +obj-$(CONFIG_CMD_DNS) += dns.o +obj-$(CONFIG_CMD_PING) += ping.o +obj-$(CONFIG_CMD_SNTP) += sntp.o +obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o +obj-$(CONFIG_CMD_WGET) += wget.o diff --git a/cmd/lwip/dhcp.c b/cmd/lwip/dhcp.c new file mode 100644 index 00000000000..3894d71f654 --- /dev/null +++ b/cmd/lwip/dhcp.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2024-2025 Linaro Ltd. */ + +#include <command.h> +#include <net.h> + +U_BOOT_CMD(dhcp, 3, 1, do_dhcp, + "boot image via network using DHCP/TFTP protocol", + "[loadAddress] [[hostIPaddr:]bootfilename]"); diff --git a/cmd/lwip/dns.c b/cmd/lwip/dns.c new file mode 100644 index 00000000000..3eb698b3f82 --- /dev/null +++ b/cmd/lwip/dns.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2024 Linaro Ltd. */ + +#include <command.h> +#include <net.h> + +U_BOOT_CMD(dns, 3, 1, do_dns, "lookup the IP of a hostname", + "hostname [envvar]"); diff --git a/cmd/lwip/ping.c b/cmd/lwip/ping.c new file mode 100644 index 00000000000..6d090fc530d --- /dev/null +++ b/cmd/lwip/ping.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2024 Linaro Ltd. */ + +#include <command.h> +#include <console.h> +#include <dm/device.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <lwip/icmp.h> +#include <lwip/inet_chksum.h> +#include <lwip/raw.h> +#include <lwip/timeouts.h> +#include <net.h> +#include <time.h> + +U_BOOT_CMD(ping, 2, 1, do_ping, "send ICMP ECHO_REQUEST to network host", + "pingAddressOrHostName"); + +#define PING_DELAY_MS 1000 +#define PING_COUNT 5 +/* Ping identifier - must fit on a u16_t */ +#define PING_ID 0xAFAF + +struct ping_ctx { + ip_addr_t target; + struct raw_pcb *pcb; + struct icmp_echo_hdr *iecho; + uint16_t seq_num; + bool alive; +}; + +static u8_t ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, + const ip_addr_t *addr) +{ + struct ping_ctx *ctx = arg; + struct icmp_echo_hdr *iecho = ctx->iecho; + + if (addr->addr != ctx->target.addr) + return 0; + + if ((p->tot_len >= (IP_HLEN + sizeof(struct icmp_echo_hdr))) && + pbuf_remove_header(p, IP_HLEN) == 0) { + iecho = (struct icmp_echo_hdr *)p->payload; + + if (iecho->id == PING_ID && + iecho->seqno == lwip_htons(ctx->seq_num)) { + ctx->alive = true; + printf("host %s is alive\n", ipaddr_ntoa(addr)); + pbuf_free(p); + return 1; /* eat the packet */ + } + /* not eaten, restore original packet */ + pbuf_add_header(p, IP_HLEN); + } + + return 0; /* don't eat the packet */ +} + +static int ping_raw_init(struct ping_ctx *ctx) +{ + ctx->pcb = raw_new(IP_PROTO_ICMP); + if (!ctx->pcb) + return -ENOMEM; + + raw_recv(ctx->pcb, ping_recv, ctx); + raw_bind(ctx->pcb, IP_ADDR_ANY); + + return 0; +} + +static void ping_raw_stop(struct ping_ctx *ctx) +{ + if (ctx->pcb) + raw_remove(ctx->pcb); +} + +static void ping_prepare_echo(struct ping_ctx *ctx) +{ + struct icmp_echo_hdr *iecho = ctx->iecho; + + ICMPH_TYPE_SET(iecho, ICMP_ECHO); + ICMPH_CODE_SET(iecho, 0); + iecho->chksum = 0; + iecho->id = PING_ID; + iecho->seqno = lwip_htons(ctx->seq_num); + + iecho->chksum = inet_chksum(iecho, sizeof(*iecho)); +} + +static void ping_send_icmp(struct ping_ctx *ctx) +{ + struct pbuf *p; + size_t ping_size = sizeof(struct icmp_echo_hdr); + + p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM); + if (!p) + return; + + if (p->len == p->tot_len && !p->next) { + ctx->iecho = (struct icmp_echo_hdr *)p->payload; + ping_prepare_echo(ctx); + raw_sendto(ctx->pcb, p, &ctx->target); + } + + pbuf_free(p); +} + +static void ping_send(void *arg) +{ + struct ping_ctx *ctx = arg; + + ctx->seq_num++; + if (ctx->seq_num <= PING_COUNT) { + ping_send_icmp(ctx); + sys_timeout(PING_DELAY_MS, ping_send, ctx); + } +} + +static int ping_loop(struct udevice *udev, const ip_addr_t *addr) +{ + struct ping_ctx ctx = {}; + struct netif *netif; + int ret; + + netif = net_lwip_new_netif(udev); + if (!netif) + return -ENODEV; + + printf("Using %s device\n", udev->name); + + ret = ping_raw_init(&ctx); + if (ret < 0) { + net_lwip_remove_netif(netif); + return ret; + } + + ctx.target = *addr; + + ping_send(&ctx); + + do { + net_lwip_rx(udev, netif); + if (ctx.alive) + break; + if (ctrlc()) { + printf("\nAbort\n"); + break; + } + } while (ctx.seq_num <= PING_COUNT); + + sys_untimeout(ping_send, &ctx); + ping_raw_stop(&ctx); + + net_lwip_remove_netif(netif); + + if (ctx.alive) + return 0; + + printf("ping failed; host %s is not alive\n", ipaddr_ntoa(addr)); + return -1; +} + +int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + ip_addr_t addr; + + if (argc < 2) + return CMD_RET_USAGE; + + if (net_lwip_dns_resolve(argv[1], &addr)) + return CMD_RET_USAGE; + + net_try_count = 1; +restart: + if (net_lwip_eth_start() < 0 || ping_loop(eth_get_dev(), &addr) < 0) { + if (net_start_again() == 0) + goto restart; + else + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} diff --git a/cmd/lwip/sntp.c b/cmd/lwip/sntp.c new file mode 100644 index 00000000000..608345c873b --- /dev/null +++ b/cmd/lwip/sntp.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2025 Linaro Ltd. */ + +#include <command.h> +#include <console.h> +#include <dm/device.h> +#include <env.h> +#include <lwip/apps/sntp.h> +#include <lwip/timeouts.h> +#include <net.h> + +U_BOOT_CMD(sntp, 2, 1, do_sntp, "synchronize RTC via network", + "[NTPServerNameOrIp]"); + +#define SNTP_TIMEOUT 10000 + +static enum done_state { + NOT_DONE = 0, + SUCCESS, + ABORTED, + TIMED_OUT +} sntp_state; + +static void no_response(void *arg) +{ + sntp_state = TIMED_OUT; +} + +/* Called by lwIP via the SNTP_SET_SYSTEM_TIME() macro */ +void sntp_set_system_time(uint32_t seconds) +{ + char *toff; + int net_ntp_time_offset = 0; + + toff = env_get("timeoffset"); + if (toff) + net_ntp_time_offset = simple_strtol(toff, NULL, 10); + + net_sntp_set_rtc(seconds + net_ntp_time_offset); + sntp_state = SUCCESS; +} + +static bool ntp_server_known(void) +{ + int i; + + for (i = 0; i < SNTP_MAX_SERVERS; i++) { + const ip_addr_t *ip = sntp_getserver(i); + + if (ip && ip->addr) + return true; + } + + return false; +} + +static int sntp_loop(struct udevice *udev, ip_addr_t *srvip) +{ + struct netif *netif; + + netif = net_lwip_new_netif(udev); + if (!netif) + return -1; + + sntp_state = NOT_DONE; + + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_servermode_dhcp(CONFIG_IS_ENABLED(CMD_DHCP)); + if (srvip) { + sntp_setserver(0, srvip); + } else { + if (!ntp_server_known()) { + log_err("error: ntpserverip not set\n"); + return -1; + } + } + sntp_init(); + + sys_timeout(SNTP_TIMEOUT, no_response, NULL); + while (sntp_state == NOT_DONE) { + net_lwip_rx(udev, netif); + if (ctrlc()) { + printf("\nAbort\n"); + sntp_state = ABORTED; + break; + } + } + sys_untimeout(no_response, NULL); + + sntp_stop(); + net_lwip_remove_netif(netif); + + if (sntp_state == SUCCESS) + return 0; + + return -1; +} + +int do_sntp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + ip_addr_t *srvip; + char *server; + ip_addr_t ipaddr; + + switch (argc) { + case 1: + srvip = NULL; + server = env_get("ntpserverip"); + if (server) { + if (!ipaddr_aton(server, &ipaddr)) { + printf("ntpserverip is invalid\n"); + return CMD_RET_FAILURE; + } + srvip = &ipaddr; + } + break; + case 2: + if (net_lwip_dns_resolve(argv[1], &ipaddr)) + return CMD_RET_FAILURE; + srvip = &ipaddr; + break; + default: + return CMD_RET_USAGE; + } + + if (net_lwip_eth_start() < 0) + return CMD_RET_FAILURE; + + if (sntp_loop(eth_get_dev(), srvip) < 0) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} diff --git a/cmd/lwip/tftp.c b/cmd/lwip/tftp.c new file mode 100644 index 00000000000..6bb7a3733a2 --- /dev/null +++ b/cmd/lwip/tftp.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2024-2025 Linaro Ltd. */ + +#include <command.h> +#include <net.h> + +U_BOOT_CMD(tftpboot, 3, 0, do_tftpb, + "boot image via network using TFTP protocol", + "[loadAddress] [[hostIPaddr:]bootfilename]"); diff --git a/cmd/lwip/wget.c b/cmd/lwip/wget.c new file mode 100644 index 00000000000..fc9bc11cd83 --- /dev/null +++ b/cmd/lwip/wget.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2024-2025 Linaro Ltd. */ + +#include <command.h> +#include <env.h> +#include <image.h> +#include <net.h> +#include <lwip/altcp_tls.h> + +U_BOOT_CMD(wget, 4, 1, do_wget, + "boot image via network using HTTP/HTTPS protocol" +#if defined(CONFIG_WGET_CACERT) + "\nwget cacert - configure wget root certificates" +#endif + , + "[loadAddress] url\n" + "wget [loadAddress] [host:]path\n" + " - load file" +#if defined(CONFIG_WGET_CACERT) + "\nwget cacert <address> <length>\n" + " - provide CA certificates (0 0 to remove current)" + "\nwget cacert none|optional|required\n" + " - set server certificate verification mode (default: optional)" +#if defined(CONFIG_WGET_BUILTIN_CACERT) + "\nwget cacert builtin\n" + " - use the builtin CA certificates" +#endif +#endif +); + +#if CONFIG_IS_ENABLED(WGET_CACERT) || CONFIG_IS_ENABLED(WGET_BUILTIN_CACERT) +char *cacert; +size_t cacert_size; +enum auth_mode cacert_auth_mode = AUTH_OPTIONAL; + +#if CONFIG_IS_ENABLED(WGET_BUILTIN_CACERT) +extern const char builtin_cacert[]; +extern const size_t builtin_cacert_size; +bool cacert_initialized; +#endif + +static int _set_cacert(const void *addr, size_t sz) +{ + mbedtls_x509_crt crt; + void *p; + int ret; + + if (cacert) + free(cacert); + + if (!addr) { + cacert = NULL; + cacert_size = 0; + return CMD_RET_SUCCESS; + } + + p = malloc(sz); + if (!p) + return CMD_RET_FAILURE; + cacert = p; + cacert_size = sz; + + memcpy(cacert, (void *)addr, sz); + + mbedtls_x509_crt_init(&crt); + ret = mbedtls_x509_crt_parse(&crt, cacert, cacert_size); + if (ret) { + if (!wget_info->silent) + printf("Could not parse certificates (%d)\n", ret); + free(cacert); + cacert = NULL; + cacert_size = 0; + return CMD_RET_FAILURE; + } + +#if CONFIG_IS_ENABLED(WGET_BUILTIN_CACERT) + cacert_initialized = true; +#endif + return CMD_RET_SUCCESS; +} + +#if CONFIG_IS_ENABLED(WGET_BUILTIN_CACERT) +int set_cacert_builtin(void) +{ + cacert_auth_mode = AUTH_REQUIRED; + return _set_cacert(builtin_cacert, builtin_cacert_size); +} +#endif +#endif /* CONFIG_WGET_CACERT || CONFIG_WGET_BUILTIN_CACERT */ + +#if CONFIG_IS_ENABLED(WGET_CACERT) +static int set_auth(enum auth_mode auth) +{ + cacert_auth_mode = auth; + + return CMD_RET_SUCCESS; +} + +static int set_cacert(char * const saddr, char * const ssz) +{ + ulong addr, sz; + + addr = hextoul(saddr, NULL); + sz = hextoul(ssz, NULL); + + return _set_cacert((void *)addr, sz); +} +#endif + +/* + * Legacy syntax support + * Convert [<server_name_or_ip>:]filename into a URL if needed + */ +static int parse_legacy_arg(char *arg, char *nurl, size_t rem) +{ + char *p = nurl; + size_t n; + char *col = strchr(arg, ':'); + char *env; + char *server; + char *path; + + if (strstr(arg, "http") == arg) { + n = snprintf(nurl, rem, "%s", arg); + if (n < 0 || n > rem) + return -1; + return 0; + } + + n = snprintf(p, rem, "%s", "http://"); + if (n < 0 || n > rem) + return -1; + p += n; + rem -= n; + + if (col) { + n = col - arg; + server = arg; + path = col + 1; + } else { + env = env_get("httpserverip"); + if (!env) + env = env_get("serverip"); + if (!env) { + log_err("error: httpserver/serverip has to be set\n"); + return -1; + } + n = strlen(env); + server = env; + path = arg; + } + + if (rem < n) + return -1; + strncpy(p, server, n); + p += n; + rem -= n; + if (rem < 1) + return -1; + *p = '/'; + p++; + rem--; + n = strlen(path); + if (rem < n) + return -1; + strncpy(p, path, n); + p += n; + rem -= n; + if (rem < 1) + return -1; + *p = '\0'; + + return 0; +} + +int do_wget(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + char *end; + char *url; + ulong dst_addr; + char nurl[1024]; + +#if CONFIG_IS_ENABLED(WGET_CACERT) + if (argc == 4 && !strncmp(argv[1], "cacert", strlen("cacert"))) + return set_cacert(argv[2], argv[3]); + if (argc == 3 && !strncmp(argv[1], "cacert", strlen("cacert"))) { +#if CONFIG_IS_ENABLED(WGET_BUILTIN_CACERT) + if (!strncmp(argv[2], "builtin", strlen("builtin"))) + return set_cacert_builtin(); +#endif + if (!strncmp(argv[2], "none", strlen("none"))) + return set_auth(AUTH_NONE); + if (!strncmp(argv[2], "optional", strlen("optional"))) + return set_auth(AUTH_OPTIONAL); + if (!strncmp(argv[2], "required", strlen("required"))) + return set_auth(AUTH_REQUIRED); + return CMD_RET_USAGE; + } +#endif + + if (argc < 2 || argc > 3) + return CMD_RET_USAGE; + + dst_addr = hextoul(argv[1], &end); + if (end == (argv[1] + strlen(argv[1]))) { + if (argc < 3) + return CMD_RET_USAGE; + url = argv[2]; + } else { + dst_addr = image_load_addr; + url = argv[1]; + } + + if (parse_legacy_arg(url, nurl, sizeof(nurl))) + return CMD_RET_FAILURE; + + wget_info = &default_wget_info; + if (wget_do_request(dst_addr, nurl)) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} diff --git a/cmd/lzmadec.c b/cmd/lzmadec.c new file mode 100644 index 00000000000..c40b96941b4 --- /dev/null +++ b/cmd/lzmadec.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2013 Patrice Bouchand <pbfwdlist_gmail_com> + * lzma uncompress command in Uboot + * + * made from existing cmd_unzip.c file of Uboot + * + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <env.h> +#include <mapmem.h> +#include <vsprintf.h> +#include <asm/io.h> + +#include <lzma/LzmaTools.h> + +static int do_lzmadec(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned long src, dst; + SizeT src_len = ~0UL, dst_len = ~0UL; + int ret; + + switch (argc) { + case 4: + dst_len = hextoul(argv[3], NULL); + /* fall through */ + case 3: + src = hextoul(argv[1], NULL); + dst = hextoul(argv[2], NULL); + break; + default: + return CMD_RET_USAGE; + } + + ret = lzmaBuffToBuffDecompress(map_sysmem(dst, dst_len), &src_len, + map_sysmem(src, 0), dst_len); + + if (ret != SZ_OK) + return 1; + printf("Uncompressed size: %ld = %#lX\n", (ulong)src_len, + (ulong)src_len); + env_set_hex("filesize", src_len); + + return 0; +} + +U_BOOT_CMD( + lzmadec, 4, 1, do_lzmadec, + "lzma uncompress a memory region", + "srcaddr dstaddr [dstsize]" +); diff --git a/cmd/mbr.c b/cmd/mbr.c new file mode 100644 index 00000000000..7fe6c9e103a --- /dev/null +++ b/cmd/mbr.c @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cmd_mbr.c -- MBR (Master Boot Record) handling command + * + * Copyright (C) 2020 Samsung Electronics + * author: Marek Szyprowski <m.szyprowski@samsung.com> + * + * based on the gpt command. + */ + +#include <blk.h> +#include <command.h> +#include <env.h> +#include <malloc.h> +#include <part.h> +#include <vsprintf.h> + +/** + * extract_val() - Extract a value from the key=value pair list + * @str: pointer to string with key=values pairs + * @key: pointer to the key to search for + * + * The list of parameters is come separated, only a value for + * the given key is returend. + * + * Function allocates memory for the value, remember to free! + * + * Return: Pointer to allocated string with the value. + */ +static char *extract_val(const char *str, const char *key) +{ + char *v, *k; + char *s, *strcopy; + char *new = NULL; + + strcopy = strdup(str); + if (strcopy == NULL) + return NULL; + + s = strcopy; + while (s) { + v = strsep(&s, ","); + if (!v) + break; + k = strsep(&v, "="); + if (!k) + break; + if (strcmp(k, key) == 0) { + new = strdup(v); + break; + } + } + + free(strcopy); + + return new; +} + +/** + * found_key() - Search for a key without a value in the parameter list + * @str: pointer to string with key + * @key: pointer to the key to search for + * + * The list of parameters is come separated. + * + * Return: True if key has been found. + */ +static bool found_key(const char *str, const char *key) +{ + char *k; + char *s, *strcopy; + bool result = false; + + strcopy = strdup(str); + if (!strcopy) + return NULL; + + s = strcopy; + while (s) { + k = strsep(&s, ","); + if (!k) + break; + if (strcmp(k, key) == 0) { + result = true; + break; + } + } + + free(strcopy); + + return result; +} + +static int str_to_partitions(const char *str_part, int blksz, + unsigned long *disk_uuid, struct disk_partition **partitions, + int *parts_count) +{ + char *tok, *str, *s; + int i; + char *val, *p; + int p_count; + struct disk_partition *parts; + int errno = 0; + uint64_t size_ll, start_ll; + + if (str_part == NULL) + return -1; + + str = strdup(str_part); + if (str == NULL) + return -ENOMEM; + + /* extract disk guid */ + s = str; + val = extract_val(str, "uuid_disk"); + if (val) { + val = strsep(&val, ";"); + p = val; + *disk_uuid = ustrtoull(p, &p, 0); + free(val); + /* Move s to first partition */ + strsep(&s, ";"); + } + if (s == NULL) { + printf("Error: is the partitions string NULL-terminated?\n"); + return -EINVAL; + } + + /* remove the optional semicolon at the end of the string */ + i = strlen(s) - 1; + if (s[i] == ';') + s[i] = '\0'; + + /* calculate expected number of partitions */ + p_count = 1; + p = s; + while (*p) { + if (*p++ == ';') + p_count++; + } + + /* allocate memory for partitions */ + parts = calloc(sizeof(struct disk_partition), p_count); + if (parts == NULL) + return -ENOMEM; + + /* retrieve partitions data from string */ + for (i = 0; i < p_count; i++) { + tok = strsep(&s, ";"); + + if (tok == NULL) + break; + + /* size */ + val = extract_val(tok, "size"); + if (!val) { /* 'size' is mandatory */ + errno = -4; + goto err; + } + p = val; + if ((strcmp(p, "-") == 0)) { + /* auto extend the size */ + parts[i].size = 0; + } else { + size_ll = ustrtoull(p, &p, 0); + parts[i].size = size_ll / blksz; + } + free(val); + + /* start address */ + val = extract_val(tok, "start"); + if (val) { /* start address is optional */ + p = val; + start_ll = ustrtoull(p, &p, 0); + parts[i].start = start_ll / blksz; + free(val); + } + + /* system id */ + val = extract_val(tok, "id"); + if (!val) { /* '' is mandatory */ + errno = -4; + goto err; + } + p = val; + parts[i].sys_ind = ustrtoul(p, &p, 0); + free(val); + + /* bootable */ + if (found_key(tok, "bootable")) + parts[i].bootable = PART_BOOTABLE; + } + + *parts_count = p_count; + *partitions = parts; + free(str); + + return 0; +err: + free(str); + free(parts); + + return errno; +} + +static int do_write_mbr(struct blk_desc *dev, const char *str) +{ + unsigned long disk_uuid = 0; + struct disk_partition *partitions; + int blksz = dev->blksz; + int count; + + if (str_to_partitions(str, blksz, &disk_uuid, &partitions, &count)) { + printf("MBR: failed to setup partitions from \"%s\"\n", str); + return -1; + } + + if (layout_mbr_partitions(partitions, count, dev->lba)) { + printf("MBR: failed to layout partitions on the device\n"); + free(partitions); + return -1; + } + + if (write_mbr_partitions(dev, partitions, count, disk_uuid)) { + printf("MBR: failed to write partitions to the device\n"); + free(partitions); + return -1; + } + + return 0; +} + +static int do_verify_mbr(struct blk_desc *dev, const char *str) +{ + unsigned long disk_uuid = 0; + struct disk_partition *partitions; + int blksz = dev->blksz; + int count, i, ret = 1; + + if (str_to_partitions(str, blksz, &disk_uuid, &partitions, &count)) { + printf("MBR: failed to setup partitions from \"%s\"\n", str); + return -1; + } + + for (i = 0; i < count; i++) { + struct disk_partition p; + + if (part_get_info_by_type(dev, i + 1, PART_TYPE_DOS, &p)) + goto fail; + + if ((partitions[i].size && p.size != partitions[i].size) || + (partitions[i].start && p.start != partitions[i].start) || + p.sys_ind != partitions[i].sys_ind) + goto fail; + } + ret = 0; +fail: + free(partitions); + return ret; +} + +static int do_mbr(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + const char *parts = NULL; + int ret = CMD_RET_SUCCESS; + int dev = 0; + char *ep; + struct blk_desc *blk_dev_desc = NULL; + + if (argc != 4 && argc != 5) + return CMD_RET_USAGE; + + dev = (int)dectoul(argv[3], &ep); + if (!ep || ep[0] != '\0') { + printf("'%s' is not a number\n", argv[3]); + return CMD_RET_USAGE; + } + blk_dev_desc = blk_get_dev(argv[2], dev); + if (!blk_dev_desc) { + printf("%s: %s dev %d NOT available\n", + __func__, argv[2], dev); + return CMD_RET_FAILURE; + } + + if ((strcmp(argv[1], "write") == 0)) { + parts = (argc == 5) ? argv[4] : env_get("mbr_parts"); + printf("MBR: write "); + ret = do_write_mbr(blk_dev_desc, parts); + } else if ((strcmp(argv[1], "verify") == 0)) { + printf("MBR: verify "); + parts = (argc == 5) ? argv[4] : env_get("mbr_parts"); + ret = do_verify_mbr(blk_dev_desc, parts); + } else { + return CMD_RET_USAGE; + } + + if (ret) { + printf("error!\n"); + return CMD_RET_FAILURE; + } + + printf("success!\n"); + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(mbr, CONFIG_SYS_MAXARGS, 1, do_mbr, + "MBR (Master Boot Record)", + "<command> <interface> <dev> <partitions_list>\n" + " - MBR partition table restoration utility\n" + " Restore or check partition information on a device connected\n" + " to the given block interface\n" + " Example usage:\n" + " mbr write mmc 0 [\"${mbr_parts}\"]\n" + " mbr verify mmc 0 [\"${partitions}\"]\n" +); diff --git a/cmd/md5sum.c b/cmd/md5sum.c new file mode 100644 index 00000000000..ded3f9e1831 --- /dev/null +++ b/cmd/md5sum.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2011 + * Joe Hershberger, National Instruments, joe.hershberger@ni.com + * + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <env.h> +#include <image.h> +#include <hash.h> +#include <mapmem.h> +#include <u-boot/md5.h> +#include <asm/io.h> + +static int do_md5sum(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int flags = HASH_FLAG_ENV; + int ac; + char *const *av; + + if (argc < 3) + return CMD_RET_USAGE; + + av = argv + 1; + ac = argc - 1; + if (IS_ENABLED(CONFIG_MD5SUM_VERIFY) && strcmp(*av, "-v") == 0) { + flags |= HASH_FLAG_VERIFY; + av++; + ac--; + } + + return hash_command("md5", flags, cmdtp, flag, ac, av); +} + +#if IS_ENABLED(CONFIG_MD5SUM_VERIFY) +U_BOOT_CMD( + md5sum, 5, 1, do_md5sum, + "compute MD5 message digest", + "address count [[*]sum]\n" + " - compute MD5 message digest [save to sum]\n" + "md5sum -v address count [*]sum\n" + " - verify md5sum of memory area" +); +#else +U_BOOT_CMD( + md5sum, 4, 1, do_md5sum, + "compute MD5 message digest", + "address count [[*]sum]\n" + " - compute MD5 message digest [save to sum]" +); +#endif /* IS_ENABLED(CONFIG_MD5SUM_VERIFY) */ diff --git a/cmd/mdio.c b/cmd/mdio.c new file mode 100644 index 00000000000..c0a87087d31 --- /dev/null +++ b/cmd/mdio.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2011 Freescale Semiconductor, Inc + * Andy Fleming + */ + +/* + * MDIO Commands + */ + +#include <command.h> +#include <dm.h> +#include <miiphy.h> +#include <phy.h> + +static char last_op[2]; +static uint last_data; +static uint last_addr_lo; +static uint last_addr_hi; +static uint last_devad_lo; +static uint last_devad_hi; +static uint last_reg_lo; +static uint last_reg_hi; + +static int extract_range(char *input, int *plo, int *phi) +{ + char *end; + *plo = simple_strtol(input, &end, 16); + if (end == input) + return -1; + + if ((*end == '-') && *(++end)) + *phi = simple_strtol(end, NULL, 16); + else if (*end == '\0') + *phi = *plo; + else + return -1; + + return 0; +} + +static int mdio_write_ranges(struct mii_dev *bus, + int addrlo, + int addrhi, int devadlo, int devadhi, + int reglo, int reghi, unsigned short data, + int extended) +{ + struct phy_device *phydev; + int addr, devad, reg; + int err = 0; + + for (addr = addrlo; addr <= addrhi; addr++) { + phydev = bus->phymap[addr]; + + for (devad = devadlo; devad <= devadhi; devad++) { + for (reg = reglo; reg <= reghi; reg++) { + if (!phydev) + err = bus->write(bus, addr, devad, + reg, data); + else if (!extended) + err = phy_write_mmd(phydev, devad, + reg, data); + else + err = phydev->drv->writeext(phydev, + addr, devad, reg, data); + + if (err) + goto err_out; + } + } + } + +err_out: + return err; +} + +static int mdio_read_ranges(struct mii_dev *bus, + int addrlo, + int addrhi, int devadlo, int devadhi, + int reglo, int reghi, int extended) +{ + int addr, devad, reg; + struct phy_device *phydev; + + printf("Reading from bus %s\n", bus->name); + for (addr = addrlo; addr <= addrhi; addr++) { + phydev = bus->phymap[addr]; + printf("PHY at address %x:\n", addr); + + for (devad = devadlo; devad <= devadhi; devad++) { + for (reg = reglo; reg <= reghi; reg++) { + int val; + + if (!phydev) + val = bus->read(bus, addr, devad, reg); + else if (!extended) + val = phy_read_mmd(phydev, devad, reg); + else + val = phydev->drv->readext(phydev, addr, + devad, reg); + + if (val < 0) { + printf("Error\n"); + + return val; + } + + if (devad >= 0) + printf("%d.", devad); + + printf("%d - 0x%x\n", reg, val & 0xffff); + } + } + } + + return 0; +} + +/* The register will be in the form [a[-b].]x[-y] */ +static int extract_reg_range(char *input, int *devadlo, int *devadhi, + int *reglo, int *reghi) +{ + char *regstr; + + /* use strrchr to find the last string after a '.' */ + regstr = strrchr(input, '.'); + + /* If it exists, extract the devad(s) */ + if (regstr) { + char devadstr[32]; + + strncpy(devadstr, input, regstr - input); + devadstr[regstr - input] = '\0'; + + if (extract_range(devadstr, devadlo, devadhi)) + return -1; + + regstr++; + } else { + /* Otherwise, we have no devad, and we just got regs */ + *devadlo = *devadhi = MDIO_DEVAD_NONE; + + regstr = input; + } + + return extract_range(regstr, reglo, reghi); +} + +static int extract_phy_range(char *const argv[], int argc, struct mii_dev **bus, + struct phy_device **phydev, + int *addrlo, int *addrhi) +{ + struct phy_device *dev = *phydev; + + if ((argc < 1) || (argc > 2)) + return -1; + + /* If there are two arguments, it's busname addr */ + if (argc == 2) { + *bus = miiphy_get_dev_by_name(argv[0]); + + if (!*bus) + return -1; + + return extract_range(argv[1], addrlo, addrhi); + } + + /* It must be one argument, here */ + + /* + * This argument can be one of two things: + * 1) Ethernet device name + * 2) Just an address (use the previously-used bus) + * + * We check all buses for a PHY which is connected to an ethernet + * device by the given name. If none are found, we call + * extract_range() on the string, and see if it's an address range. + */ + dev = mdio_phydev_for_ethname(argv[0]); + + if (dev) { + *addrlo = *addrhi = dev->addr; + *bus = dev->bus; + + return 0; + } + + /* It's an address or nothing useful */ + return extract_range(argv[0], addrlo, addrhi); +} + +/* ---------------------------------------------------------------- */ +static int do_mdio(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char op[2]; + int addrlo, addrhi, reglo, reghi, devadlo, devadhi; + unsigned short data; + int pos = argc - 1; + struct mii_dev *bus; + struct phy_device *phydev = NULL; + int extended = 0; + + if (argc < 2) + return CMD_RET_USAGE; + +#ifdef CONFIG_DM_MDIO + /* probe DM MII device before any operation so they are all accesible */ + dm_mdio_probe_devices(); +#endif + + /* + * We use the last specified parameters, unless new ones are + * entered. + */ + op[0] = argv[1][0]; + addrlo = last_addr_lo; + addrhi = last_addr_hi; + devadlo = last_devad_lo; + devadhi = last_devad_hi; + reglo = last_reg_lo; + reghi = last_reg_hi; + data = last_data; + + bus = mdio_get_current_dev(); + + if (flag & CMD_FLAG_REPEAT) + op[0] = last_op[0]; + + if (strlen(argv[1]) > 1) { + op[1] = argv[1][1]; + if (op[1] == 'x') { + phydev = mdio_phydev_for_ethname(argv[2]); + + if (phydev) { + addrlo = phydev->addr; + addrhi = addrlo; + bus = phydev->bus; + extended = 1; + } else { + return CMD_RET_FAILURE; + } + + if (!phydev->drv || + (!phydev->drv->writeext && (op[0] == 'w')) || + (!phydev->drv->readext && (op[0] == 'r'))) { + puts("PHY does not have extended functions\n"); + return CMD_RET_FAILURE; + } + } + } + + switch (op[0]) { + case 'w': + if (pos > 1) + data = hextoul(argv[pos--], NULL); + /* Intentional fall-through - Get reg for read and write */ + case 'r': + if (pos > 1) + if (extract_reg_range(argv[pos--], &devadlo, &devadhi, + ®lo, ®hi)) + return CMD_RET_FAILURE; + /* Intentional fall-through - Get phy for all commands */ + default: + if (pos > 1) + if (extract_phy_range(&argv[2], pos - 1, &bus, + &phydev, &addrlo, &addrhi)) + return CMD_RET_FAILURE; + + break; + } + + if (!bus) { + puts("No MDIO bus found\n"); + return CMD_RET_FAILURE; + } + + if (op[0] == 'l') { + mdio_list_devices(); + + return 0; + } + + /* Save the chosen bus */ + miiphy_set_current_dev(bus->name); + + switch (op[0]) { + case 'w': + mdio_write_ranges(bus, addrlo, addrhi, devadlo, devadhi, + reglo, reghi, data, extended); + break; + + case 'r': + mdio_read_ranges(bus, addrlo, addrhi, devadlo, devadhi, + reglo, reghi, extended); + break; + } + + /* + * Save the parameters for repeats. + */ + last_op[0] = op[0]; + last_addr_lo = addrlo; + last_addr_hi = addrhi; + last_devad_lo = devadlo; + last_devad_hi = devadhi; + last_reg_lo = reglo; + last_reg_hi = reghi; + last_data = data; + + return 0; +} + +/***************************************************/ + +U_BOOT_CMD( + mdio, 6, 1, do_mdio, + "MDIO utility commands", + "list - List MDIO buses\n" + "mdio read <phydev> [<devad>.]<reg> - " + "read PHY's register at <devad>.<reg>\n" + "mdio write <phydev> [<devad>.]<reg> <data> - " + "write PHY's register at <devad>.<reg>\n" + "mdio rx <phydev> [<devad>.]<reg> - " + "read PHY's extended register at <devad>.<reg>\n" + "mdio wx <phydev> [<devad>.]<reg> <data> - " + "write PHY's extended register at <devad>.<reg>\n" + "<phydev> may be:\n" + " <busname> <addr>\n" + " <addr>\n" + " <eth name>\n" + "<addr> <devad>, and <reg> may be ranges, e.g. 1-5.4-0x1f.\n" +); diff --git a/cmd/mem.c b/cmd/mem.c new file mode 100644 index 00000000000..d5d7ca2790b --- /dev/null +++ b/cmd/mem.c @@ -0,0 +1,1436 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +/* + * Memory Functions + * + * Copied from FADS ROM, Dan Malek (dmalek@jlc.net) + */ + +#include <console.h> +#include <bootretry.h> +#include <cli.h> +#include <command.h> +#include <compiler.h> +#include <console.h> +#include <display_options.h> +#include <env.h> +#ifdef CONFIG_MTD_NOR_FLASH +#include <flash.h> +#endif +#include <hash.h> +#include <log.h> +#include <mapmem.h> +#include <rand.h> +#include <time.h> +#include <watchdog.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <linux/bitops.h> +#include <linux/compiler.h> +#include <linux/ctype.h> +#include <linux/delay.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Create a compile-time value */ +#if MEM_SUPPORT_64BIT_DATA +#define HELP_Q ", .q" +#else +#define HELP_Q "" +#endif + +static int mod_mem(struct cmd_tbl *, int, int, int, char * const []); + +/* Display values from last command. + * Memory modify remembered values are different from display memory. + */ +static ulong dp_last_addr, dp_last_size; +static ulong dp_last_length = 0x40; +static ulong mm_last_addr, mm_last_size; + +static ulong base_address = 0; +#ifdef CONFIG_CMD_MEM_SEARCH +static ulong dp_last_ms_length; +static u8 search_buf[64]; +static uint search_len; +#endif + +/* Memory Display + * + * Syntax: + * md{.b, .w, .l, .q} {addr} {len} + */ +#define DISP_LINE_LEN 16 +static int do_mem_md(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr, length, bytes; + const void *buf; + int size; + int rc = 0; + + /* We use the last specified parameters, unless new ones are + * entered. + */ + addr = dp_last_addr; + size = dp_last_size; + length = dp_last_length; + + if (argc < 2) + return CMD_RET_USAGE; + + if ((flag & CMD_FLAG_REPEAT) == 0) { + /* New command specified. Check for a size specification. + * Defaults to long if no or incorrect specification. + */ + if ((size = cmd_get_data_size(argv[0], 4)) < 0) + return 1; + + /* Address is specified since argc > 1 + */ + addr = hextoul(argv[1], NULL); + addr += base_address; + + /* If another parameter, it is the length to display. + * Length is the number of objects, not number of bytes. + */ + if (argc > 2) + length = hextoul(argv[2], NULL); + } + + bytes = size * length; + buf = map_sysmem(addr, bytes); + + /* Print the lines. */ + print_buffer(addr, buf, size, length, DISP_LINE_LEN / size); + addr += bytes; + unmap_sysmem(buf); + + dp_last_addr = addr; + dp_last_length = length; + dp_last_size = size; + return (rc); +} + +static int do_mem_mm(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return mod_mem (cmdtp, 1, flag, argc, argv); +} + +static int do_mem_nm(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return mod_mem (cmdtp, 0, flag, argc, argv); +} + +static int do_mem_mw(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong writeval; /* 64-bit if MEM_SUPPORT_64BIT_DATA */ + ulong addr, count; + int size; + void *buf, *start; + ulong bytes; + + if ((argc < 3) || (argc > 4)) + return CMD_RET_USAGE; + + /* Check for size specification. + */ + if ((size = cmd_get_data_size(argv[0], 4)) < 1) + return 1; + + /* Address is specified since argc > 1 + */ + addr = hextoul(argv[1], NULL); + addr += base_address; + + /* Get the value to write. + */ + if (MEM_SUPPORT_64BIT_DATA) + writeval = simple_strtoull(argv[2], NULL, 16); + else + writeval = hextoul(argv[2], NULL); + + /* Count ? */ + if (argc == 4) { + count = hextoul(argv[3], NULL); + } else { + count = 1; + } + + bytes = size * count; + start = map_sysmem(addr, bytes); + buf = start; + while (count-- > 0) { + if (size == 4) + *((u32 *)buf) = (u32)writeval; + else if (MEM_SUPPORT_64BIT_DATA && size == 8) + *((ulong *)buf) = writeval; + else if (size == 2) + *((u16 *)buf) = (u16)writeval; + else + *((u8 *)buf) = (u8)writeval; + buf += size; + } + unmap_sysmem(start); + return 0; +} + +#ifdef CONFIG_CMD_MX_CYCLIC +static int do_mem_mdc(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i; + ulong count; + + if (argc < 4) + return CMD_RET_USAGE; + + count = dectoul(argv[3], NULL); + + for (;;) { + do_mem_md (NULL, 0, 3, argv); + + /* delay for <count> ms... */ + for (i=0; i<count; i++) + udelay(1000); + + /* check for ctrl-c to abort... */ + if (ctrlc()) { + puts("Abort\n"); + return 0; + } + } + + return 0; +} + +static int do_mem_mwc(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i; + ulong count; + + if (argc < 4) + return CMD_RET_USAGE; + + count = dectoul(argv[3], NULL); + + for (;;) { + do_mem_mw (NULL, 0, 3, argv); + + /* delay for <count> ms... */ + for (i=0; i<count; i++) + udelay(1000); + + /* check for ctrl-c to abort... */ + if (ctrlc()) { + puts("Abort\n"); + return 0; + } + } + + return 0; +} +#endif /* CONFIG_CMD_MX_CYCLIC */ + +static int do_mem_cmp(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr1, addr2, count, ngood, bytes; + int size; + int rcode = 0; + const char *type; + const void *buf1, *buf2, *base, *ptr1, *ptr2; + ulong word1, word2; /* 64-bit if MEM_SUPPORT_64BIT_DATA */ + + if (argc != 4) + return CMD_RET_USAGE; + + /* Check for size specification. + */ + if ((size = cmd_get_data_size(argv[0], 4)) < 0) + return 1; + type = size == 8 ? "double word" : + size == 4 ? "word" : + size == 2 ? "halfword" : "byte"; + + addr1 = hextoul(argv[1], NULL); + addr1 += base_address; + + addr2 = hextoul(argv[2], NULL); + addr2 += base_address; + + count = hextoul(argv[3], NULL); + + bytes = size * count; + base = buf1 = map_sysmem(addr1, bytes); + buf2 = map_sysmem(addr2, bytes); + for (ngood = 0, ptr1 = buf1, ptr2 = buf2; ngood < count; ++ngood) { + if (size == 4) { + word1 = *(u32 *)ptr1; + word2 = *(u32 *)ptr2; + } else if (MEM_SUPPORT_64BIT_DATA && size == 8) { + word1 = *(ulong *)ptr1; + word2 = *(ulong *)ptr2; + } else if (size == 2) { + word1 = *(u16 *)ptr1; + word2 = *(u16 *)ptr2; + } else { + word1 = *(u8 *)ptr1; + word2 = *(u8 *)ptr2; + } + if (word1 != word2) { + ulong offset = ptr1 - base; + printf("%s at 0x%08lx (%#0*lx) != %s at 0x%08lx (%#0*lx)\n", + type, (ulong)(addr1 + offset), size, word1, + type, (ulong)(addr2 + offset), size, word2); + rcode = 1; + break; + } + + ptr1 += size; + ptr2 += size; + + /* reset watchdog from time to time */ + if ((ngood % (64 << 10)) == 0) + schedule(); + } + unmap_sysmem(buf1); + unmap_sysmem(buf2); + + printf("Total of %ld %s(s) were the same\n", ngood, type); + return rcode; +} + +static int do_mem_cp(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr, dest, count; + void *src, *dst; + int size; + + if (argc != 4) + return CMD_RET_USAGE; + + /* Check for size specification. + */ + if ((size = cmd_get_data_size(argv[0], 4)) < 0) + return 1; + + addr = hextoul(argv[1], NULL); + addr += base_address; + + dest = hextoul(argv[2], NULL); + dest += base_address; + + count = hextoul(argv[3], NULL); + + if (count == 0) { + puts ("Zero length ???\n"); + return 1; + } + + src = map_sysmem(addr, count * size); + dst = map_sysmem(dest, count * size); + +#ifdef CONFIG_MTD_NOR_FLASH + /* check if we are copying to Flash */ + if (addr2info((ulong)dst)) { + int rc; + + puts ("Copy to Flash... "); + + rc = flash_write((char *)src, (ulong)dst, count * size); + if (rc != 0) { + flash_perror(rc); + unmap_sysmem(src); + unmap_sysmem(dst); + return (1); + } + puts ("done\n"); + unmap_sysmem(src); + unmap_sysmem(dst); + return 0; + } +#endif + + memmove(dst, src, count * size); + + unmap_sysmem(src); + unmap_sysmem(dst); + return 0; +} + +#ifdef CONFIG_CMD_MEM_SEARCH +static int do_mem_search(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr, length, bytes, offset; + u8 *ptr, *end, *buf; + bool quiet = false; + ulong last_pos; /* Offset of last match in 'size' units*/ + ulong last_addr; /* Address of last displayed line */ + int limit = 10; + int used_len; + int count; + int size; + int i; + + /* We use the last specified parameters, unless new ones are entered */ + addr = dp_last_addr; + size = dp_last_size; + length = dp_last_ms_length; + + if (argc < 3) + return CMD_RET_USAGE; + + if (!(flag & CMD_FLAG_REPEAT)) { + /* + * Check for a size specification. + * Defaults to long if no or incorrect specification. + */ + size = cmd_get_data_size(argv[0], 4); + if (size < 0 && size != CMD_DATA_SIZE_STR) + return 1; + + argc--; + argv++; + while (argc && *argv[0] == '-') { + int ch = argv[0][1]; + + if (ch == 'q') + quiet = true; + else if (ch == 'l' && isxdigit(argv[0][2])) + limit = hextoul(argv[0] + 2, NULL); + else + return CMD_RET_USAGE; + argc--; + argv++; + } + + /* Address is specified since argc > 1 */ + addr = hextoul(argv[0], NULL); + addr += base_address; + + /* Length is the number of objects, not number of bytes */ + length = hextoul(argv[1], NULL); + + /* Read the bytes to search for */ + end = search_buf + sizeof(search_buf); + for (i = 2, ptr = search_buf; i < argc && ptr < end; i++) { + if (MEM_SUPPORT_64BIT_DATA && size == 8) { + u64 val = simple_strtoull(argv[i], NULL, 16); + + *(u64 *)ptr = val; + } else if (size == -2) { /* string */ + int len = min(strlen(argv[i]), + (size_t)(end - ptr)); + + memcpy(ptr, argv[i], len); + ptr += len; + continue; + } else { + u32 val = hextoul(argv[i], NULL); + + switch (size) { + case 1: + *ptr = val; + break; + case 2: + *(u16 *)ptr = val; + break; + case 4: + *(u32 *)ptr = val; + break; + } + } + ptr += size; + } + search_len = ptr - search_buf; + } + + /* Do the search */ + if (size == -2) + size = 1; + bytes = size * length; + buf = map_sysmem(addr, bytes); + last_pos = 0; + last_addr = 0; + count = 0; + for (offset = 0; + offset < bytes && offset <= bytes - search_len && count < limit; + offset += size) { + void *ptr = buf + offset; + + if (!memcmp(ptr, search_buf, search_len)) { + uint align = (addr + offset) & 0xf; + ulong match = addr + offset; + + if (!count || (last_addr & ~0xf) != (match & ~0xf)) { + if (!quiet) { + if (count) + printf("--\n"); + print_buffer(match - align, ptr - align, + size, + ALIGN(search_len + align, + 16) / size, 0); + } + last_addr = match; + last_pos = offset / size; + } + count++; + } + } + if (!quiet) { + printf("%d match%s", count, count == 1 ? "" : "es"); + if (count == limit) + printf(" (repeat command to check for more)"); + printf("\n"); + } + env_set_hex("memmatches", count); + env_set_hex("memaddr", last_addr); + env_set_hex("mempos", last_pos); + + unmap_sysmem(buf); + + used_len = offset / size; + dp_last_addr = addr + used_len; + dp_last_size = size; + dp_last_ms_length = length < used_len ? 0 : length - used_len; + + return count ? 0 : CMD_RET_FAILURE; +} +#endif + +static int do_mem_base(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc > 1) { + /* Set new base address. + */ + base_address = hextoul(argv[1], NULL); + } + /* Print the current base address. + */ + printf("Base Address: 0x%08lx\n", base_address); + return 0; +} + +static int do_mem_loop(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr, length, i, bytes; + int size; + volatile ulong *llp; /* 64-bit if MEM_SUPPORT_64BIT_DATA */ + volatile u32 *longp; + volatile u16 *shortp; + volatile u8 *cp; + const void *buf; + + if (argc < 3) + return CMD_RET_USAGE; + + /* + * Check for a size specification. + * Defaults to long if no or incorrect specification. + */ + if ((size = cmd_get_data_size(argv[0], 4)) < 0) + return 1; + + /* Address is always specified. + */ + addr = hextoul(argv[1], NULL); + + /* Length is the number of objects, not number of bytes. + */ + length = hextoul(argv[2], NULL); + + bytes = size * length; + buf = map_sysmem(addr, bytes); + + /* We want to optimize the loops to run as fast as possible. + * If we have only one object, just run infinite loops. + */ + if (length == 1) { + if (MEM_SUPPORT_64BIT_DATA && size == 8) { + llp = (ulong *)buf; + for (;;) + i = *llp; + } + if (size == 4) { + longp = (u32 *)buf; + for (;;) + i = *longp; + } + if (size == 2) { + shortp = (u16 *)buf; + for (;;) + i = *shortp; + } + cp = (u8 *)buf; + for (;;) + i = *cp; + } + + if (MEM_SUPPORT_64BIT_DATA && size == 8) { + for (;;) { + llp = (ulong *)buf; + i = length; + while (i-- > 0) + *llp++; + } + } + if (size == 4) { + for (;;) { + longp = (u32 *)buf; + i = length; + while (i-- > 0) + *longp++; + } + } + if (size == 2) { + for (;;) { + shortp = (u16 *)buf; + i = length; + while (i-- > 0) + *shortp++; + } + } + for (;;) { + cp = (u8 *)buf; + i = length; + while (i-- > 0) + *cp++; + } + unmap_sysmem(buf); + + return 0; +} + +#ifdef CONFIG_LOOPW +static int do_mem_loopw(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr, length, i, bytes; + int size; + volatile ulong *llp; /* 64-bit if MEM_SUPPORT_64BIT_DATA */ + ulong data; /* 64-bit if MEM_SUPPORT_64BIT_DATA */ + volatile u32 *longp; + volatile u16 *shortp; + volatile u8 *cp; + void *buf; + + if (argc < 4) + return CMD_RET_USAGE; + + /* + * Check for a size specification. + * Defaults to long if no or incorrect specification. + */ + if ((size = cmd_get_data_size(argv[0], 4)) < 0) + return 1; + + /* Address is always specified. + */ + addr = hextoul(argv[1], NULL); + + /* Length is the number of objects, not number of bytes. + */ + length = hextoul(argv[2], NULL); + + /* data to write */ + if (MEM_SUPPORT_64BIT_DATA) + data = simple_strtoull(argv[3], NULL, 16); + else + data = hextoul(argv[3], NULL); + + bytes = size * length; + buf = map_sysmem(addr, bytes); + + /* We want to optimize the loops to run as fast as possible. + * If we have only one object, just run infinite loops. + */ + if (length == 1) { + if (MEM_SUPPORT_64BIT_DATA && size == 8) { + llp = (ulong *)buf; + for (;;) + *llp = data; + } + if (size == 4) { + longp = (u32 *)buf; + for (;;) + *longp = data; + } + if (size == 2) { + shortp = (u16 *)buf; + for (;;) + *shortp = data; + } + cp = (u8 *)buf; + for (;;) + *cp = data; + } + + if (MEM_SUPPORT_64BIT_DATA && size == 8) { + for (;;) { + llp = (ulong *)buf; + i = length; + while (i-- > 0) + *llp++ = data; + } + } + if (size == 4) { + for (;;) { + longp = (u32 *)buf; + i = length; + while (i-- > 0) + *longp++ = data; + } + } + if (size == 2) { + for (;;) { + shortp = (u16 *)buf; + i = length; + while (i-- > 0) + *shortp++ = data; + } + } + for (;;) { + cp = (u8 *)buf; + i = length; + while (i-- > 0) + *cp++ = data; + } +} +#endif /* CONFIG_LOOPW */ + +#ifdef CONFIG_CMD_MEMTEST +static ulong mem_test_alt(volatile ulong *buf, ulong start_addr, ulong end_addr, + volatile ulong *dummy) +{ + volatile ulong *addr; + ulong errs = 0; + ulong val, readback; + int j; + ulong offset, test_offset; + ulong pattern, anti_pattern; + ulong temp, num_words; + static const ulong bitpattern[] = { + 0x00000001, /* single bit */ + 0x00000003, /* two adjacent bits */ + 0x00000007, /* three adjacent bits */ + 0x0000000F, /* four adjacent bits */ + 0x00000005, /* two non-adjacent bits */ + 0x00000015, /* three non-adjacent bits */ + 0x00000055, /* four non-adjacent bits */ + 0xaaaaaaaa, /* alternating 1/0 */ + }; + /* Rate-limit schedule() calls to one for every 256 words. */ + u8 count = 0; + + num_words = (end_addr - start_addr) / sizeof(ulong); + + /* + * Data line test: write a pattern to the first + * location, write the 1's complement to a 'parking' + * address (changes the state of the data bus so a + * floating bus doesn't give a false OK), and then + * read the value back. Note that we read it back + * into a variable because the next time we read it, + * it might be right (been there, tough to explain to + * the quality guys why it prints a failure when the + * "is" and "should be" are obviously the same in the + * error message). + * + * Rather than exhaustively testing, we test some + * patterns by shifting '1' bits through a field of + * '0's and '0' bits through a field of '1's (i.e. + * pattern and ~pattern). + */ + addr = buf; + for (j = 0; j < sizeof(bitpattern) / sizeof(bitpattern[0]); j++) { + val = bitpattern[j]; + for (; val != 0; val <<= 1) { + *addr = val; + *dummy = ~val; /* clear the test data off the bus */ + readback = *addr; + if (readback != val) { + printf("FAILURE (data line): " + "expected %08lx, actual %08lx\n", + val, readback); + errs++; + if (ctrlc()) + return -1; + } + *addr = ~val; + *dummy = val; + readback = *addr; + if (readback != ~val) { + printf("FAILURE (data line): " + "Is %08lx, should be %08lx\n", + readback, ~val); + errs++; + if (ctrlc()) + return -1; + } + } + } + + /* + * Based on code whose Original Author and Copyright + * information follows: Copyright (c) 1998 by Michael + * Barr. This software is placed into the public + * domain and may be used for any purpose. However, + * this notice must not be changed or removed and no + * warranty is either expressed or implied by its + * publication or distribution. + */ + + /* + * Address line test + + * Description: Test the address bus wiring in a + * memory region by performing a walking + * 1's test on the relevant bits of the + * address and checking for aliasing. + * This test will find single-bit + * address failures such as stuck-high, + * stuck-low, and shorted pins. The base + * address and size of the region are + * selected by the caller. + + * Notes: For best results, the selected base + * address should have enough LSB 0's to + * guarantee single address bit changes. + * For example, to test a 64-Kbyte + * region, select a base address on a + * 64-Kbyte boundary. Also, select the + * region size as a power-of-two if at + * all possible. + * + * Returns: 0 if the test succeeds, 1 if the test fails. + */ + pattern = (ulong)0xaaaaaaaaaaaaaaaa; + anti_pattern = (ulong)0x5555555555555555; + + debug("%s:%d: length = 0x%.8lx\n", __func__, __LINE__, num_words); + /* + * Write the default pattern at each of the + * power-of-two offsets. + */ + for (offset = 1; offset < num_words; offset <<= 1) + addr[offset] = pattern; + + /* + * Check for address bits stuck high. + */ + test_offset = 0; + addr[test_offset] = anti_pattern; + + for (offset = 1; offset < num_words; offset <<= 1) { + temp = addr[offset]; + if (temp != pattern) { + printf("\nFAILURE: Address bit stuck high @ 0x%.8lx:" + " expected 0x%.8lx, actual 0x%.8lx\n", + start_addr + offset*sizeof(ulong), + pattern, temp); + errs++; + if (ctrlc()) + return -1; + } + } + addr[test_offset] = pattern; + schedule(); + + /* + * Check for addr bits stuck low or shorted. + */ + for (test_offset = 1; test_offset < num_words; test_offset <<= 1) { + addr[test_offset] = anti_pattern; + + for (offset = 1; offset < num_words; offset <<= 1) { + temp = addr[offset]; + if ((temp != pattern) && (offset != test_offset)) { + printf("\nFAILURE: Address bit stuck low or" + " shorted @ 0x%.8lx: expected 0x%.8lx," + " actual 0x%.8lx\n", + start_addr + offset*sizeof(ulong), + pattern, temp); + errs++; + if (ctrlc()) + return -1; + } + } + addr[test_offset] = pattern; + } + + /* + * Description: Test the integrity of a physical + * memory device by performing an + * increment/decrement test over the + * entire region. In the process every + * storage bit in the device is tested + * as a zero and a one. The base address + * and the size of the region are + * selected by the caller. + * + * Returns: 0 if the test succeeds, 1 if the test fails. + */ + num_words++; + + /* + * Fill memory with a known pattern. + */ + for (pattern = 1, offset = 0; offset < num_words; pattern++, offset++) { + if (!count++) + schedule(); + addr[offset] = pattern; + } + + /* + * Check each location and invert it for the second pass. + */ + for (pattern = 1, offset = 0; offset < num_words; pattern++, offset++) { + if (!count++) + schedule(); + temp = addr[offset]; + if (temp != pattern) { + printf("\nFAILURE (read/write) @ 0x%.8lx:" + " expected 0x%.8lx, actual 0x%.8lx)\n", + start_addr + offset*sizeof(ulong), + pattern, temp); + errs++; + if (ctrlc()) + return -1; + } + + anti_pattern = ~pattern; + addr[offset] = anti_pattern; + } + + /* + * Check each location for the inverted pattern and zero it. + */ + for (pattern = 1, offset = 0; offset < num_words; pattern++, offset++) { + if (!count++) + schedule(); + anti_pattern = ~pattern; + temp = addr[offset]; + if (temp != anti_pattern) { + printf("\nFAILURE (read/write): @ 0x%.8lx:" + " expected 0x%.8lx, actual 0x%.8lx)\n", + start_addr + offset*sizeof(ulong), + anti_pattern, temp); + errs++; + if (ctrlc()) + return -1; + } + addr[offset] = 0; + } + + return errs; +} + +static int compare_regions(volatile unsigned long *bufa, + volatile unsigned long *bufb, size_t count) +{ + volatile unsigned long *p1 = bufa; + volatile unsigned long *p2 = bufb; + int errs = 0; + size_t i; + + for (i = 0; i < count; i++, p1++, p2++) { + if (*p1 != *p2) { + printf("FAILURE: 0x%08lx != 0x%08lx (delta=0x%08lx -> bit %ld) at offset 0x%08lx\n", + (unsigned long)*p1, (unsigned long)*p2, + *p1 ^ *p2, __ffs(*p1 ^ *p2), + (unsigned long)(i * sizeof(unsigned long))); + errs++; + } + } + + return errs; +} + +static ulong test_bitflip_comparison(volatile unsigned long *bufa, + volatile unsigned long *bufb, size_t count) +{ + volatile unsigned long *p1 = bufa; + volatile unsigned long *p2 = bufb; + unsigned int j, k; + unsigned long q; + size_t i; + int max; + int errs = 0; + + max = sizeof(unsigned long) * 8; + for (k = 0; k < max; k++) { + q = 1UL << k; + for (j = 0; j < 8; j++) { + schedule(); + q = ~q; + p1 = (volatile unsigned long *)bufa; + p2 = (volatile unsigned long *)bufb; + for (i = 0; i < count; i++) + *p1++ = *p2++ = (i % 2) == 0 ? q : ~q; + + errs += compare_regions(bufa, bufb, count); + } + + if (ctrlc()) + return -1UL; + } + + return errs; +} + +static ulong mem_test_bitflip(vu_long *buf, ulong start, ulong end) +{ + /* + * Split the specified range into two halves. + * Note that mtest range is inclusive of start,end. + * Bitflip test instead uses a count (of 32-bit words). + */ + ulong half_size = (end - start + 1) / 2 / sizeof(unsigned long); + + return test_bitflip_comparison(buf, buf + half_size, half_size); +} + +static ulong mem_test_quick(vu_long *buf, ulong start_addr, ulong end_addr, + vu_long pattern, int iteration) +{ + vu_long *end; + vu_long *addr; + ulong errs = 0; + ulong incr, length; + ulong val, readback; + const int plen = 2 * sizeof(ulong); + + /* Alternate the pattern */ + incr = 1; + if (iteration & 1) { + incr = -incr; + /* + * Flip the pattern each time to make lots of zeros and + * then, the next time, lots of ones. We decrement + * the "negative" patterns and increment the "positive" + * patterns to preserve this feature. + */ + if (pattern > (ulong)LONG_MAX) + pattern = -pattern; /* complement & increment */ + else + pattern = ~pattern; + } + length = (end_addr - start_addr) / sizeof(ulong); + end = buf + length; + printf("\rPattern %0*lX Writing..." + "%12s" + "\b\b\b\b\b\b\b\b\b\b", + plen, pattern, ""); + + for (addr = buf, val = pattern; addr < end; addr++) { + schedule(); + *addr = val; + val += incr; + } + + puts("Reading..."); + + for (addr = buf, val = pattern; addr < end; addr++) { + schedule(); + readback = *addr; + if (readback != val) { + ulong offset = addr - buf; + + printf("\nMem error @ 0x%0*lX: found %0*lX, expected %0*lX\n", + plen, start_addr + offset * sizeof(vu_long), + plen, readback, plen, val); + errs++; + if (ctrlc()) + return -1; + } + val += incr; + } + + return errs; +} + +/* + * Perform a memory test. A more complete alternative test can be + * configured using CONFIG_SYS_ALT_MEMTEST. The complete test loops until + * interrupted by ctrl-c or by a failure of one of the sub-tests. + */ +static int do_mem_mtest(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong start, end; + vu_long scratch_space; + vu_long *buf, *dummy = &scratch_space; + ulong iteration_limit = 0; + ulong count = 0; + ulong errs = 0; /* number of errors, or -1 if interrupted */ + ulong pattern = 0; + int iteration; + + start = CONFIG_SYS_MEMTEST_START; + end = CONFIG_SYS_MEMTEST_END; + + if (argc > 1) + if (strict_strtoul(argv[1], 16, &start) < 0) + return CMD_RET_USAGE; + + if (argc > 2) + if (strict_strtoul(argv[2], 16, &end) < 0) + return CMD_RET_USAGE; + + if (argc > 3) + if (strict_strtoul(argv[3], 16, &pattern) < 0) + return CMD_RET_USAGE; + + if (argc > 4) + if (strict_strtoul(argv[4], 16, &iteration_limit) < 0) + return CMD_RET_USAGE; + + if (end < start) { + printf("Refusing to do empty test\n"); + return -1; + } + + printf("Testing %08lx ... %08lx:\n", start, end); + debug("%s:%d: start %#08lx end %#08lx\n", __func__, __LINE__, + start, end); + + buf = map_sysmem(start, end - start); + for (iteration = 0; + !iteration_limit || iteration < iteration_limit; + iteration++) { + if (ctrlc()) { + errs = -1UL; + break; + } + + printf("Iteration: %6d\r", iteration + 1); + debug("\n"); + if (IS_ENABLED(CONFIG_SYS_ALT_MEMTEST)) { + errs = mem_test_alt(buf, start, end, dummy); + if (errs == -1UL) + break; + if (IS_ENABLED(CONFIG_SYS_ALT_MEMTEST_BITFLIP)) { + count += errs; + errs = mem_test_bitflip(buf, start, end); + } + } else { + errs = mem_test_quick(buf, start, end, pattern, + iteration); + } + if (errs == -1UL) + break; + count += errs; + } + + unmap_sysmem((void *)buf); + + printf("\nTested %d iteration(s) with %lu errors.\n", iteration, count); + + return errs != 0; +} +#endif /* CONFIG_CMD_MEMTEST */ + +/* Modify memory. + * + * Syntax: + * mm{.b, .w, .l, .q} {addr} + */ +static int +mod_mem(struct cmd_tbl *cmdtp, int incrflag, int flag, int argc, + char *const argv[]) +{ + ulong addr; + ulong i; /* 64-bit if MEM_SUPPORT_64BIT_DATA */ + int nbytes, size; + void *ptr = NULL; + + if (argc != 2) + return CMD_RET_USAGE; + + bootretry_reset_cmd_timeout(); /* got a good command to get here */ + /* We use the last specified parameters, unless new ones are + * entered. + */ + addr = mm_last_addr; + size = mm_last_size; + + if ((flag & CMD_FLAG_REPEAT) == 0) { + /* New command specified. Check for a size specification. + * Defaults to long if no or incorrect specification. + */ + if ((size = cmd_get_data_size(argv[0], 4)) < 0) + return 1; + + /* Address is specified since argc > 1 + */ + addr = hextoul(argv[1], NULL); + addr += base_address; + } + + /* Print the address, followed by value. Then accept input for + * the next value. A non-converted value exits. + */ + do { + ptr = map_sysmem(addr, size); + printf("%08lx:", addr); + if (size == 4) + printf(" %08x", *((u32 *)ptr)); + else if (MEM_SUPPORT_64BIT_DATA && size == 8) + printf(" %0lx", *((ulong *)ptr)); + else if (size == 2) + printf(" %04x", *((u16 *)ptr)); + else + printf(" %02x", *((u8 *)ptr)); + + nbytes = cli_readline(" ? "); + if (nbytes == 0 || (nbytes == 1 && console_buffer[0] == '-')) { + /* <CR> pressed as only input, don't modify current + * location and move to next. "-" pressed will go back. + */ + if (incrflag) + addr += nbytes ? -size : size; + nbytes = 1; + /* good enough to not time out */ + bootretry_reset_cmd_timeout(); + } +#ifdef CONFIG_BOOT_RETRY_TIME + else if (nbytes == -2) { + break; /* timed out, exit the command */ + } +#endif + else { + char *endp; + if (MEM_SUPPORT_64BIT_DATA) + i = simple_strtoull(console_buffer, &endp, 16); + else + i = hextoul(console_buffer, &endp); + nbytes = endp - console_buffer; + if (nbytes) { + /* good enough to not time out + */ + bootretry_reset_cmd_timeout(); + if (size == 4) + *((u32 *)ptr) = i; + else if (MEM_SUPPORT_64BIT_DATA && size == 8) + *((ulong *)ptr) = i; + else if (size == 2) + *((u16 *)ptr) = i; + else + *((u8 *)ptr) = i; + if (incrflag) + addr += size; + } + } + } while (nbytes); + if (ptr) + unmap_sysmem(ptr); + + mm_last_addr = addr; + mm_last_size = size; + return 0; +} + +#ifdef CONFIG_CMD_CRC32 + +static int do_mem_crc(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int flags = 0; + int ac; + char * const *av; + + if (argc < 3) + return CMD_RET_USAGE; + + av = argv + 1; + ac = argc - 1; +#ifdef CONFIG_CRC32_VERIFY + if (strcmp(*av, "-v") == 0) { + flags |= HASH_FLAG_VERIFY | HASH_FLAG_ENV; + av++; + ac--; + } +#endif + + return hash_command("crc32", flags, cmdtp, flag, ac, av); +} + +#endif + +#ifdef CONFIG_CMD_RANDOM +static int do_random(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned long addr, len; + unsigned long seed; // NOT INITIALIZED ON PURPOSE + unsigned int *buf, *start; + unsigned char *buf8; + unsigned int i; + + if (argc < 3 || argc > 4) + return CMD_RET_USAGE; + + len = hextoul(argv[2], NULL); + addr = hextoul(argv[1], NULL); + + if (argc == 4) { + seed = hextoul(argv[3], NULL); + if (seed == 0) { + printf("The seed cannot be 0. Using 0xDEADBEEF.\n"); + seed = 0xDEADBEEF; + } + } else { + seed = get_timer(0) ^ rand(); + } + + srand(seed); + start = map_sysmem(addr, len); + buf = start; + for (i = 0; i < (len / 4); i++) + *buf++ = rand(); + + buf8 = (unsigned char *)buf; + for (i = 0; i < (len % 4); i++) + *buf8++ = rand() & 0xFF; + + unmap_sysmem(start); + printf("%lu bytes filled with random data\n", len); + + return CMD_RET_SUCCESS; +} +#endif + +/**************************************************/ +U_BOOT_CMD( + md, 3, 1, do_mem_md, + "memory display", + "[.b, .w, .l" HELP_Q "] address [# of objects]" +); + +U_BOOT_CMD( + mm, 2, 1, do_mem_mm, + "memory modify (auto-incrementing address)", + "[.b, .w, .l" HELP_Q "] address" +); + +U_BOOT_CMD( + nm, 2, 1, do_mem_nm, + "memory modify (constant address)", + "[.b, .w, .l" HELP_Q "] address" +); + +U_BOOT_CMD( + mw, 4, 1, do_mem_mw, + "memory write (fill)", + "[.b, .w, .l" HELP_Q "] address value [count]" +); + +U_BOOT_CMD( + cp, 4, 1, do_mem_cp, + "memory copy", + "[.b, .w, .l" HELP_Q "] source target count" +); + +U_BOOT_CMD( + cmp, 4, 1, do_mem_cmp, + "memory compare", + "[.b, .w, .l" HELP_Q "] addr1 addr2 count" +); + +#ifdef CONFIG_CMD_MEM_SEARCH +/**************************************************/ +U_BOOT_CMD( + ms, 255, 1, do_mem_search, + "memory search", + "[.b, .w, .l" HELP_Q ", .s] [-q | -<n>] address #-of-objects <value>..." + " -q = quiet, -l<val> = match limit" +); +#endif + +#ifdef CONFIG_CMD_CRC32 + +#ifndef CONFIG_CRC32_VERIFY + +U_BOOT_CMD( + crc32, 4, 1, do_mem_crc, + "checksum calculation", + "address count [addr]\n - compute CRC32 checksum [save at addr]" +); + +#else /* CONFIG_CRC32_VERIFY */ + +U_BOOT_CMD( + crc32, 5, 1, do_mem_crc, + "checksum calculation", + "address count [addr]\n - compute CRC32 checksum [save at addr]\n" + "-v address count crc\n - verify crc of memory area" +); + +#endif /* CONFIG_CRC32_VERIFY */ + +#endif + +U_BOOT_CMD( + base, 2, 1, do_mem_base, + "print or set address offset", + "\n - print address offset for memory commands\n" + "base off\n - set address offset for memory commands to 'off'" +); + +U_BOOT_CMD( + loop, 3, 1, do_mem_loop, + "infinite loop on address range", + "[.b, .w, .l" HELP_Q "] address number_of_objects" +); + +#ifdef CONFIG_LOOPW +U_BOOT_CMD( + loopw, 4, 1, do_mem_loopw, + "infinite write loop on address range", + "[.b, .w, .l" HELP_Q "] address number_of_objects data_to_write" +); +#endif /* CONFIG_LOOPW */ + +#ifdef CONFIG_CMD_MEMTEST +U_BOOT_CMD( + mtest, 5, 1, do_mem_mtest, + "simple RAM read/write test", + "[start [end [pattern [iterations]]]]" +); +#endif /* CONFIG_CMD_MEMTEST */ + +#ifdef CONFIG_CMD_MX_CYCLIC +U_BOOT_CMD( + mdc, 4, 1, do_mem_mdc, + "memory display cyclic", + "[.b, .w, .l" HELP_Q "] address count delay(ms)" +); + +U_BOOT_CMD( + mwc, 4, 1, do_mem_mwc, + "memory write cyclic", + "[.b, .w, .l" HELP_Q "] address value delay(ms)" +); +#endif /* CONFIG_CMD_MX_CYCLIC */ + +#ifdef CONFIG_CMD_RANDOM +U_BOOT_CMD( + random, 4, 0, do_random, + "fill memory with random pattern", + "<addr> <len> [<seed>]\n" + " - Fill 'len' bytes of memory starting at 'addr' with random data\n" +); +#endif diff --git a/cmd/meminfo.c b/cmd/meminfo.c new file mode 100644 index 00000000000..aa3b5bafe17 --- /dev/null +++ b/cmd/meminfo.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2024 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <bloblist.h> +#include <bootstage.h> +#include <command.h> +#include <display_options.h> +#include <lmb.h> +#include <malloc.h> +#include <mapmem.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +void __weak arch_dump_mem_attrs(void) +{ +} + +static void print_region(const char *name, ulong base, ulong size, ulong *uptop) +{ + ulong end = base + size; + + printf("%-12s %8lx %8lx %8lx", name, base, size, end); + if (*uptop) + printf(" %8lx", *uptop - end); + putc('\n'); + *uptop = base; +} + +static void show_lmb(const struct lmb *lmb, ulong *uptop) +{ + int i; + + for (i = lmb->used_mem.count - 1; i >= 0; i--) { + const struct lmb_region *rgn = alist_get(&lmb->used_mem, i, + struct lmb_region); + + /* + * Assume that the top lmb region is the U-Boot region, so just + * take account of the memory not already reported + */ + if (lmb->used_mem.count - 1) + print_region("lmb", rgn->base, *uptop - rgn->base, + uptop); + else + print_region("lmb", rgn->base, rgn->size, uptop); + *uptop = rgn->base; + } +} + +static int do_meminfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong upto, stk_bot; + + puts("DRAM: "); + print_size(gd->ram_size, "\n"); + + if (!IS_ENABLED(CONFIG_CMD_MEMINFO_MAP)) + return 0; + + arch_dump_mem_attrs(); + + printf("\n%-12s %8s %8s %8s %8s\n", "Region", "Base", "Size", "End", + "Gap"); + printf("------------------------------------------------\n"); + upto = 0; + if (IS_ENABLED(CONFIG_VIDEO)) + print_region("video", gd_video_bottom(), + gd_video_size(), &upto); + if (IS_ENABLED(CONFIG_TRACE)) + print_region("trace", map_to_sysmem(gd_trace_buff()), + gd_trace_size(), &upto); + print_region("code", gd->relocaddr, gd->mon_len, &upto); + print_region("malloc", map_to_sysmem((void *)mem_malloc_start), + mem_malloc_end - mem_malloc_start, &upto); + print_region("board_info", map_to_sysmem(gd->bd), + sizeof(struct bd_info), &upto); + print_region("global_data", map_to_sysmem((void *)gd), + sizeof(struct global_data), &upto); + print_region("devicetree", map_to_sysmem(gd->fdt_blob), + fdt_totalsize(gd->fdt_blob), &upto); + if (IS_ENABLED(CONFIG_BOOTSTAGE)) + print_region("bootstage", map_to_sysmem(gd_bootstage()), + bootstage_get_size(false), &upto); + if (IS_ENABLED(CONFIG_BLOBLIST)) + print_region("bloblist", map_to_sysmem(gd_bloblist()), + bloblist_get_total_size(), &upto); + stk_bot = gd->start_addr_sp - CONFIG_STACK_SIZE; + print_region("stack", stk_bot, CONFIG_STACK_SIZE, &upto); + if (IS_ENABLED(CONFIG_LMB)) + show_lmb(lmb_get(), &upto); + print_region("free", gd->ram_base, upto - gd->ram_base, &upto); + + return 0; +} + +U_BOOT_CMD( + meminfo, 1, 1, do_meminfo, + "display memory information", + "" +); diff --git a/cmd/meson/Makefile b/cmd/meson/Makefile new file mode 100644 index 00000000000..ee26c175cfe --- /dev/null +++ b/cmd/meson/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (c) 2022, SberDevices. All rights reserved. + +obj-y += sm.o diff --git a/cmd/meson/sm.c b/cmd/meson/sm.c new file mode 100644 index 00000000000..b69f8123ee2 --- /dev/null +++ b/cmd/meson/sm.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2016 Beniamino Galvani + * + * Author: Beniamino Galvani <b.galvani@gmail.com> + * Author: Vyacheslav Bocharov <adeep@lexina.in> + * Author: Neil Armstrong <narmstrong@baylibre.com> + * Author: Alexey Romanov <avromanov@sberdevices.ru> + */ + +#include <command.h> +#include <env.h> +#include <asm/arch/sm.h> +#include <stdlib.h> +#include <display_options.h> +#include <vsprintf.h> + +static int do_sm_serial(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong address; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + address = simple_strtoul(argv[1], NULL, 0); + + ret = meson_sm_get_serial((void *)address, SM_SERIAL_SIZE); + if (ret) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +#define MAX_REBOOT_REASONS 14 + +static const char *reboot_reasons[MAX_REBOOT_REASONS] = { + [REBOOT_REASON_COLD] = "cold_boot", + [REBOOT_REASON_NORMAL] = "normal", + [REBOOT_REASON_RECOVERY] = "recovery", + [REBOOT_REASON_UPDATE] = "update", + [REBOOT_REASON_FASTBOOT] = "fastboot", + [REBOOT_REASON_SUSPEND_OFF] = "suspend_off", + [REBOOT_REASON_HIBERNATE] = "hibernate", + [REBOOT_REASON_BOOTLOADER] = "bootloader", + [REBOOT_REASON_SHUTDOWN_REBOOT] = "shutdown_reboot", + [REBOOT_REASON_RPMBP] = "rpmbp", + [REBOOT_REASON_CRASH_DUMP] = "crash_dump", + [REBOOT_REASON_KERNEL_PANIC] = "kernel_panic", + [REBOOT_REASON_WATCHDOG_REBOOT] = "watchdog_reboot", +}; + +static int do_sm_reboot_reason(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *reason_str; + char *destarg = NULL; + int reason; + + if (argc > 1) + destarg = argv[1]; + + reason = meson_sm_get_reboot_reason(); + if (reason < 0) + return CMD_RET_FAILURE; + + if (reason >= MAX_REBOOT_REASONS || + !reboot_reasons[reason]) + reason_str = "unknown"; + else + reason_str = reboot_reasons[reason]; + + if (destarg) + env_set(destarg, reason_str); + else + printf("reboot reason: %s (%x)\n", reason_str, reason); + + return CMD_RET_SUCCESS; +} + +static int do_efuse_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong address, offset, size; + int ret; + + if (argc < 4) + return CMD_RET_USAGE; + + offset = simple_strtoul(argv[1], NULL, 0); + size = simple_strtoul(argv[2], NULL, 0); + + address = simple_strtoul(argv[3], NULL, 0); + + ret = meson_sm_read_efuse(offset, (void *)address, size); + if (ret != size) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +static int do_efuse_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong address, offset, size; + int ret; + + if (argc < 4) + return CMD_RET_USAGE; + + offset = simple_strtoul(argv[1], NULL, 0); + size = simple_strtoul(argv[2], NULL, 0); + + address = simple_strtoul(argv[3], NULL, 0); + + ret = meson_sm_write_efuse(offset, (void *)address, size); + if (ret != size) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +static int do_efuse_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong offset, size; + u8 *buffer; + int ret; + + if (argc != 3) + return CMD_RET_USAGE; + + offset = simple_strtoul(argv[1], NULL, 0); + size = simple_strtoul(argv[2], NULL, 0); + buffer = malloc(size); + if (!buffer) { + pr_err("Failed to allocate %lu bytes\n", size); + return CMD_RET_FAILURE; + } + + ret = meson_sm_read_efuse(offset, (void *)buffer, size); + if (ret != size) { + ret = CMD_RET_FAILURE; + goto free_buffer; + } + + print_buffer(0, buffer, 1, size, 0); + +free_buffer: + free(buffer); + return ret; +} + +static struct cmd_tbl cmd_sm_sub[] = { + U_BOOT_CMD_MKENT(serial, 2, 1, do_sm_serial, "", ""), + U_BOOT_CMD_MKENT(reboot_reason, 1, 1, do_sm_reboot_reason, "", ""), + U_BOOT_CMD_MKENT(efuseread, 4, 1, do_efuse_read, "", ""), + U_BOOT_CMD_MKENT(efusewrite, 4, 0, do_efuse_write, "", ""), + U_BOOT_CMD_MKENT(efusedump, 3, 1, do_efuse_dump, "", ""), +}; + +static int do_sm(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c; + + if (argc < 2) + return CMD_RET_USAGE; + + /* Strip off leading 'sm' command argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], &cmd_sm_sub[0], ARRAY_SIZE(cmd_sm_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + sm, 5, 0, do_sm, + "Secure Monitor Control", + "serial <address> - read chip unique id to memory address\n" + "sm reboot_reason [name] - get reboot reason and store to environment\n" + "sm efuseread <offset> <size> <address> - read efuse to memory address\n" + "sm efusewrite <offset> <size> <address> - write into efuse from memory address\n" + "sm efusedump <offset> <size> - dump efuse data range to console" +); diff --git a/cmd/mii.c b/cmd/mii.c new file mode 100644 index 00000000000..ce372489692 --- /dev/null +++ b/cmd/mii.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2001 + * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com + */ + +/* + * MII Utilities + */ + +#include <command.h> +#include <dm.h> +#include <miiphy.h> + +typedef struct _MII_field_desc_t { + ushort hi; + ushort lo; + ushort mask; + const char *name; +} MII_field_desc_t; + +static const MII_field_desc_t reg_0_desc_tbl[] = { + { 15, 15, 0x01, "reset" }, + { 14, 14, 0x01, "loopback" }, + { 13, 6, 0x81, "speed selection" }, /* special */ + { 12, 12, 0x01, "A/N enable" }, + { 11, 11, 0x01, "power-down" }, + { 10, 10, 0x01, "isolate" }, + { 9, 9, 0x01, "restart A/N" }, + { 8, 8, 0x01, "duplex" }, /* special */ + { 7, 7, 0x01, "collision test enable" }, + { 5, 0, 0x3f, "(reserved)" } +}; + +static const MII_field_desc_t reg_1_desc_tbl[] = { + { 15, 15, 0x01, "100BASE-T4 able" }, + { 14, 14, 0x01, "100BASE-X full duplex able" }, + { 13, 13, 0x01, "100BASE-X half duplex able" }, + { 12, 12, 0x01, "10 Mbps full duplex able" }, + { 11, 11, 0x01, "10 Mbps half duplex able" }, + { 10, 10, 0x01, "100BASE-T2 full duplex able" }, + { 9, 9, 0x01, "100BASE-T2 half duplex able" }, + { 8, 8, 0x01, "extended status" }, + { 7, 7, 0x01, "(reserved)" }, + { 6, 6, 0x01, "MF preamble suppression" }, + { 5, 5, 0x01, "A/N complete" }, + { 4, 4, 0x01, "remote fault" }, + { 3, 3, 0x01, "A/N able" }, + { 2, 2, 0x01, "link status" }, + { 1, 1, 0x01, "jabber detect" }, + { 0, 0, 0x01, "extended capabilities" }, +}; + +static const MII_field_desc_t reg_2_desc_tbl[] = { + { 15, 0, 0xffff, "OUI portion" }, +}; + +static const MII_field_desc_t reg_3_desc_tbl[] = { + { 15, 10, 0x3f, "OUI portion" }, + { 9, 4, 0x3f, "manufacturer part number" }, + { 3, 0, 0x0f, "manufacturer rev. number" }, +}; + +static const MII_field_desc_t reg_4_desc_tbl[] = { + { 15, 15, 0x01, "next page able" }, + { 14, 14, 0x01, "(reserved)" }, + { 13, 13, 0x01, "remote fault" }, + { 12, 12, 0x01, "(reserved)" }, + { 11, 11, 0x01, "asymmetric pause" }, + { 10, 10, 0x01, "pause enable" }, + { 9, 9, 0x01, "100BASE-T4 able" }, + { 8, 8, 0x01, "100BASE-TX full duplex able" }, + { 7, 7, 0x01, "100BASE-TX able" }, + { 6, 6, 0x01, "10BASE-T full duplex able" }, + { 5, 5, 0x01, "10BASE-T able" }, + { 4, 0, 0x1f, "selector" }, +}; + +static const MII_field_desc_t reg_5_desc_tbl[] = { + { 15, 15, 0x01, "next page able" }, + { 14, 14, 0x01, "acknowledge" }, + { 13, 13, 0x01, "remote fault" }, + { 12, 12, 0x01, "(reserved)" }, + { 11, 11, 0x01, "asymmetric pause able" }, + { 10, 10, 0x01, "pause able" }, + { 9, 9, 0x01, "100BASE-T4 able" }, + { 8, 8, 0x01, "100BASE-X full duplex able" }, + { 7, 7, 0x01, "100BASE-TX able" }, + { 6, 6, 0x01, "10BASE-T full duplex able" }, + { 5, 5, 0x01, "10BASE-T able" }, + { 4, 0, 0x1f, "partner selector" }, +}; + +static const MII_field_desc_t reg_9_desc_tbl[] = { + { 15, 13, 0x07, "test mode" }, + { 12, 12, 0x01, "manual master/slave enable" }, + { 11, 11, 0x01, "manual master/slave value" }, + { 10, 10, 0x01, "multi/single port" }, + { 9, 9, 0x01, "1000BASE-T full duplex able" }, + { 8, 8, 0x01, "1000BASE-T half duplex able" }, + { 7, 7, 0x01, "automatic TDR on link down" }, + { 6, 6, 0x7f, "(reserved)" }, +}; + +static const MII_field_desc_t reg_10_desc_tbl[] = { + { 15, 15, 0x01, "master/slave config fault" }, + { 14, 14, 0x01, "master/slave config result" }, + { 13, 13, 0x01, "local receiver status OK" }, + { 12, 12, 0x01, "remote receiver status OK" }, + { 11, 11, 0x01, "1000BASE-T full duplex able" }, + { 10, 10, 0x01, "1000BASE-T half duplex able" }, + { 9, 8, 0x03, "(reserved)" }, + { 7, 0, 0xff, "1000BASE-T idle error counter"}, +}; + +typedef struct _MII_reg_desc_t { + ushort regno; + const MII_field_desc_t *pdesc; + ushort len; + const char *name; +} MII_reg_desc_t; + +static const MII_reg_desc_t mii_reg_desc_tbl[] = { + { MII_BMCR, reg_0_desc_tbl, ARRAY_SIZE(reg_0_desc_tbl), + "PHY control register" }, + { MII_BMSR, reg_1_desc_tbl, ARRAY_SIZE(reg_1_desc_tbl), + "PHY status register" }, + { MII_PHYSID1, reg_2_desc_tbl, ARRAY_SIZE(reg_2_desc_tbl), + "PHY ID 1 register" }, + { MII_PHYSID2, reg_3_desc_tbl, ARRAY_SIZE(reg_3_desc_tbl), + "PHY ID 2 register" }, + { MII_ADVERTISE, reg_4_desc_tbl, ARRAY_SIZE(reg_4_desc_tbl), + "Autonegotiation advertisement register" }, + { MII_LPA, reg_5_desc_tbl, ARRAY_SIZE(reg_5_desc_tbl), + "Autonegotiation partner abilities register" }, + { MII_CTRL1000, reg_9_desc_tbl, ARRAY_SIZE(reg_9_desc_tbl), + "1000BASE-T control register" }, + { MII_STAT1000, reg_10_desc_tbl, ARRAY_SIZE(reg_10_desc_tbl), + "1000BASE-T status register" }, +}; + +static void dump_reg( + ushort regval, + const MII_reg_desc_t *prd); + +static bool special_field(ushort regno, const MII_field_desc_t *pdesc, + ushort regval); + +static void MII_dump(const ushort *regvals, uchar reglo, uchar reghi) +{ + ulong i; + + for (i = 0; i < ARRAY_SIZE(mii_reg_desc_tbl); i++) { + const uchar reg = mii_reg_desc_tbl[i].regno; + + if (reg >= reglo && reg <= reghi) + dump_reg(regvals[reg - reglo], &mii_reg_desc_tbl[i]); + } +} + +/* Print out field position, value, name */ +static void dump_field(const MII_field_desc_t *pdesc, ushort regval) +{ + if (pdesc->hi == pdesc->lo) + printf("%2u ", pdesc->lo); + else + printf("%2u-%2u", pdesc->hi, pdesc->lo); + + printf(" = %5u %s", (regval >> pdesc->lo) & pdesc->mask, + pdesc->name); +} + +static void dump_reg( + ushort regval, + const MII_reg_desc_t *prd) +{ + ulong i; + ushort mask_in_place; + const MII_field_desc_t *pdesc; + + printf("%u. (%04hx) -- %s --\n", + prd->regno, regval, prd->name); + + for (i = 0; i < prd->len; i++) { + pdesc = &prd->pdesc[i]; + + mask_in_place = pdesc->mask << pdesc->lo; + + printf(" (%04hx:%04x) %u.", + mask_in_place, + regval & mask_in_place, + prd->regno); + + if (!special_field(prd->regno, pdesc, regval)) + dump_field(pdesc, regval); + printf("\n"); + + } + printf("\n"); +} + +/* Special fields: +** 0.6,13 +** 0.8 +** 2.15-0 +** 3.15-0 +** 4.4-0 +** 5.4-0 +*/ + +static bool special_field(ushort regno, const MII_field_desc_t *pdesc, + ushort regval) +{ + const ushort sel_bits = (regval >> pdesc->lo) & pdesc->mask; + + if ((regno == MII_BMCR) && (pdesc->lo == 6)) { + ushort speed_bits = regval & (BMCR_SPEED1000 | BMCR_SPEED100); + printf("%2u,%2u = b%u%u speed selection = %s Mbps", + 6, 13, + (regval >> 6) & 1, + (regval >> 13) & 1, + speed_bits == BMCR_SPEED1000 ? "1000" : + speed_bits == BMCR_SPEED100 ? "100" : + "10"); + return 1; + } + + else if ((regno == MII_BMCR) && (pdesc->lo == 8)) { + dump_field(pdesc, regval); + printf(" = %s", ((regval >> pdesc->lo) & 1) ? "full" : "half"); + return 1; + } + + else if ((regno == MII_ADVERTISE) && (pdesc->lo == 0)) { + dump_field(pdesc, regval); + printf(" = %s", + sel_bits == PHY_ANLPAR_PSB_802_3 ? "IEEE 802.3 CSMA/CD" : + sel_bits == PHY_ANLPAR_PSB_802_9 ? + "IEEE 802.9 ISLAN-16T" : "???"); + return 1; + } + + else if ((regno == MII_LPA) && (pdesc->lo == 0)) { + dump_field(pdesc, regval); + printf(" = %s", + sel_bits == PHY_ANLPAR_PSB_802_3 ? "IEEE 802.3 CSMA/CD" : + sel_bits == PHY_ANLPAR_PSB_802_9 ? + "IEEE 802.9 ISLAN-16T" : "???"); + return 1; + } + + return 0; +} + +static char last_op[2]; +static uint last_data; +static uint last_addr_lo; +static uint last_addr_hi; +static uint last_reg_lo; +static uint last_reg_hi; +static uint last_mask; + +static void extract_range( + char * input, + unsigned char * plo, + unsigned char * phi) +{ + char * end; + *plo = hextoul(input, &end); + if (*end == '-') { + end++; + *phi = hextoul(end, NULL); + } + else { + *phi = *plo; + } +} + +/* ---------------------------------------------------------------- */ +static int do_mii(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char op[2]; + unsigned char addrlo, addrhi, reglo, reghi; + unsigned char addr, reg; + unsigned short data, mask; + int rcode = 0; + const char *devname; + + if (argc < 2) + return CMD_RET_USAGE; + +#if defined(CONFIG_MII_INIT) + mii_init (); +#endif + + /* + * We use the last specified parameters, unless new ones are + * entered. + */ + op[0] = last_op[0]; + op[1] = last_op[1]; + addrlo = last_addr_lo; + addrhi = last_addr_hi; + reglo = last_reg_lo; + reghi = last_reg_hi; + data = last_data; + mask = last_mask; + + if ((flag & CMD_FLAG_REPEAT) == 0) { + op[0] = argv[1][0]; + if (strlen(argv[1]) > 1) + op[1] = argv[1][1]; + else + op[1] = '\0'; + + if (argc >= 3) + extract_range(argv[2], &addrlo, &addrhi); + if (argc >= 4) + extract_range(argv[3], ®lo, ®hi); + if (argc >= 5) + data = hextoul(argv[4], NULL); + if (argc >= 6) + mask = hextoul(argv[5], NULL); + } + + if (addrhi > 31 && strncmp(op, "de", 2)) { + printf("Incorrect PHY address. Range should be 0-31\n"); + return CMD_RET_USAGE; + } + + /* use current device */ + devname = miiphy_get_current_dev(); + + /* + * check info/read/write. + */ + if (op[0] == 'i') { + unsigned char j, start, end; + unsigned int oui; + unsigned char model; + unsigned char rev; + + /* + * Look for any and all PHYs. Valid addresses are 0..31. + */ + if (argc >= 3) { + start = addrlo; end = addrhi; + } else { + start = 0; end = 31; + } + + for (j = start; j <= end; j++) { + if (miiphy_info (devname, j, &oui, &model, &rev) == 0) { + printf("PHY 0x%02X: " + "OUI = 0x%04X, " + "Model = 0x%02X, " + "Rev = 0x%02X, " + "%3dbase%s, %s\n", + j, oui, model, rev, + miiphy_speed (devname, j), + miiphy_is_1000base_x (devname, j) + ? "X" : "T", + (miiphy_duplex (devname, j) == FULL) + ? "FDX" : "HDX"); + } + } + } else if (op[0] == 'r') { + for (addr = addrlo; addr <= addrhi; addr++) { + for (reg = reglo; reg <= reghi; reg++) { + data = 0xffff; + if (miiphy_read (devname, addr, reg, &data) != 0) { + printf( + "Error reading from the PHY addr=%02x reg=%02x\n", + addr, reg); + rcode = 1; + } else { + if ((addrlo != addrhi) || (reglo != reghi)) + printf("addr=%02x reg=%02x data=", + (uint)addr, (uint)reg); + printf("%04X\n", data & 0x0000FFFF); + } + } + if ((addrlo != addrhi) && (reglo != reghi)) + printf("\n"); + } + } else if (op[0] == 'w') { + for (addr = addrlo; addr <= addrhi; addr++) { + for (reg = reglo; reg <= reghi; reg++) { + if (miiphy_write (devname, addr, reg, data) != 0) { + printf("Error writing to the PHY addr=%02x reg=%02x\n", + addr, reg); + rcode = 1; + } + } + } + } else if (op[0] == 'm') { + for (addr = addrlo; addr <= addrhi; addr++) { + for (reg = reglo; reg <= reghi; reg++) { + unsigned short val = 0; + if (miiphy_read(devname, addr, + reg, &val)) { + printf("Error reading from the PHY"); + printf(" addr=%02x", addr); + printf(" reg=%02x\n", reg); + rcode = 1; + } else { + val = (val & ~mask) | (data & mask); + if (miiphy_write(devname, addr, + reg, val)) { + printf("Error writing to the PHY"); + printf(" addr=%02x", addr); + printf(" reg=%02x\n", reg); + rcode = 1; + } + } + } + } + } else if (strncmp(op, "du", 2) == 0) { + ushort regs[MII_STAT1000 + 1]; /* Last reg is 0x0a */ + int ok = 1; + if (reglo > MII_STAT1000 || reghi > MII_STAT1000) { + printf("The MII dump command only formats the standard MII registers, 0-5, 9-a.\n"); + return 1; + } + for (addr = addrlo; addr <= addrhi; addr++) { + for (reg = reglo; reg <= reghi; reg++) { + if (miiphy_read(devname, addr, reg, + ®s[reg - reglo]) != 0) { + ok = 0; + printf( + "Error reading from the PHY addr=%02x reg=%02x\n", + addr, reg); + rcode = 1; + } + } + if (ok) + MII_dump(regs, reglo, reghi); + printf("\n"); + } + } else if (strncmp(op, "de", 2) == 0) { + if (argc == 2) + miiphy_listdev (); + else + miiphy_set_current_dev (argv[2]); + } else { + return CMD_RET_USAGE; + } + + /* + * Save the parameters for repeats. + */ + last_op[0] = op[0]; + last_op[1] = op[1]; + last_addr_lo = addrlo; + last_addr_hi = addrhi; + last_reg_lo = reglo; + last_reg_hi = reghi; + last_data = data; + last_mask = mask; + + return rcode; +} + +/***************************************************/ + +U_BOOT_CMD( + mii, 6, 1, do_mii, + "MII utility commands", + "device - list available devices\n" + "mii device <devname> - set current device\n" + "mii info <addr> - display MII PHY info\n" + "mii read <addr> <reg> - read MII PHY <addr> register <reg>\n" + "mii write <addr> <reg> <data> - write MII PHY <addr> register <reg>\n" + "mii modify <addr> <reg> <data> <mask> - modify MII PHY <addr> register <reg>\n" + " updating bits identified in <mask>\n" + "mii dump <addr> <reg> - pretty-print <addr> <reg> (0-5 only)\n" + "Addr and/or reg may be ranges, e.g. 2-7." +); diff --git a/cmd/misc.c b/cmd/misc.c new file mode 100644 index 00000000000..792d9723c75 --- /dev/null +++ b/cmd/misc.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020 Wind River Systems, Inc. + * + * Author: + * Bin Meng <bin.meng@windriver.com> + * + * A command interface to access misc devices with MISC uclass driver APIs. + */ + +#include <command.h> +#include <dm.h> +#include <errno.h> +#include <misc.h> + +enum misc_op { + MISC_OP_READ, + MISC_OP_WRITE +}; + +static char *misc_op_str[] = { + "read", + "write" +}; + +static int do_misc_list(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct udevice *dev; + + printf("Device Index Driver\n"); + printf("-------------------------------------\n"); + for (uclass_first_device(UCLASS_MISC, &dev); + dev; + uclass_next_device(&dev)) { + printf("%-20s %5d %10s\n", dev->name, dev_seq(dev), + dev->driver->name); + } + + return 0; +} + +static int do_misc_op(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[], enum misc_op op) +{ + struct udevice *dev; + int offset; + void *buf; + int size; + int ret; + + ret = uclass_get_device_by_name(UCLASS_MISC, argv[0], &dev); + if (ret) { + printf("Unable to find device %s\n", argv[0]); + return ret; + } + + offset = hextoul(argv[1], NULL); + buf = (void *)hextoul(argv[2], NULL); + size = hextoul(argv[3], NULL); + + if (op == MISC_OP_READ) + ret = misc_read(dev, offset, buf, size); + else + ret = misc_write(dev, offset, buf, size); + + if (ret < 0) { + if (ret == -ENOSYS) { + printf("The device does not support %s\n", + misc_op_str[op]); + ret = 0; + } + } else { + if (ret == size) + ret = 0; + else + printf("Partially %s %d bytes\n", misc_op_str[op], ret); + } + + return ret; +} + +static int do_misc_read(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + return do_misc_op(cmdtp, flag, argc, argv, MISC_OP_READ); +} + +static int do_misc_write(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + return do_misc_op(cmdtp, flag, argc, argv, MISC_OP_WRITE); +} + +static struct cmd_tbl misc_commands[] = { + U_BOOT_CMD_MKENT(list, 0, 1, do_misc_list, "", ""), + U_BOOT_CMD_MKENT(read, 4, 1, do_misc_read, "", ""), + U_BOOT_CMD_MKENT(write, 4, 1, do_misc_write, "", ""), +}; + +static int do_misc(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct cmd_tbl *misc_cmd; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + misc_cmd = find_cmd_tbl(argv[1], misc_commands, + ARRAY_SIZE(misc_commands)); + argc -= 2; + argv += 2; + if (!misc_cmd || argc != misc_cmd->maxargs) + return CMD_RET_USAGE; + + ret = misc_cmd->cmd(misc_cmd, flag, argc, argv); + + return cmd_process_error(misc_cmd, ret); +} + +U_BOOT_CMD( + misc, 6, 1, do_misc, + "Access miscellaneous devices with MISC uclass driver APIs", + "list - list all miscellaneous devices\n" + "misc read name offset addr len - read `len' bytes starting at\n" + " `offset' of device `name'\n" + " to memory at `addr'\n" + "misc write name offset addr len - write `len' bytes starting at\n" + " `offset' of device `name'\n" + " from memory at `addr'" +); diff --git a/cmd/mmc.c b/cmd/mmc.c new file mode 100644 index 00000000000..5340a58be8e --- /dev/null +++ b/cmd/mmc.c @@ -0,0 +1,1368 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2003 + * Kyle Harris, kharris@nexus-tech.net + */ + +#include <blk.h> +#include <command.h> +#include <console.h> +#include <display_options.h> +#include <env.h> +#include <mapmem.h> +#include <memalign.h> +#include <mmc.h> +#include <part.h> +#include <sparse_format.h> +#include <image-sparse.h> +#include <vsprintf.h> +#include <linux/ctype.h> + +static int curr_device = -1; + +static void print_mmcinfo(struct mmc *mmc) +{ + int i; + + printf("Device: %s\n", mmc->cfg->name); + printf("Manufacturer ID: %x\n", mmc->cid[0] >> 24); + if (IS_SD(mmc)) { + printf("OEM: %x\n", (mmc->cid[0] >> 8) & 0xffff); + printf("Name: %c%c%c%c%c \n", mmc->cid[0] & 0xff, + (mmc->cid[1] >> 24), (mmc->cid[1] >> 16) & 0xff, + (mmc->cid[1] >> 8) & 0xff, mmc->cid[1] & 0xff); + } else { + printf("OEM: %x\n", (mmc->cid[0] >> 8) & 0xff); + printf("Name: %c%c%c%c%c%c \n", mmc->cid[0] & 0xff, + (mmc->cid[1] >> 24), (mmc->cid[1] >> 16) & 0xff, + (mmc->cid[1] >> 8) & 0xff, mmc->cid[1] & 0xff, + (mmc->cid[2] >> 24)); + } + + printf("Bus Speed: %d\n", mmc->clock); +#if CONFIG_IS_ENABLED(MMC_VERBOSE) + printf("Mode: %s\n", mmc_mode_name(mmc->selected_mode)); + mmc_dump_capabilities("card capabilities", mmc->card_caps); + mmc_dump_capabilities("host capabilities", mmc->host_caps); +#endif + printf("Rd Block Len: %d\n", mmc->read_bl_len); + + printf("%s version %d.%d", IS_SD(mmc) ? "SD" : "MMC", + EXTRACT_SDMMC_MAJOR_VERSION(mmc->version), + EXTRACT_SDMMC_MINOR_VERSION(mmc->version)); + if (EXTRACT_SDMMC_CHANGE_VERSION(mmc->version) != 0) + printf(".%d", EXTRACT_SDMMC_CHANGE_VERSION(mmc->version)); + printf("\n"); + + printf("High Capacity: %s\n", mmc->high_capacity ? "Yes" : "No"); + puts("Capacity: "); + print_size(mmc->capacity, "\n"); + + printf("Bus Width: %d-bit%s\n", mmc->bus_width, + mmc->ddr_mode ? " DDR" : ""); + +#if CONFIG_IS_ENABLED(MMC_WRITE) + puts("Erase Group Size: "); + print_size(((u64)mmc->erase_grp_size) << 9, "\n"); +#endif + + if (!IS_SD(mmc) && mmc->version >= MMC_VERSION_4_41) { + bool has_enh = (mmc->part_support & ENHNCD_SUPPORT) != 0; + bool usr_enh = has_enh && (mmc->part_attr & EXT_CSD_ENH_USR); + ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN); + u8 wp; + int ret; + +#if CONFIG_IS_ENABLED(MMC_HW_PARTITIONING) + puts("HC WP Group Size: "); + print_size(((u64)mmc->hc_wp_grp_size) << 9, "\n"); +#endif + + puts("User Capacity: "); + print_size(mmc->capacity_user, usr_enh ? " ENH" : ""); + if (mmc->wr_rel_set & EXT_CSD_WR_DATA_REL_USR) + puts(" WRREL\n"); + else + putc('\n'); + if (usr_enh) { + puts("User Enhanced Start: "); + print_size(mmc->enh_user_start, "\n"); + puts("User Enhanced Size: "); + print_size(mmc->enh_user_size, "\n"); + } + puts("Boot Capacity: "); + print_size(mmc->capacity_boot, has_enh ? " ENH\n" : "\n"); + puts("RPMB Capacity: "); + print_size(mmc->capacity_rpmb, has_enh ? " ENH\n" : "\n"); + + for (i = 0; i < ARRAY_SIZE(mmc->capacity_gp); i++) { + bool is_enh = has_enh && + (mmc->part_attr & EXT_CSD_ENH_GP(i)); + if (mmc->capacity_gp[i]) { + printf("GP%i Capacity: ", i+1); + print_size(mmc->capacity_gp[i], + is_enh ? " ENH" : ""); + if (mmc->wr_rel_set & EXT_CSD_WR_DATA_REL_GP(i)) + puts(" WRREL\n"); + else + putc('\n'); + } + } + ret = mmc_send_ext_csd(mmc, ext_csd); + if (ret) + return; + wp = ext_csd[EXT_CSD_BOOT_WP_STATUS]; + for (i = 0; i < 2; ++i) { + printf("Boot area %d is ", i); + switch (wp & 3) { + case 0: + printf("not write protected\n"); + break; + case 1: + printf("power on protected\n"); + break; + case 2: + printf("permanently protected\n"); + break; + default: + printf("in reserved protection state\n"); + break; + } + wp >>= 2; + } + } +} + +static struct mmc *__init_mmc_device(int dev, bool force_init, + enum bus_mode speed_mode) +{ + struct mmc *mmc; + mmc = find_mmc_device(dev); + if (!mmc) { + printf("no mmc device at slot %x\n", dev); + return NULL; + } + + if (!mmc_getcd(mmc)) + force_init = true; + + if (force_init) + mmc->has_init = 0; + + if (IS_ENABLED(CONFIG_MMC_SPEED_MODE_SET)) + mmc->user_speed_mode = speed_mode; + + if (mmc_init(mmc)) + return NULL; + +#ifdef CONFIG_BLOCK_CACHE + struct blk_desc *bd = mmc_get_blk_desc(mmc); + blkcache_invalidate(bd->uclass_id, bd->devnum); +#endif + + return mmc; +} + +static struct mmc *init_mmc_device(int dev, bool force_init) +{ + return __init_mmc_device(dev, force_init, MMC_MODES_END); +} + +static int do_mmcinfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mmc *mmc; + + if (curr_device < 0) { + if (get_mmc_num() > 0) + curr_device = 0; + else { + puts("No MMC device available\n"); + return CMD_RET_FAILURE; + } + } + + mmc = init_mmc_device(curr_device, false); + if (!mmc) + return CMD_RET_FAILURE; + + print_mmcinfo(mmc); + return CMD_RET_SUCCESS; +} + +#if CONFIG_IS_ENABLED(CMD_MMC_RPMB) +static int confirm_key_prog(void) +{ + puts("Warning: Programming authentication key can be done only once !\n" + " Use this command only if you are sure of what you are doing,\n" + "Really perform the key programming? <y/N> "); + if (confirm_yesno()) + return 1; + + puts("Authentication key programming aborted\n"); + return 0; +} + +static int do_mmcrpmb_key(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + void *key_addr; + struct mmc *mmc = find_mmc_device(curr_device); + + if (argc != 2) + return CMD_RET_USAGE; + + key_addr = (void *)hextoul(argv[1], NULL); + if (!confirm_key_prog()) + return CMD_RET_FAILURE; + if (mmc_rpmb_set_key(mmc, key_addr)) { + printf("ERROR - Key already programmed ?\n"); + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; +} + +static int do_mmcrpmb_read(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + u16 blk, cnt; + void *addr; + int n; + void *key_addr = NULL; + struct mmc *mmc = find_mmc_device(curr_device); + + if (argc < 4) + return CMD_RET_USAGE; + + addr = (void *)hextoul(argv[1], NULL); + blk = hextoul(argv[2], NULL); + cnt = hextoul(argv[3], NULL); + + if (argc == 5) + key_addr = (void *)hextoul(argv[4], NULL); + + printf("MMC RPMB read: dev # %d, block # %d, count %d ... ", + curr_device, blk, cnt); + n = mmc_rpmb_read(mmc, addr, blk, cnt, key_addr); + + printf("%d RPMB blocks read: %s\n", n, (n == cnt) ? "OK" : "ERROR"); + if (n != cnt) + return CMD_RET_FAILURE; + return CMD_RET_SUCCESS; +} + +static int do_mmcrpmb_write(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + u16 blk, cnt; + void *addr; + int n; + void *key_addr; + struct mmc *mmc = find_mmc_device(curr_device); + + if (argc != 5) + return CMD_RET_USAGE; + + addr = (void *)hextoul(argv[1], NULL); + blk = hextoul(argv[2], NULL); + cnt = hextoul(argv[3], NULL); + key_addr = (void *)hextoul(argv[4], NULL); + + printf("MMC RPMB write: dev # %d, block # %d, count %d ... ", + curr_device, blk, cnt); + n = mmc_rpmb_write(mmc, addr, blk, cnt, key_addr); + + printf("%d RPMB blocks written: %s\n", n, (n == cnt) ? "OK" : "ERROR"); + if (n != cnt) + return CMD_RET_FAILURE; + return CMD_RET_SUCCESS; +} + +static int do_mmcrpmb_counter(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + unsigned long counter; + struct mmc *mmc = find_mmc_device(curr_device); + + if (mmc_rpmb_get_counter(mmc, &counter)) + return CMD_RET_FAILURE; + printf("RPMB Write counter= %lx\n", counter); + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl cmd_rpmb[] = { + U_BOOT_CMD_MKENT(key, 2, 0, do_mmcrpmb_key, "", ""), + U_BOOT_CMD_MKENT(read, 5, 1, do_mmcrpmb_read, "", ""), + U_BOOT_CMD_MKENT(write, 5, 0, do_mmcrpmb_write, "", ""), + U_BOOT_CMD_MKENT(counter, 1, 1, do_mmcrpmb_counter, "", ""), +}; + +static int do_mmcrpmb(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct cmd_tbl *cp; + struct mmc *mmc; + char original_part; + int ret; + + cp = find_cmd_tbl(argv[1], cmd_rpmb, ARRAY_SIZE(cmd_rpmb)); + + /* Drop the rpmb subcommand */ + argc--; + argv++; + + if (cp == NULL || argc > cp->maxargs) + return CMD_RET_USAGE; + if (flag == CMD_FLAG_REPEAT && !cmd_is_repeatable(cp)) + return CMD_RET_SUCCESS; + + mmc = init_mmc_device(curr_device, false); + if (!mmc) + return CMD_RET_FAILURE; + + if (!(mmc->version & MMC_VERSION_MMC)) { + printf("It is not an eMMC device\n"); + return CMD_RET_FAILURE; + } + if (mmc->version < MMC_VERSION_4_41) { + printf("RPMB not supported before version 4.41\n"); + return CMD_RET_FAILURE; + } + /* Switch to the RPMB partition */ +#ifndef CONFIG_BLK + original_part = mmc->block_dev.hwpart; +#else + original_part = mmc_get_blk_desc(mmc)->hwpart; +#endif + if (blk_select_hwpart_devnum(UCLASS_MMC, curr_device, MMC_PART_RPMB) != + 0) + return CMD_RET_FAILURE; + ret = cp->cmd(cmdtp, flag, argc, argv); + + /* Return to original partition */ + if (blk_select_hwpart_devnum(UCLASS_MMC, curr_device, original_part) != + 0) + return CMD_RET_FAILURE; + return ret; +} +#endif + +static int do_mmc_read(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct mmc *mmc; + u32 blk, cnt, n; + void *ptr; + + if (argc != 4) + return CMD_RET_USAGE; + + ptr = map_sysmem(hextoul(argv[1], NULL), 0); + blk = hextoul(argv[2], NULL); + cnt = hextoul(argv[3], NULL); + + mmc = init_mmc_device(curr_device, false); + if (!mmc) + return CMD_RET_FAILURE; + + printf("MMC read: dev # %d, block # %d, count %d ... ", + curr_device, blk, cnt); + + n = blk_dread(mmc_get_blk_desc(mmc), blk, cnt, ptr); + printf("%d blocks read: %s\n", n, (n == cnt) ? "OK" : "ERROR"); + unmap_sysmem(ptr); + + return (n == cnt) ? CMD_RET_SUCCESS : CMD_RET_FAILURE; +} + +#if CONFIG_IS_ENABLED(CMD_MMC_SWRITE) +static lbaint_t mmc_sparse_write(struct sparse_storage *info, lbaint_t blk, + lbaint_t blkcnt, const void *buffer) +{ + struct blk_desc *dev_desc = info->priv; + + return blk_dwrite(dev_desc, blk, blkcnt, buffer); +} + +static lbaint_t mmc_sparse_reserve(struct sparse_storage *info, + lbaint_t blk, lbaint_t blkcnt) +{ + return blkcnt; +} + +static int do_mmc_sparse_write(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct sparse_storage sparse; + struct blk_desc *dev_desc; + struct mmc *mmc; + char dest[11]; + void *addr; + u32 blk; + + if (argc != 3) + return CMD_RET_USAGE; + + addr = (void *)hextoul(argv[1], NULL); + blk = hextoul(argv[2], NULL); + + if (!is_sparse_image(addr)) { + printf("Not a sparse image\n"); + return CMD_RET_FAILURE; + } + + mmc = init_mmc_device(curr_device, false); + if (!mmc) + return CMD_RET_FAILURE; + + printf("MMC Sparse write: dev # %d, block # %d ... ", + curr_device, blk); + + if (mmc_getwp(mmc) == 1) { + printf("Error: card is write protected!\n"); + return CMD_RET_FAILURE; + } + + dev_desc = mmc_get_blk_desc(mmc); + sparse.priv = dev_desc; + sparse.blksz = 512; + sparse.start = blk; + sparse.size = dev_desc->lba - blk; + sparse.write = mmc_sparse_write; + sparse.reserve = mmc_sparse_reserve; + sparse.mssg = NULL; + sprintf(dest, "0x" LBAF, sparse.start * sparse.blksz); + + if (write_sparse_image(&sparse, dest, addr, NULL)) + return CMD_RET_FAILURE; + else + return CMD_RET_SUCCESS; +} +#endif + +#if CONFIG_IS_ENABLED(MMC_WRITE) +static int do_mmc_write(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct mmc *mmc; + u32 blk, cnt, n; + void *ptr; + + if (argc != 4) + return CMD_RET_USAGE; + + ptr = map_sysmem(hextoul(argv[1], NULL), 0); + blk = hextoul(argv[2], NULL); + cnt = hextoul(argv[3], NULL); + + mmc = init_mmc_device(curr_device, false); + if (!mmc) + return CMD_RET_FAILURE; + + printf("MMC write: dev # %d, block # %d, count %d ... ", + curr_device, blk, cnt); + + if (mmc_getwp(mmc) == 1) { + printf("Error: card is write protected!\n"); + return CMD_RET_FAILURE; + } + n = blk_dwrite(mmc_get_blk_desc(mmc), blk, cnt, ptr); + printf("%d blocks written: %s\n", n, (n == cnt) ? "OK" : "ERROR"); + unmap_sysmem(ptr); + + return (n == cnt) ? CMD_RET_SUCCESS : CMD_RET_FAILURE; +} + +static int do_mmc_erase(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct mmc *mmc; + struct disk_partition info; + u32 blk, cnt, n; + + if (argc < 2 || argc > 3) + return CMD_RET_USAGE; + + mmc = init_mmc_device(curr_device, false); + if (!mmc) + return CMD_RET_FAILURE; + + if (argc == 3) { + blk = hextoul(argv[1], NULL); + cnt = hextoul(argv[2], NULL); + } else if (part_get_info_by_name(mmc_get_blk_desc(mmc), argv[1], &info) >= 0) { + blk = info.start; + cnt = info.size; + } else { + return CMD_RET_FAILURE; + } + + printf("MMC erase: dev # %d, block # %d, count %d ... ", + curr_device, blk, cnt); + + if (mmc_getwp(mmc) == 1) { + printf("Error: card is write protected!\n"); + return CMD_RET_FAILURE; + } + n = blk_derase(mmc_get_blk_desc(mmc), blk, cnt); + printf("%d blocks erased: %s\n", n, (n == cnt) ? "OK" : "ERROR"); + + return (n == cnt) ? CMD_RET_SUCCESS : CMD_RET_FAILURE; +} +#endif + +static int do_mmc_rescan(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct mmc *mmc; + + if (argc == 1) { + mmc = init_mmc_device(curr_device, true); + } else if (argc == 2) { + enum bus_mode speed_mode; + + speed_mode = (int)dectoul(argv[1], NULL); + mmc = __init_mmc_device(curr_device, true, speed_mode); + } else { + return CMD_RET_USAGE; + } + + if (!mmc) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +static int do_mmc_part(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct blk_desc *mmc_dev; + struct mmc *mmc; + + mmc = init_mmc_device(curr_device, false); + if (!mmc) + return CMD_RET_FAILURE; + + mmc_dev = blk_get_devnum_by_uclass_id(UCLASS_MMC, curr_device); + if (mmc_dev != NULL && mmc_dev->type != DEV_TYPE_UNKNOWN) { + part_print(mmc_dev); + return CMD_RET_SUCCESS; + } + + puts("get mmc type error!\n"); + return CMD_RET_FAILURE; +} + +static int do_mmc_dev(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int dev, part = 0, ret; + struct mmc *mmc; + + if (argc == 1) { + dev = curr_device; + mmc = init_mmc_device(dev, true); + } else if (argc == 2) { + dev = (int)dectoul(argv[1], NULL); + mmc = init_mmc_device(dev, true); + } else if (argc == 3) { + dev = (int)dectoul(argv[1], NULL); + part = (int)dectoul(argv[2], NULL); + if (part > PART_ACCESS_MASK) { + printf("#part_num shouldn't be larger than %d\n", + PART_ACCESS_MASK); + return CMD_RET_FAILURE; + } + mmc = init_mmc_device(dev, true); + } else if (argc == 4) { + enum bus_mode speed_mode; + + dev = (int)dectoul(argv[1], NULL); + part = (int)dectoul(argv[2], NULL); + if (part > PART_ACCESS_MASK) { + printf("#part_num shouldn't be larger than %d\n", + PART_ACCESS_MASK); + return CMD_RET_FAILURE; + } + speed_mode = (int)dectoul(argv[3], NULL); + mmc = __init_mmc_device(dev, true, speed_mode); + } else { + return CMD_RET_USAGE; + } + + if (!mmc) + return CMD_RET_FAILURE; + + ret = blk_select_hwpart_devnum(UCLASS_MMC, dev, part); + printf("switch to partitions #%d, %s\n", + part, (!ret) ? "OK" : "ERROR"); + if (ret) + return 1; + + curr_device = dev; + if (mmc->part_config == MMCPART_NOAVAILABLE) + printf("mmc%d is current device\n", curr_device); + else + printf("mmc%d(part %d) is current device\n", + curr_device, mmc_get_blk_desc(mmc)->hwpart); + + return CMD_RET_SUCCESS; +} + +static int do_mmc_list(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + print_mmc_devices('\n'); + return CMD_RET_SUCCESS; +} + +#if CONFIG_IS_ENABLED(MMC_HW_PARTITIONING) +static void parse_hwpart_user_enh_size(struct mmc *mmc, + struct mmc_hwpart_conf *pconf, + char *argv) +{ + int i, ret; + + pconf->user.enh_size = 0; + + if (!strcmp(argv, "-")) { /* The rest of eMMC */ + ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN); + ret = mmc_send_ext_csd(mmc, ext_csd); + if (ret) + return; + /* The enh_size value is in 512B block units */ + pconf->user.enh_size = + ((ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT + 2] << 16) + + (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT + 1] << 8) + + ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT]) * 1024 * + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * + ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + pconf->user.enh_size -= pconf->user.enh_start; + for (i = 0; i < ARRAY_SIZE(mmc->capacity_gp); i++) { + /* + * If the eMMC already has GP partitions set, + * subtract their size from the maximum USER + * partition size. + * + * Else, if the command was used to configure new + * GP partitions, subtract their size from maximum + * USER partition size. + */ + if (mmc->capacity_gp[i]) { + /* The capacity_gp is in 1B units */ + pconf->user.enh_size -= mmc->capacity_gp[i] >> 9; + } else if (pconf->gp_part[i].size) { + /* The gp_part[].size is in 512B units */ + pconf->user.enh_size -= pconf->gp_part[i].size; + } + } + } else { + pconf->user.enh_size = dectoul(argv, NULL); + } +} + +static int parse_hwpart_user(struct mmc *mmc, struct mmc_hwpart_conf *pconf, + int argc, char *const argv[]) +{ + int i = 0; + + memset(&pconf->user, 0, sizeof(pconf->user)); + + while (i < argc) { + if (!strcmp(argv[i], "enh")) { + if (i + 2 >= argc) + return -1; + pconf->user.enh_start = + dectoul(argv[i + 1], NULL); + parse_hwpart_user_enh_size(mmc, pconf, argv[i + 2]); + i += 3; + } else if (!strcmp(argv[i], "wrrel")) { + if (i + 1 >= argc) + return -1; + pconf->user.wr_rel_change = 1; + if (!strcmp(argv[i+1], "on")) + pconf->user.wr_rel_set = 1; + else if (!strcmp(argv[i+1], "off")) + pconf->user.wr_rel_set = 0; + else + return -1; + i += 2; + } else { + break; + } + } + return i; +} + +static int parse_hwpart_gp(struct mmc_hwpart_conf *pconf, int pidx, + int argc, char *const argv[]) +{ + int i; + + memset(&pconf->gp_part[pidx], 0, sizeof(pconf->gp_part[pidx])); + + if (1 >= argc) + return -1; + pconf->gp_part[pidx].size = dectoul(argv[0], NULL); + + i = 1; + while (i < argc) { + if (!strcmp(argv[i], "enh")) { + pconf->gp_part[pidx].enhanced = 1; + i += 1; + } else if (!strcmp(argv[i], "wrrel")) { + if (i + 1 >= argc) + return -1; + pconf->gp_part[pidx].wr_rel_change = 1; + if (!strcmp(argv[i+1], "on")) + pconf->gp_part[pidx].wr_rel_set = 1; + else if (!strcmp(argv[i+1], "off")) + pconf->gp_part[pidx].wr_rel_set = 0; + else + return -1; + i += 2; + } else { + break; + } + } + return i; +} + +static int do_mmc_hwpartition(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct mmc *mmc; + struct mmc_hwpart_conf pconf = { }; + enum mmc_hwpart_conf_mode mode = MMC_HWPART_CONF_CHECK; + int i, r, pidx; + + mmc = init_mmc_device(curr_device, false); + if (!mmc) + return CMD_RET_FAILURE; + + if (IS_SD(mmc)) { + puts("SD doesn't support partitioning\n"); + return CMD_RET_FAILURE; + } + + if (argc < 1) + return CMD_RET_USAGE; + i = 1; + while (i < argc) { + if (!strcmp(argv[i], "user")) { + i++; + r = parse_hwpart_user(mmc, &pconf, argc - i, &argv[i]); + if (r < 0) + return CMD_RET_USAGE; + i += r; + } else if (!strncmp(argv[i], "gp", 2) && + strlen(argv[i]) == 3 && + argv[i][2] >= '1' && argv[i][2] <= '4') { + pidx = argv[i][2] - '1'; + i++; + r = parse_hwpart_gp(&pconf, pidx, argc-i, &argv[i]); + if (r < 0) + return CMD_RET_USAGE; + i += r; + } else if (!strcmp(argv[i], "check")) { + mode = MMC_HWPART_CONF_CHECK; + i++; + } else if (!strcmp(argv[i], "set")) { + mode = MMC_HWPART_CONF_SET; + i++; + } else if (!strcmp(argv[i], "complete")) { + mode = MMC_HWPART_CONF_COMPLETE; + i++; + } else { + return CMD_RET_USAGE; + } + } + + puts("Partition configuration:\n"); + if (pconf.user.enh_size) { + puts("\tUser Enhanced Start: "); + print_size(((u64)pconf.user.enh_start) << 9, "\n"); + puts("\tUser Enhanced Size: "); + print_size(((u64)pconf.user.enh_size) << 9, "\n"); + } else { + puts("\tNo enhanced user data area\n"); + } + if (pconf.user.wr_rel_change) + printf("\tUser partition write reliability: %s\n", + pconf.user.wr_rel_set ? "on" : "off"); + for (pidx = 0; pidx < 4; pidx++) { + if (pconf.gp_part[pidx].size) { + printf("\tGP%i Capacity: ", pidx+1); + print_size(((u64)pconf.gp_part[pidx].size) << 9, + pconf.gp_part[pidx].enhanced ? + " ENH\n" : "\n"); + } else { + printf("\tNo GP%i partition\n", pidx+1); + } + if (pconf.gp_part[pidx].wr_rel_change) + printf("\tGP%i write reliability: %s\n", pidx+1, + pconf.gp_part[pidx].wr_rel_set ? "on" : "off"); + } + + if (!mmc_hwpart_config(mmc, &pconf, mode)) { + if (mode == MMC_HWPART_CONF_COMPLETE) + puts("Partitioning successful, " + "power-cycle to make effective\n"); + return CMD_RET_SUCCESS; + } else { + puts("Failed!\n"); + return CMD_RET_FAILURE; + } +} +#endif + +#ifdef CONFIG_SUPPORT_EMMC_BOOT +static int do_mmc_bootbus(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int dev; + struct mmc *mmc; + u8 width, reset, mode; + + if (argc != 5) + return CMD_RET_USAGE; + dev = dectoul(argv[1], NULL); + width = dectoul(argv[2], NULL); + reset = dectoul(argv[3], NULL); + mode = dectoul(argv[4], NULL); + + mmc = init_mmc_device(dev, false); + if (!mmc) + return CMD_RET_FAILURE; + + if (IS_SD(mmc)) { + puts("BOOT_BUS_WIDTH only exists on eMMC\n"); + return CMD_RET_FAILURE; + } + + /* + * BOOT_BUS_CONDITIONS[177] + * BOOT_MODE[4:3] + * 0x0 : Use SDR + Backward compatible timing in boot operation + * 0x1 : Use SDR + High Speed Timing in boot operation mode + * 0x2 : Use DDR in boot operation + * RESET_BOOT_BUS_CONDITIONS + * 0x0 : Reset bus width to x1, SDR, Backward compatible + * 0x1 : Retain BOOT_BUS_WIDTH and BOOT_MODE + * BOOT_BUS_WIDTH + * 0x0 : x1(sdr) or x4 (ddr) buswidth + * 0x1 : x4(sdr/ddr) buswith + * 0x2 : x8(sdr/ddr) buswith + * + */ + if (width >= 0x3) { + printf("boot_bus_width %d is invalid\n", width); + return CMD_RET_FAILURE; + } + + if (reset >= 0x2) { + printf("reset_boot_bus_width %d is invalid\n", reset); + return CMD_RET_FAILURE; + } + + if (mode >= 0x3) { + printf("reset_boot_bus_width %d is invalid\n", mode); + return CMD_RET_FAILURE; + } + + /* acknowledge to be sent during boot operation */ + if (mmc_set_boot_bus_width(mmc, width, reset, mode)) { + puts("BOOT_BUS_WIDTH is failed to change.\n"); + return CMD_RET_FAILURE; + } + + printf("Set to BOOT_BUS_WIDTH = 0x%x, RESET = 0x%x, BOOT_MODE = 0x%x\n", + width, reset, mode); + return CMD_RET_SUCCESS; +} + +static int do_mmc_boot_resize(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int dev; + struct mmc *mmc; + u32 bootsize, rpmbsize; + + if (argc != 4) + return CMD_RET_USAGE; + dev = dectoul(argv[1], NULL); + bootsize = dectoul(argv[2], NULL); + rpmbsize = dectoul(argv[3], NULL); + + mmc = init_mmc_device(dev, false); + if (!mmc) + return CMD_RET_FAILURE; + + if (IS_SD(mmc)) { + printf("It is not an eMMC device\n"); + return CMD_RET_FAILURE; + } + + if (mmc_boot_partition_size_change(mmc, bootsize, rpmbsize)) { + printf("EMMC boot partition Size change Failed.\n"); + return CMD_RET_FAILURE; + } + + printf("EMMC boot partition Size %d MB\n", bootsize); + printf("EMMC RPMB partition Size %d MB\n", rpmbsize); + return CMD_RET_SUCCESS; +} + +static int mmc_partconf_print(struct mmc *mmc, const char *varname) +{ + u8 ack, access, part; + + if (mmc->part_config == MMCPART_NOAVAILABLE) { + printf("No part_config info for ver. 0x%x\n", mmc->version); + return CMD_RET_FAILURE; + } + + access = EXT_CSD_EXTRACT_PARTITION_ACCESS(mmc->part_config); + ack = EXT_CSD_EXTRACT_BOOT_ACK(mmc->part_config); + part = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config); + + if(varname) + env_set_hex(varname, part); + + printf("EXT_CSD[179], PARTITION_CONFIG:\n" + "BOOT_ACK: 0x%x\n" + "BOOT_PARTITION_ENABLE: 0x%x (%s)\n" + "PARTITION_ACCESS: 0x%x (%s)\n", ack, part, emmc_boot_part_names[part], + access, emmc_hwpart_names[access]); + + return CMD_RET_SUCCESS; +} + +static int do_mmc_partconf(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int ret, dev; + struct mmc *mmc; + u8 ack, part_num, access; + + if (argc != 2 && argc != 3 && argc != 5) + return CMD_RET_USAGE; + + dev = dectoul(argv[1], NULL); + + mmc = init_mmc_device(dev, false); + if (!mmc) + return CMD_RET_FAILURE; + + if (IS_SD(mmc)) { + puts("PARTITION_CONFIG only exists on eMMC\n"); + return CMD_RET_FAILURE; + } + + if (argc == 2 || argc == 3) + return mmc_partconf_print(mmc, cmd_arg2(argc, argv)); + + /* BOOT_ACK */ + ack = dectoul(argv[2], NULL); + /* BOOT_PARTITION_ENABLE */ + if (!isdigit(*argv[3])) { + for (part_num = ARRAY_SIZE(emmc_boot_part_names) - 1; part_num > 0; part_num--) { + if (!strcmp(argv[3], emmc_boot_part_names[part_num])) + break; + } + } else { + part_num = dectoul(argv[3], NULL); + } + /* PARTITION_ACCESS */ + if (!isdigit(*argv[4])) { + for (access = ARRAY_SIZE(emmc_hwpart_names) - 1; access > 0; access--) { + if (!strcmp(argv[4], emmc_hwpart_names[access])) + break; + } + } else { + access = dectoul(argv[4], NULL); + } + + /* acknowledge to be sent during boot operation */ + ret = mmc_set_part_conf(mmc, ack, part_num, access); + if (ret != 0) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +static int do_mmc_rst_func(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int ret, dev; + struct mmc *mmc; + u8 enable; + + /* + * Set the RST_n_ENABLE bit of RST_n_FUNCTION + * The only valid values are 0x0, 0x1 and 0x2 and writing + * a value of 0x1 or 0x2 sets the value permanently. + */ + if (argc != 3) + return CMD_RET_USAGE; + + dev = dectoul(argv[1], NULL); + enable = dectoul(argv[2], NULL); + + if (enable > 2) { + puts("Invalid RST_n_ENABLE value\n"); + return CMD_RET_USAGE; + } + + mmc = init_mmc_device(dev, false); + if (!mmc) + return CMD_RET_FAILURE; + + if (IS_SD(mmc)) { + puts("RST_n_FUNCTION only exists on eMMC\n"); + return CMD_RET_FAILURE; + } + + ret = mmc_set_rst_n_function(mmc, enable); + if (ret != 0) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} +#endif +static int do_mmc_setdsr(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct mmc *mmc; + u32 val; + int ret; + + if (argc != 2) + return CMD_RET_USAGE; + val = hextoul(argv[1], NULL); + + mmc = find_mmc_device(curr_device); + if (!mmc) { + printf("no mmc device at slot %x\n", curr_device); + return CMD_RET_FAILURE; + } + ret = mmc_set_dsr(mmc, val); + printf("set dsr %s\n", (!ret) ? "OK, force rescan" : "ERROR"); + if (!ret) { + mmc->has_init = 0; + if (mmc_init(mmc)) + return CMD_RET_FAILURE; + else + return CMD_RET_SUCCESS; + } + return ret; +} + +#ifdef CONFIG_CMD_BKOPS_ENABLE +static int mmc_bkops_common(char *device, bool autobkops, bool enable) +{ + struct mmc *mmc; + int dev; + + dev = dectoul(device, NULL); + + mmc = init_mmc_device(dev, false); + if (!mmc) + return CMD_RET_FAILURE; + + if (IS_SD(mmc)) { + puts("BKOPS_EN only exists on eMMC\n"); + return CMD_RET_FAILURE; + } + + return mmc_set_bkops_enable(mmc, autobkops, enable); +} + +static int do_mmc_bkops(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + bool autobkops, enable; + + if (argc != 4) + return CMD_RET_USAGE; + + if (!strcmp(argv[2], "manual")) + autobkops = false; + else if (!strcmp(argv[2], "auto")) + autobkops = true; + else + return CMD_RET_FAILURE; + + if (!strcmp(argv[3], "disable")) + enable = false; + else if (!strcmp(argv[3], "enable")) + enable = true; + else + return CMD_RET_FAILURE; + + return mmc_bkops_common(argv[1], autobkops, enable); +} + +static int do_mmc_bkops_enable(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + if (argc != 2) + return CMD_RET_USAGE; + + return mmc_bkops_common(argv[1], false, true); +} +#endif + +static int do_mmc_boot_wp(struct cmd_tbl *cmdtp, int flag, + int argc, char * const argv[]) +{ + int err; + struct mmc *mmc; + int part; + + mmc = init_mmc_device(curr_device, false); + if (!mmc) + return CMD_RET_FAILURE; + if (IS_SD(mmc)) { + printf("It is not an eMMC device\n"); + return CMD_RET_FAILURE; + } + + if (argc == 2) { + part = dectoul(argv[1], NULL); + err = mmc_boot_wp_single_partition(mmc, part); + } else { + err = mmc_boot_wp(mmc); + } + + if (err) + return CMD_RET_FAILURE; + printf("boot areas protected\n"); + return CMD_RET_SUCCESS; +} + +#if CONFIG_IS_ENABLED(CMD_MMC_REG) +static int do_mmc_reg(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN); + struct mmc *mmc; + int i, ret; + u32 off; + + if (argc < 3 || argc > 5) + return CMD_RET_USAGE; + + mmc = find_mmc_device(curr_device); + if (!mmc) { + printf("no mmc device at slot %x\n", curr_device); + return CMD_RET_FAILURE; + } + + if (IS_SD(mmc)) { + printf("SD registers are not supported\n"); + return CMD_RET_FAILURE; + } + + off = simple_strtoul(argv[3], NULL, 10); + if (!strcmp(argv[2], "cid")) { + if (off > 3) + return CMD_RET_USAGE; + printf("CID[%i]: 0x%08x\n", off, mmc->cid[off]); + if (argv[4]) + env_set_hex(argv[4], mmc->cid[off]); + return CMD_RET_SUCCESS; + } + if (!strcmp(argv[2], "csd")) { + if (off > 3) + return CMD_RET_USAGE; + printf("CSD[%i]: 0x%08x\n", off, mmc->csd[off]); + if (argv[4]) + env_set_hex(argv[4], mmc->csd[off]); + return CMD_RET_SUCCESS; + } + if (!strcmp(argv[2], "dsr")) { + printf("DSR: 0x%08x\n", mmc->dsr); + if (argv[4]) + env_set_hex(argv[4], mmc->dsr); + return CMD_RET_SUCCESS; + } + if (!strcmp(argv[2], "ocr")) { + printf("OCR: 0x%08x\n", mmc->ocr); + if (argv[4]) + env_set_hex(argv[4], mmc->ocr); + return CMD_RET_SUCCESS; + } + if (!strcmp(argv[2], "rca")) { + printf("RCA: 0x%08x\n", mmc->rca); + if (argv[4]) + env_set_hex(argv[4], mmc->rca); + return CMD_RET_SUCCESS; + } + if (!strcmp(argv[2], "extcsd") && + mmc->version >= MMC_VERSION_4_41) { + ret = mmc_send_ext_csd(mmc, ext_csd); + if (ret) + return CMD_RET_FAILURE; + if (!strcmp(argv[3], "all")) { + /* Dump the entire register */ + printf("EXT_CSD:"); + for (i = 0; i < MMC_MAX_BLOCK_LEN; i++) { + if (!(i % 10)) + printf("\n%03i: ", i); + printf(" %02x", ext_csd[i]); + } + printf("\n"); + return CMD_RET_SUCCESS; + } + off = simple_strtoul(argv[3], NULL, 10); + if (off > 512) + return CMD_RET_USAGE; + printf("EXT_CSD[%i]: 0x%02x\n", off, ext_csd[off]); + if (argv[4]) + env_set_hex(argv[4], ext_csd[off]); + return CMD_RET_SUCCESS; + } + + return CMD_RET_FAILURE; +} +#endif + +static struct cmd_tbl cmd_mmc[] = { + U_BOOT_CMD_MKENT(info, 1, 0, do_mmcinfo, "", ""), + U_BOOT_CMD_MKENT(read, 4, 1, do_mmc_read, "", ""), + U_BOOT_CMD_MKENT(wp, 2, 0, do_mmc_boot_wp, "", ""), +#if CONFIG_IS_ENABLED(MMC_WRITE) + U_BOOT_CMD_MKENT(write, 4, 0, do_mmc_write, "", ""), + U_BOOT_CMD_MKENT(erase, 3, 0, do_mmc_erase, "", ""), +#endif +#if CONFIG_IS_ENABLED(CMD_MMC_SWRITE) + U_BOOT_CMD_MKENT(swrite, 3, 0, do_mmc_sparse_write, "", ""), +#endif + U_BOOT_CMD_MKENT(rescan, 2, 1, do_mmc_rescan, "", ""), + U_BOOT_CMD_MKENT(part, 1, 1, do_mmc_part, "", ""), + U_BOOT_CMD_MKENT(dev, 4, 0, do_mmc_dev, "", ""), + U_BOOT_CMD_MKENT(list, 1, 1, do_mmc_list, "", ""), +#if CONFIG_IS_ENABLED(MMC_HW_PARTITIONING) + U_BOOT_CMD_MKENT(hwpartition, 28, 0, do_mmc_hwpartition, "", ""), +#endif +#ifdef CONFIG_SUPPORT_EMMC_BOOT + U_BOOT_CMD_MKENT(bootbus, 5, 0, do_mmc_bootbus, "", ""), + U_BOOT_CMD_MKENT(bootpart-resize, 4, 0, do_mmc_boot_resize, "", ""), + U_BOOT_CMD_MKENT(partconf, 5, 0, do_mmc_partconf, "", ""), + U_BOOT_CMD_MKENT(rst-function, 3, 0, do_mmc_rst_func, "", ""), +#endif +#if CONFIG_IS_ENABLED(CMD_MMC_RPMB) + U_BOOT_CMD_MKENT(rpmb, CONFIG_SYS_MAXARGS, 1, do_mmcrpmb, "", ""), +#endif + U_BOOT_CMD_MKENT(setdsr, 2, 0, do_mmc_setdsr, "", ""), +#ifdef CONFIG_CMD_BKOPS_ENABLE + U_BOOT_CMD_MKENT(bkops-enable, 2, 0, do_mmc_bkops_enable, "", ""), + U_BOOT_CMD_MKENT(bkops, 4, 0, do_mmc_bkops, "", ""), +#endif +#if CONFIG_IS_ENABLED(CMD_MMC_REG) + U_BOOT_CMD_MKENT(reg, 5, 0, do_mmc_reg, "", ""), +#endif +}; + +static int do_mmcops(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cp; + + cp = find_cmd_tbl(argv[1], cmd_mmc, ARRAY_SIZE(cmd_mmc)); + + /* Drop the mmc command */ + argc--; + argv++; + + if (cp == NULL || argc > cp->maxargs) + return CMD_RET_USAGE; + if (flag == CMD_FLAG_REPEAT && !cmd_is_repeatable(cp)) + return CMD_RET_SUCCESS; + + if (curr_device < 0) { + if (get_mmc_num() > 0) { + curr_device = 0; + } else { + puts("No MMC device available\n"); + return CMD_RET_FAILURE; + } + } + return cp->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD( + mmc, 29, 1, do_mmcops, + "MMC sub system", + "info - display info of the current MMC device\n" + "mmc read addr blk# cnt\n" + "mmc write addr blk# cnt\n" +#if CONFIG_IS_ENABLED(CMD_MMC_SWRITE) + "mmc swrite addr blk#\n" +#endif + "mmc erase blk# cnt\n" + "mmc erase partname\n" + "mmc rescan [mode]\n" + "mmc part - lists available partition on current mmc device\n" + "mmc dev [dev] [part] [mode] - show or set current mmc device [partition] and set mode\n" + " - the required speed mode is passed as the index from the following list\n" + " [MMC_LEGACY, MMC_HS, SD_HS, MMC_HS_52, MMC_DDR_52, UHS_SDR12, UHS_SDR25,\n" + " UHS_SDR50, UHS_DDR50, UHS_SDR104, MMC_HS_200, MMC_HS_400, MMC_HS_400_ES]\n" + "mmc list - lists available devices\n" + "mmc wp [PART] - power on write protect boot partitions\n" + " arguments:\n" + " PART - [0|1]\n" + " : 0 - first boot partition, 1 - second boot partition\n" + " if not assigned, write protect all boot partitions\n" +#if CONFIG_IS_ENABLED(MMC_HW_PARTITIONING) + "mmc hwpartition <USER> <GP> <MODE> - does hardware partitioning\n" + " arguments (sizes in 512-byte blocks):\n" + " USER - <user> <enh> <start> <cnt> <wrrel> <{on|off}>\n" + " : sets user data area attributes\n" + " GP - <{gp1|gp2|gp3|gp4}> <cnt> <enh> <wrrel> <{on|off}>\n" + " : general purpose partition\n" + " MODE - <{check|set|complete}>\n" + " : mode, complete set partitioning completed\n" + " WARNING: Partitioning is a write-once setting once it is set to complete.\n" + " Power cycling is required to initialize partitions after set to complete.\n" +#endif +#ifdef CONFIG_SUPPORT_EMMC_BOOT + "mmc bootbus <dev> <boot_bus_width> <reset_boot_bus_width> <boot_mode>\n" + " - Set the BOOT_BUS_WIDTH field of the specified device\n" + "mmc bootpart-resize <dev> <boot part size MB> <RPMB part size MB>\n" + " - Change sizes of boot and RPMB partitions of specified device\n" + "mmc partconf <dev> [[varname] | [<boot_ack> <boot_partition> <partition_access>]]\n" + " - Show or change the bits of the PARTITION_CONFIG field of the specified device\n" + " If showing the bits, optionally store the boot_partition field into varname\n" + "mmc rst-function <dev> <value>\n" + " - Change the RST_n_FUNCTION field of the specified device\n" + " WARNING: This is a write-once field and 0 / 1 / 2 are the only valid values.\n" +#endif +#if CONFIG_IS_ENABLED(CMD_MMC_RPMB) + "mmc rpmb read addr blk# cnt [address of auth-key] - block size is 256 bytes\n" + "mmc rpmb write addr blk# cnt <address of auth-key> - block size is 256 bytes\n" + "mmc rpmb key <address of auth-key> - program the RPMB authentication key.\n" + "mmc rpmb counter - read the value of the write counter\n" +#endif + "mmc setdsr <value> - set DSR register value\n" +#ifdef CONFIG_CMD_BKOPS_ENABLE + "mmc bkops-enable <dev> - enable background operations handshake on device\n" + " WARNING: This is a write-once setting.\n" + "mmc bkops <dev> [auto|manual] [enable|disable]\n" + " - configure background operations handshake on device\n" +#endif +#if CONFIG_IS_ENABLED(CMD_MMC_REG) + "mmc reg read <reg> <offset> [env] - read card register <reg> offset <offset>\n" + " (optionally into [env] variable)\n" + " - reg: cid/csd/dsr/ocr/rca/extcsd\n" + " - offset: for cid/csd [0..3], for extcsd [0..511,all]\n" +#endif + ); + +/* Old command kept for compatibility. Same as 'mmc info' */ +U_BOOT_CMD( + mmcinfo, 1, 0, do_mmcinfo, + "display MMC info", + "- display info of the current MMC device" +); diff --git a/cmd/mp.c b/cmd/mp.c new file mode 100644 index 00000000000..686e1f8a82f --- /dev/null +++ b/cmd/mp.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2008-2009 Freescale Semiconductor, Inc. + */ + +#include <command.h> +#include <cpu_func.h> +#include <vsprintf.h> +#include <linux/string.h> + +static int cpu_status_all(void) +{ + unsigned long cpuid; + + for (cpuid = 0; ; cpuid++) { + if (!is_core_valid(cpuid)) { + if (cpuid == 0) { + printf("Core num: %lu is not valid\n", cpuid); + return 1; + } + break; + } + cpu_status(cpuid); + } + + return 0; +} + +static int +cpu_cmd(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + unsigned long cpuid; + + if (argc == 2 && strncmp(argv[1], "status", 6) == 0) + return cpu_status_all(); + + if (argc < 3) + return CMD_RET_USAGE; + + cpuid = dectoul(argv[1], NULL); + if (!is_core_valid(cpuid)) { + printf ("Core num: %lu is not valid\n", cpuid); + return 1; + } + + if (argc == 3) { + if (strncmp(argv[2], "reset", 5) == 0) + cpu_reset(cpuid); + else if (strncmp(argv[2], "status", 6) == 0) + cpu_status(cpuid); + else if (strncmp(argv[2], "disable", 7) == 0) + return cpu_disable(cpuid); + else + return CMD_RET_USAGE; + + return 0; + } + + /* 4 or greater, make sure its release */ + if (strncmp(argv[2], "release", 7) != 0) + return CMD_RET_USAGE; + + if (cpu_release(cpuid, argc - 3, argv + 3)) + return CMD_RET_USAGE; + + return 0; +} + +U_BOOT_LONGHELP(cpu, + "<num> reset - Reset cpu <num>\n" + "cpu status - Status of all cpus\n" + "cpu <num> status - Status of cpu <num>\n" + "cpu <num> disable - Disable cpu <num>\n" + "cpu <num> release <addr> [args] - Release cpu <num> at <addr> with [args]" +#ifdef CONFIG_PPC + "\n" + " [args] : <pir> <r3> <r6>\n" \ + " pir - processor id (if writeable)\n" \ + " r3 - value for gpr 3\n" \ + " r6 - value for gpr 6\n" \ + "\n" \ + " Use '-' for any arg if you want the default value.\n" \ + " Default for r3 is <num> and r6 is 0\n" \ + "\n" \ + " When cpu <num> is released r4 and r5 = 0.\n" \ + " r7 will contain the size of the initial mapped area" +#endif + ); + +U_BOOT_CMD( + cpu, CONFIG_SYS_MAXARGS, 1, cpu_cmd, + "Multiprocessor CPU boot manipulation and release", cpu_help_text +); diff --git a/cmd/mtd.c b/cmd/mtd.c new file mode 100644 index 00000000000..2520b89eed2 --- /dev/null +++ b/cmd/mtd.c @@ -0,0 +1,831 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * mtd.c + * + * Generic command to handle basic operations on any memory device. + * + * Copyright: Bootlin, 2018 + * Author: Miquèl Raynal <miquel.raynal@bootlin.com> + */ + +#include <command.h> +#include <console.h> +#include <led.h> +#if CONFIG_IS_ENABLED(CMD_MTD_OTP) +#include <hexdump.h> +#endif +#include <malloc.h> +#include <mapmem.h> +#include <mtd.h> +#include <time.h> +#include <dm/devres.h> +#include <linux/err.h> + +#include <linux/ctype.h> + +static struct mtd_info *get_mtd_by_name(const char *name) +{ + struct mtd_info *mtd; + + mtd_probe_devices(); + + mtd = get_mtd_device_nm(name); + if (IS_ERR_OR_NULL(mtd)) + printf("MTD device %s not found, ret %ld\n", name, + PTR_ERR(mtd)); + + return mtd; +} + +static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len) +{ + do_div(len, mtd->writesize); + + return len; +} + +static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size) +{ + return !do_div(size, mtd->writesize); +} + +static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size) +{ + return !do_div(size, mtd->erasesize); +} + +static void mtd_dump_buf(const u8 *buf, uint len, uint offset) +{ + int i, j; + + for (i = 0; i < len; ) { + printf("0x%08x:\t", offset + i); + for (j = 0; j < 8; j++) + printf("%02x ", buf[i + j]); + printf(" "); + i += 8; + for (j = 0; j < 8; j++) + printf("%02x ", buf[i + j]); + printf("\n"); + i += 8; + } +} + +static void mtd_dump_device_buf(struct mtd_info *mtd, u64 start_off, + const u8 *buf, u64 len, bool woob) +{ + bool has_pages = mtd->type == MTD_NANDFLASH || + mtd->type == MTD_MLCNANDFLASH; + int npages = mtd_len_to_pages(mtd, len); + uint page; + + if (has_pages) { + for (page = 0; page < npages; page++) { + u64 data_off = (u64)page * mtd->writesize; + + printf("\nDump %d data bytes from 0x%08llx:\n", + mtd->writesize, start_off + data_off); + mtd_dump_buf(&buf[data_off], + mtd->writesize, start_off + data_off); + + if (woob) { + u64 oob_off = (u64)page * mtd->oobsize; + + printf("Dump %d OOB bytes from page at 0x%08llx:\n", + mtd->oobsize, start_off + data_off); + mtd_dump_buf(&buf[len + oob_off], + mtd->oobsize, 0); + } + } + } else { + printf("\nDump %lld data bytes from 0x%llx:\n", + len, start_off); + mtd_dump_buf(buf, len, start_off); + } +} + +static void mtd_show_parts(struct mtd_info *mtd, int level) +{ + struct mtd_info *part; + int i; + + list_for_each_entry(part, &mtd->partitions, node) { + for (i = 0; i < level; i++) + printf("\t"); + printf(" - 0x%012llx-0x%012llx : \"%s\"\n", + part->offset, part->offset + part->size, part->name); + + mtd_show_parts(part, level + 1); + } +} + +static void mtd_show_device(struct mtd_info *mtd) +{ + /* Device */ + printf("* %s\n", mtd->name); + if (mtd->dev) { + printf(" - device: %s\n", mtd->dev->name); + printf(" - parent: %s\n", mtd->dev->parent->name); + printf(" - driver: %s\n", mtd->dev->driver->name); + } + if (IS_ENABLED(CONFIG_OF_CONTROL) && mtd->dev) { + char buf[256]; + int res; + + res = ofnode_get_path(mtd_get_ofnode(mtd), buf, 256); + printf(" - path: %s\n", res == 0 ? buf : "unavailable"); + } + + /* MTD device information */ + printf(" - type: "); + switch (mtd->type) { + case MTD_RAM: + printf("RAM\n"); + break; + case MTD_ROM: + printf("ROM\n"); + break; + case MTD_NORFLASH: + printf("NOR flash\n"); + break; + case MTD_NANDFLASH: + printf("NAND flash\n"); + break; + case MTD_DATAFLASH: + printf("Data flash\n"); + break; + case MTD_UBIVOLUME: + printf("UBI volume\n"); + break; + case MTD_MLCNANDFLASH: + printf("MLC NAND flash\n"); + break; + case MTD_ABSENT: + default: + printf("Unknown\n"); + break; + } + + printf(" - block size: 0x%x bytes\n", mtd->erasesize); + printf(" - min I/O: 0x%x bytes\n", mtd->writesize); + + if (mtd->oobsize) { + printf(" - OOB size: %u bytes\n", mtd->oobsize); + printf(" - OOB available: %u bytes\n", mtd->oobavail); + } + + if (mtd->ecc_strength) { + printf(" - ECC strength: %u bits\n", mtd->ecc_strength); + printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size); + printf(" - bitflip threshold: %u bits\n", + mtd->bitflip_threshold); + } + + printf(" - 0x%012llx-0x%012llx : \"%s\"\n", + mtd->offset, mtd->offset + mtd->size, mtd->name); + + /* MTD partitions, if any */ + mtd_show_parts(mtd, 1); +} + +/* Logic taken from fs/ubifs/recovery.c:is_empty() */ +static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op) +{ + int i; + + for (i = 0; i < op->len; i++) + if (op->datbuf[i] != 0xff) + return false; + + for (i = 0; i < op->ooblen; i++) + if (op->oobbuf[i] != 0xff) + return false; + + return true; +} + +#if CONFIG_IS_ENABLED(CMD_MTD_OTP) +static int do_mtd_otp_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + size_t retlen; + off_t from; + size_t len; + bool user; + int ret; + u8 *buf; + + if (argc != 5) + return CMD_RET_USAGE; + + if (!strcmp(argv[2], "u")) + user = true; + else if (!strcmp(argv[2], "f")) + user = false; + else + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + from = simple_strtoul(argv[3], NULL, 0); + len = simple_strtoul(argv[4], NULL, 0); + + ret = CMD_RET_FAILURE; + + buf = malloc(len); + if (!buf) + goto put_mtd; + + printf("Reading %s OTP from 0x%lx, %zu bytes\n", + user ? "user" : "factory", from, len); + + if (user) + ret = mtd_read_user_prot_reg(mtd, from, len, &retlen, buf); + else + ret = mtd_read_fact_prot_reg(mtd, from, len, &retlen, buf); + if (ret) { + free(buf); + pr_err("OTP read failed: %d\n", ret); + ret = CMD_RET_FAILURE; + goto put_mtd; + } + + if (retlen != len) + pr_err("OTP read returns %zu, but %zu expected\n", + retlen, len); + + print_hex_dump("", 0, 16, 1, buf, retlen, true); + + free(buf); + + ret = CMD_RET_SUCCESS; + +put_mtd: + put_mtd_device(mtd); + + return ret; +} + +static int do_mtd_otp_lock(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + off_t from; + size_t len; + int ret; + + if (argc != 4) + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + from = simple_strtoul(argv[2], NULL, 0); + len = simple_strtoul(argv[3], NULL, 0); + + ret = mtd_lock_user_prot_reg(mtd, from, len); + if (ret) { + pr_err("OTP lock failed: %d\n", ret); + ret = CMD_RET_FAILURE; + goto put_mtd; + } + + ret = CMD_RET_SUCCESS; + +put_mtd: + put_mtd_device(mtd); + + return ret; +} + +static int do_mtd_otp_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + size_t retlen; + size_t binlen; + u8 *binbuf; + off_t from; + int ret; + + if (argc != 4) + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + from = simple_strtoul(argv[2], NULL, 0); + binlen = strlen(argv[3]) / 2; + + ret = CMD_RET_FAILURE; + binbuf = malloc(binlen); + if (!binbuf) + goto put_mtd; + + hex2bin(binbuf, argv[3], binlen); + + printf("Will write:\n"); + + print_hex_dump("", 0, 16, 1, binbuf, binlen, true); + + printf("to 0x%lx\n", from); + + printf("Continue (y/n)?\n"); + + if (confirm_yesno() != 1) { + pr_err("OTP write canceled\n"); + ret = CMD_RET_SUCCESS; + goto put_mtd; + } + + ret = mtd_write_user_prot_reg(mtd, from, binlen, &retlen, binbuf); + if (ret) { + pr_err("OTP write failed: %d\n", ret); + ret = CMD_RET_FAILURE; + goto put_mtd; + } + + if (retlen != binlen) + pr_err("OTP write returns %zu, but %zu expected\n", + retlen, binlen); + + ret = CMD_RET_SUCCESS; + +put_mtd: + free(binbuf); + put_mtd_device(mtd); + + return ret; +} + +static int do_mtd_otp_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct otp_info otp_info; + struct mtd_info *mtd; + size_t retlen; + bool user; + int ret; + + if (argc != 3) + return CMD_RET_USAGE; + + if (!strcmp(argv[2], "u")) + user = true; + else if (!strcmp(argv[2], "f")) + user = false; + else + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + if (user) + ret = mtd_get_user_prot_info(mtd, sizeof(otp_info), &retlen, + &otp_info); + else + ret = mtd_get_fact_prot_info(mtd, sizeof(otp_info), &retlen, + &otp_info); + if (ret) { + pr_err("OTP info failed: %d\n", ret); + ret = CMD_RET_FAILURE; + goto put_mtd; + } + + if (retlen != sizeof(otp_info)) { + pr_err("OTP info returns %zu, but %zu expected\n", + retlen, sizeof(otp_info)); + ret = CMD_RET_FAILURE; + goto put_mtd; + } + + printf("%s OTP region info:\n", user ? "User" : "Factory"); + printf("\tstart: %u\n", otp_info.start); + printf("\tlength: %u\n", otp_info.length); + printf("\tlocked: %u\n", otp_info.locked); + + ret = CMD_RET_SUCCESS; + +put_mtd: + put_mtd_device(mtd); + + return ret; +} +#endif + +static int do_mtd_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + int dev_nb = 0; + + /* Ensure all devices (and their partitions) are probed */ + mtd_probe_devices(); + + printf("List of MTD devices:\n"); + mtd_for_each_device(mtd) { + if (!mtd_is_partition(mtd)) + mtd_show_device(mtd); + + dev_nb++; + } + + if (!dev_nb) { + printf("No MTD device found\n"); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int mtd_special_write_oob(struct mtd_info *mtd, u64 off, + struct mtd_oob_ops *io_op, + bool write_empty_pages, bool woob) +{ + int ret = 0; + + /* + * By default, do not write an empty page. + * Skip it by simulating a successful write. + */ + if (!write_empty_pages && mtd_oob_write_is_empty(io_op)) { + io_op->retlen = mtd->writesize; + io_op->oobretlen = woob ? mtd->oobsize : 0; + } else { + ret = mtd_write_oob(mtd, off, io_op); + } + + return ret; +} + +static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool dump, read, raw, woob, benchmark, write_empty_pages, has_pages = false; + u64 start_off, off, len, remaining, default_len; + unsigned long bench_start, bench_end; + struct mtd_oob_ops io_op = {}; + uint user_addr = 0, npages; + const char *cmd = argv[0]; + struct mtd_info *mtd; + u32 oob_len; + u8 *buf; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + if (mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH) + has_pages = true; + + dump = !strncmp(cmd, "dump", 4); + read = dump || !strncmp(cmd, "read", 4); + raw = strstr(cmd, ".raw"); + woob = strstr(cmd, ".oob"); + benchmark = strstr(cmd, ".benchmark"); + write_empty_pages = !has_pages || strstr(cmd, ".dontskipff"); + + argc -= 2; + argv += 2; + + if (!dump) { + if (!argc) { + ret = CMD_RET_USAGE; + goto out_put_mtd; + } + + user_addr = hextoul(argv[0], NULL); + argc--; + argv++; + } + + start_off = argc > 0 ? hextoul(argv[0], NULL) : 0; + if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) { + printf("Offset not aligned with a page (0x%x)\n", + mtd->writesize); + ret = CMD_RET_FAILURE; + goto out_put_mtd; + } + + default_len = dump ? mtd->writesize : mtd->size; + len = argc > 1 ? hextoul(argv[1], NULL) : default_len; + if (!mtd_is_aligned_with_min_io_size(mtd, len)) { + len = round_up(len, mtd->writesize); + printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n", + mtd->writesize, len); + } + + remaining = len; + npages = mtd_len_to_pages(mtd, len); + oob_len = woob ? npages * mtd->oobsize : 0; + + if (dump) + buf = kmalloc(len + oob_len, GFP_KERNEL); + else + buf = map_sysmem(user_addr, 0); + + if (!buf) { + printf("Could not map/allocate the user buffer\n"); + ret = CMD_RET_FAILURE; + goto out_put_mtd; + } + + if (has_pages) + printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n", + read ? "Reading" : "Writing", len, npages, start_off, + raw ? " [raw]" : "", woob ? " [oob]" : "", + !read && write_empty_pages ? " [dontskipff]" : ""); + else + printf("%s %lld byte(s) at offset 0x%08llx\n", + read ? "Reading" : "Writing", len, start_off); + + io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB; + io_op.len = has_pages ? mtd->writesize : len; + io_op.ooblen = woob ? mtd->oobsize : 0; + io_op.datbuf = buf; + io_op.oobbuf = woob ? &buf[len] : NULL; + + /* Search for the first good block after the given offset */ + off = start_off; + while (mtd_block_isbad(mtd, off)) + off += mtd->erasesize; + + led_activity_blink(); + + if (benchmark) + bench_start = timer_get_us(); + + /* Loop over the pages to do the actual read/write */ + while (remaining) { + /* Skip the block if it is bad */ + if (mtd_is_aligned_with_block_size(mtd, off) && + mtd_block_isbad(mtd, off)) { + off += mtd->erasesize; + continue; + } + + if (read) + ret = mtd_read_oob(mtd, off, &io_op); + else + ret = mtd_special_write_oob(mtd, off, &io_op, + write_empty_pages, woob); + + if (ret) { + printf("Failure while %s at offset 0x%llx\n", + read ? "reading" : "writing", off); + break; + } + + off += io_op.retlen; + remaining -= io_op.retlen; + io_op.datbuf += io_op.retlen; + io_op.oobbuf += io_op.oobretlen; + } + + if (benchmark && bench_start) { + bench_end = timer_get_us(); + printf("%s speed: %lukiB/s\n", + read ? "Read" : "Write", + ((io_op.len * 1000000) / (bench_end - bench_start)) / 1024); + } + + led_activity_off(); + + if (!ret && dump) + mtd_dump_device_buf(mtd, start_off, buf, len, woob); + + if (dump) + kfree(buf); + else + unmap_sysmem(buf); + + if (ret) { + printf("%s on %s failed with error %d\n", + read ? "Read" : "Write", mtd->name, ret); + ret = CMD_RET_FAILURE; + } else { + ret = CMD_RET_SUCCESS; + } + +out_put_mtd: + put_mtd_device(mtd); + + return ret; +} + +static int do_mtd_erase(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct erase_info erase_op = {}; + struct mtd_info *mtd; + u64 off, len; + bool scrub; + int ret = 0; + + if (argc < 2) + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + scrub = strstr(argv[0], ".dontskipbad"); + + argc -= 2; + argv += 2; + + off = argc > 0 ? hextoul(argv[0], NULL) : 0; + len = argc > 1 ? hextoul(argv[1], NULL) : mtd->size; + + if (!mtd_is_aligned_with_block_size(mtd, off)) { + printf("Offset not aligned with a block (0x%x)\n", + mtd->erasesize); + ret = CMD_RET_FAILURE; + goto out_put_mtd; + } + + if (!mtd_is_aligned_with_block_size(mtd, len)) { + printf("Size not a multiple of a block (0x%x)\n", + mtd->erasesize); + ret = CMD_RET_FAILURE; + goto out_put_mtd; + } + + printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n", + off, off + len - 1, mtd_div_by_eb(len, mtd)); + + erase_op.mtd = mtd; + erase_op.addr = off; + erase_op.len = mtd->erasesize; + + led_activity_blink(); + + while (len) { + if (!scrub) { + ret = mtd_block_isbad(mtd, erase_op.addr); + if (ret < 0) { + printf("Failed to get bad block at 0x%08llx\n", + erase_op.addr); + ret = CMD_RET_FAILURE; + goto out_put_mtd; + } + + if (ret > 0) { + printf("Skipping bad block at 0x%08llx\n", + erase_op.addr); + ret = 0; + len -= mtd->erasesize; + erase_op.addr += mtd->erasesize; + continue; + } + } + + ret = mtd_erase(mtd, &erase_op); + if (ret && ret != -EIO) + break; + + len -= mtd->erasesize; + erase_op.addr += mtd->erasesize; + } + + led_activity_off(); + + if (ret && ret != -EIO) + ret = CMD_RET_FAILURE; + else + ret = CMD_RET_SUCCESS; + +out_put_mtd: + put_mtd_device(mtd); + + return ret; +} + +static int do_mtd_bad(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + loff_t off; + + if (argc < 2) + return CMD_RET_USAGE; + + mtd = get_mtd_by_name(argv[1]); + if (IS_ERR_OR_NULL(mtd)) + return CMD_RET_FAILURE; + + if (!mtd_can_have_bb(mtd)) { + printf("Only NAND-based devices can have bad blocks\n"); + goto out_put_mtd; + } + + printf("MTD device %s bad blocks list:\n", mtd->name); + for (off = 0; off < mtd->size; off += mtd->erasesize) { + if (mtd_block_isbad(mtd, off)) + printf("\t0x%08llx\n", off); + } + +out_put_mtd: + put_mtd_device(mtd); + + return CMD_RET_SUCCESS; +} + +#ifdef CONFIG_AUTO_COMPLETE +static int mtd_name_complete(int argc, char *const argv[], char last_char, + int maxv, char *cmdv[]) +{ + int len = 0, n_found = 0; + struct mtd_info *mtd; + + argc--; + argv++; + + if (argc > 1 || + (argc == 1 && (last_char == '\0' || isblank(last_char)))) + return 0; + + if (argc) + len = strlen(argv[0]); + + mtd_for_each_device(mtd) { + if (argc && + (len > strlen(mtd->name) || + strncmp(argv[0], mtd->name, len))) + continue; + + if (n_found >= maxv - 2) { + cmdv[n_found++] = "..."; + break; + } + + cmdv[n_found++] = mtd->name; + } + + cmdv[n_found] = NULL; + + return n_found; +} +#endif /* CONFIG_AUTO_COMPLETE */ + +U_BOOT_LONGHELP(mtd, + "- generic operations on memory technology devices\n\n" + "mtd list\n" + "mtd read[.raw][.oob] <name> <addr> [<off> [<size>]]\n" + "mtd dump[.raw][.oob] <name> [<off> [<size>]]\n" + "mtd write[.raw][.oob][.dontskipff] <name> <addr> [<off> [<size>]]\n" + "mtd erase[.dontskipbad] <name> [<off> [<size>]]\n" + "\n" + "Specific functions:\n" + "mtd bad <name>\n" +#if CONFIG_IS_ENABLED(CMD_MTD_OTP) + "mtd otpread <name> [u|f] <off> <size>\n" + "mtd otpwrite <name> <off> <hex string>\n" + "mtd otplock <name> <off> <size>\n" + "mtd otpinfo <name> [u|f]\n" +#endif + "\n" + "With:\n" + "\t<name>: NAND partition/chip name (or corresponding DM device name or OF path)\n" + "\t<addr>: user address from/to which data will be retrieved/stored\n" + "\t<off>: offset in <name> in bytes (default: start of the part)\n" + "\t\t* must be block-aligned for erase\n" + "\t\t* must be page-aligned otherwise\n" + "\t<size>: length of the operation in bytes (default: the entire device)\n" + "\t\t* must be a multiple of a block for erase\n" + "\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n" +#if CONFIG_IS_ENABLED(CMD_MTD_OTP) + "\t<hex string>: hex string without '0x' and spaces. Example: ABCD1234\n" + "\t[u|f]: user or factory OTP region\n" +#endif + "\n" + "The .dontskipff option forces writing empty pages, don't use it if unsure.\n"); + +U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text, +#if CONFIG_IS_ENABLED(CMD_MTD_OTP) + U_BOOT_SUBCMD_MKENT(otpread, 5, 1, do_mtd_otp_read), + U_BOOT_SUBCMD_MKENT(otpwrite, 4, 1, do_mtd_otp_write), + U_BOOT_SUBCMD_MKENT(otplock, 4, 1, do_mtd_otp_lock), + U_BOOT_SUBCMD_MKENT(otpinfo, 3, 1, do_mtd_otp_info), +#endif + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_mtd_list), + U_BOOT_SUBCMD_MKENT_COMPLETE(read, 5, 0, do_mtd_io, + mtd_name_complete), + U_BOOT_SUBCMD_MKENT_COMPLETE(write, 5, 0, do_mtd_io, + mtd_name_complete), + U_BOOT_SUBCMD_MKENT_COMPLETE(dump, 4, 0, do_mtd_io, + mtd_name_complete), + U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase, + mtd_name_complete), + U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad, + mtd_name_complete)); diff --git a/cmd/mtdparts.c b/cmd/mtdparts.c new file mode 100644 index 00000000000..571b79f091d --- /dev/null +++ b/cmd/mtdparts.c @@ -0,0 +1,2126 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2002 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * (C) Copyright 2002 + * Robert Schwebel, Pengutronix, <r.schwebel@pengutronix.de> + * + * (C) Copyright 2003 + * Kai-Uwe Bloem, Auerswald GmbH & Co KG, <linux-development@auerswald.de> + * + * (C) Copyright 2005 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * Added support for reading flash partition table from environment. + * Parsing routines are based on driver/mtd/cmdline.c from the linux 2.4 + * kernel tree. + * + * (C) Copyright 2008 + * Harald Welte, OpenMoko, Inc., Harald Welte <laforge@openmoko.org> + * + * $Id: cmdlinepart.c,v 1.17 2004/11/26 11:18:47 lavinen Exp $ + * Copyright 2002 SYSGO Real-Time Solutions GmbH + */ + +/* + * Three environment variables are used by the parsing routines: + * + * 'partition' - keeps current partition identifier + * + * partition := <part-id> + * <part-id> := <dev-id>,part_num + * + * + * 'mtdids' - linux kernel mtd device id <-> u-boot device id mapping + * + * mtdids=<idmap>[,<idmap>,...] + * + * <idmap> := <dev-id>=<mtd-id> + * <dev-id> := 'nand'|'nor'|'onenand'|'spi-nand'<dev-num> + * <dev-num> := mtd device number, 0... + * <mtd-id> := unique device tag used by linux kernel to find mtd device (mtd->name) + * + * + * 'mtdparts' - partition list + * + * mtdparts=[mtdparts=]<mtd-def>[;<mtd-def>...] + * + * <mtd-def> := <mtd-id>:<part-def>[,<part-def>...] + * <mtd-id> := unique device tag used by linux kernel to find mtd device (mtd->name) + * <part-def> := <size>[@<offset>][<name>][<ro-flag>] + * <size> := standard linux memsize OR '-' to denote all remaining space + * <offset> := partition start offset within the device + * <name> := '(' NAME ')' + * <ro-flag> := when set to 'ro' makes partition read-only (not used, passed to kernel) + * + * Notes: + * - each <mtd-id> used in mtdparts must albo exist in 'mtddis' mapping + * - if the above variables are not set defaults for a given target are used + * + * Examples: + * + * 1 NOR Flash, with 1 single writable partition: + * mtdids=nor0=edb7312-nor + * mtdparts=[mtdparts=]edb7312-nor:- + * + * 1 NOR Flash with 2 partitions, 1 NAND with one + * mtdids=nor0=edb7312-nor,nand0=edb7312-nand + * mtdparts=[mtdparts=]edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home) + * + */ + +#include <command.h> +#include <env.h> +#include <log.h> +#include <malloc.h> +#include <mtd.h> +#include <asm/global_data.h> +#include <jffs2/load_kernel.h> +#include <linux/list.h> +#include <linux/ctype.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> + +#if defined(CONFIG_CMD_NAND) +#include <linux/mtd/rawnand.h> +#include <nand.h> +#endif + +#if defined(CONFIG_CMD_ONENAND) +#include <linux/mtd/onenand.h> +#include <onenand_uboot.h> +#endif + +DECLARE_GLOBAL_DATA_PTR; + +/* special size referring to all the remaining space in a partition */ +#define SIZE_REMAINING (~0llu) + +/* special offset value, it is used when not provided by user + * + * this value is used temporarily during parsing, later such offests + * are recalculated */ +#define OFFSET_NOT_SPECIFIED (~0llu) + +/* minimum partition size */ +#define MIN_PART_SIZE 4096 + +/* this flag needs to be set in part_info struct mask_flags + * field for read-only partitions */ +#define MTD_WRITEABLE_CMD 1 + +/* default values for mtdids and mtdparts variables */ +#ifdef CONFIG_MTDIDS_DEFAULT +#define MTDIDS_DEFAULT CONFIG_MTDIDS_DEFAULT +#else +#define MTDIDS_DEFAULT NULL +#endif +#ifdef CONFIG_MTDPARTS_DEFAULT +#define MTDPARTS_DEFAULT CONFIG_MTDPARTS_DEFAULT +#else +#define MTDPARTS_DEFAULT NULL +#endif + +#if defined(CONFIG_SYS_MTDPARTS_RUNTIME) +extern void board_mtdparts_default(const char **mtdids, const char **mtdparts); +#endif +static const char *mtdids_default = MTDIDS_DEFAULT; +static const char *mtdparts_default = MTDPARTS_DEFAULT; + +/* copies of last seen 'mtdids', 'mtdparts' and 'partition' env variables */ +#define MTDIDS_MAXLEN 128 +#define MTDPARTS_MAXLEN 512 +#define PARTITION_MAXLEN 16 +static char last_ids[MTDIDS_MAXLEN + 1]; +static char last_parts[MTDPARTS_MAXLEN + 1]; +static char last_partition[PARTITION_MAXLEN + 1]; + +/* low level jffs2 cache cleaning routine */ +extern void jffs2_free_cache(struct part_info *part); + +/* mtdids mapping list, filled by parse_ids() */ +static struct list_head mtdids; + +/* device/partition list, parse_cmdline() parses into here */ +static struct list_head devices; + +/* current active device and partition number */ +struct mtd_device *current_mtd_dev = NULL; +u8 current_mtd_partnum = 0; + +u8 use_defaults; + +static struct part_info* mtd_part_info(struct mtd_device *dev, unsigned int part_num); + +/* command line only routines */ +static struct mtdids* id_find_by_mtd_id(const char *mtd_id, unsigned int mtd_id_len); +static int device_del(struct mtd_device *dev); + +/** + * Parses a string into a number. The number stored at ptr is + * potentially suffixed with K (for kilobytes, or 1024 bytes), + * M (for megabytes, or 1048576 bytes), or G (for gigabytes, or + * 1073741824). If the number is suffixed with K, M, or G, then + * the return value is the number multiplied by one kilobyte, one + * megabyte, or one gigabyte, respectively. + * + * @param ptr where parse begins + * @param retptr output pointer to next char after parse completes (output) + * Return: resulting unsigned int + */ +static u64 memsize_parse (const char *const ptr, const char **retptr) +{ + u64 ret = simple_strtoull(ptr, (char **)retptr, 0); + + switch (**retptr) { + case 'G': + case 'g': + ret <<= 10; + /* Fallthrough */ + case 'M': + case 'm': + ret <<= 10; + /* Fallthrough */ + case 'K': + case 'k': + ret <<= 10; + (*retptr)++; + /* Fallthrough */ + default: + break; + } + + return ret; +} + +/** + * Format string describing supplied size. This routine does the opposite job + * to memsize_parse(). Size in bytes is converted to string and if possible + * shortened by using k (kilobytes), m (megabytes) or g (gigabytes) suffix. + * + * Note, that this routine does not check for buffer overflow, it's the caller + * who must assure enough space. + * + * @param buf output buffer + * @param size size to be converted to string + */ +static void memsize_format(char *buf, u64 size) +{ +#define SIZE_GB ((u32)1024*1024*1024) +#define SIZE_MB ((u32)1024*1024) +#define SIZE_KB ((u32)1024) + + if ((size % SIZE_GB) == 0) + sprintf(buf, "%llug", size/SIZE_GB); + else if ((size % SIZE_MB) == 0) + sprintf(buf, "%llum", size/SIZE_MB); + else if (size % SIZE_KB == 0) + sprintf(buf, "%lluk", size/SIZE_KB); + else + sprintf(buf, "%llu", size); +} + +/** + * This routine does global indexing of all partitions. Resulting index for + * current partition is saved in 'mtddevnum'. Current partition name in + * 'mtddevname'. + */ +static void index_partitions(void) +{ + u16 mtddevnum; + struct part_info *part; + struct list_head *dentry; + struct mtd_device *dev; + + debug("--- index partitions ---\n"); + + if (current_mtd_dev) { + mtddevnum = 0; + list_for_each(dentry, &devices) { + dev = list_entry(dentry, struct mtd_device, link); + if (dev == current_mtd_dev) { + mtddevnum += current_mtd_partnum; + env_set_ulong("mtddevnum", mtddevnum); + debug("=> mtddevnum %d,\n", mtddevnum); + break; + } + mtddevnum += dev->num_parts; + } + + part = mtd_part_info(current_mtd_dev, current_mtd_partnum); + if (part) { + env_set("mtddevname", part->name); + + debug("=> mtddevname %s\n", part->name); + } else { + env_set("mtddevname", NULL); + + debug("=> mtddevname NULL\n"); + } + } else { + env_set("mtddevnum", NULL); + env_set("mtddevname", NULL); + + debug("=> mtddevnum NULL\n=> mtddevname NULL\n"); + } +} + +/** + * Save current device and partition in environment variable 'partition'. + */ +static void current_save(void) +{ + char buf[16]; + + debug("--- current_save ---\n"); + + if (current_mtd_dev) { + sprintf(buf, "%s%d,%d", MTD_DEV_TYPE(current_mtd_dev->id->type), + current_mtd_dev->id->num, current_mtd_partnum); + + env_set("partition", buf); + strncpy(last_partition, buf, 16); + + debug("=> partition %s\n", buf); + } else { + env_set("partition", NULL); + last_partition[0] = '\0'; + + debug("=> partition NULL\n"); + } + index_partitions(); +} + +/** + * Produce a mtd_info given a type and num. + * + * @param type mtd type + * @param num mtd number + * @param mtd a pointer to an mtd_info instance (output) + * Return: 0 if device is valid, 1 otherwise + */ +static int get_mtd_info(u8 type, u8 num, struct mtd_info **mtd) +{ + char mtd_dev[16]; + + sprintf(mtd_dev, "%s%d", MTD_DEV_TYPE(type), num); + *mtd = get_mtd_device_nm(mtd_dev); + if (IS_ERR(*mtd)) { + printf("Device %s not found!\n", mtd_dev); + return 1; + } + put_mtd_device(*mtd); + + return 0; +} + +/** + * Performs sanity check for supplied flash partition. + * Table of existing MTD flash devices is searched and partition device + * is located. Alignment with the granularity of nand erasesize is verified. + * + * @param id of the parent device + * @param part partition to validate + * Return: 0 if partition is valid, 1 otherwise + */ +static int part_validate_eraseblock(struct mtdids *id, struct part_info *part) +{ + struct mtd_info *mtd = NULL; + int i, j; + ulong start; + u64 offset, size; + + if (get_mtd_info(id->type, id->num, &mtd)) + return 1; + + part->sector_size = mtd->erasesize; + + if (!mtd->numeraseregions) { + /* + * Only one eraseregion (NAND, SPI-NAND, OneNAND or uniform NOR), + * checking for alignment is easy here + */ + offset = part->offset; + if (do_div(offset, mtd->erasesize)) { + printf("%s%d: partition (%s) start offset" + "alignment incorrect\n", + MTD_DEV_TYPE(id->type), id->num, part->name); + return 1; + } + + size = part->size; + if (do_div(size, mtd->erasesize)) { + printf("%s%d: partition (%s) size alignment incorrect\n", + MTD_DEV_TYPE(id->type), id->num, part->name); + return 1; + } + } else { + /* + * Multiple eraseregions (non-uniform NOR), + * checking for alignment is more complex here + */ + + /* Check start alignment */ + for (i = 0; i < mtd->numeraseregions; i++) { + start = mtd->eraseregions[i].offset; + for (j = 0; j < mtd->eraseregions[i].numblocks; j++) { + if (part->offset == start) + goto start_ok; + start += mtd->eraseregions[i].erasesize; + } + } + + printf("%s%d: partition (%s) start offset alignment incorrect\n", + MTD_DEV_TYPE(id->type), id->num, part->name); + return 1; + + start_ok: + + /* Check end/size alignment */ + for (i = 0; i < mtd->numeraseregions; i++) { + start = mtd->eraseregions[i].offset; + for (j = 0; j < mtd->eraseregions[i].numblocks; j++) { + if ((part->offset + part->size) == start) + goto end_ok; + start += mtd->eraseregions[i].erasesize; + } + } + /* Check last sector alignment */ + if ((part->offset + part->size) == start) + goto end_ok; + + printf("%s%d: partition (%s) size alignment incorrect\n", + MTD_DEV_TYPE(id->type), id->num, part->name); + return 1; + + end_ok: + return 0; + } + + return 0; +} + +/** + * Performs sanity check for supplied partition. Offset and size are + * verified to be within valid range. Partition type is checked and + * part_validate_eraseblock() is called with the argument of part. + * + * @param id of the parent device + * @param part partition to validate + * Return: 0 if partition is valid, 1 otherwise + */ +static int part_validate(struct mtdids *id, struct part_info *part) +{ + if (part->size == SIZE_REMAINING) + part->size = id->size - part->offset; + + if (part->offset > id->size) { + printf("%s: offset %08llx beyond flash size %08llx\n", + id->mtd_id, part->offset, id->size); + return 1; + } + + if ((part->offset + part->size) <= part->offset) { + printf("%s%d: partition (%s) size too big\n", + MTD_DEV_TYPE(id->type), id->num, part->name); + return 1; + } + + if (part->offset + part->size > id->size) { + printf("%s: partitioning exceeds flash size\n", id->mtd_id); + return 1; + } + + /* + * Now we need to check if the partition starts and ends on + * sector (eraseblock) regions + */ + return part_validate_eraseblock(id, part); +} + +/** + * Delete selected partition from the partition list of the specified device. + * + * @param dev device to delete partition from + * @param part partition to delete + * Return: 0 on success, 1 otherwise + */ +static int part_del(struct mtd_device *dev, struct part_info *part) +{ + u8 current_save_needed = 0; + + /* if there is only one partition, remove whole device */ + if (dev->num_parts == 1) + return device_del(dev); + + /* otherwise just delete this partition */ + + if (dev == current_mtd_dev) { + /* we are modyfing partitions for the current device, + * update current */ + struct part_info *curr_pi; + curr_pi = mtd_part_info(current_mtd_dev, current_mtd_partnum); + + if (curr_pi) { + if (curr_pi == part) { + printf("current partition deleted, resetting current to 0\n"); + current_mtd_partnum = 0; + } else if (part->offset <= curr_pi->offset) { + current_mtd_partnum--; + } + current_save_needed = 1; + } + } + + list_del(&part->link); + free(part); + dev->num_parts--; + + if (current_save_needed > 0) + current_save(); + else + index_partitions(); + + return 0; +} + +/** + * Delete all partitions from parts head list, free memory. + * + * @param head list of partitions to delete + */ +static void part_delall(struct list_head *head) +{ + struct list_head *entry, *n; + struct part_info *part_tmp; + + /* clean tmp_list and free allocated memory */ + list_for_each_safe(entry, n, head) { + part_tmp = list_entry(entry, struct part_info, link); + + list_del(entry); + free(part_tmp); + } +} + +/** + * Add new partition to the supplied partition list. Make sure partitions are + * sorted by offset in ascending order. + * + * @param head list this partition is to be added to + * @param new partition to be added + */ +static int part_sort_add(struct mtd_device *dev, struct part_info *part) +{ + struct list_head *entry; + struct part_info *new_pi, *curr_pi; + + /* link partition to parrent dev */ + part->dev = dev; + + if (list_empty(&dev->parts)) { + debug("part_sort_add: list empty\n"); + list_add(&part->link, &dev->parts); + dev->num_parts++; + index_partitions(); + return 0; + } + + new_pi = list_entry(&part->link, struct part_info, link); + + /* get current partition info if we are updating current device */ + curr_pi = NULL; + if (dev == current_mtd_dev) + curr_pi = mtd_part_info(current_mtd_dev, current_mtd_partnum); + + list_for_each(entry, &dev->parts) { + struct part_info *pi; + + pi = list_entry(entry, struct part_info, link); + + /* be compliant with kernel cmdline, allow only one partition at offset zero */ + if ((new_pi->offset == pi->offset) && (pi->offset == 0)) { + printf("cannot add second partition at offset 0\n"); + return 1; + } + + if (new_pi->offset <= pi->offset) { + list_add_tail(&part->link, entry); + dev->num_parts++; + + if (curr_pi && (pi->offset <= curr_pi->offset)) { + /* we are modyfing partitions for the current + * device, update current */ + current_mtd_partnum++; + current_save(); + } else { + index_partitions(); + } + return 0; + } + } + + list_add_tail(&part->link, &dev->parts); + dev->num_parts++; + index_partitions(); + return 0; +} + +/** + * Add provided partition to the partition list of a given device. + * + * @param dev device to which partition is added + * @param part partition to be added + * Return: 0 on success, 1 otherwise + */ +static int part_add(struct mtd_device *dev, struct part_info *part) +{ + /* verify alignment and size */ + if (part_validate(dev->id, part) != 0) + return 1; + + /* partition is ok, add it to the list */ + if (part_sort_add(dev, part) != 0) + return 1; + + return 0; +} + +/** + * Parse one partition definition, allocate memory and return pointer to this + * location in retpart. + * + * @param partdef pointer to the partition definition string i.e. <part-def> + * @param ret output pointer to next char after parse completes (output) + * @param retpart pointer to the allocated partition (output) + * Return: 0 on success, 1 otherwise + */ +static int part_parse(const char *const partdef, const char **ret, struct part_info **retpart) +{ + struct part_info *part; + u64 size; + u64 offset; + const char *name; + int name_len; + unsigned int mask_flags; + const char *p; + + p = partdef; + *retpart = NULL; + *ret = NULL; + + /* fetch the partition size */ + if (*p == '-') { + /* assign all remaining space to this partition */ + debug("'-': remaining size assigned\n"); + size = SIZE_REMAINING; + p++; + } else { + size = memsize_parse(p, &p); + if (size < MIN_PART_SIZE) { + printf("partition size too small (%llx)\n", size); + return 1; + } + } + + /* check for offset */ + offset = OFFSET_NOT_SPECIFIED; + if (*p == '@') { + p++; + offset = memsize_parse(p, &p); + } + + /* now look for the name */ + if (*p == '(') { + name = ++p; + if ((p = strchr(name, ')')) == NULL) { + printf("no closing ) found in partition name\n"); + return 1; + } + name_len = p - name + 1; + if ((name_len - 1) == 0) { + printf("empty partition name\n"); + return 1; + } + p++; + } else { + /* 0x00000000@0x00000000 */ + name_len = 22; + name = NULL; + } + + /* test for options */ + mask_flags = 0; + if (strncmp(p, "ro", 2) == 0) { + mask_flags |= MTD_WRITEABLE_CMD; + p += 2; + } + + /* check for next partition definition */ + if (*p == ',') { + if (size == SIZE_REMAINING) { + *ret = NULL; + printf("no partitions allowed after a fill-up partition\n"); + return 1; + } + *ret = ++p; + } else if ((*p == ';') || (*p == '\0')) { + *ret = p; + } else { + printf("unexpected character '%c' at the end of partition\n", *p); + *ret = NULL; + return 1; + } + + /* allocate memory */ + part = (struct part_info *)malloc(sizeof(struct part_info) + name_len); + if (!part) { + printf("out of memory\n"); + return 1; + } + memset(part, 0, sizeof(struct part_info) + name_len); + part->size = size; + part->offset = offset; + part->mask_flags = mask_flags; + part->name = (char *)(part + 1); + + if (name) { + /* copy user provided name */ + strncpy(part->name, name, name_len - 1); + part->auto_name = 0; + } else { + /* auto generated name in form of size@offset */ + snprintf(part->name, name_len, "0x%08llx@0x%08llx", size, offset); + part->auto_name = 1; + } + + part->name[name_len - 1] = '\0'; + INIT_LIST_HEAD(&part->link); + + debug("+ partition: name %-22s size 0x%08llx offset 0x%08llx mask flags %d\n", + part->name, part->size, + part->offset, part->mask_flags); + + *retpart = part; + return 0; +} + +/** + * Check device number to be within valid range for given device type. + * + * @param type mtd type + * @param num mtd number + * @param size a pointer to the size of the mtd device (output) + * Return: 0 if device is valid, 1 otherwise + */ +static int mtd_device_validate(u8 type, u8 num, u64 *size) +{ + struct mtd_info *mtd = NULL; + + if (get_mtd_info(type, num, &mtd)) + return 1; + + *size = mtd->size; + + return 0; +} + +/** + * Delete all mtd devices from a supplied devices list, free memory allocated for + * each device and delete all device partitions. + * + * Return: 0 on success, 1 otherwise + */ +static int device_delall(struct list_head *head) +{ + struct list_head *entry, *n; + struct mtd_device *dev_tmp; + + /* clean devices list */ + list_for_each_safe(entry, n, head) { + dev_tmp = list_entry(entry, struct mtd_device, link); + list_del(entry); + part_delall(&dev_tmp->parts); + free(dev_tmp); + } + INIT_LIST_HEAD(&devices); + + return 0; +} + +/** + * If provided device exists it's partitions are deleted, device is removed + * from device list and device memory is freed. + * + * @param dev device to be deleted + * Return: 0 on success, 1 otherwise + */ +static int device_del(struct mtd_device *dev) +{ + part_delall(&dev->parts); + list_del(&dev->link); + free(dev); + + if (dev == current_mtd_dev) { + /* we just deleted current device */ + if (list_empty(&devices)) { + current_mtd_dev = NULL; + } else { + /* reset first partition from first dev from the + * devices list as current */ + current_mtd_dev = list_entry(devices.next, struct mtd_device, link); + current_mtd_partnum = 0; + } + current_save(); + return 0; + } + + index_partitions(); + return 0; +} + +/** + * Search global device list and return pointer to the device of type and num + * specified. + * + * @param type device type + * @param num device number + * Return: NULL if requested device does not exist + */ +struct mtd_device *device_find(u8 type, u8 num) +{ + struct list_head *entry; + struct mtd_device *dev_tmp; + + list_for_each(entry, &devices) { + dev_tmp = list_entry(entry, struct mtd_device, link); + + if ((dev_tmp->id->type == type) && (dev_tmp->id->num == num)) + return dev_tmp; + } + + return NULL; +} + +/** + * Add specified device to the global device list. + * + * @param dev device to be added + */ +static void device_add(struct mtd_device *dev) +{ + u8 current_save_needed = 0; + + if (list_empty(&devices)) { + current_mtd_dev = dev; + current_mtd_partnum = 0; + current_save_needed = 1; + } + + list_add_tail(&dev->link, &devices); + + if (current_save_needed > 0) + current_save(); + else + index_partitions(); +} + +/** + * Parse device type, name and mtd-id. If syntax is ok allocate memory and + * return pointer to the device structure. + * + * @param mtd_dev pointer to the device definition string i.e. <mtd-dev> + * @param ret output pointer to next char after parse completes (output) + * @param retdev pointer to the allocated device (output) + * Return: 0 on success, 1 otherwise + */ +static int device_parse(const char *const mtd_dev, const char **ret, struct mtd_device **retdev) +{ + struct mtd_device *dev; + struct part_info *part; + struct mtdids *id; + const char *mtd_id; + unsigned int mtd_id_len; + const char *p; + const char *pend; + LIST_HEAD(tmp_list); + struct list_head *entry, *n; + u16 num_parts; + u64 offset; + int err = 1; + + debug("===device_parse===\n"); + + assert(retdev); + *retdev = NULL; + + if (ret) + *ret = NULL; + + /* fetch <mtd-id> */ + mtd_id = p = mtd_dev; + if (!(p = strchr(mtd_id, ':'))) { + printf("no <mtd-id> identifier\n"); + return 1; + } + mtd_id_len = p - mtd_id + 1; + p++; + + /* verify if we have a valid device specified */ + if ((id = id_find_by_mtd_id(mtd_id, mtd_id_len - 1)) == NULL) { + printf("invalid mtd device '%.*s'\n", mtd_id_len - 1, mtd_id); + return 1; + } + + pend = strchr(p, ';'); + debug("dev type = %d (%s), dev num = %d, mtd-id = %s\n", + id->type, MTD_DEV_TYPE(id->type), + id->num, id->mtd_id); + debug("parsing partitions %.*s\n", (int)(pend ? pend - p : strlen(p)), p); + + /* parse partitions */ + num_parts = 0; + + offset = 0; + if ((dev = device_find(id->type, id->num)) != NULL) { + /* if device already exists start at the end of the last partition */ + part = list_entry(dev->parts.prev, struct part_info, link); + offset = part->offset + part->size; + } + + while (p && (*p != '\0') && (*p != ';')) { + err = 1; + if ((part_parse(p, &p, &part) != 0) || (!part)) + break; + + /* calculate offset when not specified */ + if (part->offset == OFFSET_NOT_SPECIFIED) + part->offset = offset; + else + offset = part->offset; + + /* verify alignment and size */ + if (part_validate(id, part) != 0) + break; + + offset += part->size; + + /* partition is ok, add it to the list */ + list_add_tail(&part->link, &tmp_list); + num_parts++; + err = 0; + } + if (err == 1) { + part_delall(&tmp_list); + return 1; + } + + debug("\ntotal partitions: %d\n", num_parts); + + /* check for next device presence */ + if (p) { + if (*p == ';') { + if (ret) + *ret = ++p; + } else if (*p == '\0') { + if (ret) + *ret = p; + } else { + printf("unexpected character '%c' at the end of device\n", *p); + if (ret) + *ret = NULL; + return 1; + } + } + + /* allocate memory for mtd_device structure */ + if ((dev = (struct mtd_device *)malloc(sizeof(struct mtd_device))) == NULL) { + printf("out of memory\n"); + return 1; + } + memset(dev, 0, sizeof(struct mtd_device)); + dev->id = id; + dev->num_parts = 0; /* part_sort_add increments num_parts */ + INIT_LIST_HEAD(&dev->parts); + INIT_LIST_HEAD(&dev->link); + + /* move partitions from tmp_list to dev->parts */ + list_for_each_safe(entry, n, &tmp_list) { + part = list_entry(entry, struct part_info, link); + list_del(entry); + if (part_sort_add(dev, part) != 0) { + device_del(dev); + return 1; + } + } + + *retdev = dev; + + debug("===\n\n"); + return 0; +} + +/** + * Initialize global device list. + * + * Return: 0 on success, 1 otherwise + */ +static int mtd_devices_init(void) +{ + last_parts[0] = '\0'; + current_mtd_dev = NULL; + current_save(); + + return device_delall(&devices); +} + +/* + * Search global mtdids list and find id of requested type and number. + * + * Return: pointer to the id if it exists, NULL otherwise + */ +static struct mtdids* id_find(u8 type, u8 num) +{ + struct list_head *entry; + struct mtdids *id; + + list_for_each(entry, &mtdids) { + id = list_entry(entry, struct mtdids, link); + + if ((id->type == type) && (id->num == num)) + return id; + } + + return NULL; +} + +/** + * Search global mtdids list and find id of a requested mtd_id. + * + * Note: first argument is not null terminated. + * + * @param mtd_id string containing requested mtd_id + * @param mtd_id_len length of supplied mtd_id + * Return: pointer to the id if it exists, NULL otherwise + */ +static struct mtdids* id_find_by_mtd_id(const char *mtd_id, unsigned int mtd_id_len) +{ + struct list_head *entry; + struct mtdids *id; + + debug("--- id_find_by_mtd_id: '%.*s' (len = %d)\n", + mtd_id_len, mtd_id, mtd_id_len); + + list_for_each(entry, &mtdids) { + id = list_entry(entry, struct mtdids, link); + + debug("entry: '%s' (len = %zu)\n", + id->mtd_id, strlen(id->mtd_id)); + + if (mtd_id_len != strlen(id->mtd_id)) + continue; + if (strncmp(id->mtd_id, mtd_id, mtd_id_len) == 0) + return id; + } + + return NULL; +} + +/** + * Parse device id string <dev-id> := 'nand'|'nor'|'onenand'|'spi-nand'<dev-num>, + * return device type and number. + * + * @param id string describing device id + * @param ret_id output pointer to next char after parse completes (output) + * @param dev_type parsed device type (output) + * @param dev_num parsed device number (output) + * Return: 0 on success, 1 otherwise + */ +int mtd_id_parse(const char *id, const char **ret_id, u8 *dev_type, + u8 *dev_num) +{ + const char *p = id; + + *dev_type = 0; + if (strncmp(p, "nand", 4) == 0) { + *dev_type = MTD_DEV_TYPE_NAND; + p += 4; + } else if (strncmp(p, "nor", 3) == 0) { + *dev_type = MTD_DEV_TYPE_NOR; + p += 3; + } else if (strncmp(p, "onenand", 7) == 0) { + *dev_type = MTD_DEV_TYPE_ONENAND; + p += 7; + } else if (strncmp(p, "spi-nand", 8) == 0) { + *dev_type = MTD_DEV_TYPE_SPINAND; + p += 8; + } else { + printf("incorrect device type in %s\n", id); + return 1; + } + + if (!isdigit(*p)) { + printf("incorrect device number in %s\n", id); + return 1; + } + + *dev_num = simple_strtoul(p, (char **)&p, 0); + if (ret_id) + *ret_id = p; + return 0; +} + +/** + * Process all devices and generate corresponding mtdparts string describing + * all partitions on all devices. + * + * @param buf output buffer holding generated mtdparts string (output) + * @param buflen buffer size + * Return: 0 on success, 1 otherwise + */ +static int generate_mtdparts(char *buf, u32 buflen) +{ + struct list_head *pentry, *dentry; + struct mtd_device *dev; + struct part_info *part, *prev_part; + char *p = buf; + char tmpbuf[32]; + u64 size, offset; + u32 len, part_cnt; + u32 maxlen = buflen - 1; + + debug("--- generate_mtdparts ---\n"); + + if (list_empty(&devices)) { + buf[0] = '\0'; + return 0; + } + + list_for_each(dentry, &devices) { + dev = list_entry(dentry, struct mtd_device, link); + + /* copy mtd_id */ + len = strlen(dev->id->mtd_id) + 1; + if (len > maxlen) + goto cleanup; + memcpy(p, dev->id->mtd_id, len - 1); + p += len - 1; + *(p++) = ':'; + maxlen -= len; + + /* format partitions */ + prev_part = NULL; + part_cnt = 0; + list_for_each(pentry, &dev->parts) { + part = list_entry(pentry, struct part_info, link); + size = part->size; + offset = part->offset; + part_cnt++; + + /* partition size */ + memsize_format(tmpbuf, size); + len = strlen(tmpbuf); + if (len > maxlen) + goto cleanup; + memcpy(p, tmpbuf, len); + p += len; + maxlen -= len; + + /* add offset only when there is a gap between + * partitions */ + if ((!prev_part && (offset != 0)) || + (prev_part && ((prev_part->offset + prev_part->size) != part->offset))) { + + memsize_format(tmpbuf, offset); + len = strlen(tmpbuf) + 1; + if (len > maxlen) + goto cleanup; + *(p++) = '@'; + memcpy(p, tmpbuf, len - 1); + p += len - 1; + maxlen -= len; + } + + /* copy name only if user supplied */ + if(!part->auto_name) { + len = strlen(part->name) + 2; + if (len > maxlen) + goto cleanup; + + *(p++) = '('; + memcpy(p, part->name, len - 2); + p += len - 2; + *(p++) = ')'; + maxlen -= len; + } + + /* ro mask flag */ + if (part->mask_flags && MTD_WRITEABLE_CMD) { + len = 2; + if (len > maxlen) + goto cleanup; + *(p++) = 'r'; + *(p++) = 'o'; + maxlen -= 2; + } + + /* print ',' separator if there are other partitions + * following */ + if (dev->num_parts > part_cnt) { + if (1 > maxlen) + goto cleanup; + *(p++) = ','; + maxlen--; + } + prev_part = part; + } + /* print ';' separator if there are other devices following */ + if (dentry->next != &devices) { + if (1 > maxlen) + goto cleanup; + *(p++) = ';'; + maxlen--; + } + } + + /* we still have at least one char left, as we decremented maxlen at + * the begining */ + *p = '\0'; + + return 0; + +cleanup: + last_parts[0] = '\0'; + return 1; +} + +/** + * Call generate_mtdparts to process all devices and generate corresponding + * mtdparts string, save it in mtdparts environment variable. + * + * @param buf output buffer holding generated mtdparts string (output) + * @param buflen buffer size + * Return: 0 on success, 1 otherwise + */ +static int generate_mtdparts_save(char *buf, u32 buflen) +{ + int ret; + + ret = generate_mtdparts(buf, buflen); + + if ((buf[0] != '\0') && (ret == 0)) + env_set("mtdparts", buf); + else + env_set("mtdparts", NULL); + + return ret; +} + +#if defined(CONFIG_CMD_MTDPARTS_SHOW_NET_SIZES) +/** + * Get the net size (w/o bad blocks) of the given partition. + * + * @param mtd the mtd info + * @param part the partition + * Return: the calculated net size of this partition + */ +static uint64_t net_part_size(struct mtd_info *mtd, struct part_info *part) +{ + uint64_t i, net_size = 0; + + if (!mtd->_block_isbad) + return part->size; + + for (i = 0; i < part->size; i += mtd->erasesize) { + if (!mtd->_block_isbad(mtd, part->offset + i)) + net_size += mtd->erasesize; + } + + return net_size; +} +#endif + +static void print_partition_table(void) +{ + struct list_head *dentry, *pentry; + struct part_info *part; + struct mtd_device *dev; + int part_num; + + list_for_each(dentry, &devices) { + dev = list_entry(dentry, struct mtd_device, link); + /* list partitions for given device */ + part_num = 0; +#if defined(CONFIG_CMD_MTDPARTS_SHOW_NET_SIZES) + struct mtd_info *mtd; + + if (get_mtd_info(dev->id->type, dev->id->num, &mtd)) + return; + + printf("\ndevice %s%d <%s>, # parts = %d\n", + MTD_DEV_TYPE(dev->id->type), dev->id->num, + dev->id->mtd_id, dev->num_parts); + printf(" #: name\t\tsize\t\tnet size\toffset\t\tmask_flags\n"); + + list_for_each(pentry, &dev->parts) { + u32 net_size; + char *size_note; + + part = list_entry(pentry, struct part_info, link); + net_size = net_part_size(mtd, part); + size_note = part->size == net_size ? " " : " (!)"; + printf("%2d: %-20s0x%08llx\t0x%08x%s\t0x%08llx\t%d\n", + part_num, part->name, part->size, + net_size, size_note, part->offset, + part->mask_flags); +#else /* !defined(CONFIG_CMD_MTDPARTS_SHOW_NET_SIZES) */ + printf("\ndevice %s%d <%s>, # parts = %d\n", + MTD_DEV_TYPE(dev->id->type), dev->id->num, + dev->id->mtd_id, dev->num_parts); + printf(" #: name\t\tsize\t\toffset\t\tmask_flags\n"); + + list_for_each(pentry, &dev->parts) { + part = list_entry(pentry, struct part_info, link); + printf("%2d: %-20s0x%08llx\t0x%08llx\t%d\n", + part_num, part->name, part->size, + part->offset, part->mask_flags); +#endif /* defined(CONFIG_CMD_MTDPARTS_SHOW_NET_SIZES) */ + part_num++; + } + } + + if (list_empty(&devices)) + printf("no partitions defined\n"); +} + +/** + * Format and print out a partition list for each device from global device + * list. + */ +static void list_partitions(void) +{ + struct part_info *part; + + debug("\n---list_partitions---\n"); + print_partition_table(); + + /* current_mtd_dev is not NULL only when we have non empty device list */ + if (current_mtd_dev) { + part = mtd_part_info(current_mtd_dev, current_mtd_partnum); + if (part) { + printf("\nactive partition: %s%d,%d - (%s) 0x%08llx @ 0x%08llx\n", + MTD_DEV_TYPE(current_mtd_dev->id->type), + current_mtd_dev->id->num, current_mtd_partnum, + part->name, part->size, part->offset); + } else { + printf("could not get current partition info\n\n"); + } + } + + printf("\ndefaults:\n"); + printf("mtdids : %s\n", + mtdids_default ? mtdids_default : "none"); + /* + * Using printf() here results in printbuffer overflow + * if default mtdparts string is greater than console + * printbuffer. Use puts() to prevent system crashes. + */ + puts("mtdparts: "); + puts(mtdparts_default ? mtdparts_default : "none"); + puts("\n"); +} + +/** + * Given partition identifier in form of <dev_type><dev_num>,<part_num> find + * corresponding device and verify partition number. + * + * @param id string describing device and partition or partition name + * @param dev pointer to the requested device (output) + * @param part_num verified partition number (output) + * @param part pointer to requested partition (output) + * Return: 0 on success, 1 otherwise + */ +int find_dev_and_part(const char *id, struct mtd_device **dev, + u8 *part_num, struct part_info **part) +{ + struct list_head *dentry, *pentry; + u8 type, dnum, pnum; + const char *p; + + debug("--- find_dev_and_part ---\nid = %s\n", id); + + list_for_each(dentry, &devices) { + *part_num = 0; + *dev = list_entry(dentry, struct mtd_device, link); + list_for_each(pentry, &(*dev)->parts) { + *part = list_entry(pentry, struct part_info, link); + if (strcmp((*part)->name, id) == 0) + return 0; + (*part_num)++; + } + } + + p = id; + *dev = NULL; + *part = NULL; + *part_num = 0; + + if (mtd_id_parse(p, &p, &type, &dnum) != 0) + return 1; + + if ((*p++ != ',') || (*p == '\0')) { + printf("no partition number specified\n"); + return 1; + } + pnum = simple_strtoul(p, (char **)&p, 0); + if (*p != '\0') { + printf("unexpected trailing character '%c'\n", *p); + return 1; + } + + if ((*dev = device_find(type, dnum)) == NULL) { + printf("no such device %s%d\n", MTD_DEV_TYPE(type), dnum); + return 1; + } + + if ((*part = mtd_part_info(*dev, pnum)) == NULL) { + printf("no such partition\n"); + *dev = NULL; + return 1; + } + + *part_num = pnum; + + return 0; +} + +/** + * Find and delete partition. For partition id format see find_dev_and_part(). + * + * @param id string describing device and partition + * Return: 0 on success, 1 otherwise + */ +static int delete_partition(const char *id) +{ + u8 pnum; + struct mtd_device *dev; + struct part_info *part; + + if (find_dev_and_part(id, &dev, &pnum, &part) == 0) { + + debug("delete_partition: device = %s%d, partition %d = (%s) 0x%08llx@0x%08llx\n", + MTD_DEV_TYPE(dev->id->type), dev->id->num, pnum, + part->name, part->size, part->offset); + + if (part_del(dev, part) != 0) + return 1; + + if (generate_mtdparts_save(last_parts, MTDPARTS_MAXLEN) != 0) { + printf("generated mtdparts too long, resetting to null\n"); + return 1; + } + return 0; + } + + printf("partition %s not found\n", id); + return 1; +} + +#if defined(CONFIG_CMD_MTDPARTS_SPREAD) +/** + * Increase the size of the given partition so that it's net size is at least + * as large as the size member and such that the next partition would start on a + * good block if it were adjacent to this partition. + * + * @param mtd the mtd device + * @param part the partition + * @param next_offset pointer to the offset of the next partition after this + * partition's size has been modified (output) + */ +static void spread_partition(struct mtd_info *mtd, struct part_info *part, + uint64_t *next_offset) +{ + uint64_t net_size, padding_size = 0; + int truncated; + + mtd_get_len_incl_bad(mtd, part->offset, part->size, &net_size, + &truncated); + + /* + * Absorb bad blocks immediately following this + * partition also into the partition, such that + * the next partition starts with a good block. + */ + if (!truncated) { + mtd_get_len_incl_bad(mtd, part->offset + net_size, + mtd->erasesize, &padding_size, &truncated); + if (truncated) + padding_size = 0; + else + padding_size -= mtd->erasesize; + } + + if (truncated) { + printf("truncated partition %s to %lld bytes\n", part->name, + (uint64_t) net_size + padding_size); + } + + part->size = net_size + padding_size; + *next_offset = part->offset + part->size; +} + +/** + * Adjust all of the partition sizes, such that all partitions are at least + * as big as their mtdparts environment variable sizes and they each start + * on a good block. + * + * Return: 0 on success, 1 otherwise + */ +static int spread_partitions(void) +{ + struct list_head *dentry, *pentry; + struct mtd_device *dev; + struct part_info *part; + struct mtd_info *mtd; + int part_num; + uint64_t cur_offs; + + list_for_each(dentry, &devices) { + dev = list_entry(dentry, struct mtd_device, link); + + if (get_mtd_info(dev->id->type, dev->id->num, &mtd)) + return 1; + + part_num = 0; + cur_offs = 0; + list_for_each(pentry, &dev->parts) { + part = list_entry(pentry, struct part_info, link); + + debug("spread_partitions: device = %s%d, partition %d =" + " (%s) 0x%08llx@0x%08llx\n", + MTD_DEV_TYPE(dev->id->type), dev->id->num, + part_num, part->name, part->size, + part->offset); + + if (cur_offs > part->offset) + part->offset = cur_offs; + + spread_partition(mtd, part, &cur_offs); + + part_num++; + } + } + + index_partitions(); + + if (generate_mtdparts_save(last_parts, MTDPARTS_MAXLEN) != 0) { + printf("generated mtdparts too long, resetting to null\n"); + return 1; + } + return 0; +} +#endif /* CONFIG_CMD_MTDPARTS_SPREAD */ + +/** + * The mtdparts variable tends to be long. If we need to access it + * before the env is relocated, then we need to use our own stack + * buffer. gd->env_buf will be too small. + * + * @param buf temporary buffer pointer MTDPARTS_MAXLEN long + * Return: mtdparts variable string, NULL if not found + */ +static const char *env_get_mtdparts(char *buf) +{ + if (gd->flags & GD_FLG_ENV_READY) + return env_get("mtdparts"); + if (env_get_f("mtdparts", buf, MTDPARTS_MAXLEN) != -1) + return buf; + return NULL; +} + +/** + * Accept character string describing mtd partitions and call device_parse() + * for each entry. Add created devices to the global devices list. + * + * @param mtdparts string specifing mtd partitions + * Return: 0 on success, 1 otherwise + */ +static int parse_mtdparts(const char *const mtdparts) +{ + const char *p; + struct mtd_device *dev; + int err = 1; + char tmp_parts[MTDPARTS_MAXLEN]; + + debug("\n---parse_mtdparts---\nmtdparts = %s\n\n", mtdparts); + + /* delete all devices and partitions */ + if (mtd_devices_init() != 0) { + printf("could not initialise device list\n"); + return err; + } + + /* re-read 'mtdparts' variable, mtd_devices_init may be updating env */ + p = env_get_mtdparts(tmp_parts); + if (!p) + p = mtdparts; + + /* Skip the useless prefix, if any */ + if (strncmp(p, "mtdparts=", 9) == 0) + p += 9; + + while (*p != '\0') { + err = 1; + if ((device_parse(p, &p, &dev) != 0) || (!dev)) + break; + + debug("+ device: %s\t%d\t%s\n", MTD_DEV_TYPE(dev->id->type), + dev->id->num, dev->id->mtd_id); + + /* check if parsed device is already on the list */ + if (device_find(dev->id->type, dev->id->num) != NULL) { + printf("device %s%d redefined, please correct mtdparts variable\n", + MTD_DEV_TYPE(dev->id->type), dev->id->num); + break; + } + + list_add_tail(&dev->link, &devices); + err = 0; + } + if (err == 1) { + free(dev); + device_delall(&devices); + } + + return err; +} + +/** + * Parse provided string describing mtdids mapping (see file header for mtdids + * variable format). Allocate memory for each entry and add all found entries + * to the global mtdids list. + * + * @param ids mapping string + * Return: 0 on success, 1 otherwise + */ +static int parse_mtdids(const char *const ids) +{ + const char *p = ids; + const char *mtd_id; + int mtd_id_len; + struct mtdids *id; + struct list_head *entry, *n; + struct mtdids *id_tmp; + u8 type, num; + u64 size; + int ret = 1; + + debug("\n---parse_mtdids---\nmtdids = %s\n\n", ids); + + /* clean global mtdids list */ + list_for_each_safe(entry, n, &mtdids) { + id_tmp = list_entry(entry, struct mtdids, link); + debug("mtdids del: %d %d\n", id_tmp->type, id_tmp->num); + list_del(entry); + free(id_tmp); + } + last_ids[0] = '\0'; + INIT_LIST_HEAD(&mtdids); + + while(p && (*p != '\0')) { + + ret = 1; + /* parse 'nor'|'nand'|'onenand'|'spi-nand'<dev-num> */ + if (mtd_id_parse(p, &p, &type, &num) != 0) + break; + + if (*p != '=') { + printf("mtdids: incorrect <dev-num>\n"); + break; + } + p++; + + /* check if requested device exists */ + if (mtd_device_validate(type, num, &size) != 0) + return 1; + + /* locate <mtd-id> */ + mtd_id = p; + if ((p = strchr(mtd_id, ',')) != NULL) { + mtd_id_len = p - mtd_id + 1; + p++; + } else { + mtd_id_len = strlen(mtd_id) + 1; + } + if (mtd_id_len == 0) { + printf("mtdids: no <mtd-id> identifier\n"); + break; + } + + /* check if this id is already on the list */ + int double_entry = 0; + list_for_each(entry, &mtdids) { + id_tmp = list_entry(entry, struct mtdids, link); + if ((id_tmp->type == type) && (id_tmp->num == num)) { + double_entry = 1; + break; + } + } + if (double_entry) { + printf("device id %s%d redefined, please correct mtdids variable\n", + MTD_DEV_TYPE(type), num); + break; + } + + /* allocate mtdids structure */ + if (!(id = (struct mtdids *)malloc(sizeof(struct mtdids) + mtd_id_len))) { + printf("out of memory\n"); + break; + } + memset(id, 0, sizeof(struct mtdids) + mtd_id_len); + id->num = num; + id->type = type; + id->size = size; + id->mtd_id = (char *)(id + 1); + strncpy(id->mtd_id, mtd_id, mtd_id_len - 1); + id->mtd_id[mtd_id_len - 1] = '\0'; + INIT_LIST_HEAD(&id->link); + + debug("+ id %s%d\t%16lld bytes\t%s\n", + MTD_DEV_TYPE(id->type), id->num, + id->size, id->mtd_id); + + list_add_tail(&id->link, &mtdids); + ret = 0; + } + if (ret == 1) { + /* clean mtdids list and free allocated memory */ + list_for_each_safe(entry, n, &mtdids) { + id_tmp = list_entry(entry, struct mtdids, link); + list_del(entry); + free(id_tmp); + } + return 1; + } + + return 0; +} + +/** + * Parse and initialize global mtdids mapping and create global + * device/partition list. + * + * Return: 0 on success, 1 otherwise + */ +int mtdparts_init(void) +{ + static int initialized = 0; + const char *ids, *parts; + const char *current_partition; + int ids_changed; + char tmp_ep[PARTITION_MAXLEN + 1]; + char tmp_parts[MTDPARTS_MAXLEN]; + + debug("\n---mtdparts_init---\n"); + if (!initialized) { + INIT_LIST_HEAD(&mtdids); + INIT_LIST_HEAD(&devices); + memset(last_ids, 0, sizeof(last_ids)); + memset(last_parts, 0, sizeof(last_parts)); + memset(last_partition, 0, sizeof(last_partition)); +#if defined(CONFIG_SYS_MTDPARTS_RUNTIME) + board_mtdparts_default(&mtdids_default, &mtdparts_default); +#endif + use_defaults = 1; + initialized = 1; + } + + /* get variables */ + ids = env_get("mtdids"); + parts = env_get_mtdparts(tmp_parts); + current_partition = env_get("partition"); + + /* save it for later parsing, cannot rely on current partition pointer + * as 'partition' variable may be updated during init */ + memset(tmp_parts, 0, sizeof(tmp_parts)); + memset(tmp_ep, 0, sizeof(tmp_ep)); + if (current_partition) + strncpy(tmp_ep, current_partition, PARTITION_MAXLEN); + + debug("last_ids : %s\n", last_ids); + debug("env_ids : %s\n", ids); + debug("last_parts: %s\n", last_parts); + debug("env_parts : %s\n\n", parts); + + debug("last_partition : %s\n", last_partition); + debug("env_partition : %s\n", current_partition); + + /* if mtdids variable is empty try to use defaults */ + if (!ids) { + if (mtdids_default) { + debug("mtdids variable not defined, using default\n"); + ids = mtdids_default; + env_set("mtdids", (char *)ids); + } else { + printf("mtdids not defined, no default present\n"); + return 1; + } + } + if (strlen(ids) > MTDIDS_MAXLEN - 1) { + printf("mtdids too long (> %d)\n", MTDIDS_MAXLEN); + return 1; + } + + /* use defaults when mtdparts variable is not defined + * once mtdparts is saved environment, drop use_defaults flag */ + if (!parts) { + if (mtdparts_default && use_defaults) { + parts = mtdparts_default; + if (env_set("mtdparts", (char *)parts) == 0) + use_defaults = 0; + } else + printf("mtdparts variable not set, see 'help mtdparts'\n"); + } + + if (parts && (strlen(parts) > MTDPARTS_MAXLEN - 1)) { + printf("mtdparts too long (> %d)\n", MTDPARTS_MAXLEN); + return 1; + } + + /* check if we have already parsed those mtdids */ + if ((last_ids[0] != '\0') && (strcmp(last_ids, ids) == 0)) { + ids_changed = 0; + } else { + ids_changed = 1; + + if (parse_mtdids(ids) != 0) { + mtd_devices_init(); + return 1; + } + + /* ok it's good, save new ids */ + strncpy(last_ids, ids, MTDIDS_MAXLEN); + } + + /* parse partitions if either mtdparts or mtdids were updated */ + if (parts && ((last_parts[0] == '\0') || ((strcmp(last_parts, parts) != 0)) || ids_changed)) { + if (parse_mtdparts(parts) != 0) + return 1; + + if (list_empty(&devices)) { + printf("mtdparts_init: no valid partitions\n"); + return 1; + } + + /* ok it's good, save new parts */ + strncpy(last_parts, parts, MTDPARTS_MAXLEN); + + /* reset first partition from first dev from the list as current */ + current_mtd_dev = list_entry(devices.next, struct mtd_device, link); + current_mtd_partnum = 0; + current_save(); + + debug("mtdparts_init: current_mtd_dev = %s%d, current_mtd_partnum = %d\n", + MTD_DEV_TYPE(current_mtd_dev->id->type), + current_mtd_dev->id->num, current_mtd_partnum); + } + + /* mtdparts variable was reset to NULL, delete all devices/partitions */ + if (!parts && (last_parts[0] != '\0')) + return mtd_devices_init(); + + /* do not process current partition if mtdparts variable is null */ + if (!parts) + return 0; + + /* is current partition set in environment? if so, use it */ + if ((tmp_ep[0] != '\0') && (strcmp(tmp_ep, last_partition) != 0)) { + struct part_info *p; + struct mtd_device *cdev; + u8 pnum; + + debug("--- getting current partition: %s\n", tmp_ep); + + if (find_dev_and_part(tmp_ep, &cdev, &pnum, &p) == 0) { + current_mtd_dev = cdev; + current_mtd_partnum = pnum; + current_save(); + } + } else if (env_get("partition") == NULL) { + debug("no partition variable set, setting...\n"); + current_save(); + } + + return 0; +} + +/** + * Return pointer to the partition of a requested number from a requested + * device. + * + * @param dev device that is to be searched for a partition + * @param part_num requested partition number + * Return: pointer to the part_info, NULL otherwise + */ +static struct part_info* mtd_part_info(struct mtd_device *dev, unsigned int part_num) +{ + struct list_head *entry; + struct part_info *part; + int num; + + if (!dev) + return NULL; + + debug("\n--- mtd_part_info: partition number %d for device %s%d (%s)\n", + part_num, MTD_DEV_TYPE(dev->id->type), + dev->id->num, dev->id->mtd_id); + + if (part_num >= dev->num_parts) { + printf("invalid partition number %d for device %s%d (%s)\n", + part_num, MTD_DEV_TYPE(dev->id->type), + dev->id->num, dev->id->mtd_id); + return NULL; + } + + /* locate partition number, return it */ + num = 0; + list_for_each(entry, &dev->parts) { + part = list_entry(entry, struct part_info, link); + + if (part_num == num++) { + return part; + } + } + + return NULL; +} + +/***************************************************/ +/* U-Boot commands */ +/***************************************************/ +/* command line only */ +/** + * Routine implementing u-boot chpart command. Sets new current partition based + * on the user supplied partition id. For partition id format see find_dev_and_part(). + * + * @param cmdtp command internal data + * @param flag command flag + * @param argc number of arguments supplied to the command + * @param argv arguments list + * Return: 0 on success, 1 otherwise + */ +static int do_chpart(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ +/* command line only */ + struct mtd_device *dev; + struct part_info *part; + u8 pnum; + + if (mtdparts_init() !=0) + return 1; + + if (argc < 2) { + printf("no partition id specified\n"); + return 1; + } + + if (find_dev_and_part(argv[1], &dev, &pnum, &part) != 0) + return 1; + + current_mtd_dev = dev; + current_mtd_partnum = pnum; + current_save(); + + printf("partition changed to %s%d,%d\n", + MTD_DEV_TYPE(dev->id->type), dev->id->num, pnum); + + return 0; +} + +/** + * Routine implementing u-boot mtdparts command. Initialize/update default global + * partition list and process user partition request (list, add, del). + * + * @param cmdtp command internal data + * @param flag command flag + * @param argc number of arguments supplied to the command + * @param argv arguments list + * Return: 0 on success, 1 otherwise + */ +static int do_mtdparts(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc == 2) { + if (strcmp(argv[1], "default") == 0) { + env_set("mtdids", NULL); + env_set("mtdparts", NULL); + env_set("partition", NULL); + use_defaults = 1; + + mtdparts_init(); + return 0; + } else if (strcmp(argv[1], "delall") == 0) { + /* this may be the first run, initialize lists if needed */ + mtdparts_init(); + + env_set("mtdparts", NULL); + + /* mtd_devices_init() calls current_save() */ + return mtd_devices_init(); + } + } + + /* make sure we are in sync with env variables */ + if (mtdparts_init() != 0) + return 1; + + if (argc == 1) { + list_partitions(); + return 0; + } + + /* mtdparts add <mtd-dev> <size>[@<offset>] <name> [ro] */ + if (((argc == 5) || (argc == 6)) && (strncmp(argv[1], "add", 3) == 0)) { +#define PART_ADD_DESC_MAXLEN 64 + char tmpbuf[PART_ADD_DESC_MAXLEN]; +#if defined(CONFIG_CMD_MTDPARTS_SPREAD) + struct mtd_info *mtd; + uint64_t next_offset; +#endif + u8 type, num, len; + struct mtd_device *dev; + struct mtd_device *dev_tmp; + struct mtdids *id; + struct part_info *p; + + if (mtd_id_parse(argv[2], NULL, &type, &num) != 0) + return 1; + + if ((id = id_find(type, num)) == NULL) { + printf("no such device %s defined in mtdids variable\n", argv[2]); + return 1; + } + + len = strlen(id->mtd_id) + 1; /* 'mtd_id:' */ + len += strlen(argv[3]); /* size@offset */ + len += strlen(argv[4]) + 2; /* '(' name ')' */ + if (argv[5] && (strlen(argv[5]) == 2)) + len += 2; /* 'ro' */ + + if (len >= PART_ADD_DESC_MAXLEN) { + printf("too long partition description\n"); + return 1; + } + sprintf(tmpbuf, "%s:%s(%s)%s", + id->mtd_id, argv[3], argv[4], argv[5] ? argv[5] : ""); + debug("add tmpbuf: %s\n", tmpbuf); + + if ((device_parse(tmpbuf, NULL, &dev) != 0) || (!dev)) + return 1; + + debug("+ %s\t%d\t%s\n", MTD_DEV_TYPE(dev->id->type), + dev->id->num, dev->id->mtd_id); + + p = list_entry(dev->parts.next, struct part_info, link); + +#if defined(CONFIG_CMD_MTDPARTS_SPREAD) + if (get_mtd_info(dev->id->type, dev->id->num, &mtd)) + return 1; + + if (!strcmp(&argv[1][3], ".spread")) { + spread_partition(mtd, p, &next_offset); + debug("increased %s to %llu bytes\n", p->name, p->size); + } +#endif + + dev_tmp = device_find(dev->id->type, dev->id->num); + if (dev_tmp == NULL) { + device_add(dev); + } else if (part_add(dev_tmp, p) != 0) { + /* merge new partition with existing ones*/ + device_del(dev); + return 1; + } + + if (generate_mtdparts_save(last_parts, MTDPARTS_MAXLEN) != 0) { + printf("generated mtdparts too long, resetting to null\n"); + return 1; + } + + return 0; + } + + /* mtdparts del part-id */ + if ((argc == 3) && (strcmp(argv[1], "del") == 0)) { + debug("del: part-id = %s\n", argv[2]); + + return delete_partition(argv[2]); + } + +#if defined(CONFIG_CMD_MTDPARTS_SPREAD) + if ((argc == 2) && (strcmp(argv[1], "spread") == 0)) + return spread_partitions(); +#endif /* CONFIG_CMD_MTDPARTS_SPREAD */ + + return CMD_RET_USAGE; +} + +/***************************************************/ +U_BOOT_CMD( + chpart, 2, 0, do_chpart, + "change active partition of a MTD device", + "part-id\n" + " - change active partition (e.g. part-id = nand0,1) of a MTD device" +); + +U_BOOT_LONGHELP(mtdparts, + "\n" + " - list partition table\n" + "mtdparts delall\n" + " - delete all partitions\n" + "mtdparts del part-id\n" + " - delete partition (e.g. part-id = nand0,1)\n" + "mtdparts add <mtd-dev> <size>[@<offset>] [<name>] [ro]\n" + " - add partition\n" +#if defined(CONFIG_CMD_MTDPARTS_SPREAD) + "mtdparts add.spread <mtd-dev> <size>[@<offset>] [<name>] [ro]\n" + " - add partition, padding size by skipping bad blocks\n" +#endif + "mtdparts default\n" + " - reset partition table to defaults\n" +#if defined(CONFIG_CMD_MTDPARTS_SPREAD) + "mtdparts spread\n" + " - adjust the sizes of the partitions so they are\n" + " at least as big as the mtdparts variable specifies\n" + " and they each start on a good block\n\n" +#else + "\n" +#endif /* CONFIG_CMD_MTDPARTS_SPREAD */ + "-----\n\n" + "this command uses three environment variables:\n\n" + "'partition' - keeps current partition identifier\n\n" + "partition := <part-id>\n" + "<part-id> := <dev-id>,part_num\n\n" + "'mtdids' - linux kernel mtd device id <-> u-boot device id mapping\n\n" + "mtdids=<idmap>[,<idmap>,...]\n\n" + "<idmap> := <dev-id>=<mtd-id>\n" + "<dev-id> := 'nand'|'nor'|'onenand'|'spi-nand'<dev-num>\n" + "<dev-num> := mtd device number, 0...\n" + "<mtd-id> := unique device tag used by linux kernel to find mtd device (mtd->name)\n\n" + "'mtdparts' - partition list\n\n" + "mtdparts=mtdparts=<mtd-def>[;<mtd-def>...]\n\n" + "<mtd-def> := <mtd-id>:<part-def>[,<part-def>...]\n" + "<mtd-id> := unique device tag used by linux kernel to find mtd device (mtd->name)\n" + "<part-def> := <size>[@<offset>][<name>][<ro-flag>]\n" + "<size> := standard linux memsize OR '-' to denote all remaining space\n" + "<offset> := partition start offset within the device\n" + "<name> := '(' NAME ')'\n" + "<ro-flag> := when set to 'ro' makes partition read-only (not used, passed to kernel)"); + +U_BOOT_CMD( + mtdparts, 6, 0, do_mtdparts, + "define flash/nand partitions", mtdparts_help_text +); +/***************************************************/ diff --git a/cmd/mux.c b/cmd/mux.c new file mode 100644 index 00000000000..2f6c08b8b07 --- /dev/null +++ b/cmd/mux.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * List, select, and deselect mux controllers on the fly. + * + * Copyright (c) 2020 Texas Instruments Inc. + * Author: Pratyush Yadav <p.yadav@ti.com> + */ + +#include <command.h> +#include <errno.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <mux.h> +#include <mux-internal.h> +#include <linux/err.h> +#include <dt-bindings/mux/mux.h> + +#define COLUMN_SIZE 16 + +/* + * Print a member of a column. The total size of the text printed, including + * trailing whitespace, will always be COLUMN_SIZE. + */ +#define PRINT_COLUMN(fmt, args...) do { \ + char buf[COLUMN_SIZE + 1]; \ + snprintf(buf, COLUMN_SIZE + 1, fmt, ##args); \ + printf("%-*s", COLUMN_SIZE, buf); \ +} while (0) + +/* + * Find a mux based on its device name in argv[1] and index in the chip in + * argv[2]. + */ +static struct mux_control *cmd_mux_find(char *const argv[]) +{ + struct udevice *dev; + struct mux_chip *chip; + int ret; + unsigned long id; + + ret = strict_strtoul(argv[2], 10, &id); + if (ret) + return ERR_PTR(ret); + + ret = uclass_get_device_by_name(UCLASS_MUX, argv[1], &dev); + if (ret) + return ERR_PTR(ret); + + chip = dev_get_uclass_priv(dev); + if (!chip) + return ERR_PTR(-EINVAL); + + if (id >= chip->controllers) + return ERR_PTR(-EINVAL); + + return &chip->mux[id]; +} + +/* + * Print the details of a mux. The columns printed correspond to: "Selected", + * "Current State", "Idle State", and "Num States". + */ +static void print_mux(struct mux_control *mux) +{ + PRINT_COLUMN("%s", mux->in_use ? "yes" : "no"); + + if (mux->cached_state == MUX_IDLE_AS_IS) + PRINT_COLUMN("%s", "unknown"); + else + PRINT_COLUMN("0x%x", mux->cached_state); + + if (mux->idle_state == MUX_IDLE_AS_IS) + PRINT_COLUMN("%s", "as-is"); + else if (mux->idle_state == MUX_IDLE_DISCONNECT) + PRINT_COLUMN("%s", "disconnect"); + else + PRINT_COLUMN("0x%x", mux->idle_state); + + PRINT_COLUMN("0x%x", mux->states); + + printf("\n"); +} + +static int do_mux_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + struct mux_chip *chip; + int j; + + for (uclass_first_device(UCLASS_MUX, &dev); + dev; + uclass_next_device(&dev)) { + chip = dev_get_uclass_priv(dev); + if (!chip) { + dev_err(dev, "can't find mux chip\n"); + continue; + } + + printf("%s:\n", dev->name); + + printf(" "); + PRINT_COLUMN("ID"); + PRINT_COLUMN("Selected"); + PRINT_COLUMN("Current State"); + PRINT_COLUMN("Idle State"); + PRINT_COLUMN("Num States"); + printf("\n"); + for (j = 0; j < chip->controllers; j++) { + printf(" "); + PRINT_COLUMN("%d", j); + print_mux(&chip->mux[j]); + } + printf("\n"); + } + + return 0; +} + +static int do_mux_select(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mux_control *mux; + int ret; + unsigned long state; + + if (argc != 4) + return CMD_RET_USAGE; + + mux = cmd_mux_find(argv); + if (IS_ERR_OR_NULL(mux)) { + printf("Failed to find the specified mux\n"); + return CMD_RET_FAILURE; + } + + ret = strict_strtoul(argv[3], 16, &state); + if (ret) { + printf("Invalid state\n"); + return CMD_RET_FAILURE; + } + + ret = mux_control_select(mux, state); + if (ret) { + printf("Failed to select requested state\n"); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_mux_deselect(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mux_control *mux; + int ret; + + if (argc != 3) + return CMD_RET_USAGE; + + mux = cmd_mux_find(argv); + if (IS_ERR_OR_NULL(mux)) { + printf("Failed to find the specified mux\n"); + return CMD_RET_FAILURE; + } + + ret = mux_control_deselect(mux); + if (ret) { + printf("Failed to deselect mux\n"); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(mux, + "list - List all Muxes and their states\n" + "select <chip> <id> <state> - Select the given mux state\n" + "deselect <chip> <id> - Deselect the given mux and reset it to its idle state"); + +U_BOOT_CMD_WITH_SUBCMDS(mux, "List, select, and deselect muxes", mux_help_text, + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_mux_list), + U_BOOT_SUBCMD_MKENT(select, 4, 0, do_mux_select), + U_BOOT_SUBCMD_MKENT(deselect, 3, 0, do_mux_deselect)); diff --git a/cmd/mvebu/Kconfig b/cmd/mvebu/Kconfig new file mode 100644 index 00000000000..e83a9829491 --- /dev/null +++ b/cmd/mvebu/Kconfig @@ -0,0 +1,82 @@ +menu "MVEBU commands" +depends on ARCH_MVEBU + +config CMD_MVEBU_BUBT + bool "bubt" + default y + select SHA256 if ARMADA_3700 + select SHA512 if ARMADA_3700 + select DOS_PARTITION if ARMADA_3700 + select EFI_PARTITION if ARMADA_3700 + select PARTITION_TYPE_GUID if ARMADA_3700 + select MVEBU_EFUSE if ARMADA_38X || ARMADA_3700 + help + bubt - Burn a u-boot image to flash + For details about bubt command please see the documentation + in doc/mvebu/cmd/bubt.txt + +if CMD_MVEBU_BUBT + +choice + prompt "Flash for image" + default MVEBU_SPI_BOOT if MVEBU_SPL_BOOT_DEVICE_SPI + default MVEBU_NAND_BOOT if MVEBU_SPL_BOOT_DEVICE_NAND + default MVEBU_MMC_BOOT if MVEBU_SPL_BOOT_DEVICE_MMC + default MVEBU_SATA_BOOT if MVEBU_SPL_BOOT_DEVICE_SATA + default MVEBU_SPI_BOOT + +config MVEBU_NAND_BOOT + bool "NAND flash boot" + depends on NAND_PXA3XX + help + Enable boot from NAND flash. + Allow usage of NAND flash as a target for "bubt" command + For details about bubt command please see the documentation + in doc/mvebu/cmd/bubt.txt + +config MVEBU_SPI_BOOT + bool "SPI flash boot" + depends on SPI_FLASH + help + Enable boot from SPI flash. + Allow usage of SPI flash as a target for "bubt" command + For details about bubt command please see the documentation + in doc/mvebu/cmd/bubt.txt + +config MVEBU_MMC_BOOT + bool "eMMC flash boot" + depends on MVEBU_MMC || MMC_SDHCI_XENON + help + Enable boot from eMMC boot partition + Allow usage of eMMC/SD device as a target for "bubt" command + For details about bubt command please see the documentation + in doc/mvebu/cmd/bubt.txt + +config MVEBU_SATA_BOOT + bool "SATA flash boot" + depends on SCSI + help + Enable boot from SATA disk. + Allow usage of SATA disk as a target for "bubt" command + For details about bubt command please see the documentation + in doc/mvebu/cmd/bubt.txt + +endchoice + +config MVEBU_UBOOT_DFLT_NAME + string "Default image name for bubt command" + default BUILD_TARGET if ARMADA_32BIT && BUILD_TARGET != "" + default "flash-image.bin" + help + This option should contain a default file name to be used with + MVEBU "bubt" command if the source file name is omitted + +endif + +config CMD_MVEBU_COMPHY_RX_TRAINING + bool "mvebu_comphy_rx_training" + depends on ARMADA_8K + help + Perform COMPHY RX training sequence + +endmenu diff --git a/cmd/mvebu/Makefile b/cmd/mvebu/Makefile new file mode 100644 index 00000000000..ca96ad01d91 --- /dev/null +++ b/cmd/mvebu/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2016 Marvell International Ltd. +# +# https://spdx.org/licenses + +obj-$(CONFIG_CMD_MVEBU_BUBT) += bubt.o +obj-$(CONFIG_CMD_MVEBU_COMPHY_RX_TRAINING) += comphy_rx_training.o diff --git a/cmd/mvebu/bubt.c b/cmd/mvebu/bubt.c new file mode 100644 index 00000000000..2755c26cdf7 --- /dev/null +++ b/cmd/mvebu/bubt.c @@ -0,0 +1,1265 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016 Marvell International Ltd. + * https://spdx.org/licenses + */ + +#include <config.h> +#include <command.h> +#include <env.h> +#include <image.h> +#include <net.h> +#include <vsprintf.h> +#include <errno.h> +#include <dm.h> +#include <fuse.h> +#include <mach/efuse.h> + +#include <spi_flash.h> +#include <spi.h> +#include <nand.h> +#include <scsi.h> +#include <usb.h> +#include <fs.h> +#include <mmc.h> +#ifdef CONFIG_BLK +#include <blk.h> +#endif +#include <u-boot/sha1.h> +#include <u-boot/sha256.h> +#include <u-boot/sha512.h> + +#if defined(CONFIG_ARMADA_8K) +#define MAIN_HDR_MAGIC 0xB105B002 + +struct mvebu_image_header { + u32 magic; /* 0-3 */ + u32 prolog_size; /* 4-7 */ + u32 prolog_checksum; /* 8-11 */ + u32 boot_image_size; /* 12-15 */ + u32 boot_image_checksum; /* 16-19 */ + u32 rsrvd0; /* 20-23 */ + u32 load_addr; /* 24-27 */ + u32 exec_addr; /* 28-31 */ + u8 uart_cfg; /* 32 */ + u8 baudrate; /* 33 */ + u8 ext_count; /* 34 */ + u8 aux_flags; /* 35 */ + u32 io_arg_0; /* 36-39 */ + u32 io_arg_1; /* 40-43 */ + u32 io_arg_2; /* 43-47 */ + u32 io_arg_3; /* 48-51 */ + u32 rsrvd1; /* 52-55 */ + u32 rsrvd2; /* 56-59 */ + u32 rsrvd3; /* 60-63 */ +}; +#elif defined(CONFIG_ARMADA_3700) /* A3700 */ +#define HASH_SUM_LEN 16 +#define IMAGE_VERSION_3_6_0 0x030600 +#define IMAGE_VERSION_3_5_0 0x030500 + +struct tim_boot_flash_sign { + unsigned int id; + const char *name; +}; + +struct tim_boot_flash_sign tim_boot_flash_signs[] = { + { 0x454d4d08, "mmc" }, + { 0x454d4d0b, "mmc" }, + { 0x5350490a, "spi" }, + { 0x5350491a, "nand" }, + { 0x55415223, "uart" }, + { 0x53415432, "sata" }, + {}, +}; + +struct common_tim_data { + u32 version; + u32 identifier; + u32 trusted; + u32 issue_date; + u32 oem_unique_id; + u32 reserved[5]; /* Reserve 20 bytes */ + u32 boot_flash_sign; + u32 num_images; + u32 num_keys; + u32 size_of_reserved; +}; + +struct mvebu_image_info { + u32 image_id; + u32 next_image_id; + u32 flash_entry_addr; + u32 load_addr; + u32 image_size; + u32 image_size_to_hash; + u32 hash_algorithm_id; + u32 hash[HASH_SUM_LEN]; /* Reserve 512 bits for the hash */ + u32 partition_number; + u32 enc_algorithm_id; + u32 encrypt_start_offset; + u32 encrypt_size; +}; +#elif defined(CONFIG_ARMADA_32BIT) + +/* Structure of the main header, version 1 (Armada 370/XP/375/38x/39x) */ +struct a38x_main_hdr_v1 { + u8 blockid; /* 0x0 */ + u8 flags; /* 0x1 */ + u16 nandpagesize; /* 0x2-0x3 */ + u32 blocksize; /* 0x4-0x7 */ + u8 version; /* 0x8 */ + u8 headersz_msb; /* 0x9 */ + u16 headersz_lsb; /* 0xA-0xB */ + u32 srcaddr; /* 0xC-0xF */ + u32 destaddr; /* 0x10-0x13 */ + u32 execaddr; /* 0x14-0x17 */ + u8 options; /* 0x18 */ + u8 nandblocksize; /* 0x19 */ + u8 nandbadblklocation; /* 0x1A */ + u8 reserved4; /* 0x1B */ + u16 reserved5; /* 0x1C-0x1D */ + u8 ext; /* 0x1E */ + u8 checksum; /* 0x1F */ +}; + +/* + * Header for the optional headers, version 1 (Armada 370/XP/375/38x/39x) + */ +struct a38x_opt_hdr_v1 { + u8 headertype; + u8 headersz_msb; + u16 headersz_lsb; + u8 data[0]; +}; +#define A38X_OPT_HDR_V1_SECURE_TYPE 0x1 + +struct a38x_boot_mode { + unsigned int id; + const char *name; +}; + +/* The blockid header field values used to indicate boot device of image */ +struct a38x_boot_mode a38x_boot_modes[] = { + { 0x4D, "i2c" }, + { 0x5A, "spi" }, + { 0x69, "uart" }, + { 0x78, "sata" }, + { 0x8B, "nand" }, + { 0x9C, "pex" }, + { 0xAE, "mmc" }, + {}, +}; + +#endif + +struct bubt_dev { + char name[8]; + size_t (*read)(const char *file_name); + int (*write)(size_t image_size); + int (*active)(void); +}; + +static ulong get_load_addr(void) +{ + const char *addr_str; + unsigned long addr; + + addr_str = env_get("loadaddr"); + if (addr_str) + addr = hextoul(addr_str, NULL); + else + addr = CONFIG_SYS_LOAD_ADDR; + + return addr; +} + +/******************************************************************** + * eMMC services + ********************************************************************/ +#if CONFIG_IS_ENABLED(DM_MMC) && CONFIG_IS_ENABLED(MMC_WRITE) +static int mmc_burn_image(size_t image_size) +{ + struct mmc *mmc; + lbaint_t start_lba; + lbaint_t blk_count; + ulong blk_written; + int err; + const u8 mmc_dev_num = CONFIG_ENV_MMC_DEVICE_INDEX; +#ifdef CONFIG_BLK + struct blk_desc *blk_desc; +#endif +#ifdef CONFIG_SUPPORT_EMMC_BOOT + u8 part; + u8 orig_part; +#endif + + mmc = find_mmc_device(mmc_dev_num); + if (!mmc) { + printf("No SD/MMC/eMMC card found\n"); + return -ENOMEDIUM; + } + + err = mmc_init(mmc); + if (err) { + printf("%s(%d) init failed\n", IS_SD(mmc) ? "SD" : "MMC", + mmc_dev_num); + return err; + } + +#ifdef CONFIG_BLK + blk_desc = mmc_get_blk_desc(mmc); + if (!blk_desc) { + printf("Error - failed to obtain block descriptor\n"); + return -ENODEV; + } +#endif + +#ifdef CONFIG_SUPPORT_EMMC_BOOT +#ifdef CONFIG_BLK + orig_part = blk_desc->hwpart; +#else + orig_part = mmc->block_dev.hwpart; +#endif + + part = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config); + if (part == EMMC_BOOT_PART_USER) + part = EMMC_HWPART_DEFAULT; + +#ifdef CONFIG_BLK + err = blk_dselect_hwpart(blk_desc, part); +#else + err = mmc_switch_part(mmc, part); +#endif + + if (err) { + printf("Error - MMC partition switch failed\n"); + return err; + } +#endif + + /* SD reserves LBA-0 for MBR and boots from LBA-1, + * MMC/eMMC boots from LBA-0 and LBA-4096 + */ + if (IS_SD(mmc)) + start_lba = 1; +#ifdef CONFIG_SUPPORT_EMMC_BOOT + else if (part) + start_lba = 0; +#endif + else + start_lba = 4096; +#ifdef CONFIG_BLK + blk_count = image_size / mmc->write_bl_len; + if (image_size % mmc->write_bl_len) + blk_count += 1; + + blk_written = blk_dwrite(blk_desc, start_lba, blk_count, + (void *)get_load_addr()); +#else + blk_count = image_size / mmc->block_dev.blksz; + if (image_size % mmc->block_dev.blksz) + blk_count += 1; + + blk_written = mmc->block_dev.block_write(mmc_dev_num, + start_lba, blk_count, + (void *)get_load_addr()); +#endif /* CONFIG_BLK */ + +#ifdef CONFIG_SUPPORT_EMMC_BOOT +#ifdef CONFIG_BLK + err = blk_dselect_hwpart(blk_desc, orig_part); +#else + err = mmc_switch_part(mmc, orig_part); +#endif + if (err) + printf("Error - MMC failed to switch back to original partition\n"); +#endif + + if (blk_written != blk_count) { + printf("Error - written %#lx blocks\n", blk_written); + return -ENOSPC; + } + printf("Done!\n"); + + return 0; +} + +static size_t mmc_read_file(const char *file_name) +{ + loff_t act_read = 0; + int rc; + struct mmc *mmc; + const u8 mmc_dev_num = CONFIG_ENV_MMC_DEVICE_INDEX; + + mmc = find_mmc_device(mmc_dev_num); + if (!mmc) { + printf("No SD/MMC/eMMC card found\n"); + return 0; + } + + if (mmc_init(mmc)) { + printf("%s(%d) init failed\n", IS_SD(mmc) ? "SD" : "MMC", + mmc_dev_num); + return 0; + } + + /* Load from data partition (0) */ + if (fs_set_blk_dev("mmc", "0", FS_TYPE_ANY)) { + printf("Error: MMC 0 not found\n"); + return 0; + } + + /* Perfrom file read */ + rc = fs_read(file_name, get_load_addr(), 0, 0, &act_read); + if (rc) + return 0; + + return act_read; +} + +static int is_mmc_active(void) +{ + return 1; +} +#else /* CONFIG_DM_MMC */ +static int mmc_burn_image(size_t image_size) +{ + return -ENODEV; +} + +static size_t mmc_read_file(const char *file_name) +{ + return 0; +} + +static int is_mmc_active(void) +{ + return 0; +} +#endif /* CONFIG_DM_MMC */ + +/******************************************************************** + * SATA services + ********************************************************************/ +#if defined(CONFIG_SCSI) && defined(CONFIG_BLK) +static int sata_burn_image(size_t image_size) +{ +#if defined(CONFIG_ARMADA_3700) || defined(CONFIG_ARMADA_32BIT) + lbaint_t start_lba; + lbaint_t blk_count; + ulong blk_written; + struct blk_desc *blk_desc; +#ifdef CONFIG_ARMADA_3700 + struct disk_partition info; + int part; +#endif + + scsi_scan(false); + + blk_desc = blk_get_devnum_by_uclass_id(UCLASS_SCSI, 0); + if (!blk_desc) + return -ENODEV; + +#ifdef CONFIG_ARMADA_3700 + /* + * 64-bit Armada 3700 BootROM loads SATA firmware from + * GPT 'Marvell Armada 3700 Boot partition' or from + * MBR 'M' (0x4d) partition. + */ + switch (blk_desc->part_type) { + case PART_TYPE_DOS: + for (part = 1; part <= 4; part++) { + info.sys_ind = 0; + if (part_get_info(blk_desc, part, &info)) + continue; + if (info.sys_ind == 'M') + break; + } + if (part > 4) { + printf("Error - cannot find MBR 'M' (0x4d) partition on SATA disk\n"); + return -ENODEV; + } + start_lba = info.start; + break; + case PART_TYPE_EFI: + for (part = 1; part <= 64; part++) { + info.type_guid[0] = 0; + if (part_get_info(blk_desc, part, &info)) + continue; + /* Check for GPT type GUID of 'Marvell Armada 3700 Boot partition' */ + if (strcmp(info.type_guid, "6828311A-BA55-42A4-BCDE-A89BB5EDECAE") == 0) + break; + } + if (part > 64) { + printf("Error - cannot find GPT 'Marvell Armada 3700 Boot partition' on SATA disk\n"); + return -ENODEV; + } + start_lba = info.start; + break; + default: + printf("Error - no partitions on SATA disk\n"); + return -ENODEV; + } +#else + /* 32-bit Armada BootROM loads SATA firmware from the sector 1. */ + start_lba = 1; +#endif + + blk_count = image_size / blk_desc->blksz; + if (image_size % blk_desc->blksz) + blk_count += 1; + + blk_written = blk_dwrite(blk_desc, start_lba, blk_count, + (void *)get_load_addr()); + + if (blk_written != blk_count) { + printf("Error - written %#lx blocks\n", blk_written); + return -ENOSPC; + } + + printf("Done!\n"); + return 0; +#else + return -ENODEV; +#endif +} + +static size_t sata_read_file(const char *file_name) +{ + loff_t act_read = 0; + struct udevice *dev; + int rc; + + /* try to recognize storage devices immediately */ + scsi_scan(false); + + /* Try to recognize storage devices immediately */ + blk_first_device(UCLASS_SCSI, &dev); + if (!dev) { + printf("Error: SATA device not found\n"); + return 0; + } + + /* Always load from scsi 0 */ + if (fs_set_blk_dev("scsi", "0", FS_TYPE_ANY)) { + printf("Error: SATA 0 not found\n"); + return 0; + } + + /* Perfrom file read */ + rc = fs_read(file_name, get_load_addr(), 0, 0, &act_read); + if (rc) + return 0; + + return act_read; +} + +static int is_sata_active(void) +{ + return 1; +} +#else /* CONFIG_SCSI */ +static int sata_burn_image(size_t image_size) +{ + return -ENODEV; +} + +static size_t sata_read_file(const char *file_name) +{ + return 0; +} + +static int is_sata_active(void) +{ + return 0; +} +#endif /* CONFIG_SCSI */ + +/******************************************************************** + * SPI services + ********************************************************************/ +#ifdef CONFIG_SPI_FLASH +static int spi_burn_image(size_t image_size) +{ + int ret; + struct spi_flash *flash; + u32 erase_bytes; + + /* Probe the SPI bus to get the flash device */ + flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS, + CONFIG_SF_DEFAULT_CS, + CONFIG_SF_DEFAULT_SPEED, + CONFIG_SF_DEFAULT_MODE); + if (!flash) { + printf("Failed to probe SPI Flash\n"); + return -ENOMEDIUM; + } + + erase_bytes = image_size + + (flash->erase_size - image_size % flash->erase_size); + printf("Erasing %d bytes (%d blocks) at offset 0 ...", + erase_bytes, erase_bytes / flash->erase_size); + ret = spi_flash_erase(flash, 0, erase_bytes); + if (ret) + printf("Error!\n"); + else + printf("Done!\n"); + + printf("Writing %d bytes from 0x%lx to offset 0 ...", + (int)image_size, get_load_addr()); + ret = spi_flash_write(flash, 0, image_size, (void *)get_load_addr()); + if (ret) + printf("Error!\n"); + else + printf("Done!\n"); + + return ret; +} + +static int is_spi_active(void) +{ + return 1; +} + +#else /* CONFIG_SPI_FLASH */ +static int spi_burn_image(size_t image_size) +{ + return -ENODEV; +} + +static int is_spi_active(void) +{ + return 0; +} +#endif /* CONFIG_SPI_FLASH */ + +/******************************************************************** + * NAND services + ********************************************************************/ +#ifdef CONFIG_CMD_NAND +static int nand_burn_image(size_t image_size) +{ + int ret; + uint32_t block_size; + struct mtd_info *mtd; + + mtd = get_nand_dev_by_index(nand_curr_device); + if (!mtd) { + puts("\nno devices available\n"); + return -ENOMEDIUM; + } + block_size = mtd->erasesize; + + /* Align U-Boot size to currently used blocksize */ + image_size = ((image_size + (block_size - 1)) & (~(block_size - 1))); + + /* Erase the U-Boot image space */ + printf("Erasing 0x%x - 0x%x:...", 0, (int)image_size); + ret = nand_erase(mtd, 0, image_size); + if (ret) { + printf("Error!\n"); + goto error; + } + printf("Done!\n"); + + /* Write the image to flash */ + printf("Writing %d bytes from 0x%lx to offset 0 ... ", + (int)image_size, get_load_addr()); + ret = nand_write(mtd, 0, &image_size, (void *)get_load_addr()); + if (ret) + printf("Error!\n"); + else + printf("Done!\n"); + +error: + return ret; +} + +static int is_nand_active(void) +{ + return 1; +} + +#else /* CONFIG_CMD_NAND */ +static int nand_burn_image(size_t image_size) +{ + return -ENODEV; +} + +static int is_nand_active(void) +{ + return 0; +} +#endif /* CONFIG_CMD_NAND */ + +/******************************************************************** + * USB services + ********************************************************************/ +#if defined(CONFIG_USB_STORAGE) && defined(CONFIG_BLK) +static size_t usb_read_file(const char *file_name) +{ + loff_t act_read = 0; + struct udevice *dev; + int rc; + + usb_stop(); + + if (usb_init() < 0) { + printf("Error: usb_init failed\n"); + return 0; + } + + /* Try to recognize storage devices immediately */ + blk_first_device(UCLASS_USB, &dev); + if (!dev) { + printf("Error: USB storage device not found\n"); + return 0; + } + + /* Always load from usb 0 */ + if (fs_set_blk_dev("usb", "0", FS_TYPE_ANY)) { + printf("Error: USB 0 not found\n"); + return 0; + } + + /* Perfrom file read */ + rc = fs_read(file_name, get_load_addr(), 0, 0, &act_read); + if (rc) + return 0; + + return act_read; +} + +static int is_usb_active(void) +{ + return 1; +} + +#else /* defined(CONFIG_USB_STORAGE) && defined (CONFIG_BLK) */ +static size_t usb_read_file(const char *file_name) +{ + return 0; +} + +static int is_usb_active(void) +{ + return 0; +} +#endif /* defined(CONFIG_USB_STORAGE) && defined (CONFIG_BLK) */ + +/******************************************************************** + * Network services + ********************************************************************/ +#ifdef CONFIG_CMD_NET +static size_t tftp_read_file(const char *file_name) +{ + int ret; + + /* + * update global variable image_load_addr before tftp file from network + */ + image_load_addr = get_load_addr(); + ret = net_loop(TFTPGET); + return ret > 0 ? ret : 0; +} + +static int is_tftp_active(void) +{ + return 1; +} + +#else +static size_t tftp_read_file(const char *file_name) +{ + return 0; +} + +static int is_tftp_active(void) +{ + return 0; +} +#endif /* CONFIG_CMD_NET */ + +enum bubt_devices { + BUBT_DEV_NET = 0, + BUBT_DEV_USB, + BUBT_DEV_MMC, + BUBT_DEV_SATA, + BUBT_DEV_SPI, + BUBT_DEV_NAND, + + BUBT_MAX_DEV +}; + +static struct bubt_dev bubt_devs[BUBT_MAX_DEV] = { + {"tftp", tftp_read_file, NULL, is_tftp_active}, + {"usb", usb_read_file, NULL, is_usb_active}, + {"mmc", mmc_read_file, mmc_burn_image, is_mmc_active}, + {"sata", sata_read_file, sata_burn_image, is_sata_active}, + {"spi", NULL, spi_burn_image, is_spi_active}, + {"nand", NULL, nand_burn_image, is_nand_active}, +}; + +static int bubt_write_file(struct bubt_dev *dst, size_t image_size) +{ + if (!dst->write) { + printf("Error: Write not supported on device %s\n", dst->name); + return -ENOTSUPP; + } + + return dst->write(image_size); +} + +#if defined(CONFIG_ARMADA_8K) +static u32 do_checksum32(u32 *start, int32_t len) +{ + u32 sum = 0; + u32 *startp = start; + + do { + sum += *startp; + startp++; + len -= 4; + } while (len > 0); + + return sum; +} + +static int check_image_header(void) +{ + struct mvebu_image_header *hdr = + (struct mvebu_image_header *)get_load_addr(); + u32 checksum; + u32 checksum_ref; + + /* + * For now compare checksum, and magic. Later we can + * verify more stuff on the header like interface type, etc + */ + if (hdr->magic != MAIN_HDR_MAGIC) { + printf("ERROR: Bad MAGIC 0x%08x != 0x%08x\n", + hdr->magic, MAIN_HDR_MAGIC); + return -ENOEXEC; + } + + checksum_ref = hdr->prolog_checksum; + checksum = do_checksum32((u32 *)hdr, hdr->prolog_size); + checksum -= hdr->prolog_checksum; + if (checksum != checksum_ref) { + printf("Error: Bad Prolog checksum. 0x%x != 0x%x\n", + checksum, checksum_ref); + return -ENOEXEC; + } + + checksum_ref = hdr->boot_image_checksum; + checksum = do_checksum32((u32 *)((u8 *)hdr + hdr->prolog_size), hdr->boot_image_size); + if (checksum != checksum_ref) { + printf("Error: Bad Image checksum. 0x%x != 0x%x\n", + checksum, checksum_ref); + return -ENOEXEC; + } + + printf("Image checksum...OK!\n"); + + return 0; +} +#elif defined(CONFIG_ARMADA_3700) /* Armada 3700 */ +static int check_image_header(void) +{ + struct common_tim_data *hdr = (struct common_tim_data *)get_load_addr(); + int image_num; + u8 hash_160_output[SHA1_SUM_LEN]; + u8 hash_256_output[SHA256_SUM_LEN]; + u8 hash_512_output[SHA512_SUM_LEN]; + sha1_context hash1_text; + sha256_context hash256_text; + sha512_context hash512_text; + u8 *hash_output; + u32 hash_algorithm_id; + u32 image_size_to_hash; + u32 flash_entry_addr; + u32 *hash_value; + u32 internal_hash[HASH_SUM_LEN]; + const u8 *buff; + u32 num_of_image = hdr->num_images; + u32 version = hdr->version; + u32 trusted = hdr->trusted; + + /* bubt checksum validation only supports nontrusted images */ + if (trusted == 1) { + printf("bypass image validation, "); + printf("only untrusted image is supported now\n"); + return 0; + } + /* only supports image version 3.5 and 3.6 */ + if (version != IMAGE_VERSION_3_5_0 && version != IMAGE_VERSION_3_6_0) { + printf("Error: Unsupported Image version = 0x%08x\n", version); + return -ENOEXEC; + } + /* validate images hash value */ + for (image_num = 0; image_num < num_of_image; image_num++) { + struct mvebu_image_info *info = + (struct mvebu_image_info *)(get_load_addr() + + sizeof(struct common_tim_data) + + image_num * sizeof(struct mvebu_image_info)); + hash_algorithm_id = info->hash_algorithm_id; + image_size_to_hash = info->image_size_to_hash; + flash_entry_addr = info->flash_entry_addr; + hash_value = info->hash; + buff = (const u8 *)(get_load_addr() + flash_entry_addr); + + if (image_num == 0) { + /* + * The first image includes hash values in its content. + * For hash calculation, we need to save the original + * hash values to a local variable that will be + * copied back for comparsion and set all zeros to + * the orignal hash values for calculating new value. + * First image original format : + * x...x (datum1) x...x(orig. hash values) x...x(datum2) + * Replaced first image format : + * x...x (datum1) 0...0(hash values) x...x(datum2) + */ + memcpy(internal_hash, hash_value, + sizeof(internal_hash)); + memset(hash_value, 0, sizeof(internal_hash)); + } + if (image_size_to_hash == 0) { + printf("Warning: Image_%d hash checksum is disabled, ", + image_num); + printf("skip the image validation.\n"); + continue; + } + switch (hash_algorithm_id) { + case SHA1_SUM_LEN: + sha1_starts(&hash1_text); + sha1_update(&hash1_text, buff, image_size_to_hash); + sha1_finish(&hash1_text, hash_160_output); + hash_output = hash_160_output; + break; + case SHA256_SUM_LEN: + sha256_starts(&hash256_text); + sha256_update(&hash256_text, buff, image_size_to_hash); + sha256_finish(&hash256_text, hash_256_output); + hash_output = hash_256_output; + break; + case SHA512_SUM_LEN: + sha512_starts(&hash512_text); + sha512_update(&hash512_text, buff, image_size_to_hash); + sha512_finish(&hash512_text, hash_512_output); + hash_output = hash_512_output; + break; + default: + printf("Error: Unsupported hash_algorithm_id = %d\n", + hash_algorithm_id); + return -ENOEXEC; + } + if (image_num == 0) + memcpy(hash_value, internal_hash, + sizeof(internal_hash)); + if (memcmp(hash_value, hash_output, hash_algorithm_id) != 0) { + printf("Error: Image_%d checksum is not correct\n", + image_num); + return -ENOEXEC; + } + } + printf("Image checksum...OK!\n"); + + return 0; +} +#elif defined(CONFIG_ARMADA_32BIT) +static size_t a38x_header_size(const struct a38x_main_hdr_v1 *h) +{ + if (h->version == 1) + return (h->headersz_msb << 16) | le16_to_cpu(h->headersz_lsb); + + printf("Error: Invalid A38x image (header version 0x%x unknown)!\n", + h->version); + return 0; +} + +static uint8_t image_checksum8(const void *start, size_t len) +{ + u8 csum = 0; + const u8 *p = start; + + while (len) { + csum += *p; + ++p; + --len; + } + + return csum; +} + +static uint32_t image_checksum32(const void *start, size_t len) +{ + u32 csum = 0; + const u32 *p = start; + + while (len) { + csum += *p; + ++p; + len -= sizeof(u32); + } + + return csum; +} + +static int check_image_header(void) +{ + u8 checksum; + u32 checksum32, exp_checksum32; + u32 offset, size; + const struct a38x_main_hdr_v1 *hdr = + (struct a38x_main_hdr_v1 *)get_load_addr(); + const size_t hdr_size = a38x_header_size(hdr); + + if (!hdr_size) + return -ENOEXEC; + + checksum = image_checksum8(hdr, hdr_size); + checksum -= hdr->checksum; + if (checksum != hdr->checksum) { + printf("Error: Bad A38x image header checksum. 0x%x != 0x%x\n", + checksum, hdr->checksum); + return -ENOEXEC; + } + + offset = le32_to_cpu(hdr->srcaddr); + size = le32_to_cpu(hdr->blocksize); + + if (hdr->blockid == 0x78) { /* SATA id */ + struct blk_desc *blk_dev = IS_ENABLED(CONFIG_BLK) ? blk_get_devnum_by_uclass_id(UCLASS_SCSI, 0) : NULL; + unsigned long blksz = blk_dev ? blk_dev->blksz : 512; + offset *= blksz; + } + + if (offset % 4 != 0 || size < 4 || size % 4 != 0) { + printf("Error: Bad A38x image blocksize.\n"); + return -ENOEXEC; + } + + checksum32 = image_checksum32((u8 *)hdr + offset, size - 4); + exp_checksum32 = *(u32 *)((u8 *)hdr + offset + size - 4); + if (checksum32 != exp_checksum32) { + printf("Error: Bad A38x image data checksum. 0x%08x != 0x%08x\n", + checksum32, exp_checksum32); + return -ENOEXEC; + } + + printf("Image checksum...OK!\n"); + return 0; +} + +#if defined(CONFIG_ARMADA_38X) +static int a38x_image_is_secure(const struct a38x_main_hdr_v1 *hdr) +{ + const size_t hdr_size = a38x_header_size(hdr); + struct a38x_opt_hdr_v1 *ohdr; + u32 ohdr_size; + + if (hdr->version != 1) + return 0; + + if (!hdr->ext) + return 0; + + ohdr = (struct a38x_opt_hdr_v1 *)(hdr + 1); + do { + if (ohdr->headertype == A38X_OPT_HDR_V1_SECURE_TYPE) + return 1; + + ohdr_size = (ohdr->headersz_msb << 16) | le16_to_cpu(ohdr->headersz_lsb); + + if (!*((u8 *)ohdr + ohdr_size - 4)) + break; + + ohdr = (struct a38x_opt_hdr_v1 *)((u8 *)ohdr + ohdr_size); + if ((u8 *)ohdr >= (u8 *)hdr + hdr_size) + break; + } while (1); + + return 0; +} +#endif +#else /* Not ARMADA? */ +static int check_image_header(void) +{ + printf("bubt cmd does not support this SoC device or family!\n"); + return -ENOEXEC; +} +#endif + +#if defined(CONFIG_ARMADA_3700) || defined(CONFIG_ARMADA_38X) +static u64 fuse_read_u64(u32 bank) +{ + u32 val[2]; + int ret; + + ret = fuse_read(bank, 0, &val[0]); + if (ret < 0) + return -1; + + ret = fuse_read(bank, 1, &val[1]); + if (ret < 0) + return -1; + + return ((u64)val[1] << 32) | val[0]; +} +#endif + +#if defined(CONFIG_ARMADA_3700) +static inline u8 maj3(u8 val) +{ + /* return majority vote of 3 bits */ + return ((val & 0x7) == 3 || (val & 0x7) > 4) ? 1 : 0; +} +#endif + +static int bubt_check_boot_mode(const struct bubt_dev *dst) +{ +#if defined(CONFIG_ARMADA_3700) || defined(CONFIG_ARMADA_32BIT) + int mode; +#if defined(CONFIG_ARMADA_3700) || defined(CONFIG_ARMADA_38X) + int secure_mode; +#endif +#if defined(CONFIG_ARMADA_3700) + const struct tim_boot_flash_sign *boot_modes = tim_boot_flash_signs; + const struct common_tim_data *hdr = + (struct common_tim_data *)get_load_addr(); + u32 id = hdr->boot_flash_sign; + int is_secure = hdr->trusted != 0; + u64 otp_secure_bits = fuse_read_u64(1); + int otp_secure_boot = ((maj3(otp_secure_bits >> 0) << 0) | + (maj3(otp_secure_bits >> 4) << 1)) == 2; + unsigned int otp_boot_device = (maj3(otp_secure_bits >> 48) << 0) | + (maj3(otp_secure_bits >> 52) << 1) | + (maj3(otp_secure_bits >> 56) << 2) | + (maj3(otp_secure_bits >> 60) << 3); +#elif defined(CONFIG_ARMADA_32BIT) + const struct a38x_boot_mode *boot_modes = a38x_boot_modes; + const struct a38x_main_hdr_v1 *hdr = + (struct a38x_main_hdr_v1 *)get_load_addr(); + u32 id = hdr->blockid; +#if defined(CONFIG_ARMADA_38X) + int is_secure = a38x_image_is_secure(hdr); + u64 otp_secure_bits = fuse_read_u64(EFUSE_LINE_SECURE_BOOT); + int otp_secure_boot = otp_secure_bits & 0x1; + unsigned int otp_boot_device = (otp_secure_bits >> 8) & 0x7; +#endif +#endif + + for (mode = 0; boot_modes[mode].name; mode++) { + if (boot_modes[mode].id == id) + break; + } + + if (!boot_modes[mode].name) { + printf("Error: unknown boot device in image header: 0x%x\n", id); + return -ENOEXEC; + } + + if (strcmp(boot_modes[mode].name, dst->name) != 0) { + printf("Error: image meant to be booted from \"%s\", not \"%s\"!\n", + boot_modes[mode].name, dst->name); + return -ENOEXEC; + } + +#if defined(CONFIG_ARMADA_38X) || defined(CONFIG_ARMADA_3700) + if (otp_secure_bits == (u64)-1) { + printf("Error: cannot read OTP secure bits\n"); + return -ENOEXEC; + } else { + if (otp_secure_boot && !is_secure) { + printf("Error: secure boot is enabled in OTP but image does not have secure boot header!\n"); + return -ENOEXEC; + } else if (!otp_secure_boot && is_secure) { +#if defined(CONFIG_ARMADA_3700) + /* + * Armada 3700 BootROM rejects trusted image when secure boot is not enabled. + * Armada 385 BootROM accepts image with secure boot header also when secure boot is not enabled. + */ + printf("Error: secure boot is disabled in OTP but image has secure boot header!\n"); + return -ENOEXEC; +#endif + } else if (otp_boot_device && otp_boot_device != id) { + for (secure_mode = 0; boot_modes[secure_mode].name; secure_mode++) { + if (boot_modes[secure_mode].id == otp_boot_device) + break; + } + printf("Error: boot source is set to \"%s\" in OTP but image is for \"%s\"!\n", + boot_modes[secure_mode].name ?: "unknown", dst->name); + return -ENOEXEC; + } + } +#endif +#endif + return 0; +} + +static int bubt_verify(const struct bubt_dev *dst) +{ + int err; + + /* Check a correct image header exists */ + err = check_image_header(); + if (err) { + printf("Error: Image header verification failed\n"); + return err; + } + + err = bubt_check_boot_mode(dst); + if (err) { + printf("Error: Image boot mode verification failed\n"); + return err; + } + + return 0; +} + +static int bubt_read_file(struct bubt_dev *src) +{ + size_t image_size; + + if (!src->read) { + printf("Error: Read not supported on device \"%s\"\n", + src->name); + return 0; + } + + image_size = src->read(net_boot_file_name); + if (image_size <= 0) { + printf("Error: Failed to read file %s from %s\n", + net_boot_file_name, src->name); + return 0; + } + + return image_size; +} + +static int bubt_is_dev_active(struct bubt_dev *dev) +{ + if (!dev->active) { + printf("Device \"%s\" not supported by U-Boot image\n", + dev->name); + return 0; + } + + if (!dev->active()) { + printf("Device \"%s\" is inactive\n", dev->name); + return 0; + } + + return 1; +} + +static struct bubt_dev *find_bubt_dev(char *dev_name) +{ + int dev; + + for (dev = 0; dev < BUBT_MAX_DEV; dev++) { + if (strcmp(bubt_devs[dev].name, dev_name) == 0) + return &bubt_devs[dev]; + } + + return 0; +} + +#define DEFAULT_BUBT_SRC "tftp" + +#ifndef DEFAULT_BUBT_DST +#ifdef CONFIG_MVEBU_SPI_BOOT +#define DEFAULT_BUBT_DST "spi" +#elif defined(CONFIG_MVEBU_NAND_BOOT) +#define DEFAULT_BUBT_DST "nand" +#elif defined(CONFIG_MVEBU_MMC_BOOT) +#define DEFAULT_BUBT_DST "mmc" +#elif defined(CONFIG_MVEBU_SATA_BOOT) +#define DEFAULT_BUBT_DST "sata" +#else +#define DEFAULT_BUBT_DST "error" +#endif +#endif /* DEFAULT_BUBT_DST */ + +static int do_bubt_cmd(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct bubt_dev *src, *dst; + size_t image_size; + char src_dev_name[8]; + char dst_dev_name[8]; + char *name; + int err; + + if (argc < 2) + copy_filename(net_boot_file_name, + CONFIG_MVEBU_UBOOT_DFLT_NAME, + sizeof(net_boot_file_name)); + else + copy_filename(net_boot_file_name, argv[1], + sizeof(net_boot_file_name)); + + if (argc >= 3) { + strncpy(dst_dev_name, argv[2], 8); + } else { + name = DEFAULT_BUBT_DST; + strncpy(dst_dev_name, name, 8); + } + + if (argc >= 4) + strncpy(src_dev_name, argv[3], 8); + else + strncpy(src_dev_name, DEFAULT_BUBT_SRC, 8); + + /* Figure out the destination device */ + dst = find_bubt_dev(dst_dev_name); + if (!dst) { + printf("Error: Unknown destination \"%s\"\n", dst_dev_name); + return 1; + } + + if (!bubt_is_dev_active(dst)) + return 1; + + /* Figure out the source device */ + src = find_bubt_dev(src_dev_name); + if (!src) { + printf("Error: Unknown source \"%s\"\n", src_dev_name); + return 1; + } + + if (!bubt_is_dev_active(src)) + return -ENODEV; + + printf("Burning U-Boot image \"%s\" from \"%s\" to \"%s\"\n", + net_boot_file_name, src->name, dst->name); + + image_size = bubt_read_file(src); + if (!image_size) + return 1; + + err = bubt_verify(dst); + if (err) + return 1; + + err = bubt_write_file(dst, image_size); + if (err) + return 1; + + return 0; +} + +U_BOOT_CMD( + bubt, 4, 0, do_bubt_cmd, + "Burn a u-boot image to flash", + "[file-name] [destination [source]]\n" + "\t-file-name The image file name to burn. Default = " CONFIG_MVEBU_UBOOT_DFLT_NAME "\n" + "\t-destination Flash to burn to [spi, nand, mmc, sata]. Default = " DEFAULT_BUBT_DST "\n" + "\t-source The source to load image from [tftp, usb, mmc, sata]. Default = " DEFAULT_BUBT_SRC "\n" + "Examples:\n" + "\tbubt - Burn flash-image.bin from tftp to active boot device\n" + "\tbubt flash-image-new.bin nand - Burn flash-image-new.bin from tftp to NAND flash\n" + "\tbubt backup-flash-image.bin mmc usb - Burn backup-flash-image.bin from usb to MMC\n" + +); diff --git a/cmd/mvebu/comphy_rx_training.c b/cmd/mvebu/comphy_rx_training.c new file mode 100644 index 00000000000..5653877cd4a --- /dev/null +++ b/cmd/mvebu/comphy_rx_training.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017 Marvell International Ltd. + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include <command.h> +#include <console.h> +#include <dm.h> +#include <fdtdec.h> +#include <dm/device-internal.h> +#include <mvebu/comphy.h> + +int mvebu_comphy_rx_training_cmd(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + struct udevice *dev; + struct uclass *uc; + int ret, cp_index, comphy_index, i = 0; + + if (argc != 3) { + printf("missing arguments\n"); + return CMD_RET_USAGE; + } + + cp_index = hextoul(argv[1], NULL); + comphy_index = hextoul(argv[2], NULL); + + ret = uclass_get(UCLASS_MISC, &uc); + if (ret) { + printf("Couldn't find UCLASS_MISC\n"); + return ret; + } + + uclass_foreach_dev(dev, uc) { + if (!(memcmp(dev->name, "comphy", 5))) { + if (i == cp_index) { + comphy_rx_training(dev, comphy_index); + return 0; + } + + i++; + } + } + + printf("Coudn't find comphy %d\n", cp_index); + + return 0; +} + +U_BOOT_CMD( + mvebu_comphy_rx_training, 3, 0, mvebu_comphy_rx_training_cmd, + "mvebu_comphy_rx_training <cp id> <comphy id>\n", + "\n\tRun COMPHY RX training sequence, the user must state CP index (0/1) and comphy ID (0/5)" +); diff --git a/cmd/nand.c b/cmd/nand.c new file mode 100644 index 00000000000..b5678b0a008 --- /dev/null +++ b/cmd/nand.c @@ -0,0 +1,1253 @@ +/* + * Driver for NAND support, Rick Bronson + * borrowed heavily from: + * (c) 1999 Machine Vision Holdings, Inc. + * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org> + * + * Ported 'dynenv' to 'nand env.oob' command + * (C) 2010 Nanometrics, Inc. + * 'dynenv' -- Dynamic environment offset in NAND OOB + * (C) Copyright 2006-2007 OpenMoko, Inc. + * Added 16-bit nand support + * (C) 2004 Texas Instruments + * + * Copyright 2010, 2012 Freescale Semiconductor + * The portions of this file whose copyright is held by Freescale and which + * are not considered a derived work of GPL v2-only code may be distributed + * and/or modified under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The function nand_biterror() in this file is inspired from + * mtd-utils/nand-utils/nandflipbits.c which was released under GPLv2 + * only + */ + +#include <bootstage.h> +#include <image.h> +#include <asm/cache.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/rawnand.h> +#include <command.h> +#include <console.h> +#include <env.h> +#include <watchdog.h> +#include <malloc.h> +#include <mapmem.h> +#include <asm/byteorder.h> +#include <jffs2/jffs2.h> +#include <nand.h> +#include <display_options.h> + +#include "legacy-mtd-utils.h" + +#if defined(CONFIG_CMD_MTDPARTS) + +/* partition handling routines */ +int mtdparts_init(void); +int find_dev_and_part(const char *id, struct mtd_device **dev, + u8 *part_num, struct part_info **part); +#endif + +#define MAX_NUM_PAGES 64 + +static int nand_biterror(struct mtd_info *mtd, ulong off, int bit) +{ + int ret = 0; + int page = 0; + ulong block_off; + u_char *datbuf[MAX_NUM_PAGES]; /* Data and OOB */ + u_char data; + int pages_per_blk = mtd->erasesize / mtd->writesize; + struct erase_info einfo; + + if (pages_per_blk > MAX_NUM_PAGES) { + printf("Too many pages in one erase block\n"); + return 1; + } + + if (bit < 0 || bit > 7) { + printf("bit position 0 to 7 is allowed\n"); + return 1; + } + + /* Allocate memory */ + memset(datbuf, 0, sizeof(datbuf)); + for (page = 0; page < pages_per_blk ; page++) { + datbuf[page] = malloc(mtd->writesize + mtd->oobsize); + if (!datbuf[page]) { + printf("No memory for page buffer\n"); + ret = -ENOMEM; + goto free_memory; + } + } + + /* Align to erase block boundary */ + block_off = off & (~(mtd->erasesize - 1)); + + /* Read out memory as first step */ + for (page = 0; page < pages_per_blk ; page++) { + struct mtd_oob_ops ops; + loff_t addr = (loff_t)block_off; + + memset(&ops, 0, sizeof(ops)); + ops.datbuf = datbuf[page]; + ops.oobbuf = datbuf[page] + mtd->writesize; + ops.len = mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.mode = MTD_OPS_RAW; + ret = mtd_read_oob(mtd, addr, &ops); + if (ret < 0) { + printf("Error (%d) reading page %08lx\n", + ret, block_off); + ret = 1; + goto free_memory; + } + block_off += mtd->writesize; + } + + /* Erase the block */ + memset(&einfo, 0, sizeof(einfo)); + einfo.mtd = mtd; + /* Align to erase block boundary */ + einfo.addr = (loff_t)(off & (~(mtd->erasesize - 1))); + einfo.len = mtd->erasesize; + ret = mtd_erase(mtd, &einfo); + if (ret < 0) { + printf("Error (%d) nand_erase_nand page %08llx\n", + ret, einfo.addr); + ret = 1; + goto free_memory; + } + + /* Twist a bit in data part */ + block_off = off & (mtd->erasesize - 1); + data = datbuf[block_off / mtd->writesize][block_off % mtd->writesize]; + data ^= (1 << bit); + datbuf[block_off / mtd->writesize][block_off % mtd->writesize] = data; + + printf("Flip data at 0x%lx with xor 0x%02x (bit=%d) to value=0x%02x\n", + off, (1 << bit), bit, data); + + /* Write back twisted data and unmodified OOB */ + /* Align to erase block boundary */ + block_off = off & (~(mtd->erasesize - 1)); + for (page = 0; page < pages_per_blk; page++) { + struct mtd_oob_ops ops; + loff_t addr = (loff_t)block_off; + + memset(&ops, 0, sizeof(ops)); + ops.datbuf = datbuf[page]; + ops.oobbuf = datbuf[page] + mtd->writesize; + ops.len = mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.mode = MTD_OPS_RAW; + ret = mtd_write_oob(mtd, addr, &ops); + if (ret < 0) { + printf("Error (%d) write page %08lx\n", ret, block_off); + ret = 1; + goto free_memory; + } + block_off += mtd->writesize; + } + +free_memory: + for (page = 0; page < pages_per_blk ; page++) { + if (datbuf[page]) + free(datbuf[page]); + } + return ret; +} + +static int nand_dump(struct mtd_info *mtd, ulong off, int only_oob, + int ecc, int repeat) +{ + int i; + u_char *datbuf, *oobbuf, *p; + static loff_t last; + int ret = 0; + + if (repeat) + off = last + mtd->writesize; + + last = off; + + datbuf = memalign(ARCH_DMA_MINALIGN, mtd->writesize); + if (!datbuf) { + puts("No memory for page buffer\n"); + return 1; + } + + oobbuf = memalign(ARCH_DMA_MINALIGN, mtd->oobsize); + if (!oobbuf) { + puts("No memory for page buffer\n"); + ret = 1; + goto free_dat; + } + off &= ~(mtd->writesize - 1); + loff_t addr = (loff_t) off; + struct mtd_oob_ops ops; + memset(&ops, 0, sizeof(ops)); + ops.datbuf = datbuf; + ops.oobbuf = oobbuf; + ops.len = mtd->writesize; + ops.ooblen = mtd->oobsize; + if (ecc) + ops.mode = MTD_OPS_PLACE_OOB; + else + ops.mode = MTD_OPS_RAW; + i = mtd_read_oob(mtd, addr, &ops); + if (i < 0) { + printf("Error reading page at offset %08lx, %d %s\n", + off, i, i == -EUCLEAN ? "correctable" : + "uncorrectable, dumping raw data"); + ret = 1; + } + printf("\nPage at offset %08lx dump:\n", off); + + if (!only_oob) { + i = mtd->writesize; + p = datbuf; + print_buffer(off, p, 1, i, 16); + } + + puts("\nOOB:\n"); + i = mtd->oobsize; + p = oobbuf; + print_buffer(0, p, 1, i, 8); + + free(oobbuf); +free_dat: + free(datbuf); + + return ret; +} + +#ifdef CONFIG_CMD_NAND_WATCH +static int nand_watch_bf(struct mtd_info *mtd, ulong off, ulong size, bool quiet) +{ + unsigned int max_bf = 0, pages_wbf = 0; + unsigned int first_page, pages, i; + struct mtd_oob_ops ops = {}; + u_char *buf; + int ret; + + buf = memalign(ARCH_DMA_MINALIGN, mtd->writesize); + if (!buf) { + puts("No memory for page buffer\n"); + return 1; + } + + first_page = off / mtd->writesize; + pages = size / mtd->writesize; + + ops.datbuf = buf; + ops.len = mtd->writesize; + for (i = first_page; i < first_page + pages; i++) { + ulong addr = mtd->writesize * i; + ret = mtd_read_oob_bf(mtd, addr, &ops); + if (ret < 0) { + if (quiet) + continue; + + printf("Page %7d (0x%08lx) -> error %d\n", + i, addr, ret); + } else if (ret) { + max_bf = max(max_bf, (unsigned int)ret); + pages_wbf++; + if (quiet) + continue; + printf("Page %7d (0x%08lx) -> up to %2d bf/chunk\n", + i, addr, ret); + } + } + + printf("Maximum number of bitflips: %u\n", max_bf); + printf("Pages with bitflips: %u/%u\n", pages_wbf, pages); + + free(buf); + + return 0; +} +#endif + +/* ------------------------------------------------------------------------- */ + +static int set_dev(int dev) +{ + struct mtd_info *mtd = get_nand_dev_by_index(dev); + + if (!mtd) + return -ENODEV; + + if (nand_curr_device == dev) + return 0; + + printf("Device %d: %s", dev, mtd->name); + puts("... is now current device\n"); + nand_curr_device = dev; + +#ifdef CONFIG_SYS_NAND_SELECT_DEVICE + board_nand_select_device(mtd_to_nand(mtd), dev); +#endif + + return 0; +} + +#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK +static void print_status(ulong start, ulong end, ulong erasesize, int status) +{ + /* + * Micron NAND flash (e.g. MT29F4G08ABADAH4) BLOCK LOCK READ STATUS is + * not the same as others. Instead of bit 1 being lock, it is + * #lock_tight. To make the driver support either format, ignore bit 1 + * and use only bit 0 and bit 2. + */ + printf("%08lx - %08lx: %08lx blocks %s%s%s\n", + start, + end - 1, + (end - start) / erasesize, + ((status & NAND_LOCK_STATUS_TIGHT) ? "TIGHT " : ""), + (!(status & NAND_LOCK_STATUS_UNLOCK) ? "LOCK " : ""), + ((status & NAND_LOCK_STATUS_UNLOCK) ? "UNLOCK " : "")); +} + +static void do_nand_status(struct mtd_info *mtd) +{ + ulong block_start = 0; + ulong off; + int last_status = -1; + + struct nand_chip *nand_chip = mtd_to_nand(mtd); + /* check the WP bit */ + nand_chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); + printf("device is %swrite protected\n", + (nand_chip->read_byte(mtd) & 0x80 ? + "NOT " : "")); + + for (off = 0; off < mtd->size; off += mtd->erasesize) { + int s = nand_get_lock_status(mtd, off); + + /* print message only if status has changed */ + if (s != last_status && off != 0) { + print_status(block_start, off, mtd->erasesize, + last_status); + block_start = off; + } + last_status = s; + } + /* Print the last block info */ + print_status(block_start, off, mtd->erasesize, last_status); +} +#endif + +#ifdef CONFIG_ENV_OFFSET_OOB +unsigned long nand_env_oob_offset; + +int do_nand_env_oob(struct cmd_tbl *cmdtp, int argc, char *const argv[]) +{ + int ret; + uint32_t oob_buf[ENV_OFFSET_SIZE/sizeof(uint32_t)]; + struct mtd_info *mtd = get_nand_dev_by_index(0); + char *cmd = argv[1]; + + if (CONFIG_SYS_MAX_NAND_DEVICE == 0 || !mtd) { + puts("no devices available\n"); + return 1; + } + + set_dev(0); + + if (!strcmp(cmd, "get")) { + ret = get_nand_env_oob(mtd, &nand_env_oob_offset); + if (ret) + return 1; + + printf("0x%08lx\n", nand_env_oob_offset); + } else if (!strcmp(cmd, "set")) { + loff_t addr; + loff_t maxsize; + struct mtd_oob_ops ops; + int idx = 0; + + if (argc < 3) + goto usage; + + mtd = get_nand_dev_by_index(idx); + /* We don't care about size, or maxsize. */ + if (mtd_arg_off(argv[2], &idx, &addr, &maxsize, &maxsize, + MTD_DEV_TYPE_NAND, mtd->size)) { + puts("Offset or partition name expected\n"); + return 1; + } + if (set_dev(idx)) { + puts("Offset or partition name expected\n"); + return 1; + } + + if (idx != 0) { + puts("Partition not on first NAND device\n"); + return 1; + } + + if (mtd->oobavail < ENV_OFFSET_SIZE) { + printf("Insufficient available OOB bytes:\n" + "%d OOB bytes available but %d required for " + "env.oob support\n", + mtd->oobavail, ENV_OFFSET_SIZE); + return 1; + } + + if ((addr & (mtd->erasesize - 1)) != 0) { + printf("Environment offset must be block-aligned\n"); + return 1; + } + + ops.datbuf = NULL; + ops.mode = MTD_OPS_AUTO_OOB; + ops.ooboffs = 0; + ops.ooblen = ENV_OFFSET_SIZE; + ops.oobbuf = (void *) oob_buf; + + oob_buf[0] = ENV_OOB_MARKER; + oob_buf[1] = addr / mtd->erasesize; + + ret = mtd->write_oob(mtd, ENV_OFFSET_SIZE, &ops); + if (ret) { + printf("Error writing OOB block 0\n"); + return ret; + } + + ret = get_nand_env_oob(mtd, &nand_env_oob_offset); + if (ret) { + printf("Error reading env offset in OOB\n"); + return ret; + } + + if (addr != nand_env_oob_offset) { + printf("Verification of env offset in OOB failed: " + "0x%08llx expected but got 0x%08lx\n", + (unsigned long long)addr, nand_env_oob_offset); + return 1; + } + } else { + goto usage; + } + + return ret; + +usage: + return CMD_RET_USAGE; +} + +#endif + +static void nand_print_and_set_info(int idx) +{ + struct mtd_info *mtd; + struct nand_chip *chip; + + mtd = get_nand_dev_by_index(idx); + if (!mtd) + return; + + chip = mtd_to_nand(mtd); + printf("Device %d: ", idx); + if (chip->numchips > 1) + printf("%dx ", chip->numchips); + printf("%s, sector size %u KiB\n", + mtd->name, mtd->erasesize >> 10); + printf(" Page size %8d b\n", mtd->writesize); + printf(" OOB size %8d b\n", mtd->oobsize); + printf(" Erase size %8d b\n", mtd->erasesize); + printf(" ecc strength %8d bits\n", mtd->ecc_strength); + printf(" ecc step size %8d b\n", mtd->ecc_step_size); + printf(" subpagesize %8d b\n", chip->subpagesize); + printf(" options 0x%08x\n", chip->options); + printf(" bbt options 0x%08x\n", chip->bbt_options); + + /* Set geometry info */ + env_set_hex("nand_writesize", mtd->writesize); + env_set_hex("nand_oobsize", mtd->oobsize); + env_set_hex("nand_erasesize", mtd->erasesize); +} + +static int raw_access(struct mtd_info *mtd, void *buf, loff_t off, + ulong count, int read, int no_verify) +{ + int ret = 0; + + while (count--) { + /* Raw access */ + mtd_oob_ops_t ops = { + .datbuf = buf, + .oobbuf = buf + mtd->writesize, + .len = mtd->writesize, + .ooblen = mtd->oobsize, + .mode = MTD_OPS_RAW + }; + + if (read) { + ret = mtd_read_oob(mtd, off, &ops); + } else { + ret = mtd_write_oob(mtd, off, &ops); + if (!ret && !no_verify) + ret = nand_verify_page_oob(mtd, &ops, off); + } + + if (ret) { + printf("%s: error at offset %llx, ret %d\n", + __func__, (long long)off, ret); + break; + } + + buf += mtd->writesize + mtd->oobsize; + off += mtd->writesize; + } + + return ret; +} + +/* Adjust a chip/partition size down for bad blocks so we don't + * read/write past the end of a chip/partition by accident. + */ +static void adjust_size_for_badblocks(loff_t *size, loff_t offset, int dev) +{ + /* We grab the nand info object here fresh because this is usually + * called after arg_off_size() which can change the value of dev. + */ + struct mtd_info *mtd = get_nand_dev_by_index(dev); + loff_t maxoffset = offset + *size; + int badblocks = 0; + + /* count badblocks in NAND from offset to offset + size */ + for (; offset < maxoffset; offset += mtd->erasesize) { + if (nand_block_isbad(mtd, offset)) + badblocks++; + } + /* adjust size if any bad blocks found */ + if (badblocks) { + *size -= badblocks * mtd->erasesize; + printf("size adjusted to 0x%llx (%d bad blocks)\n", + (unsigned long long)*size, badblocks); + } +} + +static int do_nand(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i, ret = 0; + ulong addr; + loff_t off, size, maxsize; + char *cmd, *s; + struct mtd_info *mtd; +#ifdef CONFIG_SYS_NAND_QUIET + int quiet = CONFIG_SYS_NAND_QUIET; +#else + int quiet = 0; +#endif + const char *quiet_str = env_get("quiet"); + int dev = nand_curr_device; + int repeat = flag & CMD_FLAG_REPEAT; + + /* at least two arguments please */ + if (argc < 2) + goto usage; + + if (quiet_str) + quiet = simple_strtoul(quiet_str, NULL, 0) != 0; + + cmd = argv[1]; + + /* Only "dump" is repeatable. */ + if (repeat && strcmp(cmd, "dump")) + return 0; + + if (strcmp(cmd, "info") == 0) { + + putc('\n'); + for (i = 0; i < CONFIG_SYS_MAX_NAND_DEVICE; i++) + nand_print_and_set_info(i); + return 0; + } + + if (strcmp(cmd, "device") == 0) { + if (argc < 3) { + putc('\n'); + if (dev < 0 || dev >= CONFIG_SYS_MAX_NAND_DEVICE) + puts("no devices available\n"); + else + nand_print_and_set_info(dev); + return 0; + } + + dev = (int)dectoul(argv[2], NULL); + set_dev(dev); + + return 0; + } + +#ifdef CONFIG_ENV_OFFSET_OOB + /* this command operates only on the first nand device */ + if (strcmp(cmd, "env.oob") == 0) + return do_nand_env_oob(cmdtp, argc - 1, argv + 1); +#endif + + /* The following commands operate on the current device, unless + * overridden by a partition specifier. Note that if somehow the + * current device is invalid, it will have to be changed to a valid + * one before these commands can run, even if a partition specifier + * for another device is to be used. + */ + mtd = get_nand_dev_by_index(dev); + if (!mtd) { + puts("\nno devices available\n"); + return 1; + } + + if (strcmp(cmd, "bad") == 0) { + printf("\nDevice %d bad blocks:\n", dev); + for (off = 0; off < mtd->size; off += mtd->erasesize) { + ret = nand_block_isbad(mtd, off); + if (ret) + printf(" 0x%08llx%s\n", (unsigned long long)off, + ret == 2 ? "\t (bbt reserved)" : ""); + } + return 0; + } + + /* + * Syntax is: + * 0 1 2 3 4 + * nand erase [clean] [off size] + */ + if (strncmp(cmd, "erase", 5) == 0 || strncmp(cmd, "scrub", 5) == 0) { + nand_erase_options_t opts; + /* "clean" at index 2 means request to write cleanmarker */ + int clean = argc > 2 && !strcmp("clean", argv[2]); + int scrub_yes = argc > 2 && !strcmp("-y", argv[2]); + int o = (clean || scrub_yes) ? 3 : 2; + int scrub = !strncmp(cmd, "scrub", 5); + int spread = 0; + int args = 2; + const char *scrub_warn = + "Warning: " + "scrub option will erase all factory set bad blocks!\n" + " " + "There is no reliable way to recover them.\n" + " " + "Use this command only for testing purposes if you\n" + " " + "are sure of what you are doing!\n" + "\nReally scrub this NAND flash? <y/N>\n"; + + if (cmd[5] != 0) { + if (!strcmp(&cmd[5], ".spread")) { + spread = 1; + } else if (!strcmp(&cmd[5], ".part")) { + args = 1; + } else if (!strcmp(&cmd[5], ".chip")) { + args = 0; + } else { + goto usage; + } + } + + /* + * Don't allow missing arguments to cause full chip/partition + * erases -- easy to do accidentally, e.g. with a misspelled + * variable name. + */ + if (argc != o + args) + goto usage; + + printf("\nNAND %s: ", cmd); + /* skip first two or three arguments, look for offset and size */ + if (mtd_arg_off_size(argc - o, argv + o, &dev, &off, &size, + &maxsize, MTD_DEV_TYPE_NAND, + mtd->size) != 0) + return 1; + + if (set_dev(dev)) + return 1; + + mtd = get_nand_dev_by_index(dev); + + memset(&opts, 0, sizeof(opts)); + opts.offset = off; + opts.length = size; + opts.jffs2 = clean; + opts.quiet = quiet; + opts.spread = spread; + + if (scrub) { + if (scrub_yes) { + opts.scrub = 1; + } else { + puts(scrub_warn); + if (confirm_yesno()) { + opts.scrub = 1; + } else { + puts("scrub aborted\n"); + return 1; + } + } + } + ret = nand_erase_opts(mtd, &opts); + printf("%s\n", ret ? "ERROR" : "OK"); + + return ret == 0 ? 0 : 1; + } + + if (strncmp(cmd, "dump", 4) == 0) { + int only_oob, ecc; + + if (argc < 3) + goto usage; + + only_oob = !strcmp(&cmd[4], ".oob") || !strcmp(&cmd[4], ".ecc.oob") || + !strcmp(&cmd[4], ".oob.ecc"); + + ecc = !strcmp(&cmd[4], ".ecc") || !strcmp(&cmd[4], ".ecc.oob") || + !strcmp(&cmd[4], ".oob.ecc"); + + off = (int)hextoul(argv[2], NULL); + ret = nand_dump(mtd, off, only_oob, ecc, repeat); + + return ret == 0 ? 1 : 0; + } + + if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) { + size_t rwsize; + ulong pagecount = 1; + int read; + int raw = 0; + int no_verify = 0; + void *buf; + + if (argc < 4) + goto usage; + + addr = (ulong)hextoul(argv[2], NULL); + + read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */ + printf("\nNAND %s: ", read ? "read" : "write"); + + s = strchr(cmd, '.'); + + if (s && !strncmp(s, ".raw", 4)) { + raw = 1; + + if (!strcmp(s, ".raw.noverify")) + no_verify = 1; + + if (mtd_arg_off(argv[3], &dev, &off, &size, &maxsize, + MTD_DEV_TYPE_NAND, + mtd->size)) + return 1; + + if (set_dev(dev)) + return 1; + + mtd = get_nand_dev_by_index(dev); + + if (argc > 4 && !str2long(argv[4], &pagecount)) { + printf("'%s' is not a number\n", argv[4]); + return 1; + } + + if (pagecount * mtd->writesize > size) { + puts("Size exceeds partition or device limit\n"); + return -1; + } + + rwsize = pagecount * (mtd->writesize + mtd->oobsize); + } else { + if (mtd_arg_off_size(argc - 3, argv + 3, &dev, &off, + &size, &maxsize, + MTD_DEV_TYPE_NAND, + mtd->size) != 0) + return 1; + + if (set_dev(dev)) + return 1; + + /* size is unspecified */ + if (argc < 5) + adjust_size_for_badblocks(&size, off, dev); + rwsize = size; + } + + mtd = get_nand_dev_by_index(dev); + buf = map_sysmem(addr, maxsize); + + if (!s || !strcmp(s, ".jffs2") || + !strcmp(s, ".e") || !strcmp(s, ".i")) { + if (read) + ret = nand_read_skip_bad(mtd, off, &rwsize, + NULL, maxsize, buf); + else + ret = nand_write_skip_bad(mtd, off, &rwsize, + NULL, maxsize, buf, + WITH_WR_VERIFY); +#ifdef CONFIG_CMD_NAND_TRIMFFS + } else if (!strcmp(s, ".trimffs")) { + if (read) { + printf("Unknown nand command suffix '%s'\n", s); + unmap_sysmem(buf); + return 1; + } + ret = nand_write_skip_bad(mtd, off, &rwsize, NULL, + maxsize, buf, + WITH_DROP_FFS | WITH_WR_VERIFY); +#endif + } else if (!strcmp(s, ".oob")) { + /* out-of-band data */ + mtd_oob_ops_t ops = { + .oobbuf = buf, + .ooblen = rwsize, + .mode = MTD_OPS_RAW + }; + + if (read) + ret = mtd_read_oob(mtd, off, &ops); + else + ret = mtd_write_oob(mtd, off, &ops); + } else if (raw) { + ret = raw_access(mtd, buf, off, pagecount, read, + no_verify); + } else { + printf("Unknown nand command suffix '%s'.\n", s); + unmap_sysmem(buf); + return 1; + } + + unmap_sysmem(buf); + printf(" %zu bytes %s: %s\n", rwsize, + read ? "read" : "written", ret ? "ERROR" : "OK"); + + return ret == 0 ? 0 : 1; + } + +#ifdef CONFIG_CMD_NAND_WATCH + if (strncmp(cmd, "watch", 5) == 0) { + int args = 2; + + if (cmd[5]) { + if (!strncmp(&cmd[5], ".part", 5)) { + args = 1; + } else if (!strncmp(&cmd[5], ".chip", 5)) { + args = 0; + } else { + goto usage; + } + } + + if (cmd[10]) + if (!strncmp(&cmd[10], ".quiet", 6)) + quiet = true; + + if (argc != 2 + args) + goto usage; + + ret = mtd_arg_off_size(argc - 2, argv + 2, &dev, &off, &size, + &maxsize, MTD_DEV_TYPE_NAND, mtd->size); + if (ret) + return ret; + + /* size is unspecified */ + if (argc < 4) + adjust_size_for_badblocks(&size, off, dev); + + if ((off & (mtd->writesize - 1)) || + (size & (mtd->writesize - 1))) { + printf("Attempt to read non page-aligned data\n"); + return -EINVAL; + } + + ret = set_dev(dev); + if (ret) + return ret; + + mtd = get_nand_dev_by_index(dev); + + printf("\nNAND watch for bitflips in area 0x%llx-0x%llx:\n", + off, off + size); + + return nand_watch_bf(mtd, off, size, quiet); + } +#endif + +#ifdef CONFIG_CMD_NAND_TORTURE + if (strcmp(cmd, "torture") == 0) { + loff_t endoff; + unsigned int failed = 0, passed = 0; + + if (argc < 3) + goto usage; + + if (!str2off(argv[2], &off)) { + puts("Offset is not a valid number\n"); + return 1; + } + + size = mtd->erasesize; + if (argc > 3) { + if (!str2off(argv[3], &size)) { + puts("Size is not a valid number\n"); + return 1; + } + } + + endoff = off + size; + if (endoff > mtd->size) { + puts("Arguments beyond end of NAND\n"); + return 1; + } + + off = round_down(off, mtd->erasesize); + endoff = round_up(endoff, mtd->erasesize); + size = endoff - off; + printf("\nNAND torture: device %d offset 0x%llx size 0x%llx (block size 0x%x)\n", + dev, off, size, mtd->erasesize); + while (off < endoff) { + ret = nand_torture(mtd, off); + if (ret) { + failed++; + printf(" block at 0x%llx failed\n", off); + } else { + passed++; + } + off += mtd->erasesize; + } + printf(" Passed: %u, failed: %u\n", passed, failed); + return failed != 0; + } +#endif + + if (strcmp(cmd, "markbad") == 0) { + argc -= 2; + argv += 2; + + if (argc <= 0) + goto usage; + + while (argc > 0) { + addr = hextoul(*argv, NULL); + + if (mtd_block_markbad(mtd, addr)) { + printf("block 0x%08lx NOT marked " + "as bad! ERROR %d\n", + addr, ret); + ret = 1; + } else { + printf("block 0x%08lx successfully " + "marked as bad\n", + addr); + } + --argc; + ++argv; + } + return ret; + } + + if (strcmp(cmd, "biterr") == 0) { + int bit; + + if (argc != 4) + goto usage; + + off = (int)simple_strtoul(argv[2], NULL, 16); + bit = (int)simple_strtoul(argv[3], NULL, 10); + ret = nand_biterror(mtd, off, bit); + return ret; + } + +#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK + if (strcmp(cmd, "lock") == 0) { + int tight = 0; + int status = 0; + if (argc == 3) { + if (!strcmp("tight", argv[2])) + tight = 1; + if (!strcmp("status", argv[2])) + status = 1; + } + if (status) { + do_nand_status(mtd); + } else { + if (!nand_lock(mtd, tight)) { + puts("NAND flash successfully locked\n"); + } else { + puts("Error locking NAND flash\n"); + return 1; + } + } + return 0; + } + + if (strncmp(cmd, "unlock", 5) == 0) { + int allexcept = 0; + + s = strchr(cmd, '.'); + + if (s && !strcmp(s, ".allexcept")) + allexcept = 1; + + if (mtd_arg_off_size(argc - 2, argv + 2, &dev, &off, &size, + &maxsize, MTD_DEV_TYPE_NAND, + mtd->size) < 0) + return 1; + + if (set_dev(dev)) + return 1; + + mtd = get_nand_dev_by_index(dev); + + if (!nand_unlock(mtd, off, size, allexcept)) { + puts("NAND flash successfully unlocked\n"); + } else { + puts("Error unlocking NAND flash, " + "write and erase will probably fail\n"); + return 1; + } + return 0; + } +#endif + +usage: + return CMD_RET_USAGE; +} + +U_BOOT_LONGHELP(nand, + "info - show available NAND devices\n" + "nand device [dev] - show or set current device\n" + "nand read - addr off|partition size\n" + "nand write - addr off|partition size\n" + " read/write 'size' bytes starting at offset 'off'\n" + " to/from memory address 'addr', skipping bad blocks.\n" + "nand read.raw - addr off|partition [pages]\n" + "nand write.raw[.noverify] - addr off|partition [pages]\n" + " Use read.raw/write.raw to avoid ECC and access the flash as-is.\n" +#ifdef CONFIG_CMD_NAND_TRIMFFS + "nand write.trimffs - addr off|partition size\n" + " write 'size' bytes starting at offset 'off' from memory address\n" + " 'addr', skipping bad blocks and dropping any pages at the end\n" + " of eraseblocks that contain only 0xFF\n" +#endif + "nand erase[.spread] [clean] off size - erase 'size' bytes " + "from offset 'off'\n" + " With '.spread', erase enough for given file size, otherwise,\n" + " 'size' includes skipped bad blocks.\n" + "nand erase.part [clean] partition - erase entire mtd partition'\n" + "nand erase.chip [clean] - erase entire chip'\n" + "nand bad - show bad blocks\n" + "nand dump[.oob][.ecc] off - dump raw (default) or ecc corrected page at offset\n" +#ifdef CONFIG_CMD_NAND_WATCH + "nand watch <off> <size> - check an area for bitflips\n" + "nand watch.part <part> - check a partition for bitflips\n" + "nand watch.chip - check the whole device for bitflips\n" + "\t\t.quiet - Query only the summary, not the details\n" +#endif +#ifdef CONFIG_CMD_NAND_TORTURE + "nand torture off - torture one block at offset\n" + "nand torture off [size] - torture blocks from off to off+size\n" +#endif + "nand scrub [-y] off size | scrub.part partition | scrub.chip\n" + " really clean NAND erasing bad blocks (UNSAFE)\n" + "nand markbad off [...] - mark bad block(s) at offset (UNSAFE)\n" + "nand biterr off bit - make a bit error at offset and bit position (UNSAFE)" +#ifdef CONFIG_CMD_NAND_LOCK_UNLOCK + "\n" + "nand lock [tight] [status]\n" + " bring nand to lock state or display locked pages\n" + "nand unlock[.allexcept] [offset] [size] - unlock section" +#endif +#ifdef CONFIG_ENV_OFFSET_OOB + "\n" + "nand env.oob - environment offset in OOB of block 0 of" + " first device.\n" + "nand env.oob set off|partition - set enviromnent offset\n" + "nand env.oob get - get environment offset" +#endif + ); + +U_BOOT_CMD( + nand, CONFIG_SYS_MAXARGS, 1, do_nand, + "NAND sub-system", nand_help_text +); + +static int nand_load_image(struct cmd_tbl *cmdtp, struct mtd_info *mtd, + ulong offset, ulong addr, char *cmd) +{ + int r; + char *s; + size_t cnt; +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + struct legacy_img_hdr *hdr; +#endif +#if defined(CONFIG_FIT) + const void *fit_hdr = NULL; +#endif + + s = strchr(cmd, '.'); + if (s != NULL && + (strcmp(s, ".jffs2") && strcmp(s, ".e") && strcmp(s, ".i"))) { + printf("Unknown nand load suffix '%s'\n", s); + bootstage_error(BOOTSTAGE_ID_NAND_SUFFIX); + return 1; + } + + printf("\nLoading from %s, offset 0x%lx\n", mtd->name, offset); + + cnt = mtd->writesize; + r = nand_read_skip_bad(mtd, offset, &cnt, NULL, mtd->size, + (u_char *)addr); + if (r) { + puts("** Read error\n"); + bootstage_error(BOOTSTAGE_ID_NAND_HDR_READ); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_NAND_HDR_READ); + + switch (genimg_get_format ((void *)addr)) { +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + case IMAGE_FORMAT_LEGACY: + hdr = (struct legacy_img_hdr *)addr; + + bootstage_mark(BOOTSTAGE_ID_NAND_TYPE); + image_print_contents (hdr); + + cnt = image_get_image_size (hdr); + break; +#endif +#if defined(CONFIG_FIT) + case IMAGE_FORMAT_FIT: + fit_hdr = (const void *)addr; + puts ("Fit image detected...\n"); + + cnt = fit_get_size (fit_hdr); + break; +#endif + default: + bootstage_error(BOOTSTAGE_ID_NAND_TYPE); + puts ("** Unknown image type\n"); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_NAND_TYPE); + + r = nand_read_skip_bad(mtd, offset, &cnt, NULL, mtd->size, + (u_char *)addr); + if (r) { + puts("** Read error\n"); + bootstage_error(BOOTSTAGE_ID_NAND_READ); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_NAND_READ); + +#if defined(CONFIG_FIT) + /* This cannot be done earlier, we need complete FIT image in RAM first */ + if (genimg_get_format ((void *)addr) == IMAGE_FORMAT_FIT) { + if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) { + bootstage_error(BOOTSTAGE_ID_NAND_FIT_READ); + puts ("** Bad FIT image format\n"); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_NAND_FIT_READ_OK); + fit_print_contents (fit_hdr); + } +#endif + + /* Loading ok, update default load address */ + + image_load_addr = addr; + + return bootm_maybe_autostart(cmdtp, cmd); +} + +static int do_nandboot(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *boot_device = NULL; + int idx; + ulong addr, offset = 0; + struct mtd_info *mtd; +#if defined(CONFIG_CMD_MTDPARTS) + struct mtd_device *dev; + struct part_info *part; + u8 pnum; + + if (argc >= 2) { + char *p = (argc == 2) ? argv[1] : argv[2]; + if (!(str2long(p, &addr)) && (mtdparts_init() == 0) && + (find_dev_and_part(p, &dev, &pnum, &part) == 0)) { + if (dev->id->type != MTD_DEV_TYPE_NAND) { + puts("Not a NAND device\n"); + return 1; + } + if (argc > 3) + goto usage; + if (argc == 3) + addr = hextoul(argv[1], NULL); + else + addr = CONFIG_SYS_LOAD_ADDR; + + mtd = get_nand_dev_by_index(dev->id->num); + return nand_load_image(cmdtp, mtd, part->offset, + addr, argv[0]); + } + } +#endif + + bootstage_mark(BOOTSTAGE_ID_NAND_PART); + switch (argc) { + case 1: + addr = CONFIG_SYS_LOAD_ADDR; + boot_device = env_get("bootdevice"); + break; + case 2: + addr = hextoul(argv[1], NULL); + boot_device = env_get("bootdevice"); + break; + case 3: + addr = hextoul(argv[1], NULL); + boot_device = argv[2]; + break; + case 4: + addr = hextoul(argv[1], NULL); + boot_device = argv[2]; + offset = hextoul(argv[3], NULL); + break; + default: +#if defined(CONFIG_CMD_MTDPARTS) +usage: +#endif + bootstage_error(BOOTSTAGE_ID_NAND_SUFFIX); + return CMD_RET_USAGE; + } + bootstage_mark(BOOTSTAGE_ID_NAND_SUFFIX); + + if (!boot_device) { + puts("\n** No boot device **\n"); + bootstage_error(BOOTSTAGE_ID_NAND_BOOT_DEVICE); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_NAND_BOOT_DEVICE); + + idx = hextoul(boot_device, NULL); + + mtd = get_nand_dev_by_index(idx); + if (!mtd) { + printf("\n** Device %d not available\n", idx); + bootstage_error(BOOTSTAGE_ID_NAND_AVAILABLE); + return 1; + } + bootstage_mark(BOOTSTAGE_ID_NAND_AVAILABLE); + + return nand_load_image(cmdtp, mtd, offset, addr, argv[0]); +} + +U_BOOT_CMD(nboot, 4, 1, do_nandboot, + "boot from NAND device", + "[partition] | [[[loadAddr] dev] offset]" +); diff --git a/cmd/net-common.c b/cmd/net-common.c new file mode 100644 index 00000000000..1c6f11cd435 --- /dev/null +++ b/cmd/net-common.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <dm/device.h> +#include <dm/uclass.h> +#include <net.h> +#include <linux/compat.h> +#include <linux/ethtool.h> + +static int do_net_list(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + const struct udevice *current = eth_get_dev(); + unsigned char env_enetaddr[ARP_HLEN]; + const struct udevice *dev; + struct uclass *uc; + + uclass_id_foreach_dev(UCLASS_ETH, dev, uc) { + eth_env_get_enetaddr_by_index("eth", dev_seq(dev), env_enetaddr); + printf("eth%d : %s %pM %s\n", dev_seq(dev), dev->name, env_enetaddr, + current == dev ? "active" : ""); + } + return CMD_RET_SUCCESS; +} + +static int do_net_stats(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int nstats, err, i, off; + struct udevice *dev; + u64 *values; + u8 *strings; + + if (argc < 2) + return CMD_RET_USAGE; + + err = uclass_get_device_by_name(UCLASS_ETH, argv[1], &dev); + if (err) { + printf("Could not find device %s\n", argv[1]); + return CMD_RET_FAILURE; + } + + if (!eth_get_ops(dev)->get_sset_count || + !eth_get_ops(dev)->get_strings || + !eth_get_ops(dev)->get_stats) { + printf("Driver does not implement stats dump!\n"); + return CMD_RET_FAILURE; + } + + nstats = eth_get_ops(dev)->get_sset_count(dev); + strings = kcalloc(nstats, ETH_GSTRING_LEN, GFP_KERNEL); + if (!strings) + return CMD_RET_FAILURE; + + values = kcalloc(nstats, sizeof(u64), GFP_KERNEL); + if (!values) + goto err_free_strings; + + eth_get_ops(dev)->get_strings(dev, strings); + eth_get_ops(dev)->get_stats(dev, values); + + off = 0; + for (i = 0; i < nstats; i++) { + printf(" %s: %llu\n", &strings[off], values[i]); + off += ETH_GSTRING_LEN; + }; + + kfree(strings); + kfree(values); + + return CMD_RET_SUCCESS; + +err_free_strings: + kfree(strings); + + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_net[] = { + U_BOOT_CMD_MKENT(list, 1, 0, do_net_list, "", ""), + U_BOOT_CMD_MKENT(stats, 2, 0, do_net_stats, "", ""), +}; + +static int do_net(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *cp; + + cp = find_cmd_tbl(argv[1], cmd_net, ARRAY_SIZE(cmd_net)); + + /* Drop the net command */ + argc--; + argv++; + + if (!cp || argc > cp->maxargs) + return CMD_RET_USAGE; + if (flag == CMD_FLAG_REPEAT && !cmd_is_repeatable(cp)) + return CMD_RET_SUCCESS; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD(net, 3, 1, do_net, "NET sub-system", + "list - list available devices\n" + "stats <device> - dump statistics for specified device\n"); diff --git a/cmd/net.c b/cmd/net.c new file mode 100644 index 00000000000..886735ea14f --- /dev/null +++ b/cmd/net.c @@ -0,0 +1,677 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#define LOG_CATEGORY UCLASS_ETH + +/* + * Boot support + */ +#include <bootstage.h> +#include <command.h> +#include <dm.h> +#include <dm/devres.h> +#include <env.h> +#include <image.h> +#include <log.h> +#include <net.h> +#include <net6.h> +#include <net/udp.h> +#include <net/sntp.h> +#include <net/ncsi.h> + +static int netboot_common(enum proto_t, struct cmd_tbl *, int, char * const []); + +#ifdef CONFIG_CMD_BOOTP +static int do_bootp(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return netboot_common(BOOTP, cmdtp, argc, argv); +} + +U_BOOT_CMD( + bootp, 3, 1, do_bootp, + "boot image via network using BOOTP/TFTP protocol", + "[loadAddress] [[hostIPaddr:]bootfilename]" +); +#endif + +#ifdef CONFIG_CMD_TFTPBOOT +int do_tftpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int ret; + + bootstage_mark_name(BOOTSTAGE_KERNELREAD_START, "tftp_start"); + ret = netboot_common(TFTPGET, cmdtp, argc, argv); + bootstage_mark_name(BOOTSTAGE_KERNELREAD_STOP, "tftp_done"); + return ret; +} + +#if IS_ENABLED(CONFIG_IPV6) +U_BOOT_CMD( + tftpboot, 4, 1, do_tftpb, + "boot image via network using TFTP protocol\n" + "To use IPv6 add -ipv6 parameter or use IPv6 hostIPaddr framed " + "with [] brackets", + "[loadAddress] [[hostIPaddr:]bootfilename] [" USE_IP6_CMD_PARAM "]" +); +#else +U_BOOT_CMD( + tftpboot, 3, 1, do_tftpb, + "load file via network using TFTP protocol", + "[loadAddress] [[hostIPaddr:]bootfilename]" +); +#endif +#endif + +#ifdef CONFIG_CMD_TFTPPUT +static int do_tftpput(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return netboot_common(TFTPPUT, cmdtp, argc, argv); +} + +U_BOOT_CMD( + tftpput, 4, 1, do_tftpput, + "TFTP put command, for uploading files to a server", + "Address Size [[hostIPaddr:]filename]" +); +#endif + +#ifdef CONFIG_CMD_TFTPSRV +static int do_tftpsrv(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return netboot_common(TFTPSRV, cmdtp, argc, argv); +} + +U_BOOT_CMD( + tftpsrv, 2, 1, do_tftpsrv, + "act as a TFTP server and boot the first received file", + "[loadAddress]\n" + "Listen for an incoming TFTP transfer, receive a file and boot it.\n" + "The transfer is aborted if a transfer has not been started after\n" + "about 50 seconds or if Ctrl-C is pressed." +); +#endif + +#ifdef CONFIG_CMD_RARP +int do_rarpb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return netboot_common(RARP, cmdtp, argc, argv); +} + +U_BOOT_CMD( + rarpboot, 3, 1, do_rarpb, + "boot image via network using RARP/TFTP protocol", + "[loadAddress] [[hostIPaddr:]bootfilename]" +); +#endif + +#if defined(CONFIG_CMD_DHCP6) +static int do_dhcp6(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i; + int dhcp_argc; + char *dhcp_argv[] = {NULL, NULL, NULL, NULL}; + + /* Add -ipv6 flag for autoload */ + for (i = 0; i < argc; i++) + dhcp_argv[i] = argv[i]; + dhcp_argc = argc + 1; + dhcp_argv[dhcp_argc - 1] = USE_IP6_CMD_PARAM; + + return netboot_common(DHCP6, cmdtp, dhcp_argc, dhcp_argv); +} + +U_BOOT_CMD(dhcp6, 3, 1, do_dhcp6, + "boot image via network using DHCPv6/TFTP protocol.\n" + "Use IPv6 hostIPaddr framed with [] brackets", + "[loadAddress] [[hostIPaddr:]bootfilename]"); +#endif + +#if defined(CONFIG_CMD_DHCP) +static int do_dhcp(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return netboot_common(DHCP, cmdtp, argc, argv); +} + +U_BOOT_CMD( + dhcp, 3, 1, do_dhcp, + "boot image via network using DHCP/TFTP protocol", + "[loadAddress] [[hostIPaddr:]bootfilename]" +); + +int dhcp_run(ulong addr, const char *fname, bool autoload) +{ + char *dhcp_argv[] = {"dhcp", NULL, (char *)fname, NULL}; + struct cmd_tbl cmdtp = {}; /* dummy */ + char file_addr[17]; + int old_autoload; + int ret, result; + + log_debug("addr=%lx, fname=%s, autoload=%d\n", addr, fname, autoload); + old_autoload = env_get_yesno("autoload"); + ret = env_set("autoload", autoload ? "y" : "n"); + if (ret) + return log_msg_ret("en1", -EINVAL); + + if (autoload) { + sprintf(file_addr, "%lx", addr); + dhcp_argv[1] = file_addr; + } + + result = do_dhcp(&cmdtp, 0, !autoload ? 1 : fname ? 3 : 2, dhcp_argv); + + ret = env_set("autoload", old_autoload == -1 ? NULL : + old_autoload ? "y" : "n"); + if (ret) + return log_msg_ret("en2", -EINVAL); + + if (result) + return log_msg_ret("res", -ENOENT); + + return 0; +} +#endif + +#if defined(CONFIG_CMD_NFS) +static int do_nfs(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return netboot_common(NFS, cmdtp, argc, argv); +} + +U_BOOT_CMD( + nfs, 3, 1, do_nfs, + "boot image via network using NFS protocol", + "[loadAddress] [[hostIPaddr:]bootfilename]" +); +#endif + +#if defined(CONFIG_CMD_WGET) +static int do_wget(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + wget_info = &default_wget_info; + + return netboot_common(WGET, cmdtp, argc, argv); +} + +U_BOOT_CMD( + wget, 3, 1, do_wget, + "boot image via network using HTTP protocol", + "[loadAddress] [[hostIPaddr:]path and image name]" +); +#endif + +static void netboot_update_env(void) +{ + char tmp[46]; + + if (net_gateway.s_addr) { + ip_to_string(net_gateway, tmp); + env_set("gatewayip", tmp); + } + + if (net_netmask.s_addr) { + ip_to_string(net_netmask, tmp); + env_set("netmask", tmp); + } + +#ifdef CONFIG_CMD_BOOTP + if (net_hostname[0]) + env_set("hostname", net_hostname); +#endif + +#ifdef CONFIG_CMD_BOOTP + if (net_root_path[0]) + env_set("rootpath", net_root_path); +#endif + + if (net_ip.s_addr) { + ip_to_string(net_ip, tmp); + env_set("ipaddr", tmp); + } + /* + * Only attempt to change serverip if net/bootp.c:store_net_params() + * could have set it + */ + if (!IS_ENABLED(CONFIG_BOOTP_SERVERIP) && net_server_ip.s_addr) { + ip_to_string(net_server_ip, tmp); + env_set("serverip", tmp); + } + if (net_dns_server.s_addr) { + ip_to_string(net_dns_server, tmp); + env_set("dnsip", tmp); + } +#if defined(CONFIG_BOOTP_DNS2) + if (net_dns_server2.s_addr) { + ip_to_string(net_dns_server2, tmp); + env_set("dnsip2", tmp); + } +#endif +#ifdef CONFIG_CMD_BOOTP + if (net_nis_domain[0]) + env_set("domain", net_nis_domain); +#endif + +#if defined(CONFIG_CMD_SNTP) && defined(CONFIG_BOOTP_TIMEOFFSET) + if (net_ntp_time_offset) { + sprintf(tmp, "%d", net_ntp_time_offset); + env_set("timeoffset", tmp); + } +#endif +#if defined(CONFIG_CMD_SNTP) && defined(CONFIG_BOOTP_NTPSERVER) + if (net_ntp_server.s_addr) { + ip_to_string(net_ntp_server, tmp); + env_set("ntpserverip", tmp); + } +#endif + + if (IS_ENABLED(CONFIG_IPV6)) { + if (!ip6_is_unspecified_addr(&net_ip6) || + net_prefix_length != 0) { + if (net_prefix_length != 0) + snprintf(tmp, sizeof(tmp), "%pI6c/%d", &net_ip6, net_prefix_length); + else + snprintf(tmp, sizeof(tmp), "%pI6c", &net_ip6); + env_set("ip6addr", tmp); + } + + if (!ip6_is_unspecified_addr(&net_server_ip6)) { + snprintf(tmp, sizeof(tmp), "%pI6c", &net_server_ip6); + env_set("serverip6", tmp); + } + + if (!ip6_is_unspecified_addr(&net_gateway6)) { + snprintf(tmp, sizeof(tmp), "%pI6c", &net_gateway6); + env_set("gatewayip6", tmp); + } + } +} + +/** + * parse_addr_size() - parse address and size arguments for tftpput + * + * @argv: command line arguments + * Return: 0 on success + */ +static int parse_addr_size(char * const argv[]) +{ + if (strict_strtoul(argv[1], 16, &image_save_addr) < 0 || + strict_strtoul(argv[2], 16, &image_save_size) < 0) { + printf("Invalid address/size\n"); + return CMD_RET_USAGE; + } + return 0; +} + +/** + * parse_args() - parse command line arguments + * + * @proto: command prototype + * @argc: number of arguments + * @argv: command line arguments + * Return: 0 on success + */ +static int parse_args(enum proto_t proto, int argc, char *const argv[]) +{ + ulong addr; + char *end; + + switch (argc) { + case 1: + if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) + return 1; + + /* refresh bootfile name from env */ + copy_filename(net_boot_file_name, env_get("bootfile"), + sizeof(net_boot_file_name)); + break; + + case 2: + if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) + return 1; + /* + * Only one arg - accept two forms: + * Just load address, or just boot file name. The latter + * form must be written in a format which can not be + * mis-interpreted as a valid number. + */ + addr = hextoul(argv[1], &end); + if (end == (argv[1] + strlen(argv[1]))) { + image_load_addr = addr; + /* refresh bootfile name from env */ + copy_filename(net_boot_file_name, env_get("bootfile"), + sizeof(net_boot_file_name)); + } else { + net_boot_file_name_explicit = true; + copy_filename(net_boot_file_name, argv[1], + sizeof(net_boot_file_name)); + } + break; + + case 3: + if (IS_ENABLED(CONFIG_CMD_TFTPPUT) && proto == TFTPPUT) { + if (parse_addr_size(argv)) + return 1; + } else { + image_load_addr = hextoul(argv[1], NULL); + net_boot_file_name_explicit = true; + copy_filename(net_boot_file_name, argv[2], + sizeof(net_boot_file_name)); + } + break; + +#ifdef CONFIG_CMD_TFTPPUT + case 4: + if (parse_addr_size(argv)) + return 1; + net_boot_file_name_explicit = true; + copy_filename(net_boot_file_name, argv[3], + sizeof(net_boot_file_name)); + break; +#endif + default: + return 1; + } + return 0; +} + +static int netboot_common(enum proto_t proto, struct cmd_tbl *cmdtp, int argc, + char *const argv[]) +{ + char *s; + int rcode = 0; + int size; + + net_boot_file_name_explicit = false; + *net_boot_file_name = '\0'; + + /* pre-set image_load_addr */ + s = env_get("loadaddr"); + if (s != NULL) + image_load_addr = hextoul(s, NULL); + + if (IS_ENABLED(CONFIG_IPV6)) { + use_ip6 = false; + + /* IPv6 parameter has to be always *last* */ + if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM)) { + use_ip6 = true; + /* It is a hack not to break switch/case code */ + --argc; + } + } + + if (parse_args(proto, argc, argv)) { + bootstage_error(BOOTSTAGE_ID_NET_START); + return CMD_RET_USAGE; + } + + bootstage_mark(BOOTSTAGE_ID_NET_START); + + if (IS_ENABLED(CONFIG_IPV6) && !use_ip6) { + char *s, *e; + size_t len; + + s = strchr(net_boot_file_name, '['); + e = strchr(net_boot_file_name, ']'); + if (s && e) { + len = e - s; + if (!string_to_ip6(s + 1, len - 1, &net_server_ip6)) + use_ip6 = true; + } + } + + size = net_loop(proto); + if (size < 0) { + bootstage_error(BOOTSTAGE_ID_NET_NETLOOP_OK); + return CMD_RET_FAILURE; + } + bootstage_mark(BOOTSTAGE_ID_NET_NETLOOP_OK); + + /* net_loop ok, update environment */ + netboot_update_env(); + + /* done if no file was loaded (no errors though) */ + if (size == 0) { + bootstage_error(BOOTSTAGE_ID_NET_LOADED); + return CMD_RET_SUCCESS; + } + + bootstage_mark(BOOTSTAGE_ID_NET_LOADED); + + rcode = bootm_maybe_autostart(cmdtp, argv[0]); + + if (rcode == CMD_RET_SUCCESS) + bootstage_mark(BOOTSTAGE_ID_NET_DONE); + else + bootstage_error(BOOTSTAGE_ID_NET_DONE_ERR); + return rcode; +} + +#if defined(CONFIG_CMD_PING) +int do_ping(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + if (argc < 2) + return CMD_RET_USAGE; + + net_ping_ip = string_to_ip(argv[1]); + if (net_ping_ip.s_addr == 0) + return CMD_RET_USAGE; + + if (net_loop(PING) < 0) { + printf("ping failed; host %s is not alive\n", argv[1]); + return CMD_RET_FAILURE; + } + + printf("host %s is alive\n", argv[1]); + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + ping, 2, 1, do_ping, + "send ICMP ECHO_REQUEST to network host", + "pingAddress" +); +#endif + +#if IS_ENABLED(CONFIG_CMD_PING6) +int do_ping6(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + if (string_to_ip6(argv[1], strlen(argv[1]), &net_ping_ip6)) + return CMD_RET_USAGE; + + use_ip6 = true; + if (net_loop(PING6) < 0) { + use_ip6 = false; + printf("ping6 failed; host %pI6c is not alive\n", + &net_ping_ip6); + return 1; + } + + use_ip6 = false; + printf("host %pI6c is alive\n", &net_ping_ip6); + return 0; +} + +U_BOOT_CMD( + ping6, 2, 1, do_ping6, + "send ICMPv6 ECHO_REQUEST to network host", + "pingAddress" +); +#endif /* CONFIG_CMD_PING6 */ + +#if defined(CONFIG_CMD_CDP) + +static void cdp_update_env(void) +{ + char tmp[16]; + + if (cdp_appliance_vlan != htons(-1)) { + printf("CDP offered appliance VLAN %d\n", + ntohs(cdp_appliance_vlan)); + vlan_to_string(cdp_appliance_vlan, tmp); + env_set("vlan", tmp); + net_our_vlan = cdp_appliance_vlan; + } + + if (cdp_native_vlan != htons(-1)) { + printf("CDP offered native VLAN %d\n", ntohs(cdp_native_vlan)); + vlan_to_string(cdp_native_vlan, tmp); + env_set("nvlan", tmp); + net_native_vlan = cdp_native_vlan; + } +} + +int do_cdp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int r; + + r = net_loop(CDP); + if (r < 0) { + printf("cdp failed; perhaps not a CISCO switch?\n"); + return CMD_RET_FAILURE; + } + + cdp_update_env(); + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + cdp, 1, 1, do_cdp, + "Perform CDP network configuration", + "\n" +); +#endif + +#if defined(CONFIG_CMD_SNTP) +static struct udp_ops sntp_ops = { + .prereq = sntp_prereq, + .start = sntp_start, + .data = NULL, +}; + +int do_sntp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *toff; + + if (argc < 2) { + net_ntp_server = string_to_ip(env_get("ntpserverip")); + if (net_ntp_server.s_addr == 0) { + printf("ntpserverip not set\n"); + return CMD_RET_FAILURE; + } + } else { + net_ntp_server = string_to_ip(argv[1]); + if (net_ntp_server.s_addr == 0) { + printf("Bad NTP server IP address\n"); + return CMD_RET_FAILURE; + } + } + + toff = env_get("timeoffset"); + if (toff == NULL) + net_ntp_time_offset = 0; + else + net_ntp_time_offset = simple_strtol(toff, NULL, 10); + + if (udp_loop(&sntp_ops) < 0) { + printf("SNTP failed: host %pI4 not responding\n", + &net_ntp_server); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + sntp, 2, 1, do_sntp, + "synchronize RTC via network", + "[NTP server IP]\n" +); +#endif + +#if defined(CONFIG_CMD_DNS) +int do_dns(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + if (argc == 1) + return CMD_RET_USAGE; + + /* + * We should check for a valid hostname: + * - Each label must be between 1 and 63 characters long + * - the entire hostname has a maximum of 255 characters + * - only the ASCII letters 'a' through 'z' (case-insensitive), + * the digits '0' through '9', and the hyphen + * - cannot begin or end with a hyphen + * - no other symbols, punctuation characters, or blank spaces are + * permitted + * but hey - this is a minimalist implmentation, so only check length + * and let the name server deal with things. + */ + if (strlen(argv[1]) >= 255) { + printf("dns error: hostname too long\n"); + return CMD_RET_FAILURE; + } + + net_dns_resolve = argv[1]; + + if (argc == 3) + net_dns_env_var = argv[2]; + else + net_dns_env_var = NULL; + + if (net_loop(DNS) < 0) { + printf("dns lookup of %s failed, check setup\n", argv[1]); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + dns, 3, 1, do_dns, + "lookup the IP of a hostname", + "hostname [envvar]" +); + +#endif /* CONFIG_CMD_DNS */ + +#if defined(CONFIG_CMD_LINK_LOCAL) +static int do_link_local(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char tmp[22]; + + if (net_loop(LINKLOCAL) < 0) + return CMD_RET_FAILURE; + + net_gateway.s_addr = 0; + ip_to_string(net_gateway, tmp); + env_set("gatewayip", tmp); + + ip_to_string(net_netmask, tmp); + env_set("netmask", tmp); + + ip_to_string(net_ip, tmp); + env_set("ipaddr", tmp); + env_set("llipaddr", tmp); /* store this for next time */ + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + linklocal, 1, 1, do_link_local, + "acquire a network IP address using the link-local protocol", + "" +); + +#endif /* CONFIG_CMD_LINK_LOCAL */ diff --git a/cmd/nvedit.c b/cmd/nvedit.c new file mode 100644 index 00000000000..11c3cea882b --- /dev/null +++ b/cmd/nvedit.c @@ -0,0 +1,1296 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2013 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * (C) Copyright 2001 Sysgo Real-Time Solutions, GmbH <www.elinos.com> + * Andreas Heppel <aheppel@sysgo.de> + * + * Copyright 2011 Freescale Semiconductor, Inc. + */ + +/* + * Support for persistent environment data + * + * The "environment" is stored on external storage as a list of '\0' + * terminated "name=value" strings. The end of the list is marked by + * a double '\0'. The environment is preceded by a 32 bit CRC over + * the data part and, in case of redundant environment, a byte of + * flags. + * + * This linearized representation will also be used before + * relocation, i. e. as long as we don't have a full C runtime + * environment. After that, we use a hash table. + */ + +#include <config.h> +#include <cli.h> +#include <command.h> +#include <console.h> +#include <env.h> +#include <env_internal.h> +#include <log.h> +#include <search.h> +#include <errno.h> +#include <malloc.h> +#include <mapmem.h> +#include <asm/global_data.h> +#include <linux/bitops.h> +#include <linux/printk.h> +#include <u-boot/crc.h> +#include <linux/stddef.h> +#include <asm/byteorder.h> +#include <asm/io.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* + * Maximum expected input data size for import command + */ +#define MAX_ENV_SIZE (1 << 20) /* 1 MiB */ + +#ifndef CONFIG_XPL_BUILD +/* + * Command interface: print one or all environment variables + * + * Returns 0 in case of error, or length of printed string + */ +static int env_print(char *name, int flag) +{ + char *res = NULL; + ssize_t len; + + if (name) { /* print a single name */ + struct env_entry e, *ep; + + e.key = name; + e.data = NULL; + hsearch_r(e, ENV_FIND, &ep, &env_htab, flag); + if (ep == NULL) + return 0; + len = printf("%s=%s\n", ep->key, ep->data); + return len; + } + + /* print whole list */ + len = hexport_r(&env_htab, '\n', flag, &res, 0, 0, NULL); + + if (len > 0) { + puts(res); + free(res); + return len; + } + + /* should never happen */ + printf("## Error: cannot export environment\n"); + return 0; +} + +static int do_env_print(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i; + int rcode = 0; + int env_flag = H_HIDE_DOT; + +#if defined(CONFIG_CMD_NVEDIT_EFI) + if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'e') + return do_env_print_efi(cmdtp, flag, --argc, ++argv); +#endif + + if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'a') { + argc--; + argv++; + env_flag &= ~H_HIDE_DOT; + } + + if (argc == 1) { + /* print all env vars */ + rcode = env_print(NULL, env_flag); + if (!rcode) + return 1; + printf("\nEnvironment size: %d/%ld bytes\n", + rcode, (ulong)ENV_SIZE); + return 0; + } + + /* print selected env vars */ + env_flag &= ~H_HIDE_DOT; + for (i = 1; i < argc; ++i) { + int rc = env_print(argv[i], env_flag); + if (!rc) { + printf("## Error: \"%s\" not defined\n", argv[i]); + ++rcode; + } + } + + return rcode; +} + +#ifdef CONFIG_CMD_GREPENV +static int do_env_grep(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + char *res = NULL; + int len, grep_how, grep_what; + + if (argc < 2) + return CMD_RET_USAGE; + + grep_how = H_MATCH_SUBSTR; /* default: substring search */ + grep_what = H_MATCH_BOTH; /* default: grep names and values */ + + while (--argc > 0 && **++argv == '-') { + char *arg = *argv; + while (*++arg) { + switch (*arg) { +#ifdef CONFIG_REGEX + case 'e': /* use regex matching */ + grep_how = H_MATCH_REGEX; + break; +#endif + case 'n': /* grep for name */ + grep_what = H_MATCH_KEY; + break; + case 'v': /* grep for value */ + grep_what = H_MATCH_DATA; + break; + case 'b': /* grep for both */ + grep_what = H_MATCH_BOTH; + break; + case '-': + goto DONE; + default: + return CMD_RET_USAGE; + } + } + } + +DONE: + len = hexport_r(&env_htab, '\n', + flag | grep_what | grep_how, + &res, 0, argc, argv); + + if (len > 0) { + puts(res); + free(res); + } + + if (len < 2) + return 1; + + return 0; +} +#endif +#endif /* CONFIG_XPL_BUILD */ + +#ifndef CONFIG_XPL_BUILD +static int do_env_set(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc < 2) + return CMD_RET_USAGE; + + return env_do_env_set(flag, argc, argv, H_INTERACTIVE); +} + +/* + * Prompt for environment variable + */ +#if defined(CONFIG_CMD_ASKENV) +static int do_env_ask(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char message[CONFIG_SYS_CBSIZE]; + int i, len, pos, size; + char *local_args[4]; + char *endptr; + + local_args[0] = argv[0]; + local_args[1] = argv[1]; + local_args[2] = NULL; + local_args[3] = NULL; + + /* + * Check the syntax: + * + * env_ask envname [message1 ...] [size] + */ + if (argc == 1) + return CMD_RET_USAGE; + + /* + * We test the last argument if it can be converted + * into a decimal number. If yes, we assume it's + * the size. Otherwise we echo it as part of the + * message. + */ + i = dectoul(argv[argc - 1], &endptr); + if (*endptr != '\0') { /* no size */ + size = CONFIG_SYS_CBSIZE - 1; + } else { /* size given */ + size = i; + --argc; + } + + if (argc <= 2) { + sprintf(message, "Please enter '%s': ", argv[1]); + } else { + /* env_ask envname message1 ... messagen [size] */ + for (i = 2, pos = 0; i < argc && pos+1 < sizeof(message); i++) { + if (pos) + message[pos++] = ' '; + + strncpy(message + pos, argv[i], sizeof(message) - pos); + pos += strlen(argv[i]); + } + if (pos < sizeof(message) - 1) { + message[pos++] = ' '; + message[pos] = '\0'; + } else + message[CONFIG_SYS_CBSIZE - 1] = '\0'; + } + + if (size >= CONFIG_SYS_CBSIZE) + size = CONFIG_SYS_CBSIZE - 1; + + if (size <= 0) + return 1; + + /* prompt for input */ + len = cli_readline(message); + + if (size < len) + console_buffer[size] = '\0'; + + len = 2; + if (console_buffer[0] != '\0') { + local_args[2] = console_buffer; + len = 3; + } + + /* Continue calling setenv code */ + return env_do_env_set(flag, len, local_args, H_INTERACTIVE); +} +#endif + +#if defined(CONFIG_CMD_ENV_CALLBACK) +static int print_static_binding(const char *var_name, const char *callback_name, + void *priv) +{ + printf("\t%-20s %-20s\n", var_name, callback_name); + + return 0; +} + +static int print_active_callback(struct env_entry *entry) +{ + struct env_clbk_tbl *clbkp; + int i; + int num_callbacks; + + if (entry->callback == NULL) + return 0; + + /* look up the callback in the linker-list */ + num_callbacks = ll_entry_count(struct env_clbk_tbl, env_clbk); + for (i = 0, clbkp = ll_entry_start(struct env_clbk_tbl, env_clbk); + i < num_callbacks; + i++, clbkp++) { + if (entry->callback == clbkp->callback) + break; + } + + if (i == num_callbacks) + /* this should probably never happen, but just in case... */ + printf("\t%-20s %p\n", entry->key, entry->callback); + else + printf("\t%-20s %-20s\n", entry->key, clbkp->name); + + return 0; +} + +/* + * Print the callbacks available and what they are bound to + */ +static int do_env_callback(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct env_clbk_tbl *clbkp; + int i; + int num_callbacks; + + /* Print the available callbacks */ + puts("Available callbacks:\n"); + puts("\tCallback Name\n"); + puts("\t-------------\n"); + num_callbacks = ll_entry_count(struct env_clbk_tbl, env_clbk); + for (i = 0, clbkp = ll_entry_start(struct env_clbk_tbl, env_clbk); + i < num_callbacks; + i++, clbkp++) + printf("\t%s\n", clbkp->name); + puts("\n"); + + /* Print the static bindings that may exist */ + puts("Static callback bindings:\n"); + printf("\t%-20s %-20s\n", "Variable Name", "Callback Name"); + printf("\t%-20s %-20s\n", "-------------", "-------------"); + env_attr_walk(ENV_CALLBACK_LIST_STATIC, print_static_binding, NULL); + puts("\n"); + + /* walk through each variable and print the callback if it has one */ + puts("Active callback bindings:\n"); + printf("\t%-20s %-20s\n", "Variable Name", "Callback Name"); + printf("\t%-20s %-20s\n", "-------------", "-------------"); + hwalk_r(&env_htab, print_active_callback); + return 0; +} +#endif + +#if defined(CONFIG_CMD_ENV_FLAGS) +static int print_static_flags(const char *var_name, const char *flags, + void *priv) +{ + enum env_flags_vartype type = env_flags_parse_vartype(flags); + enum env_flags_varaccess access = env_flags_parse_varaccess(flags); + + printf("\t%-20s %-20s %-20s\n", var_name, + env_flags_get_vartype_name(type), + env_flags_get_varaccess_name(access)); + + return 0; +} + +static int print_active_flags(struct env_entry *entry) +{ + enum env_flags_vartype type; + enum env_flags_varaccess access; + + if (entry->flags == 0) + return 0; + + type = (enum env_flags_vartype) + (entry->flags & ENV_FLAGS_VARTYPE_BIN_MASK); + access = env_flags_parse_varaccess_from_binflags(entry->flags); + printf("\t%-20s %-20s %-20s\n", entry->key, + env_flags_get_vartype_name(type), + env_flags_get_varaccess_name(access)); + + return 0; +} + +/* + * Print the flags available and what variables have flags + */ +static int do_env_flags(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + /* Print the available variable types */ + printf("Available variable type flags (position %d):\n", + ENV_FLAGS_VARTYPE_LOC); + puts("\tFlag\tVariable Type Name\n"); + puts("\t----\t------------------\n"); + env_flags_print_vartypes(); + puts("\n"); + + /* Print the available variable access types */ + printf("Available variable access flags (position %d):\n", + ENV_FLAGS_VARACCESS_LOC); + puts("\tFlag\tVariable Access Name\n"); + puts("\t----\t--------------------\n"); + env_flags_print_varaccess(); + puts("\n"); + + /* Print the static flags that may exist */ + puts("Static flags:\n"); + printf("\t%-20s %-20s %-20s\n", "Variable Name", "Variable Type", + "Variable Access"); + printf("\t%-20s %-20s %-20s\n", "-------------", "-------------", + "---------------"); + env_attr_walk(ENV_FLAGS_LIST_STATIC, print_static_flags, NULL); + puts("\n"); + + /* walk through each variable and print the flags if non-default */ + puts("Active flags:\n"); + printf("\t%-20s %-20s %-20s\n", "Variable Name", "Variable Type", + "Variable Access"); + printf("\t%-20s %-20s %-20s\n", "-------------", "-------------", + "---------------"); + hwalk_r(&env_htab, print_active_flags); + return 0; +} +#endif + +/* + * Interactively edit an environment variable + */ +#if defined(CONFIG_CMD_EDITENV) +static int do_env_edit(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char buffer[CONFIG_SYS_CBSIZE]; + char *init_val; + + if (argc < 2) + return CMD_RET_USAGE; + + /* before import into hashtable */ + if (!(gd->flags & GD_FLG_ENV_READY)) + return 1; + + /* Set read buffer to initial value or empty sting */ + init_val = env_get(argv[1]); + if (init_val) + snprintf(buffer, CONFIG_SYS_CBSIZE, "%s", init_val); + else + buffer[0] = '\0'; + + if (cli_readline_into_buffer("edit: ", buffer, 0) < 0) + return 1; + + if (buffer[0] == '\0') { + const char * const _argv[3] = { "setenv", argv[1], NULL }; + + return env_do_env_set(0, 2, (char * const *)_argv, H_INTERACTIVE); + } else { + const char * const _argv[4] = { "setenv", argv[1], buffer, + NULL }; + + return env_do_env_set(0, 3, (char * const *)_argv, H_INTERACTIVE); + } +} +#endif /* CONFIG_CMD_EDITENV */ + +#if defined(CONFIG_CMD_SAVEENV) && !IS_ENABLED(CONFIG_ENV_IS_DEFAULT) +static int do_env_save(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return env_save() ? 1 : 0; +} + +U_BOOT_CMD( + saveenv, 1, 0, do_env_save, + "save environment variables to persistent storage", + "" +); + +#if defined(CONFIG_CMD_ERASEENV) +static int do_env_erase(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return env_erase() ? 1 : 0; +} + +U_BOOT_CMD( + eraseenv, 1, 0, do_env_erase, + "erase environment variables from persistent storage", + "" +); +#endif +#endif + +#if defined(CONFIG_CMD_NVEDIT_LOAD) +static int do_env_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return env_reload() ? 1 : 0; +} +#endif + +#if defined(CONFIG_CMD_NVEDIT_SELECT) +static int do_env_select(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return env_select(argv[1]) ? 1 : 0; +} +#endif + +#endif /* CONFIG_XPL_BUILD */ + +#ifndef CONFIG_XPL_BUILD +static int do_env_default(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int all = 0, env_flag = H_INTERACTIVE; + + debug("Initial value for argc=%d\n", argc); + while (--argc > 0 && **++argv == '-') { + char *arg = *argv; + + while (*++arg) { + switch (*arg) { + case 'a': /* default all */ + all = 1; + break; + case 'f': /* force */ + env_flag |= H_FORCE; + break; + case 'k': + env_flag |= H_NOCLEAR; + break; + default: + return cmd_usage(cmdtp); + } + } + } + debug("Final value for argc=%d\n", argc); + if (all && (argc == 0)) { + /* Reset the whole environment */ + env_set_default("## Resetting to default environment\n", + env_flag); + return 0; + } + if (!all && (argc > 0)) { + /* Reset individual variables */ + env_set_default_vars(argc, argv, env_flag); + return 0; + } + + return cmd_usage(cmdtp); +} + +static int do_env_delete(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int env_flag = H_INTERACTIVE; + int ret = 0; + + debug("Initial value for argc=%d\n", argc); + while (argc > 1 && **(argv + 1) == '-') { + char *arg = *++argv; + + --argc; + while (*++arg) { + switch (*arg) { + case 'f': /* force */ + env_flag |= H_FORCE; + break; + default: + return CMD_RET_USAGE; + } + } + } + debug("Final value for argc=%d\n", argc); + + env_inc_id(); + + while (--argc > 0) { + char *name = *++argv; + + if (hdelete_r(name, &env_htab, env_flag)) + ret = 1; + } + + return ret; +} + +#ifdef CONFIG_CMD_EXPORTENV +/* + * env export [-t | -b | -c] [-s size] addr [var ...] + * -t: export as text format; if size is given, data will be + * padded with '\0' bytes; if not, one terminating '\0' + * will be added (which is included in the "filesize" + * setting so you can for exmple copy this to flash and + * keep the termination). + * -b: export as binary format (name=value pairs separated by + * '\0', list end marked by double "\0\0") + * -c: export as checksum protected environment format as + * used for example by "saveenv" command + * -s size: + * size of output buffer + * addr: memory address where environment gets stored + * var... List of variable names that get included into the + * export. Without arguments, the whole environment gets + * exported. + * + * With "-c" and size is NOT given, then the export command will + * format the data as currently used for the persistent storage, + * i. e. it will use CONFIG_ENV_SECT_SIZE as output block size and + * prepend a valid CRC32 checksum and, in case of redundant + * environment, a "current" redundancy flag. If size is given, this + * value will be used instead of CONFIG_ENV_SECT_SIZE; again, CRC32 + * checksum and redundancy flag will be inserted. + * + * With "-b" and "-t", always only the real data (including a + * terminating '\0' byte) will be written; here the optional size + * argument will be used to make sure not to overflow the user + * provided buffer; the command will abort if the size is not + * sufficient. Any remaining space will be '\0' padded. + * + * On successful return, the variable "filesize" will be set. + * Note that filesize includes the trailing/terminating '\0' byte(s). + * + * Usage scenario: create a text snapshot/backup of the current settings: + * + * => env export -t 100000 + * => era ${backup_addr} +${filesize} + * => cp.b 100000 ${backup_addr} ${filesize} + * + * Re-import this snapshot, deleting all other settings: + * + * => env import -d -t ${backup_addr} + */ +static int do_env_export(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + char buf[32]; + ulong addr; + char *ptr, *cmd, *res; + size_t size = 0; + ssize_t len; + env_t *envp; + char sep = '\n'; + int chk = 0; + int fmt = 0; + + cmd = *argv; + + while (--argc > 0 && **++argv == '-') { + char *arg = *argv; + while (*++arg) { + switch (*arg) { + case 'b': /* raw binary format */ + if (fmt++) + goto sep_err; + sep = '\0'; + break; + case 'c': /* external checksum format */ + if (fmt++) + goto sep_err; + sep = '\0'; + chk = 1; + break; + case 's': /* size given */ + if (--argc <= 0) + return cmd_usage(cmdtp); + size = hextoul(*++argv, NULL); + goto NXTARG; + case 't': /* text format */ + if (fmt++) + goto sep_err; + sep = '\n'; + break; + default: + return CMD_RET_USAGE; + } + } +NXTARG: ; + } + + if (argc < 1) + return CMD_RET_USAGE; + + addr = hextoul(argv[0], NULL); + ptr = map_sysmem(addr, size); + + if (size) + memset(ptr, '\0', size); + + argc--; + argv++; + + if (sep) { /* export as text file */ + len = hexport_r(&env_htab, sep, + H_MATCH_KEY | H_MATCH_IDENT, + &ptr, size, argc, argv); + if (len < 0) { + pr_err("## Error: Cannot export environment: errno = %d\n", + errno); + return 1; + } + sprintf(buf, "%zX", (size_t)len); + env_set("filesize", buf); + + return 0; + } + + envp = (env_t *)ptr; + + if (chk) /* export as checksum protected block */ + res = (char *)envp->data; + else /* export as raw binary data */ + res = ptr; + + len = hexport_r(&env_htab, '\0', + H_MATCH_KEY | H_MATCH_IDENT, + &res, ENV_SIZE, argc, argv); + if (len < 0) { + pr_err("## Error: Cannot export environment: errno = %d\n", + errno); + return 1; + } + + if (chk) { + envp->crc = crc32(0, envp->data, + size ? size - offsetof(env_t, data) : ENV_SIZE); +#ifdef CONFIG_ENV_ADDR_REDUND + envp->flags = ENV_REDUND_ACTIVE; +#endif + } + env_set_hex("filesize", len + offsetof(env_t, data)); + + return 0; + +sep_err: + printf("## Error: %s: only one of \"-b\", \"-c\" or \"-t\" allowed\n", + cmd); + return 1; +} +#endif + +#ifdef CONFIG_CMD_IMPORTENV +/* + * env import [-d] [-t [-r] | -b | -c] addr [size] [var ...] + * -d: delete existing environment before importing if no var is + * passed; if vars are passed, if one var is in the current + * environment but not in the environment at addr, delete var from + * current environment; + * otherwise overwrite / append to existing definitions + * -t: assume text format; either "size" must be given or the + * text data must be '\0' terminated + * -r: handle CRLF like LF, that means exported variables with + * a content which ends with \r won't get imported. Used + * to import text files created with editors which are using CRLF + * for line endings. Only effective in addition to -t. + * -b: assume binary format ('\0' separated, "\0\0" terminated) + * -c: assume checksum protected environment format + * addr: memory address to read from + * size: length of input data; if missing, proper '\0' + * termination is mandatory + * if var is set and size should be missing (i.e. '\0' + * termination), set size to '-' + * var... List of the names of the only variables that get imported from + * the environment at address 'addr'. Without arguments, the whole + * environment gets imported. + */ +static int do_env_import(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + ulong addr; + char *cmd, *ptr; + char sep = '\n'; + int chk = 0; + int fmt = 0; + int del = 0; + int crlf_is_lf = 0; + int wl = 0; + size_t size; + + cmd = *argv; + + while (--argc > 0 && **++argv == '-') { + char *arg = *argv; + while (*++arg) { + switch (*arg) { + case 'b': /* raw binary format */ + if (fmt++) + goto sep_err; + sep = '\0'; + break; + case 'c': /* external checksum format */ + if (fmt++) + goto sep_err; + sep = '\0'; + chk = 1; + break; + case 't': /* text format */ + if (fmt++) + goto sep_err; + sep = '\n'; + break; + case 'r': /* handle CRLF like LF */ + crlf_is_lf = 1; + break; + case 'd': + del = 1; + break; + default: + return CMD_RET_USAGE; + } + } + } + + if (argc < 1) + return CMD_RET_USAGE; + + if (!fmt) + printf("## Warning: defaulting to text format\n"); + + if (sep != '\n' && crlf_is_lf ) + crlf_is_lf = 0; + + addr = hextoul(argv[0], NULL); + ptr = map_sysmem(addr, 0); + + if (argc >= 2 && strcmp(argv[1], "-")) { + size = hextoul(argv[1], NULL); + } else if (chk) { + puts("## Error: external checksum format must pass size\n"); + return CMD_RET_FAILURE; + } else { + char *s = ptr; + + size = 0; + + while (size < MAX_ENV_SIZE) { + if ((*s == sep) && (*(s+1) == '\0')) + break; + ++s; + ++size; + } + if (size == MAX_ENV_SIZE) { + printf("## Warning: Input data exceeds %d bytes" + " - truncated\n", MAX_ENV_SIZE); + } + size += 2; + printf("## Info: input data size = %zu = 0x%zX\n", size, size); + } + + if (argc > 2) + wl = 1; + + if (chk) { + uint32_t crc; + env_t *ep = (env_t *)ptr; + + if (size <= offsetof(env_t, data)) { + printf("## Error: Invalid size 0x%zX\n", size); + return 1; + } + + size -= offsetof(env_t, data); + memcpy(&crc, &ep->crc, sizeof(crc)); + + if (crc32(0, ep->data, size) != crc) { + puts("## Error: bad CRC, import failed\n"); + return 1; + } + ptr = (char *)ep->data; + } + + if (!himport_r(&env_htab, ptr, size, sep, del ? 0 : H_NOCLEAR, + crlf_is_lf, wl ? argc - 2 : 0, wl ? &argv[2] : NULL)) { + pr_err("## Error: Environment import failed: errno = %d\n", + errno); + return 1; + } + gd->flags |= GD_FLG_ENV_READY; + + return 0; + +sep_err: + printf("## %s: only one of \"-b\", \"-c\" or \"-t\" allowed\n", + cmd); + return 1; +} +#endif + +#if defined(CONFIG_CMD_NVEDIT_INDIRECT) +static int do_env_indirect(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + char *to = argv[1]; + char *from = argv[2]; + char *default_value = NULL; + int ret = 0; + char *val; + + if (argc < 3 || argc > 4) { + return CMD_RET_USAGE; + } + + if (argc == 4) { + default_value = argv[3]; + } + + val = env_get(from) ?: default_value; + if (!val) { + printf("## env indirect: Environment variable for <from> (%s) does not exist.\n", from); + + return CMD_RET_FAILURE; + } + + ret = env_set(to, val); + + if (ret == 0) { + return CMD_RET_SUCCESS; + } + else { + return CMD_RET_FAILURE; + } +} +#endif + +#if defined(CONFIG_CMD_NVEDIT_INFO) +/* + * print_env_info - print environment information + */ +static int print_env_info(void) +{ + const char *value; + + /* print environment validity value */ + switch (gd->env_valid) { + case ENV_INVALID: + value = "invalid"; + break; + case ENV_VALID: + value = "valid"; + break; + case ENV_REDUND: + value = "redundant"; + break; + default: + value = "unknown"; + break; + } + printf("env_valid = %s\n", value); + + /* print environment ready flag */ + value = gd->flags & GD_FLG_ENV_READY ? "true" : "false"; + printf("env_ready = %s\n", value); + + /* print environment using default flag */ + value = gd->flags & GD_FLG_ENV_DEFAULT ? "true" : "false"; + printf("env_use_default = %s\n", value); + + return CMD_RET_SUCCESS; +} + +#define ENV_INFO_IS_DEFAULT BIT(0) /* default environment bit mask */ +#define ENV_INFO_IS_PERSISTED BIT(1) /* environment persistence bit mask */ + +/* + * env info - display environment information + * env info [-d] - evaluate whether default environment is used + * env info [-p] - evaluate whether environment can be persisted + * Add [-q] - quiet mode, use only for command result, for test by example: + * test env info -p -d -q + */ +static int do_env_info(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + int eval_flags = 0; + int eval_results = 0; + bool quiet = false; +#if defined(CONFIG_CMD_SAVEENV) && !IS_ENABLED(CONFIG_ENV_IS_DEFAULT) + enum env_location loc; +#endif + + /* display environment information */ + if (argc <= 1) + return print_env_info(); + + /* process options */ + while (--argc > 0 && **++argv == '-') { + char *arg = *argv; + + while (*++arg) { + switch (*arg) { + case 'd': + eval_flags |= ENV_INFO_IS_DEFAULT; + break; + case 'p': + eval_flags |= ENV_INFO_IS_PERSISTED; + break; + case 'q': + quiet = true; + break; + default: + return CMD_RET_USAGE; + } + } + } + + /* evaluate whether default environment is used */ + if (eval_flags & ENV_INFO_IS_DEFAULT) { + if (gd->flags & GD_FLG_ENV_DEFAULT) { + if (!quiet) + printf("Default environment is used\n"); + eval_results |= ENV_INFO_IS_DEFAULT; + } else { + if (!quiet) + printf("Environment was loaded from persistent storage\n"); + } + } + + /* evaluate whether environment can be persisted */ + if (eval_flags & ENV_INFO_IS_PERSISTED) { +#if defined(CONFIG_CMD_SAVEENV) && !IS_ENABLED(CONFIG_ENV_IS_DEFAULT) + loc = env_get_location(ENVOP_SAVE, gd->env_load_prio); + if (ENVL_NOWHERE != loc && ENVL_UNKNOWN != loc) { + if (!quiet) + printf("Environment can be persisted\n"); + eval_results |= ENV_INFO_IS_PERSISTED; + } else { + if (!quiet) + printf("Environment cannot be persisted\n"); + } +#else + if (!quiet) + printf("Environment cannot be persisted\n"); +#endif + } + + /* The result of evaluations is combined with AND */ + if (eval_flags != eval_results) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} +#endif + +#if defined(CONFIG_CMD_ENV_EXISTS) +static int do_env_exists(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct env_entry e, *ep; + + if (argc < 2) + return CMD_RET_USAGE; + + e.key = argv[1]; + e.data = NULL; + hsearch_r(e, ENV_FIND, &ep, &env_htab, 0); + + return (ep == NULL) ? 1 : 0; +} +#endif + +/* + * New command line interface: "env" command with subcommands + */ +static struct cmd_tbl cmd_env_sub[] = { +#if defined(CONFIG_CMD_ASKENV) + U_BOOT_CMD_MKENT(ask, CONFIG_SYS_MAXARGS, 1, do_env_ask, "", ""), +#endif + U_BOOT_CMD_MKENT(default, 1, 0, do_env_default, "", ""), + U_BOOT_CMD_MKENT(delete, CONFIG_SYS_MAXARGS, 0, do_env_delete, "", ""), +#if defined(CONFIG_CMD_EDITENV) + U_BOOT_CMD_MKENT(edit, 2, 0, do_env_edit, "", ""), +#endif +#if defined(CONFIG_CMD_ENV_CALLBACK) + U_BOOT_CMD_MKENT(callbacks, 1, 0, do_env_callback, "", ""), +#endif +#if defined(CONFIG_CMD_ENV_FLAGS) + U_BOOT_CMD_MKENT(flags, 1, 0, do_env_flags, "", ""), +#endif +#if defined(CONFIG_CMD_EXPORTENV) + U_BOOT_CMD_MKENT(export, 4, 0, do_env_export, "", ""), +#endif +#if defined(CONFIG_CMD_GREPENV) + U_BOOT_CMD_MKENT(grep, CONFIG_SYS_MAXARGS, 1, do_env_grep, "", ""), +#endif +#if defined(CONFIG_CMD_IMPORTENV) + U_BOOT_CMD_MKENT(import, 5, 0, do_env_import, "", ""), +#endif +#if defined(CONFIG_CMD_NVEDIT_INDIRECT) + U_BOOT_CMD_MKENT(indirect, 3, 0, do_env_indirect, "", ""), +#endif +#if defined(CONFIG_CMD_NVEDIT_INFO) + U_BOOT_CMD_MKENT(info, 3, 0, do_env_info, "", ""), +#endif +#if defined(CONFIG_CMD_NVEDIT_LOAD) + U_BOOT_CMD_MKENT(load, 1, 0, do_env_load, "", ""), +#endif + U_BOOT_CMD_MKENT(print, CONFIG_SYS_MAXARGS, 1, do_env_print, "", ""), +#if defined(CONFIG_CMD_RUN) + U_BOOT_CMD_MKENT(run, CONFIG_SYS_MAXARGS, 1, do_run, "", ""), +#endif +#if defined(CONFIG_CMD_SAVEENV) && !IS_ENABLED(CONFIG_ENV_IS_DEFAULT) + U_BOOT_CMD_MKENT(save, 1, 0, do_env_save, "", ""), +#if defined(CONFIG_CMD_ERASEENV) + U_BOOT_CMD_MKENT(erase, 1, 0, do_env_erase, "", ""), +#endif +#endif +#if defined(CONFIG_CMD_NVEDIT_SELECT) + U_BOOT_CMD_MKENT(select, 2, 0, do_env_select, "", ""), +#endif + U_BOOT_CMD_MKENT(set, CONFIG_SYS_MAXARGS, 0, do_env_set, "", ""), +#if defined(CONFIG_CMD_ENV_EXISTS) + U_BOOT_CMD_MKENT(exists, 2, 0, do_env_exists, "", ""), +#endif +}; + +static int do_env(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *cp; + + if (argc < 2) + return CMD_RET_USAGE; + + /* drop initial "env" arg */ + argc--; + argv++; + + cp = find_cmd_tbl(argv[0], cmd_env_sub, ARRAY_SIZE(cmd_env_sub)); + + if (cp) + return cp->cmd(cmdtp, flag, argc, argv); + + return CMD_RET_USAGE; +} + +U_BOOT_LONGHELP(env, +#if defined(CONFIG_CMD_ASKENV) + "ask name [message] [size] - ask for environment variable\nenv " +#endif +#if defined(CONFIG_CMD_ENV_CALLBACK) + "callbacks - print callbacks and their associated variables\nenv " +#endif + "default [-k] [-f] -a - [forcibly] reset default environment\n" + "env default [-k] [-f] var [...] - [forcibly] reset variable(s) to their default values\n" + " \"-k\": keep variables not defined in default environment\n" + "env delete [-f] var [...] - [forcibly] delete variable(s)\n" +#if defined(CONFIG_CMD_EDITENV) + "env edit name - edit environment variable\n" +#endif +#if defined(CONFIG_CMD_ENV_EXISTS) + "env exists name - tests for existence of variable\n" +#endif +#if defined(CONFIG_CMD_EXPORTENV) + "env export [-t | -b | -c] [-s size] addr [var ...] - export environment\n" +#endif +#if defined(CONFIG_CMD_ENV_FLAGS) + "env flags - print variables that have non-default flags\n" +#endif +#if defined(CONFIG_CMD_GREPENV) +#ifdef CONFIG_REGEX + "env grep [-e] [-n | -v | -b] string [...] - search environment\n" +#else + "env grep [-n | -v | -b] string [...] - search environment\n" +#endif +#endif +#if defined(CONFIG_CMD_IMPORTENV) + "env import [-d] [-t [-r] | -b | -c] addr [size] [var ...] - import environment\n" +#endif +#if defined(CONFIG_CMD_NVEDIT_INDIRECT) + "env indirect <to> <from> [default] - sets <to> to the value of <from>, using [default] when unset\n" +#endif +#if defined(CONFIG_CMD_NVEDIT_INFO) + "env info - display environment information\n" + "env info [-d] [-p] [-q] - evaluate environment information\n" + " \"-d\": default environment is used\n" + " \"-p\": environment can be persisted\n" + " \"-q\": quiet output\n" +#endif + "env print [-a | name ...] - print environment\n" +#if defined(CONFIG_CMD_NVEDIT_EFI) + "env print -e [-guid guid] [-n] [name ...] - print UEFI environment\n" +#endif +#if defined(CONFIG_CMD_RUN) + "env run var [...] - run commands in an environment variable\n" +#endif +#if defined(CONFIG_CMD_SAVEENV) && !IS_ENABLED(CONFIG_ENV_IS_DEFAULT) + "env save - save environment\n" +#if defined(CONFIG_CMD_ERASEENV) + "env erase - erase environment\n" +#endif +#endif +#if defined(CONFIG_CMD_NVEDIT_LOAD) + "env load - load environment\n" +#endif +#if defined(CONFIG_CMD_NVEDIT_SELECT) + "env select [target] - select environment target\n" +#endif +#if defined(CONFIG_CMD_NVEDIT_EFI) + "env set -e [-nv][-bs][-rt][-at][-a][-i addr:size][-v] name [arg ...]\n" + " - set UEFI variable; unset if '-i' or 'arg' not specified\n" +#endif + "env set [-f] name [arg ...]\n"); + +U_BOOT_CMD( + env, CONFIG_SYS_MAXARGS, 1, do_env, + "environment handling commands", env_help_text +); + +/* + * Old command line interface, kept for compatibility + */ + +#if defined(CONFIG_CMD_EDITENV) +U_BOOT_CMD_COMPLETE( + editenv, 2, 0, do_env_edit, + "edit environment variable", + "name\n" + " - edit environment variable 'name'", + var_complete +); +#endif + +U_BOOT_CMD_COMPLETE( + printenv, CONFIG_SYS_MAXARGS, 1, do_env_print, + "print environment variables", + "[-a]\n - print [all] values of all environment variables\n" +#if defined(CONFIG_CMD_NVEDIT_EFI) + "printenv -e [-guid guid][-n] [name ...]\n" + " - print UEFI variable 'name' or all the variables\n" + " \"-guid\": GUID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n" + " \"-n\": suppress dumping variable's value\n" +#endif + "printenv name ...\n" + " - print value of environment variable 'name'", + var_complete +); + +#ifdef CONFIG_CMD_GREPENV +U_BOOT_CMD_COMPLETE( + grepenv, CONFIG_SYS_MAXARGS, 0, do_env_grep, + "search environment variables", +#ifdef CONFIG_REGEX + "[-e] [-n | -v | -b] string ...\n" +#else + "[-n | -v | -b] string ...\n" +#endif + " - list environment name=value pairs matching 'string'\n" +#ifdef CONFIG_REGEX + " \"-e\": enable regular expressions;\n" +#endif + " \"-n\": search variable names; \"-v\": search values;\n" + " \"-b\": search both names and values (default)", + var_complete +); +#endif + +U_BOOT_CMD_COMPLETE( + setenv, CONFIG_SYS_MAXARGS, 0, do_env_set, + "set environment variables", +#if defined(CONFIG_CMD_NVEDIT_EFI) + "-e [-guid guid][-nv][-bs][-rt][-at][-a][-v]\n" + " [-i addr:size name], or [name [value ...]]\n" + " - set UEFI variable 'name' to 'value' ...'\n" + " \"-guid\": GUID xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n" + " \"-nv\": set non-volatile attribute\n" + " \"-bs\": set boot-service attribute\n" + " \"-rt\": set runtime attribute\n" + " \"-at\": set time-based authentication attribute\n" + " \"-a\": append-write\n" + " \"-i addr:size\": use <addr,size> as variable's value\n" + " \"-v\": verbose message\n" + " - delete UEFI variable 'name' if 'value' not specified\n" +#endif + "setenv [-f] name value ...\n" + " - [forcibly] set environment variable 'name' to 'value ...'\n" + "setenv [-f] name\n" + " - [forcibly] delete environment variable 'name'", + var_complete +); + +#if defined(CONFIG_CMD_ASKENV) + +U_BOOT_CMD( + askenv, CONFIG_SYS_MAXARGS, 1, do_env_ask, + "get environment variables from stdin", + "name [message] [size]\n" + " - get environment variable 'name' from stdin (max 'size' chars)" +); +#endif + +#if defined(CONFIG_CMD_RUN) +U_BOOT_CMD_COMPLETE( + run, CONFIG_SYS_MAXARGS, 1, do_run, + "run commands in an environment variable", + "var [...]\n" + " - run the commands in the environment variable(s) 'var'", + var_complete +); +#endif +#endif /* CONFIG_XPL_BUILD */ diff --git a/cmd/nvedit_efi.c b/cmd/nvedit_efi.c new file mode 100644 index 00000000000..351ae47e870 --- /dev/null +++ b/cmd/nvedit_efi.c @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Integrate UEFI variables to u-boot env interface + * + * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited + */ + +#include <charset.h> +#include <command.h> +#include <efi_loader.h> +#include <efi_variable.h> +#include <env.h> +#include <exports.h> +#include <hexdump.h> +#include <malloc.h> +#include <mapmem.h> +#include <rtc.h> +#include <u-boot/uuid.h> +#include <linux/kernel.h> + +/* + * From efi_variable.c, + * + * Mapping between UEFI variables and u-boot variables: + * + * efi_$guid_$varname = {attributes}(type)value + */ + +static const struct { + u32 mask; + char *text; +} efi_var_attrs[] = { + {EFI_VARIABLE_NON_VOLATILE, "NV"}, + {EFI_VARIABLE_BOOTSERVICE_ACCESS, "BS"}, + {EFI_VARIABLE_RUNTIME_ACCESS, "RT"}, + {EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS, "AW"}, + {EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, "AT"}, + {EFI_VARIABLE_READ_ONLY, "RO"}, +}; + +/** + * efi_dump_single_var() - show information about a UEFI variable + * + * @name: Name of the variable + * @guid: Vendor GUID + * @verbose: if true, dump data + * + * Show information encoded in one UEFI variable + */ +static void efi_dump_single_var(u16 *name, const efi_guid_t *guid, bool verbose) +{ + u32 attributes; + u8 *data; + u64 time; + struct rtc_time tm; + efi_uintn_t size; + int count, i; + efi_status_t ret; + + data = NULL; + size = 0; + ret = efi_get_variable_int(name, guid, &attributes, &size, data, &time); + if (ret == EFI_BUFFER_TOO_SMALL) { + data = malloc(size); + if (!data) + goto out; + + ret = efi_get_variable_int(name, guid, &attributes, &size, + data, &time); + } + if (ret == EFI_NOT_FOUND) { + printf("Error: \"%ls\" not defined\n", name); + goto out; + } + if (ret != EFI_SUCCESS) + goto out; + + rtc_to_tm(time, &tm); + printf("%ls:\n %pUl (%pUs)\n", name, guid, guid); + if (attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) + printf(" %04d-%02d-%02d %02d:%02d:%02d\n", tm.tm_year, + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + printf(" "); + for (count = 0, i = 0; i < ARRAY_SIZE(efi_var_attrs); i++) + if (attributes & efi_var_attrs[i].mask) { + if (count) + putc('|'); + count++; + puts(efi_var_attrs[i].text); + } + printf(", DataSize = 0x%zx\n", size); + if (verbose) + print_hex_dump(" ", DUMP_PREFIX_OFFSET, 16, 1, + data, size, true); + +out: + free(data); +} + +static bool match_name(int argc, char *const argv[], u16 *var_name16) +{ + char *buf, *p; + size_t buflen; + int i; + bool result = false; + + buflen = utf16_utf8_strlen(var_name16) + 1; + buf = calloc(1, buflen); + if (!buf) + return result; + + p = buf; + utf16_utf8_strcpy(&p, var_name16); + + for (i = 0; i < argc; argc--, argv++) { + if (!strcmp(buf, argv[i])) { + result = true; + goto out; + } + } + +out: + free(buf); + + return result; +} + +/** + * efi_dump_var_all() - show information about all the UEFI variables + * + * @argc: Number of arguments (variables) + * @argv: Argument (variable name) array + * @verbose: if true, dump data + * Return: CMD_RET_SUCCESS on success, or CMD_RET_RET_FAILURE + * + * Show information encoded in all the UEFI variables + */ +static int efi_dump_var_all(int argc, char *const argv[], + const efi_guid_t *guid_p, bool verbose) +{ + u16 *var_name16, *p; + efi_uintn_t buf_size, size; + efi_guid_t guid; + efi_status_t ret; + bool match = false; + + buf_size = 128; + var_name16 = malloc(buf_size); + if (!var_name16) + return CMD_RET_FAILURE; + + var_name16[0] = 0; + for (;;) { + size = buf_size; + ret = efi_get_next_variable_name_int(&size, var_name16, + &guid); + if (ret == EFI_NOT_FOUND) + break; + if (ret == EFI_BUFFER_TOO_SMALL) { + buf_size = size; + p = realloc(var_name16, buf_size); + if (!p) { + free(var_name16); + return CMD_RET_FAILURE; + } + var_name16 = p; + ret = efi_get_next_variable_name_int(&size, var_name16, + &guid); + } + if (ret != EFI_SUCCESS) { + free(var_name16); + return CMD_RET_FAILURE; + } + + if (guid_p && guidcmp(guid_p, &guid)) + continue; + if (!argc || match_name(argc, argv, var_name16)) { + match = true; + efi_dump_single_var(var_name16, &guid, verbose); + } + } + free(var_name16); + + if (!match && argc == 1) { + printf("Error: \"%s\" not defined\n", argv[0]); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +/** + * do_env_print_efi() - show information about UEFI variables + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, or CMD_RET_RET_FAILURE + * + * This function is for "env print -e" or "printenv -e" command: + * => env print -e [-n] [-guid <guid> | -all] [var [...]] + * If one or more variable names are specified, show information + * named UEFI variables, otherwise show all the UEFI variables. + */ +int do_env_print_efi(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const efi_guid_t *guid_p = NULL; + efi_guid_t guid; + bool verbose = true; + efi_status_t ret; + + /* Initialize EFI drivers */ + ret = efi_init_obj_list(); + if (ret != EFI_SUCCESS) { + printf("Error: Cannot initialize UEFI sub-system, r = %lu\n", + ret & ~EFI_ERROR_MASK); + return CMD_RET_FAILURE; + } + + for (argc--, argv++; argc > 0 && argv[0][0] == '-'; argc--, argv++) { + if (!strcmp(argv[0], "-guid")) { + if (argc == 1) + return CMD_RET_USAGE; + argc--; + argv++; + if (uuid_str_to_bin(argv[0], guid.b, + UUID_STR_FORMAT_GUID)) + return CMD_RET_USAGE; + guid_p = (const efi_guid_t *)guid.b; + } else if (!strcmp(argv[0], "-n")) { + verbose = false; + } else { + return CMD_RET_USAGE; + } + } + + /* enumerate and show all UEFI variables */ + return efi_dump_var_all(argc, argv, guid_p, verbose); +} + +/** + * append_value() - encode UEFI variable's value + * @bufp: Buffer of encoded UEFI variable's value + * @sizep: Size of buffer + * @data: data to be encoded into the value + * Return: 0 on success, -1 otherwise + * + * Interpret a given data string and append it to buffer. + * Buffer will be realloc'ed if necessary. + * + * Currently supported formats are: + * =0x0123...: Hexadecimal number + * =H0123...: Hexadecimal-byte array + * ="...", =S"..." or <string>: + * String + */ +static int append_value(char **bufp, size_t *sizep, char *data) +{ + char *tmp_buf = NULL, *new_buf = NULL, *value; + unsigned long len = 0; + + if (!strncmp(data, "=0x", 3)) { /* hexadecimal number */ + union { + u8 u8; + u16 u16; + u32 u32; + u64 u64; + } tmp_data; + unsigned long hex_value; + void *hex_ptr; + + data += 3; + len = strlen(data); + if ((len & 0x1)) /* not multiple of two */ + return -1; + + len /= 2; + if (len > 8) + return -1; + else if (len > 4) + len = 8; + else if (len > 2) + len = 4; + + /* convert hex hexadecimal number */ + if (strict_strtoul(data, 16, &hex_value) < 0) + return -1; + + tmp_buf = malloc(len); + if (!tmp_buf) + return -1; + + if (len == 1) { + tmp_data.u8 = hex_value; + hex_ptr = &tmp_data.u8; + } else if (len == 2) { + tmp_data.u16 = hex_value; + hex_ptr = &tmp_data.u16; + } else if (len == 4) { + tmp_data.u32 = hex_value; + hex_ptr = &tmp_data.u32; + } else { + tmp_data.u64 = hex_value; + hex_ptr = &tmp_data.u64; + } + memcpy(tmp_buf, hex_ptr, len); + value = tmp_buf; + + } else if (!strncmp(data, "=H", 2)) { /* hexadecimal-byte array */ + data += 2; + len = strlen(data); + if (len & 0x1) /* not multiple of two */ + return -1; + + len /= 2; + tmp_buf = malloc(len); + if (!tmp_buf) + return -1; + + if (hex2bin((u8 *)tmp_buf, data, len) < 0) { + printf("Error: illegal hexadecimal string\n"); + free(tmp_buf); + return -1; + } + + value = tmp_buf; + } else { /* string */ + if (!strncmp(data, "=\"", 2) || !strncmp(data, "=S\"", 3)) { + if (data[1] == '"') + data += 2; + else + data += 3; + value = data; + len = strlen(data) - 1; + if (data[len] != '"') + return -1; + } else { + value = data; + len = strlen(data); + } + } + + new_buf = realloc(*bufp, *sizep + len); + if (!new_buf) + goto out; + + memcpy(new_buf + *sizep, value, len); + *bufp = new_buf; + *sizep += len; + +out: + free(tmp_buf); + + return 0; +} + +/** + * do_env_set_efi() - set UEFI variable + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * Return: CMD_RET_SUCCESS on success, or CMD_RET_RET_FAILURE + * + * This function is for "env set -e" or "setenv -e" command: + * => env set -e [-guid guid][-nv][-bs][-rt][-at][-a][-v] + * [-i address:size] var, or + * var [value ...] + * Encode values specified and set given UEFI variable. + * If no value is specified, delete the variable. + */ +int do_env_set_efi(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *var_name, *value, *ep; + ulong addr; + efi_uintn_t size; + efi_guid_t guid; + u32 attributes; + bool default_guid, verbose, value_on_memory; + u16 *var_name16; + efi_status_t ret; + + if (argc == 1) + return CMD_RET_USAGE; + + /* Initialize EFI drivers */ + ret = efi_init_obj_list(); + if (ret != EFI_SUCCESS) { + printf("Error: Cannot initialize UEFI sub-system, r = %lu\n", + ret & ~EFI_ERROR_MASK); + return CMD_RET_FAILURE; + } + + /* + * attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | + * EFI_VARIABLE_RUNTIME_ACCESS; + */ + value = NULL; + size = 0; + attributes = 0; + guid = efi_global_variable_guid; + default_guid = true; + verbose = false; + value_on_memory = false; + for (argc--, argv++; argc > 0 && argv[0][0] == '-'; argc--, argv++) { + if (!strcmp(argv[0], "-guid")) { + if (argc == 1) + return CMD_RET_USAGE; + + argc--; + argv++; + if (uuid_str_to_bin(argv[0], guid.b, + UUID_STR_FORMAT_GUID)) { + return CMD_RET_USAGE; + } + default_guid = false; + } else if (!strcmp(argv[0], "-bs")) { + attributes |= EFI_VARIABLE_BOOTSERVICE_ACCESS; + } else if (!strcmp(argv[0], "-rt")) { + attributes |= EFI_VARIABLE_RUNTIME_ACCESS; + } else if (!strcmp(argv[0], "-nv")) { + attributes |= EFI_VARIABLE_NON_VOLATILE; + } else if (!strcmp(argv[0], "-at")) { + attributes |= + EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + } else if (!strcmp(argv[0], "-a")) { + attributes |= EFI_VARIABLE_APPEND_WRITE; + } else if (!strcmp(argv[0], "-i")) { + /* data comes from memory */ + if (argc == 1) + return CMD_RET_USAGE; + + argc--; + argv++; + addr = hextoul(argv[0], &ep); + if (*ep != ':') + return CMD_RET_USAGE; + + /* 0 should be allowed for delete */ + size = hextoul(++ep, NULL); + + value_on_memory = true; + } else if (!strcmp(argv[0], "-v")) { + verbose = true; + } else { + return CMD_RET_USAGE; + } + } + if (!argc) + return CMD_RET_USAGE; + + var_name = argv[0]; + if (default_guid) { + if (!strcmp(var_name, "db") || !strcmp(var_name, "dbx") || + !strcmp(var_name, "dbt")) + guid = efi_guid_image_security_database; + else + guid = efi_global_variable_guid; + } + + if (verbose) { + printf("GUID: %pUl (%pUs)\n", &guid, &guid); + printf("Attributes: 0x%x\n", attributes); + } + + /* for value */ + if (value_on_memory) + value = map_sysmem(addr, 0); + else if (argc > 1) + for (argc--, argv++; argc > 0; argc--, argv++) + if (append_value(&value, &size, argv[0]) < 0) { + printf("## Failed to process an argument, %s\n", + argv[0]); + ret = CMD_RET_FAILURE; + goto out; + } + + if (size && verbose) { + printf("Value:\n"); + print_hex_dump(" ", DUMP_PREFIX_OFFSET, + 16, 1, value, size, true); + } + + var_name16 = efi_convert_string(var_name); + if (!var_name16) { + printf("## Out of memory\n"); + ret = CMD_RET_FAILURE; + goto out; + } + ret = efi_set_variable_int(var_name16, &guid, attributes, size, value, + true); + free(var_name16); + unmap_sysmem(value); + if (ret == EFI_SUCCESS) { + ret = CMD_RET_SUCCESS; + } else { + const char *msg; + + switch (ret) { + case EFI_NOT_FOUND: + msg = " (not found)"; + break; + case EFI_WRITE_PROTECTED: + msg = " (read only)"; + break; + case EFI_INVALID_PARAMETER: + msg = " (invalid parameter)"; + break; + case EFI_SECURITY_VIOLATION: + msg = " (validation failed)"; + break; + case EFI_OUT_OF_RESOURCES: + msg = " (out of memory)"; + break; + default: + msg = ""; + break; + } + printf("## Failed to set EFI variable%s\n", msg); + ret = CMD_RET_FAILURE; + } +out: + if (value_on_memory) + unmap_sysmem(value); + else + free(value); + + return ret; +} diff --git a/cmd/nvme.c b/cmd/nvme.c new file mode 100644 index 00000000000..f2c9acba5c3 --- /dev/null +++ b/cmd/nvme.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 NXP Semiconductors + * Copyright (C) 2017 Bin Meng <bmeng.cn@gmail.com> + */ + +#include <blk.h> +#include <command.h> +#include <dm.h> +#include <nvme.h> + +static int nvme_curr_dev; + +static int do_nvme(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + if (argc == 2) { + if (strncmp(argv[1], "scan", 4) == 0) { + ret = nvme_scan_namespace(); + if (ret) + return CMD_RET_FAILURE; + + return ret; + } + if (strncmp(argv[1], "deta", 4) == 0) { + struct udevice *udev; + + ret = blk_get_device(UCLASS_NVME, nvme_curr_dev, + &udev); + if (ret < 0) + return CMD_RET_FAILURE; + + nvme_print_info(udev); + + return ret; + } + } + + return blk_common_cmd(argc, argv, UCLASS_NVME, &nvme_curr_dev); +} + +U_BOOT_CMD( + nvme, 8, 1, do_nvme, + "NVM Express sub-system", + "scan - scan NVMe devices\n" + "nvme detail - show details of current NVMe device\n" + "nvme info - show all available NVMe devices\n" + "nvme device [dev] - show or set current NVMe device\n" + "nvme part [dev] - print partition table of one or all NVMe devices\n" + "nvme read addr blk# cnt - read `cnt' blocks starting at block\n" + " `blk#' to memory address `addr'\n" + "nvme write addr blk# cnt - write `cnt' blocks starting at block\n" + " `blk#' from memory address `addr'" +); diff --git a/cmd/onenand.c b/cmd/onenand.c new file mode 100644 index 00000000000..6e808ce3fce --- /dev/null +++ b/cmd/onenand.c @@ -0,0 +1,599 @@ +/* + * U-Boot command for OneNAND support + * + * Copyright (C) 2005-2008 Samsung Electronics + * Kyungmin Park <kyungmin.park@samsung.com> + * + * 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. + */ + +#include <command.h> +#include <malloc.h> +#include <linux/printk.h> + +#include <linux/compat.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/onenand.h> + +#include <asm/io.h> + +static struct mtd_info *mtd; + +static loff_t next_ofs; +static loff_t skip_ofs; + +static int arg_off_size_onenand(int argc, char *const argv[], ulong *off, + size_t *size) +{ + if (argc >= 1) { + if (!(str2long(argv[0], off))) { + printf("'%s' is not a number\n", argv[0]); + return -1; + } + } else { + *off = 0; + } + + if (argc >= 2) { + if (!(str2long(argv[1], (ulong *)size))) { + printf("'%s' is not a number\n", argv[1]); + return -1; + } + } else { + *size = mtd->size - *off; + } + + if ((*off + *size) > mtd->size) { + printf("total chip size (0x%llx) exceeded!\n", mtd->size); + return -1; + } + + if (*size == mtd->size) + puts("whole chip\n"); + else + printf("offset 0x%lx, size 0x%zx\n", *off, *size); + + return 0; +} + +static int onenand_block_read(loff_t from, size_t len, + size_t *retlen, u_char *buf, int oob) +{ + struct onenand_chip *this = mtd->priv; + int blocks = (int) len >> this->erase_shift; + int blocksize = (1 << this->erase_shift); + loff_t ofs = from; + struct mtd_oob_ops ops = { + .retlen = 0, + }; + int ret; + + if (oob) + ops.ooblen = blocksize; + else + ops.len = blocksize; + + while (blocks) { + ret = mtd_block_isbad(mtd, ofs); + if (ret) { + printk("Bad blocks %d at 0x%x\n", + (u32)(ofs >> this->erase_shift), (u32)ofs); + ofs += blocksize; + continue; + } + + if (oob) + ops.oobbuf = buf; + else + ops.datbuf = buf; + + ops.retlen = 0; + ret = mtd_read_oob(mtd, ofs, &ops); + if (ret) { + printk("Read failed 0x%x, %d\n", (u32)ofs, ret); + ofs += blocksize; + continue; + } + ofs += blocksize; + buf += blocksize; + blocks--; + *retlen += ops.retlen; + } + + return 0; +} + +static int onenand_write_oneblock_withoob(loff_t to, const u_char * buf, + size_t *retlen) +{ + struct mtd_oob_ops ops = { + .len = mtd->writesize, + .ooblen = mtd->oobsize, + .mode = MTD_OPS_AUTO_OOB, + }; + int page, ret = 0; + for (page = 0; page < (mtd->erasesize / mtd->writesize); page ++) { + ops.datbuf = (u_char *)buf; + buf += mtd->writesize; + ops.oobbuf = (u_char *)buf; + buf += mtd->oobsize; + ret = mtd_write_oob(mtd, to, &ops); + if (ret) + break; + to += mtd->writesize; + } + + *retlen = (ret) ? 0 : mtd->erasesize; + return ret; +} + +static int onenand_block_write(loff_t to, size_t len, + size_t *retlen, const u_char * buf, int withoob) +{ + struct onenand_chip *this = mtd->priv; + int blocks = len >> this->erase_shift; + int blocksize = (1 << this->erase_shift); + loff_t ofs; + size_t _retlen = 0; + int ret; + + if ((to & (mtd->writesize - 1)) != 0) { + printf("Attempt to write non block-aligned data\n"); + *retlen = 0; + return 1; + } + + if (to == next_ofs) { + next_ofs = to + len; + to += skip_ofs; + } else { + next_ofs = to + len; + skip_ofs = 0; + } + ofs = to; + + while (blocks) { + ret = mtd_block_isbad(mtd, ofs); + if (ret) { + printk("Bad blocks %d at 0x%x\n", + (u32)(ofs >> this->erase_shift), (u32)ofs); + skip_ofs += blocksize; + goto next; + } + + if (!withoob) + ret = mtd_write(mtd, ofs, blocksize, &_retlen, buf); + else + ret = onenand_write_oneblock_withoob(ofs, buf, &_retlen); + if (ret) { + printk("Write failed 0x%x, %d", (u32)ofs, ret); + skip_ofs += blocksize; + goto next; + } + + buf += blocksize; + blocks--; + *retlen += _retlen; +next: + ofs += blocksize; + } + + return 0; +} + +static int onenand_block_erase(u32 start, u32 size, int force) +{ + struct onenand_chip *this = mtd->priv; + struct erase_info instr = {}; + loff_t ofs; + int ret; + int blocksize = 1 << this->erase_shift; + + for (ofs = start; ofs < (start + size); ofs += blocksize) { + ret = mtd_block_isbad(mtd, ofs); + if (ret && !force) { + printf("Skip erase bad block %d at 0x%x\n", + (u32)(ofs >> this->erase_shift), (u32)ofs); + continue; + } + + instr.addr = ofs; + instr.len = blocksize; + instr.priv = force; + instr.mtd = mtd; + ret = mtd_erase(mtd, &instr); + if (ret) { + printf("erase failed block %d at 0x%x\n", + (u32)(ofs >> this->erase_shift), (u32)ofs); + continue; + } + } + + return 0; +} + +static int onenand_block_test(u32 start, u32 size) +{ + struct onenand_chip *this = mtd->priv; + struct erase_info instr = {}; + + int blocks; + loff_t ofs; + int blocksize = 1 << this->erase_shift; + int start_block, end_block; + size_t retlen; + u_char *buf; + u_char *verify_buf; + int ret; + + buf = malloc(blocksize); + if (!buf) { + printf("Not enough malloc space available!\n"); + return -1; + } + + verify_buf = malloc(blocksize); + if (!verify_buf) { + printf("Not enough malloc space available!\n"); + return -1; + } + + start_block = start >> this->erase_shift; + end_block = (start + size) >> this->erase_shift; + + /* Protect boot-loader from badblock testing */ + if (start_block < 2) + start_block = 2; + + if (end_block > (mtd->size >> this->erase_shift)) + end_block = mtd->size >> this->erase_shift; + + blocks = start_block; + ofs = start; + while (blocks < end_block) { + printf("\rTesting block %d at 0x%x", (u32)(ofs >> this->erase_shift), (u32)ofs); + + ret = mtd_block_isbad(mtd, ofs); + if (ret) { + printf("Skip erase bad block %d at 0x%x\n", + (u32)(ofs >> this->erase_shift), (u32)ofs); + goto next; + } + + instr.addr = ofs; + instr.len = blocksize; + ret = mtd_erase(mtd, &instr); + if (ret) { + printk("Erase failed 0x%x, %d\n", (u32)ofs, ret); + goto next; + } + + ret = mtd_write(mtd, ofs, blocksize, &retlen, buf); + if (ret) { + printk("Write failed 0x%x, %d\n", (u32)ofs, ret); + goto next; + } + + ret = mtd_read(mtd, ofs, blocksize, &retlen, verify_buf); + if (ret) { + printk("Read failed 0x%x, %d\n", (u32)ofs, ret); + goto next; + } + + if (memcmp(buf, verify_buf, blocksize)) + printk("\nRead/Write test failed at 0x%x\n", (u32)ofs); + +next: + ofs += blocksize; + blocks++; + } + printf("...Done\n"); + + free(buf); + free(verify_buf); + + return 0; +} + +static int onenand_dump(struct mtd_info *mtd, ulong off, int only_oob) +{ + int i; + u_char *datbuf, *oobbuf, *p; + struct mtd_oob_ops ops; + loff_t addr; + + datbuf = malloc(mtd->writesize + mtd->oobsize); + oobbuf = malloc(mtd->oobsize); + if (!datbuf || !oobbuf) { + puts("No memory for page buffer\n"); + return 1; + } + off &= ~(mtd->writesize - 1); + addr = (loff_t) off; + memset(&ops, 0, sizeof(ops)); + ops.datbuf = datbuf; + ops.oobbuf = oobbuf; + ops.len = mtd->writesize; + ops.ooblen = mtd->oobsize; + ops.retlen = 0; + i = mtd_read_oob(mtd, addr, &ops); + if (i < 0) { + printf("Error (%d) reading page %08lx\n", i, off); + free(datbuf); + free(oobbuf); + return 1; + } + printf("Page %08lx dump:\n", off); + i = mtd->writesize >> 4; + p = datbuf; + + while (i--) { + if (!only_oob) + printf("\t%02x %02x %02x %02x %02x %02x %02x %02x" + " %02x %02x %02x %02x %02x %02x %02x %02x\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], + p[8], p[9], p[10], p[11], p[12], p[13], p[14], + p[15]); + p += 16; + } + puts("OOB:\n"); + i = mtd->oobsize >> 3; + p = oobbuf; + + while (i--) { + printf("\t%02x %02x %02x %02x %02x %02x %02x %02x\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); + p += 8; + } + free(datbuf); + free(oobbuf); + + return 0; +} + +static int do_onenand_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + printf("%s\n", mtd->name); + return 0; +} + +static int do_onenand_bad(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong ofs; + + mtd = &onenand_mtd; + /* Currently only one OneNAND device is supported */ + printf("\nDevice %d bad blocks:\n", 0); + for (ofs = 0; ofs < mtd->size; ofs += mtd->erasesize) { + if (mtd_block_isbad(mtd, ofs)) + printf(" %08x\n", (u32)ofs); + } + + return 0; +} + +static int do_onenand_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *s; + int oob = 0; + ulong addr, ofs; + size_t len; + int ret = 0; + size_t retlen = 0; + + if (argc < 3) + return CMD_RET_USAGE; + + s = strchr(argv[0], '.'); + if ((s != NULL) && (!strcmp(s, ".oob"))) + oob = 1; + + addr = (ulong)hextoul(argv[1], NULL); + + printf("\nOneNAND read: "); + if (arg_off_size_onenand(argc - 2, argv + 2, &ofs, &len) != 0) + return 1; + + ret = onenand_block_read(ofs, len, &retlen, (u8 *)addr, oob); + + printf(" %zu bytes read: %s\n", retlen, ret ? "ERROR" : "OK"); + + return ret == 0 ? 0 : 1; +} + +static int do_onenand_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr, ofs; + size_t len; + int ret = 0, withoob = 0; + size_t retlen = 0; + + if (argc < 3) + return CMD_RET_USAGE; + + if (strncmp(argv[0] + 6, "yaffs", 5) == 0) + withoob = 1; + + addr = (ulong)hextoul(argv[1], NULL); + + printf("\nOneNAND write: "); + if (arg_off_size_onenand(argc - 2, argv + 2, &ofs, &len) != 0) + return 1; + + ret = onenand_block_write(ofs, len, &retlen, (u8 *)addr, withoob); + + printf(" %zu bytes written: %s\n", retlen, ret ? "ERROR" : "OK"); + + return ret == 0 ? 0 : 1; +} + +static int do_onenand_erase(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong ofs; + int ret = 0; + size_t len; + int force; + + /* + * Syntax is: + * 0 1 2 3 4 + * onenand erase [force] [off size] + */ + argc--; + argv++; + if (argc) + { + if (!strcmp("force", argv[0])) + { + force = 1; + argc--; + argv++; + } + } + printf("\nOneNAND erase: "); + + /* skip first two or three arguments, look for offset and size */ + if (arg_off_size_onenand(argc, argv, &ofs, &len) != 0) + return 1; + + ret = onenand_block_erase(ofs, len, force); + + printf("%s\n", ret ? "ERROR" : "OK"); + + return ret == 0 ? 0 : 1; +} + +static int do_onenand_test(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong ofs; + int ret = 0; + size_t len; + + /* + * Syntax is: + * 0 1 2 3 4 + * onenand test [force] [off size] + */ + + printf("\nOneNAND test: "); + + /* skip first two or three arguments, look for offset and size */ + if (arg_off_size_onenand(argc - 1, argv + 1, &ofs, &len) != 0) + return 1; + + ret = onenand_block_test(ofs, len); + + printf("%s\n", ret ? "ERROR" : "OK"); + + return ret == 0 ? 0 : 1; +} + +static int do_onenand_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong ofs; + int ret = 0; + char *s; + + if (argc < 2) + return CMD_RET_USAGE; + + s = strchr(argv[0], '.'); + ofs = (int)hextoul(argv[1], NULL); + + if (s != NULL && strcmp(s, ".oob") == 0) + ret = onenand_dump(mtd, ofs, 1); + else + ret = onenand_dump(mtd, ofs, 0); + + return ret == 0 ? 1 : 0; +} + +static int do_onenand_markbad(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret = 0; + ulong addr; + + argc -= 2; + argv += 2; + + if (argc <= 0) + return CMD_RET_USAGE; + + while (argc > 0) { + addr = hextoul(*argv, NULL); + + if (mtd_block_markbad(mtd, addr)) { + printf("block 0x%08lx NOT marked " + "as bad! ERROR %d\n", + addr, ret); + ret = 1; + } else { + printf("block 0x%08lx successfully " + "marked as bad\n", + addr); + } + --argc; + ++argv; + } + return ret; +} + +static struct cmd_tbl cmd_onenand_sub[] = { + U_BOOT_CMD_MKENT(info, 1, 0, do_onenand_info, "", ""), + U_BOOT_CMD_MKENT(bad, 1, 0, do_onenand_bad, "", ""), + U_BOOT_CMD_MKENT(read, 4, 0, do_onenand_read, "", ""), + U_BOOT_CMD_MKENT(write, 4, 0, do_onenand_write, "", ""), + U_BOOT_CMD_MKENT(write.yaffs, 4, 0, do_onenand_write, "", ""), + U_BOOT_CMD_MKENT(erase, 3, 0, do_onenand_erase, "", ""), + U_BOOT_CMD_MKENT(test, 3, 0, do_onenand_test, "", ""), + U_BOOT_CMD_MKENT(dump, 2, 0, do_onenand_dump, "", ""), + U_BOOT_CMD_MKENT(markbad, CONFIG_SYS_MAXARGS, 0, do_onenand_markbad, "", ""), +}; + +static int do_onenand(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c; + + if (argc < 2) + return CMD_RET_USAGE; + + mtd = &onenand_mtd; + + /* Strip off leading 'onenand' command argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], &cmd_onenand_sub[0], ARRAY_SIZE(cmd_onenand_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + onenand, CONFIG_SYS_MAXARGS, 1, do_onenand, + "OneNAND sub-system", + "info - show available OneNAND devices\n" + "onenand bad - show bad blocks\n" + "onenand read[.oob] addr off size\n" + "onenand write[.yaffs] addr off size\n" + " read/write 'size' bytes starting at offset 'off'\n" + " to/from memory address 'addr', skipping bad blocks.\n" + "onenand erase [force] [off size] - erase 'size' bytes from\n" + "onenand test [off size] - test 'size' bytes from\n" + " offset 'off' (entire device if not specified)\n" + "onenand dump[.oob] off - dump page\n" + "onenand markbad off [...] - mark bad block(s) at offset (UNSAFE)" +); diff --git a/cmd/optee.c b/cmd/optee.c new file mode 100644 index 00000000000..155c9f1bb73 --- /dev/null +++ b/cmd/optee.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) Copyright 2024, Advanced Micro Devices, Inc. + */ +#include <command.h> +#include <errno.h> +#include <tee.h> +#include <vsprintf.h> +#include <linux/string.h> + +#define TA_HELLO_WORLD_CMD_INC_VALUE 0 +/* This needs to match the UUID of the Hello World TA. */ +#define TA_HELLO_WORLD_UUID \ + { 0x8aaaf200, 0x2450, 0x11e4, \ + { 0xab, 0xe2, 0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} } + +static int hello_world_ta(unsigned int value) +{ + const struct tee_optee_ta_uuid uuid = TA_HELLO_WORLD_UUID; + struct tee_open_session_arg session_arg; + struct udevice *tee = NULL; + struct tee_invoke_arg arg; + struct tee_param param[2]; + int rc; + + tee = tee_find_device(tee, NULL, NULL, NULL); + if (!tee) + return -ENODEV; + + memset(&session_arg, 0, sizeof(session_arg)); + tee_optee_ta_uuid_to_octets(session_arg.uuid, &uuid); + rc = tee_open_session(tee, &session_arg, 0, NULL); + if (rc) { + printf("tee_open_session(): failed(%d)\n", rc); + return rc; + } + + arg.func = TA_HELLO_WORLD_CMD_INC_VALUE; + arg.session = session_arg.session; + + param[0].attr = TEE_PARAM_ATTR_TYPE_VALUE_INOUT; + param[0].u.value.a = value; + + printf("Value before: 0x%x\n", (int)param[0].u.value.a); + printf("Calling TA\n"); + tee_invoke_func(tee, &arg, 1, param); + + printf("Value after: 0x%x\n", (int)param[0].u.value.a); + return tee_close_session(tee, session_arg.session); +} + +static int do_optee_hello_world_ta(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + int ret, value = 0; + + if (argc > 1) + value = hextoul(argv[1], NULL); + + ret = hello_world_ta(value); + if (ret) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(optee, + "hello [<value>] Invoke the OP-TEE 'Hello World' TA\n"); + +U_BOOT_CMD_WITH_SUBCMDS(optee, "OP-TEE commands", optee_help_text, + U_BOOT_SUBCMD_MKENT(hello, 2, 1, do_optee_hello_world_ta)); diff --git a/cmd/optee_rpmb.c b/cmd/optee_rpmb.c new file mode 100644 index 00000000000..b155278ee2a --- /dev/null +++ b/cmd/optee_rpmb.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020 NXP + */ + +#include <command.h> +#include <env.h> +#include <errno.h> +#include <image.h> +#include <malloc.h> +#include <mmc.h> +#include <tee.h> +#include <tee/optee_ta_avb.h> + +static struct udevice *tee; +static u32 session; + +static int avb_ta_open_session(void) +{ + const struct tee_optee_ta_uuid uuid = TA_AVB_UUID; + struct tee_open_session_arg arg; + int rc; + + tee = tee_find_device(tee, NULL, NULL, NULL); + if (!tee) + return -ENODEV; + + memset(&arg, 0, sizeof(arg)); + tee_optee_ta_uuid_to_octets(arg.uuid, &uuid); + rc = tee_open_session(tee, &arg, 0, NULL); + if (!rc) + session = arg.session; + + return 0; +} + +static int invoke_func(u32 func, ulong num_param, struct tee_param *param) +{ + struct tee_invoke_arg arg; + + if (!tee) + if (avb_ta_open_session()) + return -ENODEV; + + memset(&arg, 0, sizeof(arg)); + arg.func = func; + arg.session = session; + + if (tee_invoke_func(tee, &arg, num_param, param)) + return -EFAULT; + switch (arg.ret) { + case TEE_SUCCESS: + return 0; + case TEE_ERROR_OUT_OF_MEMORY: + case TEE_ERROR_STORAGE_NO_SPACE: + return -ENOSPC; + case TEE_ERROR_ITEM_NOT_FOUND: + return -EIO; + case TEE_ERROR_TARGET_DEAD: + /* + * The TA has paniced, close the session to reload the TA + * for the next request. + */ + tee_close_session(tee, session); + tee = NULL; + return -EIO; + default: + return -EIO; + } +} + +static int read_persistent_value(const char *name, + size_t buffer_size, + u8 *out_buffer, + size_t *out_num_bytes_read) +{ + int rc = 0; + struct tee_shm *shm_name; + struct tee_shm *shm_buf; + struct tee_param param[2]; + size_t name_size = strlen(name) + 1; + + if (!tee) + if (avb_ta_open_session()) + return -ENODEV; + + rc = tee_shm_alloc(tee, name_size, + TEE_SHM_ALLOC, &shm_name); + if (rc) { + rc = -ENOMEM; + goto close_session; + } + + rc = tee_shm_alloc(tee, buffer_size, + TEE_SHM_ALLOC, &shm_buf); + if (rc) { + rc = -ENOMEM; + goto free_name; + } + + memcpy(shm_name->addr, name, name_size); + + memset(param, 0, sizeof(param)); + param[0].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT; + param[0].u.memref.shm = shm_name; + param[0].u.memref.size = name_size; + param[1].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INOUT; + param[1].u.memref.shm = shm_buf; + param[1].u.memref.size = buffer_size; + + rc = invoke_func(TA_AVB_CMD_READ_PERSIST_VALUE, + 2, param); + if (rc) + goto out; + + if (param[1].u.memref.size > buffer_size) { + rc = -EINVAL; + goto out; + } + + *out_num_bytes_read = param[1].u.memref.size; + + memcpy(out_buffer, shm_buf->addr, *out_num_bytes_read); + +out: + tee_shm_free(shm_buf); +free_name: + tee_shm_free(shm_name); +close_session: + tee_close_session(tee, session); + tee = NULL; + + return rc; +} + +static int write_persistent_value(const char *name, + size_t value_size, + const u8 *value) +{ + int rc = 0; + struct tee_shm *shm_name; + struct tee_shm *shm_buf; + struct tee_param param[2]; + size_t name_size = strlen(name) + 1; + + if (!value_size) + return -EINVAL; + + if (!tee) { + if (avb_ta_open_session()) + return -ENODEV; + } + + rc = tee_shm_alloc(tee, name_size, + TEE_SHM_ALLOC, &shm_name); + if (rc) { + rc = -ENOMEM; + goto close_session; + } + + rc = tee_shm_alloc(tee, value_size, + TEE_SHM_ALLOC, &shm_buf); + if (rc) { + rc = -ENOMEM; + goto free_name; + } + + memcpy(shm_name->addr, name, name_size); + memcpy(shm_buf->addr, value, value_size); + + memset(param, 0, sizeof(param)); + param[0].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT; + param[0].u.memref.shm = shm_name; + param[0].u.memref.size = name_size; + param[1].attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT; + param[1].u.memref.shm = shm_buf; + param[1].u.memref.size = value_size; + + rc = invoke_func(TA_AVB_CMD_WRITE_PERSIST_VALUE, + 2, param); + if (rc) + goto out; + +out: + tee_shm_free(shm_buf); +free_name: + tee_shm_free(shm_name); +close_session: + tee_close_session(tee, session); + tee = NULL; + + return rc; +} + +int do_optee_rpmb_read(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + const char *name; + size_t bytes; + size_t bytes_read; + void *buffer; + char *endp; + + if (argc != 3) + return CMD_RET_USAGE; + + name = argv[1]; + bytes = dectoul(argv[2], &endp); + if (*endp && *endp != '\n') + return CMD_RET_USAGE; + + buffer = malloc(bytes); + if (!buffer) + return CMD_RET_FAILURE; + + if (read_persistent_value(name, bytes, buffer, &bytes_read) == 0) { + printf("Read %zu bytes, value = %s\n", bytes_read, + (char *)buffer); + free(buffer); + return CMD_RET_SUCCESS; + } + + printf("Failed to read persistent value\n"); + + free(buffer); + + return CMD_RET_FAILURE; +} + +int do_optee_rpmb_write(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + const char *name; + const char *value; + + if (argc != 3) + return CMD_RET_USAGE; + + name = argv[1]; + value = argv[2]; + + if (write_persistent_value(name, strlen(value) + 1, + (const uint8_t *)value) == 0) { + printf("Wrote %zu bytes\n", strlen(value) + 1); + return CMD_RET_SUCCESS; + } + + printf("Failed to write persistent value\n"); + + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_optee_rpmb[] = { + U_BOOT_CMD_MKENT(read_pvalue, 3, 0, do_optee_rpmb_read, "", ""), + U_BOOT_CMD_MKENT(write_pvalue, 3, 0, do_optee_rpmb_write, "", ""), +}; + +static int do_optee_rpmb(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + struct cmd_tbl *cp; + + cp = find_cmd_tbl(argv[1], cmd_optee_rpmb, ARRAY_SIZE(cmd_optee_rpmb)); + + argc--; + argv++; + + if (!cp || argc > cp->maxargs) + return CMD_RET_USAGE; + + if (flag == CMD_FLAG_REPEAT) + return CMD_RET_FAILURE; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD ( + optee_rpmb, 29, 0, do_optee_rpmb, + "Provides commands for testing secure storage on RPMB on OPTEE", + "read_pvalue <name> <bytes> - read a persistent value <name>\n" + "optee_rpmb write_pvalue <name> <value> - write a persistent value <name>\n" + ); diff --git a/cmd/osd.c b/cmd/osd.c new file mode 100644 index 00000000000..5671338d9e7 --- /dev/null +++ b/cmd/osd.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2017 + * Mario Six, Guntermann & Drunck GmbH, mario.six@gdsys.cc + * + * based on the gdsys osd driver, which is + * + * (C) Copyright 2010 + * Dirk Eibach, Guntermann & Drunck GmbH, eibach@gdsys.de + */ + +#include <command.h> +#include <dm.h> +#include <hexdump.h> +#include <video_osd.h> +#include <malloc.h> + +/* Container for selected OSD device */ +static struct udevice *osd_cur; + +/** + * cmd_osd_set_osd_num() - Set the OSD selected for operation + * + * Set the OSD device, which will be used by all subsequent OSD commands. + * + * Devices are identified by their uclass sequence number (as listed by 'osd + * show'). + * + * @osdnum: The OSD device to be selected, identified by its sequence number. + * Return: 0 if OK, -ve on error + */ +static int cmd_osd_set_osd_num(unsigned int osdnum) +{ + struct udevice *osd; + int res; + + res = uclass_get_device_by_seq(UCLASS_VIDEO_OSD, osdnum, &osd); + if (res) { + printf("%s: No OSD %u (err = %d)\n", __func__, osdnum, res); + return res; + } + osd_cur = osd; + + return 0; +} + +/** + * osd_get_osd_cur() - Get the selected OSD device + * + * Get the OSD device that is used by all OSD commands. + * + * @osdp: Pointer to structure that will receive the currently selected OSD + * device. + * Return: 0 if OK, -ve on error + */ +static int osd_get_osd_cur(struct udevice **osdp) +{ + if (!osd_cur) { + puts("No osd selected\n"); + return -ENODEV; + } + *osdp = osd_cur; + + return 0; +} + +/** + * show_osd() - Display information about a OSD device + * + * Display a device's ID (sequence number), and whether it is active (i.e. + * probed) or not. + * + * @osd: OSD device to print information for + */ +static void show_osd(struct udevice *osd) +{ + printf("OSD %d:\t%s", dev_seq(osd), osd->name); + if (device_active(osd)) + printf(" (active)"); + printf("\n"); +} + +static int do_osd_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint x, y; + uint count; + char *hexstr; + u8 *buffer; + size_t buflen; + int res; + + if (argc < 4 || (strlen(argv[3]) % 2)) + return CMD_RET_USAGE; + + if (!osd_cur) { + puts("No osd selected\n"); + return CMD_RET_FAILURE; + } + + x = hextoul(argv[1], NULL); + y = hextoul(argv[2], NULL); + hexstr = argv[3]; + count = (argc > 4) ? hextoul(argv[4], NULL) : 1; + + buflen = strlen(hexstr) / 2; + + buffer = malloc(buflen); + if (!buffer) { + puts("Memory allocation failure\n"); + return CMD_RET_FAILURE; + } + + res = hex2bin(buffer, hexstr, buflen); + if (res) { + free(buffer); + puts("Hexadecimal input contained invalid characters\n"); + return CMD_RET_FAILURE; + } + + res = video_osd_set_mem(osd_cur, x, y, buffer, buflen, count); + if (res) { + free(buffer); + printf("%s: Could not write to video mem\n", + osd_cur->name); + return CMD_RET_FAILURE; + } + + free(buffer); + + return CMD_RET_SUCCESS; +} + +static int do_osd_print(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint x, y; + u8 color; + char *text; + int res; + + if (argc < 5) + return CMD_RET_USAGE; + + if (!osd_cur) { + puts("No osd selected\n"); + return CMD_RET_FAILURE; + } + + x = hextoul(argv[1], NULL); + y = hextoul(argv[2], NULL); + color = hextoul(argv[3], NULL); + text = argv[4]; + + res = video_osd_print(osd_cur, x, y, color, text); + if (res) { + printf("Could not print string to osd %s\n", osd_cur->name); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_osd_size(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + uint x, y; + int res; + + if (argc < 3) + return CMD_RET_USAGE; + + if (!osd_cur) { + puts("No osd selected\n"); + return CMD_RET_FAILURE; + } + + x = hextoul(argv[1], NULL); + y = hextoul(argv[2], NULL); + + res = video_osd_set_size(osd_cur, x, y); + if (res) { + printf("Could not set size on osd %s\n", osd_cur->name); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_show_osd(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *osd; + + if (argc == 1) { + /* show all OSDs */ + struct uclass *uc; + int res; + + res = uclass_get(UCLASS_VIDEO_OSD, &uc); + if (res) { + printf("Error while getting OSD uclass (err=%d)\n", + res); + return CMD_RET_FAILURE; + } + + uclass_foreach_dev(osd, uc) + show_osd(osd); + } else { + int i, res; + + /* show specific OSD */ + i = dectoul(argv[1], NULL); + + res = uclass_get_device_by_seq(UCLASS_VIDEO_OSD, i, &osd); + if (res) { + printf("Invalid osd %d: err=%d\n", i, res); + return CMD_RET_FAILURE; + } + show_osd(osd); + } + + return CMD_RET_SUCCESS; +} + +static int do_osd_num(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int osd_no; + int res = 0; + + if (argc == 1) { + /* querying current setting */ + struct udevice *osd; + + if (!osd_get_osd_cur(&osd)) + osd_no = dev_seq(osd); + else + osd_no = -1; + printf("Current osd is %d\n", osd_no); + } else { + osd_no = dectoul(argv[1], NULL); + printf("Setting osd to %d\n", osd_no); + + res = cmd_osd_set_osd_num(osd_no); + if (res) + printf("Failure changing osd number (err = %d)\n", res); + } + + return res ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static struct cmd_tbl cmd_osd_sub[] = { + U_BOOT_CMD_MKENT(show, 1, 1, do_show_osd, "", ""), + U_BOOT_CMD_MKENT(dev, 1, 1, do_osd_num, "", ""), + U_BOOT_CMD_MKENT(write, 4, 1, do_osd_write, "", ""), + U_BOOT_CMD_MKENT(print, 4, 1, do_osd_print, "", ""), + U_BOOT_CMD_MKENT(size, 2, 1, do_osd_size, "", ""), +}; + +static int do_osd(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *c; + + if (argc < 2) + return CMD_RET_USAGE; + + /* Strip off leading 'osd' command argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], &cmd_osd_sub[0], ARRAY_SIZE(cmd_osd_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_LONGHELP(osd, + "show - show OSD info\n" + "osd dev [dev] - show or set current OSD\n" + "write [pos_x] [pos_y] [buffer] [count] - write 8-bit hex encoded buffer to osd memory at a given position\n" + "print [pos_x] [pos_y] [color] [text] - write ASCII buffer (given by text data and driver-specific color information) to osd memory\n" + "size [size_x] [size_y] - set OSD XY size in characters\n"); + +U_BOOT_CMD( + osd, 6, 1, do_osd, + "OSD sub-system", + osd_help_text +); diff --git a/cmd/panic.c b/cmd/panic.c new file mode 100644 index 00000000000..7c0affa5eb5 --- /dev/null +++ b/cmd/panic.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020 Theobroma Systems Design und Consulting GmbH + */ + +#include <vsprintf.h> +#include <command.h> + +static int do_panic(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + char *text = (argc < 2) ? "" : argv[1]; + + panic(text); + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + panic, 2, 1, do_panic, + "Panic with optional message", + "[message]" +); diff --git a/cmd/part.c b/cmd/part.c new file mode 100644 index 00000000000..db7bc5819c0 --- /dev/null +++ b/cmd/part.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * made from cmd_ext2, which was: + * + * (C) Copyright 2004 + * esd gmbh <www.esd-electronics.com> + * Reinhard Arlt <reinhard.arlt@esd-electronics.com> + * + * made from cmd_reiserfs by + * + * (C) Copyright 2003 - 2004 + * Sysgo Real-Time Solutions, AG <www.elinos.com> + * Pavel Bartusek <pba@sysgo.com> + */ + +#include <config.h> +#include <command.h> +#include <env.h> +#include <part.h> +#include <stdio.h> +#include <vsprintf.h> + +enum cmd_part_info { + CMD_PART_INFO_START = 0, + CMD_PART_INFO_SIZE, + CMD_PART_INFO_NUMBER +}; + +static int do_part_uuid(int argc, char *const argv[]) +{ + int part; + struct blk_desc *dev_desc; + struct disk_partition info; + + if (argc < 2) + return CMD_RET_USAGE; + if (argc > 3) + return CMD_RET_USAGE; + + part = blk_get_device_part_str(argv[0], argv[1], &dev_desc, &info, 0); + if (part < 0) + return 1; + + if (argc > 2) + env_set(argv[2], info.uuid); + else + printf("%s\n", info.uuid); + + return 0; +} + +static int do_part_list(int argc, char *const argv[]) +{ + int ret; + struct blk_desc *desc; + char *var = NULL; + bool bootable = false; + int i; + + if (argc < 2) + return CMD_RET_USAGE; + + if (argc > 2) { + for (i = 2; i < argc ; i++) { + if (argv[i][0] == '-') { + if (!strcmp(argv[i], "-bootable")) { + bootable = true; + } else { + printf("Unknown option %s\n", argv[i]); + return CMD_RET_USAGE; + } + } else { + var = argv[i]; + break; + } + } + + /* Loops should have been exited at the last argument, which + * as it contained the variable */ + if (argc != i + 1) + return CMD_RET_USAGE; + } + + ret = blk_get_device_by_str(argv[0], argv[1], &desc); + if (ret < 0) + return 1; + + if (var != NULL) { + int p; + char str[3 * MAX_SEARCH_PARTITIONS] = { '\0', }; + struct disk_partition info; + + for (p = 1; p <= MAX_SEARCH_PARTITIONS; p++) { + char t[5]; + int r = part_get_info(desc, p, &info); + + if (r != 0) + continue; + + if (bootable && !info.bootable) + continue; + + sprintf(t, "%s%x", str[0] ? " " : "", p); + strcat(str, t); + } + env_set(var, str); + return 0; + } + + part_print(desc); + + return 0; +} + +static int do_part_info(int argc, char *const argv[], enum cmd_part_info param) +{ + struct blk_desc *desc; + struct disk_partition info; + char buf[512] = { 0 }; + char *endp; + int part; + int err; + int ret; + + if (argc < 3) + return CMD_RET_USAGE; + if (argc > 4) + return CMD_RET_USAGE; + + ret = blk_get_device_by_str(argv[0], argv[1], &desc); + if (ret < 0) + return 1; + + part = simple_strtoul(argv[2], &endp, 0); + if (*endp == '\0') { + err = part_get_info(desc, part, &info); + if (err) + return 1; + } else { + part = part_get_info_by_name(desc, argv[2], &info); + if (part < 0) + return 1; + } + + switch (param) { + case CMD_PART_INFO_START: + snprintf(buf, sizeof(buf), LBAF, info.start); + break; + case CMD_PART_INFO_SIZE: + snprintf(buf, sizeof(buf), LBAF, info.size); + break; + case CMD_PART_INFO_NUMBER: + snprintf(buf, sizeof(buf), "0x%x", part); + break; + default: + printf("** Unknown cmd_part_info value: %d\n", param); + return 1; + } + + if (argc > 3) + env_set(argv[3], buf); + else + printf("%s\n", buf); + + return 0; +} + +static int do_part_start(int argc, char *const argv[]) +{ + return do_part_info(argc, argv, CMD_PART_INFO_START); +} + +static int do_part_size(int argc, char *const argv[]) +{ + return do_part_info(argc, argv, CMD_PART_INFO_SIZE); +} + +static int do_part_number(int argc, char *const argv[]) +{ + return do_part_info(argc, argv, CMD_PART_INFO_NUMBER); +} + +static int do_part_set(int argc, char *const argv[]) +{ + const char *devname, *partstr, *typestr; + struct blk_desc *desc; + int dev; + + if (argc < 3) + return CMD_RET_USAGE; + + /* Look up the device */ + devname = argv[0]; + partstr = argv[1]; + typestr = argv[2]; + dev = blk_get_device_by_str(devname, partstr, &desc); + if (dev < 0) { + printf("** Bad device specification %s %s **\n", devname, + partstr); + return CMD_RET_FAILURE; + } + + desc->part_type = part_get_type_by_name(typestr); + if (!desc->part_type) { + printf("Unknown partition type '%s'\n", typestr); + return CMD_RET_FAILURE; + } + part_print(desc); + + return 0; +} + +#ifdef CONFIG_PARTITION_TYPE_GUID +static int do_part_type(int argc, char *const argv[]) +{ + int part; + struct blk_desc *dev_desc; + struct disk_partition info; + + if (argc < 2) + return CMD_RET_USAGE; + if (argc > 3) + return CMD_RET_USAGE; + + part = blk_get_device_part_str(argv[0], argv[1], &dev_desc, &info, 0); + if (part < 0) + return 1; + + if (argc > 2) + env_set(argv[2], info.type_guid); + else + printf("%s\n", info.type_guid); + + return 0; +} +#endif + +static int do_part_types(int argc, char * const argv[]) +{ + struct part_driver *drv = ll_entry_start(struct part_driver, + part_driver); + const int n_ents = ll_entry_count(struct part_driver, part_driver); + struct part_driver *entry; + int i = 0; + + puts("Supported partition tables"); + + for (entry = drv; entry != drv + n_ents; entry++) { + printf("%c %s", i ? ',' : ':', entry->name); + i++; + } + if (!i) + puts(": <none>"); + puts("\n"); + return CMD_RET_SUCCESS; +} + +static int do_part(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc < 2) + return CMD_RET_USAGE; + + if (!strcmp(argv[1], "uuid")) + return do_part_uuid(argc - 2, argv + 2); + else if (!strcmp(argv[1], "list")) + return do_part_list(argc - 2, argv + 2); + else if (!strcmp(argv[1], "start")) + return do_part_start(argc - 2, argv + 2); + else if (!strcmp(argv[1], "size")) + return do_part_size(argc - 2, argv + 2); + else if (!strcmp(argv[1], "number")) + return do_part_number(argc - 2, argv + 2); + else if (!strcmp(argv[1], "types")) + return do_part_types(argc - 2, argv + 2); + else if (!strcmp(argv[1], "set")) + return do_part_set(argc - 2, argv + 2); +#ifdef CONFIG_PARTITION_TYPE_GUID + else if (!strcmp(argv[1], "type")) + return do_part_type(argc - 2, argv + 2); +#endif + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + part, CONFIG_SYS_MAXARGS, 1, do_part, + "disk partition related commands", + "uuid <interface> <dev>:<part>\n" + " - print partition UUID\n" + "part uuid <interface> <dev>:<part> <varname>\n" + " - set environment variable to partition UUID\n" + "part list <interface> <dev>\n" + " - print a device's partition table\n" + "part list <interface> <dev> [flags] <varname>\n" + " - set environment variable to the list of partitions\n" + " flags can be -bootable (list only bootable partitions)\n" + "part start <interface> <dev> <part> <varname>\n" + " - set environment variable to the start of the partition (in blocks)\n" + " part can be either partition number or partition name\n" + "part size <interface> <dev> <part> <varname>\n" + " - set environment variable to the size of the partition (in blocks)\n" + " part can be either partition number or partition name\n" + "part number <interface> <dev> <part> <varname>\n" + " - set environment variable to the partition number using the partition name\n" + " part must be specified as partition name\n" +#ifdef CONFIG_PARTITION_TYPE_GUID + "part type <interface> <dev>:<part>\n" + " - print partition type\n" + "part type <interface> <dev>:<part> <varname>\n" + " - set environment variable to partition type\n" +#endif + "part set <interface> <dev> type\n" + " - set partition type for a device\n" + "part types\n" + " - list supported partition table types" +); diff --git a/cmd/pause.c b/cmd/pause.c new file mode 100644 index 00000000000..c97833c0d70 --- /dev/null +++ b/cmd/pause.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2021 + * Samuel Dionne-Riel <samuel@dionne-riel.com> + */ + +#include <command.h> +#include <stdio.h> + +static int do_pause(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *message = "Press any key to continue..."; + + if (argc == 2) + message = argv[1]; + + /* No newline, so it sticks to the bottom of the screen */ + printf("%s", message); + + /* Wait on "any" key... */ + (void) getchar(); + + /* Since there was no newline, we need it now */ + printf("\n"); + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(pause, 2, 1, do_pause, + "delay until user input", + "[prompt] - Wait until users presses any key. [prompt] can be used to customize the message.\n" +); diff --git a/cmd/pcap.c b/cmd/pcap.c new file mode 100644 index 00000000000..8d610966c13 --- /dev/null +++ b/cmd/pcap.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2019 + * Ramon Fried <rfried.dev@gmail.com> + */ + +#include <command.h> +#include <vsprintf.h> +#include <net.h> +#include <net/pcap.h> + +static int do_pcap_init(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + phys_addr_t addr; + unsigned int size; + + if (argc != 3) + return CMD_RET_USAGE; + + addr = hextoul(argv[1], NULL); + size = dectoul(argv[2], NULL); + + return pcap_init(addr, size) ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static int do_pcap_start(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return pcap_start_stop(true) ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static int do_pcap_stop(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return pcap_start_stop(false) ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static int do_pcap_status(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return pcap_print_status() ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static int do_pcap_clear(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return pcap_clear() ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(pcap, + "- network packet capture\n\n" + "pcap\n" + "pcap init\t\t\t<addr> <max_size>\n" + "pcap start\t\t\tstart capture\n" + "pcap stop\t\t\tstop capture\n" + "pcap status\t\t\tprint status\n" + "pcap clear\t\t\tclear capture buffer\n" + "\n" + "With:\n" + "\t<addr>: user address to which pcap will be stored (hexedcimal)\n" + "\t<max_size>: Maximum size of pcap file (decimal)\n" + "\n"); + +U_BOOT_CMD_WITH_SUBCMDS(pcap, "pcap", pcap_help_text, + U_BOOT_SUBCMD_MKENT(init, 3, 0, do_pcap_init), + U_BOOT_SUBCMD_MKENT(start, 1, 0, do_pcap_start), + U_BOOT_SUBCMD_MKENT(stop, 1, 0, do_pcap_stop), + U_BOOT_SUBCMD_MKENT(status, 1, 0, do_pcap_status), + U_BOOT_SUBCMD_MKENT(clear, 1, 0, do_pcap_clear), +); diff --git a/cmd/pci.c b/cmd/pci.c new file mode 100644 index 00000000000..3c0aed50cae --- /dev/null +++ b/cmd/pci.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2001 Sysgo Real-Time Solutions, GmbH <www.elinos.com> + * Andreas Heppel <aheppel@sysgo.de> + * + * (C) Copyright 2002 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * Wolfgang Grandegger, DENX Software Engineering, wg@denx.de. + */ + +/* + * PCI routines + */ + +#include <bootretry.h> +#include <cli.h> +#include <command.h> +#include <console.h> +#include <dm.h> +#include <init.h> +#include <asm/processor.h> +#include <asm/io.h> +#include <pci.h> + +struct pci_reg_info { + const char *name; + enum pci_size_t size; + u8 offset; +}; + +static int pci_byte_size(enum pci_size_t size) +{ + switch (size) { + case PCI_SIZE_8: + return 1; + case PCI_SIZE_16: + return 2; + case PCI_SIZE_32: + default: + return 4; + } +} + +static int pci_field_width(enum pci_size_t size) +{ + return pci_byte_size(size) * 2; +} + +static void pci_show_regs(struct udevice *dev, struct pci_reg_info *regs) +{ + for (; regs->name; regs++) { + unsigned long val; + + dm_pci_read_config(dev, regs->offset, &val, regs->size); + printf(" %s =%*s%#.*lx\n", regs->name, + (int)(28 - strlen(regs->name)), "", + pci_field_width(regs->size), val); + } +} + +static int pci_bar_show(struct udevice *dev) +{ + u8 header_type; + int bar_cnt, bar_id, mem_type; + bool is_64, is_io; + u32 base_low, base_high; + u32 size_low, size_high; + u64 base, size; + u32 reg_addr; + int prefetchable; + + dm_pci_read_config8(dev, PCI_HEADER_TYPE, &header_type); + header_type &= 0x7f; + + if (header_type == PCI_HEADER_TYPE_CARDBUS) { + printf("CardBus doesn't support BARs\n"); + return -ENOSYS; + } else if (header_type != PCI_HEADER_TYPE_NORMAL && + header_type != PCI_HEADER_TYPE_BRIDGE) { + printf("unknown header type\n"); + return -ENOSYS; + } + + bar_cnt = (header_type == PCI_HEADER_TYPE_NORMAL) ? 6 : 2; + + printf("ID Base Size Width Type\n"); + printf("----------------------------------------------------------\n"); + + bar_id = 0; + reg_addr = PCI_BASE_ADDRESS_0; + while (bar_cnt) { + dm_pci_read_config32(dev, reg_addr, &base_low); + dm_pci_write_config32(dev, reg_addr, 0xffffffff); + dm_pci_read_config32(dev, reg_addr, &size_low); + dm_pci_write_config32(dev, reg_addr, base_low); + reg_addr += 4; + + base = base_low & ~0xf; + size = size_low & ~0xf; + base_high = 0x0; + size_high = 0xffffffff; + is_64 = 0; + prefetchable = base_low & PCI_BASE_ADDRESS_MEM_PREFETCH; + is_io = base_low & PCI_BASE_ADDRESS_SPACE_IO; + mem_type = base_low & PCI_BASE_ADDRESS_MEM_TYPE_MASK; + + if (mem_type == PCI_BASE_ADDRESS_MEM_TYPE_64) { + dm_pci_read_config32(dev, reg_addr, &base_high); + dm_pci_write_config32(dev, reg_addr, 0xffffffff); + dm_pci_read_config32(dev, reg_addr, &size_high); + dm_pci_write_config32(dev, reg_addr, base_high); + bar_cnt--; + reg_addr += 4; + is_64 = 1; + } + + base = base | ((u64)base_high << 32); + size = size | ((u64)size_high << 32); + + if ((!is_64 && size_low) || (is_64 && size)) { + size = ~size + 1; + printf(" %d %#018llx %#018llx %d %s %s\n", + bar_id, (unsigned long long)base, + (unsigned long long)size, is_64 ? 64 : 32, + is_io ? "I/O" : "MEM", + prefetchable ? "Prefetchable" : ""); + } + + bar_id++; + bar_cnt--; + } + + return 0; +} + +static struct pci_reg_info regs_start[] = { + { "vendor ID", PCI_SIZE_16, PCI_VENDOR_ID }, + { "device ID", PCI_SIZE_16, PCI_DEVICE_ID }, + { "command register ID", PCI_SIZE_16, PCI_COMMAND }, + { "status register", PCI_SIZE_16, PCI_STATUS }, + { "revision ID", PCI_SIZE_8, PCI_REVISION_ID }, + {}, +}; + +static struct pci_reg_info regs_rest[] = { + { "sub class code", PCI_SIZE_8, PCI_CLASS_SUB_CODE }, + { "programming interface", PCI_SIZE_8, PCI_CLASS_PROG }, + { "cache line", PCI_SIZE_8, PCI_CACHE_LINE_SIZE }, + { "latency time", PCI_SIZE_8, PCI_LATENCY_TIMER }, + { "header type", PCI_SIZE_8, PCI_HEADER_TYPE }, + { "BIST", PCI_SIZE_8, PCI_BIST }, + { "base address 0", PCI_SIZE_32, PCI_BASE_ADDRESS_0 }, + {}, +}; + +static struct pci_reg_info regs_normal[] = { + { "base address 1", PCI_SIZE_32, PCI_BASE_ADDRESS_1 }, + { "base address 2", PCI_SIZE_32, PCI_BASE_ADDRESS_2 }, + { "base address 3", PCI_SIZE_32, PCI_BASE_ADDRESS_3 }, + { "base address 4", PCI_SIZE_32, PCI_BASE_ADDRESS_4 }, + { "base address 5", PCI_SIZE_32, PCI_BASE_ADDRESS_5 }, + { "cardBus CIS pointer", PCI_SIZE_32, PCI_CARDBUS_CIS }, + { "sub system vendor ID", PCI_SIZE_16, PCI_SUBSYSTEM_VENDOR_ID }, + { "sub system ID", PCI_SIZE_16, PCI_SUBSYSTEM_ID }, + { "expansion ROM base address", PCI_SIZE_32, PCI_ROM_ADDRESS }, + { "interrupt line", PCI_SIZE_8, PCI_INTERRUPT_LINE }, + { "interrupt pin", PCI_SIZE_8, PCI_INTERRUPT_PIN }, + { "min Grant", PCI_SIZE_8, PCI_MIN_GNT }, + { "max Latency", PCI_SIZE_8, PCI_MAX_LAT }, + {}, +}; + +static struct pci_reg_info regs_bridge[] = { + { "base address 1", PCI_SIZE_32, PCI_BASE_ADDRESS_1 }, + { "primary bus number", PCI_SIZE_8, PCI_PRIMARY_BUS }, + { "secondary bus number", PCI_SIZE_8, PCI_SECONDARY_BUS }, + { "subordinate bus number", PCI_SIZE_8, PCI_SUBORDINATE_BUS }, + { "secondary latency timer", PCI_SIZE_8, PCI_SEC_LATENCY_TIMER }, + { "IO base", PCI_SIZE_8, PCI_IO_BASE }, + { "IO limit", PCI_SIZE_8, PCI_IO_LIMIT }, + { "secondary status", PCI_SIZE_16, PCI_SEC_STATUS }, + { "memory base", PCI_SIZE_16, PCI_MEMORY_BASE }, + { "memory limit", PCI_SIZE_16, PCI_MEMORY_LIMIT }, + { "prefetch memory base", PCI_SIZE_16, PCI_PREF_MEMORY_BASE }, + { "prefetch memory limit", PCI_SIZE_16, PCI_PREF_MEMORY_LIMIT }, + { "prefetch memory base upper", PCI_SIZE_32, PCI_PREF_BASE_UPPER32 }, + { "prefetch memory limit upper", PCI_SIZE_32, PCI_PREF_LIMIT_UPPER32 }, + { "IO base upper 16 bits", PCI_SIZE_16, PCI_IO_BASE_UPPER16 }, + { "IO limit upper 16 bits", PCI_SIZE_16, PCI_IO_LIMIT_UPPER16 }, + { "expansion ROM base address", PCI_SIZE_32, PCI_ROM_ADDRESS1 }, + { "interrupt line", PCI_SIZE_8, PCI_INTERRUPT_LINE }, + { "interrupt pin", PCI_SIZE_8, PCI_INTERRUPT_PIN }, + { "bridge control", PCI_SIZE_16, PCI_BRIDGE_CONTROL }, + {}, +}; + +static struct pci_reg_info regs_cardbus[] = { + { "capabilities", PCI_SIZE_8, PCI_CB_CAPABILITY_LIST }, + { "secondary status", PCI_SIZE_16, PCI_CB_SEC_STATUS }, + { "primary bus number", PCI_SIZE_8, PCI_CB_PRIMARY_BUS }, + { "CardBus number", PCI_SIZE_8, PCI_CB_CARD_BUS }, + { "subordinate bus number", PCI_SIZE_8, PCI_CB_SUBORDINATE_BUS }, + { "CardBus latency timer", PCI_SIZE_8, PCI_CB_LATENCY_TIMER }, + { "CardBus memory base 0", PCI_SIZE_32, PCI_CB_MEMORY_BASE_0 }, + { "CardBus memory limit 0", PCI_SIZE_32, PCI_CB_MEMORY_LIMIT_0 }, + { "CardBus memory base 1", PCI_SIZE_32, PCI_CB_MEMORY_BASE_1 }, + { "CardBus memory limit 1", PCI_SIZE_32, PCI_CB_MEMORY_LIMIT_1 }, + { "CardBus IO base 0", PCI_SIZE_16, PCI_CB_IO_BASE_0 }, + { "CardBus IO base high 0", PCI_SIZE_16, PCI_CB_IO_BASE_0_HI }, + { "CardBus IO limit 0", PCI_SIZE_16, PCI_CB_IO_LIMIT_0 }, + { "CardBus IO limit high 0", PCI_SIZE_16, PCI_CB_IO_LIMIT_0_HI }, + { "CardBus IO base 1", PCI_SIZE_16, PCI_CB_IO_BASE_1 }, + { "CardBus IO base high 1", PCI_SIZE_16, PCI_CB_IO_BASE_1_HI }, + { "CardBus IO limit 1", PCI_SIZE_16, PCI_CB_IO_LIMIT_1 }, + { "CardBus IO limit high 1", PCI_SIZE_16, PCI_CB_IO_LIMIT_1_HI }, + { "interrupt line", PCI_SIZE_8, PCI_INTERRUPT_LINE }, + { "interrupt pin", PCI_SIZE_8, PCI_INTERRUPT_PIN }, + { "bridge control", PCI_SIZE_16, PCI_CB_BRIDGE_CONTROL }, + { "subvendor ID", PCI_SIZE_16, PCI_CB_SUBSYSTEM_VENDOR_ID }, + { "subdevice ID", PCI_SIZE_16, PCI_CB_SUBSYSTEM_ID }, + { "PC Card 16bit base address", PCI_SIZE_32, PCI_CB_LEGACY_MODE_BASE }, + {}, +}; + +/** + * pci_header_show() - Show the header of the specified PCI device. + * + * @dev: Bus+Device+Function number + */ +static void pci_header_show(struct udevice *dev) +{ + unsigned long class, header_type; + + dm_pci_read_config(dev, PCI_CLASS_CODE, &class, PCI_SIZE_8); + dm_pci_read_config(dev, PCI_HEADER_TYPE, &header_type, PCI_SIZE_8); + pci_show_regs(dev, regs_start); + printf(" class code = 0x%.2x (%s)\n", (int)class, + pci_class_str(class)); + pci_show_regs(dev, regs_rest); + + switch (header_type & 0x7f) { + case PCI_HEADER_TYPE_NORMAL: /* "normal" PCI device */ + pci_show_regs(dev, regs_normal); + break; + case PCI_HEADER_TYPE_BRIDGE: /* PCI-to-PCI bridge */ + pci_show_regs(dev, regs_bridge); + break; + case PCI_HEADER_TYPE_CARDBUS: /* PCI-to-CardBus bridge */ + pci_show_regs(dev, regs_cardbus); + break; + + default: + printf("unknown header\n"); + break; + } +} + +static void pciinfo_header(bool short_listing) +{ + if (short_listing) { + printf("BusDevFun VendorId DeviceId Device Class Sub-Class\n"); + printf("_____________________________________________________________\n"); + } +} + +/** + * pci_header_show_brief() - Show the short-form PCI device header + * + * Reads and prints the header of the specified PCI device in short form. + * + * @dev: PCI device to show + */ +static void pci_header_show_brief(struct udevice *dev) +{ + ulong vendor, device; + ulong class, subclass; + + dm_pci_read_config(dev, PCI_VENDOR_ID, &vendor, PCI_SIZE_16); + dm_pci_read_config(dev, PCI_DEVICE_ID, &device, PCI_SIZE_16); + dm_pci_read_config(dev, PCI_CLASS_CODE, &class, PCI_SIZE_8); + dm_pci_read_config(dev, PCI_CLASS_SUB_CODE, &subclass, PCI_SIZE_8); + + printf("0x%.4lx 0x%.4lx %-23s 0x%.2lx\n", + vendor, device, + pci_class_str(class), subclass); +} + +static void pciinfo(struct udevice *bus, bool short_listing, bool multi) +{ + struct udevice *dev; + + if (!multi) + printf("Scanning PCI devices on bus %d\n", dev_seq(bus)); + + if (!multi || dev_seq(bus) == 0) + pciinfo_header(short_listing); + + for (device_find_first_child(bus, &dev); + dev; + device_find_next_child(&dev)) { + struct pci_child_plat *pplat; + + pplat = dev_get_parent_plat(dev); + if (short_listing) { + printf("%02x.%02x.%02x ", dev_seq(bus), + PCI_DEV(pplat->devfn), PCI_FUNC(pplat->devfn)); + pci_header_show_brief(dev); + } else { + printf("\nFound PCI device %02x.%02x.%02x:\n", + dev_seq(bus), + PCI_DEV(pplat->devfn), PCI_FUNC(pplat->devfn)); + pci_header_show(dev); + } + } +} + +/** + * get_pci_dev() - Convert the "bus.device.function" identifier into a number + * + * @name: Device string in the form "bus.device.function" where each is in hex + * Return: encoded pci_dev_t or -1 if the string was invalid + */ +static pci_dev_t get_pci_dev(char *name) +{ + char cnum[12]; + int len, i, iold, n; + int bdfs[3] = {0,0,0}; + + len = strlen(name); + if (len > 8) + return -1; + for (i = 0, iold = 0, n = 0; i < len; i++) { + if (name[i] == '.') { + memcpy(cnum, &name[iold], i - iold); + cnum[i - iold] = '\0'; + bdfs[n++] = hextoul(cnum, NULL); + iold = i + 1; + } + } + strcpy(cnum, &name[iold]); + if (n == 0) + n = 1; + bdfs[n] = hextoul(cnum, NULL); + + return PCI_BDF(bdfs[0], bdfs[1], bdfs[2]); +} + +static int pci_cfg_display(struct udevice *dev, ulong addr, + enum pci_size_t size, ulong length) +{ +#define DISP_LINE_LEN 16 + ulong i, nbytes, linebytes; + int byte_size; + int rc = 0; + + byte_size = pci_byte_size(size); + if (length == 0) + length = 0x40 / byte_size; /* Standard PCI config space */ + + if (addr >= 4096) + return 1; + + /* Print the lines. + * once, and all accesses are with the specified bus width. + */ + nbytes = length * byte_size; + do { + printf("%08lx:", addr); + linebytes = (nbytes > DISP_LINE_LEN) ? DISP_LINE_LEN : nbytes; + for (i = 0; i < linebytes; i += byte_size) { + unsigned long val; + + dm_pci_read_config(dev, addr, &val, size); + printf(" %0*lx", pci_field_width(size), val); + addr += byte_size; + } + printf("\n"); + nbytes -= linebytes; + if (ctrlc()) { + rc = 1; + break; + } + } while (nbytes > 0 && addr < 4096); + + if (rc == 0 && nbytes > 0) + return 1; + + return (rc); +} + +static int pci_cfg_modify(struct udevice *dev, ulong addr, ulong size, + ulong value, int incrflag) +{ + ulong i; + int nbytes; + ulong val; + + if (addr >= 4096) + return 1; + + /* Print the address, followed by value. Then accept input for + * the next value. A non-converted value exits. + */ + do { + printf("%08lx:", addr); + dm_pci_read_config(dev, addr, &val, size); + printf(" %0*lx", pci_field_width(size), val); + + nbytes = cli_readline(" ? "); + if (nbytes == 0 || (nbytes == 1 && console_buffer[0] == '-')) { + /* <CR> pressed as only input, don't modify current + * location and move to next. "-" pressed will go back. + */ + if (incrflag) + addr += nbytes ? -size : size; + nbytes = 1; + /* good enough to not time out */ + bootretry_reset_cmd_timeout(); + } +#ifdef CONFIG_BOOT_RETRY_TIME + else if (nbytes == -2) { + break; /* timed out, exit the command */ + } +#endif + else { + char *endp; + i = hextoul(console_buffer, &endp); + nbytes = endp - console_buffer; + if (nbytes) { + /* good enough to not time out + */ + bootretry_reset_cmd_timeout(); + dm_pci_write_config(dev, addr, i, size); + if (incrflag) + addr += size; + } + } + } while (nbytes && addr < 4096); + + if (nbytes) + return 1; + + return 0; +} + +static const struct pci_flag_info { + uint flag; + const char *name; +} pci_flag_info[] = { + { PCI_REGION_IO, "io" }, + { PCI_REGION_PREFETCH, "prefetch" }, + { PCI_REGION_SYS_MEMORY, "sysmem" }, + { PCI_REGION_RO, "readonly" }, +}; + +static void pci_show_regions(struct udevice *bus) +{ + struct pci_controller *hose = dev_get_uclass_priv(pci_get_controller(bus)); + const struct pci_region *reg; + int i, j; + + if (!hose) { + printf("Bus '%s' is not a PCI controller\n", bus->name); + return; + } + + printf("Buses %02x-%02x\n", hose->first_busno, hose->last_busno); + printf("# %-18s %-18s %-18s %s\n", "Bus start", "Phys start", "Size", + "Flags"); + for (i = 0, reg = hose->regions; i < hose->region_count; i++, reg++) { + printf("%d %#018llx %#018llx %#018llx ", i, + (unsigned long long)reg->bus_start, + (unsigned long long)reg->phys_start, + (unsigned long long)reg->size); + if (!(reg->flags & PCI_REGION_TYPE)) + printf("mem "); + for (j = 0; j < ARRAY_SIZE(pci_flag_info); j++) { + if (reg->flags & pci_flag_info[j].flag) + printf("%s ", pci_flag_info[j].name); + } + printf("\n"); + } +} + +/* PCI Configuration Space access commands + * + * Syntax: + * pci display[.b, .w, .l] bus.device.function} [addr] [len] + * pci next[.b, .w, .l] bus.device.function [addr] + * pci modify[.b, .w, .l] bus.device.function [addr] + * pci write[.b, .w, .l] bus.device.function addr value + */ +static int do_pci(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + ulong addr = 0, value = 0, cmd_size = 0; + enum pci_size_t size = PCI_SIZE_32; + struct udevice *dev, *bus; + int busnum = -1; + pci_dev_t bdf = 0; + char cmd = 's'; + int ret = 0; + char *endp; + + if (argc > 1) + cmd = argv[1][0]; + + switch (cmd) { + case 'd': /* display */ + case 'n': /* next */ + case 'm': /* modify */ + case 'w': /* write */ + /* Check for a size specification. */ + cmd_size = cmd_get_data_size(argv[1], 4); + size = (cmd_size == 4) ? PCI_SIZE_32 : cmd_size - 1; + if (argc > 3) + addr = hextoul(argv[3], NULL); + if (argc > 4) + value = hextoul(argv[4], NULL); + fallthrough; + case 'h': /* header */ + case 'b': /* bars */ + if (argc < 3) + goto usage; + if ((bdf = get_pci_dev(argv[2])) == -1) + return 1; + break; + case 'e': + pci_init(); + return 0; + case 'r': /* no break */ + default: /* scan bus */ + value = 1; /* short listing */ + if (argc > 1) { + if (cmd != 'r' && argv[argc-1][0] == 'l') { + value = 0; + argc--; + } + if (argc > 2 || (argc > 1 && cmd != 'r' && argv[1][0] != 's')) { + if (argv[argc - 1][0] != '*') { + busnum = hextoul(argv[argc - 1], &endp); + if (*endp) + goto usage; + } + argc--; + } + if (cmd == 'r' && argc > 2) + goto usage; + else if (cmd != 'r' && (argc > 2 || (argc == 2 && argv[1][0] != 's'))) + goto usage; + } + if (busnum == -1) { + if (cmd != 'r') { + for (busnum = 0; + uclass_get_device_by_seq(UCLASS_PCI, busnum, &bus) == 0; + busnum++) + pciinfo(bus, value, true); + } else { + for (busnum = 0; + uclass_get_device_by_seq(UCLASS_PCI, busnum, &bus) == 0; + busnum++) { + /* Regions are controller specific so skip non-root buses */ + if (device_is_on_pci_bus(bus)) + continue; + pci_show_regions(bus); + } + } + return 0; + } + ret = uclass_get_device_by_seq(UCLASS_PCI, busnum, &bus); + if (ret) { + printf("No such bus\n"); + return CMD_RET_FAILURE; + } + if (cmd == 'r') + pci_show_regions(bus); + else + pciinfo(bus, value, false); + return 0; + } + + ret = dm_pci_bus_find_bdf(bdf, &dev); + if (ret) { + printf("No such device\n"); + return CMD_RET_FAILURE; + } + + switch (argv[1][0]) { + case 'h': /* header */ + pci_header_show(dev); + break; + case 'd': /* display */ + return pci_cfg_display(dev, addr, size, value); + case 'n': /* next */ + if (argc < 4) + goto usage; + ret = pci_cfg_modify(dev, addr, size, value, 0); + break; + case 'm': /* modify */ + if (argc < 4) + goto usage; + ret = pci_cfg_modify(dev, addr, size, value, 1); + break; + case 'w': /* write */ + if (argc < 5) + goto usage; + ret = dm_pci_write_config(dev, addr, value, size); + break; + case 'b': /* bars */ + return pci_bar_show(dev); + default: + ret = CMD_RET_USAGE; + break; + } + + return ret; + usage: + return CMD_RET_USAGE; +} + +/***************************************************/ + +U_BOOT_LONGHELP(pci, + "[bus|*] [long]\n" + " - short or long list of PCI devices on bus 'bus'\n" + "pci enum\n" + " - Enumerate PCI buses\n" + "pci header b.d.f\n" + " - show header of PCI device 'bus.device.function'\n" + "pci bar b.d.f\n" + " - show BARs base and size for device b.d.f'\n" + "pci regions [bus|*]\n" + " - show PCI regions\n" + "pci display[.b, .w, .l] b.d.f [address] [# of objects]\n" + " - display PCI configuration space (CFG)\n" + "pci next[.b, .w, .l] b.d.f address\n" + " - modify, read and keep CFG address\n" + "pci modify[.b, .w, .l] b.d.f address\n" + " - modify, auto increment CFG address\n" + "pci write[.b, .w, .l] b.d.f address value\n" + " - write to CFG address"); + +U_BOOT_CMD( + pci, 5, 1, do_pci, + "list and access PCI Configuration Space", pci_help_text +); diff --git a/cmd/pci_mps.c b/cmd/pci_mps.c new file mode 100644 index 00000000000..19e71db8cbd --- /dev/null +++ b/cmd/pci_mps.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2022 Microsoft Corporation <www.microsoft.com> + * Stephen Carlson <stcarlso@linux.microsoft.com> + * + * PCI Express Maximum Packet Size (MPS) configuration + */ + +#include <bootretry.h> +#include <cli.h> +#include <command.h> +#include <console.h> +#include <dm.h> +#include <init.h> +#include <asm/processor.h> +#include <asm/io.h> +#include <pci.h> + +#define PCI_MPS_SAFE 0 +#define PCI_MPS_PEER2PEER 1 + +static int pci_mps_find_safe(struct udevice *bus, unsigned int *min_mps, + unsigned int *n) +{ + struct udevice *dev; + int res = 0, addr; + unsigned int mpss; + u32 regval; + + if (!min_mps || !n) + return -EINVAL; + + for (device_find_first_child(bus, &dev); + dev; + device_find_next_child(&dev)) { + addr = dm_pci_find_capability(dev, PCI_CAP_ID_EXP); + if (addr <= 0) + continue; + + res = dm_pci_read_config32(dev, addr + PCI_EXP_DEVCAP, + ®val); + if (res != 0) + return res; + mpss = (unsigned int)(regval & PCI_EXP_DEVCAP_PAYLOAD); + *n += 1; + if (mpss < *min_mps) + *min_mps = mpss; + } + + return res; +} + +static int pci_mps_set_bus(struct udevice *bus, unsigned int target) +{ + struct udevice *dev; + u32 mpss, target_mps = (u32)(target << 5); + u16 mps; + int res = 0, addr; + + for (device_find_first_child(bus, &dev); + dev && res == 0; + device_find_next_child(&dev)) { + addr = dm_pci_find_capability(dev, PCI_CAP_ID_EXP); + if (addr <= 0) + continue; + + res = dm_pci_read_config32(dev, addr + PCI_EXP_DEVCAP, + &mpss); + if (res != 0) + return res; + + /* Do not set device above its maximum MPSS */ + mpss = (mpss & PCI_EXP_DEVCAP_PAYLOAD) << 5; + if (target_mps < mpss) + mps = (u16)target_mps; + else + mps = (u16)mpss; + res = dm_pci_clrset_config16(dev, addr + PCI_EXP_DEVCTL, + PCI_EXP_DEVCTL_PAYLOAD, mps); + } + + return res; +} + +/* + * Sets the MPS of each PCI Express device to the specified policy. + */ +static int pci_mps_set(int policy) +{ + struct udevice *bus; + int i, res = 0; + /* 0 = 128B, min value for hotplug */ + unsigned int mps = 0; + + if (policy == PCI_MPS_SAFE) { + unsigned int min_mps = PCI_EXP_DEVCAP_PAYLOAD_4096B, n = 0; + + /* Find maximum MPS supported by all devices */ + for (i = 0; + uclass_get_device_by_seq(UCLASS_PCI, i, &bus) == 0 && + res == 0; + i++) + res = pci_mps_find_safe(bus, &min_mps, &n); + + /* If no devices were found, do not reconfigure */ + if (n == 0) + return res; + mps = min_mps; + } + + /* This message is checked by the sandbox test */ + printf("Setting MPS of all devices to %uB\n", 128U << mps); + for (i = 0; + uclass_get_device_by_seq(UCLASS_PCI, i, &bus) == 0 && res == 0; + i++) + res = pci_mps_set_bus(bus, mps); + + return res; +} + +/* + * PCI MPS tuning commands + * + * Syntax: + * pci_mps safe + * pci_mps peer2peer + */ +static int do_pci_mps(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char cmd = 'u'; + int ret = 0; + + if (argc > 1) + cmd = argv[1][0]; + + switch (cmd) { + case 's': /* safe */ + ret = pci_mps_set(PCI_MPS_SAFE); + break; + case 'p': /* peer2peer/hotplug */ + ret = pci_mps_set(PCI_MPS_PEER2PEER); + break; + default: /* usage, help */ + goto usage; + } + + return ret; +usage: + return CMD_RET_USAGE; +} + +/***************************************************/ + +U_BOOT_LONGHELP(pci_mps, + "safe\n" + " - Set PCI Express MPS of all devices to safe values\n" + "pci_mps peer2peer\n" + " - Set PCI Express MPS of all devices to support hotplug and peer-to-peer DMA\n"); + +U_BOOT_CMD(pci_mps, 2, 0, do_pci_mps, + "configure PCI Express MPS", pci_mps_help_text); diff --git a/cmd/pinmux.c b/cmd/pinmux.c new file mode 100644 index 00000000000..01f3e4af6ce --- /dev/null +++ b/cmd/pinmux.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + */ + +#include <command.h> +#include <dm.h> +#include <errno.h> +#include <dm/pinctrl.h> +#include <dm/uclass-internal.h> + +#define LIMIT_DEVNAME 30 + +static struct udevice *currdev; + +static int do_dev(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *name; + int ret; + + switch (argc) { + case 2: + name = argv[1]; + ret = uclass_get_device_by_name(UCLASS_PINCTRL, name, &currdev); + if (ret) { + printf("Can't get the pin-controller: %s!\n", name); + return CMD_RET_FAILURE; + } + /* fall through */ + case 1: + if (!currdev) { + printf("Pin-controller device is not set!\n"); + return CMD_RET_USAGE; + } + + printf("dev: %s\n", currdev->name); + } + + return CMD_RET_SUCCESS; +} + +/** + * Print the muxing information for one or all pins of one pinctrl device + * + * @param dev pinctrl device + * @param name NULL to display all the pins + * or name of the pin to display + * Return: 0 on success, non-0 on error + */ +static int show_pinmux(struct udevice *dev, char *name) +{ + char pin_name[PINNAME_SIZE]; + char pin_mux[PINMUX_SIZE]; + int pins_count; + int i; + int ret; + bool found = false; + + pins_count = pinctrl_get_pins_count(dev); + + if (pins_count == -ENOSYS) { + printf("Ops get_pins_count not supported by %s\n", dev->name); + return pins_count; + } + + for (i = 0; i < pins_count; i++) { + ret = pinctrl_get_pin_name(dev, i, pin_name, PINNAME_SIZE); + if (ret) { + printf("Ops get_pin_name error (%d) by %s\n", ret, dev->name); + return ret; + } + if (name && strcmp(name, pin_name)) + continue; + found = true; + ret = pinctrl_get_pin_muxing(dev, i, pin_mux, PINMUX_SIZE); + if (ret) { + printf("Ops get_pin_muxing error (%d) by %s in %s\n", + ret, pin_name, dev->name); + return ret; + } + + printf("%-*s: %-*s\n", PINNAME_SIZE, pin_name, + PINMUX_SIZE, pin_mux); + } + + if (!found) + return -ENOENT; + + return 0; +} + +static int do_status(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + char *name; + int ret; + + if (argc < 2) { + if (!currdev) { + printf("pin-controller device not selected\n"); + return CMD_RET_FAILURE; + } + show_pinmux(currdev, NULL); + return CMD_RET_SUCCESS; + } + + if (strcmp(argv[1], "-a")) + name = argv[1]; + else + name = NULL; + + uclass_foreach_dev_probe(UCLASS_PINCTRL, dev) { + if (!name) { + /* insert a separator between each pin-controller display */ + printf("--------------------------\n"); + printf("%s:\n", dev->name); + } + ret = show_pinmux(dev, name); + /* stop when the status of requested pin is displayed */ + if (name && !ret) + return CMD_RET_SUCCESS; + } + + if (name) { + printf("%s not found\n", name); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + + printf("| %-*.*s| %-*.*s| %s\n", + LIMIT_DEVNAME, LIMIT_DEVNAME, "Device", + LIMIT_DEVNAME, LIMIT_DEVNAME, "Driver", + "Parent"); + + uclass_foreach_dev_probe(UCLASS_PINCTRL, dev) { + printf("| %-*.*s| %-*.*s| %s\n", + LIMIT_DEVNAME, LIMIT_DEVNAME, dev->name, + LIMIT_DEVNAME, LIMIT_DEVNAME, dev->driver->name, + dev->parent->name); + } + + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl pinmux_subcmd[] = { + U_BOOT_CMD_MKENT(dev, 2, 1, do_dev, "", ""), + U_BOOT_CMD_MKENT(list, 1, 1, do_list, "", ""), + U_BOOT_CMD_MKENT(status, 2, 1, do_status, "", ""), +}; + +static int do_pinmux(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cmd; + + argc--; + argv++; + + cmd = find_cmd_tbl(argv[0], pinmux_subcmd, ARRAY_SIZE(pinmux_subcmd)); + if (!cmd || argc > cmd->maxargs) + return CMD_RET_USAGE; + + return cmd->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD(pinmux, CONFIG_SYS_MAXARGS, 1, do_pinmux, + "show pin-controller muxing", + "list - list UCLASS_PINCTRL devices\n" + "pinmux dev [pincontroller-name] - select pin-controller device\n" + "pinmux status [-a | pin-name] - print pin-controller muxing [for all | for pin-name]\n" +); diff --git a/cmd/pmc.c b/cmd/pmc.c new file mode 100644 index 00000000000..1a3416fb2a9 --- /dev/null +++ b/cmd/pmc.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel PMC command + * + * Copyright 2019 Google LLC + */ + +#include <command.h> +#include <dm.h> +#include <power/acpi_pmc.h> + +static int get_pmc_dev(struct udevice **devp) +{ + struct udevice *dev; + int ret; + + ret = uclass_first_device_err(UCLASS_ACPI_PMC, &dev); + if (ret) { + printf("Could not find device (err=%d)\n", ret); + return ret; + } + ret = pmc_init(dev); + if (ret) { + printf("Could not init device (err=%d)\n", ret); + return ret; + } + *devp = dev; + + return 0; +} + +static int do_pmc_init(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret; + + ret = get_pmc_dev(&dev); + if (ret) + return CMD_RET_FAILURE; + + return 0; +} + +static int do_pmc_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret; + + ret = get_pmc_dev(&dev); + if (ret) + return CMD_RET_FAILURE; + pmc_dump_info(dev); + + return 0; +} + +static struct cmd_tbl cmd_pmc_sub[] = { + U_BOOT_CMD_MKENT(init, 0, 1, do_pmc_init, "", ""), + U_BOOT_CMD_MKENT(info, 0, 1, do_pmc_info, "", ""), +}; + +static int do_pmc(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + const struct cmd_tbl *cp; + + if (argc < 2) /* no subcommand */ + return cmd_usage(cmdtp); + + cp = find_cmd_tbl(argv[1], &cmd_pmc_sub[0], ARRAY_SIZE(cmd_pmc_sub)); + if (!cp) + return CMD_RET_USAGE; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD( + pmc, 2, 1, do_pmc, "Power-management controller info", + "info - read state and show info about the PMC\n" + "pmc init - read state from the PMC\n" + ); diff --git a/cmd/pmic.c b/cmd/pmic.c new file mode 100644 index 00000000000..e5fc8f97b75 --- /dev/null +++ b/cmd/pmic.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2014-2015 Samsung Electronics + * Przemyslaw Marczak <p.marczak@samsung.com> + */ +#include <command.h> +#include <errno.h> +#include <dm.h> +#include <dm/uclass-internal.h> +#include <power/pmic.h> + +#define LIMIT_DEV 32 +#define LIMIT_PARENT 20 + +static struct udevice *currdev; + +static int failure(int ret) +{ + printf("Error: %d (%s)\n", ret, errno_str(ret)); + + return CMD_RET_FAILURE; +} + +static int do_dev(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *name; + int ret = -ENODEV; + + switch (argc) { + case 2: + name = argv[1]; + ret = pmic_get(name, &currdev); + if (ret) { + printf("Can't get PMIC: %s!\n", name); + return failure(ret); + } + fallthrough; + case 1: + if (!currdev) { + printf("PMIC device is not set!\n\n"); + return CMD_RET_USAGE; + } + + printf("dev: %d @ %s\n", dev_seq(currdev), currdev->name); + } + + return CMD_RET_SUCCESS; +} + +static int do_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret, err = 0; + + printf("| %-*.*s| %-*.*s| %s @ %s\n", + LIMIT_DEV, LIMIT_DEV, "Name", + LIMIT_PARENT, LIMIT_PARENT, "Parent name", + "Parent uclass", "seq"); + + for (ret = uclass_first_device_check(UCLASS_PMIC, &dev); dev; + ret = uclass_next_device_check(&dev)) { + if (ret) + err = ret; + + printf("| %-*.*s| %-*.*s| %s @ %d | status: %i\n", + LIMIT_DEV, LIMIT_DEV, dev->name, + LIMIT_PARENT, LIMIT_PARENT, dev->parent->name, + dev_get_uclass_name(dev->parent), dev_seq(dev->parent), + ret); + } + + if (err) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +static int do_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct uc_pmic_priv *priv; + struct udevice *dev; + char fmt[16]; + uint reg; + int ret; + + if (!currdev) { + printf("First, set the PMIC device!\n"); + return CMD_RET_USAGE; + } + + dev = currdev; + priv = dev_get_uclass_priv(dev); + printf("Dump pmic: %s registers\n", dev->name); + + sprintf(fmt, "%%%d.%dx ", priv->trans_len * 2, + priv->trans_len * 2); + + for (reg = 0; reg < pmic_reg_count(dev); reg++) { + ret = pmic_reg_read(dev, reg); + if (ret < 0 && ret != -ENODATA) { + printf("Can't read register: %d\n", reg); + return failure(ret); + } + + if (!(reg % 16)) + printf("\n0x%02x: ", reg); + + if (ret == -ENODATA) { + int i; + + for (i = 0; i < priv->trans_len; i++) + puts("--"); + puts(" "); + } else { + printf(fmt, ret); + } + } + printf("\n"); + + return CMD_RET_SUCCESS; +} + +static int do_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct uc_pmic_priv *priv; + struct udevice *dev; + int regs, ret; + char fmt[24]; + uint reg; + + if (!currdev) { + printf("First, set the PMIC device!\n"); + return CMD_RET_USAGE; + } + + dev = currdev; + priv = dev_get_uclass_priv(dev); + + if (argc != 2) + return CMD_RET_USAGE; + + reg = simple_strtoul(argv[1], NULL, 0); + regs = pmic_reg_count(dev); + if (reg > regs) { + printf("PMIC max reg: %d\n", regs); + return failure(-EFAULT); + } + + ret = pmic_reg_read(dev, reg); + if (ret < 0) { + printf("Can't read PMIC register: %d!\n", reg); + return failure(ret); + } + + sprintf(fmt, "0x%%02x: 0x%%%d.%dx\n", priv->trans_len * 2, + priv->trans_len * 2); + printf(fmt, reg, ret); + + return CMD_RET_SUCCESS; +} + +static int do_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + uint reg, value; + int regs, ret; + + if (!currdev) { + printf("First, set the PMIC device!\n"); + return CMD_RET_USAGE; + } + + dev = currdev; + + if (argc != 3) + return CMD_RET_USAGE; + + reg = simple_strtoul(argv[1], NULL, 0); + regs = pmic_reg_count(dev); + if (reg > regs) { + printf("PMIC max reg: %d\n", regs); + return failure(-EFAULT); + } + + value = simple_strtoul(argv[2], NULL, 0); + + ret = pmic_reg_write(dev, reg, value); + if (ret) { + printf("Can't write PMIC register: %d!\n", reg); + return failure(ret); + } + + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl subcmd[] = { + U_BOOT_CMD_MKENT(dev, 2, 1, do_dev, "", ""), + U_BOOT_CMD_MKENT(list, 1, 1, do_list, "", ""), + U_BOOT_CMD_MKENT(dump, 1, 1, do_dump, "", ""), + U_BOOT_CMD_MKENT(read, 2, 1, do_read, "", ""), + U_BOOT_CMD_MKENT(write, 3, 1, do_write, "", ""), +}; + +static int do_pmic(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cmd; + + argc--; + argv++; + + cmd = find_cmd_tbl(argv[0], subcmd, ARRAY_SIZE(subcmd)); + if (cmd == NULL || argc > cmd->maxargs) + return CMD_RET_USAGE; + + return cmd->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD(pmic, CONFIG_SYS_MAXARGS, 1, do_pmic, + "PMIC sub-system", + "list - list pmic devices\n" + "pmic dev [name] - show or [set] operating PMIC device\n" + "pmic dump - dump registers\n" + "pmic read <reg> - read byte of 'reg' register\n" + "pmic write <reg> <byte> - write 'byte' byte to 'reg' register\n" +); diff --git a/cmd/printf.c b/cmd/printf.c new file mode 100644 index 00000000000..a1727ac15a2 --- /dev/null +++ b/cmd/printf.c @@ -0,0 +1,647 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2021 Weidmüller Interface GmbH & Co. KG + * Roland Gaudig <roland.gaudig@weidmueller.com> + * + * Copyright 1999 Dave Cinege + * Portions copyright (C) 1990-1996 Free Software Foundation, Inc. + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ +/* + * This file provides a shell printf like format string expansion as required + * for the setexpr <name> fmt <format> <value> command. + * This source file was mostly taken from the BusyBox project (www.busybox.net) + * In contrast to the original sources the output is not written to stdout + * anymore but into a char array, which can be used as input for the env_set() + * function. + */ +/* Usage: printf format [argument...] + * + * A front end to the printf function that lets it be used from the shell. + * + * Backslash escapes: + * + * \" = double quote + * \\ = backslash + * \a = alert (bell) + * \b = backspace + * \c = produce no further output + * \f = form feed + * \n = new line + * \r = carriage return + * \t = horizontal tab + * \v = vertical tab + * \0ooo = octal number (ooo is 0 to 3 digits) + * \xhhh = hexadecimal number (hhh is 1 to 3 digits) + * + * Additional directive: + * + * %b = print an argument string, interpreting backslash escapes + * + * The 'format' argument is re-used as many times as necessary + * to convert all of the given arguments. + * + * David MacKenzie <djm@gnu.ai.mit.edu> + */ +/* 19990508 Busy Boxed! Dave Cinege */ + +//config:config PRINTF +//config: bool "printf (3.8 kb)" +//config: default y +//config: help +//config: printf is used to format and print specified strings. +//config: It's similar to 'echo' except it has more options. + +//applet:IF_PRINTF(APPLET_NOFORK(printf, printf, BB_DIR_USR_BIN, BB_SUID_DROP, printf)) + +//kbuild:lib-$(CONFIG_PRINTF) += printf.o +//kbuild:lib-$(CONFIG_ASH_PRINTF) += printf.o +//kbuild:lib-$(CONFIG_HUSH_PRINTF) += printf.o + +//usage:#define printf_trivial_usage +//usage: "FORMAT [ARG]..." +//usage:#define printf_full_usage "\n\n" +//usage: "Format and print ARG(s) according to FORMAT (a-la C printf)" +//usage: +//usage:#define printf_example_usage +//usage: "$ printf \"Val=%d\\n\" 5\n" +//usage: "Val=5\n" + +/* A note on bad input: neither bash 3.2 nor coreutils 6.10 stop on it. + * They report it: + * bash: printf: XXX: invalid number + * printf: XXX: expected a numeric value + * bash: printf: 123XXX: invalid number + * printf: 123XXX: value not completely converted + * but then they use 0 (or partially converted numeric prefix) as a value + * and continue. They exit with 1 in this case. + * Both accept insane field width/precision (e.g. %9999999999.9999999999d). + * Both print error message and assume 0 if %*.*f width/precision is "bad" + * (but negative numbers are not "bad"). + * Both accept negative numbers for %u specifier. + * + * We try to be compatible. + */ + +#include <ctype.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <vsprintf.h> + +#define WANT_HEX_ESCAPES 0 +#define PRINT_CONVERSION_ERROR 1 +#define PRINT_TRUNCATED_ERROR 2 +#define PRINT_SIZE_ERROR 4 + +struct print_inf { + char *str; + size_t size; + size_t offset; + unsigned int error; +}; + +typedef void (*converter)(const char *arg, void *result); + +/** + * printf_str() - print formatted into char array with length checks + * + * This function povides a printf like function for printing into a char array + * with checking the boundaries. + * Unlike snprintf, all checks are performed inside this function and status + * reports are stored inside the print_inf struct. That way, this function can + * be used almost as drop-in replacement without needing much code changes. + * Unlike snprintf errors are not reported by return value, but inside the + * error member of struct print_inf. The output stored inside the struct + * print_inf str member shall only be used when the error member is 0. + * + * @inf: Info structure for print operation + * @char: format string with optional arguments + */ +static void printf_str(struct print_inf *inf, char *format, ...) +{ + va_list args; + int i; + + if (!inf) + return; + + /* Do not write anything if previous error is pending */ + if (inf->error) + return; + + /* Check if end of receiving buffer is already reached */ + if (inf->offset >= inf->size) { + inf->error |= PRINT_SIZE_ERROR; + return; + } + + size_t remaining = inf->size - inf->offset; + + va_start(args, format); + i = vsnprintf(inf->str + inf->offset, remaining, format, args); + va_end(args); + + if (i < 0) + inf->error |= PRINT_CONVERSION_ERROR; + else if ((unsigned int)i >= remaining) + inf->error |= PRINT_TRUNCATED_ERROR; + else + inf->offset += i; +} + +/** + * putchar_str() - Print single character into char array with length checks + * + * This function provices a putchar like function, which stores the output + * into a char array with checking boundaries. + * + * @inf: Info structure for print operation + * @char: Single character to be printed + */ +static void putchar_str(struct print_inf *inf, char c) +{ + printf_str(inf, "%c", c); +} + +static char process_escape_sequence(const char **ptr) +{ + const char *q; + unsigned int num_digits; + unsigned int n; + unsigned int base; + + num_digits = 0; + n = 0; + base = 8; + q = *ptr; + + if (WANT_HEX_ESCAPES && *q == 'x') { + ++q; + base = 16; + ++num_digits; + } + + /* bash requires leading 0 in octal escapes: + * \02 works, \2 does not (prints \ and 2). + * We treat \2 as a valid octal escape sequence. + */ + do { + unsigned int r; + unsigned int d = (unsigned char)(*q) - '0'; +#if WANT_HEX_ESCAPES + if (d >= 10) { + d = (unsigned char)tolower(*q) - 'a'; + //d += 10; + /* The above would map 'A'-'F' and 'a'-'f' to 10-15, + * however, some chars like '@' would map to 9 < base. + * Do not allow that, map invalid chars to N > base: + */ + if ((int)d >= 0) + d += 10; + } +#endif + if (d >= base) { + if (WANT_HEX_ESCAPES && base == 16) { + --num_digits; + if (num_digits == 0) { + /* \x<bad_char>: return '\', + * leave ptr pointing to x + */ + return '\\'; + } + } + break; + } + + r = n * base + d; + if (r > 255) + break; + + n = r; + ++q; + } while (++num_digits < 3); + + if (num_digits == 0) { + /* Not octal or hex escape sequence. + * Is it one-letter one? + */ + /* bash builtin "echo -e '\ec'" interprets \e as ESC, + * but coreutils "/bin/echo -e '\ec'" does not. + * Manpages tend to support coreutils way. + * Update: coreutils added support for \e on 28 Oct 2009. + */ + static const char charmap[] = { + 'a', 'b', 'e', 'f', 'n', 'r', 't', 'v', '\\', '\0', + '\a', '\b', 27, '\f', '\n', '\r', '\t', '\v', '\\', '\\', + }; + + const char *p = charmap; + + do { + if (*p == *q) { + q++; + break; + } + } while (*++p != '\0'); + /* p points to found escape char or NUL, + * advance it and find what it translates to. + * Note that \NUL and unrecognized sequence \z return '\' + * and leave ptr pointing to NUL or z. + */ + n = p[sizeof(charmap) / 2]; + } + + *ptr = q; + + return (char)n; +} + +static char *skip_whitespace(const char *s) +{ + /* In POSIX/C locale (the only locale we care about: do we REALLY want + * to allow Unicode whitespace in, say, .conf files? nuts!) + * isspace is only these chars: "\t\n\v\f\r" and space. + * "\t\n\v\f\r" happen to have ASCII codes 9,10,11,12,13. + * Use that. + */ + while (*s == ' ' || (unsigned char)(*s - 9) <= (13 - 9)) + s++; + + return (char *)s; +} + +/* Like strcpy but can copy overlapping strings. */ +static void overlapping_strcpy(char *dst, const char *src) +{ + /* Cheap optimization for dst == src case - + * better to have it here than in many callers. + */ + if (dst != src) { + while ((*dst = *src) != '\0') { + dst++; + src++; + } + } +} + +static int multiconvert(const char *arg, void *result, converter convert) +{ + if (*arg == '"' || *arg == '\'') + sprintf((char *)arg + strlen(arg), "%u", (unsigned char)arg[1]); + //errno = 0; + convert(arg, result); + /* Unlike their Posix counterparts, simple_strtoll and + * simple_strtoull do not set errno + * + * if (errno) { + * printf("error invalid number '%s'", arg); + * return 1; + * } + */ + return 0; +} + +static void conv_strtoull(const char *arg, void *result) +{ + /* both coreutils 6.10 and bash 3.2: + * $ printf '%x\n' -2 + * fffffffffffffffe + * Mimic that: + */ + if (arg[0] == '-') { + *(unsigned long long *)result = simple_strtoll(arg, NULL, 16); + return; + } + /* Allow leading '+' - simple_strtoull() by itself does not allow it, + * and probably shouldn't (other callers might require purely numeric + * inputs to be allowed. + */ + if (arg[0] == '+') + arg++; + *(unsigned long long *)result = simple_strtoull(arg, NULL, 16); +} + +static void conv_strtoll(const char *arg, void *result) +{ + if (arg[0] == '+') + arg++; + *(long long *)result = simple_strtoll(arg, NULL, 16); +} + +/* Callers should check errno to detect errors */ +static unsigned long long my_xstrtoull(const char *arg) +{ + unsigned long long result; + + if (multiconvert(arg, &result, conv_strtoull)) + result = 0; + return result; +} + +static long long my_xstrtoll(const char *arg) +{ + long long result; + + if (multiconvert(arg, &result, conv_strtoll)) + result = 0; + return result; +} + +/* Handles %b; return 1 if output is to be short-circuited by \c */ +static int print_esc_string(struct print_inf *inf, const char *str) +{ + char c; + + while ((c = *str) != '\0') { + str++; + if (c == '\\') { + /* %b also accepts 4-digit octals of the form \0### */ + if (*str == '0') { + if ((unsigned char)(str[1] - '0') < 8) { + /* 2nd char is 0..7: skip leading '0' */ + str++; + } + } else if (*str == 'c') { + return 1; + } + { + /* optimization: don't force arg to be on-stack, + * use another variable for that. + */ + const char *z = str; + + c = process_escape_sequence(&z); + str = z; + } + } + putchar_str(inf, c); + } + + return 0; +} + +static void print_direc(struct print_inf *inf, char *format, unsigned int fmt_length, + int field_width, int precision, + const char *argument) +{ + long long llv; + char saved; + char *have_prec, *have_width; + + saved = format[fmt_length]; + format[fmt_length] = '\0'; + + have_prec = strstr(format, ".*"); + have_width = strchr(format, '*'); + if (have_width - 1 == have_prec) + have_width = NULL; + + /* multiconvert sets errno = 0, but %s needs it cleared */ + errno = 0; + + switch (format[fmt_length - 1]) { + case 'c': + printf_str(inf, format, *argument); + break; + case 'd': + case 'i': + llv = my_xstrtoll(skip_whitespace(argument)); + print_long: + if (!have_width) { + if (!have_prec) + printf_str(inf, format, llv); + else + printf_str(inf, format, precision, llv); + } else { + if (!have_prec) + printf_str(inf, format, field_width, llv); + else + printf_str(inf, format, field_width, precision, llv); + } + break; + case 'o': + case 'u': + case 'x': + case 'X': + llv = my_xstrtoull(skip_whitespace(argument)); + /* cheat: unsigned long and long have same width, so... */ + goto print_long; + case 's': + /* Are char* and long long the same? */ + if (sizeof(argument) == sizeof(llv)) { + llv = (long long)(ptrdiff_t)argument; + goto print_long; + } else { + /* Hope compiler will optimize it out by moving call + * instruction after the ifs... + */ + if (!have_width) { + if (!have_prec) + printf_str(inf, format, argument, + /*unused:*/ argument, argument); + else + printf_str(inf, format, precision, + argument, /*unused:*/ argument); + } else { + if (!have_prec) + printf_str(inf, format, field_width, + argument, /*unused:*/ argument); + else + printf_str(inf, format, field_width, + precision, argument); + } + break; + } + break; + } /* switch */ + + format[fmt_length] = saved; +} + +/* Handle params for "%*.*f". Negative numbers are ok (compat). */ +static int get_width_prec(const char *str) +{ + long v = simple_strtol(str, NULL, 10); + + /* Unlike its Posix counterpart, simple_strtol does not set errno + * + * if (errno) { + * printf("error invalid number '%s'", str); + * v = 0; + * } + */ + return (int)v; +} + +/* Print the text in FORMAT, using ARGV for arguments to any '%' directives. + * Return advanced ARGV. + */ +static char **print_formatted(struct print_inf *inf, char *f, char **argv, int *conv_err) +{ + char *direc_start; /* Start of % directive. */ + unsigned int direc_length; /* Length of % directive. */ + int field_width; /* Arg to first '*' */ + int precision; /* Arg to second '*' */ + char **saved_argv = argv; + + for (; *f; ++f) { + switch (*f) { + case '%': + direc_start = f++; + direc_length = 1; + field_width = 0; + precision = 0; + if (*f == '%') { + putchar_str(inf, '%'); + break; + } + if (*f == 'b') { + if (*argv) { + if (print_esc_string(inf, *argv)) + return saved_argv; /* causes main() to exit */ + ++argv; + } + break; + } + if (*f && strchr("-+ #", *f)) { + ++f; + ++direc_length; + } + if (*f == '*') { + ++f; + ++direc_length; + if (*argv) + field_width = get_width_prec(*argv++); + } else { + while (isdigit(*f)) { + ++f; + ++direc_length; + } + } + if (*f == '.') { + ++f; + ++direc_length; + if (*f == '*') { + ++f; + ++direc_length; + if (*argv) + precision = get_width_prec(*argv++); + } else { + while (isdigit(*f)) { + ++f; + ++direc_length; + } + } + } + + /* Remove "lLhz" size modifiers, repeatedly. + * bash does not like "%lld", but coreutils + * happily takes even "%Llllhhzhhzd"! + * We are permissive like coreutils + */ + while ((*f | 0x20) == 'l' || *f == 'h' || *f == 'z') + overlapping_strcpy(f, f + 1); + /* Add "ll" if integer modifier, then print */ + { + static const char format_chars[] = "diouxXcs"; + char *p = strchr(format_chars, *f); + /* needed - try "printf %" without it */ + if (!p || *f == '\0') { + printf("`%s': invalid format\n", direc_start); + /* causes main() to exit with error */ + return saved_argv - 1; + } + ++direc_length; + if (p - format_chars <= 5) { + /* it is one of "diouxX" */ + p = malloc(direc_length + 3); + if (!p) { + /* exit with error */ + return saved_argv - 1; + } + memcpy(p, direc_start, direc_length); + p[direc_length + 1] = p[direc_length - 1]; + p[direc_length - 1] = 'l'; + p[direc_length] = 'l'; + //bb_error_msg("<%s>", p); + direc_length += 2; + direc_start = p; + } else { + p = NULL; + } + if (*argv) { + print_direc(inf, direc_start, direc_length, + field_width, precision, *argv++); + } else { + print_direc(inf, direc_start, direc_length, + field_width, precision, ""); + } + *conv_err |= errno; + free(p); + } + break; + case '\\': + if (*++f == 'c') + return saved_argv; /* causes main() to exit */ + putchar_str(inf, process_escape_sequence((const char **)&f)); + f--; + break; + default: + putchar_str(inf, *f); + } + } + + return argv; +} + +/** + * printf_setexpr() - Implements the setexpr <name> fmt <format> command + * + * This function implements the format string evaluation for the + * setexpr <name> fmt <format> <value> command. + * + * @str: Output string of the evaluated expression + * @size: Length of @str buffer + * @argc: Number of arguments + * @argv: Argument list + * @return: 0 if OK, 1 on error + */ +int printf_setexpr(char *str, size_t size, int argc, char *const *argv) +{ + int conv_err; + char *format; + char **argv2; + struct print_inf inf = { + .str = str, + .size = size, + .offset = 0, + .error = 0, + }; + + if (!str || !size) + return 1; + + inf.str[0] = '\0'; + + format = argv[0]; + argv2 = (char **)argv + 1; + + conv_err = 0; + argv = argv2; + /* In case any print_str call raises an error inf.error will be + * set after print_formatted returns. + */ + argv2 = print_formatted(&inf, format, (char **)argv, &conv_err); + + /* coreutils compat (bash doesn't do this): + *if (*argv) + * fprintf(stderr, "excess args ignored"); + */ + + return (argv2 < argv) || /* if true, print_formatted errored out */ + conv_err || /* print_formatted saw invalid number */ + inf.error; /* print_str reported error */ +} diff --git a/cmd/printf.h b/cmd/printf.h new file mode 100644 index 00000000000..dcaff6d0972 --- /dev/null +++ b/cmd/printf.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef __PRINTF_H +#define __PRINTF_H + +int printf_setexpr(char *str, size_t size, int argc, char *const *argv); + +#endif diff --git a/cmd/pstore.c b/cmd/pstore.c new file mode 100644 index 00000000000..9795eea2dbc --- /dev/null +++ b/cmd/pstore.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright © 2019 Collabora Ltd + */ + +#include <config.h> +#include <command.h> +#include <fdtdec.h> +#include <fs.h> +#include <log.h> +#include <mapmem.h> +#include <memalign.h> +#include <part.h> +#include <fdt_support.h> + +struct persistent_ram_buffer { + u32 sig; + u32 start; + u32 size; + u8 data[0]; +}; + +#define PERSISTENT_RAM_SIG (0x43474244) /* DBGC */ +#define RAMOOPS_KERNMSG_HDR "====" + +#define PSTORE_TYPE_DMESG 0 +#define PSTORE_TYPE_CONSOLE 2 +#define PSTORE_TYPE_FTRACE 3 +#define PSTORE_TYPE_PMSG 7 +#define PSTORE_TYPE_ALL 255 + +static phys_addr_t pstore_addr = CONFIG_CMD_PSTORE_MEM_ADDR; +static phys_size_t pstore_length = CONFIG_CMD_PSTORE_MEM_SIZE; +static unsigned int pstore_record_size = CONFIG_CMD_PSTORE_RECORD_SIZE; +static unsigned int pstore_console_size = CONFIG_CMD_PSTORE_CONSOLE_SIZE; +static unsigned int pstore_ftrace_size = CONFIG_CMD_PSTORE_FTRACE_SIZE; +static unsigned int pstore_pmsg_size = CONFIG_CMD_PSTORE_PMSG_SIZE; +static unsigned int pstore_ecc_size = CONFIG_CMD_PSTORE_ECC_SIZE; +static unsigned int buffer_size; + + /** + * pstore_read_kmsg_hdr() - Check kernel header and get compression flag if + * available. + * @buffer: Kernel messages buffer. + * @compressed: Returns TRUE if kernel buffer is compressed, else FALSE. + * + * Check if buffer starts with a kernel header of the form: + * ====<secs>.<nsecs>[-<compression>]\n + * If <compression> is equal to 'C' then the buffer is compressed, else iter + * should be 'D'. + * + * Return: Length of kernel header. + */ +static int pstore_read_kmsg_hdr(char *buffer, bool *compressed) +{ + char *ptr = buffer; + *compressed = false; + + if (strncmp(RAMOOPS_KERNMSG_HDR, ptr, strlen(RAMOOPS_KERNMSG_HDR)) != 0) + return 0; + + ptr += strlen(RAMOOPS_KERNMSG_HDR); + + ptr = strchr(ptr, '\n'); + if (!ptr) + return 0; + + if (ptr[-2] == '-' && ptr[-1] == 'C') + *compressed = true; + + return ptr - buffer + 1; +} + +/** + * pstore_get_buffer() - Get unwrapped record buffer + * @sig: Signature to check + * @buffer: Buffer containing wrapped record + * @size: wrapped record size + * @dest: Buffer used to store unwrapped record + * + * The record starts with <signature><start><size> header. + * The signature is 'DBGC' for all records except for Ftrace's record(s) wich + * use LINUX_VERSION_CODE ^ 'DBGC'. + * Use 0 for @sig to prevent checking signature. + * Start and size are 4 bytes long. + * + * Return: record's length + */ +static u32 pstore_get_buffer(u32 sig, phys_addr_t buffer, u32 size, char *dest) +{ + struct persistent_ram_buffer *prb = + (struct persistent_ram_buffer *)map_sysmem(buffer, size); + u32 dest_size; + + if (sig == 0 || prb->sig == sig) { + if (prb->size == 0) { + log_debug("found existing empty buffer\n"); + return 0; + } + + if (prb->size > size) { + log_debug("found existing invalid buffer, size %u, start %u\n", + prb->size, prb->start); + return 0; + } + } else { + log_debug("no valid data in buffer (sig = 0x%08x)\n", prb->sig); + return 0; + } + + log_debug("found existing buffer, size %u, start %u\n", + prb->size, prb->start); + + memcpy(dest, &prb->data[prb->start], prb->size - prb->start); + memcpy(dest + prb->size - prb->start, &prb->data[0], prb->start); + + dest_size = prb->size; + unmap_sysmem(prb); + + return dest_size; +} + +/** + * pstore_init_buffer_size() - Init buffer size to largest record size + * + * Records, console, FTrace and user logs can use different buffer sizes. + * This function allows to retrieve the biggest one. + */ +static void pstore_init_buffer_size(void) +{ + if (pstore_record_size > buffer_size) + buffer_size = pstore_record_size; + + if (pstore_console_size > buffer_size) + buffer_size = pstore_console_size; + + if (pstore_ftrace_size > buffer_size) + buffer_size = pstore_ftrace_size; + + if (pstore_pmsg_size > buffer_size) + buffer_size = pstore_pmsg_size; +} + +/** + * pstore_set() - Initialize PStore settings from command line arguments + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * Set pstore reserved memory info, starting at 'addr' for 'len' bytes. + * Default length for records is 4K. + * Mandatory arguments: + * - addr: ramoops starting address + * - len: ramoops total length + * Optional arguments: + * - record-size: size of one panic or oops record ('dump' type) + * - console-size: size of the kernel logs record + * - ftrace-size: size of the ftrace record(s), this can be a single record or + * divided in parts based on number of CPUs + * - pmsg-size: size of the user space logs record + * - ecc-size: enables/disables ECC support and specifies ECC buffer size in + * bytes (0 disables it, 1 is a special value, means 16 bytes ECC) + * + * Return: zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +static int pstore_set(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + if (argc < 3) + return CMD_RET_USAGE; + + /* Address is specified since argc > 2 + */ + pstore_addr = hextoul(argv[1], NULL); + + /* Length is specified since argc > 2 + */ + pstore_length = hextoul(argv[2], NULL); + + if (argc > 3) + pstore_record_size = hextoul(argv[3], NULL); + + if (argc > 4) + pstore_console_size = hextoul(argv[4], NULL); + + if (argc > 5) + pstore_ftrace_size = hextoul(argv[5], NULL); + + if (argc > 6) + pstore_pmsg_size = hextoul(argv[6], NULL); + + if (argc > 7) + pstore_ecc_size = hextoul(argv[7], NULL); + + if (pstore_length < (pstore_record_size + pstore_console_size + + pstore_ftrace_size + pstore_pmsg_size)) { + printf("pstore <len> should be larger than the sum of all records sizes\n"); + pstore_length = 0; + } + + log_debug("pstore set done: start 0x%08llx - length 0x%llx\n", + (unsigned long long)pstore_addr, + (unsigned long long)pstore_length); + + return 0; +} + +/** + * pstore_print_buffer() - Print buffer + * @type: buffer type + * @buffer: buffer to print + * @size: buffer size + * + * Print buffer type and content + */ +static void pstore_print_buffer(char *type, char *buffer, u32 size) +{ + u32 i = 0; + + printf("**** %s\n", type); + while (i < size && buffer[i] != 0) { + putc(buffer[i]); + i++; + } +} + +/** + * pstore_display() - Display existing records in pstore reserved memory + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * A 'record-type' can be given to only display records of this kind. + * If no 'record-type' is given, all valid records are dispayed. + * 'record-type' can be one of 'dump', 'console', 'ftrace' or 'user'. For 'dump' + * and 'ftrace' types, a 'nb' can be given to only display one record. + * + * Return: zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +static int pstore_display(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + int type = PSTORE_TYPE_ALL; + phys_addr_t ptr; + char *buffer; + u32 size; + int header_len = 0; + bool compressed; + + if (argc > 1) { + if (!strcmp(argv[1], "dump")) + type = PSTORE_TYPE_DMESG; + else if (!strcmp(argv[1], "console")) + type = PSTORE_TYPE_CONSOLE; + else if (!strcmp(argv[1], "ftrace")) + type = PSTORE_TYPE_FTRACE; + else if (!strcmp(argv[1], "user")) + type = PSTORE_TYPE_PMSG; + else + return CMD_RET_USAGE; + } + + if (pstore_length == 0) { + printf("Please set PStore configuration\n"); + return CMD_RET_USAGE; + } + + if (buffer_size == 0) + pstore_init_buffer_size(); + + buffer = malloc_cache_aligned(buffer_size); + + if (type == PSTORE_TYPE_DMESG || type == PSTORE_TYPE_ALL) { + ptr = pstore_addr; + phys_addr_t ptr_end = ptr + pstore_length - pstore_pmsg_size + - pstore_ftrace_size - pstore_console_size; + + if (argc > 2) { + ptr += dectoul(argv[2], NULL) + * pstore_record_size; + ptr_end = ptr + pstore_record_size; + } + + while (ptr < ptr_end) { + size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, + pstore_record_size, buffer); + ptr += pstore_record_size; + + if (size == 0) + continue; + + header_len = pstore_read_kmsg_hdr(buffer, &compressed); + if (header_len == 0) { + log_debug("no valid kernel header\n"); + continue; + } + + if (compressed) { + printf("Compressed buffer, display not available\n"); + continue; + } + + pstore_print_buffer("Dump", buffer + header_len, + size - header_len); + } + } + + if (type == PSTORE_TYPE_CONSOLE || type == PSTORE_TYPE_ALL) { + ptr = pstore_addr + pstore_length - pstore_pmsg_size + - pstore_ftrace_size - pstore_console_size; + size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, + pstore_console_size, buffer); + if (size != 0) + pstore_print_buffer("Console", buffer, size); + } + + if (type == PSTORE_TYPE_FTRACE || type == PSTORE_TYPE_ALL) { + ptr = pstore_addr + pstore_length - pstore_pmsg_size + - pstore_ftrace_size; + /* The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC' + * signature, pass 0 to pstore_get_buffer to prevent + * checking it + */ + size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer); + if (size != 0) + pstore_print_buffer("FTrace", buffer, size); + } + + if (type == PSTORE_TYPE_PMSG || type == PSTORE_TYPE_ALL) { + ptr = pstore_addr + pstore_length - pstore_pmsg_size; + size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, + pstore_pmsg_size, buffer); + if (size != 0) + pstore_print_buffer("User", buffer, size); + } + + free(buffer); + + return 0; +} + +/** + * pstore_save() - Save existing records from pstore reserved memory + * @cmdtp: Command data struct pointer + * @flag: Command flag + * @argc: Command-line argument count + * @argv: Array of command-line arguments + * + * the records are saved under 'directory path', which should already exist, + * to partition 'part' on device type 'interface' instance 'dev' + * Filenames are automatically generated, depending on record type, like in + * /sys/fs/pstore under Linux + * + * Return: zero on success, CMD_RET_USAGE in case of misuse and negative + * on error. + */ +static int pstore_save(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + phys_addr_t ptr, ptr_end; + char *buffer; + char *save_argv[6]; + char addr[19], length[19]; + char path[256]; + u32 size; + unsigned int index; + int header_len = 0; + bool compressed; + + if (argc < 4) + return CMD_RET_USAGE; + + if (pstore_length == 0) { + printf("Please set PStore configuration\n"); + return CMD_RET_USAGE; + } + + if (buffer_size == 0) + pstore_init_buffer_size(); + + buffer = malloc_cache_aligned(buffer_size); + sprintf(addr, "0x%p", buffer); + + save_argv[0] = argv[0]; + save_argv[1] = argv[1]; + save_argv[2] = argv[2]; + save_argv[3] = addr; + save_argv[4] = path; + save_argv[5] = length; + + /* Save all Dump records */ + ptr = pstore_addr; + ptr_end = ptr + pstore_length - pstore_pmsg_size - pstore_ftrace_size + - pstore_console_size; + index = 0; + while (ptr < ptr_end) { + size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, + pstore_record_size, buffer); + ptr += pstore_record_size; + + if (size == 0) + continue; + + header_len = pstore_read_kmsg_hdr(buffer, &compressed); + if (header_len == 0) { + log_debug("no valid kernel header\n"); + continue; + } + + sprintf(addr, "0x%08lx", (ulong)map_to_sysmem(buffer + header_len)); + sprintf(length, "0x%X", size - header_len); + sprintf(path, "%s/dmesg-ramoops-%u%s", argv[3], index, + compressed ? ".enc.z" : ""); + do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY); + index++; + } + + sprintf(addr, "0x%08lx", (ulong)map_to_sysmem(buffer)); + + /* Save Console record */ + size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_console_size, + buffer); + if (size != 0) { + sprintf(length, "0x%X", size); + sprintf(path, "%s/console-ramoops-0", argv[3]); + do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY); + } + ptr += pstore_console_size; + + /* Save FTrace record(s) + * The FTrace record(s) uses LINUX_VERSION_CODE ^ 'DBGC' signature, + * pass 0 to pstore_get_buffer to prevent checking it + */ + size = pstore_get_buffer(0, ptr, pstore_ftrace_size, buffer); + if (size != 0) { + sprintf(length, "0x%X", size); + sprintf(path, "%s/ftrace-ramoops-0", argv[3]); + do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY); + } + ptr += pstore_ftrace_size; + + /* Save Console record */ + size = pstore_get_buffer(PERSISTENT_RAM_SIG, ptr, pstore_pmsg_size, + buffer); + if (size != 0) { + sprintf(length, "0x%X", size); + sprintf(path, "%s/pmsg-ramoops-0", argv[3]); + do_save(cmdtp, flag, 6, save_argv, FS_TYPE_ANY); + } + + free(buffer); + + return 0; +} + +static struct cmd_tbl cmd_pstore_sub[] = { + U_BOOT_CMD_MKENT(set, 8, 0, pstore_set, "", ""), + U_BOOT_CMD_MKENT(display, 3, 0, pstore_display, "", ""), + U_BOOT_CMD_MKENT(save, 4, 0, pstore_save, "", ""), +}; + +static int do_pstore(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + struct cmd_tbl *c; + + if (argc < 2) + return CMD_RET_USAGE; + + /* Strip off leading argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], cmd_pstore_sub, ARRAY_SIZE(cmd_pstore_sub)); + + if (!c) + return CMD_RET_USAGE; + + return c->cmd(cmdtp, flag, argc, argv); +} + +void fdt_fixup_pstore(void *blob) +{ + char node[32]; + int nodeoffset; /* node offset from libfdt */ + u32 addr_cells_root; + u32 size_cells_root; + u32 addr_cells; + u32 size_cells; + + nodeoffset = fdt_path_offset(blob, "/"); + if (nodeoffset < 0) { + /* Not found or something else bad happened. */ + log_err("fdt_path_offset() returned %s\n", fdt_strerror(nodeoffset)); + return; + } + addr_cells_root = fdt_getprop_u32_default_node(blob, nodeoffset, 0, "#address-cells", 2); + size_cells_root = fdt_getprop_u32_default_node(blob, nodeoffset, 0, "#size-cells", 2); + + nodeoffset = fdt_find_or_add_subnode(blob, nodeoffset, "reserved-memory"); + if (nodeoffset < 0) { + log_err("Add 'reserved-memory' node failed: %s\n", + fdt_strerror(nodeoffset)); + return; + } + + addr_cells = fdt_getprop_u32_default_node(blob, nodeoffset, 0, + "#address-cells", addr_cells_root); + size_cells = fdt_getprop_u32_default_node(blob, nodeoffset, 0, + "#size-cells", size_cells_root); + fdt_setprop_u32(blob, nodeoffset, "#address-cells", addr_cells); + fdt_setprop_u32(blob, nodeoffset, "#size-cells", size_cells); + + fdt_setprop_empty(blob, nodeoffset, "ranges"); + + sprintf(node, "ramoops@%llx", (unsigned long long)pstore_addr); + nodeoffset = fdt_add_subnode(blob, nodeoffset, node); + if (nodeoffset < 0) { + log_err("Add '%s' node failed: %s\n", node, fdt_strerror(nodeoffset)); + return; + } + + fdt_setprop_string(blob, nodeoffset, "compatible", "ramoops"); + + if (addr_cells == 1) { + fdt_setprop_u32(blob, nodeoffset, "reg", pstore_addr); + } else if (addr_cells == 2) { + fdt_setprop_u64(blob, nodeoffset, "reg", pstore_addr); + } else { + log_err("Unsupported #address-cells: %u\n", addr_cells); + goto clean_ramoops; + } + + if (size_cells == 1) { + // Let's consider that the pstore_length fits in a 32 bits value + fdt_appendprop_u32(blob, nodeoffset, "reg", pstore_length); + } else if (size_cells == 2) { + fdt_appendprop_u64(blob, nodeoffset, "reg", pstore_length); + } else { + log_err("Unsupported #size-cells: %u\n", addr_cells); + goto clean_ramoops; + } + + fdt_setprop_u32(blob, nodeoffset, "record-size", pstore_record_size); + fdt_setprop_u32(blob, nodeoffset, "console-size", pstore_console_size); + fdt_setprop_u32(blob, nodeoffset, "ftrace-size", pstore_ftrace_size); + fdt_setprop_u32(blob, nodeoffset, "pmsg-size", pstore_pmsg_size); + fdt_setprop_u32(blob, nodeoffset, "ecc-size", pstore_ecc_size); + +clean_ramoops: + fdt_del_node_and_alias(blob, node); +} + +U_BOOT_CMD(pstore, 10, 0, do_pstore, + "Manage Linux Persistent Storage", + "set <addr> <len> [record-size] [console-size] [ftrace-size] [pmsg_size] [ecc-size]\n" + "- Set pstore reserved memory info, starting at 'addr' for 'len' bytes.\n" + " Default length for records is 4K.\n" + " 'record-size' is the size of one panic or oops record ('dump' type).\n" + " 'console-size' is the size of the kernel logs record.\n" + " 'ftrace-size' is the size of the ftrace record(s), this can be a single\n" + " record or divided in parts based on number of CPUs.\n" + " 'pmsg-size' is the size of the user space logs record.\n" + " 'ecc-size' enables/disables ECC support and specifies ECC buffer size in\n" + " bytes (0 disables it, 1 is a special value, means 16 bytes ECC).\n" + "pstore display [record-type] [nb]\n" + "- Display existing records in pstore reserved memory. A 'record-type' can\n" + " be given to only display records of this kind. 'record-type' can be one\n" + " of 'dump', 'console', 'ftrace' or 'user'. For 'dump' and 'ftrace' types,\n" + " a 'nb' can be given to only display one record.\n" + "pstore save <interface> <dev[:part]> <directory-path>\n" + "- Save existing records in pstore reserved memory under 'directory path'\n" + " to partition 'part' on device type 'interface' instance 'dev'.\n" + " Filenames are automatically generated, depending on record type, like\n" + " in /sys/fs/pstore under Linux.\n" + " The 'directory-path' should already exist.\n" +); diff --git a/cmd/pvblock.c b/cmd/pvblock.c new file mode 100644 index 00000000000..3a83ac9cd92 --- /dev/null +++ b/cmd/pvblock.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2020 EPAM Systems Inc. + * + * XEN para-virtualized block device support + */ + +#include <blk.h> +#include <command.h> + +/* Current I/O Device */ +static int pvblock_curr_device; + +int do_pvblock(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return blk_common_cmd(argc, argv, UCLASS_PVBLOCK, + &pvblock_curr_device); +} + +U_BOOT_CMD(pvblock, 5, 1, do_pvblock, + "Xen para-virtualized block device", + "info - show available block devices\n" + "pvblock device [dev] - show or set current device\n" + "pvblock part [dev] - print partition table of one or all devices\n" + "pvblock read addr blk# cnt\n" + "pvblock write addr blk# cnt - read/write `cnt'" + " blocks starting at block `blk#'\n" + " to/from memory address `addr'"); diff --git a/cmd/pwm.c b/cmd/pwm.c new file mode 100644 index 00000000000..7e82955239f --- /dev/null +++ b/cmd/pwm.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Control PWM channels + * + * Copyright (c) 2020 SiFive, Inc + * author: Pragnesh Patel <pragnesh.patel@sifive.com> + */ + +#include <command.h> +#include <dm.h> +#include <pwm.h> + +enum pwm_cmd { + PWM_SET_INVERT, + PWM_SET_CONFIG, + PWM_SET_ENABLE, + PWM_SET_DISABLE, +}; + +static int do_pwm(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *str_cmd, *str_channel = NULL, *str_enable = NULL; + const char *str_pwm = NULL, *str_period = NULL, *str_duty = NULL; + enum pwm_cmd sub_cmd; + struct udevice *dev; + u32 channel, pwm_enable, pwm_dev, period_ns = 0, duty_ns = 0; + int ret; + + if (argc < 4) + return CMD_RET_USAGE; + + str_cmd = argv[1]; + argc -= 2; + argv += 2; + + str_pwm = *argv; + argc--; + argv++; + + if (!str_pwm) + return CMD_RET_USAGE; + + switch (*str_cmd) { + case 'i': + sub_cmd = PWM_SET_INVERT; + if (argc != 2) + return CMD_RET_USAGE; + break; + case 'c': + sub_cmd = PWM_SET_CONFIG; + if (argc != 3) + return CMD_RET_USAGE; + break; + case 'e': + sub_cmd = PWM_SET_ENABLE; + if (argc != 1) + return CMD_RET_USAGE; + break; + case 'd': + sub_cmd = PWM_SET_DISABLE; + if (argc != 1) + return CMD_RET_USAGE; + break; + default: + return CMD_RET_USAGE; + } + + pwm_dev = dectoul(str_pwm, NULL); + ret = uclass_get_device(UCLASS_PWM, pwm_dev, &dev); + if (ret) { + printf("pwm: '%s' not found\n", str_pwm); + return cmd_process_error(cmdtp, ret); + } + + str_channel = *argv; + channel = dectoul(str_channel, NULL); + argc--; + argv++; + + if (sub_cmd == PWM_SET_INVERT) { + str_enable = *argv; + pwm_enable = dectoul(str_enable, NULL); + ret = pwm_set_invert(dev, channel, pwm_enable); + } else if (sub_cmd == PWM_SET_CONFIG) { + str_period = *argv; + argc--; + argv++; + period_ns = dectoul(str_period, NULL); + + str_duty = *argv; + duty_ns = dectoul(str_duty, NULL); + + ret = pwm_set_config(dev, channel, period_ns, duty_ns); + } else if (sub_cmd == PWM_SET_ENABLE) { + ret = pwm_set_enable(dev, channel, 1); + } else if (sub_cmd == PWM_SET_DISABLE) { + ret = pwm_set_enable(dev, channel, 0); + } + + if (ret) { + printf("error(%d)\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(pwm, 6, 0, do_pwm, + "control pwm channels", + "invert <pwm_dev_num> <channel> <polarity> - invert polarity\n" + "pwm config <pwm_dev_num> <channel> <period_ns> <duty_ns> - config PWM\n" + "pwm enable <pwm_dev_num> <channel> - enable PWM output\n" + "pwm disable <pwm_dev_num> <channel> - disable PWM output\n" + "Note: All input values are in decimal"); diff --git a/cmd/pxe.c b/cmd/pxe.c new file mode 100644 index 00000000000..3deae5e6d47 --- /dev/null +++ b/cmd/pxe.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2010-2011 Calxeda, Inc. + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ + +#include <command.h> +#include <env.h> +#include <fs.h> +#include <net.h> +#include <net6.h> +#include <malloc.h> +#include <vsprintf.h> + +#include "pxe_utils.h" + +const char *pxe_default_paths[] = { +#ifdef CONFIG_SYS_SOC +#ifdef CONFIG_SYS_BOARD + "default-" CONFIG_SYS_ARCH "-" CONFIG_SYS_SOC "-" CONFIG_SYS_BOARD, +#endif + "default-" CONFIG_SYS_ARCH "-" CONFIG_SYS_SOC, +#endif + "default-" CONFIG_SYS_ARCH, + "default", + NULL +}; + +static int do_get_tftp(struct pxe_context *ctx, const char *file_path, + char *file_addr, enum bootflow_img_t type, ulong *sizep) +{ + char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; + int ret; + int num_args; + + tftp_argv[1] = file_addr; + tftp_argv[2] = (void *)file_path; + if (ctx->use_ipv6) { + tftp_argv[3] = USE_IP6_CMD_PARAM; + num_args = 4; + } else { + num_args = 3; + } + + if (do_tftpb(ctx->cmdtp, 0, num_args, tftp_argv)) + return -ENOENT; + + ret = pxe_get_file_size(sizep); + if (ret) + return log_msg_ret("tftp", ret); + ctx->pxe_file_size = *sizep; + + return 1; +} + +/* + * Looks for a pxe file with specified config file name, + * which is received from DHCPv4 option 209 or + * DHCPv6 option 60. + * + * Returns 1 on success or < 0 on error. + */ +static int pxe_dhcp_option_path(struct pxe_context *ctx, unsigned long pxefile_addr_r) +{ + int ret = get_pxe_file(ctx, pxelinux_configfile, pxefile_addr_r); + + free(pxelinux_configfile); + /* set to NULL to avoid double-free if DHCP is tried again */ + pxelinux_configfile = NULL; + + return ret; +} + +/* + * Looks for a pxe file with a name based on the pxeuuid environment variable. + * + * Returns 1 on success or < 0 on error. + */ +static int pxe_uuid_path(struct pxe_context *ctx, unsigned long pxefile_addr_r) +{ + char *uuid_str; + + uuid_str = from_env("pxeuuid"); + + if (!uuid_str) + return -ENOENT; + + return get_pxelinux_path(ctx, uuid_str, pxefile_addr_r); +} + +/* + * Looks for a pxe file with a name based on the 'ethaddr' environment + * variable. + * + * Returns 1 on success or < 0 on error. + */ +static int pxe_mac_path(struct pxe_context *ctx, unsigned long pxefile_addr_r) +{ + char mac_str[21]; + int err; + + err = format_mac_pxe(mac_str, sizeof(mac_str)); + + if (err < 0) + return err; + + return get_pxelinux_path(ctx, mac_str, pxefile_addr_r); +} + +/* + * Looks for pxe files with names based on our IP address. See pxelinux + * documentation for details on what these file names look like. We match + * that exactly. + * + * Returns 1 on success or < 0 on error. + */ +static int pxe_ipaddr_paths(struct pxe_context *ctx, unsigned long pxefile_addr_r) +{ + char ip_addr[9]; + int mask_pos, err; + + sprintf(ip_addr, "%08X", ntohl(net_ip.s_addr)); + + for (mask_pos = 7; mask_pos >= 0; mask_pos--) { + err = get_pxelinux_path(ctx, ip_addr, pxefile_addr_r); + + if (err > 0) + return err; + + ip_addr[mask_pos] = '\0'; + } + + return -ENOENT; +} + +int pxe_get(ulong pxefile_addr_r, char **bootdirp, ulong *sizep, bool use_ipv6) +{ + struct cmd_tbl cmdtp[] = {}; /* dummy */ + struct pxe_context ctx; + int i; + + if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false, + env_get("bootfile"), use_ipv6, false)) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_BOOTP_PXE_DHCP_OPTION) && + pxelinux_configfile && !use_ipv6) { + if (pxe_dhcp_option_path(&ctx, pxefile_addr_r) > 0) + goto done; + + goto error_exit; + } + + if (IS_ENABLED(CONFIG_DHCP6_PXE_DHCP_OPTION) && + pxelinux_configfile && use_ipv6) { + if (pxe_dhcp_option_path(&ctx, pxefile_addr_r) > 0) + goto done; + + goto error_exit; + } + + /* + * Keep trying paths until we successfully get a file we're looking + * for. + */ + if (pxe_uuid_path(&ctx, pxefile_addr_r) > 0 || + pxe_mac_path(&ctx, pxefile_addr_r) > 0 || + pxe_ipaddr_paths(&ctx, pxefile_addr_r) > 0) + goto done; + + i = 0; + while (pxe_default_paths[i]) { + if (get_pxelinux_path(&ctx, pxe_default_paths[i], + pxefile_addr_r) > 0) + goto done; + i++; + } + +error_exit: + pxe_destroy_ctx(&ctx); + + return -ENOENT; +done: + *bootdirp = env_get("bootfile"); + + /* + * The PXE file size is returned but not the name. It is probably not + * that useful. + */ + *sizep = ctx.pxe_file_size; + pxe_destroy_ctx(&ctx); + + return 0; +} + +/* + * Entry point for the 'pxe get' command. + * This Follows pxelinux's rules to download a config file from a tftp server. + * The file is stored at the location given by the pxefile_addr_r environment + * variable, which must be set. + * + * UUID comes from pxeuuid env variable, if defined + * MAC addr comes from ethaddr env variable, if defined + * IP + * + * see http://syslinux.zytor.com/wiki/index.php/PXELINUX + * + * Returns 0 on success or 1 on error. + */ +static int +do_pxe_get(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *pxefile_addr_str; + ulong pxefile_addr_r; + char *fname; + ulong size; + int ret; + bool use_ipv6 = false; + + if (IS_ENABLED(CONFIG_IPV6)) { + if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM)) + use_ipv6 = true; + + if (!(argc == 1 || (argc == 2 && use_ipv6))) + return CMD_RET_USAGE; + } else { + if (argc != 1) + return CMD_RET_USAGE; + } + + pxefile_addr_str = from_env("pxefile_addr_r"); + + if (!pxefile_addr_str) + return 1; + + ret = strict_strtoul(pxefile_addr_str, 16, + (unsigned long *)&pxefile_addr_r); + if (ret < 0) + return 1; + + ret = pxe_get(pxefile_addr_r, &fname, &size, use_ipv6); + switch (ret) { + case 0: + printf("Config file '%s' found\n", fname); + break; + case -ENOMEM: + printf("Out of memory\n"); + return CMD_RET_FAILURE; + default: + printf("Config file not found\n"); + return CMD_RET_FAILURE; + } + + return 0; +} + +/* + * Boots a system using a pxe file + * + * Returns 0 on success, 1 on error. + */ +static int +do_pxe_boot(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + unsigned long pxefile_addr_r; + char *pxefile_addr_str; + struct pxe_context ctx; + int ret; + bool use_ipv6 = false; + + if (IS_ENABLED(CONFIG_IPV6)) { + if (!strcmp(argv[argc - 1], USE_IP6_CMD_PARAM)) + use_ipv6 = true; + } + + if (argc == 1 || (argc == 2 && use_ipv6)) { + pxefile_addr_str = from_env("pxefile_addr_r"); + if (!pxefile_addr_str) + return 1; + + } else if (argc == 2 || (argc == 3 && use_ipv6)) { + pxefile_addr_str = argv[1]; + } else { + return CMD_RET_USAGE; + } + + if (strict_strtoul(pxefile_addr_str, 16, &pxefile_addr_r) < 0) { + printf("Invalid pxefile address: %s\n", pxefile_addr_str); + return 1; + } + + if (pxe_setup_ctx(&ctx, cmdtp, do_get_tftp, NULL, false, + env_get("bootfile"), use_ipv6, false)) { + printf("Out of memory\n"); + return CMD_RET_FAILURE; + } + ret = pxe_process(&ctx, pxefile_addr_r, false); + pxe_destroy_ctx(&ctx); + if (ret) + return CMD_RET_FAILURE; + + copy_filename(net_boot_file_name, "", sizeof(net_boot_file_name)); + + return 0; +} + +static struct cmd_tbl cmd_pxe_sub[] = { + U_BOOT_CMD_MKENT(get, 2, 1, do_pxe_get, "", ""), + U_BOOT_CMD_MKENT(boot, 3, 1, do_pxe_boot, "", "") +}; + +static int do_pxe(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *cp; + + if (argc < 2) + return CMD_RET_USAGE; + + /* drop initial "pxe" arg */ + argc--; + argv++; + + cp = find_cmd_tbl(argv[0], cmd_pxe_sub, ARRAY_SIZE(cmd_pxe_sub)); + + if (cp) + return cp->cmd(cmdtp, flag, argc, argv); + + return CMD_RET_USAGE; +} + +U_BOOT_CMD(pxe, 4, 1, do_pxe, + "get and boot from pxe files", + "get [" USE_IP6_CMD_PARAM "] - try to retrieve a pxe file using tftp\n" + "pxe boot [pxefile_addr_r] [-ipv6] - boot from the pxe file at pxefile_addr_r\n" +); diff --git a/cmd/qfw.c b/cmd/qfw.c new file mode 100644 index 00000000000..1b108118658 --- /dev/null +++ b/cmd/qfw.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2015 Miao Yan <yanmiaobest@gmail.com> + */ + +#include <command.h> +#include <env.h> +#include <errno.h> +#include <qfw.h> +#include <dm.h> + +static struct udevice *qfw_dev; + +static int qemu_fwcfg_cmd_list_firmware(void) +{ + int ret; + struct fw_cfg_file_iter iter; + struct fw_file *file; + + /* make sure fw_list is loaded */ + ret = qfw_read_firmware_list(qfw_dev); + if (ret) + return ret; + + for (file = qfw_file_iter_init(qfw_dev, &iter); + !qfw_file_iter_end(&iter); + file = qfw_file_iter_next(&iter)) { + printf("%08lx %-56s\n", file->addr, file->cfg.name); + } + + return 0; +} + +static int qemu_fwcfg_do_list(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + if (qemu_fwcfg_cmd_list_firmware() < 0) + return CMD_RET_FAILURE; + + return 0; +} + +static int qemu_fwcfg_do_cpus(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + printf("%d cpu(s) online\n", qfw_online_cpus(qfw_dev)); + return 0; +} + +static int qemu_fwcfg_do_load(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + char *env; + ulong load_addr; + ulong initrd_addr; + + env = env_get("loadaddr"); + load_addr = env ? + hextoul(env, NULL) : + CONFIG_SYS_LOAD_ADDR; + + env = env_get("ramdiskaddr"); + initrd_addr = env ? + hextoul(env, NULL) : +#ifdef CFG_RAMDISK_ADDR + CFG_RAMDISK_ADDR; +#else + 0; +#endif + + if (argc == 2) { + load_addr = hextoul(argv[0], NULL); + initrd_addr = hextoul(argv[1], NULL); + } else if (argc == 1) { + load_addr = hextoul(argv[0], NULL); + } + + if (!load_addr || !initrd_addr) { + printf("missing load or initrd address\n"); + return CMD_RET_FAILURE; + } + + return qemu_fwcfg_setup_kernel(qfw_dev, load_addr, initrd_addr); +} + +static struct cmd_tbl fwcfg_commands[] = { + U_BOOT_CMD_MKENT(list, 0, 1, qemu_fwcfg_do_list, "", ""), + U_BOOT_CMD_MKENT(cpus, 0, 1, qemu_fwcfg_do_cpus, "", ""), + U_BOOT_CMD_MKENT(load, 2, 1, qemu_fwcfg_do_load, "", ""), +}; + +static int do_qemu_fw(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + struct cmd_tbl *fwcfg_cmd; + + ret = qfw_get_dev(&qfw_dev); + if (ret) { + printf("QEMU fw_cfg interface not found\n"); + return CMD_RET_USAGE; + } + + fwcfg_cmd = find_cmd_tbl(argv[1], fwcfg_commands, + ARRAY_SIZE(fwcfg_commands)); + argc -= 2; + argv += 2; + if (!fwcfg_cmd || argc > fwcfg_cmd->maxargs) + return CMD_RET_USAGE; + + ret = fwcfg_cmd->cmd(fwcfg_cmd, flag, argc, argv); + + return cmd_process_error(fwcfg_cmd, ret); +} + +U_BOOT_CMD( + qfw, 4, 1, do_qemu_fw, + "QEMU firmware interface", + "<command>\n" + " - list : print firmware(s) currently loaded\n" + " - cpus : print online cpu number\n" + " - load <kernel addr> <initrd addr> : load kernel and initrd (if any), and setup for zboot\n" +); diff --git a/cmd/read.c b/cmd/read.c new file mode 100644 index 00000000000..8e21f004423 --- /dev/null +++ b/cmd/read.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + */ + +#include <command.h> +#include <mapmem.h> +#include <part.h> +#include <vsprintf.h> + +static int +do_rw(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct blk_desc *dev_desc = NULL; + struct disk_partition part_info; + ulong offset, limit; + uint blk, cnt, res; + void *ptr; + int part; + + if (argc != 6) { + cmd_usage(cmdtp); + return 1; + } + + part = part_get_info_by_dev_and_name_or_num(argv[1], argv[2], + &dev_desc, &part_info, 1); + if (part < 0) + return 1; + + ptr = map_sysmem(hextoul(argv[3], NULL), 0); + blk = hextoul(argv[4], NULL); + cnt = hextoul(argv[5], NULL); + + if (part > 0) { + offset = part_info.start; + limit = part_info.size; + } else { + /* Largest address not available in struct blk_desc. */ + offset = 0; + limit = ~0; + } + + if (cnt + blk > limit) { + printf("%s out of range\n", cmdtp->name); + unmap_sysmem(ptr); + return 1; + } + + if (IS_ENABLED(CONFIG_CMD_WRITE) && !strcmp(cmdtp->name, "write")) + res = blk_dwrite(dev_desc, offset + blk, cnt, ptr); + else + res = blk_dread(dev_desc, offset + blk, cnt, ptr); + unmap_sysmem(ptr); + + if (res != cnt) { + printf("%s error\n", cmdtp->name); + return 1; + } + + return 0; +} + +#ifdef CONFIG_CMD_READ +U_BOOT_CMD( + read, 6, 0, do_rw, + "Load binary data from a partition", + "<interface> <dev[:part|#partname]> addr blk# cnt" +); +#endif + +#ifdef CONFIG_CMD_WRITE +U_BOOT_CMD( + write, 6, 0, do_rw, + "Store binary data to a partition", + "<interface> <dev[:part|#partname]> addr blk# cnt" +); +#endif diff --git a/cmd/reginfo.c b/cmd/reginfo.c new file mode 100644 index 00000000000..53b8bc41bfe --- /dev/null +++ b/cmd/reginfo.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000 + * Subodh Nijsure, SkyStream Networks, snijsure@skystream.com + */ + +#include <command.h> +#include <asm/ppc.h> + +static int do_reginfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + print_reginfo(); + + return 0; +} + + /**************************************************/ + +U_BOOT_CMD( + reginfo, 2, 1, do_reginfo, + "print register information", + "" +); diff --git a/cmd/regulator.c b/cmd/regulator.c new file mode 100644 index 00000000000..8d743c8d269 --- /dev/null +++ b/cmd/regulator.c @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2014-2015 Samsung Electronics + * Przemyslaw Marczak <p.marczak@samsung.com> + */ +#include <command.h> +#include <errno.h> +#include <dm.h> +#include <dm/uclass-internal.h> +#include <linux/printk.h> +#include <power/regulator.h> + +#define LIMIT_DEVNAME 20 +#define LIMIT_OFNAME 32 +#define LIMIT_INFO 18 + +static struct udevice *currdev; + +static int failure(int ret) +{ + printf("Error: %d (%s)\n", ret, errno_str(ret)); + + return CMD_RET_FAILURE; +} + +static int do_dev(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct dm_regulator_uclass_plat *uc_pdata; + const char *name; + int ret = -ENXIO; + + switch (argc) { + case 2: + name = argv[1]; + ret = regulator_get_by_platname(name, &currdev); + if (ret) { + printf("Can't get the regulator: %s!\n", name); + return failure(ret); + } + fallthrough; + case 1: + if (!currdev) { + printf("Regulator device is not set!\n\n"); + return CMD_RET_USAGE; + } + + uc_pdata = dev_get_uclass_plat(currdev); + if (!uc_pdata) { + printf("%s: no regulator platform data!\n", currdev->name); + return failure(ret); + } + + printf("dev: %s @ %s\n", uc_pdata->name, currdev->name); + } + + return CMD_RET_SUCCESS; +} + +static int curr_dev_and_plat(struct udevice **devp, + struct dm_regulator_uclass_plat **uc_pdata, + bool allow_type_fixed) +{ + *devp = NULL; + *uc_pdata = NULL; + + if (!currdev) { + printf("First, set the regulator device!\n"); + return CMD_RET_FAILURE; + } + + *devp = currdev; + + *uc_pdata = dev_get_uclass_plat(*devp); + if (!*uc_pdata) { + pr_err("Regulator: %s - missing platform data!\n", currdev->name); + return CMD_RET_FAILURE; + } + + if (!allow_type_fixed && (*uc_pdata)->type == REGULATOR_TYPE_FIXED) { + printf("Operation not allowed for fixed regulator!\n"); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct dm_regulator_uclass_plat *uc_pdata; + struct udevice *dev; + int ret; + + printf("| %-*.*s| %-*.*s| %s\n", + LIMIT_DEVNAME, LIMIT_DEVNAME, "Device", + LIMIT_OFNAME, LIMIT_OFNAME, "regulator-name", + "Parent"); + + ret = uclass_find_first_device(UCLASS_REGULATOR, &dev); + if (ret) + return ret; + + for (; dev; uclass_find_next_device(&dev)) { + uc_pdata = dev_get_uclass_plat(dev); + printf("| %-*.*s| %-*.*s| %s\n", + LIMIT_DEVNAME, LIMIT_DEVNAME, dev->name, + LIMIT_OFNAME, LIMIT_OFNAME, uc_pdata->name, + dev->parent->name); + } + + return ret; +} + +static int constraint(const char *name, int val, const char *val_name) +{ + printf("%-*s", LIMIT_INFO, name); + if (val < 0) { + printf(" %s (err: %d)\n", errno_str(val), val); + return val; + } + + if (val_name) + printf(" %d (%s)\n", val, val_name); + else + printf(" %d\n", val); + + return 0; +} + +static const char *get_mode_name(struct dm_regulator_mode *mode, + int mode_count, + int mode_id) +{ + while (mode_count--) { + if (mode->id == mode_id) + return mode->name; + mode++; + } + + return NULL; +} + +static int do_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + struct dm_regulator_uclass_plat *uc_pdata; + struct dm_regulator_mode *modes; + const char *parent_uc; + int mode_count; + int ret; + int i; + + ret = curr_dev_and_plat(&dev, &uc_pdata, true); + if (ret) + return ret; + + parent_uc = dev_get_uclass_name(dev->parent); + + printf("%s\n%-*s %s\n%-*s %s\n%-*s %s\n%-*s %s\n%-*s\n", + "Regulator info:", + LIMIT_INFO, "* regulator-name:", uc_pdata->name, + LIMIT_INFO, "* device name:", dev->name, + LIMIT_INFO, "* parent name:", dev->parent->name, + LIMIT_INFO, "* parent uclass:", parent_uc, + LIMIT_INFO, "* constraints:"); + + constraint(" - min uV:", uc_pdata->min_uV, NULL); + constraint(" - max uV:", uc_pdata->max_uV, NULL); + constraint(" - min uA:", uc_pdata->min_uA, NULL); + constraint(" - max uA:", uc_pdata->max_uA, NULL); + constraint(" - always on:", uc_pdata->always_on, + uc_pdata->always_on ? "true" : "false"); + constraint(" - boot on:", uc_pdata->boot_on, + uc_pdata->boot_on ? "true" : "false"); + + mode_count = regulator_mode(dev, &modes); + constraint("* op modes:", mode_count, NULL); + + for (i = 0; i < mode_count; i++, modes++) + constraint(" - mode id:", modes->id, modes->name); + + return CMD_RET_SUCCESS; +} + +static void do_status_detail(struct udevice *dev, + struct dm_regulator_uclass_plat *uc_pdata) +{ + int current, value, mode; + const char *mode_name; + bool enabled; + + printf("Regulator %s status:\n", uc_pdata->name); + + enabled = regulator_get_enable(dev); + constraint(" * enable:", enabled, enabled ? "true" : "false"); + + value = regulator_get_value(dev); + constraint(" * value uV:", value, NULL); + + current = regulator_get_current(dev); + constraint(" * current uA:", current, NULL); + + mode = regulator_get_mode(dev); + mode_name = get_mode_name(uc_pdata->mode, uc_pdata->mode_count, mode); + constraint(" * mode id:", mode, mode_name); +} + +static void do_status_line(struct udevice *dev, int status) +{ + struct dm_regulator_uclass_plat *pdata; + int current, value, mode; + const char *mode_name; + bool enabled; + + pdata = dev_get_uclass_plat(dev); + enabled = regulator_get_enable(dev); + value = regulator_get_value(dev); + current = regulator_get_current(dev); + mode = regulator_get_mode(dev); + mode_name = get_mode_name(pdata->mode, pdata->mode_count, mode); + printf("%-20s %-10s ", pdata->name, enabled ? "enabled" : "disabled"); + if (value >= 0) + printf("%10d ", value); + else + printf("%10s ", "-"); + if (current >= 0) + printf("%10d ", current); + else + printf("%10s ", "-"); + if (mode >= 0) + printf("%-10s", mode_name); + else + printf("%-10s", "-"); + printf(" %i", status); + printf("\n"); +} + +static int do_status(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct dm_regulator_uclass_plat *uc_pdata; + struct udevice *dev; + int ret; + + if (currdev && (argc < 2 || strcmp(argv[1], "-a"))) { + ret = curr_dev_and_plat(&dev, &uc_pdata, true); + if (ret) + return CMD_RET_FAILURE; + do_status_detail(dev, uc_pdata); + return 0; + } + + /* Show all of them in a list, probing them as needed */ + printf("%-20s %-10s %10s %10s %-10s %s\n", "Name", "Enabled", "uV", "mA", + "Mode", "Status"); + for (ret = uclass_first_device_check(UCLASS_REGULATOR, &dev); dev; + ret = uclass_next_device_check(&dev)) + do_status_line(dev, ret); + + return CMD_RET_SUCCESS; +} + +static int do_value(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + struct dm_regulator_uclass_plat *uc_pdata; + int value; + int force; + int ret; + + ret = curr_dev_and_plat(&dev, &uc_pdata, argc == 1); + if (ret) + return ret; + + if (argc == 1) { + ret = regulator_get_value(dev); + if (ret < 0) { + printf("Regulator: %s - can't get the Voltage!\n", + uc_pdata->name); + return failure(ret); + } + + printf("%d uV\n", ret); + return CMD_RET_SUCCESS; + } + + if (argc == 3) + force = !strcmp("-f", argv[2]); + else + force = 0; + + value = simple_strtoul(argv[1], NULL, 0); + if ((value < uc_pdata->min_uV || value > uc_pdata->max_uV) && !force) { + printf("Value exceeds regulator constraint limits %d..%d uV\n", + uc_pdata->min_uV, uc_pdata->max_uV); + return CMD_RET_FAILURE; + } + + if (!force) + ret = regulator_set_value(dev, value); + else + ret = regulator_set_value_force(dev, value); + if (ret) { + printf("Regulator: %s - can't set the Voltage!\n", + uc_pdata->name); + return failure(ret); + } + + return CMD_RET_SUCCESS; +} + +static int do_current(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + struct dm_regulator_uclass_plat *uc_pdata; + int current; + int ret; + + ret = curr_dev_and_plat(&dev, &uc_pdata, argc == 1); + if (ret) + return ret; + + if (argc == 1) { + ret = regulator_get_current(dev); + if (ret < 0) { + printf("Regulator: %s - can't get the Current!\n", + uc_pdata->name); + return failure(ret); + } + + printf("%d uA\n", ret); + return CMD_RET_SUCCESS; + } + + current = simple_strtoul(argv[1], NULL, 0); + if (current < uc_pdata->min_uA || current > uc_pdata->max_uA) { + printf("Current exceeds regulator constraint limits\n"); + return CMD_RET_FAILURE; + } + + ret = regulator_set_current(dev, current); + if (ret) { + printf("Regulator: %s - can't set the Current!\n", + uc_pdata->name); + return failure(ret); + } + + return CMD_RET_SUCCESS; +} + +static int do_mode(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + struct dm_regulator_uclass_plat *uc_pdata; + int mode; + int ret; + + ret = curr_dev_and_plat(&dev, &uc_pdata, false); + if (ret) + return ret; + + if (argc == 1) { + ret = regulator_get_mode(dev); + if (ret < 0) { + printf("Regulator: %s - can't get the operation mode!\n", + uc_pdata->name); + return failure(ret); + } + + printf("mode id: %d\n", ret); + return CMD_RET_SUCCESS; + } + + mode = simple_strtoul(argv[1], NULL, 0); + + ret = regulator_set_mode(dev, mode); + if (ret) { + printf("Regulator: %s - can't set the operation mode!\n", + uc_pdata->name); + return failure(ret); + } + + return CMD_RET_SUCCESS; +} + +static int do_enable(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + struct dm_regulator_uclass_plat *uc_pdata; + int ret; + + ret = curr_dev_and_plat(&dev, &uc_pdata, true); + if (ret) + return ret; + + ret = regulator_set_enable(dev, true); + if (ret) { + printf("Regulator: %s - can't enable!\n", uc_pdata->name); + return failure(ret); + } + + return CMD_RET_SUCCESS; +} + +static int do_disable(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + struct dm_regulator_uclass_plat *uc_pdata; + int ret; + + ret = curr_dev_and_plat(&dev, &uc_pdata, true); + if (ret) + return ret; + + ret = regulator_set_enable(dev, false); + if (ret) { + printf("Regulator: %s - can't disable!\n", uc_pdata->name); + return failure(ret); + } + + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl subcmd[] = { + U_BOOT_CMD_MKENT(dev, 2, 1, do_dev, "", ""), + U_BOOT_CMD_MKENT(list, 1, 1, do_list, "", ""), + U_BOOT_CMD_MKENT(info, 2, 1, do_info, "", ""), + U_BOOT_CMD_MKENT(status, 2, 1, do_status, "", ""), + U_BOOT_CMD_MKENT(value, 3, 1, do_value, "", ""), + U_BOOT_CMD_MKENT(current, 3, 1, do_current, "", ""), + U_BOOT_CMD_MKENT(mode, 2, 1, do_mode, "", ""), + U_BOOT_CMD_MKENT(enable, 1, 1, do_enable, "", ""), + U_BOOT_CMD_MKENT(disable, 1, 1, do_disable, "", ""), +}; + +static int do_regulator(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cmd; + + argc--; + argv++; + + cmd = find_cmd_tbl(argv[0], subcmd, ARRAY_SIZE(subcmd)); + if (cmd == NULL || argc > cmd->maxargs) + return CMD_RET_USAGE; + + return cmd->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD(regulator, CONFIG_SYS_MAXARGS, 1, do_regulator, + "uclass operations", + "list - list UCLASS regulator devices\n" + "regulator dev [regulator-name] - show/[set] operating regulator device\n" + "regulator info - print constraints info\n" + "regulator status [-a] - print operating status [for all]\n" + "regulator value [val] [-f] - print/[set] voltage value [uV] (force)\n" + "regulator current [val] - print/[set] current value [uA]\n" + "regulator mode [id] - print/[set] operating mode id\n" + "regulator enable - enable the regulator output\n" + "regulator disable - disable the regulator output\n" +); diff --git a/cmd/remoteproc.c b/cmd/remoteproc.c new file mode 100644 index 00000000000..3c5b6a05b1a --- /dev/null +++ b/cmd/remoteproc.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2015 + * Texas Instruments Incorporated - https://www.ti.com/ + */ +#include <command.h> +#include <dm.h> +#include <errno.h> +#include <malloc.h> +#include <remoteproc.h> + +/** + * print_remoteproc_list() - print all the remote processor devices + * + * Return: 0 if no error, else returns appropriate error value. + */ +static int print_remoteproc_list(void) +{ + struct udevice *dev; + struct uclass *uc; + int ret; + char *type; + + ret = uclass_get(UCLASS_REMOTEPROC, &uc); + if (ret) { + printf("Cannot find Remote processor class\n"); + return ret; + } + + uclass_foreach_dev(dev, uc) { + struct dm_rproc_uclass_pdata *uc_pdata; + const struct dm_rproc_ops *ops = rproc_get_ops(dev); + + uc_pdata = dev_get_uclass_plat(dev); + + /* Do not print if rproc is not probed */ + if (!(dev_get_flags(dev) & DM_FLAG_ACTIVATED)) + continue; + + switch (uc_pdata->mem_type) { + case RPROC_INTERNAL_MEMORY_MAPPED: + type = "internal memory mapped"; + break; + default: + type = "unknown"; + break; + } + printf("%d - Name:'%s' type:'%s' supports: %s%s%s%s%s%s\n", + dev_seq(dev), + uc_pdata->name, + type, + ops->load ? "load " : "", + ops->start ? "start " : "", + ops->stop ? "stop " : "", + ops->reset ? "reset " : "", + ops->is_running ? "is_running " : "", + ops->ping ? "ping " : ""); + } + return 0; +} + +/** + * do_rproc_init() - do basic initialization + * @cmdtp: unused + * @flag: unused + * @argc: unused + * @argv: unused + * + * Return: 0 if no error, else returns appropriate error value. + */ +static int do_rproc_init(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int id; + + if (rproc_is_initialized()) { + printf("\tRemote Processors are already initialized\n"); + return CMD_RET_FAILURE; + } + + if (argc == 1) { + if (!rproc_init()) + return 0; + printf("Few Remote Processors failed to be initialized\n"); + } else if (argc == 2) { + id = (int)dectoul(argv[1], NULL); + if (!rproc_dev_init(id)) + return 0; + printf("Remote Processor %d failed to be initialized\n", id); + } + + return CMD_RET_FAILURE; +} + +/** + * do_remoteproc_list() - print list of remote proc devices. + * @cmdtp: unused + * @flag: unused + * @argc: unused + * @argv: unused + * + * Return: 0 if no error, else returns appropriate error value. + */ +static int do_remoteproc_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (print_remoteproc_list()) + return CMD_RET_FAILURE; + + return 0; +} + +/** + * do_remoteproc_load() - Load a remote processor with binary image + * @cmdtp: unused + * @flag: unused + * @argc: argument count for the load function + * @argv: arguments for the load function + * + * Return: 0 if no error, else returns appropriate error value. + */ +static int do_remoteproc_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr, size; + int id, ret; + + if (argc != 4) + return CMD_RET_USAGE; + + id = (int)dectoul(argv[1], NULL); + addr = hextoul(argv[2], NULL); + + size = hextoul(argv[3], NULL); + + if (!size) { + printf("\t Expect some size??\n"); + return CMD_RET_USAGE; + } + + ret = rproc_load(id, addr, size); + printf("Load Remote Processor %d with data@addr=0x%08lx %lu bytes:%s\n", + id, addr, size, ret ? " Failed!" : " Success!"); + + return ret ? CMD_RET_FAILURE : 0; +} + +/** + * do_remoteproc_wrapper() - wrapper for various rproc commands + * @cmdtp: unused + * @flag: unused + * @argc: argument count for the rproc command + * @argv: arguments for the rproc command + * + * Most of the commands just take id as a parameter andinvoke various + * helper routines in remote processor core. by using a set of + * common checks, we can reduce the amount of code used for this. + * + * Return: 0 if no error, else returns appropriate error value. + */ +static int do_remoteproc_wrapper(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int id, ret = CMD_RET_USAGE; + + if (argc != 2) + return CMD_RET_USAGE; + + id = (int)dectoul(argv[1], NULL); + + if (!strcmp(argv[0], "start")) { + ret = rproc_start(id); + } else if (!strcmp(argv[0], "stop")) { + ret = rproc_stop(id); + } else if (!strcmp(argv[0], "reset")) { + ret = rproc_reset(id); + } else if (!strcmp(argv[0], "is_running")) { + ret = rproc_is_running(id); + if (!ret) { + printf("Remote processor is Running\n"); + } else if (ret == 1) { + printf("Remote processor is NOT Running\n"); + ret = 0; + } + /* Else error.. */ + } else if (!strcmp(argv[0], "ping")) { + ret = rproc_ping(id); + if (!ret) { + printf("Remote processor responds 'Pong'\n"); + } else if (ret == 1) { + printf("No response from Remote processor\n"); + ret = 0; + } + /* Else error.. */ + } + + if (ret < 0) + printf("Operation Failed with error (%d)\n", ret); + + return ret ? CMD_RET_FAILURE : 0; +} + +static struct cmd_tbl cmd_remoteproc_sub[] = { + U_BOOT_CMD_MKENT(init, 1, 1, do_rproc_init, + "Enumerate and initialize the remote processor(s)", + "id - ID of the remote processor\n" + "If id is not passed, initialize all the remote processors"), + U_BOOT_CMD_MKENT(list, 0, 1, do_remoteproc_list, + "list remote processors", ""), + U_BOOT_CMD_MKENT(load, 5, 1, do_remoteproc_load, + "Load remote processor with provided image", + "<id> [addr] [size]\n" + "- id: ID of the remote processor(see 'list' cmd)\n" + "- addr: Address in memory of the image to loadup\n" + "- size: Size of the image to loadup\n"), + U_BOOT_CMD_MKENT(start, 1, 1, do_remoteproc_wrapper, + "Start remote processor", + "id - ID of the remote processor (see 'list' cmd)\n"), + U_BOOT_CMD_MKENT(stop, 1, 1, do_remoteproc_wrapper, + "Stop remote processor", + "id - ID of the remote processor (see 'list' cmd)\n"), + U_BOOT_CMD_MKENT(reset, 1, 1, do_remoteproc_wrapper, + "Reset remote processor", + "id - ID of the remote processor (see 'list' cmd)\n"), + U_BOOT_CMD_MKENT(is_running, 1, 1, do_remoteproc_wrapper, + "Check to see if remote processor is running\n", + "id - ID of the remote processor (see 'list' cmd)\n"), + U_BOOT_CMD_MKENT(ping, 1, 1, do_remoteproc_wrapper, + "Ping to communicate with remote processor\n", + "id - ID of the remote processor (see 'list' cmd)\n"), +}; + +/** + * do_remoteproc() - (replace: short desc) + * @cmdtp: unused + * @flag: unused + * @argc: argument count + * @argv: argument list + * + * parses up the command table to invoke the correct command. + * + * Return: 0 if no error, else returns appropriate error value. + */ +static int do_remoteproc(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c = NULL; + + /* Strip off leading 'rproc' command argument */ + argc--; + argv++; + + if (argc) + c = find_cmd_tbl(argv[0], cmd_remoteproc_sub, + ARRAY_SIZE(cmd_remoteproc_sub)); + if (c) + return c->cmd(cmdtp, flag, argc, argv); + + return CMD_RET_USAGE; +} + +U_BOOT_CMD(rproc, 5, 1, do_remoteproc, + "Control operation of remote processors in an SoC", + " [init|list|load|start|stop|reset|is_running|ping]\n" + "\t\t Where:\n" + "\t\t[addr] is a memory address\n" + "\t\t<id> is a numerical identifier for the remote processor\n" + "\t\t provided by 'list' command.\n" + "\t\tNote: Remote processors must be initalized prior to usage\n" + "\t\tNote: Services are dependent on the driver capability\n" + "\t\t 'list' command shows the capability of each device\n" + "\n\tSubcommands:\n" + "\tinit <id> - Enumerate and initalize the remote processor.\n" + "\t if id is not passed, initialize all the remote prcessors\n" + "\tlist - list available remote processors\n" + "\tload <id> [addr] [size]- Load the remote processor with binary\n" + "\t image stored at address [addr] in memory\n" + "\tstart <id> - Start the remote processor(must be loaded)\n" + "\tstop <id> - Stop the remote processor\n" + "\treset <id> - Reset the remote processor\n" + "\tis_running <id> - Reports if the remote processor is running\n" + "\tping <id> - Ping the remote processor for communication\n"); diff --git a/cmd/riscv/Makefile b/cmd/riscv/Makefile new file mode 100644 index 00000000000..1e6ac364e34 --- /dev/null +++ b/cmd/riscv/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_CMD_EXCEPTION) += exception.o +obj-$(CONFIG_CMD_SBI) += sbi.o diff --git a/cmd/riscv/exception.c b/cmd/riscv/exception.c new file mode 100644 index 00000000000..16e335327f1 --- /dev/null +++ b/cmd/riscv/exception.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'exception' command can be used for testing exception handling. + * + * Copyright (c) 2018, Heinrich Schuchardt <xypron.glpk@gmx.de> + */ + +#include <command.h> + +static int do_compressed(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + /* c.li a0, 0; c.li a0, 0 */ + asm volatile (".long 0x45014501\n"); + printf("The system supports compressed instructions.\n"); + return CMD_RET_SUCCESS; +} + +static int do_ebreak(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + asm volatile ("ebreak\n"); + return CMD_RET_FAILURE; +} + +static int do_ialign16(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + asm volatile ( + /* jump skipping 2 bytes */ + ".long 0x0060006f\n" + ".long 0x006f0000\n" + ".long 0x00000060\n" + ); + printf("The system supports 16 bit aligned instructions.\n"); + return CMD_RET_SUCCESS; +} + +static int do_rdcycle(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + printf("cycle = 0x%lx\n", csr_read(CSR_CYCLE)); + + return CMD_RET_SUCCESS; +} + +static int do_unaligned(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + asm volatile ( + "auipc a1, 0\n" + "ori a1, a1, 3\n" + "lw a2, (0)(a1)\n" + ); + printf("The system supports unaligned access.\n"); + return CMD_RET_SUCCESS; +} + +static int do_undefined(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + asm volatile (".word 0xffffffff\n"); + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_sub[] = { + U_BOOT_CMD_MKENT(compressed, CONFIG_SYS_MAXARGS, 1, do_compressed, + "", ""), + U_BOOT_CMD_MKENT(ebreak, CONFIG_SYS_MAXARGS, 1, do_ebreak, + "", ""), + U_BOOT_CMD_MKENT(ialign16, CONFIG_SYS_MAXARGS, 1, do_ialign16, + "", ""), + U_BOOT_CMD_MKENT(rdcycle, CONFIG_SYS_MAXARGS, 1, do_rdcycle, + "", ""), + U_BOOT_CMD_MKENT(unaligned, CONFIG_SYS_MAXARGS, 1, do_unaligned, + "", ""), + U_BOOT_CMD_MKENT(undefined, CONFIG_SYS_MAXARGS, 1, do_undefined, + "", ""), +}; + +U_BOOT_LONGHELP(exception, + "<ex>\n" + " The following exceptions are available:\n" + " compressed - compressed instruction\n" + " ebreak - breakpoint\n" + " ialign16 - 16 bit aligned instruction\n" + " rdcycle - read cycle CSR\n" + " unaligned - load address misaligned\n" + " undefined - illegal instruction\n"); + +#include <exception.h> diff --git a/cmd/riscv/sbi.c b/cmd/riscv/sbi.c new file mode 100644 index 00000000000..b013c8c8d4e --- /dev/null +++ b/cmd/riscv/sbi.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'sbi' command displays information about the SBI implementation. + * + * Copyright (c) 2020, Heinrich Schuchardt <xypron.glpk@gmx.de> + */ + +#include <command.h> +#include <asm/sbi.h> + +struct sbi_imp { + const long id; + const char *name; +}; + +struct sbi_ext { + const u32 id; + const char *name; +}; + +static struct sbi_imp implementations[] = { + { 0, "Berkeley Boot Loader (BBL)" }, + { 1, "OpenSBI" }, + { 2, "Xvisor" }, + { 3, "KVM" }, + { 4, "RustSBI" }, + { 5, "Diosix" }, + { 6, "Coffer" }, + { 7, "Xen Project" }, + { 8, "PolarFire Hart Software Services" }, + { 9, "coreboot" }, + { 10, "oreboot" }, + { 11, "bhyve" }, +}; + +static struct sbi_ext extensions[] = { + { SBI_EXT_0_1_SET_TIMER, "Set Timer" }, + { SBI_EXT_0_1_CONSOLE_PUTCHAR, "Console Putchar" }, + { SBI_EXT_0_1_CONSOLE_GETCHAR, "Console Getchar" }, + { SBI_EXT_0_1_CLEAR_IPI, "Clear IPI" }, + { SBI_EXT_0_1_SEND_IPI, "Send IPI" }, + { SBI_EXT_0_1_REMOTE_FENCE_I, "Remote FENCE.I" }, + { SBI_EXT_0_1_REMOTE_SFENCE_VMA, "Remote SFENCE.VMA" }, + { SBI_EXT_0_1_REMOTE_SFENCE_VMA_ASID, "Remote SFENCE.VMA with ASID" }, + { SBI_EXT_0_1_SHUTDOWN, "System Shutdown" }, + { SBI_EXT_BASE, "SBI Base Functionality" }, + { SBI_EXT_TIME, "Timer Extension" }, + { SBI_EXT_IPI, "IPI Extension" }, + { SBI_EXT_RFENCE, "RFENCE Extension" }, + { SBI_EXT_HSM, "Hart State Management Extension" }, + { SBI_EXT_SRST, "System Reset Extension" }, + { SBI_EXT_PMU, "Performance Monitoring Unit Extension" }, + { SBI_EXT_DBCN, "Debug Console Extension" }, + { SBI_EXT_SUSP, "System Suspend Extension" }, + { SBI_EXT_CPPC, "Collaborative Processor Performance Control Extension" }, + { SBI_EXT_NACL, "Nested Acceleration Extension" }, + { SBI_EXT_STA, "Steal-time Accounting Extension" }, + { SBI_EXT_SSE, "Supervisor Software Events" }, + { SBI_EXT_FWFT, "Firmware Features Extension" }, + { SBI_EXT_DBTR, "Debug Triggers Extension" }, + { SBI_EXT_MPXY, "Message Proxy Extension" }, +}; + +static int do_sbi(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int i, impl_id; + long ret; + long mvendorid, marchid, mimpid; + + ret = sbi_get_spec_version(); + if (ret < 0) { + printf("No SBI 0.2+\n"); + return CMD_RET_FAILURE; + } + printf("SBI %ld.%ld", ret >> 24, ret & 0xffffff); + impl_id = sbi_get_impl_id(); + if (impl_id >= 0) { + for (i = 0; i < ARRAY_SIZE(implementations); ++i) { + if (impl_id == implementations[i].id) { + long vers; + + printf("\n%s ", implementations[i].name); + ret = sbi_get_impl_version(&vers); + if (ret < 0) + break; + switch (impl_id) { + case 1: /* OpenSBI */ + case 8: /* PolarFire Hart Software Services */ + printf("%ld.%ld", + vers >> 16, vers & 0xffff); + break; + case 3: /* KVM */ + case 4: /* RustSBI */ + printf("%ld.%ld.%ld", + vers >> 16, + (vers >> 8) & 0xff, + vers & 0xff); + break; + default: + printf("0x%lx", vers); + break; + } + break; + } + } + if (i == ARRAY_SIZE(implementations)) + printf("\nUnknown implementation ID 0x%x", impl_id); + } + printf("\nMachine:\n"); + ret = sbi_get_mvendorid(&mvendorid); + if (!ret) + printf(" Vendor ID %lx\n", mvendorid); + ret = sbi_get_marchid(&marchid); + if (!ret) + printf(" Architecture ID %lx\n", marchid); + ret = sbi_get_mimpid(&mimpid); + if (!ret) + printf(" Implementation ID %lx\n", mimpid); + printf("Extensions:\n"); + for (i = 0; i < ARRAY_SIZE(extensions); ++i) { + ret = sbi_probe_extension(extensions[i].id); + if (ret > 0) + printf(" %s\n", extensions[i].name); + } + return 0; +} + +U_BOOT_LONGHELP(sbi, + "- display SBI spec version, implementation, and available extensions"); + +U_BOOT_CMD_COMPLETE( + sbi, 1, 0, do_sbi, + "display SBI information", + sbi_help_text, NULL +); diff --git a/cmd/rkmtd.c b/cmd/rkmtd.c new file mode 100644 index 00000000000..a870c119110 --- /dev/null +++ b/cmd/rkmtd.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * + * Driver interface derived from: + * /cmd/host.c + * Copyright (c) 2012, Google Inc. + * + * Copyright (C) 2023 Johan Jonker <jbx6244@gmail.com> + */ + +#include <blk.h> +#include <command.h> +#include <dm.h> +#include <rkmtd.h> +#include <stdio.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> + +static int do_rkmtd_bind(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + const char *label; + int ret; + + argc--; + argv++; + + if (argc < 1) + return CMD_RET_USAGE; + + if (argc > 1) + return CMD_RET_USAGE; + + label = argv[0]; + ret = rkmtd_create_attach_mtd(label, &dev); + if (ret) { + printf("Cannot create device / bind mtd\n"); + return CMD_RET_FAILURE; + } + + return 0; +} + +static struct udevice *parse_rkmtd_label(const char *label) +{ + struct udevice *dev; + + dev = rkmtd_find_by_label(label); + if (!dev) { + int devnum; + char *ep; + + devnum = hextoul(label, &ep); + if (*ep || + uclass_find_device_by_seq(UCLASS_RKMTD, devnum, &dev)) { + printf("No such device '%s'\n", label); + return NULL; + } + } + + return dev; +} + +static int do_rkmtd_unbind(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + const char *label; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + label = argv[1]; + dev = parse_rkmtd_label(label); + if (!dev) + return CMD_RET_FAILURE; + + ret = rkmtd_detach(dev); + if (ret) { + printf("Cannot detach mtd\n"); + return CMD_RET_FAILURE; + } + + ret = device_unbind(dev); + if (ret) { + printf("Cannot unbind device '%s'\n", dev->name); + return CMD_RET_FAILURE; + } + + return 0; +} + +static void show_rkmtd_dev(struct udevice *dev) +{ + struct rkmtd_dev *plat = dev_get_plat(dev); + struct blk_desc *desc; + struct udevice *blk; + int ret; + + printf("%3d ", dev_seq(dev)); + + ret = blk_get_from_parent(dev, &blk); + if (ret) + return; + + desc = dev_get_uclass_plat(blk); + printf("%12lu %-15s\n", (unsigned long)desc->lba, plat->label); +} + +static int do_rkmtd_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + + if (argc < 1) + return CMD_RET_USAGE; + + dev = NULL; + if (argc >= 2) { + dev = parse_rkmtd_label(argv[1]); + if (!dev) + return CMD_RET_FAILURE; + } + + printf("%3s %12s %-15s\n", "dev", "blocks", "label"); + if (dev) { + show_rkmtd_dev(dev); + } else { + struct uclass *uc; + + uclass_id_foreach_dev(UCLASS_RKMTD, dev, uc) + show_rkmtd_dev(dev); + } + + return 0; +} + +static int do_rkmtd_dev(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + const char *label; + + if (argc < 1 || argc > 3) + return CMD_RET_USAGE; + + if (argc == 1) { + struct rkmtd_dev *plat; + + dev = rkmtd_get_cur_dev(); + if (!dev) { + printf("No current rkmtd device\n"); + return CMD_RET_FAILURE; + } + plat = dev_get_plat(dev); + printf("Current rkmtd device: %d: %s\n", dev_seq(dev), + plat->label); + return 0; + } + + label = argv[1]; + dev = parse_rkmtd_label(argv[1]); + if (!dev) + return CMD_RET_FAILURE; + + rkmtd_set_cur_dev(dev); + + return 0; +} + +static struct cmd_tbl cmd_rkmtd_sub[] = { + U_BOOT_CMD_MKENT(bind, 4, 0, do_rkmtd_bind, "", ""), + U_BOOT_CMD_MKENT(unbind, 4, 0, do_rkmtd_unbind, "", ""), + U_BOOT_CMD_MKENT(info, 3, 0, do_rkmtd_info, "", ""), + U_BOOT_CMD_MKENT(dev, 0, 1, do_rkmtd_dev, "", ""), +}; + +static int do_rkmtd(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c; + + argc--; + argv++; + + c = find_cmd_tbl(argv[0], cmd_rkmtd_sub, ARRAY_SIZE(cmd_rkmtd_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + rkmtd, 8, 1, do_rkmtd, + "Rockchip MTD sub-system", + "bind <label> - bind RKMTD device\n" + "rkmtd unbind <label> - unbind RKMTD device\n" + "rkmtd info [<label>] - show all available RKMTD devices\n" + "rkmtd dev [<label>] - show or set current RKMTD device\n" +); diff --git a/cmd/rng.c b/cmd/rng.c new file mode 100644 index 00000000000..4d91094a8ec --- /dev/null +++ b/cmd/rng.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'rng' command prints bytes from the hardware random number generator. + * + * Copyright (c) 2019, Heinrich Schuchardt <xypron.glpk@gmx.de> + */ +#include <command.h> +#include <dm.h> +#include <hexdump.h> +#include <malloc.h> +#include <rng.h> + +static int do_rng(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + size_t n; + u8 buf[64]; + int devnum; + struct udevice *dev; + int ret = CMD_RET_SUCCESS, err; + + if (argc == 2 && !strcmp(argv[1], "list")) { + int idx = 0; + + uclass_foreach_dev_probe(UCLASS_RNG, dev) { + idx++; + printf("RNG #%d - %s\n", dev->seq_, dev->name); + } + + if (!idx) { + log_err("No RNG device\n"); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; + } + + switch (argc) { + case 1: + devnum = 0; + n = 0x40; + break; + case 2: + devnum = hextoul(argv[1], NULL); + n = 0x40; + break; + case 3: + devnum = hextoul(argv[1], NULL); + n = hextoul(argv[2], NULL); + break; + default: + return CMD_RET_USAGE; + } + + if (uclass_get_device_by_seq(UCLASS_RNG, devnum, &dev) || !dev) { + printf("No RNG device\n"); + return CMD_RET_FAILURE; + } + + if (!n) + return 0; + + n = min(n, sizeof(buf)); + + err = dm_rng_read(dev, buf, n); + if (err) { + puts(err == -EINTR ? "Abort\n" : "Reading RNG failed\n"); + ret = CMD_RET_FAILURE; + } else { + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, buf, n); + } + + return ret; +} + +U_BOOT_CMD( + rng, 3, 0, do_rng, + "print bytes from the hardware random number generator", + "list - list all probed rng devices\n" + "rng [dev [n]] - print n random bytes (max 64) read from dev\n" +); diff --git a/cmd/rockusb.c b/cmd/rockusb.c new file mode 100644 index 00000000000..48497aa8764 --- /dev/null +++ b/cmd/rockusb.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017 Eddie Cai <eddie.cai.linux@gmail.com> + */ + +#include <command.h> +#include <console.h> +#include <g_dnl.h> +#include <usb.h> +#include <asm/arch-rockchip/f_rockusb.h> + +static int do_rockusb(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int controller_index, dev_index; + char *usb_controller; + struct udevice *udc; + char *devtype; + char *devnum; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + usb_controller = argv[1]; + controller_index = simple_strtoul(usb_controller, NULL, 0); + + if (argc >= 4) { + devtype = argv[2]; + devnum = argv[3]; + } else { + return CMD_RET_USAGE; + } + dev_index = simple_strtoul(devnum, NULL, 0); + rockusb_dev_init(devtype, dev_index); + + ret = udc_device_get_by_index(controller_index, &udc); + if (ret) { + printf("USB init failed: %d\n", ret); + return CMD_RET_FAILURE; + } + + g_dnl_clear_detach(); + ret = g_dnl_register("usb_dnl_rockusb"); + if (ret) + return CMD_RET_FAILURE; + + if (!g_dnl_board_usb_cable_connected()) { + puts("\rUSB cable not detected, Command exit.\n"); + ret = CMD_RET_FAILURE; + goto exit; + } + + while (1) { + if (g_dnl_detach()) + break; + if (ctrlc()) + break; + dm_usb_gadget_handle_interrupts(udc); + } + ret = CMD_RET_SUCCESS; + +exit: + g_dnl_unregister(); + g_dnl_clear_detach(); + udc_device_put(udc); + + return ret; +} + +U_BOOT_CMD(rockusb, 4, 1, do_rockusb, + "use the rockusb protocol", + "<USB_controller> <devtype> <dev[:part]> e.g. rockusb 0 mmc 0\n" +); diff --git a/cmd/rtc.c b/cmd/rtc.c new file mode 100644 index 00000000000..a931fd9d54f --- /dev/null +++ b/cmd/rtc.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <command.h> +#include <display_options.h> +#include <dm.h> +#include <hexdump.h> +#include <i2c.h> +#include <mapmem.h> +#include <rtc.h> + +#define MAX_RTC_BYTES 32 + +static int do_rtc_read(struct udevice *dev, int argc, char * const argv[]) +{ + u8 buf[MAX_RTC_BYTES]; + int reg, len, ret, r; + + if (argc < 2 || argc > 3) + return CMD_RET_USAGE; + + reg = hextoul(argv[0], NULL); + len = hextoul(argv[1], NULL); + + if (argc == 3) { + u8 *addr; + + addr = map_sysmem(hextoul(argv[2], NULL), len); + ret = dm_rtc_read(dev, reg, addr, len); + unmap_sysmem(addr); + if (ret) { + printf("dm_rtc_read() failed: %d\n", ret); + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; + } + + while (len) { + r = min_t(int, len, sizeof(buf)); + ret = dm_rtc_read(dev, reg, buf, r); + if (ret) { + printf("dm_rtc_read() failed: %d\n", ret); + return CMD_RET_FAILURE; + } + print_buffer(reg, buf, 1, r, 0); + len -= r; + reg += r; + } + + return CMD_RET_SUCCESS; +} + +static int do_rtc_write(struct udevice *dev, int argc, char * const argv[]) +{ + u8 buf[MAX_RTC_BYTES]; + int reg, len, ret; + const char *s; + int slen; + + if (argc < 2 || argc > 3) + return CMD_RET_USAGE; + + reg = hextoul(argv[0], NULL); + + if (argc == 3) { + u8 *addr; + + len = hextoul(argv[1], NULL); + addr = map_sysmem(hextoul(argv[2], NULL), len); + ret = dm_rtc_write(dev, reg, addr, len); + unmap_sysmem(addr); + if (ret) { + printf("dm_rtc_write() failed: %d\n", ret); + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; + } + + s = argv[1]; + slen = strlen(s); + + if (slen % 2) { + printf("invalid hex string\n"); + return CMD_RET_FAILURE; + } + + while (slen) { + len = min_t(int, slen / 2, sizeof(buf)); + if (hex2bin(buf, s, len)) { + printf("invalid hex string\n"); + return CMD_RET_FAILURE; + } + + ret = dm_rtc_write(dev, reg, buf, len); + if (ret) { + printf("dm_rtc_write() failed: %d\n", ret); + return CMD_RET_FAILURE; + } + s += 2 * len; + slen -= 2 * len; + } + + return CMD_RET_SUCCESS; +} + +int do_rtc(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + static int curr_rtc; + struct udevice *dev; + int ret, idx; + + if (argc < 2) + return CMD_RET_USAGE; + + argc--; + argv++; + + if (!strcmp(argv[0], "list")) { + struct uclass *uc; + + idx = 0; + uclass_id_foreach_dev(UCLASS_RTC, dev, uc) { + printf("RTC #%d - %s\n", idx++, dev->name); + } + if (!idx) { + printf("*** no RTC devices available ***\n"); + return CMD_RET_FAILURE; + } + return CMD_RET_SUCCESS; + } + + idx = curr_rtc; + if (!strcmp(argv[0], "dev") && argc >= 2) + idx = dectoul(argv[1], NULL); + + ret = uclass_get_device(UCLASS_RTC, idx, &dev); + if (ret) { + printf("Cannot find RTC #%d: err=%d\n", idx, ret); + return CMD_RET_FAILURE; + } + + if (!strcmp(argv[0], "dev")) { + /* Show the existing or newly selected RTC */ + if (argc >= 2) + curr_rtc = idx; + printf("RTC #%d - %s\n", idx, dev->name); + return CMD_RET_SUCCESS; + } + + if (!strcmp(argv[0], "read")) + return do_rtc_read(dev, argc - 1, argv + 1); + + if (!strcmp(argv[0], "write")) + return do_rtc_write(dev, argc - 1, argv + 1); + + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + rtc, 5, 0, do_rtc, + "RTC subsystem", + "list - show available rtc devices\n" + "rtc dev [n] - show or set current rtc device\n" + "rtc read <reg> <count> - read and display 8-bit registers starting at <reg>\n" + "rtc read <reg> <count> <addr> - read 8-bit registers starting at <reg> to memory <addr>\n" + "rtc write <reg> <hexstring> - write 8-bit registers starting at <reg>\n" + "rtc write <reg> <count> <addr> - write from memory <addr> to 8-bit registers starting at <reg>\n" +); diff --git a/cmd/sandbox/Makefile b/cmd/sandbox/Makefile new file mode 100644 index 00000000000..24df023ece9 --- /dev/null +++ b/cmd/sandbox/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_CMD_EXCEPTION) += exception.o diff --git a/cmd/sandbox/exception.c b/cmd/sandbox/exception.c new file mode 100644 index 00000000000..e015acf60e2 --- /dev/null +++ b/cmd/sandbox/exception.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'exception' command can be used for testing exception handling. + * + * Copyright (c) 2020, Heinrich Schuchardt <xypron.glpk@gmx.de> + */ + +#include <command.h> +#include <env.h> + +static int do_sigsegv(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u8 *ptr = NULL; + + *ptr = 0; + return CMD_RET_FAILURE; +} + +static int do_undefined(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ +#ifdef __powerpc__ + asm volatile (".long 0xffffffff\n"); +#else + asm volatile (".word 0xffff\n"); +#endif + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_sub[] = { + U_BOOT_CMD_MKENT(sigsegv, CONFIG_SYS_MAXARGS, 1, do_sigsegv, + "", ""), + U_BOOT_CMD_MKENT(undefined, CONFIG_SYS_MAXARGS, 1, do_undefined, + "", ""), +}; + +U_BOOT_LONGHELP(exception, + "<ex>\n" + " The following exceptions are available:\n" + " undefined - undefined instruction\n" + " sigsegv - illegal memory access\n"); + +#include <exception.h> diff --git a/cmd/sata.c b/cmd/sata.c new file mode 100644 index 00000000000..8b923f9378b --- /dev/null +++ b/cmd/sata.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2000-2005, DENX Software Engineering + * Wolfgang Denk <wd@denx.de> + * Copyright (C) Procsys. All rights reserved. + * Mushtaq Khan <mushtaq_k@procsys.com> + * <mushtaqk_921@yahoo.co.in> + * Copyright (C) 2008 Freescale Semiconductor, Inc. + * Dave Liu <daveliu@freescale.com> + */ + +#include <ahci.h> +#include <blk.h> +#include <dm.h> +#include <command.h> +#include <part.h> +#include <sata.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> + +static int sata_curr_device = -1; + +int sata_remove(int devnum) +{ +#ifdef CONFIG_AHCI + struct udevice *dev; + int rc; + + blk_unbind_all(UCLASS_AHCI); + + rc = uclass_find_device(UCLASS_AHCI, devnum, &dev); + if (!rc && !dev) + rc = uclass_find_first_device(UCLASS_AHCI, &dev); + if (rc || !dev) { + printf("Cannot find SATA device %d (err=%d)\n", devnum, rc); + return CMD_RET_FAILURE; + } + + rc = device_remove(dev, DM_REMOVE_NORMAL); + if (rc) { + printf("Cannot remove SATA device '%s' (err=%d)\n", dev->name, + rc); + return CMD_RET_FAILURE; + } + + return 0; +#else + return sata_stop(); +#endif +} + +int sata_probe(int devnum) +{ +#ifdef CONFIG_AHCI + struct udevice *dev; + int rc; + + rc = uclass_get_device(UCLASS_AHCI, devnum, &dev); + if (rc) + rc = uclass_find_first_device(UCLASS_AHCI, &dev); + if (rc) { + printf("Cannot probe SATA device %d (err=%d)\n", devnum, rc); + return CMD_RET_FAILURE; + } + if (!dev) { + printf("No SATA device found!\n"); + return CMD_RET_FAILURE; + } + rc = sata_scan(dev); + if (rc) { + printf("Cannot scan SATA device %d (err=%d)\n", devnum, rc); + return CMD_RET_FAILURE; + } + + return 0; +#else + return sata_initialize() < 0 ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +#endif +} + +static int do_sata(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int rc = 0; + + if (argc >= 2) { + int devnum = 0; + + if (argc == 3) + devnum = (int)dectoul(argv[2], NULL); + if (!strcmp(argv[1], "stop")) + return sata_remove(devnum); + + if (!strcmp(argv[1], "init")) { + if (sata_curr_device != -1) { + rc = sata_remove(devnum); + if (rc) + return rc; + } + + return sata_probe(devnum); + } + } + + /* If the user has not yet run `sata init`, do it now */ + if (sata_curr_device == -1) { + rc = sata_probe(0); + if (rc) + return rc; + sata_curr_device = 0; + } + + return blk_common_cmd(argc, argv, UCLASS_AHCI, &sata_curr_device); +} + +U_BOOT_CMD( + sata, 5, 1, do_sata, + "SATA sub system", + "init - init SATA sub system\n" + "sata stop [dev] - disable SATA sub system or device\n" + "sata info - show available SATA devices\n" + "sata device [dev] - show or set current device\n" + "sata part [dev] - print partition table\n" + "sata read addr blk# cnt\n" + "sata write addr blk# cnt" +); diff --git a/cmd/sb.c b/cmd/sb.c new file mode 100644 index 00000000000..79f3fb0aacd --- /dev/null +++ b/cmd/sb.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2018, Google Inc. + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <dm.h> +#include <spl.h> +#include <asm/cpu.h> +#include <asm/global_data.h> +#include <asm/state.h> + +static int do_sb_handoff(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ +#if CONFIG_IS_ENABLED(HANDOFF) + if (gd->spl_handoff) + printf("SPL handoff magic %lx\n", gd->spl_handoff->arch.magic); + else + printf("SPL handoff info not received\n"); + + return 0; +#else + printf("Command not supported\n"); + + return CMD_RET_USAGE; +#endif +} + +static int do_sb_map(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + sandbox_map_list(); + + return 0; +} + +static int do_sb_state(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct sandbox_state *state; + + state = state_get_current(); + state_show(state); + + return 0; +} + +U_BOOT_LONGHELP(sb, + "handoff - Show handoff data received from SPL\n" + "sb map - Show mapped memory\n" + "sb state - Show sandbox state"); + +U_BOOT_CMD_WITH_SUBCMDS(sb, "Sandbox status commands", sb_help_text, + U_BOOT_SUBCMD_MKENT(handoff, 1, 1, do_sb_handoff), + U_BOOT_SUBCMD_MKENT(map, 1, 1, do_sb_map), + U_BOOT_SUBCMD_MKENT(state, 1, 1, do_sb_state)); diff --git a/cmd/scmi.c b/cmd/scmi.c new file mode 100644 index 00000000000..d0498b816aa --- /dev/null +++ b/cmd/scmi.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SCMI (System Control and Management Interface) utility command + * + * Copyright (c) 2023 Linaro Limited + * Author: AKASHI Takahiro + */ + +#include <command.h> +#include <exports.h> +#include <scmi_agent.h> +#include <scmi_agent-uclass.h> +#include <stdlib.h> +#include <asm/types.h> +#include <dm/device.h> +#include <dm/uclass.h> /* uclass_get_device */ +#include <linux/bitfield.h> +#include <linux/bitops.h> + +struct { + enum scmi_std_protocol id; + const char *name; +} protocol_name[] = { + {SCMI_PROTOCOL_ID_BASE, "Base"}, + {SCMI_PROTOCOL_ID_POWER_DOMAIN, "Power domain management"}, + {SCMI_PROTOCOL_ID_SYSTEM, "System power management"}, + {SCMI_PROTOCOL_ID_PERF, "Performance domain management"}, + {SCMI_PROTOCOL_ID_CLOCK, "Clock management"}, + {SCMI_PROTOCOL_ID_SENSOR, "Sensor management"}, + {SCMI_PROTOCOL_ID_RESET_DOMAIN, "Reset domain management"}, + {SCMI_PROTOCOL_ID_VOLTAGE_DOMAIN, "Voltage domain management"}, + {SCMI_PROTOCOL_ID_PINCTRL, "Pin control management"}, +}; + +/** + * get_agent() - get SCMI agent device + * + * Return: Pointer to SCMI agent device on success, NULL on failure + */ +static struct udevice *get_agent(void) +{ + struct udevice *agent; + + if (uclass_get_device(UCLASS_SCMI_AGENT, 0, &agent)) { + printf("Cannot find any SCMI agent\n"); + return NULL; + } + + return agent; +} + +/** + * get_base_proto() - get SCMI base protocol device + * @agent: SCMI agent device + * + * Return: Pointer to SCMI base protocol device on success, + * NULL on failure + */ +static struct udevice *get_base_proto(struct udevice *agent) +{ + struct udevice *base_proto; + + if (!agent) { + agent = get_agent(); + if (!agent) + return NULL; + } + + base_proto = scmi_get_protocol(agent, SCMI_PROTOCOL_ID_BASE); + if (!base_proto) { + printf("SCMI base protocol not found\n"); + return NULL; + } + + return base_proto; +} + +/** + * get_proto_name() - get the name of SCMI protocol + * + * @id: SCMI Protocol ID + * + * Get the printable name of the protocol, @id + * + * Return: Name string on success, NULL on failure + */ +static const char *get_proto_name(enum scmi_std_protocol id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(protocol_name); i++) + if (id == protocol_name[i].id) + return protocol_name[i].name; + + return NULL; +} + +/** + * do_scmi_info() - get the information of SCMI services + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * + * Get the information of SCMI services using various interfaces + * provided by the Base protocol. + * + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + */ +static int do_scmi_info(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + struct udevice *agent, *base_proto; + u32 agent_id, num_protocols; + u8 *agent_name, *protocols; + int i, ret; + + if (argc != 1) + return CMD_RET_USAGE; + + agent = get_agent(); + if (!agent) + return CMD_RET_FAILURE; + base_proto = get_base_proto(agent); + if (!base_proto) + return CMD_RET_FAILURE; + + printf("SCMI device: %s\n", agent->name); + printf(" protocol version: 0x%x\n", scmi_version(agent)); + printf(" # of agents: %d\n", scmi_num_agents(agent)); + for (i = 0; i < scmi_num_agents(agent); i++) { + ret = scmi_base_discover_agent(base_proto, i, &agent_id, + &agent_name); + if (ret) { + if (ret != -EOPNOTSUPP) + printf("base_discover_agent() failed for id: %d (%d)\n", + i, ret); + break; + } + printf(" %c%2d: %s\n", i == scmi_agent_id(agent) ? '>' : ' ', + i, agent_name); + free(agent_name); + } + printf(" # of protocols: %d\n", scmi_num_protocols(agent)); + num_protocols = scmi_num_protocols(agent); + protocols = scmi_protocols(agent); + if (protocols) + for (i = 0; i < num_protocols; i++) + printf(" %s\n", get_proto_name(protocols[i])); + printf(" vendor: %s\n", scmi_vendor(agent)); + printf(" sub vendor: %s\n", scmi_sub_vendor(agent)); + printf(" impl version: 0x%x\n", scmi_impl_version(agent)); + + return CMD_RET_SUCCESS; +} + +/** + * do_scmi_set_dev() - set access permission to device + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * + * Set access permission to device with SCMI_BASE_SET_DEVICE_PERMISSIONS + * + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + */ +static int do_scmi_set_dev(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + u32 agent_id, device_id, flags, attributes; + char *end; + struct udevice *base_proto; + int ret; + + if (argc != 4) + return CMD_RET_USAGE; + + agent_id = simple_strtoul(argv[1], &end, 16); + if (*end != '\0') + return CMD_RET_USAGE; + + device_id = simple_strtoul(argv[2], &end, 16); + if (*end != '\0') + return CMD_RET_USAGE; + + flags = simple_strtoul(argv[3], &end, 16); + if (*end != '\0') + return CMD_RET_USAGE; + + base_proto = get_base_proto(NULL); + if (!base_proto) + return CMD_RET_FAILURE; + + ret = scmi_base_protocol_message_attrs(base_proto, + SCMI_BASE_SET_DEVICE_PERMISSIONS, + &attributes); + if (ret) { + printf("This operation is not supported\n"); + return CMD_RET_FAILURE; + } + + ret = scmi_base_set_device_permissions(base_proto, agent_id, + device_id, flags); + if (ret) { + printf("%s access to device:%u failed (%d)\n", + flags ? "Allowing" : "Denying", device_id, ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +/** + * do_scmi_set_proto() - set protocol permission to device + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * + * Set protocol permission to device with SCMI_BASE_SET_PROTOCOL_PERMISSIONS + * + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + */ +static int do_scmi_set_proto(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + u32 agent_id, device_id, protocol_id, flags, attributes; + char *end; + struct udevice *base_proto; + int ret; + + if (argc != 5) + return CMD_RET_USAGE; + + agent_id = simple_strtoul(argv[1], &end, 16); + if (*end != '\0') + return CMD_RET_USAGE; + + device_id = simple_strtoul(argv[2], &end, 16); + if (*end != '\0') + return CMD_RET_USAGE; + + protocol_id = simple_strtoul(argv[3], &end, 16); + if (*end != '\0') + return CMD_RET_USAGE; + + flags = simple_strtoul(argv[4], &end, 16); + if (*end != '\0') + return CMD_RET_USAGE; + + base_proto = get_base_proto(NULL); + if (!base_proto) + return CMD_RET_FAILURE; + + ret = scmi_base_protocol_message_attrs(base_proto, + SCMI_BASE_SET_PROTOCOL_PERMISSIONS, + &attributes); + if (ret) { + printf("This operation is not supported\n"); + return CMD_RET_FAILURE; + } + + ret = scmi_base_set_protocol_permissions(base_proto, agent_id, + device_id, protocol_id, + flags); + if (ret) { + printf("%s access to protocol:0x%x on device:%u failed (%d)\n", + flags ? "Allowing" : "Denying", protocol_id, device_id, + ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +/** + * do_scmi_reset() - reset platform resource settings + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * + * Reset platform resource settings with BASE_RESET_AGENT_CONFIGURATION + * + * Return: CMD_RET_SUCCESS on success, CMD_RET_RET_FAILURE on failure + */ +static int do_scmi_reset(struct cmd_tbl *cmdtp, int flag, int argc, + char * const argv[]) +{ + u32 agent_id, flags, attributes; + char *end; + struct udevice *base_proto; + int ret; + + if (argc != 3) + return CMD_RET_USAGE; + + agent_id = simple_strtoul(argv[1], &end, 16); + if (*end != '\0') + return CMD_RET_USAGE; + + flags = simple_strtoul(argv[2], &end, 16); + if (*end != '\0') + return CMD_RET_USAGE; + + base_proto = get_base_proto(NULL); + if (!base_proto) + return CMD_RET_FAILURE; + + ret = scmi_base_protocol_message_attrs(base_proto, + SCMI_BASE_RESET_AGENT_CONFIGURATION, + &attributes); + if (ret) { + printf("Reset is not supported\n"); + return CMD_RET_FAILURE; + } + + ret = scmi_base_reset_agent_configuration(base_proto, agent_id, flags); + if (ret) { + printf("Reset failed (%d)\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl cmd_scmi_sub[] = { + U_BOOT_CMD_MKENT(info, CONFIG_SYS_MAXARGS, 1, + do_scmi_info, "", ""), + U_BOOT_CMD_MKENT(perm_dev, CONFIG_SYS_MAXARGS, 1, + do_scmi_set_dev, "", ""), + U_BOOT_CMD_MKENT(perm_proto, CONFIG_SYS_MAXARGS, 1, + do_scmi_set_proto, "", ""), + U_BOOT_CMD_MKENT(reset, CONFIG_SYS_MAXARGS, 1, + do_scmi_reset, "", ""), +}; + +/** + * do_scmi() - SCMI utility + * + * @cmdtp: Command table + * @flag: Command flag + * @argc: Number of arguments + * @argv: Argument array + * + * Provide user interfaces to SCMI protocols. + * + * Return: CMD_RET_SUCCESS on success, + * CMD_RET_USAGE or CMD_RET_RET_FAILURE on failure + */ +static int do_scmi(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct cmd_tbl *cp; + + if (argc < 2) + return CMD_RET_USAGE; + + argc--; argv++; + + cp = find_cmd_tbl(argv[0], cmd_scmi_sub, ARRAY_SIZE(cmd_scmi_sub)); + if (!cp) + return CMD_RET_USAGE; + + return cp->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_LONGHELP(scmi, + " - SCMI utility\n" + " info - get the info of SCMI services\n" + " perm_dev <agent-id in hex> <device-id in hex> <flags in hex>\n" + " - set access permission to device\n" + " perm_proto <agent-id in hex> <device-id in hex> <protocol-id in hex> <flags in hex>\n" + " - set protocol permission to device\n" + " reset <agent-id in hex> <flags in hex>\n" + " - reset platform resource settings\n"); + +U_BOOT_CMD(scmi, CONFIG_SYS_MAXARGS, 0, do_scmi, "SCMI utility", + scmi_help_text); diff --git a/cmd/scp03.c b/cmd/scp03.c new file mode 100644 index 00000000000..9c749d19af8 --- /dev/null +++ b/cmd/scp03.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2021, Foundries.IO + * + */ + +#include <command.h> +#include <env.h> +#include <scp03.h> + +int do_scp03_enable(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc != 1) + return CMD_RET_USAGE; + + if (tee_enable_scp03()) { + printf("TEE failed to enable SCP03\n"); + return CMD_RET_FAILURE; + } + + printf("SCP03 is enabled\n"); + + return CMD_RET_SUCCESS; +} + +int do_scp03_provision(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc != 1) + return CMD_RET_USAGE; + + if (tee_provision_scp03()) { + printf("TEE failed to provision SCP03 keys\n"); + return CMD_RET_FAILURE; + } + + printf("SCP03 is provisioned\n"); + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(scp03, + "provides a command to enable SCP03 and provision the SCP03 keys\n" + " enable - enable SCP03 on the TEE\n" + " provision - provision SCP03 on the TEE\n"); + +U_BOOT_CMD_WITH_SUBCMDS(scp03, "Secure Channel Protocol 03 control", + scp03_help_text, + U_BOOT_SUBCMD_MKENT(enable, 1, 1, do_scp03_enable), + U_BOOT_SUBCMD_MKENT(provision, 1, 1, do_scp03_provision)); diff --git a/cmd/scsi.c b/cmd/scsi.c new file mode 100644 index 00000000000..9f7613424e5 --- /dev/null +++ b/cmd/scsi.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2001 + * Denis Peter, MPL AG Switzerland + */ + +/* + * SCSI support. + */ +#include <blk.h> +#include <command.h> +#include <scsi.h> + +static int scsi_curr_dev; /* current device */ + +/* + * scsi boot command intepreter. Derived from diskboot + */ +static int do_scsiboot(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return common_diskboot(cmdtp, "scsi", argc, argv); +} + +/* + * scsi command intepreter + */ +static int do_scsi(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + if (argc == 2) { + if (strncmp(argv[1], "res", 3) == 0) { + printf("\nReset SCSI\n"); + ret = scsi_scan(true); + if (ret) + return CMD_RET_FAILURE; + return ret; + } + if (strncmp(argv[1], "scan", 4) == 0) { + ret = scsi_scan(true); + if (ret) + return CMD_RET_FAILURE; + return ret; + } + } + + return blk_common_cmd(argc, argv, UCLASS_SCSI, &scsi_curr_dev); +} + +U_BOOT_CMD( + scsi, 5, 1, do_scsi, + "SCSI sub-system", + "reset - reset SCSI controller\n" + "scsi info - show available SCSI devices\n" + "scsi scan - (re-)scan SCSI bus\n" + "scsi device [dev] - show or set current device\n" + "scsi part [dev] - print partition table of one or all SCSI devices\n" + "scsi read addr blk# cnt - read `cnt' blocks starting at block `blk#'\n" + " to memory address `addr'\n" + "scsi write addr blk# cnt - write `cnt' blocks starting at block\n" + " `blk#' from memory address `addr'\n" + "scsi erase blk# cnt - erase `cnt' blocks starting at block `blk#'" +); + +U_BOOT_CMD( + scsiboot, 3, 1, do_scsiboot, + "boot from SCSI device", + "loadAddr dev:part" +); diff --git a/cmd/seama.c b/cmd/seama.c new file mode 100644 index 00000000000..d6287978090 --- /dev/null +++ b/cmd/seama.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2023 Linus Walleij <linus.walleij@linaro.org> + * Support for the "SEAttle iMAge" SEAMA NAND image format + */ + +#include <command.h> +#include <env.h> +#include <nand.h> + +/* + * All SEAMA data is stored in the flash in "network endianness" + * i.e. big endian, which means that it needs to be byte-swapped + * on all little endian platforms. + * + * structure for a SEAMA entity in NAND flash: + * + * 32 bit SEAMA magic 0x5EA3A417 + * 16 bit reserved + * 16 bit metadata size (following the header) + * 32 bit image size + * 16 bytes MD5 digest of the image + * meta data + * ... image data ... + * + * Then if a new SEAMA magic follows, that is the next image. + */ + +#define SEAMA_MAGIC 0x5EA3A417 +#define SEAMA_HDR_NO_META_SZ 28 +#define SEAMA_MAX_META_SZ (1024 - SEAMA_HDR_NO_META_SZ) + +struct seama_header { + u32 magic; + u32 meta_size; + u32 image_size; + u8 md5[16]; + u8 metadata[SEAMA_MAX_META_SZ]; +}; + +static struct seama_header shdr; + +static int env_set_val(const char *varname, ulong val) +{ + int ret; + + ret = env_set_hex(varname, val); + if (ret) + printf("Failed to %s env var\n", varname); + + return ret; +} + +static int do_seama_load_image(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct mtd_info *mtd; + uintptr_t load_addr; + unsigned long image_index; + u32 len; + size_t readsz; + int ret; + u32 *start; + u32 *offset; + u32 *end; + u32 tmp; + + if (argc < 2 || argc > 3) + return CMD_RET_USAGE; + + load_addr = hextoul(argv[1], NULL); + if (!load_addr) { + printf("Invalid load address\n"); + return CMD_RET_USAGE; + } + + /* Can be 0 for first image */ + image_index = hextoul(argv[2], NULL); + + /* We only support one NAND, the first one */ + nand_curr_device = 0; + mtd = get_nand_dev_by_index(0); + if (!mtd) { + printf("NAND Device 0 not available\n"); + return CMD_RET_FAILURE; + } + +#ifdef CONFIG_SYS_NAND_SELECT_DEVICE + board_nand_select_device(mtd_to_nand(mtd), 0); +#endif + + printf("Loading SEAMA image %lu from %s\n", image_index, mtd->name); + + readsz = sizeof(shdr); + offset = 0; + ret = nand_read_skip_bad(mtd, 0, &readsz, NULL, mtd->size, + (u_char *)&shdr); + if (ret) { + printf("Read error reading SEAMA header\n"); + return CMD_RET_FAILURE; + } + + if (shdr.magic != SEAMA_MAGIC) { + printf("Invalid SEAMA image magic: 0x%08x\n", shdr.magic); + return CMD_RET_FAILURE; + } + + /* Only the lower 16 bits are valid */ + shdr.meta_size &= 0xFFFF; + + if (env_set_val("seama_image_size", 0)) + return CMD_RET_FAILURE; + + printf("SEMA IMAGE:\n"); + printf(" metadata size %d\n", shdr.meta_size); + printf(" image size %d\n", shdr.image_size); + printf(" checksum %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + shdr.md5[0], shdr.md5[1], shdr.md5[2], shdr.md5[3], + shdr.md5[4], shdr.md5[5], shdr.md5[6], shdr.md5[7], + shdr.md5[8], shdr.md5[9], shdr.md5[10], shdr.md5[11], + shdr.md5[12], shdr.md5[13], shdr.md5[14], shdr.md5[15]); + + /* TODO: handle metadata if needed */ + + len = shdr.image_size; + if (env_set_val("seama_image_size", len)) + return CMD_RET_FAILURE; + + /* We need to include the header (read full pages) */ + readsz = shdr.image_size + SEAMA_HDR_NO_META_SZ + shdr.meta_size; + ret = nand_read_skip_bad(mtd, 0, &readsz, NULL, mtd->size, + (u_char *)load_addr); + if (ret) { + printf("Read error reading SEAMA main image\n"); + return CMD_RET_FAILURE; + } + + /* We use a temporary variable tmp to avoid to hairy casts */ + start = (u32 *)load_addr; + tmp = (u32)start; + tmp += SEAMA_HDR_NO_META_SZ + shdr.meta_size; + offset = (u32 *)tmp; + tmp += shdr.image_size; + end = (u32 *)tmp; + + printf("Decoding SEAMA image 0x%08x..0x%08x to 0x%08x\n", + (u32)offset, (u32)end, (u32)start); + for (; start < end; start++, offset++) + *start = be32_to_cpu(*offset); + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD + (seama, 3, 1, do_seama_load_image, + "Load the SEAMA image and sets envs", + "seama <addr> <imageindex>\n" +); diff --git a/cmd/setexpr.c b/cmd/setexpr.c new file mode 100644 index 00000000000..c45fa85c887 --- /dev/null +++ b/cmd/setexpr.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * Copyright 2013 Wolfgang Denk <wd@denx.de> + */ + +/* + * This file provides a shell like 'expr' function to return. + */ + +#include <config.h> +#include <command.h> +#include <ctype.h> +#include <env.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <vsprintf.h> +#include <linux/errno.h> +#include <linux/sizes.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; + }; +}; + +/** + * arg_set_str() - copy string to expression argument + * + * The string is truncated to 64 KiB plus NUL terminator. + * + * @p: pointer to string + * @argp: pointer to expression argument + * Return: 0 on success, -ENOMEM if out of memory + */ +static int arg_set_str(void *p, struct expr_arg *argp) +{ + int len; + char *str; + + /* Maximum string length of 64 KiB plus NUL terminator */ + len = strnlen((char *)p, SZ_64K) + 1; + str = malloc(len); + if (!str) { + printf("Out of memory\n"); + return -ENOMEM; + } + memcpy(str, p, len); + str[len - 1] = '\0'; + argp->sval = str; + return 0; +} + +static int get_arg(char *s, int w, struct expr_arg *argp) +{ + struct expr_arg arg; + int ret; + + /* + * If the parameter starts with a '*' then assume it is a pointer to + * the value we want. + */ + if (s[0] == '*') { + ulong *p; + ulong addr; + ulong val; + + addr = hextoul(&s[1], NULL); + switch (w) { + case 1: + p = map_sysmem(addr, sizeof(uchar)); + val = (ulong)*(uchar *)p; + unmap_sysmem(p); + arg.ival = val; + break; + case 2: + p = map_sysmem(addr, sizeof(ushort)); + val = (ulong)*(ushort *)p; + unmap_sysmem(p); + arg.ival = val; + break; + case CMD_DATA_SIZE_STR: + p = map_sysmem(addr, SZ_64K); + ret = arg_set_str(p, &arg); + unmap_sysmem(p); + if (ret) + return ret; + break; + case 4: + p = map_sysmem(addr, sizeof(u32)); + val = *(u32 *)p; + unmap_sysmem(p); + arg.ival = val; + break; + default: + p = map_sysmem(addr, sizeof(ulong)); + val = *p; + unmap_sysmem(p); + arg.ival = val; + break; + } + } else { + if (w == CMD_DATA_SIZE_STR) { + ret = arg_set_str(s, &arg); + if (ret) + return ret; + } else { + arg.ival = hextoul(s, NULL); + } + } + *argp = arg; + + return 0; +} + +#ifdef CONFIG_REGEX + +#include <slre.h> + +/* + * memstr - Find the first substring in memory + * @s1: The string to be searched + * @s2: The string to search for + * + * Similar to and based on strstr(), + * but strings do not need to be NUL terminated. + */ +static char *memstr(const char *s1, int l1, const char *s2, int l2) +{ + if (!l2) + return (char *)s1; + + while (l1 >= l2) { + l1--; + if (!memcmp(s1, s2, l2)) + return (char *)s1; + s1++; + } + return NULL; +} + +/** + * substitute() - Substitute part of one string with another + * + * This updates @string so that the first occurrence of @old is replaced with + * @new + * + * @string: String buffer containing string to update at the start + * @slen: Pointer to current string length, updated on success + * @ssize: Size of string buffer + * @old: Old string to find in the buffer (no terminator needed) + * @olen: Length of @old excluding terminator + * @new: New string to replace @old with + * @nlen: Length of @new excluding terminator + * Return: pointer to immediately after the copied @new in @string, or NULL if + * no replacement took place + */ +static char *substitute(char *string, int *slen, int ssize, + const char *old, int olen, const char *new, int nlen) +{ + char *p = memstr(string, *slen, old, olen); + + if (p == NULL) + return NULL; + + debug("## Match at pos %ld: match len %d, subst len %d\n", + (long)(p - string), olen, nlen); + + /* make sure replacement matches */ + if (*slen + nlen - olen > ssize) { + printf("## error: substitution buffer overflow\n"); + return NULL; + } + + /* move tail if needed */ + if (olen != nlen) { + int tail, len; + + len = (olen > nlen) ? olen : nlen; + + tail = ssize - (p + len - string); + + debug("## tail len %d\n", tail); + + memmove(p + nlen, p + olen, tail); + } + + /* insert substitute */ + memcpy(p, new, nlen); + + *slen += nlen - olen; + + return p + nlen; +} + +int setexpr_regex_sub(char *data, uint data_size, char *nbuf, uint nbuf_size, + const char *r, const char *s, bool global) +{ + struct slre slre; + char *datap = data; + int res, len, nlen, loop; + + if (slre_compile(&slre, r) == 0) { + printf("Error compiling regex: %s\n", slre.err_str); + return 1; + } + + len = strlen(data); + for (loop = 0;; loop++) { + struct cap caps[slre.num_caps + 2]; + const char *old; + char *np; + int i, olen; + + (void) memset(caps, 0, sizeof(caps)); + + res = slre_match(&slre, datap, len - (datap - data), caps); + + debug("Result: %d\n", res); + + for (i = 0; i <= slre.num_caps; i++) { + if (caps[i].len > 0) { + debug("Substring %d: [%.*s]\n", i, + caps[i].len, caps[i].ptr); + } + } + + if (res == 0) { + if (loop == 0) { + debug("%s: No match\n", data); + } else { + debug("## MATCH ## %s\n", data); + } + break; + } + + if (!s) + return 1; + + old = caps[0].ptr; + olen = caps[0].len; + nlen = strlen(s); + + if (nlen + 1 >= nbuf_size) { + printf("## error: pattern buffer overflow: have %d, need %d\n", + nbuf_size, nlen + 1); + return 1; + } + strcpy(nbuf, s); + + debug("## SUBST(1) ## %s\n", nbuf); + + /* + * Handle back references + * + * Support for \0 ... \9, where \0 is the + * whole matched pattern (similar to &). + * + * Implementation is a bit simpleminded as + * backrefs are substituted sequentially, one + * by one. This will lead to somewhat + * unexpected results if the replacement + * strings contain any \N strings then then + * may get substitued, too. We accept this + * restriction for the sake of simplicity. + */ + for (i = 0; i < 10; ++i) { + char backref[2] = { + '\\', + '0', + }; + + if (caps[i].len == 0) + break; + + backref[1] += i; + + debug("## BACKREF %d: replace \"%.*s\" by \"%.*s\" in \"%s\"\n", + i, + 2, backref, + caps[i].len, caps[i].ptr, + nbuf); + + for (np = nbuf;;) { + char *p = memstr(np, nlen, backref, 2); + + if (p == NULL) + break; + + np = substitute(np, &nlen, + nbuf_size - (np - nbuf), + backref, 2, + caps[i].ptr, caps[i].len); + + if (np == NULL) + return 1; + } + } + debug("## SUBST(2) ## %s\n", nbuf); + + datap = substitute(datap, &len, data_size - (datap - data), + old, olen, nbuf, nlen); + + if (datap == NULL) + return 1; + + debug("## REMAINDER: %s\n", datap); + + debug("## RESULT: %s\n", data); + + if (!global) + break; + } + debug("## FINAL (now env_set()) : %s\n", data); + + return 0; +} + +#define SLRE_BUFSZ 16384 +#define SLRE_PATSZ 4096 + +/* + * Perform regex operations on a environment variable + * + * Returns 0 if OK, 1 in case of errors. + */ +static int regex_sub_var(const char *name, const char *r, const char *s, + const char *t, int global) +{ + struct slre slre; + char data[SLRE_BUFSZ]; + char nbuf[SLRE_PATSZ]; + const char *value; + int len; + int ret; + + if (!name) + return 1; + + if (slre_compile(&slre, r) == 0) { + printf("Error compiling regex: %s\n", slre.err_str); + return 1; + } + + if (!t) { + value = env_get(name); + if (!value) { + printf("## Error: variable \"%s\" not defined\n", name); + return 1; + } + t = value; + } + + debug("REGEX on %s=%s\n", name, t); + debug("REGEX=\"%s\", SUBST=\"%s\", GLOBAL=%d\n", r, s ? s : "<NULL>", + global); + + len = strlen(t); + if (len + 1 > SLRE_BUFSZ) { + printf("## error: subst buffer overflow: have %d, need %d\n", + SLRE_BUFSZ, len + 1); + return 1; + } + + strcpy(data, t); + + ret = setexpr_regex_sub(data, SLRE_BUFSZ, nbuf, SLRE_PATSZ, r, s, + global); + if (ret) + return 1; + + debug("%s=%s\n", name, data); + + return env_set(name, data); +} +#endif + +static int do_setexpr(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct expr_arg aval, bval; + ulong value; + int ret = 0; + int w; + + /* + * We take 3, 5, or 6 arguments, except fmt operation, which + * takes 4 to 8 arguments (limited by _maxargs): + * 3 : setexpr name value + * 5 : setexpr name val1 op val2 + * setexpr name [g]sub r s + * 6 : setexpr name [g]sub r s t + * setexpr name fmt format [val1] [val2] [val3] [val4] + */ + + if (argc < 3) + return CMD_RET_USAGE; + + w = cmd_get_data_size(argv[0], 4); + + if (get_arg(argv[2], w, &aval)) + return CMD_RET_FAILURE; + + /* format string assignment: "setexpr name fmt %d value" */ + if (strcmp(argv[2], "fmt") == 0 && IS_ENABLED(CONFIG_CMD_SETEXPR_FMT)) { + char str[MAX_STR_LEN]; + int result; + + if (argc == 3) + return CMD_RET_USAGE; + + result = printf_setexpr(str, sizeof(str), argc - 3, &argv[3]); + if (result) + return result; + + return env_set(argv[1], str); + } + + if (argc == 4 || argc > 6) + return CMD_RET_USAGE; + + /* plain assignment: "setexpr name value" */ + if (argc == 3) { + if (w == CMD_DATA_SIZE_STR) { + ret = env_set(argv[1], aval.sval); + free(aval.sval); + } else { + ret = env_set_hex(argv[1], aval.ival); + } + + return ret; + } + + /* 5 or 6 args (6 args only with [g]sub) */ +#ifdef CONFIG_REGEX + /* + * rexep handling: "setexpr name [g]sub r s [t]" + * with 5 args, "t" will be NULL + */ + if (strcmp(argv[2], "gsub") == 0) + return regex_sub_var(argv[1], argv[3], argv[4], argv[5], 1); + + if (strcmp(argv[2], "sub") == 0) + return regex_sub_var(argv[1], argv[3], argv[4], argv[5], 0); +#endif + + /* standard operators: "setexpr name val1 op val2" */ + if (argc != 5) + return CMD_RET_USAGE; + + if (strlen(argv[3]) != 1) + return CMD_RET_USAGE; + + if (get_arg(argv[4], w, &bval)) { + if (w == CMD_DATA_SIZE_STR) + free(aval.sval); + return CMD_RET_FAILURE; + } + + if (w == CMD_DATA_SIZE_STR) { + int len; + char *str; + + switch (argv[3][0]) { + case '+': + len = strlen(aval.sval) + strlen(bval.sval) + 1; + str = malloc(len); + if (!str) { + printf("Out of memory\n"); + ret = CMD_RET_FAILURE; + } else { + /* These were copied out and checked earlier */ + strcpy(str, aval.sval); + strcat(str, bval.sval); + ret = env_set(argv[1], str); + if (ret) + printf("Could not set var\n"); + free(str); + } + break; + default: + printf("invalid op\n"); + ret = 1; + } + } else { + ulong a = aval.ival; + ulong b = bval.ival; + + switch (argv[3][0]) { + case '|': + value = a | b; + break; + case '&': + value = a & b; + break; + case '+': + value = a + b; + break; + case '^': + value = a ^ b; + break; + case '-': + value = a - b; + break; + case '*': + value = a * b; + break; + case '/': + value = a / b; + break; + case '%': + value = a % b; + break; + default: + printf("invalid op\n"); + return 1; + } + + env_set_hex(argv[1], value); + } + + if (w == CMD_DATA_SIZE_STR) { + free(aval.sval); + free(bval.sval); + } + + return ret; +} + +U_BOOT_CMD( + setexpr, 8, 0, do_setexpr, + "set environment variable as the result of eval expression", + "[.b, .w, .l, .s] name [*]value1 <op> [*]value2\n" + " - set environment variable 'name' to the result of the evaluated\n" + " expression specified by <op>. <op> can be &, |, ^, +, -, *, /, %\n" + " (for strings only + is supported)\n" + " size argument is only meaningful if value1 and/or value2 are\n" + " memory addresses (*)\n" + "setexpr[.b, .w, .l] name [*]value\n" + " - load a value into a variable" +#ifdef CONFIG_CMD_SETEXPR_FMT + "\n" + "setexpr name fmt <format> [value1] [value2] [value3] [value4]\n" + " - set environment variable 'name' to the result of the bash like\n" + " format string evaluation of value." +#endif +#ifdef CONFIG_REGEX + "\n" + "setexpr name gsub r s [t]\n" + " - For each substring matching the regular expression <r> in the\n" + " string <t>, substitute the string <s>. The result is\n" + " assigned to <name>. If <t> is not supplied, use the old\n" + " value of <name>. If no substring matching <r> is found in <t>,\n" + " assign <t> to <name>.\n" + "setexpr name sub r s [t]\n" + " - Just like gsub(), but replace only the first matching substring" +#endif +); diff --git a/cmd/sf.c b/cmd/sf.c new file mode 100644 index 00000000000..08e364e1914 --- /dev/null +++ b/cmd/sf.c @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Command for accessing SPI flash. + * + * Copyright (C) 2008 Atmel Corporation + */ + +#include <command.h> +#include <display_options.h> +#include <div64.h> +#include <dm.h> +#include <log.h> +#include <lmb.h> +#include <malloc.h> +#include <mapmem.h> +#include <spi.h> +#include <time.h> +#include <spi_flash.h> +#include <asm/cache.h> +#include <jffs2/jffs2.h> +#include <linux/mtd/mtd.h> + +#include <asm/io.h> +#include <dm/device-internal.h> + +#include "legacy-mtd-utils.h" + +static struct spi_flash *flash; + +/* + * This function computes the length argument for the erase command. + * The length on which the command is to operate can be given in two forms: + * 1. <cmd> offset len - operate on <'offset', 'len') + * 2. <cmd> offset +len - operate on <'offset', 'round_up(len)') + * If the second form is used and the length doesn't fall on the + * sector boundary, than it will be adjusted to the next sector boundary. + * If it isn't in the flash, the function will fail (return -1). + * Input: + * arg: length specification (i.e. both command arguments) + * Output: + * len: computed length for operation + * Return: + * 1: success + * -1: failure (bad format, bad address). + */ +static int sf_parse_len_arg(char *arg, ulong *len) +{ + char *ep; + char round_up_len; /* indicates if the "+length" form used */ + ulong len_arg; + + round_up_len = 0; + if (*arg == '+') { + round_up_len = 1; + ++arg; + } + + len_arg = hextoul(arg, &ep); + if (ep == arg || *ep != '\0') + return -1; + + if (round_up_len && flash->sector_size > 0) + *len = ROUND(len_arg, flash->sector_size); + else + *len = len_arg; + + return 1; +} + +/** + * This function takes a byte length and a delta unit of time to compute the + * approximate bytes per second + * + * @param len amount of bytes currently processed + * @param start_ms start time of processing in ms + * Return: bytes per second if OK, 0 on error + */ +static ulong bytes_per_second(unsigned int len, ulong start_ms) +{ + /* less accurate but avoids overflow */ + if (len >= ((unsigned int) -1) / 1024) + return len / (max(get_timer(start_ms) / 1024, 1UL)); + else + return 1024 * len / max(get_timer(start_ms), 1UL); +} + +static int do_spi_flash_probe(int argc, char *const argv[]) +{ + unsigned int bus = CONFIG_SF_DEFAULT_BUS; + unsigned int cs = CONFIG_SF_DEFAULT_CS; + /* In DM mode, defaults speed and mode will be taken from DT */ + unsigned int speed = CONFIG_SF_DEFAULT_SPEED; + unsigned int mode = CONFIG_SF_DEFAULT_MODE; + char *endp; + bool use_dt = true; +#if CONFIG_IS_ENABLED(DM_SPI_FLASH) + struct udevice *new, *bus_dev; + int ret; +#else + struct spi_flash *new; +#endif + + if (argc >= 2) { + cs = simple_strtoul(argv[1], &endp, 0); + if (*argv[1] == 0 || (*endp != 0 && *endp != ':')) + return -1; + if (*endp == ':') { + if (endp[1] == 0) + return -1; + + bus = cs; + cs = simple_strtoul(endp + 1, &endp, 0); + if (*endp != 0) + return -1; + } + } + + if (argc >= 3) { + speed = simple_strtoul(argv[2], &endp, 0); + if (*argv[2] == 0 || *endp != 0) + return -1; + use_dt = false; + } + if (argc >= 4) { + mode = hextoul(argv[3], &endp); + if (*argv[3] == 0 || *endp != 0) + return -1; + use_dt = false; + } + +#if CONFIG_IS_ENABLED(DM_SPI_FLASH) + /* Remove the old device, otherwise probe will just be a nop */ + ret = spi_find_bus_and_cs(bus, cs, &bus_dev, &new); + if (!ret) { + device_remove(new, DM_REMOVE_NORMAL); + } + flash = NULL; + if (use_dt) { + ret = spi_flash_probe_bus_cs(bus, cs, &new); + if (!ret) + flash = dev_get_uclass_priv(new); + } else { + flash = spi_flash_probe(bus, cs, speed, mode); + } + + if (!flash) { + printf("Failed to initialize SPI flash at %u:%u (error %d)\n", + bus, cs, ret); + return 1; + } +#else + if (flash) + spi_flash_free(flash); + + new = spi_flash_probe(bus, cs, speed, mode); + flash = new; + if (!new) { + printf("Failed to initialize SPI flash at %u:%u\n", bus, cs); + return 1; + } +#endif + + return 0; +} + +/** + * Write a block of data to SPI flash, first checking if it is different from + * what is already there. + * + * If the data being written is the same, then *skipped is incremented by len. + * + * @param flash flash context pointer + * @param offset flash offset to write + * @param len number of bytes to write + * @param buf buffer to write from + * @param cmp_buf read buffer to use to compare data + * @param skipped Count of skipped data (incremented by this function) + * Return: NULL if OK, else a string containing the stage which failed + */ +static const char *spi_flash_update_block(struct spi_flash *flash, u32 offset, + size_t len, const char *buf, char *cmp_buf, size_t *skipped) +{ + char *ptr = (char *)buf; + u32 start_offset = offset % flash->sector_size; + u32 read_offset = offset - start_offset; + + debug("offset=%#x+%#x, sector_size=%#x, len=%#zx\n", + read_offset, start_offset, flash->sector_size, len); + /* Read the entire sector so to allow for rewriting */ + if (spi_flash_read(flash, read_offset, flash->sector_size, cmp_buf)) + return "read"; + /* Compare only what is meaningful (len) */ + if (memcmp(cmp_buf + start_offset, buf, len) == 0) { + debug("Skip region %x+%x size %zx: no change\n", + start_offset, read_offset, len); + *skipped += len; + return NULL; + } + /* Erase the entire sector */ + if (spi_flash_erase(flash, offset, flash->sector_size)) + return "erase"; + /* If it's a partial sector, copy the data into the temp-buffer */ + if (len != flash->sector_size) { + memcpy(cmp_buf + start_offset, buf, len); + ptr = cmp_buf; + } + /* Write one complete sector */ + if (spi_flash_write(flash, offset, flash->sector_size, ptr)) + return "write"; + + return NULL; +} + +/** + * Update an area of SPI flash by erasing and writing any blocks which need + * to change. Existing blocks with the correct data are left unchanged. + * + * @param flash flash context pointer + * @param offset flash offset to write + * @param len number of bytes to write + * @param buf buffer to write from + * Return: 0 if ok, 1 on error + */ +static int spi_flash_update(struct spi_flash *flash, u32 offset, + size_t len, const char *buf) +{ + const char *err_oper = NULL; + char *cmp_buf; + const char *end = buf + len; + size_t todo; /* number of bytes to do in this pass */ + size_t skipped = 0; /* statistics */ + const ulong start_time = get_timer(0); + size_t scale = 1; + const char *start_buf = buf; + ulong delta; + + if (end - buf >= 200) + scale = (end - buf) / 100; + cmp_buf = memalign(ARCH_DMA_MINALIGN, flash->sector_size); + if (cmp_buf) { + ulong last_update = get_timer(0); + + for (; buf < end && !err_oper; buf += todo, offset += todo) { + todo = min_t(size_t, end - buf, flash->sector_size); + todo = min_t(size_t, end - buf, + flash->sector_size - (offset % flash->sector_size)); + if (get_timer(last_update) > 100) { + printf(" \rUpdating, %zu%% %lu B/s", + 100 - (end - buf) / scale, + bytes_per_second(buf - start_buf, + start_time)); + last_update = get_timer(0); + } + err_oper = spi_flash_update_block(flash, offset, todo, + buf, cmp_buf, &skipped); + } + } else { + err_oper = "malloc"; + } + free(cmp_buf); + putc('\r'); + if (err_oper) { + printf("SPI flash failed in %s step\n", err_oper); + return 1; + } + + delta = get_timer(start_time); + printf("%zu bytes written, %zu bytes skipped", len - skipped, + skipped); + printf(" in %ld.%lds, speed %ld B/s\n", + delta / 1000, delta % 1000, bytes_per_second(len, start_time)); + + return 0; +} + +static int do_spi_flash_read_write(int argc, char *const argv[]) +{ + unsigned long addr; + void *buf; + char *endp; + int ret = 1; + int dev = 0; + loff_t offset, len, maxsize; + + if (argc < 3) + return CMD_RET_USAGE; + + addr = hextoul(argv[1], &endp); + if (*argv[1] == 0 || *endp != 0) + return CMD_RET_USAGE; + + if (mtd_arg_off_size(argc - 2, &argv[2], &dev, &offset, &len, + &maxsize, MTD_DEV_TYPE_NOR, flash->size)) + return CMD_RET_FAILURE; + + /* Consistency checking */ + if (offset + len > flash->size) { + printf("ERROR: attempting %s past flash size (%#x)\n", + argv[0], flash->size); + return CMD_RET_FAILURE; + } + + if (strncmp(argv[0], "read", 4) != 0 && flash->flash_is_unlocked && + !flash->flash_is_unlocked(flash, offset, len)) { + printf("ERROR: flash area is locked\n"); + return CMD_RET_FAILURE; + } + + buf = map_physmem(addr, len, MAP_WRBACK); + if (!buf && addr) { + puts("Failed to map physical memory\n"); + return CMD_RET_FAILURE; + } + + if (strcmp(argv[0], "update") == 0) { + ret = spi_flash_update(flash, offset, len, buf); + } else if (strncmp(argv[0], "read", 4) == 0 || + strncmp(argv[0], "write", 5) == 0) { + int read; + + if (CONFIG_IS_ENABLED(LMB)) { + if (lmb_read_check(addr, len)) { + printf("ERROR: trying to overwrite reserved memory...\n"); + return CMD_RET_FAILURE; + } + } + + read = strncmp(argv[0], "read", 4) == 0; + if (read) + ret = spi_flash_read(flash, offset, len, buf); + else + ret = spi_flash_write(flash, offset, len, buf); + + printf("SF: %zu bytes @ %#x %s: ", (size_t)len, (u32)offset, + read ? "Read" : "Written"); + if (ret) + printf("ERROR %d\n", ret); + else + printf("OK\n"); + } + + unmap_physmem(buf, len); + + return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static int do_spi_flash_erase(int argc, char *const argv[]) +{ + int ret; + int dev = 0; + loff_t offset, len, maxsize; + ulong size; + + if (argc < 3) + return CMD_RET_USAGE; + + if (mtd_arg_off(argv[1], &dev, &offset, &len, &maxsize, + MTD_DEV_TYPE_NOR, flash->size)) + return CMD_RET_FAILURE; + + ret = sf_parse_len_arg(argv[2], &size); + if (ret != 1) + return CMD_RET_USAGE; + + if (size == 0) { + debug("ERROR: Invalid size 0\n"); + return CMD_RET_FAILURE; + } + + /* Consistency checking */ + if (offset + size > flash->size) { + printf("ERROR: attempting %s past flash size (%#x)\n", + argv[0], flash->size); + return CMD_RET_FAILURE; + } + + if (flash->flash_is_unlocked && + !flash->flash_is_unlocked(flash, offset, size)) { + printf("ERROR: flash area is locked\n"); + return CMD_RET_FAILURE; + } + + ret = spi_flash_erase(flash, offset, size); + printf("SF: %zu bytes @ %#x Erased: ", (size_t)size, (u32)offset); + if (ret) + printf("ERROR %d\n", ret); + else + printf("OK\n"); + + return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +static int do_spi_protect(int argc, char *const argv[]) +{ + int ret = 0; + loff_t start, len; + bool prot = false; + + if (argc != 4) + return -1; + + if (!str2off(argv[2], &start)) { + puts("start sector is not a valid number\n"); + return 1; + } + + if (!str2off(argv[3], &len)) { + puts("len is not a valid number\n"); + return 1; + } + + if (strcmp(argv[1], "lock") == 0) + prot = true; + else if (strcmp(argv[1], "unlock") == 0) + prot = false; + else + return -1; /* Unknown parameter */ + + ret = spi_flash_protect(flash, start, len, prot); + + return ret == 0 ? 0 : 1; +} + +enum { + STAGE_ERASE, + STAGE_CHECK, + STAGE_WRITE, + STAGE_READ, + + STAGE_COUNT, +}; + +static const char *stage_name[STAGE_COUNT] = { + "erase", + "check", + "write", + "read", +}; + +struct test_info { + int stage; + int bytes; + unsigned base_ms; + unsigned time_ms[STAGE_COUNT]; +}; + +static void show_time(struct test_info *test, int stage) +{ + uint64_t speed; /* KiB/s */ + int bps; /* Bits per second */ + + speed = (long long)test->bytes * 1000; + if (test->time_ms[stage]) + do_div(speed, test->time_ms[stage] * 1024); + bps = speed * 8; + + printf("%d %s: %u ticks, %d KiB/s %d.%03d Mbps\n", stage, + stage_name[stage], test->time_ms[stage], + (int)speed, bps / 1000, bps % 1000); +} + +static void spi_test_next_stage(struct test_info *test) +{ + test->time_ms[test->stage] = get_timer(test->base_ms); + show_time(test, test->stage); + test->base_ms = get_timer(0); + test->stage++; +} + +/** + * Run a test on the SPI flash + * + * @param flash SPI flash to use + * @param buf Source buffer for data to write + * @param len Size of data to read/write + * @param offset Offset within flash to check + * @param vbuf Verification buffer + * Return: 0 if ok, -1 on error + */ +static int spi_flash_test(struct spi_flash *flash, uint8_t *buf, ulong len, + ulong offset, uint8_t *vbuf) +{ + struct test_info test; + int err, i; + + printf("SPI flash test:\n"); + memset(&test, '\0', sizeof(test)); + test.base_ms = get_timer(0); + test.bytes = len; + err = spi_flash_erase(flash, offset, len); + if (err) { + printf("Erase failed (err = %d)\n", err); + return -1; + } + spi_test_next_stage(&test); + + err = spi_flash_read(flash, offset, len, vbuf); + if (err) { + printf("Check read failed (err = %d)\n", err); + return -1; + } + for (i = 0; i < len; i++) { + if (vbuf[i] != 0xff) { + printf("Check failed at %d\n", i); + print_buffer(i, vbuf + i, 1, + min_t(uint, len - i, 0x40), 0); + return -1; + } + } + spi_test_next_stage(&test); + + err = spi_flash_write(flash, offset, len, buf); + if (err) { + printf("Write failed (err = %d)\n", err); + return -1; + } + memset(vbuf, '\0', len); + spi_test_next_stage(&test); + + err = spi_flash_read(flash, offset, len, vbuf); + if (err) { + printf("Read failed (ret = %d)\n", err); + return -1; + } + spi_test_next_stage(&test); + + for (i = 0; i < len; i++) { + if (buf[i] != vbuf[i]) { + printf("Verify failed at %d, good data:\n", i); + print_buffer(i, buf + i, 1, + min_t(uint, len - i, 0x40), 0); + printf("Bad data:\n"); + print_buffer(i, vbuf + i, 1, + min_t(uint, len - i, 0x40), 0); + return -1; + } + } + printf("Test passed\n"); + for (i = 0; i < STAGE_COUNT; i++) + show_time(&test, i); + + return 0; +} + +static int do_spi_flash_test(int argc, char *const argv[]) +{ + unsigned long offset; + unsigned long len; + uint8_t *buf, *from; + char *endp; + uint8_t *vbuf; + int ret; + + if (argc < 3) + return -1; + offset = hextoul(argv[1], &endp); + if (*argv[1] == 0 || *endp != 0) + return -1; + len = hextoul(argv[2], &endp); + if (*argv[2] == 0 || *endp != 0) + return -1; + + vbuf = memalign(ARCH_DMA_MINALIGN, len); + if (!vbuf) { + printf("Cannot allocate memory (%lu bytes)\n", len); + return 1; + } + buf = memalign(ARCH_DMA_MINALIGN, len); + if (!buf) { + free(vbuf); + printf("Cannot allocate memory (%lu bytes)\n", len); + return 1; + } + + from = map_sysmem(CONFIG_TEXT_BASE, 0); + memcpy(buf, from, len); + ret = spi_flash_test(flash, buf, len, offset, vbuf); + free(vbuf); + free(buf); + if (ret) { + printf("Test failed\n"); + return 1; + } + + return 0; +} + +static int do_spi_flash(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *cmd; + int ret; + + /* need at least two arguments */ + if (argc < 2) + return CMD_RET_USAGE; + + cmd = argv[1]; + --argc; + ++argv; + + if (strcmp(cmd, "probe") == 0) + return do_spi_flash_probe(argc, argv); + + /* The remaining commands require a selected device */ + if (!flash) { + puts("No SPI flash selected. Please run `sf probe'\n"); + return CMD_RET_FAILURE; + } + + if (strcmp(cmd, "read") == 0 || strcmp(cmd, "write") == 0 || + strcmp(cmd, "update") == 0) + ret = do_spi_flash_read_write(argc, argv); + else if (strcmp(cmd, "erase") == 0) + ret = do_spi_flash_erase(argc, argv); + else if (IS_ENABLED(CONFIG_SPI_FLASH_LOCK) && strcmp(cmd, "protect") == 0) + ret = do_spi_protect(argc, argv); + else if (IS_ENABLED(CONFIG_CMD_SF_TEST) && !strcmp(cmd, "test")) + ret = do_spi_flash_test(argc, argv); + else + ret = CMD_RET_USAGE; + + return ret; +} + +U_BOOT_LONGHELP(sf, + "probe [[bus:]cs] [hz] [mode] - init flash device on given SPI bus\n" + " and chip select\n" + "sf read addr offset|partition len - read `len' bytes starting at\n" + " `offset' or from start of mtd\n" + " `partition'to memory at `addr'\n" + "sf write addr offset|partition len - write `len' bytes from memory\n" + " at `addr' to flash at `offset'\n" + " or to start of mtd `partition'\n" + "sf erase offset|partition [+]len - erase `len' bytes from `offset'\n" + " or from start of mtd `partition'\n" + " `+len' round up `len' to block size\n" + "sf update addr offset|partition len - erase and write `len' bytes from memory\n" + " at `addr' to flash at `offset'\n" + " or to start of mtd `partition'\n" +#ifdef CONFIG_SPI_FLASH_LOCK + "sf protect lock/unlock sector len - protect/unprotect 'len' bytes starting\n" + " at address 'sector'" +#endif +#ifdef CONFIG_CMD_SF_TEST + "\nsf test offset len - run a very basic destructive test" +#endif + ); + +U_BOOT_CMD( + sf, 5, 1, do_spi_flash, + "SPI flash sub-system", sf_help_text +); diff --git a/cmd/sha1sum.c b/cmd/sha1sum.c new file mode 100644 index 00000000000..f2757146bba --- /dev/null +++ b/cmd/sha1sum.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2011 + * Joe Hershberger, National Instruments, joe.hershberger@ni.com + * + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <hash.h> +#include <linux/string.h> +#include <u-boot/sha1.h> + +int do_sha1sum(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int flags = HASH_FLAG_ENV; + int ac; + char * const *av; + + if (argc < 3) + return CMD_RET_USAGE; + + av = argv + 1; + ac = argc - 1; +#ifdef CONFIG_SHA1SUM_VERIFY + if (strcmp(*av, "-v") == 0) { + flags |= HASH_FLAG_VERIFY; + av++; + ac--; + } +#endif + + return hash_command("sha1", flags, cmdtp, flag, ac, av); +} + +#ifdef CONFIG_SHA1SUM_VERIFY +U_BOOT_CMD( + sha1sum, 5, 1, do_sha1sum, + "compute SHA1 message digest", + "address count [[*]sum]\n" + " - compute SHA1 message digest [save to sum]\n" + "sha1sum -v address count [*]sum\n" + " - verify sha1sum of memory area" +); +#else +U_BOOT_CMD( + sha1sum, 4, 1, do_sha1sum, + "compute SHA1 message digest", + "address count [[*]sum]\n" + " - compute SHA1 message digest [save to sum]" +); +#endif diff --git a/cmd/sleep.c b/cmd/sleep.c new file mode 100644 index 00000000000..a8c896e0c5e --- /dev/null +++ b/cmd/sleep.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2001 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <console.h> +#include <time.h> +#include <vsprintf.h> +#include <linux/delay.h> +#include <linux/string.h> + +static int do_sleep(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong start = get_timer(0); + ulong mdelay = 0; + ulong delay; + char *frpart; + + if (argc != 2) + return CMD_RET_USAGE; + + delay = dectoul(argv[1], NULL) * CONFIG_SYS_HZ; + + frpart = strchr(argv[1], '.'); + + if (frpart) { + uint mult = CONFIG_SYS_HZ / 10; + for (frpart++; *frpart != '\0' && mult > 0; frpart++) { + if (*frpart < '0' || *frpart > '9') { + mdelay = 0; + break; + } + mdelay += (*frpart - '0') * mult; + mult /= 10; + } + } + + delay += mdelay; + + while (get_timer(start) < delay) { + if (ctrlc()) + return CMD_RET_FAILURE; + + udelay(100); + } + + return 0; +} + +U_BOOT_CMD( + sleep , 2, 1, do_sleep, + "delay execution for some time", + "N\n" + " - delay execution for N seconds (N is _decimal_ and can be\n" + " fractional)" +); diff --git a/cmd/smbios.c b/cmd/smbios.c new file mode 100644 index 00000000000..ed419f19028 --- /dev/null +++ b/cmd/smbios.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * The 'smbios' command displays information from the SMBIOS table. + * + * Copyright (c) 2023, Heinrich Schuchardt <heinrich.schuchardt@canonical.com> + */ + +#include <command.h> +#include <hexdump.h> +#include <mapmem.h> +#include <smbios.h> +#include <tables_csum.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const struct str_lookup_table wakeup_type_strings[] = { + { SMBIOS_WAKEUP_TYPE_RESERVED, "Reserved" }, + { SMBIOS_WAKEUP_TYPE_OTHER, "Other" }, + { SMBIOS_WAKEUP_TYPE_UNKNOWN, "Unknown" }, + { SMBIOS_WAKEUP_TYPE_APM_TIMER, "APM Timer" }, + { SMBIOS_WAKEUP_TYPE_MODEM_RING, "Modem Ring" }, + { SMBIOS_WAKEUP_TYPE_LAN_REMOTE, "Lan Remote" }, + { SMBIOS_WAKEUP_TYPE_POWER_SWITCH, "Power Switch" }, + { SMBIOS_WAKEUP_TYPE_PCI_PME, "PCI PME#" }, + { SMBIOS_WAKEUP_TYPE_AC_POWER_RESTORED, "AC Power Restored" }, +}; + +static const struct str_lookup_table boardtype_strings[] = { + { SMBIOS_BOARD_TYPE_UNKNOWN, "Unknown" }, + { SMBIOS_BOARD_TYPE_OTHER, "Other" }, + { SMBIOS_BOARD_TYPE_SERVER_BLADE, "Server Blade" }, + { SMBIOS_BOARD_TYPE_CON_SWITCH, "Connectivity Switch" }, + { SMBIOS_BOARD_TYPE_SM_MODULE, "System Management Module" }, + { SMBIOS_BOARD_TYPE_PROCESSOR_MODULE, "Processor Module" }, + { SMBIOS_BOARD_TYPE_IO_MODULE, "I/O Module" }, + { SMBIOS_BOARD_TYPE_MEM_MODULE, "Memory Module" }, + { SMBIOS_BOARD_TYPE_DAUGHTER_BOARD, "Daughter board" }, + { SMBIOS_BOARD_TYPE_MOTHERBOARD, "Motherboard" }, + { SMBIOS_BOARD_TYPE_PROC_MEM_MODULE, "Processor/Memory Module" }, + { SMBIOS_BOARD_TYPE_PROC_IO_MODULE, "Processor/IO Module" }, + { SMBIOS_BOARD_TYPE_INTERCON, "Interconnect board" }, +}; + +static const struct str_lookup_table chassis_state_strings[] = { + { SMBIOS_STATE_OTHER, "Other" }, + { SMBIOS_STATE_UNKNOWN, "Unknown" }, + { SMBIOS_STATE_SAFE, "Safe" }, + { SMBIOS_STATE_WARNING, "Warning" }, + { SMBIOS_STATE_CRITICAL, "Critical" }, + { SMBIOS_STATE_NONRECOVERABLE, "Non-recoverable" }, +}; + +static const struct str_lookup_table chassis_security_strings[] = { + { SMBIOS_SECURITY_OTHER, "Other" }, + { SMBIOS_SECURITY_UNKNOWN, "Unknown" }, + { SMBIOS_SECURITY_NONE, "None" }, + { SMBIOS_SECURITY_EXTINT_LOCK, "External interface locked out" }, + { SMBIOS_SECURITY_EXTINT_EN, "External interface enabled" }, +}; + +static const struct str_lookup_table processor_type_strings[] = { + { SMBIOS_PROCESSOR_TYPE_OTHER, "Other" }, + { SMBIOS_PROCESSOR_TYPE_UNKNOWN, "Unknown" }, + { SMBIOS_PROCESSOR_TYPE_CENTRAL, "Central Processor" }, + { SMBIOS_PROCESSOR_TYPE_MATH, "Math Processor" }, + { SMBIOS_PROCESSOR_TYPE_DSP, "DSP Processor" }, + { SMBIOS_PROCESSOR_TYPE_VIDEO, "Video Processor" }, +}; + +static const struct str_lookup_table processor_family_strings[] = { + { SMBIOS_PROCESSOR_FAMILY_OTHER, "Other" }, + { SMBIOS_PROCESSOR_FAMILY_UNKNOWN, "Unknown" }, + { SMBIOS_PROCESSOR_FAMILY_RSVD, "Reserved" }, + { SMBIOS_PROCESSOR_FAMILY_ARMV7, "ARMv7" }, + { SMBIOS_PROCESSOR_FAMILY_ARMV8, "ARMv8" }, + { SMBIOS_PROCESSOR_FAMILY_RV32, "RISC-V RV32" }, + { SMBIOS_PROCESSOR_FAMILY_RV64, "RISC-V RV64" }, +}; + +static const struct str_lookup_table processor_upgrade_strings[] = { + { SMBIOS_PROCESSOR_UPGRADE_OTHER, "Other" }, + { SMBIOS_PROCESSOR_UPGRADE_UNKNOWN, "Unknown" }, + { SMBIOS_PROCESSOR_UPGRADE_NONE, "None" }, +}; + +static const struct str_lookup_table err_corr_type_strings[] = { + { SMBIOS_CACHE_ERRCORR_OTHER, "Other" }, + { SMBIOS_CACHE_ERRCORR_UNKNOWN, "Unknown" }, + { SMBIOS_CACHE_ERRCORR_NONE, "None" }, + { SMBIOS_CACHE_ERRCORR_PARITY, "Parity" }, + { SMBIOS_CACHE_ERRCORR_SBITECC, "Single-bit ECC" }, + { SMBIOS_CACHE_ERRCORR_MBITECC, "Multi-bit ECC" }, +}; + +static const struct str_lookup_table sys_cache_type_strings[] = { + { SMBIOS_CACHE_SYSCACHE_TYPE_OTHER, "Other" }, + { SMBIOS_CACHE_SYSCACHE_TYPE_UNKNOWN, "Unknown" }, + { SMBIOS_CACHE_SYSCACHE_TYPE_INST, "Instruction" }, + { SMBIOS_CACHE_SYSCACHE_TYPE_DATA, "Data" }, + { SMBIOS_CACHE_SYSCACHE_TYPE_UNIFIED, "Unified" }, +}; + +static const struct str_lookup_table associativity_strings[] = { + { SMBIOS_CACHE_ASSOC_OTHER, "Other" }, + { SMBIOS_CACHE_ASSOC_UNKNOWN, "Unknown" }, + { SMBIOS_CACHE_ASSOC_DMAPPED, "Direct Mapped" }, + { SMBIOS_CACHE_ASSOC_2WAY, "2-way Set-Associative" }, + { SMBIOS_CACHE_ASSOC_4WAY, "4-way Set-Associative" }, + { SMBIOS_CACHE_ASSOC_FULLY, "Fully Associative" }, + { SMBIOS_CACHE_ASSOC_8WAY, "8-way Set-Associative" }, + { SMBIOS_CACHE_ASSOC_16WAY, "16-way Set-Associative" }, + { SMBIOS_CACHE_ASSOC_12WAY, "12-way Set-Associative" }, + { SMBIOS_CACHE_ASSOC_24WAY, "24-way Set-Associative" }, + { SMBIOS_CACHE_ASSOC_32WAY, "32-way Set-Associative" }, + { SMBIOS_CACHE_ASSOC_48WAY, "48-way Set-Associative" }, + { SMBIOS_CACHE_ASSOC_64WAY, "64-way Set-Associative" }, + { SMBIOS_CACHE_ASSOC_20WAY, "20-way Set-Associative" }, + +}; + +/** + * smbios_get_string() - get SMBIOS string from table + * + * @table: SMBIOS table + * @index: index of the string + * Return: address of string, may point to empty string + */ +static const char *smbios_get_string(void *table, int index) +{ + const char *str = (char *)table + + ((struct smbios_header *)table)->length; + static const char fallback[] = ""; + + if (!index) + return fallback; + + if (!*str) + ++str; + for (--index; *str && index; --index) + str += strlen(str) + 1; + + return str; +} + +static struct smbios_header *next_table(struct smbios_header *table) +{ + const char *str; + + if (table->type == SMBIOS_END_OF_TABLE) + return NULL; + + str = smbios_get_string(table, -1); + return (struct smbios_header *)(++str); +} + +static void smbios_print_generic(struct smbios_header *table) +{ + char *str = (char *)table + table->length; + + if (CONFIG_IS_ENABLED(HEXDUMP)) { + printf("Header and Data:\n"); + print_hex_dump("\t", DUMP_PREFIX_OFFSET, 16, 1, + table, table->length, false); + } + if (*str) { + printf("Strings:\n"); + for (int index = 1; *str; ++index) { + printf("\tString %u: %s\n", index, str); + str += strlen(str) + 1; + } + } +} + +static void smbios_print_str(const char *label, void *table, u8 index) +{ + printf("\t%s: %s\n", label, smbios_get_string(table, index)); +} + +static void smbios_print_lookup_str(const struct str_lookup_table *table, + u16 index, u16 array_size, + const char *prefix) +{ + int i; + const char *str = NULL; + + for (i = 0; i < array_size; i++) { + if ((table + i)->idx == index) + str = (table + i)->str; + } + + if (str) + printf("\t%s: %s\n", prefix, str); + else + printf("\t%s: [%04x]\n", prefix, index); +} + +static void smbios_print_type0(struct smbios_type0 *table) +{ + printf("BIOS Information\n"); + smbios_print_str("Vendor", table, table->vendor); + smbios_print_str("BIOS Version", table, table->bios_ver); + /* Keep table->bios_start_segment as 0 for UEFI-based systems */ + smbios_print_str("BIOS Release Date", table, table->bios_release_date); + printf("\tBIOS ROM Size: 0x%02x\n", table->bios_rom_size); + printf("\tBIOS Characteristics: 0x%016llx\n", + table->bios_characteristics); + printf("\tBIOS Characteristics Extension Byte 1: 0x%02x\n", + table->bios_characteristics_ext1); + printf("\tBIOS Characteristics Extension Byte 2: 0x%02x\n", + table->bios_characteristics_ext2); + printf("\tSystem BIOS Major Release: 0x%02x\n", + table->bios_major_release); + printf("\tSystem BIOS Minor Release: 0x%02x\n", + table->bios_minor_release); + printf("\tEmbedded Controller Firmware Major Release: 0x%02x\n", + table->ec_major_release); + printf("\tEmbedded Controller Firmware Minor Release: 0x%02x\n", + table->ec_minor_release); + printf("\tExtended BIOS ROM Size: 0x%04x\n", + table->extended_bios_rom_size); +} + +static void smbios_print_type1(struct smbios_type1 *table) +{ + printf("System Information\n"); + smbios_print_str("Manufacturer", table, table->manufacturer); + smbios_print_str("Product Name", table, table->product_name); + smbios_print_str("Version", table, table->version); + smbios_print_str("Serial Number", table, table->serial_number); + if (table->hdr.length >= SMBIOS_TYPE1_LENGTH_V21) { + printf("\tUUID: %pUl\n", table->uuid); + smbios_print_lookup_str(wakeup_type_strings, + table->wakeup_type, + ARRAY_SIZE(wakeup_type_strings), + "Wake-up Type"); + } + if (table->hdr.length >= SMBIOS_TYPE1_LENGTH_V24) { + smbios_print_str("SKU Number", table, table->sku_number); + smbios_print_str("Family", table, table->family); + } +} + +static void smbios_print_type2(struct smbios_type2 *table) +{ + int i; + u8 *addr = (u8 *)table + offsetof(struct smbios_type2, eos); + + printf("Baseboard Information\n"); + smbios_print_str("Manufacturer", table, table->manufacturer); + smbios_print_str("Product Name", table, table->product_name); + smbios_print_str("Version", table, table->version); + smbios_print_str("Serial Number", table, table->serial_number); + smbios_print_str("Asset Tag", table, table->asset_tag_number); + printf("\tFeature Flags: 0x%02x\n", table->feature_flags); + smbios_print_str("Chassis Location", table, table->chassis_location); + printf("\tChassis Handle: 0x%04x\n", table->chassis_handle); + smbios_print_lookup_str(boardtype_strings, + table->board_type, + ARRAY_SIZE(boardtype_strings), + "Board Type"); + printf("\tNumber of Contained Object Handles: 0x%02x\n", + table->number_contained_objects); + if (!table->number_contained_objects) + return; + + printf("\tContained Object Handles:\n"); + for (i = 0; i < table->number_contained_objects; i++) { + printf("\t\tObject[%03d]:\n", i); + if (CONFIG_IS_ENABLED(HEXDUMP)) + print_hex_dump("\t\t", DUMP_PREFIX_OFFSET, 16, 1, addr, + sizeof(u16), false); + addr += sizeof(u16); + } + printf("\n"); +} + +static void smbios_print_type3(struct smbios_type3 *table) +{ + int i; + u8 *addr = (u8 *)table + offsetof(struct smbios_type3, sku_number); + + printf("Chassis Information\n"); + smbios_print_str("Manufacturer", table, table->manufacturer); + printf("\tType: 0x%02x\n", table->chassis_type); + smbios_print_str("Version", table, table->version); + smbios_print_str("Serial Number", table, table->serial_number); + smbios_print_str("Asset Tag", table, table->asset_tag_number); + smbios_print_lookup_str(chassis_state_strings, + table->bootup_state, + ARRAY_SIZE(chassis_state_strings), + "Boot-up State"); + smbios_print_lookup_str(chassis_state_strings, + table->power_supply_state, + ARRAY_SIZE(chassis_state_strings), + "Power Supply State"); + smbios_print_lookup_str(chassis_state_strings, + table->thermal_state, + ARRAY_SIZE(chassis_state_strings), + "Thermal State"); + smbios_print_lookup_str(chassis_security_strings, + table->security_status, + ARRAY_SIZE(chassis_security_strings), + "Security Status"); + printf("\tOEM-defined: 0x%08x\n", table->oem_defined); + printf("\tHeight: 0x%02x\n", table->height); + printf("\tNumber of Power Cords: 0x%02x\n", + table->number_of_power_cords); + printf("\tContained Element Count: 0x%02x\n", table->element_count); + printf("\tContained Element Record Length: 0x%02x\n", + table->element_record_length); + if (table->element_count) { + printf("\tContained Elements:\n"); + for (i = 0; i < table->element_count; i++) { + printf("\t\tElement[%03d]:\n", i); + if (CONFIG_IS_ENABLED(HEXDUMP)) + print_hex_dump("\t\t", DUMP_PREFIX_OFFSET, 16, + 1, addr, + table->element_record_length, + false); + printf("\t\tContained Element Type: 0x%02x\n", *addr); + printf("\t\tContained Element Minimum: 0x%02x\n", + *(addr + 1)); + printf("\t\tContained Element Maximum: 0x%02x\n", + *(addr + 2)); + addr += table->element_record_length; + } + } + smbios_print_str("SKU Number", table, *addr); +} + +static void smbios_print_type4(struct smbios_type4 *table) +{ + printf("Processor Information:\n"); + smbios_print_str("Socket Designation", table, table->socket_design); + smbios_print_lookup_str(processor_type_strings, + table->processor_type, + ARRAY_SIZE(processor_type_strings), + "Processor Type"); + smbios_print_lookup_str(processor_family_strings, + table->processor_family, + ARRAY_SIZE(processor_family_strings), + "Processor Family"); + smbios_print_str("Processor Manufacturer", table, + table->processor_manufacturer); + printf("\tProcessor ID word 0: 0x%08x\n", table->processor_id[0]); + printf("\tProcessor ID word 1: 0x%08x\n", table->processor_id[1]); + smbios_print_str("Processor Version", table, table->processor_version); + printf("\tVoltage: 0x%02x\n", table->voltage); + printf("\tExternal Clock: 0x%04x\n", table->external_clock); + printf("\tMax Speed: 0x%04x\n", table->max_speed); + printf("\tCurrent Speed: 0x%04x\n", table->current_speed); + printf("\tStatus: 0x%02x\n", table->status); + smbios_print_lookup_str(processor_upgrade_strings, + table->processor_upgrade, + ARRAY_SIZE(processor_upgrade_strings), + "Processor Upgrade"); + printf("\tL1 Cache Handle: 0x%04x\n", table->l1_cache_handle); + printf("\tL2 Cache Handle: 0x%04x\n", table->l2_cache_handle); + printf("\tL3 Cache Handle: 0x%04x\n", table->l3_cache_handle); + smbios_print_str("Serial Number", table, table->serial_number); + smbios_print_str("Asset Tag", table, table->asset_tag); + smbios_print_str("Part Number", table, table->part_number); + printf("\tCore Count: 0x%02x\n", table->core_count); + printf("\tCore Enabled: 0x%02x\n", table->core_enabled); + printf("\tThread Count: 0x%02x\n", table->thread_count); + printf("\tProcessor Characteristics: 0x%04x\n", + table->processor_characteristics); + smbios_print_lookup_str(processor_family_strings, + table->processor_family2, + ARRAY_SIZE(processor_family_strings), + "Processor Family 2"); + printf("\tCore Count 2: 0x%04x\n", table->core_count2); + printf("\tCore Enabled 2: 0x%04x\n", table->core_enabled2); + printf("\tThread Count 2: 0x%04x\n", table->thread_count2); + printf("\tThread Enabled: 0x%04x\n", table->thread_enabled); +} + +static void smbios_print_type7(struct smbios_type7 *table) +{ + printf("Cache Information:\n"); + smbios_print_str("Socket Designation", table, + table->socket_design); + printf("\tCache Configuration: 0x%04x\n", table->config.data); + printf("\tMaximum Cache Size: 0x%04x\n", table->max_size.data); + printf("\tInstalled Size: 0x%04x\n", table->inst_size.data); + printf("\tSupported SRAM Type: 0x%04x\n", table->supp_sram_type.data); + printf("\tCurrent SRAM Type: 0x%04x\n", table->curr_sram_type.data); + printf("\tCache Speed: 0x%02x\n", table->speed); + smbios_print_lookup_str(err_corr_type_strings, + table->err_corr_type, + ARRAY_SIZE(err_corr_type_strings), + "Error Correction Type"); + smbios_print_lookup_str(sys_cache_type_strings, + table->sys_cache_type, + ARRAY_SIZE(sys_cache_type_strings), + "System Cache Type"); + smbios_print_lookup_str(associativity_strings, + table->associativity, + ARRAY_SIZE(associativity_strings), + "Associativity"); + printf("\tMaximum Cache Size 2: 0x%08x\n", table->max_size2.data); + printf("\tInstalled Cache Size 2: 0x%08x\n", table->inst_size2.data); +} + +static void smbios_print_type127(struct smbios_type127 *table) +{ + printf("End Of Table\n"); +} + +static int do_smbios(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr; + void *entry; + u32 size; + char version[12]; + struct smbios_header *table; + static const char smbios_sig[] = "_SM_"; + static const char smbios3_sig[] = "_SM3_"; + size_t count = 0; + u32 table_maximum_size; + + addr = gd_smbios_start(); + if (!addr) { + log_warning("SMBIOS not available\n"); + return CMD_RET_FAILURE; + } + entry = map_sysmem(addr, 0); + if (!memcmp(entry, smbios3_sig, sizeof(smbios3_sig) - 1)) { + struct smbios3_entry *entry3 = entry; + + table = (void *)(uintptr_t)entry3->struct_table_address; + snprintf(version, sizeof(version), "%d.%d.%d", + entry3->major_ver, entry3->minor_ver, entry3->doc_rev); + table = (void *)(uintptr_t)entry3->struct_table_address; + size = entry3->length; + table_maximum_size = entry3->table_maximum_size; + } else if (!memcmp(entry, smbios_sig, sizeof(smbios_sig) - 1)) { + struct smbios_entry *entry2 = entry; + + snprintf(version, sizeof(version), "%d.%d", + entry2->major_ver, entry2->minor_ver); + table = (void *)(uintptr_t)entry2->struct_table_address; + size = entry2->length; + table_maximum_size = entry2->struct_table_length; + } else { + log_err("Unknown SMBIOS anchor format\n"); + return CMD_RET_FAILURE; + } + if (table_compute_checksum(entry, size)) { + log_err("Invalid anchor checksum\n"); + return CMD_RET_FAILURE; + } + printf("SMBIOS %s present.\n", version); + + for (struct smbios_header *pos = table; pos; pos = next_table(pos)) + ++count; + printf("%zd structures occupying %d bytes\n", count, table_maximum_size); + printf("Table at 0x%llx\n", (unsigned long long)map_to_sysmem(table)); + + for (struct smbios_header *pos = table; pos; pos = next_table(pos)) { + printf("\nHandle 0x%04x, DMI type %d, %d bytes at 0x%llx\n", + pos->handle, pos->type, pos->length, + (unsigned long long)map_to_sysmem(pos)); + switch (pos->type) { + case SMBIOS_BIOS_INFORMATION: + smbios_print_type0((struct smbios_type0 *)pos); + break; + case SMBIOS_SYSTEM_INFORMATION: + smbios_print_type1((struct smbios_type1 *)pos); + break; + case SMBIOS_BOARD_INFORMATION: + smbios_print_type2((struct smbios_type2 *)pos); + break; + case SMBIOS_SYSTEM_ENCLOSURE: + smbios_print_type3((struct smbios_type3 *)pos); + break; + case SMBIOS_PROCESSOR_INFORMATION: + smbios_print_type4((struct smbios_type4 *)pos); + break; + case SMBIOS_CACHE_INFORMATION: + smbios_print_type7((struct smbios_type7 *)pos); + break; + case SMBIOS_END_OF_TABLE: + smbios_print_type127((struct smbios_type127 *)pos); + break; + default: + smbios_print_generic(pos); + break; + } + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(smbios, "- display SMBIOS information"); + +U_BOOT_CMD(smbios, 1, 0, do_smbios, "display SMBIOS information", + smbios_help_text); diff --git a/cmd/smccc.c b/cmd/smccc.c new file mode 100644 index 00000000000..fa04bb05ca4 --- /dev/null +++ b/cmd/smccc.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2018 + * Michalis Pappas <mpappas@fastmail.fm> + */ +#include <asm/psci.h> +#include <command.h> +#include <vsprintf.h> +#include <linux/arm-smccc.h> +#include <linux/compiler.h> +#include <linux/psci.h> +#include <linux/string.h> + +static int do_call(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct arm_smccc_res res; + + unsigned long fid; + + unsigned long a1; + unsigned long a2; + unsigned long a3; + unsigned long a4; + unsigned long a5; + unsigned long a6; + unsigned long a7; + + if (argc < 2) + return CMD_RET_USAGE; + + fid = hextoul(argv[1], NULL); + + a1 = argc > 2 ? hextoul(argv[2], NULL) : 0; + a2 = argc > 3 ? hextoul(argv[3], NULL) : 0; + a3 = argc > 4 ? hextoul(argv[4], NULL) : 0; + a4 = argc > 5 ? hextoul(argv[5], NULL) : 0; + a5 = argc > 6 ? hextoul(argv[6], NULL) : 0; + a6 = argc > 7 ? hextoul(argv[7], NULL) : 0; + a7 = argc > 8 ? hextoul(argv[8], NULL) : 0; + + if (!strcmp(argv[0], "smc")) + arm_smccc_smc(fid, a1, a2, a3, a4, a5, a6, a7, &res); + else + arm_smccc_hvc(fid, a1, a2, a3, a4, a5, a6, a7, &res); + + printf("Res: 0x%lx 0x%lx 0x%lx 0x%lx\n", res.a0, res.a1, res.a2, res.a3); + + return 0; +} + +#ifdef CONFIG_CMD_SMC +U_BOOT_CMD( + smc, 9, 2, do_call, + "Issue a Secure Monitor Call", + "<fid> [arg1 ... arg6] [id]\n" + " - fid Function ID\n" + " - arg SMC arguments, passed to X1-X6 (default to zero)\n" + " - id Secure OS ID / Session ID, passed to W7 (defaults to zero)\n" +); +#endif + +#ifdef CONFIG_CMD_HVC +U_BOOT_CMD( + hvc, 9, 2, do_call, + "Issue a Hypervisor Call", + "<fid> [arg1...arg6] [id]\n" + " - fid Function ID\n" + " - arg HVC arguments, passed to X1-X6 (default to zero)\n" + " - id Session ID, passed to W7 (defaults to zero)\n" +); +#endif diff --git a/cmd/sound.c b/cmd/sound.c new file mode 100644 index 00000000000..8f67cbd96e1 --- /dev/null +++ b/cmd/sound.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Samsung Electronics + * Rajeshwari Shinde <rajeshwari.s@samsung.com> + */ + +#include <command.h> +#include <dm.h> +#include <fdtdec.h> +#include <sound.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Initilaise sound subsystem */ +static int do_init(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret; + + ret = uclass_first_device_err(UCLASS_SOUND, &dev); + if (!ret) + ret = sound_setup(dev); + if (ret) { + printf("Initialise Audio driver failed (ret=%d)\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +/* play sound from buffer */ +static int do_play(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret = 0; + int msec = 1000; + int freq = 400; + bool first = true; + + ret = uclass_first_device_err(UCLASS_SOUND, &dev); + if (ret) + goto err; + --argc; + ++argv; + while (argc || first) { + first = false; + if (argc) { + msec = dectoul(argv[0], NULL); + --argc; + ++argv; + } + if (argc) { + freq = dectoul(argv[0], NULL); + --argc; + ++argv; + } + ret = sound_beep(dev, msec, freq); + if (ret) + goto err; + } + return 0; + +err: + printf("Sound device failed to play (err=%d)\n", ret); + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_sound_sub[] = { + U_BOOT_CMD_MKENT(init, 0, 1, do_init, "", ""), + U_BOOT_CMD_MKENT(play, INT_MAX, 1, do_play, "", ""), +}; + +/* process sound command */ +static int do_sound(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c; + + if (argc < 1) + return CMD_RET_USAGE; + + /* Strip off leading 'sound' command argument */ + argc--; + argv++; + + c = find_cmd_tbl(argv[0], &cmd_sound_sub[0], ARRAY_SIZE(cmd_sound_sub)); + + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_CMD( + sound, INT_MAX, 1, do_sound, + "sound sub-system", + "init - initialise the sound driver\n" + "sound play [len [freq [len [freq ...]]]] - play sounds\n" + " len - duration in ms\n" + " freq - frequency in Hz\n" +); diff --git a/cmd/source.c b/cmd/source.c new file mode 100644 index 00000000000..c9b5f8e400a --- /dev/null +++ b/cmd/source.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2001 + * Kyle Harris, kharris@nexus-tech.net + */ + +/* + * The "source" command allows to define "script images", i. e. files + * that contain command sequences that can be executed by the command + * interpreter. It returns the exit status of the last command + * executed from the script. This is very similar to running a shell + * script in a UNIX shell, hence the name for the command. + */ + +/* #define DEBUG */ + +#include <command.h> +#include <env.h> +#include <image.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <asm/byteorder.h> +#include <asm/io.h> + +static int do_source(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr; + int rcode; + const char *fit_uname = NULL, *confname = NULL; + + /* Find script image */ + if (argc < 2) { + addr = CONFIG_SYS_LOAD_ADDR; + debug("* source: default load address = 0x%08lx\n", addr); +#if defined(CONFIG_FIT) + } else if (fit_parse_subimage(argv[1], image_load_addr, &addr, + &fit_uname)) { + debug("* source: subimage '%s' from FIT image at 0x%08lx\n", + fit_uname, addr); + } else if (fit_parse_conf(argv[1], image_load_addr, &addr, &confname)) { + debug("* source: config '%s' from FIT image at 0x%08lx\n", + confname, addr); +#endif + } else { + addr = hextoul(argv[1], NULL); + debug("* source: cmdline image address = 0x%08lx\n", addr); + } + + printf ("## Executing script at %08lx\n", addr); + rcode = cmd_source_script(addr, fit_uname, confname); + return rcode; +} + +U_BOOT_LONGHELP(source, +#if defined(CONFIG_FIT) + "[<addr>][:[<image>]|#[<config>]]\n" + "\t- Run script starting at addr\n" + "\t- A FIT config name or subimage name may be specified with : or #\n" + "\t (like bootm). If the image or config name is omitted, the\n" + "\t default is used." +#else + "[<addr>]\n" + "\t- Run script starting at addr" +#endif + ); + +U_BOOT_CMD( + source, 2, 0, do_source, + "run script from memory", source_help_text +); diff --git a/cmd/spawn.c b/cmd/spawn.c new file mode 100644 index 00000000000..8829aa9728d --- /dev/null +++ b/cmd/spawn.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. + */ + +#include <command.h> +#include <console.h> +#include <env.h> +#include <malloc.h> +#include <vsprintf.h> +#include <uthread.h> + +/* Spawn arguments and job index */ +struct spa { + int argc; + char **argv; + unsigned int job_idx; +}; + +/* + * uthread group identifiers for each running job + * 0: job slot available, != 0: uthread group id + * Note that job[0] is job_id 1, job[1] is job_id 2 etc. + */ +static unsigned int job[CONFIG_CMD_SPAWN_NUM_JOBS]; +/* Return values of the commands run as jobs */ +static enum command_ret_t job_ret[CONFIG_CMD_SPAWN_NUM_JOBS]; + +static void spa_free(struct spa *spa) +{ + int i; + + if (!spa) + return; + + for (i = 0; i < spa->argc; i++) + free(spa->argv[i]); + free(spa->argv); + free(spa); +} + +static struct spa *spa_create(int argc, char *const argv[]) +{ + struct spa *spa; + int i; + + spa = calloc(1, sizeof(*spa)); + if (!spa) + return NULL; + spa->argc = argc; + spa->argv = malloc(argc * sizeof(char *)); + if (!spa->argv) + goto err; + for (i = 0; i < argc; i++) { + spa->argv[i] = strdup(argv[i]); + if (!spa->argv[i]) + goto err; + } + return spa; +err: + spa_free(spa); + return NULL; +} + +static void spawn_thread(void *arg) +{ + struct spa *spa = (struct spa *)arg; + ulong cycles = 0; + int repeatable = 0; + + job_ret[spa->job_idx] = cmd_process(0, spa->argc, spa->argv, + &repeatable, &cycles); + spa_free(spa); +} + +static unsigned int next_job_id(void) +{ + int i; + + for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++) + if (!job[i]) + return i + 1; + + /* No job available */ + return 0; +} + +static void refresh_jobs(void) +{ + int i; + + for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++) + if (job[i] && uthread_grp_done(job[i])) + job[i] = 0; + +} + +static int do_spawn(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned int id; + unsigned int idx; + struct spa *spa; + int ret; + + if (argc == 1) + return CMD_RET_USAGE; + + spa = spa_create(argc - 1, argv + 1); + if (!spa) + return CMD_RET_FAILURE; + + refresh_jobs(); + + id = next_job_id(); + if (!id) + return CMD_RET_FAILURE; + idx = id - 1; + + job[idx] = uthread_grp_new_id(); + + ret = uthread_create(NULL, spawn_thread, spa, 0, job[idx]); + if (ret) { + job[idx] = 0; + return CMD_RET_FAILURE; + } + + ret = env_set_ulong("job_id", id); + if (ret) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(spawn, CONFIG_SYS_MAXARGS, 0, do_spawn, + "run commands and summarize execution time", + "command [args...]\n"); + +static enum command_ret_t wait_job(unsigned int idx) +{ + int prev = disable_ctrlc(false); + + while (!uthread_grp_done(job[idx])) { + if (ctrlc()) { + puts("<INTERRUPT>\n"); + disable_ctrlc(prev); + return CMD_RET_FAILURE; + } + uthread_schedule(); + } + + job[idx] = 0; + disable_ctrlc(prev); + + return job_ret[idx]; +} + +static int do_wait(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum command_ret_t ret = CMD_RET_SUCCESS; + unsigned long id; + unsigned int idx; + int i; + + if (argc == 1) { + for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++) + if (job[i]) + ret = wait_job(i); + } else { + for (i = 1; i < argc; i++) { + id = dectoul(argv[i], NULL); + if (id < 1 || id > CONFIG_CMD_SPAWN_NUM_JOBS) + return CMD_RET_USAGE; + idx = (int)id - 1; + ret = wait_job(idx); + } + } + + return ret; +} + +U_BOOT_CMD(wait, CONFIG_SYS_MAXARGS, 0, do_wait, + "wait for one or more jobs to complete", + "[job_id ...]\n" + " - Wait until all specified jobs have exited and return the\n" + " exit status of the last job waited for. When no job_id is\n" + " given, wait for all the background jobs.\n"); diff --git a/cmd/spi.c b/cmd/spi.c new file mode 100644 index 00000000000..ea30c854c21 --- /dev/null +++ b/cmd/spi.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2002 + * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com + */ + +/* + * SPI Read/Write Utilities + */ + +#include <command.h> +#include <dm.h> +#include <errno.h> +#include <spi.h> + +/*----------------------------------------------------------------------- + * Definitions + */ + +#ifndef MAX_SPI_BYTES +# define MAX_SPI_BYTES 32 /* Maximum number of bytes we can handle */ +#endif + +/* + * Values from last command. + */ +static unsigned int bus; +static unsigned int cs; +static unsigned int mode; +static unsigned int freq; +static int bitlen; +static uchar dout[MAX_SPI_BYTES]; +static uchar din[MAX_SPI_BYTES]; + +static int do_spi_xfer(int bus, int cs) +{ + struct spi_slave *slave; + int ret = 0; + +#if CONFIG_IS_ENABLED(DM_SPI) + char name[30], *str; + struct udevice *dev; + + snprintf(name, sizeof(name), "generic_%d:%d", bus, cs); + str = strdup(name); + if (!str) + return -ENOMEM; + ret = _spi_get_bus_and_cs(bus, cs, freq, mode, "spi_generic_drv", + str, &dev, &slave); + if (ret) + return ret; +#else + slave = spi_setup_slave(bus, cs, freq, mode); + if (!slave) { + printf("Invalid device %d:%d\n", bus, cs); + return -EINVAL; + } +#endif + + ret = spi_claim_bus(slave); + if (ret) + goto done; + ret = spi_xfer(slave, bitlen, dout, din, + SPI_XFER_BEGIN | SPI_XFER_END); +#if !CONFIG_IS_ENABLED(DM_SPI) + /* We don't get an error code in this case */ + if (ret) + ret = -EIO; +#endif + if (ret) { + printf("Error %d during SPI transaction\n", ret); + } else { + int j; + + for (j = 0; j < ((bitlen + 7) / 8); j++) + printf("%02X", din[j]); + printf("\n"); + } +done: + spi_release_bus(slave); +#if !CONFIG_IS_ENABLED(DM_SPI) + spi_free_slave(slave); +#endif + + return ret; +} + +/* + * SPI read/write + * + * Syntax: + * spi {dev} {num_bits} {dout} + * {dev} is the device number for controlling chip select (see TBD) + * {num_bits} is the number of bits to send & receive (base 10) + * {dout} is a hexadecimal string of data to send + * The command prints out the hexadecimal string received via SPI. + */ + +int do_spi(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *cp = 0; + uchar tmp; + int j; + + /* + * We use the last specified parameters, unless new ones are + * entered. + */ + if (freq == 0) + freq = 1000000; + + if ((flag & CMD_FLAG_REPEAT) == 0) + { + if (argc < 2) + return CMD_RET_USAGE; + + if (argc >= 2) { + mode = CONFIG_DEFAULT_SPI_MODE; + bus = dectoul(argv[1], &cp); + if (*cp == ':') { + cs = dectoul(cp + 1, &cp); + } else { + cs = bus; + bus = CONFIG_DEFAULT_SPI_BUS; + } + if (*cp == '.') + mode = dectoul(cp + 1, &cp); + if (*cp == '@') + freq = dectoul(cp + 1, &cp); + } + if (argc >= 3) + bitlen = dectoul(argv[2], NULL); + if (argc >= 4) { + cp = argv[3]; + for(j = 0; *cp; j++, cp++) { + tmp = *cp - '0'; + if(tmp > 9) + tmp -= ('A' - '0') - 10; + if(tmp > 15) + tmp -= ('a' - 'A'); + if(tmp > 15) { + printf("Hex conversion error on %c\n", *cp); + return 1; + } + if((j % 2) == 0) + dout[j / 2] = (tmp << 4); + else + dout[j / 2] |= tmp; + } + } + } + + if ((bitlen < 0) || (bitlen > (MAX_SPI_BYTES * 8))) { + printf("Invalid bitlen %d\n", bitlen); + return 1; + } + + if (do_spi_xfer(bus, cs)) + return 1; + + return 0; +} + +/***************************************************/ + +U_BOOT_CMD( + sspi, 5, 1, do_spi, + "SPI utility command", + "[<bus>:]<cs>[.<mode>][@<freq>] <bit_len> <dout> - Send and receive bits\n" + "<bus> - Identifies the SPI bus\n" + "<cs> - Identifies the chip select\n" + "<mode> - Identifies the SPI mode to use\n" + "<freq> - Identifies the SPI bus frequency in Hz\n" + "<bit_len> - Number of bits to send (base 10)\n" + "<dout> - Hexadecimal string that gets sent" +); diff --git a/cmd/spl.c b/cmd/spl.c new file mode 100644 index 00000000000..f591dc07fb6 --- /dev/null +++ b/cmd/spl.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2011 + * Corscience GmbH & Co. KG - Simon Schwarz <schwarz@corscience.de> + */ + +#include <command.h> +#include <cmd_spl.h> +#include <env.h> +#include <image.h> +#include <log.h> +#include <asm/global_data.h> +#include <linux/libfdt.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const char **subcmd_list[] = { + + [SPL_EXPORT_FDT] = (const char * []) { +#ifdef CONFIG_OF_LIBFDT + "start", + "loados", + #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH + "ramdisk", + #endif + "fdt", + "cmdline", + "bdt", + "prep", +#endif + NULL, + }, + NULL +}; + +/* Calls bootm with the parameters given */ +static int call_bootm(int argc, char *const argv[], const char *subcommand[]) +{ + char *bootm_argv[5]; + + int i = 0; + int ret = 0; + int j; + + /* create paramter array */ + bootm_argv[0] = "do_bootm"; + switch (argc) { + case 3: + bootm_argv[4] = argv[2]; /* fdt addr */ + fallthrough; + case 2: + bootm_argv[3] = argv[1]; /* initrd addr */ + fallthrough; + case 1: + bootm_argv[2] = argv[0]; /* kernel addr */ + } + + /* + * - do the work - + * exec subcommands of do_bootm to init the images + * data structure + */ + while (subcommand[i] != NULL) { + bootm_argv[1] = (char *)subcommand[i]; + debug("args %d: %s %s ", argc, bootm_argv[0], bootm_argv[1]); + for (j = 0; j < argc; j++) + debug("%s ", bootm_argv[j + 2]); + debug("\n"); + + ret = do_bootm(find_cmd("do_bootm"), 0, argc+2, + bootm_argv); + debug("Subcommand retcode: %d\n", ret); + i++; + } + + if (ret) { + printf("ERROR prep subcommand failed!\n"); + return -1; + } + + return 0; +} + +static struct cmd_tbl cmd_spl_export_sub[] = { + U_BOOT_CMD_MKENT(fdt, 0, 1, (void *)SPL_EXPORT_FDT, "", ""), +}; + +static int spl_export(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const struct cmd_tbl *c; + + if (argc < 2) /* no subcommand */ + return cmd_usage(cmdtp); + + c = find_cmd_tbl(argv[1], &cmd_spl_export_sub[0], + ARRAY_SIZE(cmd_spl_export_sub)); + if ((c) && ((long)c->cmd <= SPL_EXPORT_LAST)) { + argc -= 2; + argv += 2; + if (call_bootm(argc, argv, subcmd_list[(long)c->cmd])) + return -1; + switch ((long)c->cmd) { +#ifdef CONFIG_OF_LIBFDT + case SPL_EXPORT_FDT: + printf("Argument image is now in RAM: 0x%p\n", + (void *)images.ft_addr); + env_set_addr("fdtargsaddr", images.ft_addr); + env_set_hex("fdtargslen", fdt_totalsize(images.ft_addr)); +#ifdef CONFIG_CMD_SPL_WRITE_SIZE + if (fdt_totalsize(images.ft_addr) > + CONFIG_CMD_SPL_WRITE_SIZE) + puts("WARN: FDT size > CMD_SPL_WRITE_SIZE\n"); +#endif + break; +#endif + } + } else { + /* Unrecognized command */ + return cmd_usage(cmdtp); + } + + return 0; +} + +static struct cmd_tbl cmd_spl_sub[] = { + U_BOOT_CMD_MKENT(export, 0, 1, (void *)SPL_EXPORT, "", ""), +}; + +static int do_spl(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + const struct cmd_tbl *c; + int cmd; + + if (argc < 2) /* no subcommand */ + return cmd_usage(cmdtp); + + c = find_cmd_tbl(argv[1], &cmd_spl_sub[0], ARRAY_SIZE(cmd_spl_sub)); + if (c) { + cmd = (long)c->cmd; + switch (cmd) { + case SPL_EXPORT: + argc--; + argv++; + if (spl_export(cmdtp, flag, argc, argv)) + printf("Subcommand failed\n"); + break; + default: + /* unrecognized command */ + return cmd_usage(cmdtp); + } + } else { + /* Unrecognized command */ + return cmd_usage(cmdtp); + } + return 0; +} + +U_BOOT_CMD( + spl, 6 , 1, do_spl, "SPL configuration", + "export fdt [kernel_addr] [initrd_addr] [fdt_addr]\n" + "\tkernel_addr\taddress where a kernel image is stored.\n" + "\t\t\tkernel is loaded as part of the boot process, but it is not started.\n" + "\tinitrd_addr\taddress of initial ramdisk\n" + "\t\t\tcan be set to \"-\" if fdt_addr without initrd_addr is used.\n" + "\tfdt_addr\tthe address of the device tree.\n" + ); diff --git a/cmd/sqfs.c b/cmd/sqfs.c new file mode 100644 index 00000000000..107038c4cf2 --- /dev/null +++ b/cmd/sqfs.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa <joaomarcos.costa@bootlin.com> + * + * squashfs.c: implements SquashFS related commands + */ + +#include <command.h> +#include <fs.h> +#include <squashfs.h> + +static int do_sqfs_ls(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + return do_ls(cmdtp, flag, argc, argv, FS_TYPE_SQUASHFS); +} + +U_BOOT_CMD(sqfsls, 4, 1, do_sqfs_ls, + "List files in directory. Default: root (/).", + "<interface> [<dev[:part]>] [directory]\n" + " - list files from 'dev' on 'interface' in 'directory'\n" +); + +static int do_sqfs_load(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + return do_load(cmdtp, flag, argc, argv, FS_TYPE_SQUASHFS); +} + +U_BOOT_CMD(sqfsload, 7, 0, do_sqfs_load, + "load binary file from a SquashFS filesystem", + "<interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]\n" + " - Load binary file 'filename' from 'dev' on 'interface'\n" + " to address 'addr' from SquashFS filesystem.\n" + " 'pos' gives the file position to start loading from.\n" + " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n" + " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n" + " the load stops on end of file.\n" + " If either 'pos' or 'bytes' are not aligned to\n" + " ARCH_DMA_MINALIGN then a misaligned buffer warning will\n" + " be printed and performance will suffer for the load." +); diff --git a/cmd/stackprot_test.c b/cmd/stackprot_test.c new file mode 100644 index 00000000000..78e9beba5bf --- /dev/null +++ b/cmd/stackprot_test.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Broadcom + */ + +#include <command.h> +#include <linux/string.h> + +static int do_test_stackprot_fail(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + /* + * In order to avoid having the compiler optimize away the stack smashing + * we need to do a little something here. + */ + char a[128]; + + memset(a, 0xa5, 512); + + printf("We have smashed our stack as this should not exceed 128: sizeof(a) = %zd\n", + strlen(a)); + + return 0; +} + +U_BOOT_CMD(stackprot_test, 1, 1, do_test_stackprot_fail, + "test stack protector fail", ""); diff --git a/cmd/strings.c b/cmd/strings.c new file mode 100644 index 00000000000..beac2a6e6b3 --- /dev/null +++ b/cmd/strings.c @@ -0,0 +1,47 @@ +/* + * cmd_strings.c - just like `strings` command + * + * Copyright (c) 2008 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <config.h> +#include <command.h> +#include <vsprintf.h> +#include <linux/string.h> + +static char *start_addr, *last_addr; + +int do_strings(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + if (argc == 1) + return CMD_RET_USAGE; + + if ((flag & CMD_FLAG_REPEAT) == 0) { + start_addr = (char *)hextoul(argv[1], NULL); + if (argc > 2) + last_addr = (char *)hextoul(argv[2], NULL); + else + last_addr = (char *)-1; + } + + char *addr = start_addr; + do { + puts(addr); + puts("\n"); + addr += strlen(addr) + 1; + } while (addr[0] && addr < last_addr); + + last_addr = addr + (last_addr - start_addr); + start_addr = addr; + + return 0; +} + +U_BOOT_CMD( + strings, 3, 1, do_strings, + "display strings", + "<addr> [byte count]\n" + " - display strings at <addr> for at least [byte count] or first double NUL" +); diff --git a/cmd/sysboot.c b/cmd/sysboot.c new file mode 100644 index 00000000000..93d4a400830 --- /dev/null +++ b/cmd/sysboot.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <command.h> +#include <env.h> +#include <fs.h> +#include <pxe_utils.h> +#include <vsprintf.h> + +/** + * struct sysboot_info - useful information for sysboot helpers + * + * @fstype: Filesystem type (FS_TYPE_...) + * @ifname: Interface name (e.g. "ide", "scsi") + * @dev_part_str is in the format: + * <dev>.<hw_part>:<part> where <dev> is the device number, + * <hw_part> is the optional hardware partition number and + * <part> is the partition number + */ +struct sysboot_info { + int fstype; + const char *ifname; + const char *dev_part_str; +}; + +static int sysboot_read_file(struct pxe_context *ctx, const char *file_path, + char *file_addr, enum bootflow_img_t type, + ulong *sizep) +{ + struct sysboot_info *info = ctx->userdata; + loff_t len_read; + ulong addr; + int ret; + + addr = simple_strtoul(file_addr, NULL, 16); + ret = fs_set_blk_dev(info->ifname, info->dev_part_str, info->fstype); + if (ret) + return ret; + ret = fs_read(file_path, addr, 0, 0, &len_read); + if (ret) + return ret; + *sizep = len_read; + + return 0; +} + +/* + * Boots a system using a local disk syslinux/extlinux file + * + * Returns 0 on success, 1 on error. + */ +static int do_sysboot(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned long pxefile_addr_r; + struct pxe_context ctx; + char *pxefile_addr_str; + struct sysboot_info info; + char *filename; + int prompt = 0; + int ret; + + if (argc > 1 && strstr(argv[1], "-p")) { + prompt = 1; + argc--; + argv++; + } + + if (argc < 4) + return cmd_usage(cmdtp); + + if (argc < 5) { + pxefile_addr_str = from_env("pxefile_addr_r"); + if (!pxefile_addr_str) + return 1; + } else { + pxefile_addr_str = argv[4]; + } + + if (argc < 6) { + filename = env_get("bootfile"); + if (!filename) { + printf("Specify a filename or set the ${bootfile} environment variable\n"); + return 1; + } + } else { + filename = argv[5]; + env_set("bootfile", filename); + } + + if (strstr(argv[3], "ext2")) { + info.fstype = FS_TYPE_EXT; + } else if (strstr(argv[3], "fat")) { + info.fstype = FS_TYPE_FAT; + } else if (strstr(argv[3], "any")) { + info.fstype = FS_TYPE_ANY; + } else { + printf("Invalid filesystem: %s\n", argv[3]); + return 1; + } + info.ifname = argv[1]; + info.dev_part_str = argv[2]; + + if (strict_strtoul(pxefile_addr_str, 16, &pxefile_addr_r) < 0) { + printf("Invalid pxefile address: %s\n", pxefile_addr_str); + return 1; + } + + if (pxe_setup_ctx(&ctx, cmdtp, sysboot_read_file, &info, true, + filename, false, false)) { + printf("Out of memory\n"); + return CMD_RET_FAILURE; + } + + if (get_pxe_file(&ctx, filename, pxefile_addr_r) + < 0) { + printf("Error reading config file\n"); + pxe_destroy_ctx(&ctx); + return 1; + } + + ret = pxe_process(&ctx, pxefile_addr_r, prompt); + pxe_destroy_ctx(&ctx); + if (ret) + return CMD_RET_FAILURE; + + return 0; +} + +U_BOOT_CMD(sysboot, 7, 1, do_sysboot, + "command to get and boot from syslinux files", + "[-p] <interface> <dev[:part]> <ext2|fat|any> [addr] [filename]\n" + " - load and parse syslinux menu file 'filename' from ext2, fat\n" + " or any filesystem on 'dev' on 'interface' to address 'addr'" +); diff --git a/cmd/tcpm.c b/cmd/tcpm.c new file mode 100644 index 00000000000..39578f6ccc0 --- /dev/null +++ b/cmd/tcpm.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2024 Collabora + */ + +#include <command.h> +#include <errno.h> +#include <dm.h> +#include <dm/uclass-internal.h> +#include <usb/tcpm.h> + +#define LIMIT_DEV 32 +#define LIMIT_PARENT 20 + +static struct udevice *currdev; + +static int do_dev(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int devnum, ret; + + switch (argc) { + case 2: + devnum = (int)dectoul(argv[1], NULL); + ret = tcpm_get(devnum, &currdev); + if (ret) { + log_err("Can't get TCPM %d: %d (%s)!\n", devnum, ret, errno_str(ret)); + return CMD_RET_FAILURE; + } + case 1: + if (!currdev) { + log_err("TCPM device is not set!\n\n"); + return CMD_RET_USAGE; + } + + printf("dev: %d @ %s\n", dev_seq(currdev), currdev->name); + } + + return CMD_RET_SUCCESS; +} + +static int do_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret, err = 0; + + printf("| ID | %-*.*s| %-*.*s| %s @ %s\n", + LIMIT_DEV, LIMIT_DEV, "Name", + LIMIT_PARENT, LIMIT_PARENT, "Parent name", + "Parent uclass", "seq"); + + for (ret = uclass_first_device_check(UCLASS_TCPM, &dev); dev; + ret = uclass_next_device_check(&dev)) { + if (ret) + err = ret; + + printf("| %2d | %-*.*s| %-*.*s| %s @ %d | status: %i\n", + dev_seq(dev), + LIMIT_DEV, LIMIT_DEV, dev->name, + LIMIT_PARENT, LIMIT_PARENT, dev->parent->name, + dev_get_uclass_name(dev->parent), dev_seq(dev->parent), + ret); + } + + if (err) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +int do_print_info(struct udevice *dev) +{ + enum typec_orientation orientation = tcpm_get_orientation(dev); + const char *state = tcpm_get_state(dev); + int pd_rev = tcpm_get_pd_rev(dev); + int mv = tcpm_get_voltage(dev); + int ma = tcpm_get_current(dev); + enum typec_role pwr_role = tcpm_get_pwr_role(dev); + enum typec_data_role data_role = tcpm_get_data_role(dev); + bool connected = tcpm_is_connected(dev); + + if (!connected) { + printf("TCPM State: %s\n", state); + return 0; + } + + printf("Orientation: %s\n", typec_orientation_name[orientation]); + printf("PD Revision: %s\n", typec_pd_rev_name[pd_rev]); + printf("Power Role: %s\n", typec_role_name[pwr_role]); + printf("Data Role: %s\n", typec_data_role_name[data_role]); + printf("Voltage: %2d.%03d V\n", mv / 1000, mv % 1000); + printf("Current: %2d.%03d A\n", ma / 1000, ma % 1000); + + return 0; +} + +static int do_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (!currdev) { + printf("First, set the TCPM device!\n"); + return CMD_RET_USAGE; + } + + return do_print_info(currdev); +} + +static struct cmd_tbl subcmd[] = { + U_BOOT_CMD_MKENT(dev, 2, 1, do_dev, "", ""), + U_BOOT_CMD_MKENT(list, 1, 1, do_list, "", ""), + U_BOOT_CMD_MKENT(info, 1, 1, do_info, "", ""), +}; + +static int do_tcpm(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cmd; + + argc--; + argv++; + + cmd = find_cmd_tbl(argv[0], subcmd, ARRAY_SIZE(subcmd)); + if (!cmd || argc > cmd->maxargs) + return CMD_RET_USAGE; + + return cmd->cmd(cmdtp, flag, argc, argv); +} + + /**************************************************/ + +U_BOOT_CMD(tcpm, CONFIG_SYS_MAXARGS, 1, do_tcpm, + "TCPM sub-system", + "list - list TCPM devices\n" + "tcpm dev [ID] - show or [set] operating TCPM device\n" + "tcpm info - dump information\n" +); diff --git a/cmd/temperature.c b/cmd/temperature.c new file mode 100644 index 00000000000..c145d019364 --- /dev/null +++ b/cmd/temperature.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * Copyright (c) 2022 Sartura Ltd. + * Written by Robert Marko <robert.marko@sartura.hr> + */ + +#include <command.h> +#include <dm.h> +#include <thermal.h> + +#define LIMIT_DEVNAME 30 + +static int do_get(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret, temp; + + if (argc < 2) { + printf("thermal device not selected\n"); + return CMD_RET_FAILURE; + } + + ret = uclass_get_device_by_name(UCLASS_THERMAL, argv[1], &dev); + if (ret) { + printf("thermal device not found\n"); + return CMD_RET_FAILURE; + } + + ret = thermal_get_temp(dev, &temp); + if (ret) + return CMD_RET_FAILURE; + + printf("%s: %d mC\n", dev->name, temp); + + return CMD_RET_SUCCESS; +} + +static int do_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + + printf("| %-*.*s| %-*.*s| %s\n", + LIMIT_DEVNAME, LIMIT_DEVNAME, "Device", + LIMIT_DEVNAME, LIMIT_DEVNAME, "Driver", + "Parent"); + + uclass_foreach_dev_probe(UCLASS_THERMAL, dev) { + printf("| %-*.*s| %-*.*s| %s\n", + LIMIT_DEVNAME, LIMIT_DEVNAME, dev->name, + LIMIT_DEVNAME, LIMIT_DEVNAME, dev->driver->name, + dev->parent->name); + } + + return CMD_RET_SUCCESS; +} + +static struct cmd_tbl temperature_subcmd[] = { + U_BOOT_CMD_MKENT(list, 1, 1, do_list, "", ""), + U_BOOT_CMD_MKENT(get, 2, 1, do_get, "", ""), +}; + +static int do_temperature(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *cmd; + + argc--; + argv++; + + cmd = find_cmd_tbl(argv[0], temperature_subcmd, ARRAY_SIZE(temperature_subcmd)); + if (!cmd || argc > cmd->maxargs) + return CMD_RET_USAGE; + + return cmd->cmd(cmdtp, flag, argc, argv); +} + +U_BOOT_CMD(temperature, CONFIG_SYS_MAXARGS, 1, do_temperature, + "thermal sensor temperature", + "list\t\tshow list of temperature sensors\n" + "get [thermal device name]\tprint temperature in degrees C" +); diff --git a/cmd/terminal.c b/cmd/terminal.c new file mode 100644 index 00000000000..14610694255 --- /dev/null +++ b/cmd/terminal.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2007 OpenMoko, Inc. + * Written by Harald Welte <laforge@openmoko.org> + */ + +/* + * Boot support + */ +#include <command.h> +#include <stdio_dev.h> +#include <serial.h> + +int do_terminal(struct cmd_tbl *cmd, int flag, int argc, char *const argv[]) +{ + int last_tilde = 0; + struct stdio_dev *dev = NULL; + + if (argc < 1) + return -1; + + /* Scan for selected output/input device */ + dev = stdio_get_by_name(argv[1]); + if (!dev) + return -1; + + if (IS_ENABLED(CONFIG_SERIAL) && !IS_ENABLED(CONFIG_DM_SERIAL)) + serial_reinit_all(); + + printf("Entering terminal mode for port %s\n", dev->name); + puts("Use '~.' to leave the terminal and get back to u-boot\n"); + + while (1) { + int c; + + /* read from console and display on serial port */ + if (stdio_devices[0]->tstc(stdio_devices[0])) { + c = stdio_devices[0]->getc(stdio_devices[0]); + if (last_tilde == 1) { + if (c == '.') { + putc(c); + putc('\n'); + break; + } else { + last_tilde = 0; + /* write the delayed tilde */ + dev->putc(dev, '~'); + /* fall-through to print current + * character */ + } + } + if (c == '~') { + last_tilde = 1; + puts("[u-boot]"); + putc(c); + } + dev->putc(dev, c); + } + + /* read from serial port and display on console */ + if (dev->tstc(dev)) { + c = dev->getc(dev); + putc(c); + } + } + return 0; +} + +/***************************************************/ + +U_BOOT_CMD( + terminal, 3, 1, do_terminal, + "start terminal emulator", + "" +); diff --git a/cmd/test.c b/cmd/test.c new file mode 100644 index 00000000000..a9ac07e6143 --- /dev/null +++ b/cmd/test.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2000-2009 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <fs.h> +#include <log.h> +#include <slre.h> +#include <vsprintf.h> +#include <linux/string.h> + +#define OP_INVALID 0 +#define OP_NOT 1 +#define OP_OR 2 +#define OP_AND 3 +#define OP_STR_EMPTY 4 +#define OP_STR_NEMPTY 5 +#define OP_STR_EQ 6 +#define OP_STR_NEQ 7 +#define OP_STR_LT 8 +#define OP_STR_GT 9 +#define OP_INT_EQ 10 +#define OP_INT_NEQ 11 +#define OP_INT_LT 12 +#define OP_INT_LE 13 +#define OP_INT_GT 14 +#define OP_INT_GE 15 +#define OP_FILE_EXISTS 16 +#define OP_REGEX 17 + +const struct { + int arg; + const char *str; + int op; + int adv; +} op_adv[] = { + {1, "=", OP_STR_EQ, 3}, + {1, "!=", OP_STR_NEQ, 3}, + {1, "<", OP_STR_LT, 3}, + {1, ">", OP_STR_GT, 3}, + {1, "-eq", OP_INT_EQ, 3}, + {1, "-ne", OP_INT_NEQ, 3}, + {1, "-lt", OP_INT_LT, 3}, + {1, "-le", OP_INT_LE, 3}, + {1, "-gt", OP_INT_GT, 3}, + {1, "-ge", OP_INT_GE, 3}, + {0, "!", OP_NOT, 1}, + {0, "-o", OP_OR, 1}, + {0, "-a", OP_AND, 1}, + {0, "-z", OP_STR_EMPTY, 2}, + {0, "-n", OP_STR_NEMPTY, 2}, + {0, "-e", OP_FILE_EXISTS, 4}, +#ifdef CONFIG_REGEX + {1, "=~", OP_REGEX, 3}, +#endif +}; + +static int do_test(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char * const *ap; + int i, op, left, adv, expr, last_expr, last_unop, last_binop; + + /* args? */ + if (argc < 3) + return 1; + +#ifdef DEBUG + { + debug("test(%d):", argc); + left = 1; + while (argv[left]) + debug(" '%s'", argv[left++]); + } +#endif + + left = argc - 1; + ap = argv + 1; + expr = 0; + last_unop = OP_INVALID; + last_binop = OP_INVALID; + last_expr = -1; + while (left > 0) { + for (i = 0; i < ARRAY_SIZE(op_adv); i++) { + if (left <= op_adv[i].arg) + continue; + if (!strcmp(ap[op_adv[i].arg], op_adv[i].str)) { + op = op_adv[i].op; + adv = op_adv[i].adv; + break; + } + } + if (i == ARRAY_SIZE(op_adv)) { + expr = 1; + break; + } + if (left < adv) { + expr = 1; + break; + } + + switch (op) { + case OP_STR_EMPTY: + expr = strlen(ap[1]) == 0 ? 1 : 0; + break; + case OP_STR_NEMPTY: + expr = strlen(ap[1]) == 0 ? 0 : 1; + break; + case OP_STR_EQ: + expr = strcmp(ap[0], ap[2]) == 0; + break; + case OP_STR_NEQ: + expr = strcmp(ap[0], ap[2]) != 0; + break; + case OP_STR_LT: + expr = strcmp(ap[0], ap[2]) < 0; + break; + case OP_STR_GT: + expr = strcmp(ap[0], ap[2]) > 0; + break; + case OP_INT_EQ: + expr = simple_strtol(ap[0], NULL, 0) == + simple_strtol(ap[2], NULL, 0); + break; + case OP_INT_NEQ: + expr = simple_strtol(ap[0], NULL, 0) != + simple_strtol(ap[2], NULL, 0); + break; + case OP_INT_LT: + expr = simple_strtol(ap[0], NULL, 0) < + simple_strtol(ap[2], NULL, 0); + break; + case OP_INT_LE: + expr = simple_strtol(ap[0], NULL, 0) <= + simple_strtol(ap[2], NULL, 0); + break; + case OP_INT_GT: + expr = simple_strtol(ap[0], NULL, 0) > + simple_strtol(ap[2], NULL, 0); + break; + case OP_INT_GE: + expr = simple_strtol(ap[0], NULL, 0) >= + simple_strtol(ap[2], NULL, 0); + break; + case OP_FILE_EXISTS: + expr = file_exists(ap[1], ap[2], ap[3], FS_TYPE_ANY); + break; +#ifdef CONFIG_REGEX + case OP_REGEX: { + struct slre slre; + + if (slre_compile(&slre, ap[2]) == 0) { + printf("Error compiling regex: %s\n", slre.err_str); + expr = 0; + break; + } + + expr = slre_match(&slre, ap[0], strlen(ap[0]), NULL); + break; + } +#endif + } + + switch (op) { + case OP_OR: + last_expr = expr; + last_binop = OP_OR; + break; + case OP_AND: + last_expr = expr; + last_binop = OP_AND; + break; + case OP_NOT: + if (last_unop == OP_NOT) + last_unop = OP_INVALID; + else + last_unop = OP_NOT; + break; + default: + if (last_unop == OP_NOT) { + expr = !expr; + last_unop = OP_INVALID; + } + + if (last_binop == OP_OR) + expr = last_expr || expr; + else if (last_binop == OP_AND) + expr = last_expr && expr; + last_binop = OP_INVALID; + + break; + } + + ap += adv; left -= adv; + } + + expr = !expr; + + debug (": returns %d\n", expr); + + return expr; +} + +#undef true +#undef false + +U_BOOT_CMD( + test, CONFIG_SYS_MAXARGS, 1, do_test, + "minimal test like /bin/sh", + "[args..]" +); + +static int do_false(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return 1; +} + +U_BOOT_CMD( + false, CONFIG_SYS_MAXARGS, 1, do_false, + "do nothing, unsuccessfully", + NULL +); + +static int do_true(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return 0; +} + +U_BOOT_CMD( + true, CONFIG_SYS_MAXARGS, 1, do_true, + "do nothing, successfully", + NULL +); diff --git a/cmd/thordown.c b/cmd/thordown.c new file mode 100644 index 00000000000..70061bf8d4c --- /dev/null +++ b/cmd/thordown.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cmd_thordown.c -- USB TIZEN "THOR" Downloader gadget + * + * Copyright (C) 2013 Lukasz Majewski <l.majewski@samsung.com> + * All rights reserved. + */ + +#include <command.h> +#include <thor.h> +#include <dfu.h> +#include <g_dnl.h> +#include <usb.h> +#include <linux/printk.h> + +int do_thor_down(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char *interface, *devstring; + int controller_index; + struct udevice *udc; + int ret; + + if (argc < 4) + return CMD_RET_USAGE; + + puts("TIZEN \"THOR\" Downloader\n"); + + interface = argv[2]; + devstring = argv[3]; + + ret = dfu_init_env_entities(interface, devstring); + if (ret) + goto done; + + controller_index = simple_strtoul(argv[1], NULL, 0); + ret = udc_device_get_by_index(controller_index, &udc); + if (ret) { + pr_err("USB init failed: %d\n", ret); + ret = CMD_RET_FAILURE; + goto exit; + } + + ret = g_dnl_register("usb_dnl_thor"); + if (ret) { + pr_err("g_dnl_register failed %d\n", ret); + ret = CMD_RET_FAILURE; + goto exit; + } + + ret = thor_init(udc); + if (ret) { + pr_err("THOR DOWNLOAD failed: %d\n", ret); + ret = CMD_RET_FAILURE; + goto exit; + } + + do { + ret = thor_handle(udc); + if (ret == THOR_DFU_REINIT_NEEDED) { + dfu_free_entities(); + ret = dfu_init_env_entities(interface, devstring); + } + if (ret) { + pr_err("THOR failed: %d\n", ret); + ret = CMD_RET_FAILURE; + goto exit; + } + } while (ret == 0); +exit: + g_dnl_unregister(); + udc_device_put(udc); +done: + dfu_free_entities(); + + return ret; +} + +U_BOOT_CMD(thordown, CONFIG_SYS_MAXARGS, 1, do_thor_down, + "TIZEN \"THOR\" downloader", + "<USB_controller> <interface> <dev>\n" + " - device software upgrade via LTHOR TIZEN download\n" + " program via <USB_controller> on device <dev>,\n" + " attached to interface <interface>\n" +); diff --git a/cmd/ti/Kconfig b/cmd/ti/Kconfig new file mode 100644 index 00000000000..9442c9993c1 --- /dev/null +++ b/cmd/ti/Kconfig @@ -0,0 +1,19 @@ +menu "TI specific command line interface" + +config CMD_DDR3 + bool "command for verifying DDR features" + depends on ARCH_KEYSTONE || DRA7XX + help + Support for testing ddr3 on TI platforms. This command + supports memory verification, memory comapre and ecc + verification if supported. + +config CMD_PD + bool "command for verifying power domains" + depends on TI_POWER_DOMAIN + help + Debug command for K3 power domains. For this to work, the + K3 power domain driver must be enabled for the u-boot; by + default it is only enabled for SPL. + +endmenu diff --git a/cmd/ti/Makefile b/cmd/ti/Makefile new file mode 100644 index 00000000000..5f9c64f598a --- /dev/null +++ b/cmd/ti/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2017 Texas Instruments Incorporated - https://www.ti.com/ + +obj-$(CONFIG_CMD_DDR3) += ddr3.o +obj-$(CONFIG_CMD_PD) += pd.o diff --git a/cmd/ti/ddr3.c b/cmd/ti/ddr3.c new file mode 100644 index 00000000000..70ce53d01e8 --- /dev/null +++ b/cmd/ti/ddr3.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EMIF: DDR3 test commands + * + * Copyright (C) 2012-2017 Texas Instruments Incorporated, <www.ti.com> + */ + +#include <cpu_func.h> +#include <env.h> +#include <init.h> +#include <log.h> +#include <asm/arch/hardware.h> +#include <asm/cache.h> +#include <asm/emif.h> +#include <command.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +#ifdef CONFIG_ARCH_KEYSTONE +#include <asm/arch/ddr3.h> +#define DDR_MIN_ADDR CFG_SYS_SDRAM_BASE +#define STACKSIZE (512 << 10) /* 512 KiB */ + +#define DDR_REMAP_ADDR 0x80000000 +#define ECC_START_ADDR1 ((DDR_MIN_ADDR - DDR_REMAP_ADDR) >> 17) + +#define ECC_END_ADDR1 (((gd->start_addr_sp - DDR_REMAP_ADDR - \ + STACKSIZE) >> 17) - 2) +#endif + +#define DDR_TEST_BURST_SIZE 1024 + +static int ddr_memory_test(u32 start_address, u32 end_address, int quick) +{ + u32 index_start, value, index; + + index_start = start_address; + + while (1) { + /* Write a pattern */ + for (index = index_start; + index < index_start + DDR_TEST_BURST_SIZE; + index += 4) + __raw_writel(index, index); + + /* Read and check the pattern */ + for (index = index_start; + index < index_start + DDR_TEST_BURST_SIZE; + index += 4) { + value = __raw_readl(index); + if (value != index) { + printf("ddr_memory_test: Failed at address index = 0x%x value = 0x%x *(index) = 0x%x\n", + index, value, __raw_readl(index)); + + return -1; + } + } + + index_start += DDR_TEST_BURST_SIZE; + if (index_start >= end_address) + break; + + if (quick) + continue; + + /* Write a pattern for complementary values */ + for (index = index_start; + index < index_start + DDR_TEST_BURST_SIZE; + index += 4) + __raw_writel((u32)~index, index); + + /* Read and check the pattern */ + for (index = index_start; + index < index_start + DDR_TEST_BURST_SIZE; + index += 4) { + value = __raw_readl(index); + if (value != ~index) { + printf("ddr_memory_test: Failed at address index = 0x%x value = 0x%x *(index) = 0x%x\n", + index, value, __raw_readl(index)); + + return -1; + } + } + + index_start += DDR_TEST_BURST_SIZE; + if (index_start >= end_address) + break; + + /* Write a pattern */ + for (index = index_start; + index < index_start + DDR_TEST_BURST_SIZE; + index += 2) + __raw_writew((u16)index, index); + + /* Read and check the pattern */ + for (index = index_start; + index < index_start + DDR_TEST_BURST_SIZE; + index += 2) { + value = __raw_readw(index); + if (value != (u16)index) { + printf("ddr_memory_test: Failed at address index = 0x%x value = 0x%x *(index) = 0x%x\n", + index, value, __raw_readw(index)); + + return -1; + } + } + + index_start += DDR_TEST_BURST_SIZE; + if (index_start >= end_address) + break; + + /* Write a pattern */ + for (index = index_start; + index < index_start + DDR_TEST_BURST_SIZE; + index += 1) + __raw_writeb((u8)index, index); + + /* Read and check the pattern */ + for (index = index_start; + index < index_start + DDR_TEST_BURST_SIZE; + index += 1) { + value = __raw_readb(index); + if (value != (u8)index) { + printf("ddr_memory_test: Failed at address index = 0x%x value = 0x%x *(index) = 0x%x\n", + index, value, __raw_readb(index)); + + return -1; + } + } + + index_start += DDR_TEST_BURST_SIZE; + if (index_start >= end_address) + break; + } + + puts("ddr memory test PASSED!\n"); + return 0; +} + +static int ddr_memory_compare(u32 address1, u32 address2, u32 size) +{ + u32 index, value, index2, value2; + + for (index = address1, index2 = address2; + index < address1 + size; + index += 4, index2 += 4) { + value = __raw_readl(index); + value2 = __raw_readl(index2); + + if (value != value2) { + printf("ddr_memory_test: Compare failed at address = 0x%x value = 0x%x, address2 = 0x%x value2 = 0x%x\n", + index, value, index2, value2); + + return -1; + } + } + + puts("ddr memory compare PASSED!\n"); + return 0; +} + +static void ddr_check_ecc_status(void) +{ + struct emif_reg_struct *emif = (struct emif_reg_struct *)EMIF1_BASE; + u32 err_1b = readl(&emif->emif_1b_ecc_err_cnt); + u32 int_status = readl(&emif->emif_irqstatus_raw_sys); + int ecc_test = 0; + char *env; + + env = env_get("ecc_test"); + if (env) + ecc_test = simple_strtol(env, NULL, 0); + + puts("ECC test Status:\n"); + if (int_status & EMIF_INT_WR_ECC_ERR_SYS_MASK) + puts("\tECC test: DDR ECC write error interrupted\n"); + + if (int_status & EMIF_INT_TWOBIT_ECC_ERR_SYS_MASK) + if (!ecc_test) + panic("\tECC test: DDR ECC 2-bit error interrupted"); + + if (int_status & EMIF_INT_ONEBIT_ECC_ERR_SYS_MASK) + puts("\tECC test: DDR ECC 1-bit error interrupted\n"); + + if (err_1b) + printf("\tECC test: 1-bit ECC err count: 0x%x\n", err_1b); +} + +static int ddr_memory_ecc_err(u32 addr, u32 ecc_err) +{ + struct emif_reg_struct *emif = (struct emif_reg_struct *)EMIF1_BASE; + u32 ecc_ctrl = readl(&emif->emif_ecc_ctrl_reg); + u32 val1, val2, val3; + + debug("Disabling D-Cache before ECC test\n"); + dcache_disable(); + invalidate_dcache_all(); + + puts("Testing DDR ECC:\n"); + puts("\tECC test: Disabling DDR ECC ...\n"); + writel(0, &emif->emif_ecc_ctrl_reg); + + val1 = readl(addr); + val2 = val1 ^ ecc_err; + writel(val2, addr); + + val3 = readl(addr); +#ifdef CONFIG_ARCH_KEYSTONE + ecc_ctrl = ECC_START_ADDR1 | (ECC_END_ADDR1 << 16); + writel(ecc_ctrl, EMIF1_BASE + KS2_DDR3_ECC_ADDR_RANGE1_OFFSET); + ddr3_enable_ecc(EMIF1_BASE, 1); +#else + writel(ecc_ctrl, &emif->emif_ecc_ctrl_reg); +#endif + + printf("\tECC test: addr 0x%x, read data 0x%x, written data 0x%x, err pattern: 0x%x, read after write data 0x%x\n", + addr, val1, val2, ecc_err, val3); + + puts("\tECC test: Enabled DDR ECC ...\n"); + + val1 = readl(addr); + printf("\tECC test: addr 0x%x, read data 0x%x\n", addr, val1); + + ddr_check_ecc_status(); + + debug("Enabling D-cache back after ECC test\n"); + enable_caches(); + + return 0; +} + +static int is_addr_valid(u32 addr) +{ + struct emif_reg_struct *emif = (struct emif_reg_struct *)EMIF1_BASE; + u32 start_addr, end_addr, range, ecc_ctrl; + +#ifdef CONFIG_ARCH_KEYSTONE + ecc_ctrl = EMIF_ECC_REG_ECC_ADDR_RGN_1_EN_MASK; + range = ECC_START_ADDR1 | (ECC_END_ADDR1 << 16); +#else + ecc_ctrl = readl(&emif->emif_ecc_ctrl_reg); + range = readl(&emif->emif_ecc_address_range_1); +#endif + + /* Check in ecc address range 1 */ + if (ecc_ctrl & EMIF_ECC_REG_ECC_ADDR_RGN_1_EN_MASK) { + start_addr = ((range & EMIF_ECC_REG_ECC_START_ADDR_MASK) << 16) + + CFG_SYS_SDRAM_BASE; + end_addr = (range & EMIF_ECC_REG_ECC_END_ADDR_MASK) + 0xFFFF + + CFG_SYS_SDRAM_BASE; + if ((addr >= start_addr) && (addr <= end_addr)) + /* addr within ecc address range 1 */ + return 1; + } + + /* Check in ecc address range 2 */ + if (ecc_ctrl & EMIF_ECC_REG_ECC_ADDR_RGN_2_EN_MASK) { + range = readl(&emif->emif_ecc_address_range_2); + start_addr = ((range & EMIF_ECC_REG_ECC_START_ADDR_MASK) << 16) + + CFG_SYS_SDRAM_BASE; + end_addr = (range & EMIF_ECC_REG_ECC_END_ADDR_MASK) + 0xFFFF + + CFG_SYS_SDRAM_BASE; + if ((addr >= start_addr) && (addr <= end_addr)) + /* addr within ecc address range 2 */ + return 1; + } + + return 0; +} + +static int is_ecc_enabled(void) +{ + struct emif_reg_struct *emif = (struct emif_reg_struct *)EMIF1_BASE; + u32 ecc_ctrl = readl(&emif->emif_ecc_ctrl_reg); + + return (ecc_ctrl & EMIF_ECC_CTRL_REG_ECC_EN_MASK) && + (ecc_ctrl & EMIF_ECC_REG_RMW_EN_MASK); +} + +static int do_ddr_test(struct cmd_tbl *cmdtp, + int flag, int argc, char *const argv[]) +{ + u32 start_addr, end_addr, size, ecc_err; + + if ((argc == 4) && (strncmp(argv[1], "ecc_err", 8) == 0)) { + if (!is_ecc_enabled()) { + puts("ECC not enabled. Please Enable ECC and try again\n"); + return CMD_RET_FAILURE; + } + + start_addr = hextoul(argv[2], NULL); + ecc_err = hextoul(argv[3], NULL); + + if (!is_addr_valid(start_addr)) { + puts("Invalid address. Please enter ECC supported address!\n"); + return CMD_RET_FAILURE; + } + + ddr_memory_ecc_err(start_addr, ecc_err); + return 0; + } + + if (!(((argc == 4) && (strncmp(argv[1], "test", 5) == 0)) || + ((argc == 5) && (strncmp(argv[1], "compare", 8) == 0)))) + return cmd_usage(cmdtp); + + start_addr = hextoul(argv[2], NULL); + end_addr = hextoul(argv[3], NULL); + + if ((start_addr < CFG_SYS_SDRAM_BASE) || + (start_addr > (CFG_SYS_SDRAM_BASE + + get_effective_memsize() - 1)) || + (end_addr < CFG_SYS_SDRAM_BASE) || + (end_addr > (CFG_SYS_SDRAM_BASE + + get_effective_memsize() - 1)) || (start_addr >= end_addr)) { + puts("Invalid start or end address!\n"); + return cmd_usage(cmdtp); + } + + puts("Please wait ...\n"); + if (argc == 5) { + size = hextoul(argv[4], NULL); + ddr_memory_compare(start_addr, end_addr, size); + } else { + ddr_memory_test(start_addr, end_addr, 0); + } + + return 0; +} + +U_BOOT_CMD(ddr, 5, 1, do_ddr_test, + "DDR3 test", + "test <start_addr in hex> <end_addr in hex> - test DDR from start\n" + " address to end address\n" + "ddr compare <start_addr in hex> <end_addr in hex> <size in hex> -\n" + " compare DDR data of (size) bytes from start address to end\n" + " address\n" + "ddr ecc_err <addr in hex> <bit_err in hex> - generate bit errors\n" + " in DDR data at <addr>, the command will read a 32-bit data\n" + " from <addr>, and write (data ^ bit_err) back to <addr>\n" +); diff --git a/cmd/ti/pd.c b/cmd/ti/pd.c new file mode 100644 index 00000000000..305023af1e7 --- /dev/null +++ b/cmd/ti/pd.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Power Domain test commands + * + * Copyright (C) 2020 Texas Instruments Incorporated, <www.ti.com> + */ + +#include <command.h> +#include <dm.h> +#include <k3-dev.h> + +static const struct udevice_id ti_pd_of_match[] = { + { .compatible = "ti,sci-pm-domain" }, + { /* sentinel */ } +}; + +static struct ti_k3_pd_platdata *ti_pd_find_data(void) +{ + struct udevice *dev; + int i = 0; + + while (1) { + uclass_get_device(UCLASS_POWER_DOMAIN, i++, &dev); + if (!dev) + return NULL; + + if (device_is_compatible(dev, + ti_pd_of_match[0].compatible)) + return dev_get_priv(dev); + } + + return NULL; +} + +static void dump_lpsc(struct ti_k3_pd_platdata *data, struct ti_pd *pd) +{ + int i; + struct ti_lpsc *lpsc; + u8 state; + static const char * const lpsc_states[] = { + "swrstdis", "syncrst", "disable", "enable", "autosleep", + "autowake", "unknown", + }; + + for (i = 0; i < data->num_lpsc; i++) { + lpsc = &data->lpsc[i]; + if (lpsc->pd != pd) + continue; + state = lpsc_get_state(lpsc); + if (state > ARRAY_SIZE(lpsc_states)) + state = ARRAY_SIZE(lpsc_states) - 1; + printf(" LPSC%d: state=%s, usecount=%d\n", + lpsc->id, lpsc_states[state], lpsc->usecount); + } +} + +static void dump_pd(struct ti_k3_pd_platdata *data, struct ti_psc *psc) +{ + int i; + struct ti_pd *pd; + u8 state; + static const char * const pd_states[] = { + "off", "on", "unknown" + }; + + for (i = 0; i < data->num_pd; i++) { + pd = &data->pd[i]; + if (pd->psc != psc) + continue; + state = ti_pd_state(pd); + if (state > ARRAY_SIZE(pd_states)) + state = ARRAY_SIZE(pd_states) - 1; + printf(" PD%d: state=%s, usecount=%d:\n", + pd->id, pd_states[state], pd->usecount); + dump_lpsc(data, pd); + } +} + +static void dump_psc(struct ti_k3_pd_platdata *data) +{ + int i; + struct ti_psc *psc; + + for (i = 0; i < data->num_psc; i++) { + psc = &data->psc[i]; + printf("PSC%d [%p]:\n", psc->id, psc->base); + dump_pd(data, psc); + } +} + +static int do_pd_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct ti_k3_pd_platdata *data; + + data = ti_pd_find_data(); + if (!data) + return CMD_RET_FAILURE; + + dump_psc(data); + + return 0; +} + +static int do_pd_endis(int argc, char *const argv[], u8 state) +{ + u32 psc_id; + u32 lpsc_id; + int i; + struct ti_k3_pd_platdata *data; + struct ti_lpsc *lpsc; + int ret; + + if (argc < 3) + return CMD_RET_FAILURE; + + data = ti_pd_find_data(); + if (!data) + return CMD_RET_FAILURE; + + psc_id = dectoul(argv[1], NULL); + lpsc_id = dectoul(argv[2], NULL); + + for (i = 0; i < data->num_lpsc; i++) { + lpsc = &data->lpsc[i]; + if (lpsc->pd->psc->id != psc_id) + continue; + if (lpsc->id != lpsc_id) + continue; + printf("%s pd [PSC:%d,LPSC:%d]...\n", + state == MDSTAT_STATE_ENABLE ? "Enabling" : "Disabling", + psc_id, lpsc_id); + ret = ti_lpsc_transition(lpsc, state); + if (ret) + return CMD_RET_FAILURE; + else + return 0; + } + + printf("No matching psc/lpsc found.\n"); + + return CMD_RET_FAILURE; +} + +static int do_pd_enable(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_pd_endis(argc, argv, MDSTAT_STATE_ENABLE); +} + +static int do_pd_disable(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return do_pd_endis(argc, argv, MDSTAT_STATE_SWRSTDISABLE); +} + +static struct cmd_tbl cmd_pd[] = { + U_BOOT_CMD_MKENT(dump, 1, 0, do_pd_dump, "", ""), + U_BOOT_CMD_MKENT(enable, 3, 0, do_pd_enable, "", ""), + U_BOOT_CMD_MKENT(disable, 3, 0, do_pd_disable, "", ""), +}; + +static int ti_do_pd(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + struct cmd_tbl *c; + + argc--; + argv++; + + c = find_cmd_tbl(argv[0], cmd_pd, ARRAY_SIZE(cmd_pd)); + if (c) + return c->cmd(cmdtp, flag, argc, argv); + else + return CMD_RET_USAGE; +} + +U_BOOT_LONGHELP(pd, + "dump - show power domain status\n" + "enable [psc] [lpsc] - enable power domain\n" + "disable [psc] [lpsc] - disable power domain\n"); + +U_BOOT_CMD(pd, 4, 1, ti_do_pd, + "TI power domain control", pd_help_text +); diff --git a/cmd/time.c b/cmd/time.c new file mode 100644 index 00000000000..eee6084e968 --- /dev/null +++ b/cmd/time.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. + */ + +#include <command.h> + +static void report_time(ulong cycles) +{ + ulong minutes, seconds, milliseconds; + ulong total_seconds, remainder; + + total_seconds = cycles / CONFIG_SYS_HZ; + remainder = cycles % CONFIG_SYS_HZ; + minutes = total_seconds / 60; + seconds = total_seconds % 60; + /* approximate millisecond value */ + milliseconds = (remainder * 1000 + CONFIG_SYS_HZ / 2) / CONFIG_SYS_HZ; + + printf("\ntime:"); + if (minutes) + printf(" %lu minutes,", minutes); + printf(" %lu.%03lu seconds\n", seconds, milliseconds); +} + +static int do_time(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong cycles = 0; + int retval = 0; + int repeatable = 0; + + if (argc == 1) + return CMD_RET_USAGE; + + retval = cmd_process(0, argc - 1, argv + 1, &repeatable, &cycles); + report_time(cycles); + + return retval; +} + +U_BOOT_CMD(time, CONFIG_SYS_MAXARGS, 0, do_time, + "run commands and summarize execution time", + "command [args...]\n"); diff --git a/cmd/timer.c b/cmd/timer.c new file mode 100644 index 00000000000..427309e108d --- /dev/null +++ b/cmd/timer.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2001 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <time.h> +#include <linux/string.h> + +static int do_timer(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + static ulong start; + + if (argc != 2) + return CMD_RET_USAGE; + + if (!strcmp(argv[1], "start")) + start = get_timer(0); + + if (!strcmp(argv[1], "get")) { + ulong msecs = get_timer(start) * 1000 / CONFIG_SYS_HZ; + printf("%ld.%03d\n", msecs / 1000, (int)(msecs % 1000)); + } + + return 0; +} + +U_BOOT_CMD( + timer, 2, 1, do_timer, + "access the system timer", + "start - Reset the timer reference.\n" + "timer get - Print the time since 'start'." +); diff --git a/cmd/tlv_eeprom.c b/cmd/tlv_eeprom.c new file mode 100644 index 00000000000..3127660dd9e --- /dev/null +++ b/cmd/tlv_eeprom.c @@ -0,0 +1,1111 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * See file CREDITS for list of people who contributed to this + * project. + * + * Copyright (C) 2013 Curt Brune <curt@cumulusnetworks.com> + * Copyright (C) 2014 Srideep <srideep_devireddy@dell.com> + * Copyright (C) 2013 Miles Tseng <miles_tseng@accton.com> + * Copyright (C) 2014,2016 david_yang <david_yang@accton.com> + */ + +#include <command.h> +#include <dm.h> +#include <i2c.h> +#include <i2c_eeprom.h> +#include <env.h> +#include <init.h> +#include <net.h> +#include <asm/global_data.h> +#include <linux/ctype.h> +#include <u-boot/crc.h> + +#include "tlv_eeprom.h" + +DECLARE_GLOBAL_DATA_PTR; + +#define MAX_TLV_DEVICES 2 + +/* File scope function prototypes */ +static bool is_checksum_valid(u8 *eeprom); +static int read_eeprom(int devnum, u8 *eeprom); +static void show_eeprom(int devnum, u8 *eeprom); +static void decode_tlv(struct tlvinfo_tlv *tlv); +static void update_crc(u8 *eeprom); +static int prog_eeprom(int devnum, u8 *eeprom); +static bool tlvinfo_find_tlv(u8 *eeprom, u8 tcode, int *eeprom_index); +static bool tlvinfo_delete_tlv(u8 *eeprom, u8 code); +static bool tlvinfo_add_tlv(u8 *eeprom, int tcode, char *strval); +static int set_mac(char *buf, const char *string); +static int set_date(char *buf, const char *string); +static int set_bytes(char *buf, const char *string, int *converted_accum); +static void show_tlv_devices(int current_dev); + +/* The EEPROM contents after being read into memory */ +static u8 eeprom[TLV_INFO_MAX_LEN]; + +static struct udevice *tlv_devices[MAX_TLV_DEVICES]; + +#define to_header(p) ((struct tlvinfo_header *)p) +#define to_entry(p) ((struct tlvinfo_tlv *)p) + +#define HDR_SIZE sizeof(struct tlvinfo_header) +#define ENT_SIZE sizeof(struct tlvinfo_tlv) + +static inline bool is_digit(char c) +{ + return (c >= '0' && c <= '9'); +} + +/** + * is_valid_tlv + * + * Perform basic sanity checks on a TLV field. The TLV is pointed to + * by the parameter provided. + * 1. The type code is not reserved (0x00 or 0xFF) + */ +static inline bool is_valid_tlv(struct tlvinfo_tlv *tlv) +{ + return((tlv->type != 0x00) && (tlv->type != 0xFF)); +} + +/** + * is_hex + * + * Tests if character is an ASCII hex digit + */ +static inline u8 is_hex(char p) +{ + return (((p >= '0') && (p <= '9')) || + ((p >= 'A') && (p <= 'F')) || + ((p >= 'a') && (p <= 'f'))); +} + +/** + * is_checksum_valid + * + * Validate the checksum in the provided TlvInfo EEPROM data. First, + * verify that the TlvInfo header is valid, then make sure the last + * TLV is a CRC-32 TLV. Then calculate the CRC over the EEPROM data + * and compare it to the value stored in the EEPROM CRC-32 TLV. + */ +static bool is_checksum_valid(u8 *eeprom) +{ + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + struct tlvinfo_tlv *eeprom_crc; + unsigned int calc_crc; + unsigned int stored_crc; + + // Is the eeprom header valid? + if (!is_valid_tlvinfo_header(eeprom_hdr)) + return false; + + // Is the last TLV a CRC? + eeprom_crc = to_entry(&eeprom[HDR_SIZE + + be16_to_cpu(eeprom_hdr->totallen) - (ENT_SIZE + 4)]); + if (eeprom_crc->type != TLV_CODE_CRC_32 || eeprom_crc->length != 4) + return false; + + // Calculate the checksum + calc_crc = crc32(0, (void *)eeprom, + HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen) - 4); + stored_crc = (eeprom_crc->value[0] << 24) | + (eeprom_crc->value[1] << 16) | + (eeprom_crc->value[2] << 8) | + eeprom_crc->value[3]; + return calc_crc == stored_crc; +} + +/** + * read_eeprom + * + * Read the EEPROM into memory, if it hasn't already been read. + */ +static int read_eeprom(int devnum, u8 *eeprom) +{ + int ret; + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + struct tlvinfo_tlv *eeprom_tlv = to_entry(&eeprom[HDR_SIZE]); + + /* Read the header */ + ret = read_tlv_eeprom((void *)eeprom_hdr, 0, HDR_SIZE, devnum); + /* If the header was successfully read, read the TLVs */ + if (ret == 0 && is_valid_tlvinfo_header(eeprom_hdr)) + ret = read_tlv_eeprom((void *)eeprom_tlv, HDR_SIZE, + be16_to_cpu(eeprom_hdr->totallen), devnum); + else if (ret == -ENODEV) + return ret; + + // If the contents are invalid, start over with default contents + if (!is_valid_tlvinfo_header(eeprom_hdr) || + !is_checksum_valid(eeprom)) { + strcpy(eeprom_hdr->signature, TLV_INFO_ID_STRING); + eeprom_hdr->version = TLV_INFO_VERSION; + eeprom_hdr->totallen = cpu_to_be16(0); + update_crc(eeprom); + } + +#ifdef DEBUG + show_eeprom(devnum, eeprom); +#endif + + return ret; +} + +/** + * show_eeprom + * + * Display the contents of the EEPROM + */ +static void show_eeprom(int devnum, u8 *eeprom) +{ + int tlv_end; + int curr_tlv; +#ifdef DEBUG + int i; +#endif + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + struct tlvinfo_tlv *eeprom_tlv; + + if (!is_valid_tlvinfo_header(eeprom_hdr)) { + printf("EEPROM does not contain data in a valid TlvInfo format.\n"); + return; + } + + printf("TLV: %u\n", devnum); + printf("TlvInfo Header:\n"); + printf(" Id String: %s\n", eeprom_hdr->signature); + printf(" Version: %d\n", eeprom_hdr->version); + printf(" Total Length: %d\n", be16_to_cpu(eeprom_hdr->totallen)); + + printf("TLV Name Code Len Value\n"); + printf("-------------------- ---- --- -----\n"); + curr_tlv = HDR_SIZE; + tlv_end = HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen); + while (curr_tlv < tlv_end) { + eeprom_tlv = to_entry(&eeprom[curr_tlv]); + if (!is_valid_tlv(eeprom_tlv)) { + printf("Invalid TLV field starting at EEPROM offset %d\n", + curr_tlv); + return; + } + decode_tlv(eeprom_tlv); + curr_tlv += ENT_SIZE + eeprom_tlv->length; + } + + printf("Checksum is %s.\n", + is_checksum_valid(eeprom) ? "valid" : "invalid"); + +#ifdef DEBUG + printf("EEPROM dump: (0x%x bytes)", TLV_INFO_MAX_LEN); + for (i = 0; i < TLV_INFO_MAX_LEN; i++) { + if ((i % 16) == 0) + printf("\n%02X: ", i); + printf("%02X ", eeprom[i]); + } + printf("\n"); +#endif +} + +/** + * Struct for displaying the TLV codes and names. + */ +struct tlv_code_desc { + u8 m_code; + char *m_name; +}; + +/** + * List of TLV codes and names. + */ +static struct tlv_code_desc tlv_code_list[] = { + { TLV_CODE_PRODUCT_NAME, "Product Name"}, + { TLV_CODE_PART_NUMBER, "Part Number"}, + { TLV_CODE_SERIAL_NUMBER, "Serial Number"}, + { TLV_CODE_MAC_BASE, "Base MAC Address"}, + { TLV_CODE_MANUF_DATE, "Manufacture Date"}, + { TLV_CODE_DEVICE_VERSION, "Device Version"}, + { TLV_CODE_LABEL_REVISION, "Label Revision"}, + { TLV_CODE_PLATFORM_NAME, "Platform Name"}, + { TLV_CODE_ONIE_VERSION, "ONIE Version"}, + { TLV_CODE_MAC_SIZE, "MAC Addresses"}, + { TLV_CODE_MANUF_NAME, "Manufacturer"}, + { TLV_CODE_MANUF_COUNTRY, "Country Code"}, + { TLV_CODE_VENDOR_NAME, "Vendor Name"}, + { TLV_CODE_DIAG_VERSION, "Diag Version"}, + { TLV_CODE_SERVICE_TAG, "Service Tag"}, + { TLV_CODE_VENDOR_EXT, "Vendor Extension"}, + { TLV_CODE_CRC_32, "CRC-32"}, +}; + +/** + * Look up a TLV name by its type. + */ +static inline const char *tlv_type2name(u8 type) +{ + char *name = "Unknown"; + int i; + + for (i = 0; i < ARRAY_SIZE(tlv_code_list); i++) { + if (tlv_code_list[i].m_code == type) { + name = tlv_code_list[i].m_name; + break; + } + } + + return name; +} + +/* + * decode_tlv + * + * Print a string representing the contents of the TLV field. The format of + * the string is: + * 1. The name of the field left justified in 20 characters + * 2. The type code in hex right justified in 5 characters + * 3. The length in decimal right justified in 4 characters + * 4. The value, left justified in however many characters it takes + * The validity of EEPROM contents and the TLV field have been verified + * prior to calling this function. + */ +#define DECODE_NAME_MAX 20 + +/* + * The max decode value is currently for the 'raw' type or the 'vendor + * extension' type, both of which have the same decode format. The + * max decode string size is computed as follows: + * + * strlen(" 0xFF") * TLV_VALUE_MAX_LEN + 1 + * + */ +#define DECODE_VALUE_MAX ((5 * TLV_VALUE_MAX_LEN) + 1) + +static void decode_tlv(struct tlvinfo_tlv *tlv) +{ + char name[DECODE_NAME_MAX]; + char value[DECODE_VALUE_MAX]; + int i; + + strncpy(name, tlv_type2name(tlv->type), DECODE_NAME_MAX); + + switch (tlv->type) { + case TLV_CODE_PRODUCT_NAME: + case TLV_CODE_PART_NUMBER: + case TLV_CODE_SERIAL_NUMBER: + case TLV_CODE_MANUF_DATE: + case TLV_CODE_LABEL_REVISION: + case TLV_CODE_PLATFORM_NAME: + case TLV_CODE_ONIE_VERSION: + case TLV_CODE_MANUF_NAME: + case TLV_CODE_MANUF_COUNTRY: + case TLV_CODE_VENDOR_NAME: + case TLV_CODE_DIAG_VERSION: + case TLV_CODE_SERVICE_TAG: + memcpy(value, tlv->value, tlv->length); + value[tlv->length] = 0; + break; + case TLV_CODE_MAC_BASE: + sprintf(value, "%02X:%02X:%02X:%02X:%02X:%02X", + tlv->value[0], tlv->value[1], tlv->value[2], + tlv->value[3], tlv->value[4], tlv->value[5]); + break; + case TLV_CODE_DEVICE_VERSION: + sprintf(value, "%u", tlv->value[0]); + break; + case TLV_CODE_MAC_SIZE: + sprintf(value, "%u", (tlv->value[0] << 8) | tlv->value[1]); + break; + case TLV_CODE_VENDOR_EXT: + value[0] = 0; + for (i = 0; (i < (DECODE_VALUE_MAX / 5)) && (i < tlv->length); + i++) { + sprintf(value, "%s 0x%02X", value, tlv->value[i]); + } + break; + case TLV_CODE_CRC_32: + sprintf(value, "0x%02X%02X%02X%02X", + tlv->value[0], tlv->value[1], + tlv->value[2], tlv->value[3]); + break; + default: + value[0] = 0; + for (i = 0; (i < (DECODE_VALUE_MAX / 5)) && (i < tlv->length); + i++) { + sprintf(value, "%s 0x%02X", value, tlv->value[i]); + } + break; + } + + name[DECODE_NAME_MAX - 1] = 0; + printf("%-20s 0x%02X %3d %s\n", name, tlv->type, tlv->length, value); +} + +/** + * update_crc + * + * This function updates the CRC-32 TLV. If there is no CRC-32 TLV, then + * one is added. This function should be called after each update to the + * EEPROM structure, to make sure the CRC is always correct. + */ +static void update_crc(u8 *eeprom) +{ + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + struct tlvinfo_tlv *eeprom_crc; + unsigned int calc_crc; + int eeprom_index; + + // Discover the CRC TLV + if (!tlvinfo_find_tlv(eeprom, TLV_CODE_CRC_32, &eeprom_index)) { + unsigned int totallen = be16_to_cpu(eeprom_hdr->totallen); + + if ((totallen + ENT_SIZE + 4) > TLV_TOTAL_LEN_MAX) + return; + eeprom_index = HDR_SIZE + totallen; + eeprom_hdr->totallen = cpu_to_be16(totallen + ENT_SIZE + 4); + } + eeprom_crc = to_entry(&eeprom[eeprom_index]); + eeprom_crc->type = TLV_CODE_CRC_32; + eeprom_crc->length = 4; + + // Calculate the checksum + calc_crc = crc32(0, (void *)eeprom, + HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen) - 4); + eeprom_crc->value[0] = (calc_crc >> 24) & 0xFF; + eeprom_crc->value[1] = (calc_crc >> 16) & 0xFF; + eeprom_crc->value[2] = (calc_crc >> 8) & 0xFF; + eeprom_crc->value[3] = (calc_crc >> 0) & 0xFF; +} + +/** + * prog_eeprom + * + * Write the EEPROM data from CPU memory to the hardware. + */ +static int prog_eeprom(int devnum, u8 *eeprom) +{ + int ret = 0; + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + int eeprom_len; + + update_crc(eeprom); + + eeprom_len = HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen); + ret = write_tlv_eeprom(eeprom, eeprom_len, devnum); + if (ret) { + printf("Programming failed.\n"); + return -1; + } + + printf("Programming passed.\n"); + return 0; +} + +/** + * show_tlv_code_list - Display the list of TLV codes and names + */ +void show_tlv_code_list(void) +{ + int i; + + printf("TLV Code TLV Name\n"); + printf("======== =================\n"); + for (i = 0; i < ARRAY_SIZE(tlv_code_list); i++) { + printf("0x%02X %s\n", + tlv_code_list[i].m_code, + tlv_code_list[i].m_name); + } +} + +/** + * do_tlv_eeprom + * + * This function implements the tlv_eeprom command. + */ +int do_tlv_eeprom(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + char cmd; + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + static unsigned int current_dev; + /* Set to 1 if we've read EEPROM into memory */ + static int has_been_read; + int ret; + + // If no arguments, read the EEPROM and display its contents + if (argc == 1) { + if (!has_been_read) { + ret = read_eeprom(current_dev, eeprom); + if (ret) { + printf("Failed to read EEPROM data from device.\n"); + return 0; + } + + has_been_read = 1; + } + show_eeprom(current_dev, eeprom); + return 0; + } + + // We only look at the first character to the command, so "read" and + // "reset" will both be treated as "read". + cmd = argv[1][0]; + + // select device + if (cmd == 'd') { + /* 'dev' command */ + unsigned int devnum; + + devnum = simple_strtoul(argv[2], NULL, 0); + if (devnum >= MAX_TLV_DEVICES) { + printf("Invalid device number\n"); + return 0; + } + current_dev = devnum; + has_been_read = 0; + + return 0; + } + + // Read the EEPROM contents + if (cmd == 'r') { + has_been_read = 0; + ret = read_eeprom(current_dev, eeprom); + if (ret) { + printf("Failed to read EEPROM data from device.\n"); + return 0; + } + + printf("EEPROM data loaded from device to memory.\n"); + has_been_read = 1; + return 0; + } + + // Subsequent commands require that the EEPROM has already been read. + if (!has_been_read) { + printf("Please read the EEPROM data first, using the 'tlv_eeprom read' command.\n"); + return 0; + } + + // Handle the commands that don't take parameters + if (argc == 2) { + switch (cmd) { + case 'w': /* write */ + prog_eeprom(current_dev, eeprom); + break; + case 'e': /* erase */ + strcpy(eeprom_hdr->signature, TLV_INFO_ID_STRING); + eeprom_hdr->version = TLV_INFO_VERSION; + eeprom_hdr->totallen = cpu_to_be16(0); + update_crc(eeprom); + printf("EEPROM data in memory reset.\n"); + break; + case 'l': /* list */ + show_tlv_code_list(); + break; + case 'd': /* dev */ + show_tlv_devices(current_dev); + break; + default: + return CMD_RET_USAGE; + } + return 0; + } + + // The set command takes one or two args. + if (argc > 4) + return CMD_RET_USAGE; + + // Set command. If the TLV exists in the EEPROM, delete it. Then if + // data was supplied for this TLV add the TLV with the new contents at + // the end. + if (cmd == 's') { + int tcode; + + tcode = simple_strtoul(argv[2], NULL, 0); + tlvinfo_delete_tlv(eeprom, tcode); + if (argc == 4) + tlvinfo_add_tlv(eeprom, tcode, argv[3]); + } else { + return CMD_RET_USAGE; + } + + return 0; +} + +/** + * This macro defines the tlv_eeprom command line command. + */ +U_BOOT_CMD(tlv_eeprom, 4, 1, do_tlv_eeprom, + "Display and program the system EEPROM data block.", + "[read|write|set <type_code> <string_value>|erase|list]\n" + "tlv_eeprom\n" + " - With no arguments display the current contents.\n" + "tlv_eeprom dev [dev]\n" + " - List devices or set current EEPROM device.\n" + "tlv_eeprom read\n" + " - Load EEPROM data from device to memory.\n" + "tlv_eeprom write\n" + " - Write the EEPROM data to persistent storage.\n" + "tlv_eeprom set <type_code> <string_value>\n" + " - Set a field to a value.\n" + " - If no string_value, field is deleted.\n" + " - Use 'tlv_eeprom write' to make changes permanent.\n" + "tlv_eeprom erase\n" + " - Reset the in memory EEPROM data.\n" + " - Use 'tlv_eeprom read' to refresh the in memory EEPROM data.\n" + " - Use 'tlv_eeprom write' to make changes permanent.\n" + "tlv_eeprom list\n" + " - List the understood TLV codes and names.\n" + ); + +/** + * tlvinfo_find_tlv + * + * This function finds the TLV with the supplied code in the EEPROM. + * An offset from the beginning of the EEPROM is returned in the + * eeprom_index parameter if the TLV is found. + */ +static bool tlvinfo_find_tlv(u8 *eeprom, u8 tcode, int *eeprom_index) +{ + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + struct tlvinfo_tlv *eeprom_tlv; + int eeprom_end; + + // Search through the TLVs, looking for the first one which matches the + // supplied type code. + *eeprom_index = HDR_SIZE; + eeprom_end = HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen); + while (*eeprom_index < eeprom_end) { + eeprom_tlv = to_entry(&eeprom[*eeprom_index]); + if (!is_valid_tlv(eeprom_tlv)) + return false; + if (eeprom_tlv->type == tcode) + return true; + *eeprom_index += ENT_SIZE + eeprom_tlv->length; + } + return(false); +} + +/** + * tlvinfo_delete_tlv + * + * This function deletes the TLV with the specified type code from the + * EEPROM. + */ +static bool tlvinfo_delete_tlv(u8 *eeprom, u8 code) +{ + int eeprom_index; + int tlength; + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + struct tlvinfo_tlv *eeprom_tlv; + + // Find the TLV and then move all following TLVs "forward" + if (tlvinfo_find_tlv(eeprom, code, &eeprom_index)) { + eeprom_tlv = to_entry(&eeprom[eeprom_index]); + tlength = ENT_SIZE + eeprom_tlv->length; + memcpy(&eeprom[eeprom_index], &eeprom[eeprom_index + tlength], + HDR_SIZE + + be16_to_cpu(eeprom_hdr->totallen) - eeprom_index - + tlength); + eeprom_hdr->totallen = + cpu_to_be16(be16_to_cpu(eeprom_hdr->totallen) - + tlength); + update_crc(eeprom); + return true; + } + return false; +} + +/** + * tlvinfo_add_tlv + * + * This function adds a TLV to the EEPROM, converting the value (a string) to + * the format in which it will be stored in the EEPROM. + */ +#define MAX_TLV_VALUE_LEN 256 +static bool tlvinfo_add_tlv(u8 *eeprom, int tcode, char *strval) +{ + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + struct tlvinfo_tlv *eeprom_tlv; + int new_tlv_len = 0; + u32 value; + char data[MAX_TLV_VALUE_LEN]; + int eeprom_index; + + // Encode each TLV type into the format to be stored in the EEPROM + switch (tcode) { + case TLV_CODE_PRODUCT_NAME: + case TLV_CODE_PART_NUMBER: + case TLV_CODE_SERIAL_NUMBER: + case TLV_CODE_LABEL_REVISION: + case TLV_CODE_PLATFORM_NAME: + case TLV_CODE_ONIE_VERSION: + case TLV_CODE_MANUF_NAME: + case TLV_CODE_MANUF_COUNTRY: + case TLV_CODE_VENDOR_NAME: + case TLV_CODE_DIAG_VERSION: + case TLV_CODE_SERVICE_TAG: + strncpy(data, strval, MAX_TLV_VALUE_LEN); + new_tlv_len = min_t(size_t, MAX_TLV_VALUE_LEN, strlen(strval)); + break; + case TLV_CODE_DEVICE_VERSION: + value = simple_strtoul(strval, NULL, 0); + if (value >= 256) { + printf("ERROR: Device version must be 255 or less. Value supplied: %u", + value); + return false; + } + data[0] = value & 0xFF; + new_tlv_len = 1; + break; + case TLV_CODE_MAC_SIZE: + value = simple_strtoul(strval, NULL, 0); + if (value >= 65536) { + printf("ERROR: MAC Size must be 65535 or less. Value supplied: %u", + value); + return false; + } + data[0] = (value >> 8) & 0xFF; + data[1] = value & 0xFF; + new_tlv_len = 2; + break; + case TLV_CODE_MANUF_DATE: + if (set_date(data, strval) != 0) + return false; + new_tlv_len = 19; + break; + case TLV_CODE_MAC_BASE: + if (set_mac(data, strval) != 0) + return false; + new_tlv_len = 6; + break; + case TLV_CODE_CRC_32: + printf("WARNING: The CRC TLV is set automatically and cannot be set manually.\n"); + return false; + case TLV_CODE_VENDOR_EXT: + default: + if (set_bytes(data, strval, &new_tlv_len) != 0) + return false; + break; + } + + // Is there room for this TLV? + if ((be16_to_cpu(eeprom_hdr->totallen) + ENT_SIZE + new_tlv_len) > + TLV_TOTAL_LEN_MAX) { + printf("ERROR: There is not enough room in the EEPROM to save data.\n"); + return false; + } + + // Add TLV at the end, overwriting CRC TLV if it exists + if (tlvinfo_find_tlv(eeprom, TLV_CODE_CRC_32, &eeprom_index)) + eeprom_hdr->totallen = + cpu_to_be16(be16_to_cpu(eeprom_hdr->totallen) - + ENT_SIZE - 4); + else + eeprom_index = HDR_SIZE + be16_to_cpu(eeprom_hdr->totallen); + eeprom_tlv = to_entry(&eeprom[eeprom_index]); + eeprom_tlv->type = tcode; + eeprom_tlv->length = new_tlv_len; + memcpy(eeprom_tlv->value, data, new_tlv_len); + + // Update the total length and calculate (add) a new CRC-32 TLV + eeprom_hdr->totallen = cpu_to_be16(be16_to_cpu(eeprom_hdr->totallen) + + ENT_SIZE + new_tlv_len); + update_crc(eeprom); + + return true; +} + +/** + * set_mac + * + * Converts a string MAC address into a binary buffer. + * + * This function takes a pointer to a MAC address string + * (i.e."XX:XX:XX:XX:XX:XX", where "XX" is a two-digit hex number). + * The string format is verified and then converted to binary and + * stored in a buffer. + */ +static int set_mac(char *buf, const char *string) +{ + char *p = (char *)string; + int i; + int err = 0; + char *end; + + if (!p) { + printf("ERROR: NULL mac addr string passed in.\n"); + return -1; + } + + if (strlen(p) != 17) { + printf("ERROR: MAC address strlen() != 17 -- %zu\n", strlen(p)); + printf("ERROR: Bad MAC address format: %s\n", string); + return -1; + } + + for (i = 0; i < 17; i++) { + if ((i % 3) == 2) { + if (p[i] != ':') { + err++; + printf("ERROR: mac: p[%i] != :, found: `%c'\n", + i, p[i]); + break; + } + continue; + } else if (!is_hex(p[i])) { + err++; + printf("ERROR: mac: p[%i] != hex digit, found: `%c'\n", + i, p[i]); + break; + } + } + + if (err != 0) { + printf("ERROR: Bad MAC address format: %s\n", string); + return -1; + } + + /* Convert string to binary */ + for (i = 0, p = (char *)string; i < 6; i++) { + buf[i] = p ? hextoul(p, &end) : 0; + if (p) + p = (*end) ? end + 1 : end; + } + + if (!is_valid_ethaddr((u8 *)buf)) { + printf("ERROR: MAC address must not be 00:00:00:00:00:00, a multicast address or FF:FF:FF:FF:FF:FF.\n"); + printf("ERROR: Bad MAC address format: %s\n", string); + return -1; + } + + return 0; +} + +/** + * set_date + * + * Validates the format of the data string + * + * This function takes a pointer to a date string (i.e. MM/DD/YYYY hh:mm:ss) + * and validates that the format is correct. If so the string is copied + * to the supplied buffer. + */ +static int set_date(char *buf, const char *string) +{ + int i; + + if (!string) { + printf("ERROR: NULL date string passed in.\n"); + return -1; + } + + if (strlen(string) != 19) { + printf("ERROR: Date strlen() != 19 -- %zu\n", strlen(string)); + printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", + string); + return -1; + } + + for (i = 0; string[i] != 0; i++) { + switch (i) { + case 2: + case 5: + if (string[i] != '/') { + printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", + string); + return -1; + } + break; + case 10: + if (string[i] != ' ') { + printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", + string); + return -1; + } + break; + case 13: + case 16: + if (string[i] != ':') { + printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", + string); + return -1; + } + break; + default: + if (!is_digit(string[i])) { + printf("ERROR: Bad date format (MM/DD/YYYY hh:mm:ss): %s\n", + string); + return -1; + } + break; + } + } + + strcpy(buf, string); + return 0; +} + +/** + * set_bytes + * + * Converts a space-separated string of decimal numbers into a + * buffer of bytes. + * + * This function takes a pointer to a space-separated string of decimal + * numbers (i.e. "128 0x55 0321") with "C" standard radix specifiers + * and converts them to an array of bytes. + */ +static int set_bytes(char *buf, const char *string, int *converted_accum) +{ + char *p = (char *)string; + int i; + uint byte; + + if (!p) { + printf("ERROR: NULL string passed in.\n"); + return -1; + } + + /* Convert string to bytes */ + for (i = 0, p = (char *)string; (i < TLV_VALUE_MAX_LEN) && (*p != 0); + i++) { + while ((*p == ' ') || (*p == '\t') || (*p == ',') || + (*p == ';')) { + p++; + } + if (*p != 0) { + if (!is_digit(*p)) { + printf("ERROR: Non-digit found in byte string: (%s)\n", + string); + return -1; + } + byte = simple_strtoul(p, &p, 0); + if (byte >= 256) { + printf("ERROR: The value specified is greater than 255: (%u) in string: %s\n", + byte, string); + return -1; + } + buf[i] = byte & 0xFF; + } + } + + if (i == TLV_VALUE_MAX_LEN && (*p != 0)) { + printf("ERROR: Trying to assign too many bytes (max: %d) in string: %s\n", + TLV_VALUE_MAX_LEN, string); + return -1; + } + + *converted_accum = i; + return 0; +} + +static void show_tlv_devices(int current_dev) +{ + unsigned int dev; + + for (dev = 0; dev < MAX_TLV_DEVICES; dev++) + if (tlv_devices[dev]) + printf("TLV: %u%s\n", dev, + (dev == current_dev) ? " (*)" : ""); +} + +static int find_tlv_devices(struct udevice **tlv_devices_p) +{ + int ret; + int count_dev = 0; + struct udevice *dev; + + for (ret = uclass_first_device_check(UCLASS_I2C_EEPROM, &dev); + dev; + ret = uclass_next_device_check(&dev)) { + if (ret == 0) + tlv_devices_p[count_dev++] = dev; + if (count_dev >= MAX_TLV_DEVICES) + break; + } + + return (count_dev == 0) ? -ENODEV : 0; +} + +static struct udevice *find_tlv_device_by_index(int dev_num) +{ + struct udevice *local_tlv_devices[MAX_TLV_DEVICES] = {}; + struct udevice **tlv_devices_p; + int ret; + + if (gd->flags & (GD_FLG_RELOC | GD_FLG_SPL_INIT)) { + /* Assume BSS is initialized; use static data */ + if (tlv_devices[dev_num]) + return tlv_devices[dev_num]; + tlv_devices_p = tlv_devices; + } else { + tlv_devices_p = local_tlv_devices; + } + + ret = find_tlv_devices(tlv_devices_p); + if (ret == 0 && tlv_devices_p[dev_num]) + return tlv_devices_p[dev_num]; + + return NULL; +} + +/** + * read_tlv_eeprom - read the hwinfo from i2c EEPROM + */ +int read_tlv_eeprom(void *eeprom, int offset, int len, int dev_num) +{ + struct udevice *dev; + + if (dev_num >= MAX_TLV_DEVICES) + return -EINVAL; + + dev = find_tlv_device_by_index(dev_num); + if (!dev) + return -ENODEV; + + return i2c_eeprom_read(dev, offset, eeprom, len); +} + +/** + * write_tlv_eeprom - write the hwinfo to i2c EEPROM + */ +int write_tlv_eeprom(void *eeprom, int len, int dev) +{ + if (!(gd->flags & GD_FLG_RELOC)) + return -ENODEV; + if (!tlv_devices[dev]) + return -ENODEV; + + return i2c_eeprom_write(tlv_devices[dev], 0, eeprom, len); +} + +int read_tlvinfo_tlv_eeprom(void *eeprom, struct tlvinfo_header **hdr, + struct tlvinfo_tlv **first_entry, int dev_num) +{ + int ret; + struct tlvinfo_header *tlv_hdr; + struct tlvinfo_tlv *tlv_ent; + + /* Read TLV header */ + ret = read_tlv_eeprom(eeprom, 0, HDR_SIZE, dev_num); + if (ret < 0) + return ret; + + tlv_hdr = eeprom; + if (!is_valid_tlvinfo_header(tlv_hdr)) + return -EINVAL; + + /* Read TLV entries */ + tlv_ent = to_entry(&tlv_hdr[1]); + ret = read_tlv_eeprom(tlv_ent, HDR_SIZE, + be16_to_cpu(tlv_hdr->totallen), dev_num); + if (ret < 0) + return ret; + if (!is_checksum_valid(eeprom)) + return -EINVAL; + + *hdr = tlv_hdr; + *first_entry = tlv_ent; + + return 0; +} + +/** + * mac_read_from_eeprom + * + * Read the MAC addresses from EEPROM + * + * This function reads the MAC addresses from EEPROM and sets the + * appropriate environment variables for each one read. + * + * The environment variables are only set if they haven't been set already. + * This ensures that any user-saved variables are never overwritten. + * + * This function must be called after relocation. + */ +int mac_read_from_eeprom(void) +{ + unsigned int i; + int eeprom_index; + struct tlvinfo_tlv *eeprom_tlv; + int maccount; + u8 macbase[6]; + struct tlvinfo_header *eeprom_hdr = to_header(eeprom); + int devnum = 0; // TODO: support multiple EEPROMs + + if (read_eeprom(devnum, eeprom)) { + log_err("EEPROM: read failed\n"); + return -1; + } + + maccount = 1; + if (tlvinfo_find_tlv(eeprom, TLV_CODE_MAC_SIZE, &eeprom_index)) { + eeprom_tlv = to_entry(&eeprom[eeprom_index]); + maccount = (eeprom_tlv->value[0] << 8) | eeprom_tlv->value[1]; + } + + memcpy(macbase, "\0\0\0\0\0\0", 6); + if (tlvinfo_find_tlv(eeprom, TLV_CODE_MAC_BASE, &eeprom_index)) { + eeprom_tlv = to_entry(&eeprom[eeprom_index]); + memcpy(macbase, eeprom_tlv->value, 6); + } + + for (i = 0; i < maccount; i++) { + if (is_valid_ethaddr(macbase)) { + char ethaddr[18]; + char enetvar[11]; + + sprintf(ethaddr, "%02X:%02X:%02X:%02X:%02X:%02X", + macbase[0], macbase[1], macbase[2], + macbase[3], macbase[4], macbase[5]); + sprintf(enetvar, i ? "eth%daddr" : "ethaddr", i); + /* Only initialize environment variables that are blank + * (i.e. have not yet been set) + */ + if (!env_get(enetvar)) + env_set(enetvar, ethaddr); + + macbase[5]++; + if (macbase[5] == 0) { + macbase[4]++; + if (macbase[4] == 0) { + macbase[3]++; + if (macbase[3] == 0) { + macbase[0] = 0; + macbase[1] = 0; + macbase[2] = 0; + } + } + } + } + } + + log_debug("EEPROM: %s v%u len=%u\n", eeprom_hdr->signature, eeprom_hdr->version, + be16_to_cpu(eeprom_hdr->totallen)); + + return 0; +} + +int serial_read_from_eeprom(int devnum) +{ + char serialstr[257]; + int eeprom_index; + struct tlvinfo_tlv *eeprom_tlv; + + if (env_get("serial#")) + return 0; + + if (read_eeprom(devnum, eeprom)) { + printf("Read failed.\n"); + return -1; + } + + if (tlvinfo_find_tlv(eeprom, TLV_CODE_SERIAL_NUMBER, &eeprom_index)) { + eeprom_tlv = to_entry(&eeprom[eeprom_index]); + memcpy(serialstr, eeprom_tlv->value, eeprom_tlv->length); + serialstr[eeprom_tlv->length] = 0; + env_set("serial#", serialstr); + } + + return 0; +} diff --git a/cmd/tpm-common.c b/cmd/tpm-common.c new file mode 100644 index 00000000000..1cd57f901b6 --- /dev/null +++ b/cmd/tpm-common.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2013 The Chromium OS Authors. + */ + +#include <command.h> +#include <dm.h> +#include <env.h> +#include <malloc.h> +#include <asm/unaligned.h> +#include <linux/string.h> +#include <tpm-common.h> +#include <tpm_api.h> +#include "tpm-user-utils.h" + +static struct udevice *tpm_dev; + +/** + * Print a byte string in hexdecimal format, 16-bytes per line. + * + * @param data byte string to be printed + * @param count number of bytes to be printed + */ +void print_byte_string(u8 *data, size_t count) +{ + int i, print_newline = 0; + + for (i = 0; i < count; i++) { + printf(" %02x", data[i]); + print_newline = (i % 16 == 15); + if (print_newline) + putc('\n'); + } + /* Avoid duplicated newline at the end */ + if (!print_newline) + putc('\n'); +} + +/** + * Convert a text string of hexdecimal values into a byte string. + * + * @param bytes text string of hexdecimal values with no space + * between them + * @param data output buffer for byte string. The caller has to make + * sure it is large enough for storing the output. If + * NULL is passed, a large enough buffer will be allocated, + * and the caller must free it. + * @param count_ptr output variable for the length of byte string + * Return: pointer to output buffer + */ +void *parse_byte_string(char *bytes, u8 *data, size_t *count_ptr) +{ + char byte[3]; + size_t count, length; + int i; + + if (!bytes) + return NULL; + length = strlen(bytes); + count = length / 2; + + if (!data) + data = malloc(count); + if (!data) + return NULL; + + byte[2] = '\0'; + for (i = 0; i < length; i += 2) { + byte[0] = bytes[i]; + byte[1] = bytes[i + 1]; + data[i / 2] = (u8)hextoul(byte, NULL); + } + + if (count_ptr) + *count_ptr = count; + + return data; +} + +/** + * report_return_code() - Report any error and return failure or success + * + * @param return_code TPM command return code + * Return: value of enum command_ret_t + */ +int report_return_code(int return_code) +{ + if (return_code) { + printf("Error: %d\n", return_code); + return CMD_RET_FAILURE; + } else { + return CMD_RET_SUCCESS; + } +} + +/** + * Return number of values defined by a type string. + * + * @param type_str type string + * Return: number of values of type string + */ +int type_string_get_num_values(const char *type_str) +{ + return strlen(type_str); +} + +/** + * Return total size of values defined by a type string. + * + * @param type_str type string + * Return: total size of values of type string, or 0 if type string + * contains illegal type character. + */ +size_t type_string_get_space_size(const char *type_str) +{ + size_t size; + + for (size = 0; *type_str; type_str++) { + switch (*type_str) { + case 'b': + size += 1; + break; + case 'w': + size += 2; + break; + case 'd': + size += 4; + break; + default: + return 0; + } + } + + return size; +} + +/** + * Allocate a buffer large enough to hold values defined by a type + * string. The caller has to free the buffer. + * + * @param type_str type string + * @param count pointer for storing size of buffer + * Return: pointer to buffer or NULL on error + */ +void *type_string_alloc(const char *type_str, u32 *count) +{ + void *data; + size_t size; + + size = type_string_get_space_size(type_str); + if (!size) + return NULL; + data = malloc(size); + if (data) + *count = size; + + return data; +} + +/** + * Pack values defined by a type string into a buffer. The buffer must have + * large enough space. + * + * @param type_str type string + * @param values text strings of values to be packed + * @param data output buffer of values + * Return: 0 on success, non-0 on error + */ +int type_string_pack(const char *type_str, char * const values[], + u8 *data) +{ + size_t offset; + u32 value; + + for (offset = 0; *type_str; type_str++, values++) { + value = simple_strtoul(values[0], NULL, 0); + switch (*type_str) { + case 'b': + data[offset] = value; + offset += 1; + break; + case 'w': + put_unaligned_be16(value, data + offset); + offset += 2; + break; + case 'd': + put_unaligned_be32(value, data + offset); + offset += 4; + break; + default: + return -1; + } + } + + return 0; +} + +/** + * Read values defined by a type string from a buffer, and write these values + * to environment variables. + * + * @param type_str type string + * @param data input buffer of values + * @param vars names of environment variables + * Return: 0 on success, non-0 on error + */ +int type_string_write_vars(const char *type_str, u8 *data, + char * const vars[]) +{ + size_t offset; + u32 value; + + for (offset = 0; *type_str; type_str++, vars++) { + switch (*type_str) { + case 'b': + value = data[offset]; + offset += 1; + break; + case 'w': + value = get_unaligned_be16(data + offset); + offset += 2; + break; + case 'd': + value = get_unaligned_be32(data + offset); + offset += 4; + break; + default: + return -1; + } + if (env_set_ulong(*vars, value)) + return -1; + } + + return 0; +} + +static int tpm_show_device(void) +{ + struct udevice *dev; + char buf[80]; + int n = 0, rc; + + for_each_tpm_device(dev) { + rc = tpm_get_desc(dev, buf, sizeof(buf)); + if (rc < 0) + printf("device %d: can't get info\n", n); + else + printf("device %d: %s\n", n, buf); + + n++; + }; + + return 0; +} + +static int tpm_set_device(unsigned long num) +{ + struct udevice *dev; + unsigned long n = 0; + int rc = CMD_RET_FAILURE; + + for_each_tpm_device(dev) { + if (n == num) { + rc = 0; + break; + } + + n++; + } + + if (!rc) + tpm_dev = dev; + + return rc; +} + +int get_tpm(struct udevice **devp) +{ + int rc; + + /* + * To keep a backward compatibility with previous code, + * if a tpm device is not explicitly set, we set the first one. + */ + if (!tpm_dev) { + rc = tpm_set_device(0); + if (rc) { + printf("Couldn't set TPM 0 (rc = %d)\n", rc); + return CMD_RET_FAILURE; + } + } + + if (devp) + *devp = tpm_dev; + + return 0; +} + +int do_tpm_device(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + unsigned long num; + int rc; + + if (argc == 2) { + num = dectoul(argv[1], NULL); + + rc = tpm_set_device(num); + if (rc) + printf("Couldn't set TPM %lu (rc = %d)\n", num, rc); + } else { + rc = tpm_show_device(); + } + + return rc; +} + +int do_tpm_info(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct udevice *dev; + char buf[80]; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + rc = tpm_get_desc(dev, buf, sizeof(buf)); + if (rc < 0) { + printf("Couldn't get TPM info (%d)\n", rc); + return CMD_RET_FAILURE; + } + printf("%s\n", buf); + + return 0; +} + +int do_tpm_report_state(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + char buf[80]; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + rc = tpm_report_state(dev, buf, sizeof(buf)); + if (rc < 0) { + printf("Couldn't get TPM state (%d)\n", rc); + return CMD_RET_FAILURE; + } + printf("%s\n", buf); + + return 0; +} + +int do_tpm_init(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct udevice *dev; + int rc; + + if (argc != 1) + return CMD_RET_USAGE; + rc = get_tpm(&dev); + if (rc) + return rc; + + return report_return_code(tpm_init(dev)); +} + +int do_tpm_autostart(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int rc; + + if (argc != 1) + return CMD_RET_USAGE; + rc = get_tpm(&dev); + if (rc) + return rc; + + return report_return_code(tpm_auto_start(dev)); +} + +int do_tpm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *tpm_commands, *cmd; + struct tpm_chip_priv *priv; + struct udevice *dev; + unsigned int size; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + ret = get_tpm(&dev); + if (ret) + return ret; + + priv = dev_get_uclass_priv(dev); + + /* Below getters return NULL if the desired stack is not built */ + switch (priv->version) { + case TPM_V1: + tpm_commands = get_tpm1_commands(&size); + break; + case TPM_V2: + tpm_commands = get_tpm2_commands(&size); + break; + default: + tpm_commands = NULL; + } + + if (!tpm_commands) + return CMD_RET_USAGE; + + cmd = find_cmd_tbl(argv[1], tpm_commands, size); + if (!cmd) + return CMD_RET_USAGE; + + return cmd->cmd(cmdtp, flag, argc - 1, argv + 1); +} diff --git a/cmd/tpm-user-utils.h b/cmd/tpm-user-utils.h new file mode 100644 index 00000000000..dfa11353e12 --- /dev/null +++ b/cmd/tpm-user-utils.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2013 The Chromium OS Authors. + * Coypright (c) 2013 Guntermann & Drunck GmbH + */ + +#ifndef __TPM_USER_UTILS_H +#define __TPM_USER_UTILS_H + +void print_byte_string(u8 *data, size_t count); +void *parse_byte_string(char *bytes, u8 *data, size_t *count_ptr); +int report_return_code(int return_code); +int type_string_get_num_values(const char *type_str); +size_t type_string_get_space_size(const char *type_str); +void *type_string_alloc(const char *type_str, u32 *count); +int type_string_pack(const char *type_str, char * const values[], u8 *data); +int type_string_write_vars(const char *type_str, u8 *data, char * const vars[]); +int get_tpm(struct udevice **devp); + +int do_tpm_device(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]); +int do_tpm_init(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); +int do_tpm_autostart(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); +int do_tpm_info(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); +int do_tpm_report_state(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]); +int do_tpm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); + +#endif /* __TPM_USER_UTILS_H */ diff --git a/cmd/tpm-v1.c b/cmd/tpm-v1.c new file mode 100644 index 00000000000..6e019d1c729 --- /dev/null +++ b/cmd/tpm-v1.c @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2013 The Chromium OS Authors. + */ + +#include <command.h> +#include <env.h> +#include <malloc.h> +#include <vsprintf.h> +#include <asm/unaligned.h> +#include <tpm-common.h> +#include <tpm-v1.h> +#include "tpm-user-utils.h" +#include <tpm_api.h> + +static int do_tpm_startup(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum tpm_startup_type mode; + struct udevice *dev; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + if (argc != 2) + return CMD_RET_USAGE; + if (!strcasecmp("TPM_ST_CLEAR", argv[1])) { + mode = TPM_ST_CLEAR; + } else if (!strcasecmp("TPM_ST_STATE", argv[1])) { + mode = TPM_ST_STATE; + } else if (!strcasecmp("TPM_ST_DEACTIVATED", argv[1])) { + mode = TPM_ST_DEACTIVATED; + } else { + printf("Couldn't recognize mode string: %s\n", argv[1]); + return CMD_RET_FAILURE; + } + + return report_return_code(tpm_startup(dev, mode)); +} + +static int do_tpm_nv_define_space(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 index, perm, size; + struct udevice *dev; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 4) + return CMD_RET_USAGE; + index = simple_strtoul(argv[1], NULL, 0); + perm = simple_strtoul(argv[2], NULL, 0); + size = simple_strtoul(argv[3], NULL, 0); + + return report_return_code(tpm1_nv_define_space(dev, index, perm, size)); +} + +static int do_tpm_nv_read_value(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 index, count, rc; + struct udevice *dev; + void *data; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 4) + return CMD_RET_USAGE; + index = simple_strtoul(argv[1], NULL, 0); + data = (void *)simple_strtoul(argv[2], NULL, 0); + count = simple_strtoul(argv[3], NULL, 0); + + rc = tpm_nv_read_value(dev, index, data, count); + if (!rc) { + puts("area content:\n"); + print_byte_string(data, count); + } + + return report_return_code(rc); +} + +static int do_tpm_nv_write_value(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + u32 index, rc; + size_t count; + void *data; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 3) + return CMD_RET_USAGE; + index = simple_strtoul(argv[1], NULL, 0); + data = parse_byte_string(argv[2], NULL, &count); + if (!data) { + printf("Couldn't parse byte string %s\n", argv[2]); + return CMD_RET_FAILURE; + } + + rc = tpm_nv_write_value(dev, index, data, count); + free(data); + + return report_return_code(rc); +} + +static int do_tpm_extend(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u8 in_digest[20], out_digest[20]; + struct udevice *dev; + u32 index, rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 3) + return CMD_RET_USAGE; + index = simple_strtoul(argv[1], NULL, 0); + if (!parse_byte_string(argv[2], in_digest, NULL)) { + printf("Couldn't parse byte string %s\n", argv[2]); + return CMD_RET_FAILURE; + } + + rc = tpm_pcr_extend(dev, index, in_digest, sizeof(in_digest), + out_digest, "cmd"); + if (!rc) { + puts("PCR value after execution of the command:\n"); + print_byte_string(out_digest, sizeof(out_digest)); + } + + return report_return_code(rc); +} + +static int do_tpm_pcr_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 index, count, rc; + struct udevice *dev; + void *data; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 4) + return CMD_RET_USAGE; + index = simple_strtoul(argv[1], NULL, 0); + data = (void *)simple_strtoul(argv[2], NULL, 0); + count = simple_strtoul(argv[3], NULL, 0); + + rc = tpm_pcr_read(dev, index, data, count); + if (!rc) { + puts("Named PCR content:\n"); + print_byte_string(data, count); + } + + return report_return_code(rc); +} + +static int do_tpm_tsc_physical_presence(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct udevice *dev; + u16 presence; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 2) + return CMD_RET_USAGE; + presence = (u16)simple_strtoul(argv[1], NULL, 0); + + return report_return_code(tpm_tsc_physical_presence(dev, presence)); +} + +static int do_tpm_read_pubek(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + u32 count, rc; + void *data; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 3) + return CMD_RET_USAGE; + data = (void *)simple_strtoul(argv[1], NULL, 0); + count = simple_strtoul(argv[2], NULL, 0); + + rc = tpm_read_pubek(dev, data, count); + if (!rc) { + puts("pubek value:\n"); + print_byte_string(data, count); + } + + return report_return_code(rc); +} + +static int do_tpm_physical_set_deactivated(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct udevice *dev; + u8 state; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 2) + return CMD_RET_USAGE; + state = (u8)simple_strtoul(argv[1], NULL, 0); + + return report_return_code(tpm_physical_set_deactivated(dev, state)); +} + +static int do_tpm_get_capability(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 cap_area, sub_cap, rc; + void *cap; + size_t count; + struct udevice *dev; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 5) + return CMD_RET_USAGE; + cap_area = simple_strtoul(argv[1], NULL, 0); + sub_cap = simple_strtoul(argv[2], NULL, 0); + cap = (void *)simple_strtoul(argv[3], NULL, 0); + count = simple_strtoul(argv[4], NULL, 0); + + rc = tpm_get_capability(dev, cap_area, sub_cap, cap, count); + if (!rc) { + puts("capability information:\n"); + print_byte_string(cap, count); + } + + return report_return_code(rc); +} + +static int do_tpm_raw_transfer(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + void *command; + u8 response[1024]; + size_t count, response_length = sizeof(response); + u32 rc; + + command = parse_byte_string(argv[1], NULL, &count); + if (!command) { + printf("Couldn't parse byte string %s\n", argv[1]); + return CMD_RET_FAILURE; + } + + rc = get_tpm(&dev); + if (rc) + return rc; + + rc = tpm_xfer(dev, command, count, response, &response_length); + free(command); + if (!rc) { + puts("tpm response:\n"); + print_byte_string(response, response_length); + } + + return report_return_code(rc); +} + +static int do_tpm_nv_define(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 index, perm, size; + struct udevice *dev; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 4) + return CMD_RET_USAGE; + size = type_string_get_space_size(argv[1]); + if (!size) { + printf("Couldn't parse arguments\n"); + return CMD_RET_USAGE; + } + index = simple_strtoul(argv[2], NULL, 0); + perm = simple_strtoul(argv[3], NULL, 0); + + return report_return_code(tpm1_nv_define_space(dev, index, perm, size)); +} + +static int do_tpm_nv_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 index, count, err; + struct udevice *dev; + void *data; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc < 3) + return CMD_RET_USAGE; + if (argc != 3 + type_string_get_num_values(argv[1])) + return CMD_RET_USAGE; + index = simple_strtoul(argv[2], NULL, 0); + data = type_string_alloc(argv[1], &count); + if (!data) { + printf("Couldn't parse arguments\n"); + return CMD_RET_USAGE; + } + + err = tpm_nv_read_value(dev, index, data, count); + if (!err) { + if (type_string_write_vars(argv[1], data, argv + 3)) { + printf("Couldn't write to variables\n"); + err = ~0; + } + } + free(data); + + return report_return_code(err); +} + +static int do_tpm_nv_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 index, count, err; + struct udevice *dev; + void *data; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc < 3) + return CMD_RET_USAGE; + if (argc != 3 + type_string_get_num_values(argv[1])) + return CMD_RET_USAGE; + index = simple_strtoul(argv[2], NULL, 0); + data = type_string_alloc(argv[1], &count); + if (!data) { + printf("Couldn't parse arguments\n"); + return CMD_RET_USAGE; + } + if (type_string_pack(argv[1], argv + 3, data)) { + printf("Couldn't parse arguments\n"); + free(data); + return CMD_RET_USAGE; + } + + err = tpm_nv_write_value(dev, index, data, count); + free(data); + + return report_return_code(err); +} + +#ifdef CONFIG_TPM_AUTH_SESSIONS + +static int do_tpm_oiap(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 auth_handle, err; + struct udevice *dev; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + err = tpm1_oiap(dev, &auth_handle); + + return report_return_code(err); +} + +#ifdef CONFIG_TPM_LOAD_KEY_BY_SHA1 +static int do_tpm_load_key_by_sha1(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 parent_handle = 0; + u32 key_len, key_handle, err; + u8 usage_auth[DIGEST_LENGTH]; + u8 parent_hash[DIGEST_LENGTH]; + void *key; + struct udevice *dev; + + err = get_tpm(&dev); + if (err) + return err; + + if (argc < 5) + return CMD_RET_USAGE; + + parse_byte_string(argv[1], parent_hash, NULL); + key = (void *)simple_strtoul(argv[2], NULL, 0); + key_len = simple_strtoul(argv[3], NULL, 0); + if (strlen(argv[4]) != 2 * DIGEST_LENGTH) + return CMD_RET_FAILURE; + parse_byte_string(argv[4], usage_auth, NULL); + + err = tpm1_find_key_sha1(dev, usage_auth, parent_hash, &parent_handle); + if (err) { + printf("Could not find matching parent key (err = %d)\n", err); + return CMD_RET_FAILURE; + } + + printf("Found parent key %08x\n", parent_handle); + + err = tpm1_load_key2_oiap(dev, parent_handle, key, key_len, usage_auth, + &key_handle); + if (!err) { + printf("Key handle is 0x%x\n", key_handle); + env_set_hex("key_handle", key_handle); + } + + return report_return_code(err); +} +#endif /* CONFIG_TPM_LOAD_KEY_BY_SHA1 */ + +static int do_tpm_load_key2_oiap(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 parent_handle, key_len, key_handle, err; + u8 usage_auth[DIGEST_LENGTH]; + void *key; + struct udevice *dev; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc < 5) + return CMD_RET_USAGE; + + parent_handle = simple_strtoul(argv[1], NULL, 0); + key = (void *)simple_strtoul(argv[2], NULL, 0); + key_len = simple_strtoul(argv[3], NULL, 0); + if (strlen(argv[4]) != 2 * DIGEST_LENGTH) + return CMD_RET_FAILURE; + parse_byte_string(argv[4], usage_auth, NULL); + + err = tpm1_load_key2_oiap(dev, parent_handle, key, key_len, usage_auth, + &key_handle); + if (!err) + printf("Key handle is 0x%x\n", key_handle); + + return report_return_code(err); +} + +static int do_tpm_get_pub_key_oiap(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 key_handle, err; + u8 usage_auth[DIGEST_LENGTH]; + u8 pub_key_buffer[TPM_PUBKEY_MAX_LENGTH]; + size_t pub_key_len = sizeof(pub_key_buffer); + struct udevice *dev; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc < 3) + return CMD_RET_USAGE; + + key_handle = simple_strtoul(argv[1], NULL, 0); + if (strlen(argv[2]) != 2 * DIGEST_LENGTH) + return CMD_RET_FAILURE; + parse_byte_string(argv[2], usage_auth, NULL); + + err = tpm1_get_pub_key_oiap(dev, key_handle, usage_auth, pub_key_buffer, + &pub_key_len); + if (!err) { + printf("dump of received pub key structure:\n"); + print_byte_string(pub_key_buffer, pub_key_len); + } + return report_return_code(err); +} + +TPM_COMMAND_NO_ARG(tpm1_end_oiap) + +#endif /* CONFIG_TPM_AUTH_SESSIONS */ + +#ifdef CONFIG_TPM_FLUSH_RESOURCES +static int do_tpm_flush(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int type = 0; + int rc; + + rc = get_tpm(&dev); + if (rc) + return rc; + + if (argc != 3) + return CMD_RET_USAGE; + + if (!strcasecmp(argv[1], "key")) + type = TPM_RT_KEY; + else if (!strcasecmp(argv[1], "auth")) + type = TPM_RT_AUTH; + else if (!strcasecmp(argv[1], "hash")) + type = TPM_RT_HASH; + else if (!strcasecmp(argv[1], "trans")) + type = TPM_RT_TRANS; + else if (!strcasecmp(argv[1], "context")) + type = TPM_RT_CONTEXT; + else if (!strcasecmp(argv[1], "counter")) + type = TPM_RT_COUNTER; + else if (!strcasecmp(argv[1], "delegate")) + type = TPM_RT_DELEGATE; + else if (!strcasecmp(argv[1], "daa_tpm")) + type = TPM_RT_DAA_TPM; + else if (!strcasecmp(argv[1], "daa_v0")) + type = TPM_RT_DAA_V0; + else if (!strcasecmp(argv[1], "daa_v1")) + type = TPM_RT_DAA_V1; + + if (!type) { + printf("Resource type %s unknown.\n", argv[1]); + return -1; + } + + if (!strcasecmp(argv[2], "all")) { + u16 res_count; + u8 buf[288]; + u8 *ptr; + int err; + uint i; + + /* fetch list of already loaded resources in the TPM */ + err = tpm_get_capability(dev, TPM_CAP_HANDLE, type, buf, + sizeof(buf)); + if (err) { + printf("tpm_get_capability returned error %d.\n", err); + return -1; + } + res_count = get_unaligned_be16(buf); + ptr = buf + 2; + for (i = 0; i < res_count; ++i, ptr += 4) + tpm1_flush_specific(dev, get_unaligned_be32(ptr), type); + } else { + u32 handle = simple_strtoul(argv[2], NULL, 0); + + if (!handle) { + printf("Illegal resource handle %s\n", argv[2]); + return -1; + } + tpm1_flush_specific(dev, cpu_to_be32(handle), type); + } + + return 0; +} +#endif /* CONFIG_TPM_FLUSH_RESOURCES */ + +#ifdef CONFIG_TPM_LIST_RESOURCES +static int do_tpm_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int type = 0; + u16 res_count; + u8 buf[288]; + u8 *ptr; + int err; + uint i; + + err = get_tpm(&dev); + if (err) + return err; + + if (argc != 2) + return CMD_RET_USAGE; + + if (!strcasecmp(argv[1], "key")) + type = TPM_RT_KEY; + else if (!strcasecmp(argv[1], "auth")) + type = TPM_RT_AUTH; + else if (!strcasecmp(argv[1], "hash")) + type = TPM_RT_HASH; + else if (!strcasecmp(argv[1], "trans")) + type = TPM_RT_TRANS; + else if (!strcasecmp(argv[1], "context")) + type = TPM_RT_CONTEXT; + else if (!strcasecmp(argv[1], "counter")) + type = TPM_RT_COUNTER; + else if (!strcasecmp(argv[1], "delegate")) + type = TPM_RT_DELEGATE; + else if (!strcasecmp(argv[1], "daa_tpm")) + type = TPM_RT_DAA_TPM; + else if (!strcasecmp(argv[1], "daa_v0")) + type = TPM_RT_DAA_V0; + else if (!strcasecmp(argv[1], "daa_v1")) + type = TPM_RT_DAA_V1; + + if (!type) { + printf("Resource type %s unknown.\n", argv[1]); + return -1; + } + + /* fetch list of already loaded resources in the TPM */ + err = tpm_get_capability(dev, TPM_CAP_HANDLE, type, buf, + sizeof(buf)); + if (err) { + printf("tpm_get_capability returned error %d.\n", err); + return -1; + } + res_count = get_unaligned_be16(buf); + ptr = buf + 2; + + printf("Resources of type %s (%02x):\n", argv[1], type); + if (!res_count) { + puts("None\n"); + } else { + for (i = 0; i < res_count; ++i, ptr += 4) + printf("Index %d: %08x\n", i, get_unaligned_be32(ptr)); + } + + return 0; +} +#endif /* CONFIG_TPM_LIST_RESOURCES */ + +TPM_COMMAND_NO_ARG(tpm_self_test_full) +TPM_COMMAND_NO_ARG(tpm_continue_self_test) +TPM_COMMAND_NO_ARG(tpm_force_clear) +TPM_COMMAND_NO_ARG(tpm_physical_enable) +TPM_COMMAND_NO_ARG(tpm_physical_disable) + +static struct cmd_tbl tpm1_commands[] = { + U_BOOT_CMD_MKENT(device, 0, 1, do_tpm_device, "", ""), + U_BOOT_CMD_MKENT(info, 0, 1, do_tpm_info, "", ""), + U_BOOT_CMD_MKENT(autostart, 0, 1, do_tpm_autostart, "", ""), + U_BOOT_CMD_MKENT(init, 0, 1, do_tpm_init, "", ""), + U_BOOT_CMD_MKENT(startup, 0, 1, + do_tpm_startup, "", ""), + U_BOOT_CMD_MKENT(self_test_full, 0, 1, + do_tpm_self_test_full, "", ""), + U_BOOT_CMD_MKENT(continue_self_test, 0, 1, + do_tpm_continue_self_test, "", ""), + U_BOOT_CMD_MKENT(force_clear, 0, 1, + do_tpm_force_clear, "", ""), + U_BOOT_CMD_MKENT(physical_enable, 0, 1, + do_tpm_physical_enable, "", ""), + U_BOOT_CMD_MKENT(physical_disable, 0, 1, + do_tpm_physical_disable, "", ""), + U_BOOT_CMD_MKENT(nv_define_space, 0, 1, + do_tpm_nv_define_space, "", ""), + U_BOOT_CMD_MKENT(nv_read_value, 0, 1, + do_tpm_nv_read_value, "", ""), + U_BOOT_CMD_MKENT(nv_write_value, 0, 1, + do_tpm_nv_write_value, "", ""), + U_BOOT_CMD_MKENT(extend, 0, 1, + do_tpm_extend, "", ""), + U_BOOT_CMD_MKENT(pcr_read, 0, 1, + do_tpm_pcr_read, "", ""), + U_BOOT_CMD_MKENT(tsc_physical_presence, 0, 1, + do_tpm_tsc_physical_presence, "", ""), + U_BOOT_CMD_MKENT(read_pubek, 0, 1, + do_tpm_read_pubek, "", ""), + U_BOOT_CMD_MKENT(physical_set_deactivated, 0, 1, + do_tpm_physical_set_deactivated, "", ""), + U_BOOT_CMD_MKENT(get_capability, 0, 1, + do_tpm_get_capability, "", ""), + U_BOOT_CMD_MKENT(raw_transfer, 0, 1, + do_tpm_raw_transfer, "", ""), + U_BOOT_CMD_MKENT(nv_define, 0, 1, + do_tpm_nv_define, "", ""), + U_BOOT_CMD_MKENT(nv_read, 0, 1, + do_tpm_nv_read, "", ""), + U_BOOT_CMD_MKENT(nv_write, 0, 1, + do_tpm_nv_write, "", ""), +#ifdef CONFIG_TPM_AUTH_SESSIONS + U_BOOT_CMD_MKENT(oiap, 0, 1, + do_tpm_oiap, "", ""), + U_BOOT_CMD_MKENT(end_oiap, 0, 1, + do_tpm1_end_oiap, "", ""), + U_BOOT_CMD_MKENT(load_key2_oiap, 0, 1, + do_tpm_load_key2_oiap, "", ""), +#ifdef CONFIG_TPM_LOAD_KEY_BY_SHA1 + U_BOOT_CMD_MKENT(load_key_by_sha1, 0, 1, + do_tpm_load_key_by_sha1, "", ""), +#endif /* CONFIG_TPM_LOAD_KEY_BY_SHA1 */ + U_BOOT_CMD_MKENT(get_pub_key_oiap, 0, 1, + do_tpm_get_pub_key_oiap, "", ""), +#endif /* CONFIG_TPM_AUTH_SESSIONS */ +#ifdef CONFIG_TPM_FLUSH_RESOURCES + U_BOOT_CMD_MKENT(flush, 0, 1, + do_tpm_flush, "", ""), +#endif /* CONFIG_TPM_FLUSH_RESOURCES */ +#ifdef CONFIG_TPM_LIST_RESOURCES + U_BOOT_CMD_MKENT(list, 0, 1, + do_tpm_list, "", ""), +#endif /* CONFIG_TPM_LIST_RESOURCES */ +}; + +struct cmd_tbl *get_tpm1_commands(unsigned int *size) +{ + *size = ARRAY_SIZE(tpm1_commands); + + return tpm1_commands; +} + +U_BOOT_CMD(tpm, CONFIG_SYS_MAXARGS, 1, do_tpm, +"Issue a TPMv1.x command", +"cmd args...\n" +" - Issue TPM command <cmd> with arguments <args...>.\n" +"Admin Startup and State Commands:\n" +" device [num device]\n" +" - Show all devices or set the specified device\n" +" info - Show information about the TPM\n" +" autostart\n" +" - Initalize the tpm, perform a Startup(clear) and run a full selftest\n" +" sequence\n" +" init\n" +" - Put TPM into a state where it waits for 'startup' command.\n" +" startup mode\n" +" - Issue TPM_Starup command. <mode> is one of TPM_ST_CLEAR,\n" +" TPM_ST_STATE, and TPM_ST_DEACTIVATED.\n" +"Admin Testing Commands:\n" +" self_test_full\n" +" - Test all of the TPM capabilities.\n" +" continue_self_test\n" +" - Inform TPM that it should complete the self-test.\n" +"Admin Opt-in Commands:\n" +" physical_enable\n" +" - Set the PERMANENT disable flag to FALSE using physical presence as\n" +" authorization.\n" +" physical_disable\n" +" - Set the PERMANENT disable flag to TRUE using physical presence as\n" +" authorization.\n" +" physical_set_deactivated 0|1\n" +" - Set deactivated flag.\n" +"Admin Ownership Commands:\n" +" force_clear\n" +" - Issue TPM_ForceClear command.\n" +" tsc_physical_presence flags\n" +" - Set TPM device's Physical Presence flags to <flags>.\n" +"The Capability Commands:\n" +" get_capability cap_area sub_cap addr count\n" +" - Read <count> bytes of TPM capability indexed by <cap_area> and\n" +" <sub_cap> to memory address <addr>.\n" +#if defined(CONFIG_TPM_FLUSH_RESOURCES) || defined(CONFIG_TPM_LIST_RESOURCES) +"Resource management functions\n" +#endif +#ifdef CONFIG_TPM_FLUSH_RESOURCES +" flush resource_type id\n" +" - flushes a resource of type <resource_type> (may be one of key, auth,\n" +" hash, trans, context, counter, delegate, daa_tpm, daa_v0, daa_v1),\n" +" and id <id> from the TPM. Use an <id> of \"all\" to flush all\n" +" resources of that type.\n" +#endif /* CONFIG_TPM_FLUSH_RESOURCES */ +#ifdef CONFIG_TPM_LIST_RESOURCES +" list resource_type\n" +" - lists resources of type <resource_type> (may be one of key, auth,\n" +" hash, trans, context, counter, delegate, daa_tpm, daa_v0, daa_v1),\n" +" contained in the TPM.\n" +#endif /* CONFIG_TPM_LIST_RESOURCES */ +#ifdef CONFIG_TPM_AUTH_SESSIONS +"Storage functions\n" +" loadkey2_oiap parent_handle key_addr key_len usage_auth\n" +" - loads a key data from memory address <key_addr>, <key_len> bytes\n" +" into TPM using the parent key <parent_handle> with authorization\n" +" <usage_auth> (20 bytes hex string).\n" +#ifdef CONFIG_TPM_LOAD_KEY_BY_SHA1 +" load_key_by_sha1 parent_hash key_addr key_len usage_auth\n" +" - loads a key data from memory address <key_addr>, <key_len> bytes\n" +" into TPM using the parent hash <parent_hash> (20 bytes hex string)\n" +" with authorization <usage_auth> (20 bytes hex string).\n" +#endif /* CONFIG_TPM_LOAD_KEY_BY_SHA1 */ +" get_pub_key_oiap key_handle usage_auth\n" +" - get the public key portion of a loaded key <key_handle> using\n" +" authorization <usage auth> (20 bytes hex string)\n" +#endif /* CONFIG_TPM_AUTH_SESSIONS */ +"Endorsement Key Handling Commands:\n" +" read_pubek addr count\n" +" - Read <count> bytes of the public endorsement key to memory\n" +" address <addr>\n" +"Integrity Collection and Reporting Commands:\n" +" extend index digest_hex_string\n" +" - Add a new measurement to a PCR. Update PCR <index> with the 20-bytes\n" +" <digest_hex_string>\n" +" pcr_read index addr count\n" +" - Read <count> bytes from PCR <index> to memory address <addr>.\n" +#ifdef CONFIG_TPM_AUTH_SESSIONS +"Authorization Sessions\n" +" oiap\n" +" - setup an OIAP session\n" +" end_oiap\n" +" - terminates an active OIAP session\n" +#endif /* CONFIG_TPM_AUTH_SESSIONS */ +"Non-volatile Storage Commands:\n" +" nv_define_space index permission size\n" +" - Establish a space at index <index> with <permission> of <size> bytes.\n" +" nv_read_value index addr count\n" +" - Read <count> bytes from space <index> to memory address <addr>.\n" +" nv_write_value index addr count\n" +" - Write <count> bytes from memory address <addr> to space <index>.\n" +"Miscellaneous helper functions:\n" +" raw_transfer byte_string\n" +" - Send a byte string <byte_string> to TPM and print the response.\n" +" Non-volatile storage helper functions:\n" +" These helper functions treat a non-volatile space as a non-padded\n" +" sequence of integer values. These integer values are defined by a type\n" +" string, which is a text string of 'bwd' characters: 'b' means a 8-bit\n" +" value, 'w' 16-bit value, 'd' 32-bit value. All helper functions take\n" +" a type string as their first argument.\n" +" nv_define type_string index perm\n" +" - Define a space <index> with permission <perm>.\n" +" nv_read types_string index vars...\n" +" - Read from space <index> to environment variables <vars...>.\n" +" nv_write types_string index values...\n" +" - Write to space <index> from values <values...>.\n" +); diff --git a/cmd/tpm-v2.c b/cmd/tpm-v2.c new file mode 100644 index 00000000000..346e21d27bb --- /dev/null +++ b/cmd/tpm-v2.c @@ -0,0 +1,598 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2018 Bootlin + * Author: Miquel Raynal <miquel.raynal@bootlin.com> + */ + +#include <command.h> +#include <dm.h> +#include <log.h> +#include <mapmem.h> +#include <tpm-common.h> +#include <tpm-v2.h> +#include "tpm-user-utils.h" + +static int do_tpm2_startup(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum tpm2_startup_types mode; + struct udevice *dev; + int ret; + bool bon = true; + + ret = get_tpm(&dev); + if (ret) + return ret; + + /* argv[2] is optional to perform a TPM2_CC_SHUTDOWN */ + if (argc > 3 || (argc == 3 && strcasecmp("off", argv[2]))) + return CMD_RET_USAGE; + + if (!strcasecmp("TPM2_SU_CLEAR", argv[1])) { + mode = TPM2_SU_CLEAR; + } else if (!strcasecmp("TPM2_SU_STATE", argv[1])) { + mode = TPM2_SU_STATE; + } else { + printf("Couldn't recognize mode string: %s\n", argv[1]); + return CMD_RET_FAILURE; + } + + if (argv[2]) + bon = false; + + return report_return_code(tpm2_startup(dev, bon, mode)); +} + +static int do_tpm2_self_test(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum tpm2_yes_no full_test; + struct udevice *dev; + int ret; + + ret = get_tpm(&dev); + if (ret) + return ret; + if (argc != 2) + return CMD_RET_USAGE; + + if (!strcasecmp("full", argv[1])) { + full_test = TPMI_YES; + } else if (!strcasecmp("continue", argv[1])) { + full_test = TPMI_NO; + } else { + printf("Couldn't recognize test mode: %s\n", argv[1]); + return CMD_RET_FAILURE; + } + + return report_return_code(tpm2_self_test(dev, full_test)); +} + +static int do_tpm2_clear(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 handle = 0; + const char *pw = (argc < 3) ? NULL : argv[2]; + const ssize_t pw_sz = pw ? strlen(pw) : 0; + struct udevice *dev; + int ret; + + ret = get_tpm(&dev); + if (ret) + return ret; + + if (argc < 2 || argc > 3) + return CMD_RET_USAGE; + + if (pw_sz > TPM2_DIGEST_LEN) + return -EINVAL; + + if (!strcasecmp("TPM2_RH_LOCKOUT", argv[1])) + handle = TPM2_RH_LOCKOUT; + else if (!strcasecmp("TPM2_RH_PLATFORM", argv[1])) + handle = TPM2_RH_PLATFORM; + else + return CMD_RET_USAGE; + + return report_return_code(tpm2_clear(dev, handle, pw, pw_sz)); +} + +static int do_tpm2_pcr_extend(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + struct tpm_chip_priv *priv; + u32 index = simple_strtoul(argv[1], NULL, 0); + void *digest = map_sysmem(simple_strtoul(argv[2], NULL, 0), 0); + int algo = TPM2_ALG_SHA256; + int algo_len; + int ret; + u32 rc; + + if (argc < 3 || argc > 4) + return CMD_RET_USAGE; + if (argc == 4) { + algo = tpm2_name_to_algorithm(argv[3]); + if (algo == TPM2_ALG_INVAL) + return CMD_RET_FAILURE; + } + algo_len = tpm2_algorithm_to_len(algo); + + ret = get_tpm(&dev); + if (ret) + return ret; + + priv = dev_get_uclass_priv(dev); + if (!priv) + return -EINVAL; + + if (index >= priv->pcr_count) + return -EINVAL; + + rc = tpm2_pcr_extend(dev, index, algo, digest, algo_len); + if (!rc) { + printf("PCR #%u extended with %d byte %s digest\n", index, + algo_len, tpm2_algorithm_name(algo)); + print_byte_string(digest, algo_len); + } + + unmap_sysmem(digest); + + return report_return_code(rc); +} + +static int do_tpm_pcr_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum tpm2_algorithms algo = TPM2_ALG_SHA256; + struct udevice *dev; + struct tpm_chip_priv *priv; + u32 index, rc; + int algo_len; + unsigned int updates; + void *data; + int ret; + + if (argc < 3 || argc > 4) + return CMD_RET_USAGE; + if (argc == 4) { + algo = tpm2_name_to_algorithm(argv[3]); + if (algo == TPM2_ALG_INVAL) + return CMD_RET_FAILURE; + } + algo_len = tpm2_algorithm_to_len(algo); + + ret = get_tpm(&dev); + if (ret) + return ret; + + priv = dev_get_uclass_priv(dev); + if (!priv) + return -EINVAL; + + index = simple_strtoul(argv[1], NULL, 0); + if (index >= priv->pcr_count) + return -EINVAL; + + data = map_sysmem(simple_strtoul(argv[2], NULL, 0), 0); + + rc = tpm2_pcr_read(dev, index, priv->pcr_select_min, algo, + data, algo_len, &updates); + if (!rc) { + printf("PCR #%u %s %d byte content (%u known updates):\n", index, + tpm2_algorithm_name(algo), algo_len, updates); + print_byte_string(data, algo_len); + } + + unmap_sysmem(data); + + return report_return_code(rc); +} + +static int do_tpm_get_capability(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 capability, property, rc; + u8 *data; + size_t count; + int i, j; + struct udevice *dev; + int ret; + + ret = get_tpm(&dev); + if (ret) + return ret; + + if (argc != 5) + return CMD_RET_USAGE; + + capability = simple_strtoul(argv[1], NULL, 0); + property = simple_strtoul(argv[2], NULL, 0); + data = map_sysmem(simple_strtoul(argv[3], NULL, 0), 0); + count = simple_strtoul(argv[4], NULL, 0); + + rc = tpm2_get_capability(dev, capability, property, data, count); + if (rc) + goto unmap_data; + + printf("Capabilities read from TPM:\n"); + for (i = 0; i < count; i++) { + printf("Property 0x"); + for (j = 0; j < 4; j++) + printf("%02x", data[(i * 8) + j + sizeof(u32)]); + printf(": 0x"); + for (j = 4; j < 8; j++) + printf("%02x", data[(i * 8) + j + sizeof(u32)]); + printf("\n"); + } + +unmap_data: + unmap_sysmem(data); + + return report_return_code(rc); +} + +static u32 select_mask(u32 mask, enum tpm2_algorithms algo, bool select) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(hash_algo_list); i++) { + if (hash_algo_list[i].hash_alg != algo) + continue; + + if (select) + mask |= hash_algo_list[i].hash_mask; + else + mask &= ~hash_algo_list[i].hash_mask; + + break; + } + + return mask; +} + +static bool +is_algo_in_pcrs(enum tpm2_algorithms algo, struct tpml_pcr_selection *pcrs) +{ + size_t i; + + for (i = 0; i < pcrs->count; i++) { + if (algo == pcrs->selection[i].hash) + return true; + } + + return false; +} + +static int do_tpm2_pcrallocate(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret; + enum tpm2_algorithms algo; + const char *pw = (argc < 4) ? NULL : argv[3]; + const ssize_t pw_sz = pw ? strlen(pw) : 0; + static struct tpml_pcr_selection pcr = { 0 }; + u32 pcr_len = 0; + bool bon = false; + static u32 mask; + int i; + + /* argv[1]: algorithm (bank), argv[2]: on/off */ + if (argc < 3 || argc > 4) + return CMD_RET_USAGE; + + if (!strcasecmp("on", argv[2])) + bon = true; + else if (strcasecmp("off", argv[2])) + return CMD_RET_USAGE; + + algo = tpm2_name_to_algorithm(argv[1]); + if (algo == TPM2_ALG_INVAL) + return CMD_RET_USAGE; + + ret = get_tpm(&dev); + if (ret) + return ret; + + if (!pcr.count) { + /* + * Get current active algorithms (banks), PCRs and mask via the + * first call + */ + ret = tpm2_get_pcr_info(dev, &pcr); + if (ret) + return ret; + + for (i = 0; i < pcr.count; i++) { + struct tpms_pcr_selection *sel = &pcr.selection[i]; + const char *name; + + if (!tpm2_is_active_bank(sel)) + continue; + + mask = select_mask(mask, sel->hash, true); + name = tpm2_algorithm_name(sel->hash); + if (name) + printf("Active bank[%d]: %s\n", i, name); + } + } + + if (!is_algo_in_pcrs(algo, &pcr)) { + printf("%s is not supported by the tpm device\n", argv[1]); + return CMD_RET_USAGE; + } + + mask = select_mask(mask, algo, bon); + ret = tpm2_pcr_config_algo(dev, mask, &pcr, &pcr_len); + if (ret) + return ret; + + return report_return_code(tpm2_send_pcr_allocate(dev, pw, pw_sz, &pcr, + pcr_len)); +} + +static int do_tpm_dam_reset(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *pw = (argc < 2) ? NULL : argv[1]; + const ssize_t pw_sz = pw ? strlen(pw) : 0; + struct udevice *dev; + int ret; + + ret = get_tpm(&dev); + if (ret) + return ret; + + if (argc > 2) + return CMD_RET_USAGE; + + if (pw_sz > TPM2_DIGEST_LEN) + return -EINVAL; + + return report_return_code(tpm2_dam_reset(dev, pw, pw_sz)); +} + +static int do_tpm_dam_parameters(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *pw = (argc < 5) ? NULL : argv[4]; + const ssize_t pw_sz = pw ? strlen(pw) : 0; + /* + * No Dictionary Attack Mitigation (DAM) means: + * maxtries = 0xFFFFFFFF, recovery_time = 1, lockout_recovery = 0 + */ + unsigned long int max_tries; + unsigned long int recovery_time; + unsigned long int lockout_recovery; + struct udevice *dev; + int ret; + + ret = get_tpm(&dev); + if (ret) + return ret; + + if (argc < 4 || argc > 5) + return CMD_RET_USAGE; + + if (pw_sz > TPM2_DIGEST_LEN) + return -EINVAL; + + if (strict_strtoul(argv[1], 0, &max_tries)) + return CMD_RET_USAGE; + + if (strict_strtoul(argv[2], 0, &recovery_time)) + return CMD_RET_USAGE; + + if (strict_strtoul(argv[3], 0, &lockout_recovery)) + return CMD_RET_USAGE; + + log(LOGC_NONE, LOGL_INFO, "Changing dictionary attack parameters:\n"); + log(LOGC_NONE, LOGL_INFO, "- maxTries: %lu", max_tries); + log(LOGC_NONE, LOGL_INFO, "- recoveryTime: %lu\n", recovery_time); + log(LOGC_NONE, LOGL_INFO, "- lockoutRecovery: %lu\n", lockout_recovery); + + return report_return_code(tpm2_dam_parameters(dev, pw, pw_sz, max_tries, + recovery_time, + lockout_recovery)); +} + +static int do_tpm_change_auth(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 handle; + const char *newpw = argv[2]; + const char *oldpw = (argc == 3) ? NULL : argv[3]; + const ssize_t newpw_sz = strlen(newpw); + const ssize_t oldpw_sz = oldpw ? strlen(oldpw) : 0; + struct udevice *dev; + int ret; + + ret = get_tpm(&dev); + if (ret) + return ret; + + if (argc < 3 || argc > 4) + return CMD_RET_USAGE; + + if (newpw_sz > TPM2_DIGEST_LEN || oldpw_sz > TPM2_DIGEST_LEN) + return -EINVAL; + + if (!strcasecmp("TPM2_RH_LOCKOUT", argv[1])) + handle = TPM2_RH_LOCKOUT; + else if (!strcasecmp("TPM2_RH_ENDORSEMENT", argv[1])) + handle = TPM2_RH_ENDORSEMENT; + else if (!strcasecmp("TPM2_RH_OWNER", argv[1])) + handle = TPM2_RH_OWNER; + else if (!strcasecmp("TPM2_RH_PLATFORM", argv[1])) + handle = TPM2_RH_PLATFORM; + else + return CMD_RET_USAGE; + + return report_return_code(tpm2_change_auth(dev, handle, newpw, newpw_sz, + oldpw, oldpw_sz)); +} + +static int do_tpm_pcr_setauthpolicy(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + u32 index = simple_strtoul(argv[1], NULL, 0); + char *key = argv[2]; + const char *pw = (argc < 4) ? NULL : argv[3]; + const ssize_t pw_sz = pw ? strlen(pw) : 0; + struct udevice *dev; + int ret; + + ret = get_tpm(&dev); + if (ret) + return ret; + + if (strlen(key) != TPM2_DIGEST_LEN) + return -EINVAL; + + if (argc < 3 || argc > 4) + return CMD_RET_USAGE; + + return report_return_code(tpm2_pcr_setauthpolicy(dev, pw, pw_sz, index, + key)); +} + +static int do_tpm_pcr_setauthvalue(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + u32 index = simple_strtoul(argv[1], NULL, 0); + char *key = argv[2]; + const ssize_t key_sz = strlen(key); + const char *pw = (argc < 4) ? NULL : argv[3]; + const ssize_t pw_sz = pw ? strlen(pw) : 0; + struct udevice *dev; + int ret; + + ret = get_tpm(&dev); + if (ret) + return ret; + + if (strlen(key) != TPM2_DIGEST_LEN) + return -EINVAL; + + if (argc < 3 || argc > 4) + return CMD_RET_USAGE; + + return report_return_code(tpm2_pcr_setauthvalue(dev, pw, pw_sz, index, + key, key_sz)); +} + +static struct cmd_tbl tpm2_commands[] = { + U_BOOT_CMD_MKENT(device, 0, 1, do_tpm_device, "", ""), + U_BOOT_CMD_MKENT(info, 0, 1, do_tpm_info, "", ""), + U_BOOT_CMD_MKENT(state, 0, 1, do_tpm_report_state, "", ""), + U_BOOT_CMD_MKENT(init, 0, 1, do_tpm_init, "", ""), + U_BOOT_CMD_MKENT(startup, 0, 1, do_tpm2_startup, "", ""), + U_BOOT_CMD_MKENT(self_test, 0, 1, do_tpm2_self_test, "", ""), + U_BOOT_CMD_MKENT(clear, 0, 1, do_tpm2_clear, "", ""), + U_BOOT_CMD_MKENT(pcr_extend, 0, 1, do_tpm2_pcr_extend, "", ""), + U_BOOT_CMD_MKENT(pcr_read, 0, 1, do_tpm_pcr_read, "", ""), + U_BOOT_CMD_MKENT(get_capability, 0, 1, do_tpm_get_capability, "", ""), + U_BOOT_CMD_MKENT(dam_reset, 0, 1, do_tpm_dam_reset, "", ""), + U_BOOT_CMD_MKENT(dam_parameters, 0, 1, do_tpm_dam_parameters, "", ""), + U_BOOT_CMD_MKENT(change_auth, 0, 1, do_tpm_change_auth, "", ""), + U_BOOT_CMD_MKENT(autostart, 0, 1, do_tpm_autostart, "", ""), + U_BOOT_CMD_MKENT(pcr_setauthpolicy, 0, 1, + do_tpm_pcr_setauthpolicy, "", ""), + U_BOOT_CMD_MKENT(pcr_setauthvalue, 0, 1, + do_tpm_pcr_setauthvalue, "", ""), + U_BOOT_CMD_MKENT(pcr_allocate, 0, 1, do_tpm2_pcrallocate, "", ""), +}; + +struct cmd_tbl *get_tpm2_commands(unsigned int *size) +{ + *size = ARRAY_SIZE(tpm2_commands); + + return tpm2_commands; +} + +U_BOOT_CMD(tpm2, CONFIG_SYS_MAXARGS, 1, do_tpm, "Issue a TPMv2.x command", +"<command> [<arguments>]\n" +"\n" +"device [num device]\n" +" Show all devices or set the specified device\n" +"info\n" +" Show information about the TPM.\n" +"state\n" +" Show internal state from the TPM (if available)\n" +"autostart\n" +" Initalize the tpm, perform a Startup(clear) and run a full selftest\n" +" sequence\n" +"init\n" +" Initialize the software stack. Always the first command to issue.\n" +" 'tpm startup' is the only acceptable command after a 'tpm init' has been\n" +" issued\n" +"startup <mode> [<op>]\n" +" Issue a TPM2_Startup command.\n" +" <mode> is one of:\n" +" * TPM2_SU_CLEAR (reset state)\n" +" * TPM2_SU_STATE (preserved state)\n" +" <op>:\n" +" * off - To shutdown the TPM\n" +"self_test <type>\n" +" Test the TPM capabilities.\n" +" <type> is one of:\n" +" * full (perform all tests)\n" +" * continue (only check untested tests)\n" +"clear <hierarchy>\n" +" Issue a TPM2_Clear command.\n" +" <hierarchy> is one of:\n" +" * TPM2_RH_LOCKOUT\n" +" * TPM2_RH_PLATFORM\n" +"pcr_extend <pcr> <digest_addr> [<digest_algo>]\n" +" Extend PCR #<pcr> with digest at <digest_addr> with digest_algo.\n" +" <pcr>: index of the PCR\n" +" <digest_addr>: address of digest of digest_algo type (defaults to SHA256)\n" +"pcr_read <pcr> <digest_addr> [<digest_algo>]\n" +" Read PCR #<pcr> to memory address <digest_addr> with <digest_algo>.\n" +" <pcr>: index of the PCR\n" +" <digest_addr>: address of digest of digest_algo type (defaults to SHA256)\n" +"get_capability <capability> <property> <addr> <count>\n" +" Read and display <count> entries indexed by <capability>/<property>.\n" +" Values are 4 bytes long and are written at <addr>.\n" +" <capability>: capability\n" +" <property>: property\n" +" <addr>: address to store <count> entries of 4 bytes\n" +" <count>: number of entries to retrieve\n" +"dam_reset [<password>]\n" +" If the TPM is not in a LOCKOUT state, reset the internal error counter.\n" +" <password>: optional password\n" +"dam_parameters <max_tries> <recovery_time> <lockout_recovery> [<password>]\n" +" If the TPM is not in a LOCKOUT state, set the DAM parameters\n" +" <maxTries>: maximum number of failures before lockout,\n" +" 0 means always locking\n" +" <recoveryTime>: time before decrement of the error counter,\n" +" 0 means no lockout\n" +" <lockoutRecovery>: time of a lockout (before the next try),\n" +" 0 means a reboot is needed\n" +" <password>: optional password of the LOCKOUT hierarchy\n" +"change_auth <hierarchy> <new_pw> [<old_pw>]\n" +" <hierarchy>: the hierarchy\n" +" <new_pw>: new password for <hierarchy>\n" +" <old_pw>: optional previous password of <hierarchy>\n" +"pcr_setauthpolicy|pcr_setauthvalue <pcr> <key> [<password>]\n" +" Change the <key> to access PCR #<pcr>.\n" +" hierarchy and may be empty.\n" +" /!\\WARNING: untested function, use at your own risks !\n" +" <pcr>: index of the PCR\n" +" <key>: secret to protect the access of PCR #<pcr>\n" +" <password>: optional password of the PLATFORM hierarchy\n" +"pcr_allocate <algorithm> <on/off> [<password>]\n" +" Issue a TPM2_PCR_Allocate Command to reconfig PCR bank algorithm.\n" +" <algorithm> is one of:\n" +" * sha1\n" +" * sha256\n" +" * sha384\n" +" * sha512\n" +" <on|off> is one of:\n" +" * on - Select all available PCRs associated with the specified\n" +" algorithm (bank)\n" +" * off - Clear all available PCRs associated with the specified\n" +" algorithm (bank)\n" +" <password>: optional password\n" +); diff --git a/cmd/tpm_test.c b/cmd/tpm_test.c new file mode 100644 index 00000000000..af83d78c3fe --- /dev/null +++ b/cmd/tpm_test.c @@ -0,0 +1,573 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2015 Google, Inc + */ + +#include <command.h> +#include <cpu_func.h> +#include <log.h> +#include <time.h> +#include <tpm-v1.h> +#include <linux/printk.h> +#include "tpm-user-utils.h" +#include <tpm_api.h> + +/* Prints error and returns on failure */ +#define TPM_CHECK(tpm_command) do { \ + uint32_t result; \ + \ + result = (tpm_command); \ + if (result != TPM_SUCCESS) { \ + printf("TEST FAILED: line %d: " #tpm_command ": 0x%x\n", \ + __LINE__, result); \ + return result; \ + } \ +} while (0) + +#define INDEX0 0xda70 +#define INDEX1 0xda71 +#define INDEX2 0xda72 +#define INDEX3 0xda73 +#define INDEX_INITIALISED 0xda80 +#define PHYS_PRESENCE 4 +#define PRESENCE 8 + +static uint32_t TlclStartupIfNeeded(struct udevice *dev) +{ + uint32_t result = tpm_startup(dev, TPM_ST_CLEAR); + + return result == TPM_INVALID_POSTINIT ? TPM_SUCCESS : result; +} + +static int test_timer(struct udevice *dev) +{ + printf("get_timer(0) = %lu\n", get_timer(0)); + return 0; +} + +static uint32_t tpm_get_flags(struct udevice *dev, uint8_t *disable, + uint8_t *deactivated, uint8_t *nvlocked) +{ + struct tpm_permanent_flags pflags; + uint32_t result; + + result = tpm1_get_permanent_flags(dev, &pflags); + if (result) + return result; + if (disable) + *disable = pflags.disable; + if (deactivated) + *deactivated = pflags.deactivated; + if (nvlocked) + *nvlocked = pflags.nv_locked; + debug("TPM: Got flags disable=%d, deactivated=%d, nvlocked=%d\n", + pflags.disable, pflags.deactivated, pflags.nv_locked); + + return 0; +} + +static uint32_t tpm_nv_write_value_lock(struct udevice *dev, uint32_t index) +{ + debug("TPM: Write lock 0x%x\n", index); + + return tpm_nv_write_value(dev, index, NULL, 0); +} + +static int tpm_is_owned(struct udevice *dev) +{ + uint8_t response[TPM_PUBEK_SIZE]; + uint32_t result; + + result = tpm_read_pubek(dev, response, sizeof(response)); + + return result != TPM_SUCCESS; +} + +static int test_early_extend(struct udevice *dev) +{ + uint8_t value_in[20]; + uint8_t value_out[20]; + + printf("Testing earlyextend ..."); + tpm_init(dev); + TPM_CHECK(tpm_startup(dev, TPM_ST_CLEAR)); + TPM_CHECK(tpm_continue_self_test(dev)); + TPM_CHECK(tpm_pcr_extend(dev, 1, value_in, sizeof(value_in), value_out, + "test")); + printf("done\n"); + return 0; +} + +static int test_early_nvram(struct udevice *dev) +{ + uint32_t x; + + printf("Testing earlynvram ..."); + tpm_init(dev); + TPM_CHECK(tpm_startup(dev, TPM_ST_CLEAR)); + TPM_CHECK(tpm_continue_self_test(dev)); + TPM_CHECK(tpm_tsc_physical_presence(dev, PRESENCE)); + TPM_CHECK(tpm_nv_read_value(dev, INDEX0, (uint8_t *)&x, sizeof(x))); + printf("done\n"); + return 0; +} + +static int test_early_nvram2(struct udevice *dev) +{ + uint32_t x; + + printf("Testing earlynvram2 ..."); + tpm_init(dev); + TPM_CHECK(tpm_startup(dev, TPM_ST_CLEAR)); + TPM_CHECK(tpm_continue_self_test(dev)); + TPM_CHECK(tpm_tsc_physical_presence(dev, PRESENCE)); + TPM_CHECK(tpm_nv_write_value(dev, INDEX0, (uint8_t *)&x, sizeof(x))); + printf("done\n"); + return 0; +} + +static int test_enable(struct udevice *dev) +{ + uint8_t disable = 0, deactivated = 0; + + printf("Testing enable ...\n"); + tpm_init(dev); + TPM_CHECK(TlclStartupIfNeeded(dev)); + TPM_CHECK(tpm_self_test_full(dev)); + TPM_CHECK(tpm_tsc_physical_presence(dev, PRESENCE)); + TPM_CHECK(tpm_get_flags(dev, &disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, deactivated); + TPM_CHECK(tpm_physical_enable(dev)); + TPM_CHECK(tpm_physical_set_deactivated(dev, 0)); + TPM_CHECK(tpm_get_flags(dev, &disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, deactivated); + if (disable == 1 || deactivated == 1) + printf("\tfailed to enable or activate\n"); + printf("\tdone\n"); + return 0; +} + +#define reboot() do { \ + printf("\trebooting...\n"); \ + reset_cpu(); \ +} while (0) + +static int test_fast_enable(struct udevice *dev) +{ + uint8_t disable = 0, deactivated = 0; + int i; + + printf("Testing fastenable ...\n"); + tpm_init(dev); + TPM_CHECK(TlclStartupIfNeeded(dev)); + TPM_CHECK(tpm_self_test_full(dev)); + TPM_CHECK(tpm_tsc_physical_presence(dev, PRESENCE)); + TPM_CHECK(tpm_get_flags(dev, &disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, deactivated); + for (i = 0; i < 2; i++) { + TPM_CHECK(tpm_force_clear(dev)); + TPM_CHECK(tpm_get_flags(dev, &disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, + deactivated); + assert(disable == 1 && deactivated == 1); + TPM_CHECK(tpm_physical_enable(dev)); + TPM_CHECK(tpm_physical_set_deactivated(dev, 0)); + TPM_CHECK(tpm_get_flags(dev, &disable, &deactivated, NULL)); + printf("\tdisable is %d, deactivated is %d\n", disable, + deactivated); + assert(disable == 0 && deactivated == 0); + } + printf("\tdone\n"); + return 0; +} + +static int test_global_lock(struct udevice *dev) +{ + uint32_t zero = 0; + uint32_t result; + uint32_t x; + + printf("Testing globallock ...\n"); + tpm_init(dev); + TPM_CHECK(TlclStartupIfNeeded(dev)); + TPM_CHECK(tpm_self_test_full(dev)); + TPM_CHECK(tpm_tsc_physical_presence(dev, PRESENCE)); + TPM_CHECK(tpm_nv_read_value(dev, INDEX0, (uint8_t *)&x, sizeof(x))); + TPM_CHECK(tpm_nv_write_value(dev, INDEX0, (uint8_t *)&zero, + sizeof(uint32_t))); + TPM_CHECK(tpm_nv_read_value(dev, INDEX1, (uint8_t *)&x, sizeof(x))); + TPM_CHECK(tpm_nv_write_value(dev, INDEX1, (uint8_t *)&zero, + sizeof(uint32_t))); + TPM_CHECK(tpm_set_global_lock(dev)); + /* Verifies that write to index0 fails */ + x = 1; + result = tpm_nv_write_value(dev, INDEX0, (uint8_t *)&x, sizeof(x)); + assert(result == TPM_AREA_LOCKED); + TPM_CHECK(tpm_nv_read_value(dev, INDEX0, (uint8_t *)&x, sizeof(x))); + assert(x == 0); + /* Verifies that write to index1 is still possible */ + x = 2; + TPM_CHECK(tpm_nv_write_value(dev, INDEX1, (uint8_t *)&x, sizeof(x))); + TPM_CHECK(tpm_nv_read_value(dev, INDEX1, (uint8_t *)&x, sizeof(x))); + assert(x == 2); + /* Turns off PP */ + tpm_tsc_physical_presence(dev, PHYS_PRESENCE); + /* Verifies that write to index1 fails */ + x = 3; + result = tpm_nv_write_value(dev, INDEX1, (uint8_t *)&x, sizeof(x)); + assert(result == TPM_BAD_PRESENCE); + TPM_CHECK(tpm_nv_read_value(dev, INDEX1, (uint8_t *)&x, sizeof(x))); + assert(x == 2); + printf("\tdone\n"); + return 0; +} + +static int test_lock(struct udevice *dev) +{ + printf("Testing lock ...\n"); + tpm_init(dev); + tpm_startup(dev, TPM_ST_CLEAR); + tpm_self_test_full(dev); + tpm_tsc_physical_presence(dev, PRESENCE); + tpm_nv_write_value_lock(dev, INDEX0); + printf("\tLocked 0x%x\n", INDEX0); + printf("\tdone\n"); + return 0; +} + +static void initialise_spaces(struct udevice *dev) +{ + uint32_t zero = 0; + uint32_t perm = TPM_NV_PER_WRITE_STCLEAR | TPM_NV_PER_PPWRITE; + + printf("\tInitialising spaces\n"); + tpm1_nv_set_locked(dev); /* useful only the first time */ + tpm1_nv_define_space(dev, INDEX0, perm, 4); + tpm_nv_write_value(dev, INDEX0, (uint8_t *)&zero, 4); + tpm1_nv_define_space(dev, INDEX1, perm, 4); + tpm_nv_write_value(dev, INDEX1, (uint8_t *)&zero, 4); + tpm1_nv_define_space(dev, INDEX2, perm, 4); + tpm_nv_write_value(dev, INDEX2, (uint8_t *)&zero, 4); + tpm1_nv_define_space(dev, INDEX3, perm, 4); + tpm_nv_write_value(dev, INDEX3, (uint8_t *)&zero, 4); + perm = TPM_NV_PER_READ_STCLEAR | TPM_NV_PER_WRITE_STCLEAR | + TPM_NV_PER_PPWRITE; + tpm1_nv_define_space(dev, INDEX_INITIALISED, perm, 1); +} + +static int test_readonly(struct udevice *dev) +{ + uint8_t c; + uint32_t index_0, index_1, index_2, index_3; + int read0, read1, read2, read3; + + printf("Testing readonly ...\n"); + tpm_init(dev); + tpm_startup(dev, TPM_ST_CLEAR); + tpm_self_test_full(dev); + tpm_tsc_physical_presence(dev, PRESENCE); + /* + * Checks if initialisation has completed by trying to read-lock a + * space that's created at the end of initialisation + */ + if (tpm_nv_read_value(dev, INDEX_INITIALISED, &c, 0) == TPM_BADINDEX) { + /* The initialisation did not complete */ + initialise_spaces(dev); + } + + /* Checks if spaces are OK or messed up */ + read0 = tpm_nv_read_value(dev, INDEX0, (uint8_t *)&index_0, + sizeof(index_0)); + read1 = tpm_nv_read_value(dev, INDEX1, (uint8_t *)&index_1, + sizeof(index_1)); + read2 = tpm_nv_read_value(dev, INDEX2, (uint8_t *)&index_2, + sizeof(index_2)); + read3 = tpm_nv_read_value(dev, INDEX3, (uint8_t *)&index_3, + sizeof(index_3)); + if (read0 || read1 || read2 || read3) { + printf("Invalid contents\n"); + return 0; + } + + /* + * Writes space, and locks it. Then attempts to write again. + * I really wish I could use the imperative. + */ + index_0 += 1; + if (tpm_nv_write_value(dev, INDEX0, (uint8_t *)&index_0, + sizeof(index_0)) != + TPM_SUCCESS) { + pr_err("\tcould not write index 0\n"); + } + tpm_nv_write_value_lock(dev, INDEX0); + if (tpm_nv_write_value(dev, INDEX0, (uint8_t *)&index_0, + sizeof(index_0)) == + TPM_SUCCESS) + pr_err("\tindex 0 is not locked\n"); + + printf("\tdone\n"); + return 0; +} + +static int test_redefine_unowned(struct udevice *dev) +{ + uint32_t perm; + uint32_t result; + uint32_t x; + + printf("Testing redefine_unowned ..."); + tpm_init(dev); + TPM_CHECK(TlclStartupIfNeeded(dev)); + TPM_CHECK(tpm_self_test_full(dev)); + TPM_CHECK(tpm_tsc_physical_presence(dev, PRESENCE)); + assert(!tpm_is_owned(dev)); + + /* Ensures spaces exist. */ + TPM_CHECK(tpm_nv_read_value(dev, INDEX0, (uint8_t *)&x, sizeof(x))); + TPM_CHECK(tpm_nv_read_value(dev, INDEX1, (uint8_t *)&x, sizeof(x))); + + /* Redefines spaces a couple of times. */ + perm = TPM_NV_PER_PPWRITE | TPM_NV_PER_GLOBALLOCK; + TPM_CHECK(tpm1_nv_define_space(dev, INDEX0, perm, + 2 * sizeof(uint32_t))); + TPM_CHECK(tpm1_nv_define_space(dev, INDEX0, perm, sizeof(uint32_t))); + perm = TPM_NV_PER_PPWRITE; + TPM_CHECK(tpm1_nv_define_space(dev, INDEX1, perm, + 2 * sizeof(uint32_t))); + TPM_CHECK(tpm1_nv_define_space(dev, INDEX1, perm, sizeof(uint32_t))); + + /* Sets the global lock */ + tpm_set_global_lock(dev); + + /* Verifies that index0 cannot be redefined */ + result = tpm1_nv_define_space(dev, INDEX0, perm, sizeof(uint32_t)); + assert(result == TPM_AREA_LOCKED); + + /* Checks that index1 can */ + TPM_CHECK(tpm1_nv_define_space(dev, INDEX1, perm, + 2 * sizeof(uint32_t))); + TPM_CHECK(tpm1_nv_define_space(dev, INDEX1, perm, sizeof(uint32_t))); + + /* Turns off PP */ + tpm_tsc_physical_presence(dev, PHYS_PRESENCE); + + /* Verifies that neither index0 nor index1 can be redefined */ + result = tpm1_nv_define_space(dev, INDEX0, perm, sizeof(uint32_t)); + assert(result == TPM_BAD_PRESENCE); + result = tpm1_nv_define_space(dev, INDEX1, perm, sizeof(uint32_t)); + assert(result == TPM_BAD_PRESENCE); + + printf("done\n"); + return 0; +} + +#define PERMPPGL (TPM_NV_PER_PPWRITE | TPM_NV_PER_GLOBALLOCK) +#define PERMPP TPM_NV_PER_PPWRITE + +static int test_space_perm(struct udevice *dev) +{ + uint32_t perm; + + printf("Testing spaceperm ..."); + tpm_init(dev); + TPM_CHECK(TlclStartupIfNeeded(dev)); + TPM_CHECK(tpm_continue_self_test(dev)); + TPM_CHECK(tpm_tsc_physical_presence(dev, PRESENCE)); + TPM_CHECK(tpm_get_permissions(dev, INDEX0, &perm)); + assert((perm & PERMPPGL) == PERMPPGL); + TPM_CHECK(tpm_get_permissions(dev, INDEX1, &perm)); + assert((perm & PERMPP) == PERMPP); + printf("done\n"); + return 0; +} + +static int test_startup(struct udevice *dev) +{ + uint32_t result; + + printf("Testing startup ...\n"); + + tpm_init(dev); + result = tpm_startup(dev, TPM_ST_CLEAR); + if (result != 0 && result != TPM_INVALID_POSTINIT) + printf("\ttpm startup failed with 0x%x\n", result); + result = tpm_get_flags(dev, NULL, NULL, NULL); + if (result != 0) + printf("\ttpm getflags failed with 0x%x\n", result); + printf("\texecuting SelfTestFull\n"); + tpm_self_test_full(dev); + result = tpm_get_flags(dev, NULL, NULL, NULL); + if (result != 0) + printf("\ttpm getflags failed with 0x%x\n", result); + printf("\tdone\n"); + return 0; +} + +/* + * Runs [op] and ensures it returns success and doesn't run longer than + * [time_limit] in milliseconds. + */ +#define TTPM_CHECK(op, time_limit) do { \ + ulong start, time; \ + uint32_t __result; \ + \ + start = get_timer(0); \ + __result = op; \ + if (__result != TPM_SUCCESS) { \ + printf("\t" #op ": error 0x%x\n", __result); \ + return -1; \ + } \ + time = get_timer(start); \ + printf("\t" #op ": %lu ms\n", time); \ + if (time > (ulong)time_limit) { \ + printf("\t" #op " exceeded " #time_limit " ms\n"); \ + } \ +} while (0) + +static int test_timing(struct udevice *dev) +{ + uint8_t in[20], out[20]; + uint32_t x; + + printf("Testing timing ..."); + tpm_init(dev); + TTPM_CHECK(TlclStartupIfNeeded(dev), 50); + TTPM_CHECK(tpm_continue_self_test(dev), 100); + TTPM_CHECK(tpm_self_test_full(dev), 1000); + TTPM_CHECK(tpm_tsc_physical_presence(dev, PRESENCE), 100); + TTPM_CHECK(tpm_nv_write_value(dev, INDEX0, (uint8_t *)&x, sizeof(x)), + 100); + TTPM_CHECK(tpm_nv_read_value(dev, INDEX0, (uint8_t *)&x, sizeof(x)), + 100); + TTPM_CHECK(tpm_pcr_extend(dev, 0, in, sizeof(in), out, "test"), 200); + TTPM_CHECK(tpm_set_global_lock(dev), 50); + TTPM_CHECK(tpm_tsc_physical_presence(dev, PHYS_PRESENCE), 100); + printf("done\n"); + return 0; +} + +#define TPM_MAX_NV_WRITES_NOOWNER 64 + +static int test_write_limit(struct udevice *dev) +{ + uint32_t result; + int i; + + printf("Testing writelimit ...\n"); + tpm_init(dev); + TPM_CHECK(TlclStartupIfNeeded(dev)); + TPM_CHECK(tpm_self_test_full(dev)); + TPM_CHECK(tpm_tsc_physical_presence(dev, PRESENCE)); + TPM_CHECK(tpm_force_clear(dev)); + TPM_CHECK(tpm_physical_enable(dev)); + TPM_CHECK(tpm_physical_set_deactivated(dev, 0)); + + for (i = 0; i < TPM_MAX_NV_WRITES_NOOWNER + 2; i++) { + printf("\twriting %d\n", i); + result = tpm_nv_write_value(dev, INDEX0, (uint8_t *)&i, + sizeof(i)); + switch (result) { + case TPM_SUCCESS: + break; + case TPM_MAXNVWRITES: + assert(i >= TPM_MAX_NV_WRITES_NOOWNER); + break; + default: + pr_err("\tunexpected error code %d (0x%x)\n", + result, result); + } + } + + /* Reset write count */ + TPM_CHECK(tpm_force_clear(dev)); + TPM_CHECK(tpm_physical_enable(dev)); + TPM_CHECK(tpm_physical_set_deactivated(dev, 0)); + + /* Try writing again. */ + TPM_CHECK(tpm_nv_write_value(dev, INDEX0, (uint8_t *)&i, sizeof(i))); + printf("\tdone\n"); + return 0; +} + +#define VOIDTEST(XFUNC) \ + int do_test_##XFUNC(struct cmd_tbl *cmd_tbl, int flag, int argc, \ + char *const argv[]) \ + { \ + struct udevice *dev; \ + int ret; \ +\ + ret = get_tpm(&dev); \ + if (ret) \ + return ret; \ + return test_##XFUNC(dev); \ + } + +#define VOIDENT(XNAME) \ + U_BOOT_CMD_MKENT(XNAME, 0, 1, do_test_##XNAME, "", ""), + +VOIDTEST(early_extend) +VOIDTEST(early_nvram) +VOIDTEST(early_nvram2) +VOIDTEST(enable) +VOIDTEST(fast_enable) +VOIDTEST(global_lock) +VOIDTEST(lock) +VOIDTEST(readonly) +VOIDTEST(redefine_unowned) +VOIDTEST(space_perm) +VOIDTEST(startup) +VOIDTEST(timing) +VOIDTEST(write_limit) +VOIDTEST(timer) + +static struct cmd_tbl cmd_cros_tpm_sub[] = { + VOIDENT(early_extend) + VOIDENT(early_nvram) + VOIDENT(early_nvram2) + VOIDENT(enable) + VOIDENT(fast_enable) + VOIDENT(global_lock) + VOIDENT(lock) + VOIDENT(readonly) + VOIDENT(redefine_unowned) + VOIDENT(space_perm) + VOIDENT(startup) + VOIDENT(timing) + VOIDENT(write_limit) + VOIDENT(timer) +}; + +static int do_tpmtest(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cmd_tbl *c; + int i; + + printf("argc = %d, argv = ", argc); + + for (i = 0; i < argc; i++) + printf(" %s", argv[i]); + + printf("\n------\n"); + + argc--; + argv++; + c = find_cmd_tbl(argv[0], cmd_cros_tpm_sub, + ARRAY_SIZE(cmd_cros_tpm_sub)); + return c ? c->cmd(cmdtp, flag, argc, argv) : cmd_usage(cmdtp); +} + +U_BOOT_CMD(tpmtest, 2, 1, do_tpmtest, "TPM tests", + "\n\tearly_extend\n" + "\tearly_nvram\n" + "\tearly_nvram2\n" + "\tenable\n" + "\tfast_enable\n" + "\tglobal_lock\n" + "\tlock\n" + "\treadonly\n" + "\tredefine_unowned\n" + "\tspace_perm\n" + "\tstartup\n" + "\ttiming\n" + "\twrite_limit\n"); diff --git a/cmd/trace.c b/cmd/trace.c new file mode 100644 index 00000000000..d36008720db --- /dev/null +++ b/cmd/trace.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. + */ + +#include <command.h> +#include <env.h> +#include <mapmem.h> +#include <trace.h> +#include <vsprintf.h> +#include <asm/io.h> + +static int get_args(int argc, char *const argv[], char **buff, + size_t *buff_ptr, size_t *buff_size) +{ + if (argc < 2) + return -1; + if (argc < 4) { + *buff_size = env_get_ulong("profsize", 16, 0); + *buff = map_sysmem(env_get_ulong("profbase", 16, 0), + *buff_size); + *buff_ptr = env_get_ulong("profoffset", 16, 0); + } else { + *buff_size = hextoul(argv[3], NULL); + *buff = map_sysmem(hextoul(argv[2], NULL), + *buff_size); + *buff_ptr = 0; + }; + return 0; +} + +static int create_func_list(int argc, char *const argv[]) +{ + size_t buff_size, avail, buff_ptr, needed, used; + char *buff; + int err; + + if (get_args(argc, argv, &buff, &buff_ptr, &buff_size)) + return -1; + + avail = buff_size - buff_ptr; + err = trace_list_functions(buff + buff_ptr, avail, &needed); + if (err) + printf("Error: truncated (%#zx bytes needed)\n", needed); + used = min(avail, (size_t)needed); + printf("Function trace dumped to %08lx, size %#zx\n", + (ulong)map_to_sysmem(buff + buff_ptr), used); + env_set_hex("profbase", map_to_sysmem(buff)); + env_set_hex("profsize", buff_size); + env_set_hex("profoffset", buff_ptr + used); + + return 0; +} + +static int create_call_list(int argc, char *const argv[]) +{ + size_t buff_size, avail, buff_ptr, needed, used; + char *buff; + int err; + + if (get_args(argc, argv, &buff, &buff_ptr, &buff_size)) + return -1; + + avail = buff_size - buff_ptr; + err = trace_list_calls(buff + buff_ptr, avail, &needed); + if (err) + printf("Error: truncated (%#zx bytes needed)\n", needed); + used = min(avail, (size_t)needed); + printf("Call list dumped to %08lx, size %#zx\n", + (ulong)map_to_sysmem(buff + buff_ptr), used); + + env_set_hex("profbase", map_to_sysmem(buff)); + env_set_hex("profsize", buff_size); + env_set_hex("profoffset", buff_ptr + used); + + return 0; +} + +int do_trace(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + const char *cmd = argc < 2 ? NULL : argv[1]; + + if (!cmd) + return cmd_usage(cmdtp); + switch (*cmd) { + case 'p': + trace_set_enabled(0); + break; + case 'c': + if (create_call_list(argc, argv)) + return cmd_usage(cmdtp); + break; + case 'r': + trace_set_enabled(1); + break; + case 'f': + if (create_func_list(argc, argv)) + return cmd_usage(cmdtp); + break; + case 's': + trace_print_stats(); + break; + case 'w': + if (trace_wipe()) + return CMD_RET_FAILURE; + break; + default: + return CMD_RET_USAGE; + } + + return 0; +} + +U_BOOT_CMD( + trace, 4, 1, do_trace, + "trace utility commands", + "stats - display tracing statistics\n" + "trace pause - pause tracing\n" + "trace resume - resume tracing\n" + "trace wipe - wipe traces\n" + "trace funclist [<addr> <size>] - dump function list into buffer\n" + "trace calls [<addr> <size>] " + "- dump function call trace into buffer" +); diff --git a/cmd/ubi.c b/cmd/ubi.c new file mode 100644 index 00000000000..93de6f3aea2 --- /dev/null +++ b/cmd/ubi.c @@ -0,0 +1,921 @@ +/* + * Unsorted Block Image commands + * + * Copyright (C) 2008 Samsung Electronics + * Kyungmin Park <kyungmin.park@samsung.com> + * + * Copyright 2008-2009 Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * 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. + */ + +#include <command.h> +#include <env.h> +#include <exports.h> +#include <led.h> +#include <malloc.h> +#include <memalign.h> +#include <mtd.h> +#include <nand.h> +#include <onenand_uboot.h> +#include <dm/devres.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/partitions.h> +#include <linux/err.h> +#include <ubi_uboot.h> +#include <linux/errno.h> +#include <jffs2/load_kernel.h> +#include <linux/log2.h> + +#undef ubi_msg +#define ubi_msg(fmt, ...) printf("UBI: " fmt "\n", ##__VA_ARGS__) + +/* Private own data */ +static struct ubi_device *ubi; + +#ifdef CONFIG_CMD_UBIFS +#include <ubifs_uboot.h> +#endif + +static void display_volume_info(struct ubi_device *ubi) +{ + int i; + + for (i = 0; i < (ubi->vtbl_slots + 1); i++) { + if (!ubi->volumes[i]) + continue; /* Empty record */ + ubi_dump_vol_info(ubi->volumes[i]); + } +} + +static void display_ubi_info(struct ubi_device *ubi) +{ + ubi_msg("MTD device name: \"%s\"", ubi->mtd->name); + ubi_msg("MTD device size: %llu MiB", ubi->flash_size >> 20); + ubi_msg("physical eraseblock size: %d bytes (%d KiB)", + ubi->peb_size, ubi->peb_size >> 10); + ubi_msg("logical eraseblock size: %d bytes", ubi->leb_size); + ubi_msg("number of good PEBs: %d", ubi->good_peb_count); + ubi_msg("number of bad PEBs: %d", ubi->bad_peb_count); + ubi_msg("smallest flash I/O unit: %d", ubi->min_io_size); + ubi_msg("VID header offset: %d (aligned %d)", + ubi->vid_hdr_offset, ubi->vid_hdr_aloffset); + ubi_msg("data offset: %d", ubi->leb_start); + ubi_msg("max. allowed volumes: %d", ubi->vtbl_slots); + ubi_msg("wear-leveling threshold: %d", CONFIG_MTD_UBI_WL_THRESHOLD); + ubi_msg("number of internal volumes: %d", UBI_INT_VOL_COUNT); + ubi_msg("number of user volumes: %d", + ubi->vol_count - UBI_INT_VOL_COUNT); + ubi_msg("available PEBs: %d", ubi->avail_pebs); + ubi_msg("total number of reserved PEBs: %d", ubi->rsvd_pebs); + ubi_msg("number of PEBs reserved for bad PEB handling: %d", + ubi->beb_rsvd_pebs); + ubi_msg("max/mean erase counter: %d/%d", ubi->max_ec, ubi->mean_ec); +} + +static int ubi_info(int layout) +{ + if (layout) + display_volume_info(ubi); + else + display_ubi_info(ubi); + + return 0; +} + +static int ubi_list(const char *var, int numeric) +{ + size_t namelen, len, size; + char *str, *str2; + int i; + + if (!var) { + for (i = 0; i < (ubi->vtbl_slots + 1); i++) { + if (!ubi->volumes[i]) + continue; + if (ubi->volumes[i]->vol_id >= UBI_INTERNAL_VOL_START) + continue; + printf("%d: %s\n", + ubi->volumes[i]->vol_id, + ubi->volumes[i]->name); + } + return 0; + } + + len = 0; + size = 16; + str = malloc(size); + if (!str) + return 1; + + for (i = 0; i < (ubi->vtbl_slots + 1); i++) { + if (!ubi->volumes[i]) + continue; + if (ubi->volumes[i]->vol_id >= UBI_INTERNAL_VOL_START) + continue; + + if (numeric) + namelen = 10; /* strlen(stringify(INT_MAX)) */ + else + namelen = strlen(ubi->volumes[i]->name); + + if (len + namelen + 1 > size) { + size = roundup_pow_of_two(len + namelen + 1) * 2; + str2 = realloc(str, size); + if (!str2) { + free(str); + return 1; + } + str = str2; + } + + if (len) + str[len++] = ' '; + + if (numeric) { + len += sprintf(str + len, "%d", ubi->volumes[i]->vol_id) + 1; + } else { + memcpy(str + len, ubi->volumes[i]->name, namelen); + len += namelen; + str[len] = 0; + } + } + + env_set(var, str); + free(str); + + return 0; +} + +static int ubi_check_volumename(const struct ubi_volume *vol, char *name) +{ + return strcmp(vol->name, name); +} + +static int ubi_check(char *name) +{ + int i; + + for (i = 0; i < (ubi->vtbl_slots + 1); i++) { + if (!ubi->volumes[i]) + continue; /* Empty record */ + + if (!ubi_check_volumename(ubi->volumes[i], name)) + return 0; + } + + return 1; +} + +static int verify_mkvol_req(const struct ubi_device *ubi, + const struct ubi_mkvol_req *req) +{ + int n, err = EINVAL; + + if (req->bytes < 0 || req->alignment < 0 || req->vol_type < 0 || + req->name_len < 0) + goto bad; + + if ((req->vol_id < 0 || req->vol_id >= ubi->vtbl_slots) && + req->vol_id != UBI_VOL_NUM_AUTO) + goto bad; + + if (req->alignment == 0) + goto bad; + + if (req->bytes == 0) { + printf("No space left in UBI device!\n"); + err = ENOMEM; + goto bad; + } + + if (req->vol_type != UBI_DYNAMIC_VOLUME && + req->vol_type != UBI_STATIC_VOLUME) + goto bad; + + if (req->alignment > ubi->leb_size) + goto bad; + + n = req->alignment % ubi->min_io_size; + if (req->alignment != 1 && n) + goto bad; + + if (req->name_len > UBI_VOL_NAME_MAX) { + printf("Name too long!\n"); + err = ENAMETOOLONG; + goto bad; + } + + return 0; +bad: + return err; +} + +static int ubi_create_vol(char *volume, int64_t size, int dynamic, int vol_id, + bool skipcheck) +{ + struct ubi_mkvol_req req; + int err; + + if (dynamic) + req.vol_type = UBI_DYNAMIC_VOLUME; + else + req.vol_type = UBI_STATIC_VOLUME; + + req.vol_id = vol_id; + req.alignment = 1; + req.bytes = size; + + strcpy(req.name, volume); + req.name_len = strlen(volume); + req.name[req.name_len] = '\0'; + req.flags = 0; + if (skipcheck) + req.flags |= UBI_VOL_SKIP_CRC_CHECK_FLG; + + /* It's duplicated at drivers/mtd/ubi/cdev.c */ + err = verify_mkvol_req(ubi, &req); + if (err) { + printf("verify_mkvol_req failed %d\n", err); + return err; + } + printf("Creating %s volume %s of size %lld\n", + dynamic ? "dynamic" : "static", volume, size); + /* Call real ubi create volume */ + return ubi_create_volume(ubi, &req); +} + +static struct ubi_volume *ubi_find_volume(char *volume) +{ + struct ubi_volume *vol; + int i; + + for (i = 0; i < ubi->vtbl_slots; i++) { + vol = ubi->volumes[i]; + if (vol && !strcmp(vol->name, volume)) + return vol; + } + + printf("Volume %s not found!\n", volume); + return NULL; +} + +static int ubi_remove_vol(char *volume) +{ + int err, reserved_pebs, i; + struct ubi_volume *vol; + + vol = ubi_find_volume(volume); + if (vol == NULL) + return ENODEV; + + printf("Remove UBI volume %s (id %d)\n", vol->name, vol->vol_id); + + if (ubi->ro_mode) { + printf("It's read-only mode\n"); + err = EROFS; + goto out_err; + } + + err = ubi_change_vtbl_record(ubi, vol->vol_id, NULL); + if (err) { + printf("Error changing Vol tabel record err=%x\n", err); + goto out_err; + } + reserved_pebs = vol->reserved_pebs; + for (i = 0; i < vol->reserved_pebs; i++) { + err = ubi_eba_unmap_leb(ubi, vol, i); + if (err) + goto out_err; + } + + kfree(vol->eba_tbl); + ubi->volumes[vol->vol_id]->eba_tbl = NULL; + ubi->volumes[vol->vol_id] = NULL; + + ubi->rsvd_pebs -= reserved_pebs; + ubi->avail_pebs += reserved_pebs; + i = ubi->beb_rsvd_level - ubi->beb_rsvd_pebs; + if (i > 0) { + i = ubi->avail_pebs >= i ? i : ubi->avail_pebs; + ubi->avail_pebs -= i; + ubi->rsvd_pebs += i; + ubi->beb_rsvd_pebs += i; + if (i > 0) + ubi_msg("reserve more %d PEBs", i); + } + ubi->vol_count -= 1; + + return 0; +out_err: + ubi_err(ubi, "cannot remove volume %s, error %d", volume, err); + if (err < 0) + err = -err; + return err; +} + +static int ubi_rename_vol(char *oldname, char *newname) +{ + struct ubi_volume *vol; + struct ubi_rename_entry rename; + struct ubi_volume_desc desc; + struct list_head list; + + vol = ubi_find_volume(oldname); + if (!vol) { + printf("%s: volume %s doesn't exist\n", __func__, oldname); + return ENODEV; + } + + if (!ubi_check(newname)) { + printf("%s: volume %s already exist\n", __func__, newname); + return EINVAL; + } + + printf("Rename UBI volume %s to %s\n", oldname, newname); + + if (ubi->ro_mode) { + printf("%s: ubi device is in read-only mode\n", __func__); + return EROFS; + } + + rename.new_name_len = strlen(newname); + strcpy(rename.new_name, newname); + rename.remove = 0; + desc.vol = vol; + desc.mode = 0; + rename.desc = &desc; + INIT_LIST_HEAD(&rename.list); + INIT_LIST_HEAD(&list); + list_add(&rename.list, &list); + + return ubi_rename_volumes(ubi, &list); +} + +static int ubi_volume_continue_write(char *volume, void *buf, size_t size) +{ + int err; + struct ubi_volume *vol; + + vol = ubi_find_volume(volume); + if (vol == NULL) + return ENODEV; + + if (!vol->updating) { + printf("UBI volume update was not initiated\n"); + return EINVAL; + } + + err = ubi_more_update_data(ubi, vol, buf, size); + if (err < 0) { + printf("Couldnt or partially wrote data\n"); + return -err; + } + + if (err) { + size = err; + + err = ubi_check_volume(ubi, vol->vol_id); + if (err < 0) + return -err; + + if (err) { + ubi_warn(ubi, "volume %d on UBI device %d is corrupt", + vol->vol_id, ubi->ubi_num); + vol->corrupted = 1; + } + + vol->checked = 1; + ubi_gluebi_updated(vol); + } + + return 0; +} + +int ubi_volume_begin_write(char *volume, void *buf, size_t size, + size_t full_size) +{ + int err; + int rsvd_bytes; + struct ubi_volume *vol; + + vol = ubi_find_volume(volume); + if (vol == NULL) + return ENODEV; + + rsvd_bytes = vol->reserved_pebs * (ubi->leb_size - vol->data_pad); + if (size > rsvd_bytes) { + printf("size > volume size! Aborting!\n"); + return EINVAL; + } + + err = ubi_start_update(ubi, vol, full_size); + if (err < 0) { + printf("Cannot start volume update\n"); + return -err; + } + + /* The volume is just wiped out */ + if (!full_size) + return 0; + + return ubi_volume_continue_write(volume, buf, size); +} + +static int ubi_volume_offset_write(char *volume, void *buf, loff_t offset, + size_t size) +{ + int len, tbuf_size, ret; + u64 lnum; + struct ubi_volume *vol; + loff_t off = offset; + void *tbuf; + + vol = ubi_find_volume(volume); + if (!vol) + return -ENODEV; + + if (size > vol->reserved_pebs * (ubi->leb_size - vol->data_pad)) + return -EINVAL; + + tbuf_size = vol->usable_leb_size; + tbuf = malloc_cache_aligned(tbuf_size); + if (!tbuf) + return -ENOMEM; + + lnum = off; + off = do_div(lnum, vol->usable_leb_size); + + do { + struct ubi_volume_desc desc = { + .vol = vol, + .mode = UBI_READWRITE, + }; + + len = size > tbuf_size ? tbuf_size : size; + if (off + len >= vol->usable_leb_size) + len = vol->usable_leb_size - off; + + ret = ubi_read(&desc, (int)lnum, tbuf, 0, tbuf_size); + if (ret) { + pr_err("Failed to read leb %lld (%d)\n", lnum, ret); + goto exit; + } + + memcpy(tbuf + off, buf, len); + + ret = ubi_leb_change(&desc, (int)lnum, tbuf, tbuf_size); + if (ret) { + pr_err("Failed to write leb %lld (%d)\n", lnum, ret); + goto exit; + } + + off += len; + if (off >= vol->usable_leb_size) { + lnum++; + off -= vol->usable_leb_size; + } + + buf += len; + size -= len; + } while (size); + +exit: + free(tbuf); + return ret; +} + +int ubi_volume_write(char *volume, void *buf, loff_t offset, size_t size) +{ + int ret; + + led_activity_blink(); + + if (!offset) + ret = ubi_volume_begin_write(volume, buf, size, size); + else + ret = ubi_volume_offset_write(volume, buf, offset, size); + + led_activity_off(); + + return ret; +} + +int ubi_volume_read(char *volume, char *buf, loff_t offset, size_t size) +{ + int err, lnum, off, len, tbuf_size; + void *tbuf; + unsigned long long tmp; + struct ubi_volume *vol; + loff_t offp = offset; + size_t len_read; + + vol = ubi_find_volume(volume); + if (vol == NULL) + return ENODEV; + + if (vol->updating) { + printf("updating"); + return EBUSY; + } + if (vol->upd_marker) { + printf("damaged volume, update marker is set"); + return EBADF; + } + if (offp == vol->used_bytes) + return 0; + + if (size == 0) { + printf("No size specified -> Using max size (%lld)\n", vol->used_bytes); + size = vol->used_bytes; + } + + printf("Read %zu bytes from volume %s to %p\n", size, volume, buf); + + if (vol->corrupted) + printf("read from corrupted volume %d", vol->vol_id); + if (offp + size > vol->used_bytes) + size = vol->used_bytes - offp; + + tbuf_size = vol->usable_leb_size; + if (size < tbuf_size) + tbuf_size = ALIGN(size, ubi->min_io_size); + tbuf = malloc_cache_aligned(tbuf_size); + if (!tbuf) { + printf("NO MEM\n"); + return ENOMEM; + } + len = size > tbuf_size ? tbuf_size : size; + + led_activity_blink(); + tmp = offp; + off = do_div(tmp, vol->usable_leb_size); + lnum = tmp; + len_read = size; + do { + if (off + len >= vol->usable_leb_size) + len = vol->usable_leb_size - off; + + err = ubi_eba_read_leb(ubi, vol, lnum, tbuf, off, len, 0); + if (err) { + printf("read err %x\n", err); + err = -err; + break; + } + off += len; + if (off == vol->usable_leb_size) { + lnum += 1; + off -= vol->usable_leb_size; + } + + size -= len; + offp += len; + + memcpy(buf, tbuf, len); + + buf += len; + len = size > tbuf_size ? tbuf_size : size; + } while (size); + + if (!size) + env_set_hex("filesize", len_read); + + free(tbuf); + led_activity_off(); + return err; +} + +static int ubi_dev_scan(struct mtd_info *info, const char *vid_header_offset) +{ + char ubi_mtd_param_buffer[80]; + int err; + + if (!vid_header_offset) + sprintf(ubi_mtd_param_buffer, "%s", info->name); + else + sprintf(ubi_mtd_param_buffer, "%s,%s", info->name, + vid_header_offset); + + err = ubi_mtd_param_parse(ubi_mtd_param_buffer, NULL); + if (err) + return -err; + + led_activity_blink(); + err = ubi_init(); + led_activity_off(); + if (err) + return -err; + + return 0; +} + +static int ubi_set_skip_check(char *volume, bool skip_check) +{ + struct ubi_vtbl_record vtbl_rec; + struct ubi_volume *vol; + + vol = ubi_find_volume(volume); + if (!vol) + return ENODEV; + + printf("%sing skip_check on volume %s\n", + skip_check ? "Sett" : "Clear", volume); + + vtbl_rec = ubi->vtbl[vol->vol_id]; + if (skip_check) { + vtbl_rec.flags |= UBI_VTBL_SKIP_CRC_CHECK_FLG; + vol->skip_check = 1; + } else { + vtbl_rec.flags &= ~UBI_VTBL_SKIP_CRC_CHECK_FLG; + vol->skip_check = 0; + } + + return ubi_change_vtbl_record(ubi, vol->vol_id, &vtbl_rec); +} + +static int ubi_detach(void) +{ +#ifdef CONFIG_CMD_UBIFS + /* + * Automatically unmount UBIFS partition when user + * changes the UBI device. Otherwise the following + * UBIFS commands will crash. + */ + if (ubifs_is_mounted()) + cmd_ubifs_umount(); +#endif + + /* + * Call ubi_exit() before re-initializing the UBI subsystem + */ + if (ubi) + ubi_exit(); + + ubi = NULL; + + return 0; +} + +int ubi_part(char *part_name, const char *vid_header_offset) +{ + struct mtd_info *mtd; + int err; + + if (ubi && ubi->mtd && !strcmp(ubi->mtd->name, part_name)) { + printf("UBI partition '%s' already selected\n", part_name); + return 0; + } + + ubi_detach(); + + mtd_probe_devices(); + mtd = get_mtd_device_nm(part_name); + if (IS_ERR(mtd)) { + printf("Partition %s not found!\n", part_name); + return 1; + } + put_mtd_device(mtd); + + err = ubi_dev_scan(mtd, vid_header_offset); + if (err) { + printf("UBI init error %d\n", err); + printf("Please check, if the correct MTD partition is used (size big enough?)\n"); + return err; + } + + ubi = ubi_devices[0]; + + return 0; +} + +static int do_ubi(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int64_t size; + ulong addr = 0; + bool skipcheck = false; + + if (argc < 2) + return CMD_RET_USAGE; + + if (strcmp(argv[1], "detach") == 0) + return ubi_detach(); + + if (strcmp(argv[1], "part") == 0) { + const char *vid_header_offset = NULL; + + /* Print current partition */ + if (argc == 2) { + if (!ubi) { + printf("Error, no UBI device selected!\n"); + return 1; + } + + printf("Device %d: %s, MTD partition %s\n", + ubi->ubi_num, ubi->ubi_name, ubi->mtd->name); + return 0; + } + + if (argc < 3) + return CMD_RET_USAGE; + + if (argc > 3) + vid_header_offset = argv[3]; + + return ubi_part(argv[2], vid_header_offset); + } + + if ((strcmp(argv[1], "part") != 0) && !ubi) { + printf("Error, no UBI device selected!\n"); + return 1; + } + + if (strcmp(argv[1], "info") == 0) { + int layout = 0; + if (argc > 2 && !strncmp(argv[2], "l", 1)) + layout = 1; + return ubi_info(layout); + } + + if (strcmp(argv[1], "list") == 0) { + int numeric = 0; + if (argc >= 3 && argv[2][0] == '-') { + if (strcmp(argv[2], "-numeric") == 0) + numeric = 1; + else + return CMD_RET_USAGE; + } + if (!numeric && argc != 2 && argc != 3) + return CMD_RET_USAGE; + if (numeric && argc != 3 && argc != 4) + return CMD_RET_USAGE; + return ubi_list(argv[numeric ? 3 : 2], numeric); + } + + if (strcmp(argv[1], "check") == 0) { + if (argc > 2) + return ubi_check(argv[2]); + + printf("Error, no volume name passed\n"); + return 1; + } + + if (strncmp(argv[1], "create", 6) == 0) { + int dynamic = 1; /* default: dynamic volume */ + int id = UBI_VOL_NUM_AUTO; + + /* Use maximum available size */ + size = 0; + + /* E.g., create volume with "skipcheck" bit set */ + if (argc == 7) { + skipcheck = strncmp(argv[6], "--skipcheck", 11) == 0; + argc--; + } + + /* E.g., create volume size type vol_id */ + if (argc == 6) { + id = simple_strtoull(argv[5], NULL, 16); + argc--; + } + + /* E.g., create volume size type */ + if (argc == 5) { + if (strncmp(argv[4], "s", 1) == 0) + dynamic = 0; + else if (strncmp(argv[4], "d", 1) != 0) { + printf("Incorrect type\n"); + return 1; + } + argc--; + } + /* E.g., create volume size */ + if (argc == 4) { + if (argv[3][0] != '-') + size = simple_strtoull(argv[3], NULL, 16); + argc--; + } + /* Use maximum available size */ + if (!size) { + size = (int64_t)ubi->avail_pebs * ubi->leb_size; + printf("No size specified -> Using max size (%lld)\n", size); + } + /* E.g., create volume */ + if (argc == 3) { + return ubi_create_vol(argv[2], size, dynamic, id, + skipcheck); + } + } + + if (strncmp(argv[1], "remove", 6) == 0) { + /* E.g., remove volume */ + if (argc == 3) + return ubi_remove_vol(argv[2]); + } + + if (IS_ENABLED(CONFIG_CMD_UBI_RENAME) && !strncmp(argv[1], "rename", 6)) + return ubi_rename_vol(argv[2], argv[3]); + + if (strncmp(argv[1], "skipcheck", 9) == 0) { + /* E.g., change skip_check flag */ + if (argc == 4) { + skipcheck = strncmp(argv[3], "on", 2) == 0; + return ubi_set_skip_check(argv[2], skipcheck); + } + } + + if (strncmp(argv[1], "write", 5) == 0) { + int ret; + + if (argc < 5) { + printf("Please see usage\n"); + return 1; + } + + addr = hextoul(argv[2], NULL); + size = hextoul(argv[4], NULL); + + if (strlen(argv[1]) == 10 && + strncmp(argv[1] + 5, ".part", 5) == 0) { + if (argc < 6) { + ret = ubi_volume_continue_write(argv[3], + (void *)addr, size); + } else { + size_t full_size; + full_size = hextoul(argv[5], NULL); + ret = ubi_volume_begin_write(argv[3], + (void *)addr, size, full_size); + } + } else { + ret = ubi_volume_write(argv[3], (void *)addr, 0, size); + } + if (!ret) { + printf("%lld bytes written to volume %s\n", size, + argv[3]); + } + + return ret; + } + + if (strncmp(argv[1], "read", 4) == 0) { + size = 0; + + /* E.g., read volume size */ + if (argc == 5) { + size = hextoul(argv[4], NULL); + argc--; + } + + /* E.g., read volume */ + if (argc == 4) { + addr = hextoul(argv[2], NULL); + argc--; + } + + if (argc == 3) { + return ubi_volume_read(argv[3], (char *)addr, 0, size); + } + } + + printf("Please see usage\n"); + return 1; +} + +U_BOOT_CMD( + ubi, 7, 1, do_ubi, + "ubi commands", + "detach" + " - detach ubi from a mtd partition\n" + "ubi part [part] [offset]\n" + " - Show or set current partition (with optional VID" + " header offset)\n" + "ubi info [l[ayout]]" + " - Display volume and ubi layout information\n" + "ubi list [flags]" + " - print the list of volumes\n" + "ubi list [flags] <varname>" + " - set environment variable to the list of volumes" + " (flags can be -numeric)\n" + "ubi check volumename" + " - check if volumename exists\n" + "ubi create[vol] volume [size] [type] [id] [--skipcheck]\n" + " - create volume name with size ('-' for maximum" + " available size)\n" + "ubi write[vol] address volume size" + " - Write volume from address with size\n" + "ubi write.part address volume size [fullsize]\n" + " - Write part of a volume from address\n" + "ubi read[vol] address volume [size]" + " - Read volume to address with size\n" + "ubi remove[vol] volume" + " - Remove volume\n" +#if IS_ENABLED(CONFIG_CMD_UBI_RENAME) + "ubi rename oldname newname\n" +#endif + "ubi skipcheck volume on/off - Set or clear skip_check flag in volume header\n" + "[Legends]\n" + " volume: character name\n" + " size: specified in bytes\n" + " type: s[tatic] or d[ynamic] (default=dynamic)" +); diff --git a/cmd/ubifs.c b/cmd/ubifs.c new file mode 100644 index 00000000000..22e95db8ca5 --- /dev/null +++ b/cmd/ubifs.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2008 + * Stefan Roese, DENX Software Engineering, sr@denx.de. + */ + +/* + * UBIFS command support + */ + +#undef DEBUG + +#include <config.h> +#include <command.h> +#include <log.h> +#include <ubifs_uboot.h> +#include <vsprintf.h> + +static int ubifs_initialized; +static int ubifs_mounted; + +int cmd_ubifs_mount(char *vol_name) +{ + int ret; + + debug("Using volume %s\n", vol_name); + + if (ubifs_initialized == 0) { + ubifs_init(); + ubifs_initialized = 1; + } + + ret = uboot_ubifs_mount(vol_name); + if (ret) + return CMD_RET_FAILURE; + + ubifs_mounted = 1; + + return ret; +} + +static int do_ubifs_mount(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *vol_name; + + if (argc != 2) + return CMD_RET_USAGE; + + vol_name = argv[1]; + + return cmd_ubifs_mount(vol_name); +} + +int ubifs_is_mounted(void) +{ + return ubifs_mounted; +} + +int cmd_ubifs_umount(void) +{ + if (ubifs_initialized == 0) { + printf("No UBIFS volume mounted!\n"); + return CMD_RET_FAILURE; + } + + uboot_ubifs_umount(); + ubifs_mounted = 0; + ubifs_initialized = 0; + + return 0; +} + +static int do_ubifs_umount(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc != 1) + return CMD_RET_USAGE; + + return cmd_ubifs_umount(); +} + +static int do_ubifs_ls(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *filename = "/"; + int ret; + + if (!ubifs_mounted) { + printf("UBIFS not mounted, use ubifsmount to mount volume first!\n"); + return CMD_RET_FAILURE; + } + + if (argc == 2) + filename = argv[1]; + debug("Using filename %s\n", filename); + + ret = ubifs_ls(filename); + if (ret) { + printf("** File not found %s **\n", filename); + ret = CMD_RET_FAILURE; + } + + return ret; +} + +static int do_ubifs_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *filename; + char *endp; + int ret; + unsigned long addr; + u32 size = 0; + + if (!ubifs_mounted) { + printf("UBIFS not mounted, use ubifs mount to mount volume first!\n"); + return CMD_RET_FAILURE; + } + + if (argc < 3) + return CMD_RET_USAGE; + + addr = hextoul(argv[1], &endp); + if (endp == argv[1]) + return CMD_RET_USAGE; + + filename = argv[2]; + + if (argc == 4) { + size = hextoul(argv[3], &endp); + if (endp == argv[3]) + return CMD_RET_USAGE; + } + debug("Loading file '%s' to address 0x%08lx (size %d)\n", filename, addr, size); + + ret = ubifs_load(filename, addr, size); + if (ret) { + printf("** File not found %s **\n", filename); + ret = CMD_RET_FAILURE; + } + + return ret; +} + +U_BOOT_CMD( + ubifsmount, 2, 0, do_ubifs_mount, + "mount UBIFS volume", + "<volume-name>\n" + " - mount 'volume-name' volume" +); + +U_BOOT_CMD( + ubifsumount, 1, 0, do_ubifs_umount, + "unmount UBIFS volume", + " - unmount current volume" +); + +U_BOOT_CMD( + ubifsls, 2, 0, do_ubifs_ls, + "list files in a directory", + "[directory]\n" + " - list files in a 'directory' (default '/')" +); + +U_BOOT_CMD( + ubifsload, 4, 0, do_ubifs_load, + "load file from an UBIFS filesystem", + "<addr> <filename> [bytes]\n" + " - load file 'filename' to address 'addr'" +); diff --git a/cmd/ufetch.c b/cmd/ufetch.c new file mode 100644 index 00000000000..bc5db08eee1 --- /dev/null +++ b/cmd/ufetch.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Small "fetch" utility for U-Boot */ + +#ifdef CONFIG_ARM64 +#include <asm/system.h> +#endif +#include <dm/device.h> +#include <dm/uclass-internal.h> +#include <display_options.h> +#include <mmc.h> +#include <time.h> +#include <asm/global_data.h> +#include <cli.h> +#include <command.h> +#include <dm/ofnode.h> +#include <env.h> +#include <rand.h> +#include <vsprintf.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <version.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define LINE_WIDTH 40 +#define BLUE "\033[34m" +#define YELLOW "\033[33m" +#define BOLD "\033[1m" +#define RESET "\033[0m" +static const char * const logo_lines[] = { + BLUE BOLD " ......::...... ", + BLUE BOLD " ...::::::::::::::::::... ", + BLUE BOLD " ..::::::::::::::::::::::::::.. ", + BLUE BOLD " .::::.:::::::::::::::...::::.::::. ", + BLUE BOLD " .::::::::::::::::::::..::::::::::::::. ", + BLUE BOLD " .::.:::::::::::::::::::" YELLOW "=*%#*" BLUE "::::::::::.::. ", + BLUE BOLD " .:::::::::::::::::....." YELLOW "*%%*-" BLUE ":....::::::::::. ", + BLUE BOLD " .:.:::...:::::::::.:-" YELLOW "===##*---==-" BLUE "::::::::::.:. ", + BLUE BOLD " .::::..::::........" YELLOW "-***#****###****-" BLUE "...::::::.:. ", + BLUE BOLD " ::.:.-" YELLOW "+***+=" BLUE "::-" YELLOW "=+**#%%%%%%%%%%%%###*= " BLUE "-::...::::. ", + BLUE BOLD ".:.::-" YELLOW "*****###%%%%%%%%%%%%%%%%%%%%%%%%%%#*=" BLUE ":..:::: ", + BLUE BOLD ".::" YELLOW "##" BLUE ":" YELLOW "***#%%%%%%#####%%%%%%%####%%%%%####%%%*" BLUE "-.::. ", + BLUE BOLD ":.:" YELLOW "#%" BLUE "::" YELLOW "*%%%%%%%#*****##%%%#*****##%%##*****#%%+" BLUE ".::.", + BLUE BOLD ".::" YELLOW "**==#%%%%%%%##****#%%%%##****#%%%%#****###%%" BLUE ":.. ", + BLUE BOLD "..:" YELLOW "#%" BLUE "::" YELLOW "*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#%%%%%+ " BLUE ".:.", + BLUE BOLD " ::" YELLOW "##" BLUE ":" YELLOW "+**#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%* " BLUE "-.:: ", + BLUE BOLD " ..::-" YELLOW "#****#%#%%%%%%%%%%%%%%%%%%%%%%%%%%#*=" BLUE "-..::. ", + BLUE BOLD " ...:=" YELLOW "*****=" BLUE "::-" YELLOW "=+**###%%%%%%%%###**+= " BLUE "--:...::: ", + BLUE BOLD " .::.::--:........::::::--::::::......::::::. ", + BLUE BOLD " .::.....::::::::::...........:::::::::.::. ", + BLUE BOLD " .::::::::::::::::::::::::::::::::::::. ", + BLUE BOLD " .::::.::::::::::::::::::::::.::::. ", + BLUE BOLD " ..::::::::::::::::::::::::::.. ", + BLUE BOLD " ...::::::::::::::::::... ", + BLUE BOLD " ......::...... ", +}; + +enum output_lines { + FIRST, + SECOND, + KERNEL, + SYSINFO, + HOST, + UPTIME, + IP, + CMDS, + CONSOLES, + FEATURES, + RELOCATION, + CORES, + MEMORY, + STORAGE, + + /* Up to 10 storage devices... Should be enough for anyone right? */ + _LAST_LINE = (STORAGE + 10), +#define LAST_LINE (_LAST_LINE - 1UL) +}; + +/* + * TODO/ideas: + * - Refactor to not use a for loop + * - Handle multiple network interfaces + * - Include stats about number of bound/probed devices + * - Show U-Boot's size and malloc usage, fdt size, etc. + */ + + +static int do_ufetch(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int num_lines = max((size_t)LAST_LINE + 1, ARRAY_SIZE(logo_lines)); + const char *model, *compatible; + char *ipaddr; + int n_cmds, n_cpus = 0, compatlen; + size_t size = 0; + ofnode np; + bool skip_ascii = false; + + if (argc > 1 && strcmp(argv[1], "-n") == 0) { + skip_ascii = true; + num_lines = LAST_LINE; + } + + for (int line = 0; line < num_lines; line++) { + if (!skip_ascii) { + if (line < ARRAY_SIZE(logo_lines)) + printf("%s ", logo_lines[line]); + else + printf("%*c ", LINE_WIDTH, ' '); + } + switch (line) { + case FIRST: + compatible = ofnode_read_string(ofnode_root(), "compatible"); + if (!compatible) + compatible = "unknown"; + printf(RESET "%s\n", compatible); + compatlen = strlen(compatible); + break; + case SECOND: + for (int j = 0; j < compatlen; j++) + putc('-'); + putc('\n'); + break; + case KERNEL: + printf("Kernel:" RESET " %s\n", U_BOOT_VERSION); + break; + case SYSINFO: + printf("Config:" RESET " %s_defconfig\n", CONFIG_SYS_CONFIG_NAME); + break; + case HOST: + model = ofnode_read_string(ofnode_root(), "model"); + if (model) + printf("Host:" RESET " %s\n", model); + break; + case UPTIME: + printf("Uptime:" RESET " %ld seconds\n", get_timer(0) / 1000); + break; + case IP: + ipaddr = env_get("ipaddr"); + if (!ipaddr) + ipaddr = "none"; + printf("IP Address:" RESET " %s", ipaddr); + ipaddr = env_get("ipv6addr"); + if (ipaddr) + printf(", %s\n", ipaddr); + else + putc('\n'); + break; + case CMDS: + n_cmds = ll_entry_count(struct cmd_tbl, cmd); + printf("Commands:" RESET " %d (help)\n", n_cmds); + break; + case CONSOLES: + printf("Consoles:" RESET " %s", env_get("stdout")); + if (gd->baudrate) + printf(" (%d baud)", gd->baudrate); + putc('\n'); + break; + case FEATURES: + printf("Features:" RESET " "); + if (IS_ENABLED(CONFIG_NET)) + printf("Net"); + if (IS_ENABLED(CONFIG_EFI_LOADER)) + printf(", EFI"); + if (IS_ENABLED(CONFIG_CMD_CAT)) + printf(", cat :3"); +#ifdef CONFIG_ARM64 + switch (current_el()) { + case 2: + printf(", VMs"); + break; + case 3: + printf(", full control!"); + break; + } +#endif + printf("\n"); + break; + case RELOCATION: + if (gd->flags & GD_FLG_SKIP_RELOC) + printf("Relocated:" RESET " no\n"); + else + printf("Relocated:" RESET " to %#011lx\n", gd->relocaddr); + break; + case CORES: + ofnode_for_each_subnode(np, ofnode_path("/cpus")) { + if (ofnode_name_eq(np, "cpu")) + n_cpus++; + } + printf("CPU: " RESET CONFIG_SYS_ARCH " (%d cores, 1 in use)\n", n_cpus); + break; + case MEMORY: + for (int j = 0; j < CONFIG_NR_DRAM_BANKS && gd->bd->bi_dram[j].size; j++) + size += gd->bd->bi_dram[j].size; + printf("Memory:" RESET " "); + print_size(size, "\n"); + break; + case STORAGE: + default: { +#ifdef CONFIG_BLK + struct udevice *dev; + struct blk_desc *desc; + int ret; + + ret = uclass_find_device_by_seq(UCLASS_BLK, line - STORAGE, &dev); + if (!ret && dev) { + desc = dev_get_uclass_plat(dev); + size = desc->lba * desc->blksz; + printf("%4s %d: " RESET, blk_get_uclass_name(desc->uclass_id), + desc->lun); + if (size) + print_size(size, ""); + else + printf("No media"); + } else if (ret == -ENODEV && (skip_ascii || line > ARRAY_SIZE(logo_lines))) { + break; + } +#endif + printf("\n"); + } + } + } + + printf(RESET "\n\n"); + + return 0; +} + +U_BOOT_CMD(ufetch, 2, 1, do_ufetch, + "U-Boot fetch utility", + "Print information about your device.\n" + " -n Don't print the ASCII logo" +); diff --git a/cmd/ufs.c b/cmd/ufs.c new file mode 100644 index 00000000000..790dab50f18 --- /dev/null +++ b/cmd/ufs.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0+ +/** + * ufs.c - UFS specific U-Boot commands + * + * Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com + * + */ +#include <command.h> +#include <ufs.h> +#include <vsprintf.h> +#include <linux/string.h> + +static int do_ufs(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int dev, ret; + + if (argc >= 2) { + if (!strcmp(argv[1], "init")) { + if (argc == 3) { + dev = dectoul(argv[2], NULL); + ret = ufs_probe_dev(dev); + if (ret) + return CMD_RET_FAILURE; + } else { + ufs_probe(); + } + + return CMD_RET_SUCCESS; + } + } + + return CMD_RET_USAGE; +} + +U_BOOT_CMD(ufs, 3, 1, do_ufs, + "UFS sub-system", + "init [dev] - init UFS subsystem\n" +); diff --git a/cmd/unlz4.c b/cmd/unlz4.c new file mode 100644 index 00000000000..2eadc753e6c --- /dev/null +++ b/cmd/unlz4.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 + * FUJITSU COMPUTERTECHNOLOGIES LIMITED. All rights reserved. + */ + +#include <command.h> +#include <env.h> +#include <mapmem.h> +#include <vsprintf.h> +#include <u-boot/lz4.h> + +static int do_unlz4(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned long src, dst; + size_t src_len = ~0UL, dst_len = ~0UL; + int ret; + + switch (argc) { + case 4: + src = hextoul(argv[1], NULL); + dst = hextoul(argv[2], NULL); + dst_len = hextoul(argv[3], NULL); + break; + default: + return CMD_RET_USAGE; + } + + ret = ulz4fn(map_sysmem(src, 0), src_len, map_sysmem(dst, dst_len), + &dst_len); + if (ret) { + printf("Uncompressed err :%d\n", ret); + return 1; + } + + printf("Uncompressed size: %zd = 0x%zX\n", dst_len, dst_len); + env_set_hex("filesize", dst_len); + + return 0; +} + +U_BOOT_CMD(unlz4, 4, 1, do_unlz4, + "lz4 uncompress a memory region", + "srcaddr dstaddr dstsize\n" + "NOTE: Specify the destination size that is sufficiently larger\n" + " than the source size.\n" +); diff --git a/cmd/unzip.c b/cmd/unzip.c new file mode 100644 index 00000000000..e7a3f9808b2 --- /dev/null +++ b/cmd/unzip.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <env.h> +#include <gzip.h> +#include <mapmem.h> +#include <part.h> +#include <vsprintf.h> + +static int do_unzip(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned long src, dst; + unsigned long src_len = ~0UL, dst_len = ~0UL; + + switch (argc) { + case 4: + dst_len = hextoul(argv[3], NULL); + /* fall through */ + case 3: + src = hextoul(argv[1], NULL); + dst = hextoul(argv[2], NULL); + break; + default: + return CMD_RET_USAGE; + } + + if (gunzip(map_sysmem(dst, dst_len), dst_len, map_sysmem(src, 0), + &src_len) != 0) + return 1; + + printf("Uncompressed size: %lu = 0x%lX\n", src_len, src_len); + env_set_hex("filesize", src_len); + + return 0; +} + +U_BOOT_CMD( + unzip, 4, 1, do_unzip, + "unzip a memory region", + "srcaddr dstaddr [dstsize]" +); + +static int do_gzwrite(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct blk_desc *bdev; + int ret; + unsigned char *addr; + unsigned long length; + unsigned long writebuf = 1<<20; + u64 startoffs = 0; + u64 szexpected = 0; + + if (argc < 5) + return CMD_RET_USAGE; + ret = blk_get_device_by_str(argv[1], argv[2], &bdev); + if (ret < 0) + return CMD_RET_FAILURE; + + addr = (unsigned char *)hextoul(argv[3], NULL); + length = hextoul(argv[4], NULL); + + if (5 < argc) { + writebuf = hextoul(argv[5], NULL); + if (6 < argc) { + startoffs = simple_strtoull(argv[6], NULL, 16); + if (7 < argc) + szexpected = simple_strtoull(argv[7], + NULL, 16); + } + } + + ret = gzwrite(addr, length, bdev, writebuf, startoffs, szexpected); + + return ret ? CMD_RET_FAILURE : CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + gzwrite, 8, 0, do_gzwrite, + "unzip and write memory to block device", + "<interface> <dev> <addr> length [wbuf=1M [offs=0 [outsize=0]]]\n" + "\twbuf is the size in bytes (hex) of write buffer\n" + "\t\tand should be padded to erase size for SSDs\n" + "\toffs is the output start offset in bytes (hex)\n" + "\toutsize is the size of the expected output (hex bytes)\n" + "\t\tand is required for files with uncompressed lengths\n" + "\t\t4 GiB or larger\n" +); diff --git a/cmd/upl.c b/cmd/upl.c new file mode 100644 index 00000000000..ef2183d8528 --- /dev/null +++ b/cmd/upl.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Commands for UPL handoff generation + * + * Copyright 2024 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <abuf.h> +#include <alist.h> +#include <command.h> +#include <display_options.h> +#include <env.h> +#include <mapmem.h> +#include <string.h> +#include <upl.h> +#include <dm/ofnode.h> +#include <test/ut.h> + +DECLARE_GLOBAL_DATA_PTR; + +static int do_upl_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const struct upl *upl = gd_upl(); + + printf("UPL state: %sactive\n", upl ? "" : "in"); + if (!upl) + return 0; + if (argc > 1 && !strcmp("-v", argv[1])) { + int i; + + printf("fit %lx\n", upl->fit); + printf("conf_offset %x\n", upl->conf_offset); + for (i = 0; i < upl->image.count; i++) { + const struct upl_image *img = + alist_get(&upl->image, i, struct upl_image); + + printf("image %d: load %lx size %lx offset %x: %s\n", i, + img->load, img->size, img->offset, + img->description); + } + } + + return 0; +} + +static int do_upl_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct upl s_upl, *upl = &s_upl; + struct unit_test_state uts = { 0 }; + struct abuf buf; + oftree tree; + ulong addr; + int ret; + + upl_get_test_data(&uts, upl); + + log_debug("Writing UPL\n"); + ret = upl_create_handoff_tree(upl, &tree); + if (ret) { + log_err("Failed to write (err=%dE)\n", ret); + return CMD_RET_FAILURE; + } + + log_debug("Flattening\n"); + ret = oftree_to_fdt(tree, &buf); + if (ret) { + log_err("Failed to write (err=%dE)\n", ret); + return CMD_RET_FAILURE; + } + addr = map_to_sysmem(abuf_data(&buf)); + printf("UPL handoff written to %lx size %zx\n", addr, abuf_size(&buf)); + if (env_set_hex("upladdr", addr) || + env_set_hex("uplsize", abuf_size(&buf))) { + printf("Cannot set env var\n"); + return CMD_RET_FAILURE; + } + + log_debug("done\n"); + + return 0; +} + +static int do_upl_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct upl s_upl, *upl = &s_upl; + oftree tree; + ulong addr; + int ret; + + if (argc < 1) + return CMD_RET_USAGE; + addr = hextoul(argv[1], NULL); + + printf("Reading UPL at %lx\n", addr); + tree = oftree_from_fdt(map_sysmem(addr, 0)); + ret = upl_read_handoff(upl, tree); + if (ret) { + log_err("Failed to read (err=%dE)\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +U_BOOT_LONGHELP(upl, + "info [-v] - Check UPL status\n" + "upl read <addr> - Read handoff information\n" + "upl write - Write handoff information"); + +U_BOOT_CMD_WITH_SUBCMDS(upl, "Universal Payload support", upl_help_text, + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_upl_info), + U_BOOT_SUBCMD_MKENT(read, 2, 1, do_upl_read), + U_BOOT_SUBCMD_MKENT(write, 1, 1, do_upl_write)); diff --git a/cmd/usb.c b/cmd/usb.c new file mode 100644 index 00000000000..13a2996c1f0 --- /dev/null +++ b/cmd/usb.c @@ -0,0 +1,725 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2001 + * Denis Peter, MPL AG Switzerland + * + * Adapted for U-Boot driver model + * (C) Copyright 2015 Google, Inc + * + * Most of this source has been derived from the Linux USB + * project. + */ + +#include <blk.h> +#include <bootstage.h> +#include <command.h> +#include <console.h> +#include <dm.h> +#include <dm/uclass-internal.h> +#include <memalign.h> +#include <asm/byteorder.h> +#include <asm/unaligned.h> +#include <part.h> +#include <usb.h> + +#ifdef CONFIG_USB_STORAGE +static int usb_stor_curr_dev = -1; /* current device */ +#endif + +/* some display routines (info command) */ +static char *usb_get_class_desc(unsigned char dclass) +{ + switch (dclass) { + case USB_CLASS_PER_INTERFACE: + return "See Interface"; + case USB_CLASS_AUDIO: + return "Audio"; + case USB_CLASS_COMM: + return "Communication"; + case USB_CLASS_HID: + return "Human Interface"; + case USB_CLASS_PRINTER: + return "Printer"; + case USB_CLASS_MASS_STORAGE: + return "Mass Storage"; + case USB_CLASS_HUB: + return "Hub"; + case USB_CLASS_DATA: + return "CDC Data"; + case USB_CLASS_VENDOR_SPEC: + return "Vendor specific"; + default: + return ""; + } +} + +static void usb_display_class_sub(unsigned char dclass, unsigned char subclass, + unsigned char proto) +{ + switch (dclass) { + case USB_CLASS_PER_INTERFACE: + printf("See Interface"); + break; + case USB_CLASS_HID: + printf("Human Interface, Subclass: "); + switch (subclass) { + case USB_SUB_HID_NONE: + printf("None"); + break; + case USB_SUB_HID_BOOT: + printf("Boot "); + switch (proto) { + case USB_PROT_HID_NONE: + printf("None"); + break; + case USB_PROT_HID_KEYBOARD: + printf("Keyboard"); + break; + case USB_PROT_HID_MOUSE: + printf("Mouse"); + break; + default: + printf("reserved"); + break; + } + break; + default: + printf("reserved"); + break; + } + break; + case USB_CLASS_MASS_STORAGE: + printf("Mass Storage, "); + switch (subclass) { + case US_SC_RBC: + printf("RBC "); + break; + case US_SC_8020: + printf("SFF-8020i (ATAPI)"); + break; + case US_SC_QIC: + printf("QIC-157 (Tape)"); + break; + case US_SC_UFI: + printf("UFI"); + break; + case US_SC_8070: + printf("SFF-8070"); + break; + case US_SC_SCSI: + printf("Transp. SCSI"); + break; + default: + printf("reserved"); + break; + } + printf(", "); + switch (proto) { + case US_PR_CB: + printf("Command/Bulk"); + break; + case US_PR_CBI: + printf("Command/Bulk/Int"); + break; + case US_PR_BULK: + printf("Bulk only"); + break; + default: + printf("reserved"); + break; + } + break; + default: + printf("%s", usb_get_class_desc(dclass)); + break; + } +} + +static void usb_display_string(struct usb_device *dev, int index) +{ + ALLOC_CACHE_ALIGN_BUFFER(char, buffer, 256); + + if (index != 0) { + if (usb_string(dev, index, &buffer[0], 256) > 0) + printf("String: \"%s\"", buffer); + } +} + +static void usb_display_desc(struct usb_device *dev) +{ + uint packet_size = dev->descriptor.bMaxPacketSize0; + + if (dev->descriptor.bDescriptorType == USB_DT_DEVICE) { + printf("%d: %s, USB Revision %x.%x\n", dev->devnum, + usb_get_class_desc(dev->config.if_desc[0].desc.bInterfaceClass), + (dev->descriptor.bcdUSB>>8) & 0xff, + dev->descriptor.bcdUSB & 0xff); + + if (strlen(dev->mf) || strlen(dev->prod) || + strlen(dev->serial)) + printf(" - %s %s %s\n", dev->mf, dev->prod, + dev->serial); + if (dev->descriptor.bDeviceClass) { + printf(" - Class: "); + usb_display_class_sub(dev->descriptor.bDeviceClass, + dev->descriptor.bDeviceSubClass, + dev->descriptor.bDeviceProtocol); + printf("\n"); + } else { + printf(" - Class: (from Interface) %s\n", + usb_get_class_desc( + dev->config.if_desc[0].desc.bInterfaceClass)); + } + if (dev->descriptor.bcdUSB >= cpu_to_le16(0x0300)) + packet_size = 1 << packet_size; + printf(" - PacketSize: %d Configurations: %d\n", + packet_size, dev->descriptor.bNumConfigurations); + printf(" - Vendor: 0x%04x Product 0x%04x Version %d.%d\n", + dev->descriptor.idVendor, dev->descriptor.idProduct, + (dev->descriptor.bcdDevice>>8) & 0xff, + dev->descriptor.bcdDevice & 0xff); + } + +} + +static void usb_display_conf_desc(struct usb_config_descriptor *config, + struct usb_device *dev) +{ + printf(" Configuration: %d\n", config->bConfigurationValue); + printf(" - Interfaces: %d %s%s%dmA\n", config->bNumInterfaces, + (config->bmAttributes & 0x40) ? "Self Powered " : "Bus Powered ", + (config->bmAttributes & 0x20) ? "Remote Wakeup " : "", + config->bMaxPower*2); + if (config->iConfiguration) { + printf(" - "); + usb_display_string(dev, config->iConfiguration); + printf("\n"); + } +} + +static void usb_display_if_desc(struct usb_interface_descriptor *ifdesc, + struct usb_device *dev) +{ + printf(" Interface: %d\n", ifdesc->bInterfaceNumber); + printf(" - Alternate Setting %d, Endpoints: %d\n", + ifdesc->bAlternateSetting, ifdesc->bNumEndpoints); + printf(" - Class "); + usb_display_class_sub(ifdesc->bInterfaceClass, + ifdesc->bInterfaceSubClass, ifdesc->bInterfaceProtocol); + printf("\n"); + if (ifdesc->iInterface) { + printf(" - "); + usb_display_string(dev, ifdesc->iInterface); + printf("\n"); + } +} + +static void usb_display_ep_desc(struct usb_endpoint_descriptor *epdesc) +{ + printf(" - Endpoint %d %s ", epdesc->bEndpointAddress & 0xf, + (epdesc->bEndpointAddress & 0x80) ? "In" : "Out"); + switch ((epdesc->bmAttributes & 0x03)) { + case 0: + printf("Control"); + break; + case 1: + printf("Isochronous"); + break; + case 2: + printf("Bulk"); + break; + case 3: + printf("Interrupt"); + break; + } + printf(" MaxPacket %d", get_unaligned(&epdesc->wMaxPacketSize)); + if ((epdesc->bmAttributes & 0x03) == 0x3) + printf(" Interval %dms", epdesc->bInterval); + printf("\n"); +} + +/* main routine to diasplay the configs, interfaces and endpoints */ +static void usb_display_config(struct usb_device *dev) +{ + struct usb_config *config; + struct usb_interface *ifdesc; + struct usb_endpoint_descriptor *epdesc; + int i, ii; + + config = &dev->config; + usb_display_conf_desc(&config->desc, dev); + for (i = 0; i < config->no_of_if; i++) { + ifdesc = &config->if_desc[i]; + usb_display_if_desc(&ifdesc->desc, dev); + for (ii = 0; ii < ifdesc->no_of_ep; ii++) { + epdesc = &ifdesc->ep_desc[ii]; + usb_display_ep_desc(epdesc); + } + } + printf("\n"); +} + +/* + * With driver model this isn't right since we can have multiple controllers + * and the device numbering starts at 1 on each bus. + * TODO(sjg@chromium.org): Add a way to specify the controller/bus. + */ +static struct usb_device *usb_find_device(int devnum) +{ +#ifdef CONFIG_DM_USB + struct usb_device *udev; + struct udevice *hub; + struct uclass *uc; + int ret; + + /* Device addresses start at 1 */ + devnum++; + ret = uclass_get(UCLASS_USB_HUB, &uc); + if (ret) + return NULL; + + uclass_foreach_dev(hub, uc) { + struct udevice *dev; + + if (!device_active(hub)) + continue; + udev = dev_get_parent_priv(hub); + if (udev->devnum == devnum) + return udev; + + for (device_find_first_child(hub, &dev); + dev; + device_find_next_child(&dev)) { + if (!device_active(hub)) + continue; + + udev = dev_get_parent_priv(dev); + if (udev->devnum == devnum) + return udev; + } + } +#else + struct usb_device *udev; + int d; + + for (d = 0; d < USB_MAX_DEVICE; d++) { + udev = usb_get_dev_index(d); + if (udev == NULL) + return NULL; + if (udev->devnum == devnum) + return udev; + } +#endif + + return NULL; +} + +static inline const char *portspeed(int speed) +{ + switch (speed) { + case USB_SPEED_SUPER: + return "5 Gb/s"; + case USB_SPEED_HIGH: + return "480 Mb/s"; + case USB_SPEED_LOW: + return "1.5 Mb/s"; + default: + return "12 Mb/s"; + } +} + +/* shows the device tree recursively */ +static void usb_show_tree_graph(struct usb_device *dev, char *pre) +{ + int index; + int has_child, last_child; + + index = strlen(pre); + printf(" %s", pre); +#ifdef CONFIG_DM_USB + has_child = device_has_active_children(dev->dev); + if (device_get_uclass_id(dev->dev) == UCLASS_MASS_STORAGE) { + struct udevice *child; + + for (device_find_first_child(dev->dev, &child); + child; + device_find_next_child(&child)) { + if (device_get_uclass_id(child) == UCLASS_BLK) + has_child = 0; + } + } +#else + /* check if the device has connected children */ + int i; + + has_child = 0; + for (i = 0; i < dev->maxchild; i++) { + if (dev->children[i] != NULL) + has_child = 1; + } +#endif + /* check if we are the last one */ +#ifdef CONFIG_DM_USB + /* Not the root of the usb tree? */ + if (device_get_uclass_id(dev->dev->parent) != UCLASS_USB) { + last_child = device_is_last_sibling(dev->dev); +#else + if (dev->parent != NULL) { /* not root? */ + last_child = 1; + for (i = 0; i < dev->parent->maxchild; i++) { + /* search for children */ + if (dev->parent->children[i] == dev) { + /* found our pointer, see if we have a + * little sister + */ + while (i++ < dev->parent->maxchild) { + if (dev->parent->children[i] != NULL) { + /* found a sister */ + last_child = 0; + break; + } /* if */ + } /* while */ + } /* device found */ + } /* for all children of the parent */ +#endif + printf("\b+-"); + /* correct last child */ + if (last_child && index) + pre[index-1] = ' '; + } /* if not root hub */ + else + printf(" "); + printf("%d ", dev->devnum); + pre[index++] = ' '; + pre[index++] = has_child ? '|' : ' '; + pre[index] = 0; + printf(" %s (%s, %dmA)\n", usb_get_class_desc( + dev->config.if_desc[0].desc.bInterfaceClass), + portspeed(dev->speed), + dev->config.desc.bMaxPower * 2); + if (strlen(dev->mf) || strlen(dev->prod) || strlen(dev->serial)) + printf(" %s %s %s %s\n", pre, dev->mf, dev->prod, dev->serial); + printf(" %s\n", pre); +#ifdef CONFIG_DM_USB + struct udevice *child; + + for (device_find_first_child(dev->dev, &child); + child; + device_find_next_child(&child)) { + struct usb_device *udev; + + if (!device_active(child)) + continue; + + udev = dev_get_parent_priv(child); + + /* + * Ignore emulators and block child devices, we only want + * real devices + */ + if (udev && + (device_get_uclass_id(child) != UCLASS_BOOTDEV) && + (device_get_uclass_id(child) != UCLASS_USB_EMUL) && + (device_get_uclass_id(child) != UCLASS_BLK)) { + usb_show_tree_graph(udev, pre); + pre[index] = 0; + } + } +#else + if (dev->maxchild > 0) { + for (i = 0; i < dev->maxchild; i++) { + if (dev->children[i] != NULL) { + usb_show_tree_graph(dev->children[i], pre); + pre[index] = 0; + } + } + } +#endif +} + +/* main routine for the tree command */ +static void usb_show_subtree(struct usb_device *dev) +{ + char preamble[32]; + + memset(preamble, '\0', sizeof(preamble)); + usb_show_tree_graph(dev, &preamble[0]); +} + +#ifdef CONFIG_DM_USB +typedef void (*usb_dev_func_t)(struct usb_device *udev); + +static void usb_for_each_root_dev(usb_dev_func_t func) +{ + struct udevice *bus; + + for (uclass_find_first_device(UCLASS_USB, &bus); + bus; + uclass_find_next_device(&bus)) { + struct usb_device *udev; + struct udevice *dev; + + if (!device_active(bus)) + continue; + + device_find_first_child(bus, &dev); + if (dev && device_active(dev)) { + udev = dev_get_parent_priv(dev); + func(udev); + } + } +} +#endif + +void usb_show_tree(void) +{ +#ifdef CONFIG_DM_USB + usb_for_each_root_dev(usb_show_subtree); +#else + struct usb_device *udev; + int i; + + for (i = 0; i < USB_MAX_DEVICE; i++) { + udev = usb_get_dev_index(i); + if (udev == NULL) + break; + if (udev->parent == NULL) + usb_show_subtree(udev); + } +#endif +} + +static int usb_test(struct usb_device *dev, int port, char* arg) +{ + int mode; + + if (port > dev->maxchild) { + printf("Device is no hub or does not have %d ports.\n", port); + return 1; + } + + switch (arg[0]) { + case 'J': + case 'j': + printf("Setting Test_J mode"); + mode = USB_TEST_MODE_J; + break; + case 'K': + case 'k': + printf("Setting Test_K mode"); + mode = USB_TEST_MODE_K; + break; + case 'S': + case 's': + printf("Setting Test_SE0_NAK mode"); + mode = USB_TEST_MODE_SE0_NAK; + break; + case 'P': + case 'p': + printf("Setting Test_Packet mode"); + mode = USB_TEST_MODE_PACKET; + break; + case 'F': + case 'f': + printf("Setting Test_Force_Enable mode"); + mode = USB_TEST_MODE_FORCE_ENABLE; + break; + default: + printf("Unrecognized test mode: %s\nAvailable modes: " + "J, K, S[E0_NAK], P[acket], F[orce_Enable]\n", arg); + return 1; + } + + if (port) + printf(" on downstream facing port %d...\n", port); + else + printf(" on upstream facing port...\n"); + + if (usb_control_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_FEATURE, + port ? USB_RT_PORT : USB_RECIP_DEVICE, + port ? USB_PORT_FEAT_TEST : USB_FEAT_TEST, + (mode << 8) | port, + NULL, 0, USB_CNTL_TIMEOUT) == -1) { + printf("Error during SET_FEATURE.\n"); + return 1; + } else { + printf("Test mode successfully set. Use 'usb start' " + "to return to normal operation.\n"); + return 0; + } +} + +/****************************************************************************** + * usb boot command intepreter. Derived from diskboot + */ +#ifdef CONFIG_USB_STORAGE +static int do_usbboot(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + return common_diskboot(cmdtp, "usb", argc, argv); +} +#endif /* CONFIG_USB_STORAGE */ + +static void do_usb_start(void) +{ + bootstage_mark_name(BOOTSTAGE_ID_USB_START, "usb_start"); + + if (usb_init() < 0) + return; + + /* Driver model will probe the devices as they are found */ +# ifdef CONFIG_USB_STORAGE + /* try to recognize storage devices immediately */ + usb_stor_curr_dev = usb_stor_scan(1); +# endif +} + +#ifdef CONFIG_DM_USB +static void usb_show_info(struct usb_device *udev) +{ + struct udevice *child; + + usb_display_desc(udev); + usb_display_config(udev); + for (device_find_first_child(udev->dev, &child); + child; + device_find_next_child(&child)) { + if (device_active(child) && + (device_get_uclass_id(child) != UCLASS_BOOTDEV) && + (device_get_uclass_id(child) != UCLASS_USB_EMUL) && + (device_get_uclass_id(child) != UCLASS_BLK)) { + udev = dev_get_parent_priv(child); + if (udev) + usb_show_info(udev); + } + } +} +#endif + +/****************************************************************************** + * usb command intepreter + */ +static int do_usb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct usb_device *udev = NULL; + int i; + + if (argc < 2) + return CMD_RET_USAGE; + + if (strncmp(argv[1], "start", 5) == 0) { + if (usb_started) + return 0; /* Already started */ + printf("starting USB...\n"); + do_usb_start(); + return 0; + } + + if (strncmp(argv[1], "reset", 5) == 0) { + printf("resetting USB...\n"); + usb_stop(); + do_usb_start(); + return 0; + } + if (strncmp(argv[1], "stop", 4) == 0) { + if (argc != 2) + console_assign(stdin, "serial"); + printf("stopping USB..\n"); + usb_stop(); + return 0; + } + if (!usb_started) { + printf("USB is stopped. Please issue 'usb start' first.\n"); + return 1; + } + if (strncmp(argv[1], "tree", 4) == 0) { + puts("USB device tree:\n"); + usb_show_tree(); + return 0; + } + if (strncmp(argv[1], "inf", 3) == 0) { + if (argc == 2) { +#ifdef CONFIG_DM_USB + usb_for_each_root_dev(usb_show_info); +#else + int d; + for (d = 0; d < USB_MAX_DEVICE; d++) { + udev = usb_get_dev_index(d); + if (udev == NULL) + break; + usb_display_desc(udev); + usb_display_config(udev); + } +#endif + return 0; + } else { + /* + * With driver model this isn't right since we can + * have multiple controllers and the device numbering + * starts at 1 on each bus. + */ + i = dectoul(argv[2], NULL); + printf("config for device %d\n", i); + udev = usb_find_device(i); + if (udev == NULL) { + printf("*** No device available ***\n"); + return 0; + } else { + usb_display_desc(udev); + usb_display_config(udev); + } + } + return 0; + } + if (strncmp(argv[1], "test", 4) == 0) { + if (argc < 5) + return CMD_RET_USAGE; + i = dectoul(argv[2], NULL); + udev = usb_find_device(i); + if (udev == NULL) { + printf("Device %d does not exist.\n", i); + return 1; + } + i = dectoul(argv[3], NULL); + return usb_test(udev, i, argv[4]); + } +#ifdef CONFIG_USB_STORAGE + if (strncmp(argv[1], "stor", 4) == 0) + return usb_stor_info(); + + return blk_common_cmd(argc, argv, UCLASS_USB, &usb_stor_curr_dev); +#else + return CMD_RET_USAGE; +#endif /* CONFIG_USB_STORAGE */ +} + +U_BOOT_CMD( + usb, 5, 1, do_usb, + "USB sub-system", + "start - start (scan) USB controller\n" + "usb reset - reset (rescan) USB controller\n" + "usb stop [f] - stop USB [f]=force stop\n" + "usb tree - show USB device tree\n" + "usb info [dev] - show available USB devices\n" + "usb test [dev] [port] [mode] - set USB 2.0 test mode\n" + " (specify port 0 to indicate the device's upstream port)\n" + " Available modes: J, K, S[E0_NAK], P[acket], F[orce_Enable]\n" +#ifdef CONFIG_USB_STORAGE + "usb storage - show details of USB storage devices\n" + "usb dev [dev] - show or set current USB storage device\n" + "usb part [dev] - print partition table of one or all USB storage" + " devices\n" + "usb read addr blk# cnt - read `cnt' blocks starting at block `blk#'\n" + " to memory address `addr'\n" + "usb write addr blk# cnt - write `cnt' blocks starting at block `blk#'\n" + " from memory address `addr'" +#endif /* CONFIG_USB_STORAGE */ +); + +#ifdef CONFIG_USB_STORAGE +U_BOOT_CMD( + usbboot, 3, 1, do_usbboot, + "boot from USB device", + "loadAddr dev:part" +); +#endif /* CONFIG_USB_STORAGE */ diff --git a/cmd/usb_gadget_sdp.c b/cmd/usb_gadget_sdp.c new file mode 100644 index 00000000000..39259a3b092 --- /dev/null +++ b/cmd/usb_gadget_sdp.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * cmd_sdp.c -- sdp command + * + * Copyright (C) 2016 Toradex + * Author: Stefan Agner <stefan.agner@toradex.com> + */ + +#include <command.h> +#include <g_dnl.h> +#include <sdp.h> +#include <usb.h> +#include <linux/printk.h> + +static int do_sdp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + int controller_index; + struct udevice *udc; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + controller_index = simple_strtoul(argv[1], NULL, 0); + ret = udc_device_get_by_index(controller_index, &udc); + if (ret) + return ret; + + g_dnl_clear_detach(); + ret = g_dnl_register("usb_dnl_sdp"); + if (ret) { + pr_err("SDP dnl register failed: %d\n", ret); + goto exit_register; + } + + ret = sdp_init(udc); + if (ret) { + pr_err("SDP init failed: %d\n", ret); + goto exit; + } + + /* This command typically does not return but jumps to an image */ + sdp_handle(udc); + pr_err("SDP ended\n"); + +exit: + g_dnl_unregister(); +exit_register: + udc_device_put(udc); + + return CMD_RET_FAILURE; +} + +U_BOOT_CMD(sdp, 2, 1, do_sdp, + "Serial Downloader Protocol", + "<USB_controller>\n" + " - serial downloader protocol via <USB_controller>\n" +); diff --git a/cmd/usb_mass_storage.c b/cmd/usb_mass_storage.c new file mode 100644 index 00000000000..47e8b70cd10 --- /dev/null +++ b/cmd/usb_mass_storage.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2011 Samsung Electronics + * Lukasz Majewski <l.majewski@samsung.com> + * + * Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved. + */ + +#include <blk.h> +#include <command.h> +#include <console.h> +#include <errno.h> +#include <g_dnl.h> +#include <malloc.h> +#include <part.h> +#include <usb.h> +#include <usb_mass_storage.h> +#include <watchdog.h> +#include <linux/delay.h> +#include <linux/printk.h> + +static int ums_read_sector(struct ums *ums_dev, + ulong start, lbaint_t blkcnt, void *buf) +{ + struct blk_desc *block_dev = &ums_dev->block_dev; + lbaint_t blkstart = start + ums_dev->start_sector; + + return blk_dread(block_dev, blkstart, blkcnt, buf); +} + +static int ums_write_sector(struct ums *ums_dev, + ulong start, lbaint_t blkcnt, const void *buf) +{ + struct blk_desc *block_dev = &ums_dev->block_dev; + lbaint_t blkstart = start + ums_dev->start_sector; + + return blk_dwrite(block_dev, blkstart, blkcnt, buf); +} + +static struct ums *ums; +static int ums_count; + +static void ums_fini(void) +{ + int i; + + for (i = 0; i < ums_count; i++) + free((void *)ums[i].name); + free(ums); + ums = NULL; + ums_count = 0; +} + +#define UMS_NAME_LEN 16 + +static int ums_init(const char *devtype, const char *devnums_part_str) +{ + char *s, *t, *devnum_part_str, *name; + struct blk_desc *block_dev; + struct disk_partition info; + int partnum; + int ret = -1; + struct ums *ums_new; + + s = strdup(devnums_part_str); + if (!s) + return -1; + + t = s; + ums_count = 0; + + for (;;) { + devnum_part_str = strsep(&t, ","); + if (!devnum_part_str) + break; + + partnum = part_get_info_by_dev_and_name_or_num(devtype, devnum_part_str, + &block_dev, &info, 1); + + if (partnum < 0) + goto cleanup; + + /* Check if the argument is in legacy format. If yes, + * expose all partitions by setting the partnum = 0 + * e.g. ums 0 mmc 0 + */ + if (!strchr(devnum_part_str, ':')) + partnum = 0; + + ums_new = realloc(ums, (ums_count + 1) * sizeof(*ums)); + if (!ums_new) + goto cleanup; + ums = ums_new; + + /* if partnum = 0, expose all partitions */ + if (partnum == 0) { + ums[ums_count].start_sector = 0; + ums[ums_count].num_sectors = block_dev->lba; + } else { + ums[ums_count].start_sector = info.start; + ums[ums_count].num_sectors = info.size; + } + + ums[ums_count].read_sector = ums_read_sector; + ums[ums_count].write_sector = ums_write_sector; + + name = malloc(UMS_NAME_LEN); + if (!name) + goto cleanup; + snprintf(name, UMS_NAME_LEN, "UMS disk %d", ums_count); + ums[ums_count].name = name; + ums[ums_count].block_dev = *block_dev; + + printf("UMS: LUN %d, dev %s %d, hwpart %d, sector %#x, count %#x\n", + ums_count, devtype, ums[ums_count].block_dev.devnum, + ums[ums_count].block_dev.hwpart, + ums[ums_count].start_sector, + ums[ums_count].num_sectors); + + ums_count++; + } + + if (ums_count) + ret = 0; + +cleanup: + free(s); + + if (ret < 0) + ums_fini(); + + return ret; +} + +static int do_usb_mass_storage(struct cmd_tbl *cmdtp, int flag, + int argc, char *const argv[]) +{ + const char *usb_controller; + const char *devtype; + const char *devnum; + unsigned int controller_index; + struct udevice *udc; + int rc; + int cable_ready_timeout __maybe_unused; + + if (argc < 3) + return CMD_RET_USAGE; + + usb_controller = argv[1]; + if (argc >= 4) { + devtype = argv[2]; + devnum = argv[3]; + } else { + devtype = "mmc"; + devnum = argv[2]; + } + + rc = ums_init(devtype, devnum); + if (rc < 0) + return CMD_RET_FAILURE; + + controller_index = (unsigned int)(simple_strtoul( + usb_controller, NULL, 0)); + rc = udc_device_get_by_index(controller_index, &udc); + if (rc) { + pr_err("Couldn't init USB controller.\n"); + rc = CMD_RET_FAILURE; + goto cleanup_ums_init; + } + + rc = fsg_init(ums, ums_count, udc); + if (rc) { + pr_err("fsg_init failed\n"); + rc = CMD_RET_FAILURE; + goto cleanup_board; + } + + rc = g_dnl_register("usb_dnl_ums"); + if (rc) { + pr_err("g_dnl_register failed\n"); + rc = CMD_RET_FAILURE; + goto cleanup_board; + } + + /* Timeout unit: seconds */ + cable_ready_timeout = UMS_CABLE_READY_TIMEOUT; + + if (!g_dnl_board_usb_cable_connected()) { + /* + * Won't execute if we don't know whether the cable is + * connected. + */ + puts("Please connect USB cable.\n"); + + while (!g_dnl_board_usb_cable_connected()) { + if (ctrlc()) { + puts("\rCTRL+C - Operation aborted.\n"); + rc = CMD_RET_SUCCESS; + goto cleanup_register; + } + if (!cable_ready_timeout) { + puts("\rUSB cable not detected.\n" \ + "Command exit.\n"); + rc = CMD_RET_SUCCESS; + goto cleanup_register; + } + + printf("\rAuto exit in: %.2d s.", cable_ready_timeout); + mdelay(1000); + cable_ready_timeout--; + } + puts("\r\n"); + } + + while (1) { + dm_usb_gadget_handle_interrupts(udc); + + rc = fsg_main_thread(NULL); + if (rc) { + /* Check I/O error */ + if (rc == -EIO) + printf("\rCheck USB cable connection\n"); + + /* Check CTRL+C */ + if (rc == -EPIPE) + printf("\rCTRL+C - Operation aborted\n"); + + rc = CMD_RET_SUCCESS; + goto cleanup_register; + } + + if (IS_ENABLED(CONFIG_CMD_UMS_ABORT_KEYED)) { + /* Abort by pressing any key */ + if (tstc()) { + getchar(); + printf("\rOperation aborted.\n"); + rc = CMD_RET_SUCCESS; + goto cleanup_register; + } + } + + schedule(); + } + +cleanup_register: + g_dnl_unregister(); +cleanup_board: + udc_device_put(udc); +cleanup_ums_init: + ums_fini(); + + return rc; +} + +U_BOOT_CMD(ums, 4, 1, do_usb_mass_storage, + "Use the UMS [USB Mass Storage]", + "<USB_controller> [<devtype>] <dev[:part]> e.g. ums 0 mmc 0\n" + " devtype defaults to mmc" +); diff --git a/cmd/vbe.c b/cmd/vbe.c new file mode 100644 index 00000000000..186f6e6860d --- /dev/null +++ b/cmd/vbe.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Verified Boot for Embedded (VBE) command + * + * Copyright 2022 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <bloblist.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <command.h> +#include <spl.h> +#include <vbe.h> + +static int do_vbe_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + vbe_list(); + + return 0; +} + +static int do_vbe_select(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct udevice *dev; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + if (argc < 2) { + std->vbe_bootmeth = NULL; + return 0; + } + if (vbe_find_by_any(argv[1], &dev)) + return CMD_RET_FAILURE; + + std->vbe_bootmeth = dev; + + return 0; +} + +static int do_vbe_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + char buf[256]; + int ret, len; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + if (!std->vbe_bootmeth) { + printf("No VBE bootmeth selected\n"); + return CMD_RET_FAILURE; + } + ret = bootmeth_get_state_desc(std->vbe_bootmeth, buf, sizeof(buf)); + if (ret) { + printf("Failed (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + len = strnlen(buf, sizeof(buf)); + if (len >= sizeof(buf)) { + printf("Buffer overflow\n"); + return CMD_RET_FAILURE; + } + + puts(buf); + if (buf[len] != '\n') + putc('\n'); + + return 0; +} + +static int do_vbe_state(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct vbe_handoff *handoff = NULL; + int i; + + if (IS_ENABLED(CONFIG_BLOBLIST)) { + handoff = bloblist_find(BLOBLISTT_VBE, + sizeof(struct vbe_handoff)); + } + if (!handoff) { + printf("No VBE state\n"); + return CMD_RET_FAILURE; + } + + printf("Phases:"); + for (i = PHASE_NONE; i < PHASE_COUNT; i++) { + if (handoff->phases & (1 << i)) + printf(" %s", xpl_name(i)); + + } + if (!handoff->phases) + printf(" (none)"); + printf("\n"); + + return 0; +} + +U_BOOT_LONGHELP(vbe, + "list - list VBE bootmeths\n" + "vbe select - select a VBE bootmeth by sequence or name\n" + "vbe info - show information about a VBE bootmeth\n" + "vbe state - show VBE state"); + +U_BOOT_CMD_WITH_SUBCMDS(vbe, "Verified Boot for Embedded", vbe_help_text, + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_vbe_list), + U_BOOT_SUBCMD_MKENT(select, 2, 1, do_vbe_select), + U_BOOT_SUBCMD_MKENT(state, 2, 1, do_vbe_state), + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_vbe_info)); diff --git a/cmd/version.c b/cmd/version.c new file mode 100644 index 00000000000..62406608eb0 --- /dev/null +++ b/cmd/version.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2000-2009 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <command.h> +#include <display_options.h> +#include <version.h> +#include <version_string.h> +#include <linux/compiler.h> +#ifdef CONFIG_SYS_COREBOOT +#include <asm/cb_sysinfo.h> +#endif + +static int do_version(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char buf[DISPLAY_OPTIONS_BANNER_LENGTH]; + + printf(display_options_get_banner(false, buf, sizeof(buf))); +#ifdef CC_VERSION_STRING + puts(CC_VERSION_STRING "\n"); +#endif +#ifdef LD_VERSION_STRING + puts(LD_VERSION_STRING "\n"); +#endif +#ifdef CONFIG_SYS_COREBOOT + printf("coreboot-%s (%s)\n", lib_sysinfo.version, lib_sysinfo.build); +#endif + return 0; +} + +U_BOOT_CMD( + version, 1, 1, do_version, + "print monitor, compiler and linker version", + "" +); diff --git a/cmd/video.c b/cmd/video.c new file mode 100644 index 00000000000..91bd6de14dc --- /dev/null +++ b/cmd/video.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * video commands + * + * Copyright 2022 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <dm.h> +#include <video.h> +#include <video_console.h> + +static int do_video_setcursor(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned int col, row; + struct udevice *dev; + + if (argc != 3) + return CMD_RET_USAGE; + + if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) + return CMD_RET_FAILURE; + col = dectoul(argv[1], NULL); + row = dectoul(argv[2], NULL); + vidconsole_position_cursor(dev, col, row); + + return 0; +} + +static int do_video_puts(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret; + + if (argc != 2) + return CMD_RET_USAGE; + + if (uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &dev)) + return CMD_RET_FAILURE; + ret = vidconsole_put_string(dev, argv[1]); + if (!ret) + ret = video_sync(dev->parent, false); + + return ret ? CMD_RET_FAILURE : 0; +} + +U_BOOT_CMD( + setcurs, 3, 1, do_video_setcursor, + "set cursor position within screen", + " <col> <row> in character" +); + +U_BOOT_CMD( + lcdputs, 2, 1, do_video_puts, + "print string on video framebuffer", + " <string>" +); diff --git a/cmd/virtio.c b/cmd/virtio.c new file mode 100644 index 00000000000..a42a563ab72 --- /dev/null +++ b/cmd/virtio.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi> + * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com> + */ + +#include <blk.h> +#include <command.h> +#include <dm.h> +#include <virtio_types.h> +#include <virtio.h> + +static int virtio_curr_dev; + +static int do_virtio(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (argc == 2 && !strcmp(argv[1], "scan")) { + /* + * make sure all virtio devices are enumerated. + * Do the same as virtio_init(), but also call + * device_probe() for children (i.e. virtio devices) + */ + struct udevice *bus, *child; + + uclass_first_device(UCLASS_VIRTIO, &bus); + if (!bus) + return CMD_RET_FAILURE; + + while (bus) { + device_foreach_child_probe(child, bus) + ; + uclass_next_device(&bus); + } + + return CMD_RET_SUCCESS; + } + + return blk_common_cmd(argc, argv, UCLASS_VIRTIO, &virtio_curr_dev); +} + +U_BOOT_CMD( + virtio, 8, 1, do_virtio, + "virtio block devices sub-system", + "scan - initialize virtio bus\n" + "virtio info - show all available virtio block devices\n" + "virtio device [dev] - show or set current virtio block device\n" + "virtio part [dev] - print partition table of one or all virtio block devices\n" + "virtio read addr blk# cnt - read `cnt' blocks starting at block\n" + " `blk#' to memory address `addr'\n" + "virtio write addr blk# cnt - write `cnt' blocks starting at block\n" + " `blk#' from memory address `addr'" +); diff --git a/cmd/w1.c b/cmd/w1.c new file mode 100644 index 00000000000..e462e786a96 --- /dev/null +++ b/cmd/w1.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0+ + * + * (C) Copyright 2018 + * Microchip Technology, Inc. + * Eugen Hristev <eugen.hristev@microchip.com> + */ +#include <command.h> +#include <dm.h> +#include <w1.h> +#include <w1-eeprom.h> +#include <dm/device-internal.h> + +static int w1_bus(void) +{ + struct udevice *bus, *dev; + int ret; + + ret = w1_get_bus(0, &bus); + if (ret) { + printf("one wire interface not found\n"); + return CMD_RET_FAILURE; + } + printf("Bus %d:\t%s", dev_seq(bus), bus->name); + if (device_active(bus)) + printf(" (active)"); + printf("\n"); + + for (device_find_first_child(bus, &dev); + dev; + device_find_next_child(&dev)) { + ret = device_probe(dev); + + printf("\t%s (%d) uclass %s : ", dev->name, dev_seq(dev), + dev->uclass->uc_drv->name); + + if (ret) + printf("device error\n"); + else + printf("family 0x%x\n", w1_get_device_family(dev)); + } + return CMD_RET_SUCCESS; +} + +static int w1_read(int argc, char *const argv[]) +{ + int bus_n = 0, dev_n = 0, offset = 0, len = 512; + int i; + struct udevice *bus, *dev; + int ret; + u8 buf[512]; + + if (argc > 2) + bus_n = dectoul(argv[2], NULL); + + if (argc > 3) + dev_n = dectoul(argv[3], NULL); + + if (argc > 4) + offset = dectoul(argv[4], NULL); + + if (argc > 5) + len = dectoul(argv[5], NULL); + + if (len > 512) { + printf("len needs to be <= 512\n"); + return CMD_RET_FAILURE; + } + + ret = w1_get_bus(bus_n, &bus); + if (ret) { + printf("one wire interface not found\n"); + return CMD_RET_FAILURE; + } + + for (device_find_first_child(bus, &dev), i = 0; + dev && i <= dev_n; + device_find_next_child(&dev), i++) { + ret = device_probe(dev); + if (!ret && i == dev_n) + break; + } + + if (i != dev_n || ret || !dev) { + printf("invalid dev\n"); + return CMD_RET_FAILURE; + } + + if (strcmp(dev->uclass->uc_drv->name, "w1_eeprom")) { + printf("the device present on the interface is of unknown device class\n"); + return CMD_RET_FAILURE; + } + + ret = w1_eeprom_read_buf(dev, offset, (u8 *)buf, len); + if (ret) { + printf("error reading device %s\n", dev->name); + return CMD_RET_FAILURE; + } + + for (i = 0; i < len; i++) + printf("%x", buf[i]); + printf("\n"); + + return CMD_RET_SUCCESS; +} + +int do_w1(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + if (argc < 2) + return CMD_RET_USAGE; + + if (!strcmp(argv[1], "bus")) + return w1_bus(); + + if (!strcmp(argv[1], "read")) + return w1_read(argc, argv); + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(w1, 6, 0, do_w1, + "onewire interface utility commands", + "bus - show onewire bus info (all)\n" + "w1 read [<bus> [<dev> [offset [length]]]]" + " - read from onewire device 'dev' on onewire bus 'bus'" + " starting from offset 'offset' and length 'length'\n" + " defaults: bus 0, dev 0, offset 0, length 512 bytes."); diff --git a/cmd/wdt.c b/cmd/wdt.c new file mode 100644 index 00000000000..c7a06cca181 --- /dev/null +++ b/cmd/wdt.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Watchdog commands + * + * Copyright (c) 2019 Michael Walle <michael@walle.cc> + */ + +#include <command.h> +#include <dm.h> +#include <wdt.h> + +static struct udevice *currdev; + +static int do_wdt_list(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + struct uclass *uc; + int ret; + + ret = uclass_get(UCLASS_WDT, &uc); + if (ret) + return CMD_RET_FAILURE; + + uclass_foreach_dev(dev, uc) + printf("%s (%s)\n", dev->name, dev->driver->name); + + return CMD_RET_SUCCESS; +} + +static int do_wdt_dev(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + if (argc > 1) { + ret = uclass_get_device_by_name(UCLASS_WDT, argv[1], &currdev); + if (ret) { + printf("Can't get the watchdog timer: %s\n", argv[1]); + return CMD_RET_FAILURE; + } + } else { + if (!currdev) { + printf("No watchdog timer device set!\n"); + return CMD_RET_FAILURE; + } + printf("dev: %s\n", currdev->name); + } + + return CMD_RET_SUCCESS; +} + +static int check_currdev(void) +{ + if (!currdev) { + printf("No device set, use 'wdt dev' first\n"); + return CMD_RET_FAILURE; + } + return 0; +} + +static int do_wdt_start(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + u64 timeout; + ulong flags = 0; + + if (argc < 2) + return CMD_RET_USAGE; + + ret = check_currdev(); + if (ret) + return ret; + + timeout = simple_strtoull(argv[1], NULL, 0); + if (argc > 2) + flags = simple_strtoul(argv[2], NULL, 0); + + ret = wdt_start(currdev, timeout, flags); + if (ret == -ENOSYS) { + printf("Starting watchdog timer not supported.\n"); + return CMD_RET_FAILURE; + } else if (ret) { + printf("Starting watchdog timer failed (%d)\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_wdt_stop(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + ret = check_currdev(); + if (ret) + return ret; + + ret = wdt_stop(currdev); + if (ret == -ENOSYS) { + printf("Stopping watchdog timer not supported.\n"); + return CMD_RET_FAILURE; + } else if (ret) { + printf("Stopping watchdog timer failed (%d)\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_wdt_reset(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + ret = check_currdev(); + if (ret) + return ret; + + ret = wdt_reset(currdev); + if (ret == -ENOSYS) { + printf("Resetting watchdog timer not supported.\n"); + return CMD_RET_FAILURE; + } else if (ret) { + printf("Resetting watchdog timer failed (%d)\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_wdt_expire(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + ulong flags = 0; + + ret = check_currdev(); + if (ret) + return ret; + + if (argc > 1) + flags = simple_strtoul(argv[1], NULL, 0); + + ret = wdt_expire_now(currdev, flags); + if (ret == -ENOSYS) { + printf("Expiring watchdog timer not supported.\n"); + return CMD_RET_FAILURE; + } else if (ret) { + printf("Expiring watchdog timer failed (%d)\n", ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +U_BOOT_LONGHELP(wdt, + "list - list watchdog devices\n" + "wdt dev [<name>] - get/set current watchdog device\n" + "wdt start <timeout ms> [flags] - start watchdog timer\n" + "wdt stop - stop watchdog timer\n" + "wdt reset - reset watchdog timer\n" + "wdt expire [flags] - expire watchdog timer immediately\n"); + +U_BOOT_CMD_WITH_SUBCMDS(wdt, "Watchdog sub-system", wdt_help_text, + U_BOOT_SUBCMD_MKENT(list, 1, 1, do_wdt_list), + U_BOOT_SUBCMD_MKENT(dev, 2, 1, do_wdt_dev), + U_BOOT_SUBCMD_MKENT(start, 3, 1, do_wdt_start), + U_BOOT_SUBCMD_MKENT(stop, 1, 1, do_wdt_stop), + U_BOOT_SUBCMD_MKENT(reset, 1, 1, do_wdt_reset), + U_BOOT_SUBCMD_MKENT(expire, 2, 1, do_wdt_expire)); diff --git a/cmd/wol.c b/cmd/wol.c new file mode 100644 index 00000000000..45d4ae3f719 --- /dev/null +++ b/cmd/wol.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2018 + * Lothar Felte, lothar.felten@gmail.com + */ + +/* + * Wake-on-LAN support + */ +#include <command.h> +#include <net.h> +#include <vsprintf.h> + +#if defined(CONFIG_CMD_WOL) +void wol_set_timeout(ulong); + +int do_wol(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + /* Validate arguments */ + if (argc < 2) + return CMD_RET_USAGE; + wol_set_timeout(simple_strtol(argv[1], NULL, 10) * 1000); + if (net_loop(WOL) < 0) + return CMD_RET_FAILURE; + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + wol, 2, 1, do_wol, + "wait for an incoming wake-on-lan packet", + "Timeout" +); +#endif diff --git a/cmd/x86/Makefile b/cmd/x86/Makefile new file mode 100644 index 00000000000..5f3f5be2882 --- /dev/null +++ b/cmd/x86/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_CMD_CBSYSINFO) += cbsysinfo.o +obj-y += cpuid.o msr.o mtrr.o +obj-$(CONFIG_CMD_CBCMOS) += cbcmos.o +obj-$(CONFIG_CMD_EXCEPTION) += exception.o +obj-$(CONFIG_USE_HOB) += hob.o +obj-$(CONFIG_HAVE_FSP) += fsp.o +obj-$(CONFIG_CMD_ZBOOT) += zboot.o diff --git a/cmd/x86/cbcmos.c b/cmd/x86/cbcmos.c new file mode 100644 index 00000000000..fe5582fbf51 --- /dev/null +++ b/cmd/x86/cbcmos.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Support for booting from coreboot + * + * Copyright 2021 Google LLC + */ + +#define LOG_CATEGORY UCLASS_RTC + +#include <command.h> +#include <dm.h> +#include <rtc.h> +#include <asm/cb_sysinfo.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +const struct sysinfo_t *get_table(void) +{ + if (!gd->arch.coreboot_table) { + printf("No coreboot sysinfo table found\n"); + return NULL; + } + + return &lib_sysinfo; +} + +static int calc_sum(struct udevice *dev, uint start_bit, uint bit_count) +{ + uint start_byte = start_bit / 8; + uint byte_count = bit_count / 8; + int ret, i; + uint sum; + + log_debug("Calc sum from %x: %x bytes\n", start_byte, byte_count); + sum = 0; + for (i = 0; i < bit_count / 8; i++) { + ret = rtc_read8(dev, start_bit / 8 + i); + if (ret < 0) + return ret; + sum += ret; + } + + return (sum & 0xff) << 8 | (sum & 0xff00) >> 8; +} + +/** + * prep_cbcmos() - Prepare for a CMOS-RAM command + * + * @tab: coreboot table + * @devnum: RTC device name to use, or NULL for the first one + * @dep: Returns RTC device on success + * Return: calculated checksum for CMOS RAM or -ve on error + */ +static int prep_cbcmos(const struct sysinfo_t *tab, const char *devname, + struct udevice **devp) +{ + struct udevice *dev; + int ret; + + if (!tab) + return CMD_RET_FAILURE; + if (devname) + ret = uclass_get_device_by_name(UCLASS_RTC, devname, &dev); + else + ret = uclass_first_device_err(UCLASS_RTC, &dev); + if (ret) { + printf("Failed to get RTC device: %dE\n", ret); + return ret; + } + + ret = calc_sum(dev, tab->cmos_range_start, + tab->cmos_range_end + 1 - tab->cmos_range_start); + if (ret < 0) { + printf("Failed to read RTC device: %dE\n", ret); + return ret; + } + *devp = dev; + + return ret; +} + +static int do_cbcmos_check(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const struct sysinfo_t *tab = get_table(); + struct udevice *dev; + u16 cur, sum; + int ret; + + ret = prep_cbcmos(tab, argv[1], &dev); + if (ret < 0) + return CMD_RET_FAILURE; + sum = ret; + + ret = rtc_read16(dev, tab->cmos_checksum_location / 8, &cur); + if (ret < 0) { + printf("Failed to read RTC device: %dE\n", ret); + return CMD_RET_FAILURE; + } + if (sum != cur) { + printf("Checksum %04x error: calculated %04x\n", cur, sum); + return CMD_RET_FAILURE; + } + + return 0; +} + +static int do_cbcmos_update(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const struct sysinfo_t *tab = get_table(); + struct udevice *dev; + u16 sum; + int ret; + + ret = prep_cbcmos(tab, argv[1], &dev); + if (ret < 0) + return CMD_RET_FAILURE; + sum = ret; + + ret = rtc_write16(dev, tab->cmos_checksum_location / 8, sum); + if (ret < 0) { + printf("Failed to read RTC device: %dE\n", ret); + return CMD_RET_FAILURE; + } + printf("Checksum %04x written\n", sum); + + return 0; +} + +U_BOOT_LONGHELP(cbcmos, + "check - check CMOS RAM\n" + "cbcmos update - Update CMOS-RAM checksum"; +); + +U_BOOT_CMD_WITH_SUBCMDS(cbcmos, "coreboot CMOS RAM", cbcmos_help_text, + U_BOOT_SUBCMD_MKENT(check, 2, 1, do_cbcmos_check), + U_BOOT_SUBCMD_MKENT(update, 2, 1, do_cbcmos_update)); diff --git a/cmd/x86/cbsysinfo.c b/cmd/x86/cbsysinfo.c new file mode 100644 index 00000000000..ea4d89616f6 --- /dev/null +++ b/cmd/x86/cbsysinfo.c @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <asm/cb_sysinfo.h> +#include <command.h> +#include <console.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static void cbprompt(const char *name) +{ + for (; *name == '>'; name++) + puts(" "); + printf("%-12s: ", name); +} + +static void print_dec(const char *name, int value) +{ + cbprompt(name); + printf(value > 9 ? "0d%d\n" : "%d\n", value); +} + +static void print_hex(const char *name, int value) +{ + cbprompt(name); + printf("%x\n", value); +} + +static void print_addr(const char *name, ulong value) +{ + cbprompt(name); + printf("%08lx\n", value); +} + +static void print_addr64(const char *name, u64 value) +{ + cbprompt(name); + printf("%16llx\n", value); +} + +static void print_ptr(const char *name, const void *value) +{ + cbprompt(name); + printf("%p\n", value); +} + +static void print_str(const char *name, const char *value) +{ + if (value) { + cbprompt(name); + printf("%s\n", value); + } +} + +static void print_idx(const char *name, uint idx, const u8 *strings) +{ + const char *ptr; + + cbprompt(name); + ptr = (char *)strings + idx; + printf("%d: %s\n", idx, ptr ? ptr : "(unknown)"); +} + +static const char *const cb_mem_name[] = { + NULL, + "ram", + "reserved", + "acpi", + "nvs", + "unusable", + "vendor", +}; + +static const char *get_mem_name(int tag) +{ + if (tag >= CB_MEM_RAM && tag <= CB_MEM_VENDOR_RSVD) + return cb_mem_name[tag]; + + if (tag == CB_MEM_TABLE) + return "table"; + + return "(unknown)"; +} + +static const struct timestamp_id_to_name { + uint id; + const char *name; +} timestamp_ids[] = { + /* Marker to report base_time */ + { 0, "1st timestamp" }, + { TS_START_ROMSTAGE, "start of romstage" }, + { TS_BEFORE_INITRAM, "before ram initialization" }, + { TS_AFTER_INITRAM, "after ram initialization" }, + { TS_END_ROMSTAGE, "end of romstage" }, + { TS_START_VBOOT, "start of verified boot" }, + { TS_END_VBOOT, "end of verified boot" }, + { TS_START_COPYRAM, "starting to load ramstage" }, + { TS_END_COPYRAM, "finished loading ramstage" }, + { TS_START_RAMSTAGE, "start of ramstage" }, + { TS_START_BOOTBLOCK, "start of bootblock" }, + { TS_END_BOOTBLOCK, "end of bootblock" }, + { TS_START_COPYROM, "starting to load romstage" }, + { TS_END_COPYROM, "finished loading romstage" }, + { TS_START_ULZMA, "starting LZMA decompress (ignore for x86)" }, + { TS_END_ULZMA, "finished LZMA decompress (ignore for x86)" }, + { TS_START_ULZ4F, "starting LZ4 decompress (ignore for x86)" }, + { TS_END_ULZ4F, "finished LZ4 decompress (ignore for x86)" }, + { TS_DEVICE_ENUMERATE, "device enumeration" }, + { TS_DEVICE_CONFIGURE, "device configuration" }, + { TS_DEVICE_ENABLE, "device enable" }, + { TS_DEVICE_INITIALIZE, "device initialization" }, + { TS_DEVICE_DONE, "device setup done" }, + { TS_CBMEM_POST, "cbmem post" }, + { TS_WRITE_TABLES, "write tables" }, + { TS_FINALIZE_CHIPS, "finalize chips" }, + { TS_LOAD_PAYLOAD, "load payload" }, + { TS_ACPI_WAKE_JUMP, "ACPI wake jump" }, + { TS_SELFBOOT_JUMP, "selfboot jump" }, + + { TS_START_COPYVER, "starting to load verstage" }, + { TS_END_COPYVER, "finished loading verstage" }, + { TS_START_TPMINIT, "starting to initialize TPM" }, + { TS_END_TPMINIT, "finished TPM initialization" }, + { TS_START_VERIFY_SLOT, "starting to verify keyblock/preamble (RSA)" }, + { TS_END_VERIFY_SLOT, "finished verifying keyblock/preamble (RSA)" }, + { TS_START_HASH_BODY, "starting to verify body (load+SHA2+RSA) " }, + { TS_DONE_LOADING, "finished loading body (ignore for x86)" }, + { TS_DONE_HASHING, "finished calculating body hash (SHA2)" }, + { TS_END_HASH_BODY, "finished verifying body signature (RSA)" }, + + { TS_START_COPYVPD, "starting to load Chrome OS VPD" }, + { TS_END_COPYVPD_RO, "finished loading Chrome OS VPD (RO)" }, + { TS_END_COPYVPD_RW, "finished loading Chrome OS VPD (RW)" }, + + { TS_U_BOOT_INITTED, "U-Boot start" }, + { TS_RO_PARAMS_INIT, "RO parameter init" }, + { TS_RO_VB_INIT, "RO vboot init" }, + { TS_RO_VB_SELECT_FIRMWARE, "RO vboot select firmware" }, + { TS_RO_VB_SELECT_AND_LOAD_KERNEL, "RO vboot select&load kernel" }, + { TS_RW_VB_SELECT_AND_LOAD_KERNEL, "RW vboot select&load kernel" }, + { TS_VB_SELECT_AND_LOAD_KERNEL, "vboot select&load kernel" }, + { TS_VB_EC_VBOOT_DONE, "finished EC verification" }, + { TS_VB_STORAGE_INIT_DONE, "finished storage device initialization" }, + { TS_VB_READ_KERNEL_DONE, "finished reading kernel from disk" }, + { TS_VB_VBOOT_DONE, "finished vboot kernel verification" }, + { TS_KERNEL_DECOMPRESSION, "starting kernel decompression/relocation" }, + { TS_START_KERNEL, "jumping to kernel" }, + { TS_U_BOOT_START_KERNEL, "just before jump to kernel" }, + + /* Intel ME-related timestamps */ + { TS_ME_INFORM_DRAM_WAIT, "waiting for ME acknowledgment of raminit"}, + { TS_ME_INFORM_DRAM_DONE, "finished waiting for ME response"}, + + /* FSP-related timestamps */ + { TS_FSP_MEMORY_INIT_START, "calling FspMemoryInit" }, + { TS_FSP_MEMORY_INIT_END, "returning from FspMemoryInit" }, + { TS_FSP_TEMP_RAM_EXIT_START, "calling FspTempRamExit" }, + { TS_FSP_TEMP_RAM_EXIT_END, "returning from FspTempRamExit" }, + { TS_FSP_SILICON_INIT_START, "calling FspSiliconInit" }, + { TS_FSP_SILICON_INIT_END, "returning from FspSiliconInit" }, + { TS_FSP_BEFORE_ENUMERATE, "calling FspNotify(AfterPciEnumeration)" }, + { TS_FSP_AFTER_ENUMERATE, + "returning from FspNotify(AfterPciEnumeration)" }, + { TS_FSP_BEFORE_FINALIZE, "calling FspNotify(ReadyToBoot)" }, + { TS_FSP_AFTER_FINALIZE, "returning from FspNotify(ReadyToBoot)" }, + { TS_FSP_BEFORE_END_OF_FIRMWARE, "calling FspNotify(EndOfFirmware)" }, + { TS_FSP_AFTER_END_OF_FIRMWARE, + "returning from FspNotify(EndOfFirmware)" }, +}; + +static const char *timestamp_name(uint32_t id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(timestamp_ids); i++) { + if (timestamp_ids[i].id == id) + return timestamp_ids[i].name; + } + + return "<unknown>"; +} + +static void show_option_vals(const struct cb_cmos_option_table *tab, + uint id) +{ + const void *ptr, *end; + bool found = false; + + end = (void *)tab + tab->size; + for (ptr = (void *)tab + tab->header_length; ptr < end;) { + const struct cb_record *rec = ptr; + + switch (rec->tag) { + case CB_TAG_OPTION_ENUM: { + const struct cb_cmos_enums *enums = ptr; + + if (enums->config_id == id) { + if (!found) + printf(" "); + printf(" %d:%s", enums->value, enums->text); + found = true; + } + break; + } + break; + case CB_TAG_OPTION_DEFAULTS: + case CB_TAG_OPTION_CHECKSUM: + case CB_TAG_OPTION: + break; + default: + printf("tag %x\n", rec->tag); + break; + } + ptr += rec->size; + } +} + +static void show_option_table(const struct cb_cmos_option_table *tab) +{ + const void *ptr, *end; + + print_ptr("option_table", tab); + if (!tab->size) + return; + + printf(" Bit Len Cfg ID Name\n"); + end = (void *)tab + tab->size; + for (ptr = (void *)tab + tab->header_length; ptr < end;) { + const struct cb_record *rec = ptr; + + switch (rec->tag) { + case CB_TAG_OPTION: { + const struct cb_cmos_entries *entry = ptr; + + printf("%4x %4x %3c %3x %-20s", entry->bit, + entry->length, entry->config, entry->config_id, + entry->name); + show_option_vals(tab, entry->config_id); + printf("\n"); + break; + } + case CB_TAG_OPTION_ENUM: + case CB_TAG_OPTION_DEFAULTS: + case CB_TAG_OPTION_CHECKSUM: + break; + default: + printf("tag %x\n", rec->tag); + break; + } + ptr += rec->size; + } +} + +static void show_table(struct sysinfo_t *info, bool verbose) +{ + struct cb_serial *ser = info->serial; + int i; + + printf("Coreboot table at %lx, size %x, records %x (dec %d), decoded to %p", + gd->arch.coreboot_table, info->table_size, info->rec_count, + info->rec_count, info); + if (info->header) + printf(", forwarded to %p\n", info->header); + printf("\n"); + + print_dec("CPU KHz", info->cpu_khz); + + print_addr("Serial I/O port", info->ser_ioport); + print_addr(">base", info->ser_base); + print_ptr(">pointer", ser); + if (ser) { + print_hex(">type", ser->type); + print_addr(">base", ser->baseaddr); + print_dec(">baud", ser->baud); + print_hex(">regwidth", ser->regwidth); + print_dec(">input_hz", ser->input_hertz); + print_addr(">PCI addr", ser->uart_pci_addr); + } + + print_dec("Mem ranges", info->n_memranges); + printf("%12s: %-11s || base || size\n", "id", "type"); + for (i = 0; i < info->n_memranges; i++) { + const struct memrange *mr = &info->memrange[i]; + + printf("%12d: %02x:%-8s %016llx %016llx\n", i, mr->type, + get_mem_name(mr->type), mr->base, mr->size); + } + show_option_table(info->option_table); + + print_hex("CMOS start", info->cmos_range_start); + if (info->cmos_range_start) { + print_hex(">CMOS end", info->cmos_range_end); + print_hex(">CMOS csum loc", info->cmos_checksum_location); + } + + print_hex("VBNV start", info->vbnv_start); + print_hex("VBNV size", info->vbnv_size); + + print_str("CB version", info->cb_version); + print_str(">Extra", info->extra_version); + print_str(">Build", info->build); + print_str(">Time", info->compile_time); + print_str(">By", info->compile_by); + print_str(">Host", info->compile_host); + print_str(">Domain", info->compile_domain); + print_str(">Compiler", info->compiler); + print_str(">Linker", info->linker); + print_str(">Assembler", info->assembler); + + print_ptr("Framebuffer", info->framebuffer); + if (info->framebuffer) { + struct cb_framebuffer *fb = info->framebuffer; + + print_addr64(">Phys addr", fb->physical_address); + print_dec(">X res", fb->x_resolution); + print_dec(">X res", fb->y_resolution); + print_hex(">Bytes / line", fb->bytes_per_line); + print_dec(">Bpp", fb->bits_per_pixel); + printf(" %-12s red %d/%d, green %d/%d, blue %d/%d, reserved %d/%d\n", + "pos/size", fb->red_mask_pos, fb->red_mask_size, + fb->green_mask_pos, fb->green_mask_size, + fb->blue_mask_pos, fb->blue_mask_size, + fb->reserved_mask_pos, fb->reserved_mask_size); + } + + print_dec("GPIOs", info->num_gpios); + printf("%12s: %4s %12s %3s %s\n", "id", "port", "polarity", "val", + "name"); + for (i = 0; i < info->num_gpios; i++) { + const struct cb_gpio *gpio = &info->gpios[i]; + char portstr[4]; + + if (gpio->port == 0xffffffff) + strcpy(portstr, "-"); + else + sprintf(portstr, "%x", gpio->port); + printf("%12d: %4s %12s %3d %s\n", i, portstr, + gpio->polarity == CB_GPIO_ACTIVE_LOW ? "active-low" : + "active-high", gpio->value, gpio->name); + } + print_dec("MACs", info->num_macs); + for (i = 0; i < info->num_macs; i++) { + const struct mac_address *mac = &info->macs[i]; + int j; + + printf("%12d: ", i); + for (j = 0; j < sizeof(mac->mac_addr); j++) + printf("%s%02x", j ? ":" : "", mac->mac_addr[j]); + printf("\n"); + } + print_str(">Serial #", info->serialno); + print_ptr("Multiboot tab", info->mbtable); + print_ptr("CB header", info->header); + print_ptr("CB mainboard", info->mainboard); + if (info->mainboard) { + struct cb_mainboard *mb = info->mainboard; + + print_idx(">vendor", mb->vendor_idx, mb->strings); + print_idx(">part_number", mb->part_number_idx, mb->strings); + } + print_ptr("vboot handoff", info->vboot_handoff); + print_hex(">size", info->vboot_handoff_size); + print_ptr(">vdat addr", info->vdat_addr); + print_hex(">size", info->vdat_size); + + print_addr64("SMBIOS", info->smbios_start); + print_hex(">size", info->smbios_size); + print_hex("ROM MTRR", info->x86_rom_var_mtrr_index); + + print_ptr("Tstamp table", info->tstamp_table); + if (verbose && info->tstamp_table) { + struct timestamp_table *ts = info->tstamp_table; + + printf("%-12s", "Base_time"); + print_grouped_ull(ts->base_time, 12); + printf("\n"); + print_dec("Tick MHz", ts->tick_freq_mhz); + for (i = 0; i < ts->num_entries; i++) { + const struct timestamp_entry *tse; + + tse = &ts->entries[i]; + printf(" "); + print_grouped_ull(tse->entry_stamp, 12); + printf(" %s\n", timestamp_name(tse->entry_id)); + } + } + + print_ptr("CBmem cons", info->cbmem_cons); + if (info->cbmem_cons) { + struct cbmem_console *cons = info->cbmem_cons; + int i; + + print_hex("Size", cons->size); + print_hex("Cursor", cons->cursor); + if (verbose) { + for (i = 0; i < cons->cursor; i++) { + int ch = cons->body[i]; + + putc(ch); + + if (ch == '\n') { + /* check for ctrl-c to abort... */ + if (ctrlc()) { + puts("Abort\n"); + return; + } + printf(" "); + } + } + printf("\n"); + } + } + + print_ptr("MRC cache", info->mrc_cache); + print_ptr("ACPI GNVS", info->acpi_gnvs); + print_hex("Board ID", info->board_id); + print_hex("RAM code", info->ram_code); + print_ptr("WiFi calib", info->wifi_calibration); + print_addr64("Ramoops buff", info->ramoops_buffer); + print_hex(">size", info->ramoops_buffer_size); + print_hex("SF size", info->spi_flash.size); + print_hex("SF sector", info->spi_flash.sector_size); + print_hex("SF erase cmd", info->spi_flash.erase_cmd); + + print_addr64("FMAP offset", info->fmap_offset); + print_addr64("CBFS offset", info->cbfs_offset); + print_addr64("CBFS size", info->cbfs_size); + print_addr64("Boot media size", info->boot_media_size); + print_addr64("MTC start", info->mtc_start); + print_hex("MTC size", info->mtc_size); + + print_ptr("Chrome OS VPD", info->chromeos_vpd); + print_ptr("RSDP", info->rsdp); + printf("%-12s: ", "Unimpl."); + if (info->unimpl_count) { + for (i = 0; i < info->unimpl_count; i++) + printf("%02x ", info->unimpl[i]); + printf("\n"); + } else { + printf("(none)\n"); + } +} + +static int do_cbsysinfo(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + bool verbose = false; + + if (argc > 1) { + if (!strcmp("-v", argv[1])) + verbose = true; + else + return CMD_RET_USAGE; + } + + if (!gd->arch.coreboot_table) { + printf("No coreboot sysinfo table found\n"); + return CMD_RET_FAILURE; + } + show_table(&lib_sysinfo, verbose); + + return 0; +} + +U_BOOT_CMD( + cbsysinfo, 2, 1, do_cbsysinfo, + "Show coreboot sysinfo table", + "[-v] Dumps out the contents of the sysinfo table. This only\n" + "works if U-Boot is booted from coreboot" +); diff --git a/cmd/x86/cpuid.c b/cmd/x86/cpuid.c new file mode 100644 index 00000000000..222754b5d15 --- /dev/null +++ b/cmd/x86/cpuid.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'cpuid' command provides access to the CPU's cpuid information + * + * Copyright 2024 Google, LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <vsprintf.h> +#include <asm/cpu.h> + +static int do_cpuid(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct cpuid_result res; + ulong op; + + if (argc < 2) + return CMD_RET_USAGE; + + op = hextoul(argv[1], NULL); + res = cpuid(op); + printf("eax %08x\n", res.eax); + printf("ebx %08x\n", res.ebx); + printf("ecx %08x\n", res.ecx); + printf("edx %08x\n", res.edx); + + return 0; +} + +U_BOOT_LONGHELP(cpuid, "Show CPU Identification information"); + +U_BOOT_CMD( + cpuid, 2, 1, do_cpuid, + "cpuid <op>", cpuid_help_text +); diff --git a/cmd/x86/exception.c b/cmd/x86/exception.c new file mode 100644 index 00000000000..02735494a3c --- /dev/null +++ b/cmd/x86/exception.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'exception' command can be used for testing exception handling. + * + * Copyright (c) 2018, Heinrich Schuchardt <xypron.glpk@gmx.de> + */ + +#include <command.h> + +static int do_undefined(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + asm volatile (".word 0xffff\n"); + return CMD_RET_FAILURE; +} + +static struct cmd_tbl cmd_sub[] = { + U_BOOT_CMD_MKENT(undefined, CONFIG_SYS_MAXARGS, 1, do_undefined, + "", ""), +}; + +U_BOOT_LONGHELP(exception, + "<ex>\n" + " The following exceptions are available:\n" + " undefined - undefined instruction\n"); + +#include <exception.h> diff --git a/cmd/x86/fsp.c b/cmd/x86/fsp.c new file mode 100644 index 00000000000..2620ab8ee02 --- /dev/null +++ b/cmd/x86/fsp.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2014-2015, Bin Meng <bmeng.cn@gmail.com> + */ + +#include <command.h> +#include <asm/fsp/fsp_support.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +static int do_hdr(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct fsp_header *hdr; + u32 img_addr; + char *sign; + uint addr; + int i; + +#ifdef CONFIG_FSP_VERSION2 + /* + * Only FSP-S is displayed. FSP-M was used in SPL but may not still be + * around, and we didn't keep a pointer to it. + */ + hdr = gd->arch.fsp_s_hdr; + img_addr = hdr->img_base; + addr = img_addr; +#else + addr = CONFIG_FSP_ADDR; + hdr = fsp_find_header(); + img_addr = hdr->img_base; +#endif + sign = (char *)&hdr->sign; + + printf("FSP : binary %08x, header %08x\n", addr, (int)hdr); + printf("Header : sign "); + for (i = 0; i < sizeof(hdr->sign); i++) + printf("%c", *sign++); + printf(", size %x, rev %d\n", hdr->hdr_len, hdr->hdr_rev); + printf("Image : rev "); + if (hdr->hdr_rev == FSP_HEADER_REVISION_1) { + printf("%d.%d", + (hdr->img_rev >> 8) & 0xff, hdr->img_rev & 0xff); + } else { + printf("%d.%d.%d.%d", + (hdr->img_rev >> 24) & 0xff, (hdr->img_rev >> 16) & 0xff, + (hdr->img_rev >> 8) & 0xff, hdr->img_rev & 0xff); + } + printf(", id "); + for (i = 0; i < ARRAY_SIZE(hdr->img_id); i++) + printf("%c", hdr->img_id[i]); + printf(", addr %08x, size %x\n", img_addr, hdr->img_size); + if (hdr->hdr_rev >= FSP_HEADER_REVISION_1) { + printf("GFX :%ssupported\n", + hdr->img_attr & FSP_ATTR_GRAPHICS_SUPPORT ? " " : " un"); + } + printf("VPD : addr %08x, size %x\n", + hdr->cfg_region_off + img_addr, hdr->cfg_region_size); + if (hdr->hdr_rev <= FSP_HEADER_REVISION_2) + printf("\nNumber of APIs Supported : %d\n", hdr->api_num); + if (hdr->fsp_tempram_init) + printf("\tTempRamInit : %08x\n", + hdr->fsp_tempram_init + img_addr); + if (hdr->fsp_init) + printf("\tFspInit : %08x\n", hdr->fsp_init + img_addr); + if (hdr->fsp_notify) + printf("\tFspNotify : %08x\n", hdr->fsp_notify + img_addr); + if (hdr->hdr_rev >= FSP_HEADER_REVISION_1) { + if (hdr->fsp_mem_init) + printf("\tMemoryInit : %08x\n", + hdr->fsp_mem_init + img_addr); + if (hdr->fsp_tempram_exit) + printf("\tTempRamExit : %08x\n", + hdr->fsp_tempram_exit + img_addr); + if (hdr->fsp_silicon_init) + printf("\tSiliconInit : %08x\n", + hdr->fsp_silicon_init + img_addr); + } + + return 0; +} + +static struct cmd_tbl fsp_commands[] = { + U_BOOT_CMD_MKENT(hdr, 0, 1, do_hdr, "", ""), +}; + +static int do_fsp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct cmd_tbl *fsp_cmd; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + fsp_cmd = find_cmd_tbl(argv[1], fsp_commands, ARRAY_SIZE(fsp_commands)); + argc -= 2; + argv += 2; + if (!fsp_cmd || argc > fsp_cmd->maxargs) + return CMD_RET_USAGE; + + ret = fsp_cmd->cmd(fsp_cmd, flag, argc, argv); + + return cmd_process_error(fsp_cmd, ret); +} + +U_BOOT_CMD( + fsp, 2, 1, do_fsp, + "Show Intel Firmware Support Package (FSP) related information", + "hdr - Print FSP header information" +); diff --git a/cmd/x86/hob.c b/cmd/x86/hob.c new file mode 100644 index 00000000000..d3713cef331 --- /dev/null +++ b/cmd/x86/hob.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2014-2015, Bin Meng <bmeng.cn@gmail.com> + */ + +#include <command.h> +#include <efi.h> +#include <u-boot/uuid.h> +#include <asm/global_data.h> +#include <asm/hob.h> +#include <asm/fsp/fsp_hob.h> + +DECLARE_GLOBAL_DATA_PTR; + +static char *hob_type[] = { + "reserved", + "Hand-off", + "Mem Alloc", + "Res Desc", + "GUID Ext", + "FV", + "CPU", + "Mem Pool", + "reserved", + "FV2", + "Load PEIM", + "Capsule", +}; + +static char *res_type[] = { + "System", + "Memory-mapped I/O", + "I/O", + "Firmware device", + "Memory-mapped I/O port", + "Reserved", + "I/O reserved", +}; + +static struct guid_name { + efi_guid_t guid; + const char *name; +} guid_name[] = { + { FSP_HOB_RESOURCE_OWNER_TSEG_GUID, "TSEG" }, + { FSP_HOB_RESOURCE_OWNER_FSP_GUID, "FSP" }, + { FSP_HOB_RESOURCE_OWNER_SMM_PEI_SMRAM_GUID, "SMM PEI SMRAM" }, + { FSP_NON_VOLATILE_STORAGE_HOB_GUID, "NVS" }, + { FSP_VARIABLE_NV_DATA_HOB_GUID, "Variable NVS" }, + { FSP_GRAPHICS_INFO_HOB_GUID, "Graphics info" }, + { FSP_HOB_RESOURCE_OWNER_PCD_DATABASE_GUID1, "PCD database ea" }, + { FSP_HOB_RESOURCE_OWNER_PCD_DATABASE_GUID2, "PCD database 9b" }, + { FSP_HOB_RESOURCE_OWNER_PEIM_DXE_GUID, "PEIM Init DXE" }, + { FSP_HOB_RESOURCE_OWNER_ALLOC_STACK_GUID, "Alloc stack" }, + { FSP_HOB_RESOURCE_OWNER_SMBIOS_MEMORY_GUID, "SMBIOS memory" }, + { {}, "zero-guid" }, + {} +}; + +static const char *guid_to_name(const efi_guid_t *guid) +{ + struct guid_name *entry; + + for (entry = guid_name; entry->name; entry++) { + if (!guidcmp(guid, &entry->guid)) + return entry->name; + } + + return NULL; +} + +static void show_hob_details(const struct hob_header *hdr) +{ + const void *ptr = hdr; + + switch (hdr->type) { + case HOB_TYPE_RES_DESC: { + const struct hob_res_desc *res = ptr; + const char *typename; + + typename = res->type >= RES_SYS_MEM && res->type <= RES_MAX_MEM_TYPE ? + res_type[res->type] : "unknown"; + + printf(" base = %08llx, len = %08llx, end = %08llx, type = %d (%s)\n\n", + res->phys_start, res->len, res->phys_start + res->len, + res->type, typename); + break; + } + } +} + +static int do_hob(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + const struct hob_header *hdr; + uint type; + char *desc; + int i = 0; + efi_guid_t *guid; + char uuid[UUID_STR_LEN + 1]; + bool verbose = false; + int seq = -1; /* Show all by default */ + + argc--; + argv++; + if (argc) { + if (!strcmp("-v", *argv)) { + verbose = true; + argc--; + argv++; + } + if (argc) + seq = simple_strtol(*argv, NULL, 16); + } + hdr = gd->arch.hob_list; + + printf("HOB list address: 0x%08x\n\n", (unsigned int)hdr); + + printf("# | Address | Type | Len | "); + printf("%36s\n", "GUID"); + printf("---|----------|-----------|------|-"); + printf("------------------------------------\n"); + for (i = 0; !end_of_hob(hdr); i++, hdr = get_next_hob(hdr)) { + if (seq != -1 && seq != i) + continue; + printf("%02x | %08x | ", i, (unsigned int)hdr); + type = hdr->type; + if (type == HOB_TYPE_UNUSED) + desc = "*Unused*"; + else if (type == HOB_TYPE_EOH) + desc = "*EOH*"; + else if (type >= 0 && type <= ARRAY_SIZE(hob_type)) + desc = hob_type[type]; + else + desc = "*Invalid*"; + printf("%-9s | %04x | ", desc, hdr->len); + + if (type == HOB_TYPE_MEM_ALLOC || type == HOB_TYPE_RES_DESC || + type == HOB_TYPE_GUID_EXT) { + const char *name; + + guid = (efi_guid_t *)(hdr + 1); + name = guid_to_name(guid); + if (!name) { + uuid_bin_to_str(guid->b, uuid, + UUID_STR_FORMAT_GUID); + name = uuid; + } + printf("%36s", name); + } else { + printf("%36s", "Not Available"); + } + printf("\n"); + if (verbose) + show_hob_details(hdr); + } + + return 0; +} + +U_BOOT_CMD(hob, 3, 1, do_hob, + "[-v] [seq] Print Hand-Off Block (HOB) information", + " -v - Show detailed HOB information where available\n" + " seq - Record # to show (all by default)" +); diff --git a/cmd/x86/msr.c b/cmd/x86/msr.c new file mode 100644 index 00000000000..3cb70d1f89a --- /dev/null +++ b/cmd/x86/msr.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * The 'cpuid' command provides access to the CPU's cpuid information + * + * Copyright 2024 Google, LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <command.h> +#include <vsprintf.h> +#include <asm/msr.h> + +static int do_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct msr_t msr; + ulong op; + + if (argc < 2) + return CMD_RET_USAGE; + + op = hextoul(argv[1], NULL); + msr = msr_read(op); + printf("%08x %08x\n", msr.hi, msr.lo); + + return 0; +} + +static int do_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct msr_t msr; + ulong op; + + if (argc < 4) + return CMD_RET_USAGE; + + op = hextoul(argv[1], NULL); + msr.hi = hextoul(argv[2], NULL); + msr.lo = hextoul(argv[3], NULL); + msr_write(op, msr); + + return 0; +} + +U_BOOT_LONGHELP(msr, + "read <op> - read a machine-status register (MSR) as <hi 32-bits> <lo 32-bits>\n" + "write <op< <hi> <lo> - write an MSR"); + +U_BOOT_CMD_WITH_SUBCMDS(msr, "Machine Status Registers", msr_help_text, + U_BOOT_CMD_MKENT(read, CONFIG_SYS_MAXARGS, 1, do_read, "", ""), + U_BOOT_CMD_MKENT(write, CONFIG_SYS_MAXARGS, 1, do_write, "", "")); diff --git a/cmd/x86/mtrr.c b/cmd/x86/mtrr.c new file mode 100644 index 00000000000..91130453039 --- /dev/null +++ b/cmd/x86/mtrr.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2014 Google, Inc + */ + +#include <command.h> +#include <log.h> +#include <vsprintf.h> +#include <linux/string.h> +#include <asm/msr.h> +#include <asm/mp.h> +#include <asm/mtrr.h> + +static int do_mtrr_set(int cpu_select, uint reg, int argc, char *const argv[]) +{ + const char *typename = argv[0]; + u64 start, size; + u64 base, mask; + int type = -1; + bool valid; + int ret; + + if (argc < 3) + return CMD_RET_USAGE; + type = mtrr_get_type_by_name(typename); + if (type < 0) { + printf("Invalid type name %s\n", typename); + return CMD_RET_USAGE; + } + start = hextoull(argv[1], NULL); + size = hextoull(argv[2], NULL); + + base = start | type; + valid = native_read_msr(MTRR_PHYS_MASK_MSR(reg)) & MTRR_PHYS_MASK_VALID; + mask = mtrr_to_mask(size); + if (valid) + mask |= MTRR_PHYS_MASK_VALID; + + ret = mtrr_set(cpu_select, reg, base, mask); + if (ret) + return CMD_RET_FAILURE; + + return 0; +} + +static int do_mtrr(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int reg_count = mtrr_get_var_count(); + int cmd; + int cpu_select; + uint reg; + int ret; + + cpu_select = MP_SELECT_BSP; + if (argc >= 3 && !strcmp("-c", argv[1])) { + const char *cpustr; + + cpustr = argv[2]; + if (*cpustr == 'a') + cpu_select = MP_SELECT_ALL; + else + cpu_select = simple_strtol(cpustr, NULL, 16); + argc -= 2; + argv += 2; + } + argc--; + argv++; + cmd = argv[0] ? *argv[0] : 0; + if (argc < 1 || !cmd) { + cmd = 'l'; + reg = 0; + } + if (cmd != 'l') { + if (argc < 2) + return CMD_RET_USAGE; + reg = hextoul(argv[1], NULL); + if (reg >= reg_count) { + printf("Invalid register number\n"); + return CMD_RET_USAGE; + } + } + if (cmd == 'l') { + bool first; + int i; + + i = mp_first_cpu(cpu_select); + if (i < 0) { + printf("Invalid CPU (err=%d)\n", i); + return CMD_RET_FAILURE; + } + first = true; + for (; i >= 0; i = mp_next_cpu(cpu_select, i)) { + if (!first) + printf("\n"); + printf("CPU %d:\n", i); + ret = mtrr_list(reg_count, i); + if (ret) { + printf("Failed to read CPU %s (err=%d)\n", + i < MP_SELECT_ALL ? simple_itoa(i) : "", + ret); + return CMD_RET_FAILURE; + } + first = false; + } + } else { + switch (cmd) { + case 'e': + ret = mtrr_set_valid(cpu_select, reg, true); + break; + case 'd': + ret = mtrr_set_valid(cpu_select, reg, false); + break; + case 's': + ret = do_mtrr_set(cpu_select, reg, argc - 2, argv + 2); + break; + default: + return CMD_RET_USAGE; + } + if (ret) { + printf("Operation failed (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + } + + return 0; +} + +U_BOOT_CMD( + mtrr, 8, 1, do_mtrr, + "Use x86 memory type range registers (32-bit only)", + "[list] - list current registers\n" + "set <reg> <type> <start> <size> - set a register\n" + "\t<type> is Uncacheable, Combine, Through, Protect, Back\n" + "disable <reg> - disable a register\n" + "enable <reg> - enable a register\n" + "\n" + "Precede command with '-c <n>|all' to access a particular hex CPU, e.g.\n" + " mtrr -c all list; mtrr -c 2e list" +); diff --git a/cmd/x86/zboot.c b/cmd/x86/zboot.c new file mode 100644 index 00000000000..3876d163236 --- /dev/null +++ b/cmd/x86/zboot.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. + * (C) Copyright 2002 + * Daniel Engström, Omicron Ceti AB, <daniel@omicron.se> + */ + +#define LOG_CATEGORY LOGC_BOOT + +#include <command.h> +#include <env.h> +#include <mapmem.h> +#include <vsprintf.h> +#include <asm/zimage.h> + +static int do_zboot_start(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong bzimage_addr = 0, bzimage_size, initrd_addr, initrd_size; + const char *s, *cmdline; + ulong base_addr; + int i; + + log_debug("argc %d:", argc); + for (i = 0; i < argc; i++) + log_debug(" %s", argv[i]); + log_debug("\n"); + + /* argv[1] holds the address of the bzImage */ + s = cmd_arg1(argc, argv) ? : env_get("fileaddr"); + if (s) + bzimage_addr = hextoul(s, NULL); + bzimage_size = argc > 2 ? hextoul(argv[2], NULL) : 0; + initrd_addr = argc > 3 ? hextoul(argv[3], NULL) : 0; + initrd_size = argc > 4 ? hextoul(argv[4], NULL) : 0; + base_addr = argc > 5 ? hextoul(argv[5], NULL) : 0; + cmdline = argc > 6 ? env_get(argv[6]) : NULL; + + zboot_start(bzimage_addr, bzimage_size, initrd_addr, initrd_size, + base_addr, cmdline); + + return 0; +} + +static int do_zboot_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + ret = zboot_load(); + if (ret) + return ret; + + return 0; +} + +static int do_zboot_setup(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + if (!state.base_ptr) { + printf("base is not set: use 'zboot load' first\n"); + return CMD_RET_FAILURE; + } + if (zboot_setup()) { + puts("Setting up boot parameters failed ...\n"); + return CMD_RET_FAILURE; + } + + if (zboot_setup()) + return CMD_RET_FAILURE; + + return 0; +} + +static int do_zboot_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + zboot_info(); + + return 0; +} + +static int do_zboot_go(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret; + + ret = zboot_go(); + if (ret) { + printf("Kernel returned! (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +static int do_zboot_dump(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct boot_params *base_ptr = state.base_ptr; + + if (argc > 1) + base_ptr = (void *)hextoul(argv[1], NULL); + if (!base_ptr) { + printf("No zboot setup_base\n"); + return CMD_RET_FAILURE; + } + zimage_dump(base_ptr, true); + + return 0; +} + +/* Note: This defines the complete_zboot() function */ +U_BOOT_SUBCMDS(zboot, + U_BOOT_CMD_MKENT(start, 8, 1, do_zboot_start, "", ""), + U_BOOT_CMD_MKENT(load, 1, 1, do_zboot_load, "", ""), + U_BOOT_CMD_MKENT(setup, 1, 1, do_zboot_setup, "", ""), + U_BOOT_CMD_MKENT(info, 1, 1, do_zboot_info, "", ""), + U_BOOT_CMD_MKENT(go, 1, 1, do_zboot_go, "", ""), + U_BOOT_CMD_MKENT(dump, 2, 1, do_zboot_dump, "", ""), +) + +int do_zboot_states(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[], int state_mask) +{ + int ret = 0; + + log_debug("state_mask %x\n", state_mask); + if (state_mask & ZBOOT_STATE_START) + ret = do_zboot_start(cmdtp, flag, argc, argv); + if (!ret && (state_mask & ZBOOT_STATE_LOAD)) + ret = do_zboot_load(cmdtp, flag, argc, argv); + if (!ret && (state_mask & ZBOOT_STATE_SETUP)) + ret = do_zboot_setup(cmdtp, flag, argc, argv); + if (!ret && (state_mask & ZBOOT_STATE_INFO)) + ret = do_zboot_info(cmdtp, flag, argc, argv); + if (!ret && (state_mask & ZBOOT_STATE_GO)) + ret = do_zboot_go(cmdtp, flag, argc, argv); + if (ret) + return ret; + + return 0; +} + +int do_zboot_parent(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[], int *repeatable) +{ + /* determine if we have a sub command */ + if (argc > 1) { + char *endp; + + hextoul(argv[1], &endp); + /* + * endp pointing to nul means that argv[1] was just a valid + * number, so pass it along to the normal processing + */ + if (*endp) + return do_zboot(cmdtp, flag, argc, argv, repeatable); + } + + do_zboot_states(cmdtp, flag, argc, argv, ZBOOT_STATE_START | + ZBOOT_STATE_LOAD | ZBOOT_STATE_SETUP | + ZBOOT_STATE_INFO | ZBOOT_STATE_GO); + + return CMD_RET_FAILURE; +} + +U_BOOT_CMDREP_COMPLETE( + zboot, 8, do_zboot_parent, "Boot bzImage", + "[addr] [size] [initrd addr] [initrd size] [setup] [cmdline]\n" + " addr - The optional starting address of the bzimage.\n" + " If not set it defaults to the environment\n" + " variable \"fileaddr\".\n" + " size - The optional size of the bzimage. Defaults to\n" + " zero.\n" + " initrd addr - The address of the initrd image to use, if any.\n" + " initrd size - The size of the initrd image to use, if any.\n" + " setup - The address of the kernel setup region, if this\n" + " is not at addr\n" + " cmdline - Environment variable containing the kernel\n" + " command line, to override U-Boot's normal\n" + " cmdline generation\n" + "\n" + "Sub-commands to do part of the zboot sequence:\n" + "\tstart [addr [arg ...]] - specify arguments\n" + "\tload - load OS image\n" + "\tsetup - set up table\n" + "\tinfo - show summary info\n" + "\tgo - start OS\n" + "\tdump [addr] - dump info (optional address of boot params)", + complete_zboot +); diff --git a/cmd/ximg.c b/cmd/ximg.c new file mode 100644 index 00000000000..e97167a79cc --- /dev/null +++ b/cmd/ximg.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2004 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * (C) Copyright 2003 + * Kai-Uwe Bloem, Auerswald GmbH & Co KG, <linux-development@auerswald.de> + */ + +/* + * Multi Image extract + */ +#include <command.h> +#include <cpu_func.h> +#include <env.h> +#include <gzip.h> +#if IS_ENABLED(CONFIG_ZSTD) +#include <linux/zstd.h> +#endif +#include <image.h> +#include <malloc.h> +#include <mapmem.h> +#include <watchdog.h> +#if defined(CONFIG_BZIP2) +#include <bzlib.h> +#endif +#include <asm/byteorder.h> +#include <asm/cache.h> +#include <asm/io.h> +#include <u-boot/zlib.h> + +static int +do_imgextract(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + ulong addr = image_load_addr; + ulong dest = 0; + ulong data, len; + int verify; + int part = 0; +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + ulong count; + struct legacy_img_hdr *hdr = NULL; +#endif +#if defined(CONFIG_FIT) + const char *uname = NULL; + const void* fit_hdr; + int noffset; + const void *fit_data; + size_t fit_len; +#endif +#ifdef CONFIG_GZIP + uint unc_len = CONFIG_SYS_XIMG_LEN; +#endif + uint8_t comp; + + verify = env_get_yesno("verify"); + + if (argc > 1) { + addr = hextoul(argv[1], NULL); + } + if (argc > 2) { + part = hextoul(argv[2], NULL); +#if defined(CONFIG_FIT) + uname = argv[2]; +#endif + } + if (argc > 3) { + dest = hextoul(argv[3], NULL); + } + + switch (genimg_get_format((void *)addr)) { +#if defined(CONFIG_LEGACY_IMAGE_FORMAT) + case IMAGE_FORMAT_LEGACY: + + printf("## Copying part %d from legacy image " + "at %08lx ...\n", part, addr); + + hdr = (struct legacy_img_hdr *)addr; + if (!image_check_magic(hdr)) { + printf("Bad Magic Number\n"); + return 1; + } + + if (!image_check_hcrc(hdr)) { + printf("Bad Header Checksum\n"); + return 1; + } +#ifdef DEBUG + image_print_contents(hdr); +#endif + + if (!image_check_type(hdr, IH_TYPE_MULTI) && + !image_check_type(hdr, IH_TYPE_SCRIPT)) { + printf("Wrong Image Type for %s command\n", + cmdtp->name); + return 1; + } + + comp = image_get_comp(hdr); + if ((comp != IH_COMP_NONE) && (argc < 4)) { + printf("Must specify load address for %s command " + "with compressed image\n", + cmdtp->name); + return 1; + } + + if (verify) { + printf(" Verifying Checksum ... "); + if (!image_check_dcrc(hdr)) { + printf("Bad Data CRC\n"); + return 1; + } + printf("OK\n"); + } + + count = image_multi_count(hdr); + if (part >= count) { + printf("Bad Image Part\n"); + return 1; + } + + image_multi_getimg(hdr, part, &data, &len); + break; +#endif +#if defined(CONFIG_FIT) + case IMAGE_FORMAT_FIT: + if (uname == NULL) { + puts("No FIT subimage unit name\n"); + return 1; + } + + printf("## Copying '%s' subimage from FIT image " + "at %08lx ...\n", uname, addr); + + fit_hdr = (const void *)addr; + if (fit_check_format(fit_hdr, IMAGE_SIZE_INVAL)) { + puts("Bad FIT image format\n"); + return 1; + } + + /* get subimage node offset */ + noffset = fit_image_get_node(fit_hdr, uname); + if (noffset < 0) { + printf("Can't find '%s' FIT subimage\n", uname); + return 1; + } + + if (!fit_image_check_comp(fit_hdr, noffset, IH_COMP_NONE) + && (argc < 4)) { + printf("Must specify load address for %s command " + "with compressed image\n", + cmdtp->name); + return 1; + } + + /* verify integrity */ + if (verify) { + if (!fit_image_verify(fit_hdr, noffset)) { + puts("Bad Data Hash\n"); + return 1; + } + } + + /* get subimage/external data address and length */ + if (fit_image_get_data(fit_hdr, noffset, &fit_data, &fit_len)) { + puts("Could not find script subimage data\n"); + return 1; + } + + if (fit_image_get_comp(fit_hdr, noffset, &comp)) + comp = IH_COMP_NONE; + + data = (ulong)fit_data; + len = (ulong)fit_len; + break; +#endif + default: + puts("Invalid image type for imxtract\n"); + return 1; + } + + if (argc > 3) { + switch (comp) { + case IH_COMP_NONE: +#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG) + { + size_t l = len; + size_t tail; + void *to = (void *) dest; + void *from = (void *)data; + + printf(" Loading part %d ... ", part); + + while (l > 0) { + tail = (l > CHUNKSZ) ? CHUNKSZ : l; + schedule(); + memmove(to, from, tail); + to += tail; + from += tail; + l -= tail; + } + } +#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */ + printf(" Loading part %d ... ", part); + memmove((char *) dest, (char *)data, len); +#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */ + break; +#ifdef CONFIG_GZIP + case IH_COMP_GZIP: + { + int ret = 0; + printf(" Uncompressing part %d ... ", part); + ret = gunzip((void *)dest, unc_len, + (uchar *)data, &len); + if (ret == Z_BUF_ERROR) { + puts("Image too large: increase CONFIG_SYS_XIMG_LEN\n"); + return 1; + } else if (ret != 0) { + puts("GUNZIP ERROR - image not loaded\n"); + return 1; + } + } + break; +#endif +#if defined(CONFIG_BZIP2) && defined(CONFIG_LEGACY_IMAGE_FORMAT) + case IH_COMP_BZIP2: + { + int i; + + printf(" Uncompressing part %d ... ", part); + /* + * If we've got less than 4 MB of malloc() + * space, use slower decompression algorithm + * which requires at most 2300 KB of memory. + */ + i = BZ2_bzBuffToBuffDecompress( + map_sysmem(ntohl(hdr->ih_load), 0), + &unc_len, (char *)data, len, + CONFIG_SYS_MALLOC_LEN < (4096 * 1024), + 0); + if (i != BZ_OK) { + printf("BUNZIP2 ERROR %d - " + "image not loaded\n", i); + return 1; + } + } + break; +#endif /* CONFIG_BZIP2 */ +#if IS_ENABLED(CONFIG_ZSTD) + case IH_COMP_ZSTD: + { + int ret; + struct abuf in, out; + + printf(" Uncompressing part %d ... ", part); + + abuf_init_set(&in, (void *)data, len); + abuf_init_set(&out, (void *)dest, unc_len); + ret = zstd_decompress(&in, &out); + if (ret < 0) { + printf("ZSTD ERROR %d - " + "image not loaded\n", ret); + return 1; + } + len = ret; + } + break; +#endif + default: + printf("Unimplemented compression type %d\n", comp); + return 1; + } + puts("OK\n"); + } + + flush_cache(dest, ALIGN(len, ARCH_DMA_MINALIGN)); + + env_set_hex("fileaddr", data); + env_set_hex("filesize", len); + + return 0; +} + +U_BOOT_LONGHELP(imgextract, + "addr part [dest]\n" + " - extract <part> from legacy image at <addr> and copy to <dest>" +#if defined(CONFIG_FIT) + "\n" + "addr uname [dest]\n" + " - extract <uname> subimage from FIT image at <addr> and copy to <dest>" +#endif + ); + +U_BOOT_CMD( + imxtract, 4, 1, do_imgextract, + "extract a part of a multi-image", imgextract_help_text +); diff --git a/cmd/xxd.c b/cmd/xxd.c new file mode 100644 index 00000000000..8ae05f910cb --- /dev/null +++ b/cmd/xxd.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 + * Roger Knecht <rknecht@pm.de> + */ + +#include <command.h> +#include <display_options.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> + +static int do_xxd(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *ifname; + char *dev; + char *file; + char *buffer; + phys_addr_t addr; + loff_t file_size; + + if (argc < 4) + return CMD_RET_USAGE; + + ifname = argv[1]; + dev = argv[2]; + file = argv[3]; + + // check file exists + if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY)) + return CMD_RET_FAILURE; + + if (!fs_exists(file)) { + log_err("File does not exist: ifname=%s dev=%s file=%s\n", ifname, dev, file); + return CMD_RET_FAILURE; + } + + // get file size + if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY)) + return CMD_RET_FAILURE; + + if (fs_size(file, &file_size)) { + log_err("Cannot read file size: ifname=%s dev=%s file=%s\n", ifname, dev, file); + return CMD_RET_FAILURE; + } + + // allocate memory for file content + buffer = calloc(sizeof(char), file_size); + if (!buffer) { + log_err("Out of memory\n"); + return CMD_RET_FAILURE; + } + + // map pointer to system memory + addr = map_to_sysmem(buffer); + + // read file to memory + if (fs_set_blk_dev(ifname, dev, FS_TYPE_ANY)) + return CMD_RET_FAILURE; + + if (fs_read(file, addr, 0, 0, &file_size)) { + log_err("Cannot read file: ifname=%s dev=%s file=%s\n", ifname, dev, file); + return CMD_RET_FAILURE; + } + + // print file content + print_buffer(0, buffer, sizeof(char), file_size, 0); + + free(buffer); + + return 0; +} + +U_BOOT_LONGHELP(xxd, + "<interface> <dev[:part]> <file>\n" + " - Print file from 'dev' on 'interface' as hexdump to standard output\n"); + +U_BOOT_CMD(xxd, 4, 1, do_xxd, + "Print file as hexdump to standard output", + xxd_help_text +); diff --git a/cmd/zfs.c b/cmd/zfs.c new file mode 100644 index 00000000000..471c63f2150 --- /dev/null +++ b/cmd/zfs.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * + * ZFS filesystem porting to Uboot by + * Jorgen Lundman <lundman at lundman.net> + * + * zfsfs support + * made from existing GRUB Sources by Sun, GNU and others. + */ + +#include <part.h> +#include <config.h> +#include <command.h> +#include <env.h> +#include <image.h> +#include <linux/ctype.h> +#include <asm/byteorder.h> +#include <zfs_common.h> +#include <linux/stat.h> +#include <malloc.h> + +#if defined(CONFIG_CMD_USB) && defined(CONFIG_USB_STORAGE) +#include <usb.h> +#endif + +#if !CONFIG_IS_ENABLED(DOS_PARTITION) && !CONFIG_IS_ENABLED(EFI_PARTITION) +#error DOS or EFI partition support must be selected +#endif + +#define DOS_PART_MAGIC_OFFSET 0x1fe +#define DOS_FS_TYPE_OFFSET 0x36 +#define DOS_FS32_TYPE_OFFSET 0x52 + +static int do_zfs_load(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + char *filename = NULL; + int dev; + int part; + ulong addr = 0; + struct disk_partition info; + struct blk_desc *dev_desc; + unsigned long count; + const char *addr_str; + struct zfs_file zfile; + struct device_s vdev; + + if (argc < 3) + return CMD_RET_USAGE; + + count = 0; + addr = hextoul(argv[3], NULL); + filename = env_get("bootfile"); + switch (argc) { + case 3: + addr_str = env_get("loadaddr"); + if (addr_str != NULL) + addr = hextoul(addr_str, NULL); + else + addr = CONFIG_SYS_LOAD_ADDR; + + break; + case 4: + break; + case 5: + filename = argv[4]; + break; + case 6: + filename = argv[4]; + count = hextoul(argv[5], NULL); + break; + + default: + return cmd_usage(cmdtp); + } + + if (!filename) { + puts("** No boot file defined **\n"); + return 1; + } + + part = blk_get_device_part_str(argv[1], argv[2], &dev_desc, &info, 1); + if (part < 0) + return 1; + + dev = dev_desc->devnum; + printf("Loading file \"%s\" from %s device %d%c%c\n", + filename, argv[1], dev, + part ? ':' : ' ', part ? part + '0' : ' '); + + zfs_set_blk_dev(dev_desc, &info); + vdev.part_length = info.size; + + memset(&zfile, 0, sizeof(zfile)); + zfile.device = &vdev; + if (zfs_open(&zfile, filename)) { + printf("** File not found %s **\n", filename); + return 1; + } + + if ((count < zfile.size) && (count != 0)) + zfile.size = (uint64_t)count; + + if (zfs_read(&zfile, (char *)addr, zfile.size) != zfile.size) { + printf("** Unable to read \"%s\" from %s %d:%d **\n", + filename, argv[1], dev, part); + zfs_close(&zfile); + return 1; + } + + zfs_close(&zfile); + + /* Loading ok, update default load address */ + image_load_addr = addr; + + printf("%llu bytes read\n", zfile.size); + env_set_hex("filesize", zfile.size); + + return 0; +} + +int zfs_print(const char *entry, const struct zfs_dirhook_info *data) +{ + printf("%s %s\n", + data->dir ? "<DIR> " : " ", + entry); + return 0; /* 0 continue, 1 stop */ +} + +static int do_zfs_ls(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *filename = "/"; + int part; + struct blk_desc *dev_desc; + struct disk_partition info; + struct device_s vdev; + + if (argc < 2) + return cmd_usage(cmdtp); + + if (argc == 4) + filename = argv[3]; + + part = blk_get_device_part_str(argv[1], argv[2], &dev_desc, &info, 1); + if (part < 0) + return 1; + + zfs_set_blk_dev(dev_desc, &info); + vdev.part_length = info.size; + + zfs_ls(&vdev, filename, + zfs_print); + + return 0; +} + +U_BOOT_CMD(zfsls, 4, 1, do_zfs_ls, + "list files in a directory (default /)", + "<interface> <dev[:part]> [directory]\n" + " - list files from 'dev' on 'interface' in a '/DATASET/@/$dir/'"); + +U_BOOT_CMD(zfsload, 6, 0, do_zfs_load, + "load binary file from a ZFS filesystem", + "<interface> <dev[:part]> [addr] [filename] [bytes]\n" + " - load binary file '/DATASET/@/$dir/$file' from 'dev' on 'interface'\n" + " to address 'addr' from ZFS filesystem"); diff --git a/cmd/zip.c b/cmd/zip.c new file mode 100644 index 00000000000..2d255428822 --- /dev/null +++ b/cmd/zip.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2012 + * Lei Wen <leiwen@marvell.com>, Marvell Inc. + */ + +#include <command.h> +#include <env.h> +#include <gzip.h> +#include <vsprintf.h> + +static int do_zip(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + unsigned long src, dst; + unsigned long src_len, dst_len = ~0UL; + + switch (argc) { + case 5: + dst_len = hextoul(argv[4], NULL); + /* fall through */ + case 4: + src = hextoul(argv[1], NULL); + src_len = hextoul(argv[2], NULL); + dst = hextoul(argv[3], NULL); + break; + default: + return cmd_usage(cmdtp); + } + + if (gzip((void *) dst, &dst_len, (void *) src, src_len) != 0) + return 1; + + printf("Compressed size: %lu = 0x%lX\n", dst_len, dst_len); + env_set_hex("filesize", dst_len); + + return 0; +} + +U_BOOT_CMD( + zip, 5, 1, do_zip, + "zip a memory region", + "srcaddr srcsize dstaddr [dstsize]" +); |