| // SPDX-License-Identifier: GPL-2.0-or-later | |
| // Copyright Contributors to the U-Boot project. | |
| #include "imagetool.h" | |
| #include <inttypes.h> | |
| #include <u-boot/sha256.h> | |
| /* | |
| * Image contain data in the following order: | |
| * Nonce 16 byte | |
| * Header 64 byte | |
| * Digest 32 byte | |
| * Padding align up to 4K | |
| * Payload | |
| */ | |
| #define HEADER_MAGIC 0x4c4d4140 /* @AML */ | |
| #define HEADER_OFFSET 0x10 /* 16 */ | |
| #define HEADER_SIZE 0x40 /* 64 */ | |
| #define PAYLOAD_OFFSET 0x1000 /* 4096 */ | |
| struct amlimage_header { | |
| uint32_t magic; | |
| uint32_t total_size; | |
| uint8_t header_size; | |
| uint8_t root_key_index; | |
| uint8_t version_major; | |
| uint8_t version_minor; | |
| uint32_t padding1; | |
| uint32_t digest_type; | |
| uint32_t digest_offset; | |
| uint32_t digest_size; | |
| uint32_t data_offset; | |
| uint32_t key_type; | |
| uint32_t key_offset; | |
| uint32_t key_size; | |
| uint32_t data_size; | |
| uint32_t payload_type; | |
| uint32_t payload_offset; | |
| uint32_t payload_size; | |
| uint32_t padding2; | |
| } __packed; | |
| struct amlimage_variant { | |
| const char *name; | |
| const struct amlimage_header hdr; | |
| }; | |
| #define VARIANT(name, major, minor, size) \ | |
| { name, { .magic = HEADER_MAGIC, .header_size = HEADER_SIZE, \ | |
| .version_major = major, .version_minor = minor, \ | |
| .payload_size = size, } } | |
| static const struct amlimage_variant variants[] = { | |
| VARIANT("gxbb", 1, 0, 0xb000), | |
| VARIANT("gxl", 1, 1, 0xb000), | |
| VARIANT("gxm", 1, 1, 0xb000), | |
| VARIANT("axg", 1, 1, 0xb000), | |
| VARIANT("g12a", 1, 1, 0xf000), | |
| VARIANT("g12b", 1, 1, 0xf000), | |
| VARIANT("sm1", 1, 1, 0xf000), | |
| }; | |
| static const struct amlimage_variant *amlimage_get_variant(const char *name) | |
| { | |
| if (!name) | |
| return NULL; | |
| for (int i = 0; i < ARRAY_SIZE(variants); i++) | |
| if (!strcmp(name, variants[i].name)) | |
| return &variants[i]; | |
| return NULL; | |
| } | |
| static int amlimage_check_params(struct image_tool_params *params) | |
| { | |
| const struct amlimage_variant *variant = | |
| amlimage_get_variant(params->imagename); | |
| int datafile_size; | |
| if (params->lflag || params->iflag) | |
| return EXIT_SUCCESS; | |
| if (!variant) { | |
| fprintf(stderr, "%s: unsupported image name: %s\n", | |
| params->cmdname, params->imagename); | |
| exit(EXIT_FAILURE); | |
| } | |
| datafile_size = imagetool_get_filesize(params, params->datafile); | |
| if (datafile_size < 0) { | |
| exit(EXIT_FAILURE); | |
| } else if (datafile_size > variant->hdr.payload_size) { | |
| fprintf(stderr, "%s: datafile is too large (%#x > %#x)\n", | |
| params->cmdname, datafile_size, | |
| variant->hdr.payload_size); | |
| exit(EXIT_FAILURE); | |
| } | |
| return EXIT_SUCCESS; | |
| } | |
| static int amlimage_verify_header(unsigned char *buf, int size, | |
| struct image_tool_params *params) | |
| { | |
| const struct amlimage_header *hdr = (void *)buf + HEADER_OFFSET; | |
| if (size >= HEADER_OFFSET + HEADER_SIZE + SHA256_SUM_LEN && | |
| hdr->magic == HEADER_MAGIC && hdr->header_size == HEADER_SIZE && | |
| hdr->version_major == 1 && hdr->version_minor <= 1) | |
| return 0; | |
| return -1; | |
| } | |
| static void amlimage_print_header(const void *buf, | |
| struct image_tool_params *params) | |
| { | |
| const struct amlimage_header *hdr = buf + HEADER_OFFSET; | |
| uint8_t digest[SHA256_SUM_LEN]; | |
| sha256_context ctx; | |
| bool valid; | |
| printf("Amlogic Boot Image %" PRIu8 ".%" PRIu8 "\n", | |
| hdr->version_major, hdr->version_minor); | |
| printf("Total size: %" PRIu32 "\n", hdr->total_size); | |
| printf("Digest %" PRIu32 ": %" PRIu32 " @ 0x%" PRIx32 "\n", | |
| hdr->digest_type, hdr->digest_size, hdr->digest_offset); | |
| printf("Key %" PRIu32 ": %" PRIu32 " @ 0x%" PRIx32 "\n", | |
| hdr->key_type, hdr->key_size, hdr->key_offset); | |
| printf("Payload %" PRIu32 ": %" PRIu32 " @ 0x%" PRIx32 "\n", | |
| hdr->payload_type, hdr->payload_size, hdr->payload_offset); | |
| if (hdr->digest_type == 0) { | |
| /* sha256 digest (normal boot) */ | |
| sha256_starts(&ctx); | |
| /* Header and data is used as input for sha256 digest */ | |
| sha256_update(&ctx, (void *)hdr, hdr->header_size); | |
| sha256_update(&ctx, (void *)hdr + hdr->data_offset, hdr->data_size); | |
| sha256_finish(&ctx, digest); | |
| valid = !memcmp((void *)hdr + hdr->digest_offset, | |
| digest, SHA256_SUM_LEN); | |
| printf("Data: %" PRIu32 " @ 0x%" PRIx32 " - %s\n", | |
| hdr->data_size, hdr->data_offset, valid ? "OK" : "BAD"); | |
| } else { | |
| /* RSA (secure boot) */ | |
| printf("Data: %" PRIu32 " @ 0x%" PRIx32 " - Secure Boot\n", | |
| hdr->data_size, hdr->data_offset); | |
| } | |
| } | |
| static void amlimage_set_header(void *buf, struct stat *sbuf, int ifd, | |
| struct image_tool_params *params) | |
| { | |
| struct amlimage_header *hdr = buf + HEADER_OFFSET; | |
| sha256_context ctx; | |
| /* Use header size as initial size */ | |
| hdr->total_size = hdr->header_size; | |
| /* Use sha256 digest (normal boot) */ | |
| hdr->digest_type = 0; | |
| /* The sha256 digest is stored directly following the header */ | |
| hdr->digest_offset = hdr->total_size; | |
| /* Unknown if this is used as block size instead of digest size */ | |
| hdr->digest_size = 512; | |
| hdr->total_size += hdr->digest_size; | |
| /* Use key as padding so that payload ends up 4K aligned in TZRAM */ | |
| hdr->key_type = 0; | |
| hdr->key_offset = hdr->total_size; | |
| hdr->key_size = PAYLOAD_OFFSET - HEADER_OFFSET - hdr->key_offset; | |
| hdr->total_size += hdr->key_size; | |
| /* With padding above payload will have a 0x1000 offset in TZRAM */ | |
| hdr->payload_type = 0; | |
| hdr->payload_offset = hdr->total_size; | |
| /* Payload size has already been copied from the variant header */ | |
| hdr->total_size += hdr->payload_size; | |
| /* Set the data range to be used as input for sha256 digest */ | |
| hdr->data_offset = hdr->digest_offset + SHA256_SUM_LEN; | |
| hdr->data_size = hdr->total_size - hdr->data_offset; | |
| sha256_starts(&ctx); | |
| /* Header and data is used as input for sha256 digest */ | |
| sha256_update(&ctx, (void *)hdr, hdr->header_size); | |
| sha256_update(&ctx, (void *)hdr + hdr->data_offset, hdr->data_size); | |
| /* Write sha256 digest to the 32 bytes directly following the header */ | |
| sha256_finish(&ctx, (void *)hdr + hdr->digest_offset); | |
| } | |
| static int amlimage_extract_subimage(void *buf, | |
| struct image_tool_params *params) | |
| { | |
| const struct amlimage_header *hdr = buf + HEADER_OFFSET; | |
| /* Save payload as the subimage */ | |
| return imagetool_save_subimage(params->outfile, | |
| (ulong)hdr + hdr->payload_offset, | |
| hdr->payload_size); | |
| } | |
| static int amlimage_check_image_type(uint8_t type) | |
| { | |
| if (type == IH_TYPE_AMLIMAGE) | |
| return EXIT_SUCCESS; | |
| return EXIT_FAILURE; | |
| } | |
| static int amlimage_vrec_header(struct image_tool_params *params, | |
| struct image_type_params *tparams) | |
| { | |
| const struct amlimage_variant *variant = | |
| amlimage_get_variant(params->imagename); | |
| const struct amlimage_header *hdr = &variant->hdr; | |
| /* Use payload offset as header size, datafile will be appended */ | |
| tparams->header_size = PAYLOAD_OFFSET; | |
| tparams->hdr = calloc(1, tparams->header_size); | |
| if (!tparams->hdr) { | |
| fprintf(stderr, "%s: Can't alloc header: %s\n", | |
| params->cmdname, strerror(errno)); | |
| exit(EXIT_FAILURE); | |
| } | |
| /* Start with a copy of the variant header */ | |
| memcpy(tparams->hdr + HEADER_OFFSET, hdr, hdr->header_size); | |
| /* Pad up to payload size of the variant header */ | |
| return hdr->payload_size - params->file_size; | |
| } | |
| /* | |
| * amlimage parameters | |
| */ | |
| U_BOOT_IMAGE_TYPE( | |
| amlimage, | |
| "Amlogic Boot Image", | |
| 0, | |
| NULL, | |
| amlimage_check_params, | |
| amlimage_verify_header, | |
| amlimage_print_header, | |
| amlimage_set_header, | |
| amlimage_extract_subimage, | |
| amlimage_check_image_type, | |
| NULL, | |
| amlimage_vrec_header | |
| ); |