| /* |
| * Copyright (C) 2018 Alexander Graf <agraf@suse.de> |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| #include "imagetool.h" |
| #include "mkimage.h" |
| #include "zynqmpimage.h" |
| #include <elf.h> |
| #include <image.h> |
| |
| struct bif_entry { |
| const char *filename; |
| uint64_t flags; |
| uint64_t dest_cpu; |
| uint64_t exp_lvl; |
| uint64_t dest_dev; |
| uint64_t load; |
| uint64_t entry; |
| size_t offset; |
| }; |
| |
| enum bif_flag { |
| BIF_FLAG_AESKEYFILE, |
| BIF_FLAG_INIT, |
| BIF_FLAG_UDF_BH, |
| BIF_FLAG_HEADERSIGNATURE, |
| BIF_FLAG_PPKFILE, |
| BIF_FLAG_PSKFILE, |
| BIF_FLAG_SPKFILE, |
| BIF_FLAG_SSKFILE, |
| BIF_FLAG_SPKSIGNATURE, |
| BIF_FLAG_FSBL_CONFIG, |
| BIF_FLAG_AUTH_PARAMS, |
| BIF_FLAG_KEYSRC_ENCRYPTION, |
| BIF_FLAG_PMUFW_IMAGE, |
| BIF_FLAG_BOOTLOADER, |
| BIF_FLAG_TZ, |
| BIF_FLAG_BH_KEY_IV, |
| BIF_FLAG_BH_KEYFILE, |
| BIF_FLAG_PUF_FILE, |
| BIF_FLAG_AARCH32, |
| BIF_FLAG_PART_OWNER_UBOOT, |
| |
| /* Internal flags */ |
| BIF_FLAG_BIT_FILE, |
| BIF_FLAG_ELF_FILE, |
| BIF_FLAG_BIN_FILE, |
| }; |
| |
| struct bif_flags { |
| const char name[32]; |
| uint64_t flag; |
| char *(*parse)(char *line, struct bif_entry *bf); |
| }; |
| |
| struct bif_file_type { |
| const char name[32]; |
| uint32_t header; |
| int (*add)(struct bif_entry *bf); |
| }; |
| |
| struct bif_output { |
| size_t data_len; |
| char *data; |
| struct image_header_table *imgheader; |
| struct zynqmp_header *header; |
| struct partition_header *last_part; |
| }; |
| |
| struct bif_output bif_output; |
| |
| static uint32_t zynqmp_csum(void *start, void *end) |
| { |
| uint32_t checksum = 0; |
| uint32_t *ptr32 = start; |
| |
| while (ptr32 != end) { |
| checksum += le32_to_cpu(*ptr32); |
| ptr32++; |
| } |
| |
| return ~checksum; |
| } |
| |
| static int zynqmpbif_check_params(struct image_tool_params *params) |
| { |
| if (!params) |
| return 0; |
| |
| if (params->addr != 0x0) { |
| fprintf(stderr, "Error: Load Address can not be specified.\n"); |
| return -1; |
| } |
| |
| if (params->eflag) { |
| fprintf(stderr, "Error: Entry Point can not be specified.\n"); |
| return -1; |
| } |
| |
| return !(params->lflag || params->dflag); |
| } |
| |
| static int zynqmpbif_check_image_types(uint8_t type) |
| { |
| return (type == IH_TYPE_ZYNQMPBIF) ? EXIT_SUCCESS : EXIT_FAILURE; |
| } |
| |
| static char *parse_dest_cpu(char *line, struct bif_entry *bf) |
| { |
| uint64_t i; |
| |
| for (i = 0; i < ARRAY_SIZE(dest_cpus); i++) { |
| if (!strncmp(line, dest_cpus[i], strlen(dest_cpus[i]))) { |
| bf->dest_cpu = i << PART_ATTR_DEST_CPU_SHIFT; |
| return line + strlen(dest_cpus[i]); |
| } |
| |
| /* a5x can also be written as a53 */ |
| if (!strncmp(dest_cpus[i], "a5x", 3)) { |
| char a53[] = "a53-X"; |
| |
| a53[4] = dest_cpus[i][4]; |
| if (!strncmp(line, a53, strlen(a53))) { |
| bf->dest_cpu = i << PART_ATTR_DEST_CPU_SHIFT; |
| return line + strlen(a53); |
| } |
| } |
| } |
| |
| return line; |
| } |
| |
| static char *parse_el(char *line, struct bif_entry *bf) |
| { |
| const char *dest_els[] = { "none", "el-0", "el-1", "el-2", "el-3" }; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(dest_els); i++) { |
| if (!strncmp(line, dest_els[i], strlen(dest_els[i]))) { |
| bf->exp_lvl = i; |
| return line + strlen(dest_els[i]); |
| } |
| } |
| |
| return line; |
| } |
| |
| static char *parse_load(char *line, struct bif_entry *bf) |
| { |
| char *endptr; |
| |
| bf->load = strtoll(line, &endptr, 0); |
| |
| return endptr; |
| } |
| |
| static char *parse_entry(char *line, struct bif_entry *bf) |
| { |
| char *endptr; |
| |
| bf->entry = strtoll(line, &endptr, 0); |
| |
| return endptr; |
| } |
| |
| static char *parse_offset(char *line, struct bif_entry *bf) |
| { |
| char *endptr; |
| |
| bf->offset = strtoll(line, &endptr, 0); |
| |
| return endptr; |
| } |
| |
| static char *parse_partition_owner(char *line, struct bif_entry *bf) |
| { |
| char *endptr = NULL; |
| |
| if (!strncmp(line, "fsbl", 4)) { |
| endptr = line + 4; |
| } else if (!strncmp(line, "uboot", 5)) { |
| bf->flags |= 1ULL << BIF_FLAG_PART_OWNER_UBOOT; |
| endptr = line + 5; |
| } else { |
| printf("ERROR: Unknown partition type '%s'\n", line); |
| } |
| |
| return endptr; |
| } |
| |
| static const struct bif_flags bif_flags[] = { |
| { "fsbl_config", BIF_FLAG_FSBL_CONFIG }, |
| { "trustzone", BIF_FLAG_TZ }, |
| { "pmufw_image", BIF_FLAG_PMUFW_IMAGE }, |
| { "bootloader", BIF_FLAG_BOOTLOADER }, |
| { "destination_cpu=", 0, parse_dest_cpu }, |
| { "exception_level=", 0, parse_el }, |
| { "load=", 0, parse_load }, |
| { "startup=", 0, parse_entry }, |
| { "offset=", 0, parse_offset }, |
| { "partition_owner=", 0, parse_partition_owner }, |
| }; |
| |
| static char *read_full_file(const char *filename, size_t *size) |
| { |
| char *buf, *bufp; |
| struct stat sbuf; |
| int len = 0, r, fd; |
| |
| fd = open(filename, O_RDONLY); |
| if (fd < 0) |
| return NULL; |
| |
| if (fstat(fd, &sbuf) < 0) |
| return NULL; |
| |
| if (size) |
| *size = sbuf.st_size; |
| |
| buf = malloc(sbuf.st_size); |
| if (!buf) |
| return NULL; |
| |
| bufp = buf; |
| while (len < sbuf.st_size) { |
| r = read(fd, bufp, sbuf.st_size - len); |
| if (r < 0) |
| return NULL; |
| len += r; |
| bufp += r; |
| } |
| |
| close(fd); |
| |
| return buf; |
| } |
| |
| static int bif_add_blob(const void *data, size_t len, size_t *offset) |
| { |
| size_t new_size; |
| uintptr_t header_off; |
| uintptr_t last_part_off; |
| uintptr_t imgheader_off; |
| uintptr_t old_data = (uintptr_t)bif_output.data; |
| void *new_data; |
| |
| header_off = (uintptr_t)bif_output.header - old_data; |
| last_part_off = (uintptr_t)bif_output.last_part - old_data; |
| imgheader_off = (uintptr_t)bif_output.imgheader - old_data; |
| |
| if (offset && *offset) { |
| /* Pad to a given offset */ |
| if (bif_output.data_len > *offset) { |
| printf("Can not pad to offset %zx\n", *offset); |
| return -1; |
| } |
| |
| bif_output.data_len = *offset; |
| } |
| |
| new_size = ROUND(bif_output.data_len + len, 64); |
| new_data = realloc(bif_output.data, new_size); |
| memcpy(new_data + bif_output.data_len, data, len); |
| if (offset) |
| *offset = bif_output.data_len; |
| bif_output.data = new_data; |
| bif_output.data_len = new_size; |
| |
| /* Readjust internal pointers */ |
| if (bif_output.header) |
| bif_output.header = new_data + header_off; |
| if (bif_output.last_part) |
| bif_output.last_part = new_data + last_part_off; |
| if (bif_output.imgheader) |
| bif_output.imgheader = new_data + imgheader_off; |
| |
| return 0; |
| } |
| |
| static int bif_init(void) |
| { |
| struct zynqmp_header header = { { 0 } }; |
| int r; |
| |
| zynqmpimage_default_header(&header); |
| |
| r = bif_add_blob(&header, sizeof(header), NULL); |
| if (r) |
| return r; |
| |
| bif_output.header = (void *)bif_output.data; |
| |
| return 0; |
| } |
| |
| static int bif_add_pmufw(struct bif_entry *bf, const char *data, size_t len) |
| { |
| int r; |
| |
| if (bif_output.header->image_offset) { |
| printf("PMUFW expected before bootloader in your .bif file!\n"); |
| return -1; |
| } |
| |
| r = bif_add_blob(data, len, &bf->offset); |
| if (r) |
| return r; |
| |
| len = ROUND(len, 64); |
| bif_output.header->pfw_image_length = cpu_to_le32(len); |
| bif_output.header->total_pfw_image_length = cpu_to_le32(len); |
| bif_output.header->image_offset = cpu_to_le32(bf->offset); |
| |
| return 0; |
| } |
| |
| static int bif_add_part(struct bif_entry *bf, const char *data, size_t len) |
| { |
| size_t parthdr_offset = 0; |
| struct partition_header parthdr = { |
| .len_enc = cpu_to_le32(len / 4), |
| .len_unenc = cpu_to_le32(len / 4), |
| .len = cpu_to_le32(len / 4), |
| .entry_point = cpu_to_le64(bf->entry), |
| .load_address = cpu_to_le64(bf->load), |
| }; |
| int r; |
| uint32_t csum; |
| |
| if (bf->flags & (1ULL << BIF_FLAG_PMUFW_IMAGE)) |
| return bif_add_pmufw(bf, data, len); |
| |
| r = bif_add_blob(data, len, &bf->offset); |
| if (r) |
| return r; |
| |
| parthdr.offset = cpu_to_le32(bf->offset / 4); |
| |
| if (bf->flags & (1ULL << BIF_FLAG_BOOTLOADER)) { |
| if (bif_output.last_part) { |
| printf("ERROR: Bootloader expected before others\n"); |
| return -1; |
| } |
| |
| parthdr.offset = cpu_to_le32(bif_output.header->image_offset); |
| parthdr.len = cpu_to_le32((bf->offset + len - |
| bif_output.header->image_offset) / 4); |
| parthdr.len_enc = parthdr.len; |
| parthdr.len_unenc = parthdr.len; |
| } |
| |
| /* Normalize EL */ |
| bf->exp_lvl = bf->exp_lvl ? bf->exp_lvl - 1 : 3; |
| parthdr.attributes |= bf->exp_lvl << PART_ATTR_TARGET_EL_SHIFT; |
| parthdr.attributes |= bf->dest_dev; |
| parthdr.attributes |= bf->dest_cpu; |
| if (bf->flags & (1ULL << BIF_FLAG_TZ)) |
| parthdr.attributes |= PART_ATTR_TZ_SECURE; |
| if (bf->flags & (1ULL << BIF_FLAG_PART_OWNER_UBOOT)) |
| parthdr.attributes |= PART_ATTR_PART_OWNER_UBOOT; |
| switch (bf->dest_cpu) { |
| case PART_ATTR_DEST_CPU_NONE: |
| case PART_ATTR_DEST_CPU_A53_0: |
| case PART_ATTR_DEST_CPU_A53_1: |
| case PART_ATTR_DEST_CPU_A53_2: |
| case PART_ATTR_DEST_CPU_A53_3: |
| if (bf->flags & (1ULL << BIF_FLAG_AARCH32)) |
| parthdr.attributes |= PART_ATTR_A53_EXEC_AARCH32; |
| } |
| |
| csum = zynqmp_csum(&parthdr, &parthdr.checksum); |
| parthdr.checksum = cpu_to_le32(csum); |
| |
| r = bif_add_blob(&parthdr, sizeof(parthdr), &parthdr_offset); |
| if (r) |
| return r; |
| |
| /* Add image header table if not there yet */ |
| if (!bif_output.imgheader) { |
| size_t imghdr_off = 0; |
| struct image_header_table imghdr = { |
| .version = cpu_to_le32(0x01020000), |
| .nr_parts = 0, |
| }; |
| |
| r = bif_add_blob(&imghdr, sizeof(imghdr), &imghdr_off); |
| if (r) |
| return r; |
| |
| bif_output.header->image_header_table_offset = imghdr_off; |
| bif_output.imgheader = (void *)(bif_output.data + imghdr_off); |
| } |
| |
| bif_output.imgheader->nr_parts = cpu_to_le32(le32_to_cpu( |
| bif_output.imgheader->nr_parts) + 1); |
| |
| /* Link to this partition header */ |
| if (bif_output.last_part) { |
| bif_output.last_part->next_partition_offset = |
| cpu_to_le32(parthdr_offset / 4); |
| |
| /* Recalc checksum of last_part */ |
| csum = zynqmp_csum(bif_output.last_part, |
| &bif_output.last_part->checksum); |
| bif_output.last_part->checksum = cpu_to_le32(csum); |
| } else { |
| bif_output.imgheader->partition_header_offset = |
| cpu_to_le32(parthdr_offset / 4); |
| } |
| bif_output.last_part = (void *)(bif_output.data + parthdr_offset); |
| |
| if (bf->flags & (1ULL << BIF_FLAG_BOOTLOADER)) { |
| bif_output.header->image_load = cpu_to_le32(bf->load); |
| if (!bif_output.header->image_offset) |
| bif_output.header->image_offset = |
| cpu_to_le32(bf->offset); |
| bif_output.header->image_size = cpu_to_le32(len); |
| bif_output.header->image_stored_size = cpu_to_le32(len); |
| |
| bif_output.header->image_attributes &= ~HEADER_CPU_SELECT_MASK; |
| switch (bf->dest_cpu) { |
| default: |
| case PART_ATTR_DEST_CPU_A53_0: |
| if (bf->flags & BIF_FLAG_AARCH32) |
| bif_output.header->image_attributes |= |
| HEADER_CPU_SELECT_A53_32BIT; |
| else |
| bif_output.header->image_attributes |= |
| HEADER_CPU_SELECT_A53_64BIT; |
| break; |
| case PART_ATTR_DEST_CPU_R5_0: |
| bif_output.header->image_attributes |= |
| HEADER_CPU_SELECT_R5_SINGLE; |
| break; |
| case PART_ATTR_DEST_CPU_R5_L: |
| bif_output.header->image_attributes |= |
| HEADER_CPU_SELECT_R5_DUAL; |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Add .bit bitstream */ |
| static int bif_add_bit(struct bif_entry *bf) |
| { |
| char *bit = read_full_file(bf->filename, NULL); |
| char *bitbin; |
| uint8_t initial_header[] = { 0x00, 0x09, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, |
| 0xf0, 0x0f, 0xf0, 0x00, 0x00, 0x01, 0x61 }; |
| uint16_t len; |
| uint32_t bitlen; |
| int i; |
| |
| if (!bit) |
| return -1; |
| |
| /* Skip initial header */ |
| if (memcmp(bit, initial_header, sizeof(initial_header))) |
| return -1; |
| |
| bit += sizeof(initial_header); |
| |
| /* Design name */ |
| len = be16_to_cpu(*(uint16_t *)bit); |
| bit += sizeof(uint16_t); |
| debug("Design: %s\n", bit); |
| bit += len; |
| |
| /* Device identifier */ |
| if (*bit != 'b') |
| return -1; |
| bit++; |
| len = be16_to_cpu(*(uint16_t *)bit); |
| bit += sizeof(uint16_t); |
| debug("Device: %s\n", bit); |
| bit += len; |
| |
| /* Date */ |
| if (*bit != 'c') |
| return -1; |
| bit++; |
| len = be16_to_cpu(*(uint16_t *)bit); |
| bit += sizeof(uint16_t); |
| debug("Date: %s\n", bit); |
| bit += len; |
| |
| /* Time */ |
| if (*bit != 'd') |
| return -1; |
| bit++; |
| len = be16_to_cpu(*(uint16_t *)bit); |
| bit += sizeof(uint16_t); |
| debug("Time: %s\n", bit); |
| bit += len; |
| |
| /* Bitstream length */ |
| if (*bit != 'e') |
| return -1; |
| bit++; |
| bitlen = be32_to_cpu(*(uint32_t *)bit); |
| bit += sizeof(uint32_t); |
| bitbin = bit; |
| |
| debug("Bitstream Length: 0x%x\n", bitlen); |
| for (i = 0; i < bitlen; i += sizeof(uint32_t)) { |
| uint32_t *bitbin32 = (uint32_t *)&bitbin[i]; |
| *bitbin32 = __swab32(*bitbin32); |
| } |
| |
| if (!bf->dest_dev) |
| bf->dest_dev = PART_ATTR_DEST_DEVICE_PL; |
| |
| bf->load = 0xffffffff; |
| bf->entry = 0; |
| |
| bf->flags |= 1ULL << BIF_FLAG_BIT_FILE; |
| return bif_add_part(bf, bit, bitlen); |
| } |
| |
| /* Add .bin bitstream */ |
| static int bif_add_bin(struct bif_entry *bf) |
| { |
| size_t size; |
| char *bin = read_full_file(bf->filename, &size); |
| |
| if (!bf->dest_dev) |
| bf->dest_dev = PART_ATTR_DEST_DEVICE_PS; |
| |
| bf->flags |= 1ULL << BIF_FLAG_BIN_FILE; |
| return bif_add_part(bf, bin, size); |
| } |
| |
| /* Add elf file */ |
| static char *elf2flat64(char *elf, size_t *flat_size, size_t *load_addr) |
| { |
| Elf64_Ehdr *ehdr; |
| Elf64_Shdr *shdr; |
| size_t min_addr = -1, max_addr = 0; |
| char *flat; |
| int i; |
| |
| ehdr = (void *)elf; |
| shdr = (void *)(elf + le64_to_cpu(ehdr->e_shoff)); |
| |
| /* Look for smallest / biggest address */ |
| for (i = 0; i < le64_to_cpu(ehdr->e_shnum); i++, shdr++) { |
| if (!shdr->sh_size || !shdr->sh_addr || |
| !(shdr->sh_flags & SHF_ALLOC) || |
| (shdr->sh_type == SHT_NOBITS)) |
| continue; |
| |
| if (le64_to_cpu(shdr->sh_addr) < min_addr) |
| min_addr = le64_to_cpu(shdr->sh_addr); |
| if ((le64_to_cpu(shdr->sh_addr) + le64_to_cpu(shdr->sh_size)) > |
| max_addr) |
| max_addr = le64_to_cpu(shdr->sh_addr) + |
| le64_to_cpu(shdr->sh_size); |
| } |
| |
| *load_addr = min_addr; |
| *flat_size = max_addr - min_addr; |
| flat = calloc(1, *flat_size); |
| if (!flat) |
| return NULL; |
| |
| shdr = (void *)(elf + le64_to_cpu(ehdr->e_shoff)); |
| for (i = 0; i < le64_to_cpu(ehdr->e_shnum); i++, shdr++) { |
| char *dst = flat + le64_to_cpu(shdr->sh_addr) - min_addr; |
| char *src = elf + le64_to_cpu(shdr->sh_offset); |
| |
| if (!shdr->sh_size || !shdr->sh_addr || |
| !(shdr->sh_flags & SHF_ALLOC)) |
| continue; |
| |
| if (shdr->sh_type != SHT_NOBITS) |
| memcpy(dst, src, le64_to_cpu(shdr->sh_size)); |
| } |
| |
| return flat; |
| } |
| |
| static char *elf2flat32(char *elf, size_t *flat_size, size_t *load_addr) |
| { |
| Elf32_Ehdr *ehdr; |
| Elf32_Shdr *shdr; |
| size_t min_addr = -1, max_addr = 0; |
| char *flat; |
| int i; |
| |
| ehdr = (void *)elf; |
| shdr = (void *)(elf + le32_to_cpu(ehdr->e_shoff)); |
| |
| /* Look for smallest / biggest address */ |
| for (i = 0; i < le32_to_cpu(ehdr->e_shnum); i++, shdr++) { |
| if (!shdr->sh_size || !shdr->sh_addr || |
| !(shdr->sh_flags & SHF_ALLOC) || |
| (shdr->sh_type == SHT_NOBITS)) |
| continue; |
| |
| if (le32_to_cpu(shdr->sh_addr) < min_addr) |
| min_addr = le32_to_cpu(shdr->sh_addr); |
| if ((le32_to_cpu(shdr->sh_addr) + le32_to_cpu(shdr->sh_size)) > |
| max_addr) |
| max_addr = le32_to_cpu(shdr->sh_addr) + |
| le32_to_cpu(shdr->sh_size); |
| } |
| |
| *load_addr = min_addr; |
| *flat_size = max_addr - min_addr; |
| flat = calloc(1, *flat_size); |
| if (!flat) |
| return NULL; |
| |
| shdr = (void *)(elf + le32_to_cpu(ehdr->e_shoff)); |
| for (i = 0; i < le32_to_cpu(ehdr->e_shnum); i++, shdr++) { |
| char *dst = flat + le32_to_cpu(shdr->sh_addr) - min_addr; |
| char *src = elf + le32_to_cpu(shdr->sh_offset); |
| |
| if (!shdr->sh_size || !shdr->sh_addr || |
| !(shdr->sh_flags & SHF_ALLOC)) |
| continue; |
| |
| if (shdr->sh_type != SHT_NOBITS) |
| memcpy(dst, src, le32_to_cpu(shdr->sh_size)); |
| } |
| |
| return flat; |
| } |
| |
| static int bif_add_elf(struct bif_entry *bf) |
| { |
| size_t size; |
| size_t elf_size; |
| char *elf; |
| char *flat; |
| size_t load_addr; |
| Elf32_Ehdr *ehdr32; |
| Elf64_Ehdr *ehdr64; |
| |
| elf = read_full_file(bf->filename, &elf_size); |
| if (!elf) |
| return -1; |
| |
| ehdr32 = (void *)elf; |
| ehdr64 = (void *)elf; |
| |
| switch (ehdr32->e_ident[EI_CLASS]) { |
| case ELFCLASS32: |
| flat = elf2flat32(elf, &size, &load_addr); |
| bf->entry = le32_to_cpu(ehdr32->e_entry); |
| bf->flags |= 1ULL << BIF_FLAG_AARCH32; |
| break; |
| case ELFCLASS64: |
| flat = elf2flat64(elf, &size, &load_addr); |
| bf->entry = le64_to_cpu(ehdr64->e_entry); |
| break; |
| default: |
| printf("Unknown ELF class: %d\n", ehdr32->e_ident[EI_CLASS]); |
| return -1; |
| } |
| |
| if (!flat) |
| return -1; |
| |
| bf->load = load_addr; |
| if (!bf->dest_dev) |
| bf->dest_dev = PART_ATTR_DEST_DEVICE_PS; |
| |
| bf->flags |= 1ULL << BIF_FLAG_ELF_FILE; |
| return bif_add_part(bf, flat, size); |
| } |
| |
| static const struct bif_file_type bif_file_types[] = { |
| { |
| .name = "bitstream (.bit)", |
| .header = 0x00090ff0, |
| .add = bif_add_bit, |
| }, |
| |
| { |
| .name = "ELF", |
| .header = 0x7f454c46, |
| .add = bif_add_elf, |
| }, |
| |
| /* Anything else is a .bin file */ |
| { |
| .name = ".bin", |
| .add = bif_add_bin, |
| }, |
| }; |
| |
| static int bif_fsbl_config(struct bif_entry *fsbl_config, |
| struct bif_entry *entries, int nr_entries) |
| { |
| int i; |
| int config_set = 0; |
| struct { |
| const char *name; |
| uint64_t flags; |
| uint64_t dest_cpu; |
| } configs[] = { |
| { .name = "a5x_x64", .dest_cpu = PART_ATTR_DEST_CPU_A53_0 }, |
| { .name = "a53_x64", .dest_cpu = PART_ATTR_DEST_CPU_A53_0 }, |
| { .name = "a5x_x32", .dest_cpu = PART_ATTR_DEST_CPU_A53_0, |
| .flags = 1ULL << BIF_FLAG_AARCH32 }, |
| { .name = "a53_x32", .dest_cpu = PART_ATTR_DEST_CPU_A53_0, |
| .flags = 1ULL << BIF_FLAG_AARCH32 }, |
| { .name = "r5_single", .dest_cpu = PART_ATTR_DEST_CPU_R5_0 }, |
| { .name = "r5_dual", .dest_cpu = PART_ATTR_DEST_CPU_R5_L }, |
| }; |
| |
| /* Set target CPU of bootloader entry */ |
| for (i = 0; i < nr_entries; i++) { |
| struct bif_entry *b = &entries[i]; |
| const char *config_attr = fsbl_config->filename; |
| int j; |
| |
| if (!(b->flags & (1ULL << BIF_FLAG_BOOTLOADER))) |
| continue; |
| |
| for (j = 0; j < ARRAY_SIZE(configs); j++) { |
| if (!strncmp(config_attr, configs[j].name, |
| strlen(configs[j].name))) { |
| b->dest_cpu = configs[j].dest_cpu; |
| b->flags |= configs[j].flags; |
| config_set = 1; |
| } |
| } |
| |
| if (!config_set) { |
| printf("ERROR: Unsupported fsbl_config: %s\n", |
| config_attr); |
| return -1; |
| } |
| } |
| |
| if (!config_set) { |
| printf("ERROR: fsbl_config w/o bootloader\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static const struct bif_flags *find_flag(char *str) |
| { |
| const struct bif_flags *bf; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(bif_flags); i++) { |
| bf = &bif_flags[i]; |
| if (!strncmp(bf->name, str, strlen(bf->name))) |
| return bf; |
| } |
| |
| printf("ERROR: Flag '%s' not found\n", str); |
| |
| return NULL; |
| } |
| |
| static int bif_open_file(struct bif_entry *entry) |
| { |
| int fd = open(entry->filename, O_RDONLY); |
| |
| if (fd < 0) |
| printf("Error opening file %s\n", entry->filename); |
| |
| return fd; |
| } |
| |
| static const struct bif_file_type *get_file_type(struct bif_entry *entry) |
| { |
| int fd = bif_open_file(entry); |
| uint32_t header; |
| int i; |
| |
| if (fd < 0) |
| return NULL; |
| |
| if (read(fd, &header, sizeof(header)) != sizeof(header)) { |
| printf("Error reading file %s", entry->filename); |
| return NULL; |
| } |
| |
| close(fd); |
| |
| for (i = 0; i < ARRAY_SIZE(bif_file_types); i++) { |
| const struct bif_file_type *type = &bif_file_types[i]; |
| |
| if (!type->header) |
| return type; |
| if (type->header == be32_to_cpu(header)) |
| return type; |
| } |
| |
| return NULL; |
| } |
| |
| #define NEXT_CHAR(str, chr) ({ \ |
| char *_n = strchr(str, chr); \ |
| if (!_n) \ |
| goto err; \ |
| _n; \ |
| }) |
| |
| static char *skip_whitespace(char *str) |
| { |
| while (*str == ' ' || *str == '\t') |
| str++; |
| |
| return str; |
| } |
| |
| int zynqmpbif_copy_image(int outfd, struct image_tool_params *mparams) |
| { |
| char *bif, *bifp, *bifpn; |
| char *line; |
| struct bif_entry entries[32] = { { 0 } }; |
| int nr_entries = 0; |
| struct bif_entry *entry = entries; |
| size_t len; |
| int i; |
| uint32_t csum; |
| int bldr = -1; |
| |
| bif_init(); |
| |
| /* Read .bif input file */ |
| bif = read_full_file(mparams->datafile, NULL); |
| if (!bif) |
| goto err; |
| |
| /* Interpret .bif file */ |
| bifp = bif; |
| |
| /* A bif description starts with a { section */ |
| bifp = NEXT_CHAR(bifp, '{') + 1; |
| |
| /* Read every line */ |
| while (1) { |
| bifpn = NEXT_CHAR(bifp, '\n'); |
| |
| if (bifpn[-1] == '\r') |
| bifpn[-1] = '\0'; |
| |
| *bifpn = '\0'; |
| bifpn++; |
| line = bifp; |
| |
| line = skip_whitespace(line); |
| |
| /* Attributes? */ |
| if (*line == '[') { |
| line++; |
| while (1) { |
| const struct bif_flags *bf; |
| |
| line = skip_whitespace(line); |
| bf = find_flag(line); |
| if (!bf) |
| goto err; |
| |
| line += strlen(bf->name); |
| if (bf->parse) |
| line = bf->parse(line, entry); |
| else |
| entry->flags |= 1ULL << bf->flag; |
| |
| if (!line) |
| goto err; |
| |
| /* Go to next attribute or quit */ |
| if (*line == ']') { |
| line++; |
| break; |
| } |
| if (*line == ',') |
| line++; |
| } |
| } |
| |
| /* End of image description */ |
| if (*line == '}') |
| break; |
| |
| if (*line) { |
| line = skip_whitespace(line); |
| entry->filename = line; |
| nr_entries++; |
| entry++; |
| } |
| |
| /* Use next line */ |
| bifp = bifpn; |
| } |
| |
| for (i = 0; i < nr_entries; i++) { |
| debug("Entry flags=%#lx name=%s\n", entries[i].flags, |
| entries[i].filename); |
| } |
| |
| /* |
| * Some entries are actually configuration option for other ones, |
| * let's apply them in an intermediate step. |
| */ |
| for (i = 0; i < nr_entries; i++) { |
| struct bif_entry *entry = &entries[i]; |
| |
| if (entry->flags & (1ULL << BIF_FLAG_FSBL_CONFIG)) |
| if (bif_fsbl_config(entry, entries, nr_entries)) |
| goto err; |
| } |
| |
| /* Make sure PMUFW comes before bootloader */ |
| for (i = 0; i < nr_entries; i++) { |
| struct bif_entry *entry = &entries[i]; |
| |
| if (entry->flags & (1ULL << BIF_FLAG_BOOTLOADER)) |
| bldr = i; |
| if (entry->flags & (1ULL << BIF_FLAG_PMUFW_IMAGE)) { |
| if (bldr >= 0) { |
| struct bif_entry tmp = *entry; |
| |
| *entry = entries[bldr]; |
| entries[bldr] = tmp; |
| } |
| } |
| } |
| |
| for (i = 0; i < nr_entries; i++) { |
| struct bif_entry *entry = &entries[i]; |
| const struct bif_file_type *type; |
| int r; |
| |
| if (entry->flags & (1ULL << BIF_FLAG_FSBL_CONFIG)) |
| continue; |
| |
| type = get_file_type(entry); |
| if (!type) |
| goto err; |
| |
| debug("type=%s file=%s\n", type->name, entry->filename); |
| r = type->add(entry); |
| if (r) |
| goto err; |
| } |
| |
| /* Calculate checksums */ |
| csum = zynqmp_csum(&bif_output.header->width_detection, |
| &bif_output.header->checksum); |
| bif_output.header->checksum = cpu_to_le32(csum); |
| |
| if (bif_output.imgheader) { |
| csum = zynqmp_csum(bif_output.imgheader, |
| &bif_output.imgheader->checksum); |
| bif_output.imgheader->checksum = cpu_to_le32(csum); |
| } |
| |
| /* Write headers and components */ |
| if (lseek(outfd, 0, SEEK_SET) != 0) |
| goto err; |
| |
| len = bif_output.data_len; |
| bifp = bif_output.data; |
| while (len) { |
| int r; |
| |
| r = write(outfd, bifp, len); |
| if (r < 0) |
| goto err; |
| len -= r; |
| bifp += r; |
| } |
| |
| return 0; |
| |
| err: |
| fprintf(stderr, "Error: Failed to create image.\n"); |
| return -1; |
| } |
| |
| /* Needs to be stubbed out so we can print after creation */ |
| static void zynqmpbif_set_header(void *ptr, struct stat *sbuf, int ifd, |
| struct image_tool_params *params) |
| { |
| } |
| |
| static struct zynqmp_header zynqmpimage_header; |
| |
| U_BOOT_IMAGE_TYPE( |
| zynqmpbif, |
| "Xilinx ZynqMP Boot Image support (bif)", |
| sizeof(struct zynqmp_header), |
| (void *)&zynqmpimage_header, |
| zynqmpbif_check_params, |
| NULL, |
| zynqmpimage_print_header, |
| zynqmpbif_set_header, |
| NULL, |
| zynqmpbif_check_image_types, |
| NULL, |
| NULL |
| ); |