1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright (C) 2019 Eugeniu Rosca <rosca.eugeniu@gmail.com> 4 * 5 * Command to read/modify/write Android BCB fields 6 */ 7 8#include <android_bootloader_message.h> 9#include <bcb.h> 10#include <command.h> 11#include <common.h> 12#include <display_options.h> 13#include <log.h> 14#include <part.h> 15#include <malloc.h> 16#include <memalign.h> 17#include <linux/err.h> 18 19enum bcb_cmd { 20 BCB_CMD_LOAD, 21 BCB_CMD_FIELD_SET, 22 BCB_CMD_FIELD_CLEAR, 23 BCB_CMD_FIELD_TEST, 24 BCB_CMD_FIELD_DUMP, 25 BCB_CMD_STORE, 26}; 27 28static const char * const fields[] = { 29 "command", 30 "status", 31 "recovery", 32 "stage" 33}; 34 35static struct bootloader_message bcb __aligned(ARCH_DMA_MINALIGN) = { { 0 } }; 36static struct disk_partition partition_data; 37 38static struct blk_desc *block; 39static struct disk_partition *partition = &partition_data; 40 41static int bcb_cmd_get(char *cmd) 42{ 43 if (!strcmp(cmd, "load")) 44 return BCB_CMD_LOAD; 45 if (!strcmp(cmd, "set")) 46 return BCB_CMD_FIELD_SET; 47 if (!strcmp(cmd, "clear")) 48 return BCB_CMD_FIELD_CLEAR; 49 if (!strcmp(cmd, "test")) 50 return BCB_CMD_FIELD_TEST; 51 if (!strcmp(cmd, "store")) 52 return BCB_CMD_STORE; 53 if (!strcmp(cmd, "dump")) 54 return BCB_CMD_FIELD_DUMP; 55 else 56 return -1; 57} 58 59static int bcb_is_misused(int argc, char *const argv[]) 60{ 61 int cmd = bcb_cmd_get(argv[0]); 62 63 switch (cmd) { 64 case BCB_CMD_LOAD: 65 if (argc != 3 && argc != 4) 66 goto err; 67 break; 68 case BCB_CMD_FIELD_SET: 69 if (argc != 3) 70 goto err; 71 break; 72 case BCB_CMD_FIELD_TEST: 73 if (argc != 4) 74 goto err; 75 break; 76 case BCB_CMD_FIELD_CLEAR: 77 if (argc != 1 && argc != 2) 78 goto err; 79 break; 80 case BCB_CMD_STORE: 81 if (argc != 1) 82 goto err; 83 break; 84 case BCB_CMD_FIELD_DUMP: 85 if (argc != 2) 86 goto err; 87 break; 88 default: 89 printf("Error: 'bcb %s' not supported\n", argv[0]); 90 return -1; 91 } 92 93 if (cmd != BCB_CMD_LOAD && !block) { 94 printf("Error: Please, load BCB first!\n"); 95 return -1; 96 } 97 98 return 0; 99err: 100 printf("Error: Bad usage of 'bcb %s'\n", argv[0]); 101 102 return -1; 103} 104 105static int bcb_field_get(const char *name, char **fieldp, int *sizep) 106{ 107 if (!strcmp(name, "command")) { 108 *fieldp = bcb.command; 109 *sizep = sizeof(bcb.command); 110 } else if (!strcmp(name, "status")) { 111 *fieldp = bcb.status; 112 *sizep = sizeof(bcb.status); 113 } else if (!strcmp(name, "recovery")) { 114 *fieldp = bcb.recovery; 115 *sizep = sizeof(bcb.recovery); 116 } else if (!strcmp(name, "stage")) { 117 *fieldp = bcb.stage; 118 *sizep = sizeof(bcb.stage); 119 } else if (!strcmp(name, "reserved")) { 120 *fieldp = bcb.reserved; 121 *sizep = sizeof(bcb.reserved); 122 } else { 123 printf("Error: Unknown bcb field '%s'\n", name); 124 return -1; 125 } 126 127 return 0; 128} 129 130static void __bcb_reset(void) 131{ 132 block = NULL; 133 partition = &partition_data; 134 memset(&partition_data, 0, sizeof(struct disk_partition)); 135 memset(&bcb, 0, sizeof(struct bootloader_message)); 136} 137 138static int __bcb_initialize(const char *iface, int devnum, const char *partp) 139{ 140 char *endp; 141 int part, ret; 142 143 block = blk_get_dev(iface, devnum); 144 if (!block) { 145 ret = -ENODEV; 146 goto err_read_fail; 147 } 148 149 /* 150 * always select the first hwpart in case another 151 * blk operation selected a different hwpart 152 */ 153 ret = blk_dselect_hwpart(block, 0); 154 if (IS_ERR_VALUE(ret)) { 155 ret = -ENODEV; 156 goto err_read_fail; 157 } 158 159 part = simple_strtoul(partp, &endp, 0); 160 if (*endp == '\0') { 161 ret = part_get_info(block, part, partition); 162 if (ret) 163 goto err_read_fail; 164 } else { 165 part = part_get_info_by_name(block, partp, partition); 166 if (part < 0) { 167 ret = part; 168 goto err_read_fail; 169 } 170 } 171 172 return CMD_RET_SUCCESS; 173 174err_read_fail: 175 printf("Error: %d %d:%s read failed (%d)\n", block->uclass_id, 176 block->devnum, partition->name, ret); 177 __bcb_reset(); 178 return CMD_RET_FAILURE; 179} 180 181static int __bcb_load(void) 182{ 183 u64 cnt; 184 int ret; 185 186 cnt = DIV_ROUND_UP(sizeof(struct bootloader_message), partition->blksz); 187 if (cnt > partition->size) 188 goto err_too_small; 189 190 if (blk_dread(block, partition->start, cnt, &bcb) != cnt) { 191 ret = -EIO; 192 goto err_read_fail; 193 } 194 195 debug("%s: Loaded from %d %d:%s\n", __func__, block->uclass_id, 196 block->devnum, partition->name); 197 198 return CMD_RET_SUCCESS; 199err_read_fail: 200 printf("Error: %d %d:%s read failed (%d)\n", block->uclass_id, 201 block->devnum, partition->name, ret); 202 goto err; 203err_too_small: 204 printf("Error: %d %d:%s too small!", block->uclass_id, 205 block->devnum, partition->name); 206err: 207 __bcb_reset(); 208 return CMD_RET_FAILURE; 209} 210 211static int do_bcb_load(struct cmd_tbl *cmdtp, int flag, int argc, 212 char * const argv[]) 213{ 214 int ret; 215 int devnum; 216 char *endp; 217 char *iface = "mmc"; 218 219 if (argc == 4) { 220 iface = argv[1]; 221 argc--; 222 argv++; 223 } 224 225 devnum = simple_strtoul(argv[1], &endp, 0); 226 if (*endp != '\0') { 227 printf("Error: Device id '%s' not a number\n", argv[1]); 228 return CMD_RET_FAILURE; 229 } 230 231 ret = __bcb_initialize(iface, devnum, argv[2]); 232 if (ret != CMD_RET_SUCCESS) 233 return ret; 234 235 return __bcb_load(); 236} 237 238static int __bcb_set(const char *fieldp, const char *valp) 239{ 240 int size, len; 241 char *field, *str, *found, *tmp; 242 243 if (bcb_field_get(fieldp, &field, &size)) 244 return CMD_RET_FAILURE; 245 246 len = strlen(valp); 247 if (len >= size) { 248 printf("Error: sizeof('%s') = %d >= %d = sizeof(bcb.%s)\n", 249 valp, len, size, fieldp); 250 return CMD_RET_FAILURE; 251 } 252 str = strdup(valp); 253 if (!str) { 254 printf("Error: Out of memory while strdup\n"); 255 return CMD_RET_FAILURE; 256 } 257 258 tmp = str; 259 field[0] = '\0'; 260 while ((found = strsep(&tmp, ":"))) { 261 if (field[0] != '\0') 262 strcat(field, "\n"); 263 strcat(field, found); 264 } 265 free(str); 266 267 return CMD_RET_SUCCESS; 268} 269 270static int do_bcb_set(struct cmd_tbl *cmdtp, int flag, int argc, 271 char * const argv[]) 272{ 273 return __bcb_set(argv[1], argv[2]); 274} 275 276static int do_bcb_clear(struct cmd_tbl *cmdtp, int flag, int argc, 277 char *const argv[]) 278{ 279 int size; 280 char *field; 281 282 if (argc == 1) { 283 memset(&bcb, 0, sizeof(bcb)); 284 return CMD_RET_SUCCESS; 285 } 286 287 if (bcb_field_get(argv[1], &field, &size)) 288 return CMD_RET_FAILURE; 289 290 memset(field, 0, size); 291 292 return CMD_RET_SUCCESS; 293} 294 295static int do_bcb_test(struct cmd_tbl *cmdtp, int flag, int argc, 296 char *const argv[]) 297{ 298 int size; 299 char *field; 300 char *op = argv[2]; 301 302 if (bcb_field_get(argv[1], &field, &size)) 303 return CMD_RET_FAILURE; 304 305 if (*op == '=' && *(op + 1) == '\0') { 306 if (!strncmp(argv[3], field, size)) 307 return CMD_RET_SUCCESS; 308 else 309 return CMD_RET_FAILURE; 310 } else if (*op == '~' && *(op + 1) == '\0') { 311 if (!strstr(field, argv[3])) 312 return CMD_RET_FAILURE; 313 else 314 return CMD_RET_SUCCESS; 315 } else { 316 printf("Error: Unknown operator '%s'\n", op); 317 } 318 319 return CMD_RET_FAILURE; 320} 321 322static int do_bcb_dump(struct cmd_tbl *cmdtp, int flag, int argc, 323 char *const argv[]) 324{ 325 int size; 326 char *field; 327 328 if (bcb_field_get(argv[1], &field, &size)) 329 return CMD_RET_FAILURE; 330 331 print_buffer((ulong)field - (ulong)&bcb, (void *)field, 1, size, 16); 332 333 return CMD_RET_SUCCESS; 334} 335 336static int __bcb_store(void) 337{ 338 u64 cnt; 339 int ret; 340 341 cnt = DIV_ROUND_UP(sizeof(struct bootloader_message), partition->blksz); 342 343 if (blk_dwrite(block, partition->start, cnt, &bcb) != cnt) { 344 ret = -EIO; 345 goto err; 346 } 347 348 return CMD_RET_SUCCESS; 349err: 350 printf("Error: %d %d:%s write failed (%d)\n", block->uclass_id, 351 block->devnum, partition->name, ret); 352 353 return CMD_RET_FAILURE; 354} 355 356static int do_bcb_store(struct cmd_tbl *cmdtp, int flag, int argc, 357 char * const argv[]) 358{ 359 return __bcb_store(); 360} 361 362int bcb_find_partition_and_load(const char *iface, int devnum, char *partp) 363{ 364 int ret; 365 366 __bcb_reset(); 367 368 ret = __bcb_initialize(iface, devnum, partp); 369 if (ret != CMD_RET_SUCCESS) 370 return ret; 371 372 return __bcb_load(); 373} 374 375int bcb_load(struct blk_desc *block_description, struct disk_partition *disk_partition) 376{ 377 __bcb_reset(); 378 379 block = block_description; 380 partition = disk_partition; 381 382 return __bcb_load(); 383} 384 385int bcb_set(enum bcb_field field, const char *value) 386{ 387 if (field > BCB_FIELD_STAGE) 388 return CMD_RET_FAILURE; 389 return __bcb_set(fields[field], value); 390} 391 392int bcb_get(enum bcb_field field, char *value_out, size_t value_size) 393{ 394 int size; 395 char *field_value; 396 397 if (field > BCB_FIELD_STAGE) 398 return CMD_RET_FAILURE; 399 if (bcb_field_get(fields[field], &field_value, &size)) 400 return CMD_RET_FAILURE; 401 402 strlcpy(value_out, field_value, value_size); 403 404 return CMD_RET_SUCCESS; 405} 406 407int bcb_store(void) 408{ 409 return __bcb_store(); 410} 411 412void bcb_reset(void) 413{ 414 __bcb_reset(); 415} 416 417static struct cmd_tbl cmd_bcb_sub[] = { 418 U_BOOT_CMD_MKENT(load, CONFIG_SYS_MAXARGS, 1, do_bcb_load, "", ""), 419 U_BOOT_CMD_MKENT(set, CONFIG_SYS_MAXARGS, 1, do_bcb_set, "", ""), 420 U_BOOT_CMD_MKENT(clear, CONFIG_SYS_MAXARGS, 1, do_bcb_clear, "", ""), 421 U_BOOT_CMD_MKENT(test, CONFIG_SYS_MAXARGS, 1, do_bcb_test, "", ""), 422 U_BOOT_CMD_MKENT(dump, CONFIG_SYS_MAXARGS, 1, do_bcb_dump, "", ""), 423 U_BOOT_CMD_MKENT(store, CONFIG_SYS_MAXARGS, 1, do_bcb_store, "", ""), 424}; 425 426static int do_bcb(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) 427{ 428 struct cmd_tbl *c; 429 430 if (argc < 2) 431 return CMD_RET_USAGE; 432 433 argc--; 434 argv++; 435 436 c = find_cmd_tbl(argv[0], cmd_bcb_sub, ARRAY_SIZE(cmd_bcb_sub)); 437 if (!c) 438 return CMD_RET_USAGE; 439 440 if (bcb_is_misused(argc, argv)) { 441 /* 442 * We try to improve the user experience by reporting the 443 * root-cause of misusage, so don't return CMD_RET_USAGE, 444 * since the latter prints out the full-blown help text 445 */ 446 return CMD_RET_FAILURE; 447 } 448 449 return c->cmd(cmdtp, flag, argc, argv); 450} 451 452U_BOOT_CMD( 453 bcb, CONFIG_SYS_MAXARGS, 1, do_bcb, 454 "Load/set/clear/test/dump/store Android BCB fields", 455 "load <interface> <dev> <part> - load BCB from <interface> <dev>:<part>\n" 456 "load <dev> <part> - load BCB from mmc <dev>:<part>\n" 457 "bcb set <field> <val> - set BCB <field> to <val>\n" 458 "bcb clear [<field>] - clear BCB <field> or all fields\n" 459 "bcb test <field> <op> <val> - test BCB <field> against <val>\n" 460 "bcb dump <field> - dump BCB <field>\n" 461 "bcb store - store BCB back to <interface>\n" 462 "\n" 463 "Legend:\n" 464 "<interface> - storage device interface (virtio, mmc, etc)\n" 465 "<dev> - storage device index containing the BCB partition\n" 466 "<part> - partition index or name containing the BCB\n" 467 "<field> - one of {command,status,recovery,stage,reserved}\n" 468 "<op> - the binary operator used in 'bcb test':\n" 469 " '=' returns true if <val> matches the string stored in <field>\n" 470 " '~' returns true if <val> matches a subset of <field>'s string\n" 471 "<val> - string/text provided as input to bcb {set,test}\n" 472 " NOTE: any ':' character in <val> will be replaced by line feed\n" 473 " during 'bcb set' and used as separator by upper layers\n" 474); 475