| /* | |
| * 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; | |
| size_t len_padded = ROUND(len, 4); | |
| struct partition_header parthdr = { | |
| .len_enc = cpu_to_le32(len_padded / 4), | |
| .len_unenc = cpu_to_le32(len_padded / 4), | |
| .len = cpu_to_le32(len_padded / 4), | |
| .entry_point = cpu_to_le64(bf->entry), | |
| .load_address = cpu_to_le64(bf->load), | |
| }; | |
| int r; | |
| uint32_t csum; | |
| if (len < len_padded) { | |
| char *newdata = malloc(len_padded); | |
| memcpy(newdata, data, len); | |
| memset(newdata + len, 0, len_padded - len); | |
| data = newdata; | |
| } | |
| 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_padded); | |
| bif_output.header->image_stored_size = cpu_to_le32(len_padded); | |
| 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 = __builtin_bswap32(*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 | |
| ); |