diff --git a/board/schneider/rzn1-snarc/spkgimage.cfg b/board/schneider/rzn1-snarc/spkgimage.cfg new file mode 100644 index 0000000000..b5faf96b00 --- /dev/null +++ b/board/schneider/rzn1-snarc/spkgimage.cfg @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2022 Schneider Electric +# +# SPKG image header, for booting on RZ/N1 + +# b[35:32] SPKG version +VERSION 1 + +# b[42:41] ECC Block size: 0=256 bytes, 1=512 bytes, 2=1024 bytes +NAND_ECC_BLOCK_SIZE 1 + +# b[45] NAND enable (boolean) +NAND_ECC_ENABLE 1 + +# b[50:48] ECC Scheme: 0=BCH2 1=BCH4 2=BCH8 3=BCH16 4=BCH24 5=BCH32 +NAND_ECC_SCHEME 3 + +# b[63:56] ECC bytes per block +NAND_BYTES_PER_ECC_BLOCK 28 + +# Provide dummy BLp header (boolean) +ADD_DUMMY_BLP 1 + +# Pad the image to a multiple of +PADDING 64K diff --git a/boot/image.c b/boot/image.c index 958dbf8534..5c4f9b807d 100644 --- a/boot/image.c +++ b/boot/image.c @@ -181,6 +181,7 @@ static const table_entry_t uimage_type[] = { { IH_TYPE_SUNXI_EGON, "sunxi_egon", "Allwinner eGON Boot Image" }, { IH_TYPE_SUNXI_TOC0, "sunxi_toc0", "Allwinner TOC0 Boot Image" }, { IH_TYPE_FDT_LEGACY, "fdt_legacy", "legacy Image with Flat Device Tree ", }, + { IH_TYPE_RENESAS_SPKG, "spkgimage", "Renesas SPKG Image" }, { -1, "", "", }, }; diff --git a/doc/mkimage.1 b/doc/mkimage.1 index d8727ec73c..76c7859bb0 100644 --- a/doc/mkimage.1 +++ b/doc/mkimage.1 @@ -662,6 +662,51 @@ rk3568 .TE .RE . +.SS spkgimage +The primary configuration file consists of lines containing key/value pairs +delimited by whitespace. An example follows. +.PP +.RS +.EX +# Comments and blank lines may be used +.I key1 value1 +.I key2 value2 +.EE +.RE +.P +The supported +.I key +types are as follows. +.TP +.B VERSION +.TQ +.B NAND_ECC_BLOCK_SIZE +.TQ +.B NAND_ECC_ENABLE +.TQ +.B NAND_ECC_SCHEME +.TQ +.B NAND_BYTES_PER_ECC_BLOCK +These all take a positive integer value as their argument. +The value will be copied directly into the respective field +of the SPKG header structure. For details on these values, +refer to Section 7.4 of the Renesas RZ/N1 User's Manual. +. +.TP +.B ADD_DUMMY_BLP +Takes a numeric argument, which is treated as a boolean. Any nonzero +value will cause a fake BLp security header to be included in the SPKG +output. +. +.TP +.B PADDING +Takes a positive integer value, with an optional +.B K +or +.B M +suffix, indicating KiB / MiB respectively. +The output SPKG file will be padded to a multiple of this value. +. .SS sunxi_egon The primary configuration is the name to use for the device tree. . diff --git a/include/image.h b/include/image.h index 456197d6fd..01a6787d21 100644 --- a/include/image.h +++ b/include/image.h @@ -230,6 +230,7 @@ enum image_type_t { IH_TYPE_SUNXI_EGON, /* Allwinner eGON Boot Image */ IH_TYPE_SUNXI_TOC0, /* Allwinner TOC0 Boot Image */ IH_TYPE_FDT_LEGACY, /* Binary Flat Device Tree Blob in a Legacy Image */ + IH_TYPE_RENESAS_SPKG, /* Renesas SPKG image */ IH_TYPE_COUNT, /* Number of image types */ }; diff --git a/tools/Makefile b/tools/Makefile index 38699b069d..d793cf3bec 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -122,6 +122,7 @@ dumpimage-mkimage-objs := aisimage.o \ os_support.o \ pblimage.o \ pbl_crc32.o \ + renesas_spkgimage.o \ vybridimage.o \ stm32image.o \ $(ROCKCHIP_OBS) \ diff --git a/tools/renesas_spkgimage.c b/tools/renesas_spkgimage.c new file mode 100644 index 0000000000..fa0a468cc4 --- /dev/null +++ b/tools/renesas_spkgimage.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Generate Renesas RZ/N1 BootROM header (SPKG) + * (C) Copyright 2022 Schneider Electric + * + * Based on spkg_utility.c + * (C) Copyright 2016 Renesas Electronics Europe Ltd + */ + +#include "imagetool.h" +#include +#include +#include +#include +#include +#include "renesas_spkgimage.h" + +/* Note: the ordering of the bitfields does not matter */ +struct config_file { + unsigned int version:1; + unsigned int ecc_block_size:2; + unsigned int ecc_enable:1; + unsigned int ecc_scheme:3; + unsigned int ecc_bytes:8; + unsigned int blp_len; + unsigned int padding; +}; + +static struct config_file conf; + +static int check_range(const char *name, int val, int min, int max) +{ + if (val < min) { + fprintf(stderr, "Warning: param '%s' adjusted to min %d\n", + name, min); + val = min; + } + + if (val > max) { + fprintf(stderr, "Warning: param '%s' adjusted to max %d\n", + name, max); + val = max; + } + + return val; +} + +static int spkgimage_parse_config_line(char *line, size_t line_num) +{ + char *saveptr; + char *delim = "\t "; + char *name = strtok_r(line, delim, &saveptr); + char *val_str = strtok_r(NULL, delim, &saveptr); + int value = atoi(val_str); + + if (!strcmp("VERSION", name)) { + conf.version = check_range(name, value, 1, 15); + } else if (!strcmp("NAND_ECC_ENABLE", name)) { + conf.ecc_enable = check_range(name, value, 0, 1); + } else if (!strcmp("NAND_ECC_BLOCK_SIZE", name)) { + conf.ecc_block_size = check_range(name, value, 0, 2); + } else if (!strcmp("NAND_ECC_SCHEME", name)) { + conf.ecc_scheme = check_range(name, value, 0, 7); + } else if (!strcmp("NAND_BYTES_PER_ECC_BLOCK", name)) { + conf.ecc_bytes = check_range(name, value, 0, 255); + } else if (!strcmp("ADD_DUMMY_BLP", name)) { + conf.blp_len = value ? SPKG_BLP_SIZE : 0; + } else if (!strcmp("PADDING", name)) { + if (strrchr(val_str, 'K')) + value = value * 1024; + else if (strrchr(val_str, 'M')) + value = value * 1024 * 1024; + conf.padding = check_range(name, value, 1, INT_MAX); + } else { + fprintf(stderr, + "config error: unknown keyword on line %ld\n", + line_num); + return -EINVAL; + } + + return 0; +} + +static int spkgimage_parse_config_file(char *filename) +{ + FILE *fcfg; + char line[256]; + size_t line_num = 0; + + fcfg = fopen(filename, "r"); + if (!fcfg) + return -EINVAL; + + while (fgets(line, sizeof(line), fcfg)) { + line_num += 1; + + /* Skip blank lines and comments */ + if (line[0] == '\n' || line[0] == '#') + continue; + + /* Strip any trailing newline */ + line[strcspn(line, "\n")] = 0; + + /* Parse the line */ + if (spkgimage_parse_config_line(line, line_num)) + return -EINVAL; + } + + fclose(fcfg); + + /* Avoid divide-by-zero later on */ + if (!conf.padding) + conf.padding = 1; + + return 0; +} + +static int spkgimage_check_params(struct image_tool_params *params) +{ + if (!params->addr) { + fprintf(stderr, "Error: Load Address must be set.\n"); + return -EINVAL; + } + + if (!params->imagename || !params->imagename[0]) { + fprintf(stderr, "Error: Image name must be set.\n"); + return -EINVAL; + } + + if (!params->datafile) { + fprintf(stderr, "Error: Data filename must be set.\n"); + return -EINVAL; + } + + return 0; +} + +static int spkgimage_verify_header(unsigned char *ptr, int size, + struct image_tool_params *param) +{ + struct spkg_file *file = (struct spkg_file *)ptr; + struct spkg_hdr *header = (struct spkg_hdr *)ptr; + char marker[4] = SPKG_HEADER_MARKER; + uint32_t payload_length; + uint32_t crc; + uint8_t *crc_buf; + + /* Check the marker bytes */ + if (memcmp(header->marker, marker, 4)) { + fprintf(stderr, "Error: invalid marker bytes\n"); + return -EINVAL; + } + + /* Check the CRC */ + crc = crc32(0, ptr, SPKG_HEADER_SIZE - SPKG_CRC_SIZE); + if (crc != header->crc) { + fprintf(stderr, "Error: invalid header CRC=\n"); + return -EINVAL; + } + + /* Check all copies of header are the same */ + for (int i = 1; i < SPKG_HEADER_COUNT; i++) { + if (memcmp(&header[0], &header[i], SPKG_HEADER_SIZE)) { + fprintf(stderr, "Error: header %d mismatch\n", i); + return -EINVAL; + } + } + + /* Check the payload CRC */ + payload_length = le32_to_cpu(header->payload_length) >> 8; + crc_buf = file->payload + payload_length - SPKG_CRC_SIZE; + crc = crc32(0, file->payload, payload_length - SPKG_CRC_SIZE); + if (crc_buf[0] != (crc & 0xff) || + crc_buf[1] != (crc >> 8 & 0xff) || + crc_buf[2] != (crc >> 16 & 0xff) || + crc_buf[3] != (crc >> 24 & 0xff)) { + fprintf(stderr, "Error: invalid payload CRC\n"); + return -EINVAL; + } + + return 0; +} + +static void spkgimage_print_header(const void *ptr, + struct image_tool_params *image) +{ + const struct spkg_hdr *h = ptr; + uint32_t offset = le32_to_cpu(h->execution_offset); + + printf("Image type\t: Renesas SPKG Image\n"); + printf("Marker\t\t: %c%c%c%c\n", + h->marker[0], h->marker[1], h->marker[2], h->marker[3]); + printf("Version\t\t: %d\n", h->version); + printf("ECC\t\t: "); + if (h->ecc & 0x20) + printf("Scheme %d, Block size %d, Strength %d\n", + h->ecc_scheme, (h->ecc >> 1) & 3, h->ecc_bytes); + else + printf("Not enabled\n"); + printf("Payload length\t: %d\n", le32_to_cpu(h->payload_length) >> 8); + printf("Load address\t: 0x%08x\n", le32_to_cpu(h->load_address)); + printf("Execution offset: 0x%08x (%s mode)\n", offset & ~1, + offset & 1 ? "THUMB" : "ARM"); + printf("Header checksum\t: 0x%08x\n", le32_to_cpu(h->crc)); +} + +/* + * This is the same as the macro version in include/kernel.h. + * However we cannot include that header, because for host tools, + * it ends up pulling in the host /usr/include/linux/kernel.h, + * which lacks the definition of roundup(). + */ +static inline uint32_t roundup(uint32_t x, uint32_t y) +{ + return ((x + y - 1) / y) * y; +} + +static int spkgimage_vrec_header(struct image_tool_params *params, + struct image_type_params *tparams) +{ + struct stat s; + struct spkg_file *out_buf; + + /* Parse the config file */ + if (spkgimage_parse_config_file(params->imagename)) { + fprintf(stderr, "Error parsing config file\n"); + exit(EXIT_FAILURE); + } + + /* Get size of input data file */ + if (stat(params->datafile, &s)) { + fprintf(stderr, "Could not stat data file: %s: %s\n", + params->datafile, strerror(errno)); + exit(EXIT_FAILURE); + } + params->orig_file_size = s.st_size; + + /* Determine size of resulting SPKG file */ + uint32_t header_len = SPKG_HEADER_SIZE * SPKG_HEADER_COUNT; + uint32_t payload_len = conf.blp_len + s.st_size + SPKG_CRC_SIZE; + uint32_t total_len = header_len + payload_len; + + /* Round up to next multiple of padding size */ + uint32_t padded_len = roundup(total_len, conf.padding); + + /* Number of padding bytes to add */ + conf.padding = padded_len - total_len; + + /* Fixup payload_len to include padding bytes */ + payload_len += conf.padding; + + /* Prepare the header */ + struct spkg_hdr header = { + .marker = SPKG_HEADER_MARKER, + .version = conf.version, + .ecc = (conf.ecc_enable << 5) | (conf.ecc_block_size << 1), + .ecc_scheme = conf.ecc_scheme, + .ecc_bytes = conf.ecc_bytes, + .payload_length = cpu_to_le32(payload_len << 8), + .load_address = cpu_to_le32(params->addr), + .execution_offset = cpu_to_le32(params->ep - params->addr), + }; + header.crc = crc32(0, (uint8_t *)&header, + sizeof(header) - SPKG_CRC_SIZE); + + /* The SPKG contains 8 copies of the header */ + out_buf = malloc(sizeof(struct spkg_file)); + if (!out_buf) { + fprintf(stderr, "Error: Data filename must be set.\n"); + return -ENOMEM; + } + tparams->hdr = out_buf; + tparams->header_size = sizeof(struct spkg_file); + + /* Fill the SPKG with the headers */ + for (int i = 0; i < SPKG_HEADER_COUNT; i++) + memcpy(&out_buf->header[i], &header, sizeof(header)); + + /* Extra bytes to allocate in the output file */ + return conf.blp_len + conf.padding + 4; +} + +static void spkgimage_set_header(void *ptr, struct stat *sbuf, int ifd, + struct image_tool_params *params) +{ + uint8_t *payload = ptr + SPKG_HEADER_SIZE * SPKG_HEADER_COUNT; + uint8_t *file_end = payload + conf.blp_len + params->orig_file_size; + uint8_t *crc_buf = file_end + conf.padding; + uint32_t crc; + + /* Make room for the Dummy BLp header */ + memmove(payload + conf.blp_len, payload, params->orig_file_size); + + /* Fill the SPKG with the Dummy BLp */ + memset(payload, 0x88, conf.blp_len); + + /* + * mkimage copy_file() pads the input file with zeros. + * Replace those zeros with flash friendly one bits. + * The original version skipped the first 4 bytes, + * probably an oversight, but for consistency we + * keep the same behaviour. + */ + if (conf.padding >= 4) + memset(file_end + 4, 0xff, conf.padding - 4); + + /* Add Payload CRC */ + crc = crc32(0, payload, crc_buf - payload); + crc_buf[0] = crc; + crc_buf[1] = crc >> 8; + crc_buf[2] = crc >> 16; + crc_buf[3] = crc >> 24; +} + +static int spkgimage_check_image_types(uint8_t type) +{ + return type == IH_TYPE_RENESAS_SPKG ? 0 : -EINVAL; +} + +/* + * spkgimage type parameter definition + */ +U_BOOT_IMAGE_TYPE( + spkgimage, + "Renesas SPKG Image", + 0, + NULL, + spkgimage_check_params, + spkgimage_verify_header, + spkgimage_print_header, + spkgimage_set_header, + NULL, + spkgimage_check_image_types, + NULL, + spkgimage_vrec_header +); diff --git a/tools/renesas_spkgimage.h b/tools/renesas_spkgimage.h new file mode 100644 index 0000000000..367e113de9 --- /dev/null +++ b/tools/renesas_spkgimage.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Renesas RZ/N1 Package Table format + * (C) 2015-2016 Renesas Electronics Europe, LTD + * All rights reserved. + * + * Converted to mkimage plug-in + * (C) Copyright 2022 Schneider Electric + */ + +#ifndef _SPKGIMAGE_H_ +#define _SPKGIMAGE_H_ + +#ifdef __GNUC__ +#define __packed __attribute((packed)) +#else +#define __packed +#endif + +#define SPKG_HEADER_MARKER {'R', 'Z', 'N', '1'} +#define SPKG_HEADER_SIZE 24 +#define SPKG_HEADER_COUNT 8 +#define SPKG_BLP_SIZE 264 +#define SPKG_CRC_SIZE 4 + +/** + * struct spkg_hdr - SPKG header + * @marker: magic pattern "RZN1" + * @version: header version (currently 1) + * @ecc: ECC enable and block size. + * @ecc_scheme: ECC algorithm selction + * @ecc_bytes: ECC bytes per block + * @payload_length: length of the payload (including CRC) + * @load_address: address in memory where payload should be loaded + * @execution_offset: offset from @load_address where execution starts + * @crc: 32-bit CRC of the above header fields + * + * SPKG header format is defined by Renesas. It is documented in the Reneasas + * RZ/N1 User Manual, Chapter 7.4 ("SPKG format"). + * + * The BootROM searches this header in order to find and validate the boot + * payload. It is therefore mandatory to wrap the payload in this header. + * + * The ECC-related fields @ecc @ecc_scheme @ecc_bytes are used only when + * booting from NAND flash, and they are only used while fetching the payload. + * These values are used to initialize the ECC controller. To avoid using + * non-portable bitfields, struct spkg_hdr uses uint8_t for these fields, so + * the user must shift the values into the correct spot. + * + * The payload will be loaded into memory at @payload_address. + * Execution then jumps to @payload_address + @execution_offset. + * The LSB of @execution_offset selects between ARM and Thumb mode, + * as per the usual ARM interworking convention. + */ +struct spkg_hdr { + uint8_t marker[4]; /* aka magic */ + uint8_t version; + uint8_t ecc; + uint8_t ecc_scheme; + uint8_t ecc_bytes; + uint32_t payload_length; /* only HIGHER 24 bits */ + uint32_t load_address; + uint32_t execution_offset; + uint32_t crc; /* of this header */ +} __packed; + +/** + * struct spkg_file - complete SPKG image + * + * A SPKG image consists of 8 identical copies of struct spkg_hdr, each one + * occupying 24 bytes, for a total of 192 bytes. + * + * This is followed by the payload (the u-boot binary), and a 32-bit CRC. + * + * Optionally, the payload can be being with security header ("BLp_header"). + * This feature is not currently supported in mkimage. + * + * The payload is typically padded with 0xFF bytes so as to bring the total + * image size to a multiple of the flash erase size (often 64kB). + */ +struct spkg_file { + struct spkg_hdr header[SPKG_HEADER_COUNT]; + uint8_t payload[0]; + /* then the CRC */ +} __packed; + +#endif