--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Copyright Contributors to the U-Boot project.
+
+#include "imagetool.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
+);