Stefan Roese | 540a2bc | 2020-11-30 13:14:23 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright (C) 2020 Marvell International Ltd. |
| 4 | */ |
| 5 | |
| 6 | #include <stdio.h> |
| 7 | #include <stdint.h> |
| 8 | #include <stddef.h> |
| 9 | #include <sys/types.h> |
| 10 | #include <sys/stat.h> |
| 11 | #include <fcntl.h> |
| 12 | #include <unistd.h> |
| 13 | #include <stdbool.h> |
| 14 | #include <stdlib.h> |
| 15 | #include <string.h> |
| 16 | #include <getopt.h> |
| 17 | #include <arpa/inet.h> |
| 18 | #include <linux/compiler.h> |
| 19 | #include <u-boot/crc.h> |
| 20 | |
| 21 | #include "mkimage.h" |
| 22 | |
| 23 | #include "../arch/mips/mach-octeon/include/mach/cvmx-bootloader.h" |
| 24 | |
| 25 | #define BUF_SIZE (16 * 1024) |
| 26 | #define NAME_LEN 100 |
| 27 | |
| 28 | /* word offset */ |
| 29 | #define WOFFSETOF(type, elem) (offsetof(type, elem) / 4) |
| 30 | |
| 31 | static int stage2_flag; |
| 32 | static int stage_1_5_flag; |
| 33 | static int stage_1_flag; |
| 34 | |
| 35 | /* Getoptions variables must be global */ |
| 36 | static int failsafe_flag; |
| 37 | static int pciboot_flag; |
| 38 | static int env_flag; |
| 39 | |
| 40 | static const struct option long_options[] = { |
| 41 | /* These options set a flag. */ |
| 42 | {"failsafe", no_argument, &failsafe_flag, 1}, |
| 43 | {"pciboot", no_argument, &pciboot_flag, 1}, |
| 44 | {"nandstage2", no_argument, &stage2_flag, 1}, |
| 45 | {"spistage2", no_argument, &stage2_flag, 1}, |
| 46 | {"norstage2", no_argument, &stage2_flag, 1}, |
| 47 | {"stage2", no_argument, &stage2_flag, 1}, |
| 48 | {"stage1.5", no_argument, &stage_1_5_flag, 1}, |
| 49 | {"stage1", no_argument, &stage_1_flag, 1}, |
| 50 | {"environment", no_argument, &env_flag, 1}, |
| 51 | /* |
| 52 | * These options don't set a flag. |
| 53 | * We distinguish them by their indices. |
| 54 | */ |
| 55 | {"board", required_argument, 0, 0}, |
| 56 | {"text_base", required_argument, 0, 0}, |
| 57 | {0, 0, 0, 0} |
| 58 | }; |
| 59 | |
| 60 | static int lookup_board_type(char *board_name) |
| 61 | { |
| 62 | int i; |
| 63 | int board_type = 0; |
| 64 | char *substr = NULL; |
| 65 | |
| 66 | /* Detect stage 2 bootloader boards */ |
| 67 | if (strcasestr(board_name, "_stage2")) { |
| 68 | printf("Stage 2 bootloader detected from substring %s in name %s\n", |
| 69 | "_stage2", board_name); |
| 70 | stage2_flag = 1; |
| 71 | } else { |
| 72 | printf("Stage 2 bootloader NOT detected from name \"%s\"\n", |
| 73 | board_name); |
| 74 | } |
| 75 | |
| 76 | if (strcasestr(board_name, "_stage1")) { |
| 77 | printf("Stage 1 bootloader detected from substring %s in name %s\n", |
| 78 | "_stage1", board_name); |
| 79 | stage_1_flag = 1; |
| 80 | } |
| 81 | |
| 82 | /* Generic is a special case since there are numerous sub-types */ |
| 83 | if (!strncasecmp("generic", board_name, strlen("generic"))) |
| 84 | return CVMX_BOARD_TYPE_GENERIC; |
| 85 | |
| 86 | /* |
| 87 | * If we're an eMMC stage 2 bootloader, cut off the _emmc_stage2 |
| 88 | * part of the name. |
| 89 | */ |
| 90 | substr = strcasestr(board_name, "_emmc_stage2"); |
| 91 | if (substr && (substr[strlen("_emmc_stage2")] == '\0')) { |
| 92 | /*return CVMX_BOARD_TYPE_GENERIC;*/ |
| 93 | |
| 94 | printf(" Converting board name %s to ", board_name); |
| 95 | *substr = '\0'; |
| 96 | printf("%s\n", board_name); |
| 97 | } |
| 98 | |
| 99 | /* |
| 100 | * If we're a NAND stage 2 bootloader, cut off the _nand_stage2 |
| 101 | * part of the name. |
| 102 | */ |
| 103 | substr = strcasestr(board_name, "_nand_stage2"); |
| 104 | if (substr && (substr[strlen("_nand_stage2")] == '\0')) { |
| 105 | /*return CVMX_BOARD_TYPE_GENERIC;*/ |
| 106 | |
| 107 | printf(" Converting board name %s to ", board_name); |
| 108 | *substr = '\0'; |
| 109 | printf("%s\n", board_name); |
| 110 | } |
| 111 | |
| 112 | /* |
| 113 | * If we're a SPI stage 2 bootloader, cut off the _spi_stage2 |
| 114 | * part of the name. |
| 115 | */ |
| 116 | substr = strcasestr(board_name, "_spi_stage2"); |
| 117 | if (substr && (substr[strlen("_spi_stage2")] == '\0')) { |
| 118 | printf(" Converting board name %s to ", board_name); |
| 119 | *substr = '\0'; |
| 120 | printf("%s\n", board_name); |
| 121 | } |
| 122 | |
| 123 | for (i = CVMX_BOARD_TYPE_NULL; i < CVMX_BOARD_TYPE_MAX; i++) |
| 124 | if (!strcasecmp(cvmx_board_type_to_string(i), board_name)) |
| 125 | board_type = i; |
| 126 | |
| 127 | for (i = CVMX_BOARD_TYPE_CUST_DEFINED_MIN; |
| 128 | i < CVMX_BOARD_TYPE_CUST_DEFINED_MAX; i++) |
| 129 | if (!strncasecmp(cvmx_board_type_to_string(i), board_name, |
| 130 | strlen(cvmx_board_type_to_string(i)))) |
| 131 | board_type = i; |
| 132 | |
| 133 | for (i = CVMX_BOARD_TYPE_CUST_PRIVATE_MIN; |
| 134 | i < CVMX_BOARD_TYPE_CUST_PRIVATE_MAX; i++) |
| 135 | if (!strncasecmp(cvmx_board_type_to_string(i), board_name, |
| 136 | strlen(cvmx_board_type_to_string(i)))) |
| 137 | board_type = i; |
| 138 | |
| 139 | return board_type; |
| 140 | } |
| 141 | |
| 142 | static void usage(void) |
| 143 | { |
| 144 | printf("Usage: update_octeon_header <filename> <board_name> [--failsafe] [--text_base=0xXXXXX]\n"); |
| 145 | } |
| 146 | |
| 147 | int main(int argc, char *argv[]) |
| 148 | { |
| 149 | int fd; |
| 150 | uint8_t buf[BUF_SIZE]; |
| 151 | uint32_t data_crc = 0; |
| 152 | int len; |
| 153 | int data_len = 0; |
| 154 | struct bootloader_header header; |
| 155 | char filename[NAME_LEN]; |
| 156 | int i; |
| 157 | int option_index = 0; /* getopt_long stores the option index here. */ |
| 158 | char board_name[NAME_LEN] = { 0 }; |
| 159 | char tmp_board_name[NAME_LEN] = { 0 }; |
| 160 | int c; |
| 161 | int board_type = 0; |
| 162 | unsigned long long address = 0; |
| 163 | ssize_t ret; |
| 164 | const char *type_str = NULL; |
| 165 | int hdr_size = sizeof(struct bootloader_header); |
| 166 | |
| 167 | /* |
| 168 | * Compile time check, if the size of the bootloader_header structure |
| 169 | * has changed. |
| 170 | */ |
| 171 | compiletime_assert(sizeof(struct bootloader_header) == 192, |
| 172 | "Octeon bootloader header size changed (!= 192)!"); |
| 173 | |
| 174 | /* Bail out, if argument count is incorrect */ |
| 175 | if (argc < 3) { |
| 176 | usage(); |
| 177 | return -1; |
| 178 | } |
| 179 | |
| 180 | debug("header size is: %d bytes\n", hdr_size); |
| 181 | |
| 182 | /* Parse command line options using getopt_long */ |
| 183 | while (1) { |
| 184 | c = getopt_long(argc, argv, "h", long_options, &option_index); |
| 185 | |
| 186 | /* Detect the end of the options. */ |
| 187 | if (c == -1) |
| 188 | break; |
| 189 | |
| 190 | switch (c) { |
| 191 | /* All long options handled in case 0 */ |
| 192 | case 0: |
| 193 | /* If this option set a flag, do nothing else now. */ |
| 194 | if (long_options[option_index].flag != 0) |
| 195 | break; |
| 196 | debug("option(l) %s", long_options[option_index].name); |
| 197 | |
| 198 | if (!optarg) { |
| 199 | usage(); |
| 200 | return -1; |
| 201 | } |
| 202 | debug(" with arg %s\n", optarg); |
| 203 | |
| 204 | if (!strcmp(long_options[option_index].name, "board")) { |
| 205 | if (strlen(optarg) >= NAME_LEN) { |
| 206 | printf("strncpy() issue detected!"); |
| 207 | exit(-1); |
| 208 | } |
| 209 | strncpy(board_name, optarg, NAME_LEN); |
| 210 | |
| 211 | printf("Using user supplied board name: %s\n", |
| 212 | board_name); |
| 213 | } else if (!strcmp(long_options[option_index].name, |
| 214 | "text_base")) { |
| 215 | address = strtoull(optarg, NULL, 0); |
| 216 | printf("Address of image is: 0x%llx\n", |
| 217 | (unsigned long long)address); |
| 218 | if (!(address & 0xFFFFFFFFULL << 32)) { |
| 219 | if (address & 1 << 31) { |
| 220 | address |= 0xFFFFFFFFULL << 32; |
| 221 | printf("Converting address to 64 bit compatibility space: 0x%llx\n", |
| 222 | address); |
| 223 | } |
| 224 | } |
| 225 | } |
| 226 | break; |
| 227 | |
| 228 | case 'h': |
| 229 | case '?': |
| 230 | /* getopt_long already printed an error message. */ |
| 231 | usage(); |
| 232 | return -1; |
| 233 | |
| 234 | default: |
| 235 | abort(); |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | if (optind < argc) { |
| 240 | /* |
| 241 | * We only support one argument - an optional bootloader |
| 242 | * file name |
| 243 | */ |
| 244 | if (argc - optind > 2) { |
| 245 | fprintf(stderr, "non-option ARGV-elements: "); |
| 246 | while (optind < argc) |
| 247 | fprintf(stderr, "%s ", argv[optind++]); |
| 248 | fprintf(stderr, "\n"); |
| 249 | |
| 250 | usage(); |
| 251 | return -1; |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | if (strlen(argv[optind]) >= NAME_LEN) { |
| 256 | fprintf(stderr, "strncpy() issue detected!"); |
| 257 | exit(-1); |
| 258 | } |
| 259 | strncpy(filename, argv[optind], NAME_LEN); |
| 260 | |
| 261 | if (board_name[0] == '\0') { |
| 262 | if (strlen(argv[optind + 1]) >= NAME_LEN) { |
| 263 | fprintf(stderr, "strncpy() issue detected!"); |
| 264 | exit(-1); |
| 265 | } |
| 266 | strncpy(board_name, argv[optind + 1], NAME_LEN); |
| 267 | } |
| 268 | |
| 269 | if (strlen(board_name) >= NAME_LEN) { |
| 270 | fprintf(stderr, "strncpy() issue detected!"); |
| 271 | exit(-1); |
| 272 | } |
| 273 | strncpy(tmp_board_name, board_name, NAME_LEN); |
| 274 | |
| 275 | fd = open(filename, O_RDWR); |
| 276 | if (fd < 0) { |
| 277 | fprintf(stderr, "Unable to open file: %s\n", filename); |
| 278 | exit(-1); |
| 279 | } |
| 280 | |
| 281 | if (failsafe_flag) |
| 282 | printf("Setting failsafe flag\n"); |
| 283 | |
| 284 | if (strlen(board_name)) { |
| 285 | int offset = 0; |
| 286 | |
| 287 | printf("Supplied board name of: %s\n", board_name); |
| 288 | |
| 289 | if (strstr(board_name, "failsafe")) { |
| 290 | failsafe_flag = 1; |
| 291 | printf("Setting failsafe flag based on board name\n"); |
| 292 | } |
| 293 | /* Skip leading octeon_ if present. */ |
| 294 | if (!strncmp(board_name, "octeon_", 7)) |
| 295 | offset = 7; |
| 296 | |
| 297 | /* |
| 298 | * Check to see if 'failsafe' is in the name. If so, set the |
| 299 | * failsafe flag. Also, ignore extra trailing characters on |
| 300 | * passed parameter when comparing against board names. |
| 301 | * We actually use the configuration name from u-boot, so it |
| 302 | * may have some other variant names. Variants other than |
| 303 | * failsafe _must_ be passed to this program explicitly |
| 304 | */ |
| 305 | |
| 306 | board_type = lookup_board_type(board_name + offset); |
| 307 | if (!board_type) { |
| 308 | /* Retry with 'cust_' prefix to catch boards that are |
| 309 | * in the customer section (such as nb5) |
| 310 | */ |
| 311 | sprintf(tmp_board_name, "cust_%s", board_name + offset); |
| 312 | board_type = lookup_board_type(tmp_board_name); |
| 313 | } |
| 314 | |
| 315 | /* reset to original value */ |
| 316 | strncpy(tmp_board_name, board_name, NAME_LEN); |
| 317 | if (!board_type) { |
| 318 | /* |
| 319 | * Retry with 'cust_private_' prefix to catch boards |
| 320 | * that are in the customer private section |
| 321 | */ |
| 322 | sprintf(tmp_board_name, "cust_private_%s", |
| 323 | board_name + offset); |
| 324 | board_type = lookup_board_type(tmp_board_name); |
| 325 | } |
| 326 | |
| 327 | if (!board_type) { |
| 328 | fprintf(stderr, |
| 329 | "ERROR: unable to determine board type\n"); |
| 330 | exit(-1); |
| 331 | } |
| 332 | printf("Board type is: %d: %s\n", board_type, |
| 333 | cvmx_board_type_to_string(board_type)); |
| 334 | } else { |
| 335 | fprintf(stderr, "Board name must be specified!\n"); |
| 336 | exit(-1); |
| 337 | } |
| 338 | |
| 339 | /* |
| 340 | * Check to see if there is either an existing header, or that there |
| 341 | * are zero valued bytes where we want to put the header |
| 342 | */ |
| 343 | len = read(fd, buf, BUF_SIZE); |
| 344 | if (len > 0) { |
| 345 | /* |
| 346 | * Copy the header, as the first word (jump instruction, needs |
| 347 | * to remain the same. |
| 348 | */ |
| 349 | memcpy(&header, buf, hdr_size); |
| 350 | /* |
| 351 | * Check to see if we have zero bytes (excluding first 4, which |
| 352 | * are the jump instruction) |
| 353 | */ |
| 354 | for (i = 1; i < hdr_size / 4; i++) { |
| 355 | if (((uint32_t *)buf)[i]) { |
| 356 | fprintf(stderr, |
| 357 | "ERROR: non-zero word found %x in location %d required for header, aborting\n", |
| 358 | ((uint32_t *)buf)[i], i); |
| 359 | exit(-1); |
| 360 | } |
| 361 | } |
| 362 | printf("Zero bytes found in header location, adding header.\n"); |
| 363 | |
| 364 | } else { |
| 365 | fprintf(stderr, "Unable to read from file %s\n", filename); |
| 366 | exit(-1); |
| 367 | } |
| 368 | |
| 369 | /* Read data bytes and generate CRC */ |
| 370 | lseek(fd, hdr_size, SEEK_SET); |
| 371 | |
| 372 | while ((len = read(fd, buf, BUF_SIZE)) > 0) { |
| 373 | data_crc = crc32(data_crc, buf, len); |
| 374 | data_len += len; |
| 375 | } |
| 376 | printf("CRC of data: 0x%x, length: %d\n", data_crc, data_len); |
| 377 | |
| 378 | /* Now create the new header */ |
| 379 | header.magic = htonl(BOOTLOADER_HEADER_MAGIC); |
| 380 | header.maj_rev = htons(BOOTLOADER_HEADER_CURRENT_MAJOR_REV); |
| 381 | header.min_rev = htons(BOOTLOADER_HEADER_CURRENT_MINOR_REV); |
| 382 | header.dlen = htonl(data_len); |
| 383 | header.dcrc = htonl(data_crc); |
| 384 | header.board_type = htons(board_type); |
| 385 | header.address = address; |
| 386 | if (failsafe_flag) |
| 387 | header.flags |= htonl(BL_HEADER_FLAG_FAILSAFE); |
| 388 | |
| 389 | printf("Stage 2 flag is %sset\n", stage2_flag ? "" : "not "); |
| 390 | printf("Stage 1 flag is %sset\n", stage_1_flag ? "" : "not "); |
| 391 | if (pciboot_flag) |
| 392 | header.image_type = htons(BL_HEADER_IMAGE_PCIBOOT); |
| 393 | else if (stage2_flag) |
| 394 | header.image_type = htons(BL_HEADER_IMAGE_STAGE2); |
| 395 | else if (stage_1_flag) |
| 396 | header.image_type = htons(BL_HEADER_IMAGE_STAGE1); |
| 397 | else if (env_flag) |
| 398 | header.image_type = htons(BL_HEADER_IMAGE_UBOOT_ENV); |
| 399 | else if (stage_1_5_flag || stage_1_flag) |
| 400 | header.image_type = htons(BL_HEADER_IMAGE_PRE_UBOOT); |
| 401 | else |
| 402 | header.image_type = htons(BL_HEADER_IMAGE_NOR); |
| 403 | |
| 404 | switch (ntohs(header.image_type)) { |
| 405 | case BL_HEADER_IMAGE_UNKNOWN: |
| 406 | type_str = "Unknown"; |
| 407 | break; |
| 408 | case BL_HEADER_IMAGE_STAGE1: |
| 409 | type_str = "Stage 1"; |
| 410 | break; |
| 411 | case BL_HEADER_IMAGE_STAGE2: |
| 412 | type_str = "Stage 2"; |
| 413 | break; |
| 414 | case BL_HEADER_IMAGE_PRE_UBOOT: |
| 415 | type_str = "Pre-U-Boot"; |
| 416 | break; |
| 417 | case BL_HEADER_IMAGE_STAGE3: |
| 418 | type_str = "Stage 3"; |
| 419 | break; |
| 420 | case BL_HEADER_IMAGE_NOR: |
| 421 | type_str = "NOR"; |
| 422 | break; |
| 423 | case BL_HEADER_IMAGE_PCIBOOT: |
| 424 | type_str = "PCI Boot"; |
| 425 | break; |
| 426 | case BL_HEADER_IMAGE_UBOOT_ENV: |
| 427 | type_str = "U-Boot Environment"; |
| 428 | break; |
| 429 | default: |
| 430 | if (ntohs(header.image_type) >= BL_HEADER_IMAGE_CUST_RESERVED_MIN && |
| 431 | ntohs(header.image_type) <= BL_HEADER_IMAGE_CUST_RESERVED_MAX) |
| 432 | type_str = "Customer Reserved"; |
| 433 | else |
| 434 | type_str = "Unsupported"; |
| 435 | } |
| 436 | printf("Header image type: %s\n", type_str); |
| 437 | header.hlen = htons(hdr_size); |
| 438 | |
| 439 | /* Now compute header CRC over all of the header excluding the CRC */ |
| 440 | header.hcrc = crc32(0, (void *)&header, 12); |
| 441 | header.hcrc = htonl(crc32(header.hcrc, ((void *)&(header)) + 16, |
| 442 | hdr_size - 16)); |
| 443 | |
| 444 | /* Seek to beginning of file */ |
| 445 | lseek(fd, 0, SEEK_SET); |
| 446 | |
| 447 | /* Write header to file */ |
| 448 | ret = write(fd, &header, hdr_size); |
| 449 | if (ret < 0) |
| 450 | perror("write"); |
| 451 | |
| 452 | close(fd); |
| 453 | |
| 454 | printf("Header CRC: 0x%x\n", ntohl(header.hcrc)); |
| 455 | return 0; |
| 456 | } |