| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2020 Marvell International Ltd. |
| */ |
| |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stddef.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <getopt.h> |
| #include <arpa/inet.h> |
| #include <linux/compiler.h> |
| #include <u-boot/crc.h> |
| |
| #include "mkimage.h" |
| |
| #include "../arch/mips/mach-octeon/include/mach/cvmx-bootloader.h" |
| |
| #define BUF_SIZE (16 * 1024) |
| #define NAME_LEN 100 |
| |
| /* word offset */ |
| #define WOFFSETOF(type, elem) (offsetof(type, elem) / 4) |
| |
| static int stage2_flag; |
| static int stage_1_5_flag; |
| static int stage_1_flag; |
| |
| /* Getoptions variables must be global */ |
| static int failsafe_flag; |
| static int pciboot_flag; |
| static int env_flag; |
| |
| static const struct option long_options[] = { |
| /* These options set a flag. */ |
| {"failsafe", no_argument, &failsafe_flag, 1}, |
| {"pciboot", no_argument, &pciboot_flag, 1}, |
| {"nandstage2", no_argument, &stage2_flag, 1}, |
| {"spistage2", no_argument, &stage2_flag, 1}, |
| {"norstage2", no_argument, &stage2_flag, 1}, |
| {"stage2", no_argument, &stage2_flag, 1}, |
| {"stage1.5", no_argument, &stage_1_5_flag, 1}, |
| {"stage1", no_argument, &stage_1_flag, 1}, |
| {"environment", no_argument, &env_flag, 1}, |
| /* |
| * These options don't set a flag. |
| * We distinguish them by their indices. |
| */ |
| {"board", required_argument, 0, 0}, |
| {"text_base", required_argument, 0, 0}, |
| {0, 0, 0, 0} |
| }; |
| |
| static int lookup_board_type(char *board_name) |
| { |
| int i; |
| int board_type = 0; |
| char *substr = NULL; |
| |
| /* Detect stage 2 bootloader boards */ |
| if (strcasestr(board_name, "_stage2")) { |
| printf("Stage 2 bootloader detected from substring %s in name %s\n", |
| "_stage2", board_name); |
| stage2_flag = 1; |
| } else { |
| printf("Stage 2 bootloader NOT detected from name \"%s\"\n", |
| board_name); |
| } |
| |
| if (strcasestr(board_name, "_stage1")) { |
| printf("Stage 1 bootloader detected from substring %s in name %s\n", |
| "_stage1", board_name); |
| stage_1_flag = 1; |
| } |
| |
| /* Generic is a special case since there are numerous sub-types */ |
| if (!strncasecmp("generic", board_name, strlen("generic"))) |
| return CVMX_BOARD_TYPE_GENERIC; |
| |
| /* |
| * If we're an eMMC stage 2 bootloader, cut off the _emmc_stage2 |
| * part of the name. |
| */ |
| substr = strcasestr(board_name, "_emmc_stage2"); |
| if (substr && (substr[strlen("_emmc_stage2")] == '\0')) { |
| /*return CVMX_BOARD_TYPE_GENERIC;*/ |
| |
| printf(" Converting board name %s to ", board_name); |
| *substr = '\0'; |
| printf("%s\n", board_name); |
| } |
| |
| /* |
| * If we're a NAND stage 2 bootloader, cut off the _nand_stage2 |
| * part of the name. |
| */ |
| substr = strcasestr(board_name, "_nand_stage2"); |
| if (substr && (substr[strlen("_nand_stage2")] == '\0')) { |
| /*return CVMX_BOARD_TYPE_GENERIC;*/ |
| |
| printf(" Converting board name %s to ", board_name); |
| *substr = '\0'; |
| printf("%s\n", board_name); |
| } |
| |
| /* |
| * If we're a SPI stage 2 bootloader, cut off the _spi_stage2 |
| * part of the name. |
| */ |
| substr = strcasestr(board_name, "_spi_stage2"); |
| if (substr && (substr[strlen("_spi_stage2")] == '\0')) { |
| printf(" Converting board name %s to ", board_name); |
| *substr = '\0'; |
| printf("%s\n", board_name); |
| } |
| |
| for (i = CVMX_BOARD_TYPE_NULL; i < CVMX_BOARD_TYPE_MAX; i++) |
| if (!strcasecmp(cvmx_board_type_to_string(i), board_name)) |
| board_type = i; |
| |
| for (i = CVMX_BOARD_TYPE_CUST_DEFINED_MIN; |
| i < CVMX_BOARD_TYPE_CUST_DEFINED_MAX; i++) |
| if (!strncasecmp(cvmx_board_type_to_string(i), board_name, |
| strlen(cvmx_board_type_to_string(i)))) |
| board_type = i; |
| |
| for (i = CVMX_BOARD_TYPE_CUST_PRIVATE_MIN; |
| i < CVMX_BOARD_TYPE_CUST_PRIVATE_MAX; i++) |
| if (!strncasecmp(cvmx_board_type_to_string(i), board_name, |
| strlen(cvmx_board_type_to_string(i)))) |
| board_type = i; |
| |
| return board_type; |
| } |
| |
| static void usage(void) |
| { |
| printf("Usage: update_octeon_header <filename> <board_name> [--failsafe] [--text_base=0xXXXXX]\n"); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int fd; |
| uint8_t buf[BUF_SIZE]; |
| uint32_t data_crc = 0; |
| int len; |
| int data_len = 0; |
| struct bootloader_header header; |
| char filename[NAME_LEN]; |
| int i; |
| int option_index = 0; /* getopt_long stores the option index here. */ |
| char board_name[NAME_LEN] = { 0 }; |
| char tmp_board_name[NAME_LEN] = { 0 }; |
| int c; |
| int board_type = 0; |
| unsigned long long address = 0; |
| ssize_t ret; |
| const char *type_str = NULL; |
| int hdr_size = sizeof(struct bootloader_header); |
| |
| /* |
| * Compile time check, if the size of the bootloader_header structure |
| * has changed. |
| */ |
| compiletime_assert(sizeof(struct bootloader_header) == 192, |
| "Octeon bootloader header size changed (!= 192)!"); |
| |
| /* Bail out, if argument count is incorrect */ |
| if (argc < 3) { |
| usage(); |
| return -1; |
| } |
| |
| debug("header size is: %d bytes\n", hdr_size); |
| |
| /* Parse command line options using getopt_long */ |
| while (1) { |
| c = getopt_long(argc, argv, "h", long_options, &option_index); |
| |
| /* Detect the end of the options. */ |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| /* All long options handled in case 0 */ |
| case 0: |
| /* If this option set a flag, do nothing else now. */ |
| if (long_options[option_index].flag != 0) |
| break; |
| debug("option(l) %s", long_options[option_index].name); |
| |
| if (!optarg) { |
| usage(); |
| return -1; |
| } |
| debug(" with arg %s\n", optarg); |
| |
| if (!strcmp(long_options[option_index].name, "board")) { |
| if (strlen(optarg) >= NAME_LEN) { |
| printf("strncpy() issue detected!"); |
| exit(-1); |
| } |
| strncpy(board_name, optarg, NAME_LEN); |
| |
| printf("Using user supplied board name: %s\n", |
| board_name); |
| } else if (!strcmp(long_options[option_index].name, |
| "text_base")) { |
| address = strtoull(optarg, NULL, 0); |
| printf("Address of image is: 0x%llx\n", |
| (unsigned long long)address); |
| if (!(address & 0xFFFFFFFFULL << 32)) { |
| if (address & 1 << 31) { |
| address |= 0xFFFFFFFFULL << 32; |
| printf("Converting address to 64 bit compatibility space: 0x%llx\n", |
| address); |
| } |
| } |
| } |
| break; |
| |
| case 'h': |
| case '?': |
| /* getopt_long already printed an error message. */ |
| usage(); |
| return -1; |
| |
| default: |
| abort(); |
| } |
| } |
| |
| if (optind < argc) { |
| /* |
| * We only support one argument - an optional bootloader |
| * file name |
| */ |
| if (argc - optind > 2) { |
| fprintf(stderr, "non-option ARGV-elements: "); |
| while (optind < argc) |
| fprintf(stderr, "%s ", argv[optind++]); |
| fprintf(stderr, "\n"); |
| |
| usage(); |
| return -1; |
| } |
| } |
| |
| if (strlen(argv[optind]) >= NAME_LEN) { |
| fprintf(stderr, "strncpy() issue detected!"); |
| exit(-1); |
| } |
| strncpy(filename, argv[optind], NAME_LEN); |
| |
| if (board_name[0] == '\0') { |
| if (strlen(argv[optind + 1]) >= NAME_LEN) { |
| fprintf(stderr, "strncpy() issue detected!"); |
| exit(-1); |
| } |
| strncpy(board_name, argv[optind + 1], NAME_LEN); |
| } |
| |
| if (strlen(board_name) >= NAME_LEN) { |
| fprintf(stderr, "strncpy() issue detected!"); |
| exit(-1); |
| } |
| strncpy(tmp_board_name, board_name, NAME_LEN); |
| |
| fd = open(filename, O_RDWR); |
| if (fd < 0) { |
| fprintf(stderr, "Unable to open file: %s\n", filename); |
| exit(-1); |
| } |
| |
| if (failsafe_flag) |
| printf("Setting failsafe flag\n"); |
| |
| if (strlen(board_name)) { |
| int offset = 0; |
| |
| printf("Supplied board name of: %s\n", board_name); |
| |
| if (strstr(board_name, "failsafe")) { |
| failsafe_flag = 1; |
| printf("Setting failsafe flag based on board name\n"); |
| } |
| /* Skip leading octeon_ if present. */ |
| if (!strncmp(board_name, "octeon_", 7)) |
| offset = 7; |
| |
| /* |
| * Check to see if 'failsafe' is in the name. If so, set the |
| * failsafe flag. Also, ignore extra trailing characters on |
| * passed parameter when comparing against board names. |
| * We actually use the configuration name from u-boot, so it |
| * may have some other variant names. Variants other than |
| * failsafe _must_ be passed to this program explicitly |
| */ |
| |
| board_type = lookup_board_type(board_name + offset); |
| if (!board_type) { |
| /* Retry with 'cust_' prefix to catch boards that are |
| * in the customer section (such as nb5) |
| */ |
| sprintf(tmp_board_name, "cust_%s", board_name + offset); |
| board_type = lookup_board_type(tmp_board_name); |
| } |
| |
| /* reset to original value */ |
| strncpy(tmp_board_name, board_name, NAME_LEN); |
| if (!board_type) { |
| /* |
| * Retry with 'cust_private_' prefix to catch boards |
| * that are in the customer private section |
| */ |
| sprintf(tmp_board_name, "cust_private_%s", |
| board_name + offset); |
| board_type = lookup_board_type(tmp_board_name); |
| } |
| |
| if (!board_type) { |
| fprintf(stderr, |
| "ERROR: unable to determine board type\n"); |
| exit(-1); |
| } |
| printf("Board type is: %d: %s\n", board_type, |
| cvmx_board_type_to_string(board_type)); |
| } else { |
| fprintf(stderr, "Board name must be specified!\n"); |
| exit(-1); |
| } |
| |
| /* |
| * Check to see if there is either an existing header, or that there |
| * are zero valued bytes where we want to put the header |
| */ |
| len = read(fd, buf, BUF_SIZE); |
| if (len > 0) { |
| /* |
| * Copy the header, as the first word (jump instruction, needs |
| * to remain the same. |
| */ |
| memcpy(&header, buf, hdr_size); |
| /* |
| * Check to see if we have zero bytes (excluding first 4, which |
| * are the jump instruction) |
| */ |
| for (i = 1; i < hdr_size / 4; i++) { |
| if (((uint32_t *)buf)[i]) { |
| fprintf(stderr, |
| "ERROR: non-zero word found %x in location %d required for header, aborting\n", |
| ((uint32_t *)buf)[i], i); |
| exit(-1); |
| } |
| } |
| printf("Zero bytes found in header location, adding header.\n"); |
| |
| } else { |
| fprintf(stderr, "Unable to read from file %s\n", filename); |
| exit(-1); |
| } |
| |
| /* Read data bytes and generate CRC */ |
| lseek(fd, hdr_size, SEEK_SET); |
| |
| while ((len = read(fd, buf, BUF_SIZE)) > 0) { |
| data_crc = crc32(data_crc, buf, len); |
| data_len += len; |
| } |
| printf("CRC of data: 0x%x, length: %d\n", data_crc, data_len); |
| |
| /* Now create the new header */ |
| header.magic = htonl(BOOTLOADER_HEADER_MAGIC); |
| header.maj_rev = htons(BOOTLOADER_HEADER_CURRENT_MAJOR_REV); |
| header.min_rev = htons(BOOTLOADER_HEADER_CURRENT_MINOR_REV); |
| header.dlen = htonl(data_len); |
| header.dcrc = htonl(data_crc); |
| header.board_type = htons(board_type); |
| header.address = address; |
| if (failsafe_flag) |
| header.flags |= htonl(BL_HEADER_FLAG_FAILSAFE); |
| |
| printf("Stage 2 flag is %sset\n", stage2_flag ? "" : "not "); |
| printf("Stage 1 flag is %sset\n", stage_1_flag ? "" : "not "); |
| if (pciboot_flag) |
| header.image_type = htons(BL_HEADER_IMAGE_PCIBOOT); |
| else if (stage2_flag) |
| header.image_type = htons(BL_HEADER_IMAGE_STAGE2); |
| else if (stage_1_flag) |
| header.image_type = htons(BL_HEADER_IMAGE_STAGE1); |
| else if (env_flag) |
| header.image_type = htons(BL_HEADER_IMAGE_UBOOT_ENV); |
| else if (stage_1_5_flag || stage_1_flag) |
| header.image_type = htons(BL_HEADER_IMAGE_PRE_UBOOT); |
| else |
| header.image_type = htons(BL_HEADER_IMAGE_NOR); |
| |
| switch (ntohs(header.image_type)) { |
| case BL_HEADER_IMAGE_UNKNOWN: |
| type_str = "Unknown"; |
| break; |
| case BL_HEADER_IMAGE_STAGE1: |
| type_str = "Stage 1"; |
| break; |
| case BL_HEADER_IMAGE_STAGE2: |
| type_str = "Stage 2"; |
| break; |
| case BL_HEADER_IMAGE_PRE_UBOOT: |
| type_str = "Pre-U-Boot"; |
| break; |
| case BL_HEADER_IMAGE_STAGE3: |
| type_str = "Stage 3"; |
| break; |
| case BL_HEADER_IMAGE_NOR: |
| type_str = "NOR"; |
| break; |
| case BL_HEADER_IMAGE_PCIBOOT: |
| type_str = "PCI Boot"; |
| break; |
| case BL_HEADER_IMAGE_UBOOT_ENV: |
| type_str = "U-Boot Environment"; |
| break; |
| default: |
| if (ntohs(header.image_type) >= BL_HEADER_IMAGE_CUST_RESERVED_MIN && |
| ntohs(header.image_type) <= BL_HEADER_IMAGE_CUST_RESERVED_MAX) |
| type_str = "Customer Reserved"; |
| else |
| type_str = "Unsupported"; |
| } |
| printf("Header image type: %s\n", type_str); |
| header.hlen = htons(hdr_size); |
| |
| /* Now compute header CRC over all of the header excluding the CRC */ |
| header.hcrc = crc32(0, (void *)&header, 12); |
| header.hcrc = htonl(crc32(header.hcrc, ((void *)&(header)) + 16, |
| hdr_size - 16)); |
| |
| /* Seek to beginning of file */ |
| lseek(fd, 0, SEEK_SET); |
| |
| /* Write header to file */ |
| ret = write(fd, &header, hdr_size); |
| if (ret < 0) |
| perror("write"); |
| |
| close(fd); |
| |
| printf("Header CRC: 0x%x\n", ntohl(header.hcrc)); |
| return 0; |
| } |