u-boot/tools/imx8mimage.c
Mamta Shukla 5fe1d4b5c4 tools: mkimage: Add support to generate FlexSPI Header for i.MX8m
Add struct with Flex SPI Configuration Block and enable generating
fspi header using mkimage.

Refer i.MX 8M Mini Application Processor Reference Manual for
detailed information about parameters for FlexSPI Configuration block.

Signed-off-by: Mamta Shukla <mamta.shukla@leica-geosystems.com>
Signed-off-by: Thomas Haemmerle <thomas.haemmerle@leica-geosystems.com>
Tested-by: Adam Ford <aford173@gmail.com>
Reviewed-by: Fabio Estevam <festevam@denx.de>
Reviewed-by: Andrey Zhizhikin <andrey.zhizhikin@leica-geosystems.com>
2022-07-25 15:35:34 +02:00

717 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 NXP
*
* Peng Fan <peng.fan@nxp.com>
*/
#include "imagetool.h"
#include <image.h>
#include "imximage.h"
#include "compiler.h"
static uint32_t ap_start_addr, sld_start_addr, sld_src_off;
static char *ap_img, *sld_img, *signed_hdmi;
static imx_header_v3_t imx_header[2]; /* At most there are 3 IVT headers */
static uint32_t rom_image_offset;
static uint32_t sector_size = 0x200;
static uint32_t image_off;
static uint32_t sld_header_off;
static uint32_t ivt_offset;
static uint32_t using_fit;
#define ROM_V1 1
#define ROM_V2 2
static uint32_t rom_version = ROM_V1;
#define CSF_SIZE 0x2000
#define HDMI_IVT_ID 0
#define IMAGE_IVT_ID 1
#define HDMI_FW_SIZE 0x17000 /* Use Last 0x1000 for IVT and CSF */
#define ALIGN_SIZE 0x1000
#define ALIGN_IMX(x, a) __ALIGN_MASK_IMX((x), (__typeof__(x))(a) - 1, a)
#define __ALIGN_MASK_IMX(x, mask, mask2) (((x) + (mask)) / (mask2) * (mask2))
static uint32_t get_cfg_value(char *token, char *name, int linenr)
{
char *endptr;
uint32_t value;
errno = 0;
value = strtoul(token, &endptr, 16);
if (errno || token == endptr) {
fprintf(stderr, "Error: %s[%d] - Invalid hex data(%s)\n",
name, linenr, token);
exit(EXIT_FAILURE);
}
return value;
}
int imx8mimage_check_params(struct image_tool_params *params)
{
return 0;
}
static void imx8mimage_set_header(void *ptr, struct stat *sbuf, int ifd,
struct image_tool_params *params)
{
}
static void imx8mimage_print_header(const void *ptr)
{
}
static int imx8mimage_check_image_types(uint8_t type)
{
return (type == IH_TYPE_IMX8MIMAGE) ? EXIT_SUCCESS : EXIT_FAILURE;
}
static table_entry_t imx8mimage_cmds[] = {
{CMD_BOOT_FROM, "BOOT_FROM", "boot command", },
{CMD_FIT, "FIT", "fit image", },
{CMD_SIGNED_HDMI, "SIGNED_HDMI", "signed hdmi image", },
{CMD_LOADER, "LOADER", "loader image", },
{CMD_SECOND_LOADER, "SECOND_LOADER", "2nd loader image", },
{CMD_DDR_FW, "DDR_FW", "ddr firmware", },
{CMD_ROM_VERSION, "ROM_VERSION", "rom version", },
{-1, "", "", },
};
static table_entry_t imx8mimage_ivt_offset[] = {
{0x400, "sd", "sd/emmc",},
{0x400, "emmc_fastboot", "emmc fastboot",},
{0x1000, "fspi", "flexspi", },
{-1, "", "Invalid", },
};
static void parse_cfg_cmd(int32_t cmd, char *token, char *name, int lineno)
{
switch (cmd) {
case CMD_BOOT_FROM:
ivt_offset = get_table_entry_id(imx8mimage_ivt_offset,
"imx8mimage ivt offset",
token);
if (!strncmp(token, "sd", 2))
rom_image_offset = 0x8000;
if (rom_version == ROM_V2)
ivt_offset = 0;
break;
case CMD_LOADER:
ap_img = token;
break;
case CMD_SECOND_LOADER:
sld_img = token;
break;
case CMD_SIGNED_HDMI:
signed_hdmi = token;
break;
case CMD_DDR_FW:
/* Do nothing */
break;
case CMD_ROM_VERSION:
if (!strncmp(token, "v2", 2)) {
rom_version = ROM_V2;
ivt_offset = 0;
} else if (!strncmp(token, "v1", 2)) {
rom_version = ROM_V1;
}
break;
}
}
static void parse_cfg_fld(int32_t *cmd, char *token,
char *name, int lineno, int fld)
{
switch (fld) {
case CFG_COMMAND:
*cmd = get_table_entry_id(imx8mimage_cmds,
"imx8mimage commands", token);
if (*cmd < 0) {
fprintf(stderr, "Error: %s[%d] - Invalid command" "(%s)\n",
name, lineno, token);
exit(EXIT_FAILURE);
}
switch (*cmd) {
case CMD_FIT:
using_fit = 1;
break;
}
break;
case CFG_REG_SIZE:
parse_cfg_cmd(*cmd, token, name, lineno);
break;
case CFG_REG_ADDRESS:
switch (*cmd) {
case CMD_LOADER:
ap_start_addr = get_cfg_value(token, name, lineno);
break;
case CMD_SECOND_LOADER:
sld_start_addr = get_cfg_value(token, name, lineno);
break;
}
break;
case CFG_REG_VALUE:
switch (*cmd) {
case CMD_SECOND_LOADER:
sld_src_off = get_cfg_value(token, name, lineno);
break;
}
default:
break;
}
}
static uint32_t parse_cfg_file(char *name)
{
FILE *fd = NULL;
char *line = NULL;
char *token, *saveptr1, *saveptr2;
int lineno = 0;
int fld;
size_t len;
int32_t cmd;
fd = fopen(name, "r");
if (fd == 0) {
fprintf(stderr, "Error: %s - Can't open cfg file\n", name);
exit(EXIT_FAILURE);
}
/*
* Very simple parsing, line starting with # are comments
* and are dropped
*/
while ((getline(&line, &len, fd)) > 0) {
lineno++;
token = strtok_r(line, "\r\n", &saveptr1);
if (!token)
continue;
/* Check inside the single line */
for (fld = CFG_COMMAND, cmd = CFG_INVALID,
line = token; ; line = NULL, fld++) {
token = strtok_r(line, " \t", &saveptr2);
if (!token)
break;
/* Drop all text starting with '#' as comments */
if (token[0] == '#')
break;
parse_cfg_fld(&cmd, token, name, lineno, fld);
}
}
return 0;
}
static void fill_zero(int ifd, int size, int offset)
{
int fill_size;
uint8_t zeros[4096];
int ret;
memset(zeros, 0, sizeof(zeros));
ret = lseek(ifd, offset, SEEK_SET);
if (ret < 0) {
fprintf(stderr, "%s seek: %s\n", __func__, strerror(errno));
exit(EXIT_FAILURE);
}
while (size) {
if (size > 4096)
fill_size = 4096;
else
fill_size = size;
if (write(ifd, (char *)&zeros, fill_size) != fill_size) {
fprintf(stderr, "Write error: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
size -= fill_size;
};
}
static void copy_file(int ifd, const char *datafile, int pad, int offset,
int datafile_offset)
{
int dfd;
struct stat sbuf;
unsigned char *ptr;
int tail;
uint64_t zero = 0;
uint8_t zeros[4096];
int size, ret;
memset(zeros, 0, sizeof(zeros));
dfd = open(datafile, O_RDONLY | O_BINARY);
if (dfd < 0) {
fprintf(stderr, "Can't open %s: %s\n",
datafile, strerror(errno));
exit(EXIT_FAILURE);
}
if (fstat(dfd, &sbuf) < 0) {
fprintf(stderr, "Can't stat %s: %s\n",
datafile, strerror(errno));
exit(EXIT_FAILURE);
}
ptr = mmap(0, sbuf.st_size, PROT_READ, MAP_SHARED, dfd, 0);
if (ptr == MAP_FAILED) {
fprintf(stderr, "Can't read %s: %s\n",
datafile, strerror(errno));
goto err_mmap;
}
size = sbuf.st_size - datafile_offset;
ret = lseek(ifd, offset, SEEK_SET);
if (ret < 0) {
fprintf(stderr, "lseek ifd fail\n");
exit(EXIT_FAILURE);
}
if (write(ifd, ptr + datafile_offset, size) != size) {
fprintf(stderr, "Write error %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
tail = size % 4;
pad = pad - size;
if (pad == 1 && tail != 0) {
if (write(ifd, (char *)&zero, 4 - tail) != 4 - tail) {
fprintf(stderr, "Write error on %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
} else if (pad > 1) {
while (pad > 0) {
int todo = sizeof(zeros);
if (todo > pad)
todo = pad;
if (write(ifd, (char *)&zeros, todo) != todo) {
fprintf(stderr, "Write error: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
pad -= todo;
}
}
munmap((void *)ptr, sbuf.st_size);
err_mmap:
close(dfd);
}
/* Return this IVT offset in the final output file */
static int generate_ivt_for_fit(int fd, int fit_offset, uint32_t ep,
uint32_t *fit_load_addr)
{
image_header_t image_header;
int ret;
uint32_t fit_size, load_addr;
int align_len = 64 - 1; /* 64 is cacheline size */
ret = lseek(fd, fit_offset, SEEK_SET);
if (ret < 0) {
fprintf(stderr, "lseek fd fail for fit\n");
exit(EXIT_FAILURE);
}
if (read(fd, (char *)&image_header, sizeof(image_header_t)) !=
sizeof(image_header_t)) {
fprintf(stderr, "generate_ivt_for_fit read failed: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
if (be32_to_cpu(image_header.ih_magic) != FDT_MAGIC) {
fprintf(stderr, "%s error: not a FIT file\n", __func__);
exit(EXIT_FAILURE);
}
fit_size = fdt_totalsize(&image_header);
fit_size = ALIGN_IMX(fit_size, ALIGN_SIZE);
ret = lseek(fd, fit_offset + fit_size, SEEK_SET);
if (ret < 0) {
fprintf(stderr, "lseek fd fail for fit\n");
exit(EXIT_FAILURE);
}
/*
* ep is the u-boot entry. SPL loads the FIT before the u-boot
* address. 0x2000 is for CSF_SIZE
*/
load_addr = (ep - (fit_size + CSF_SIZE) - 512 - align_len) &
~align_len;
flash_header_v2_t ivt_header = { { 0xd1, 0x2000, 0x40 },
load_addr, 0, 0, 0,
(load_addr + fit_size),
(load_addr + fit_size + 0x20),
0 };
if (write(fd, &ivt_header, sizeof(flash_header_v2_t)) !=
sizeof(flash_header_v2_t)) {
fprintf(stderr, "IVT writing error on fit image\n");
exit(EXIT_FAILURE);
}
*fit_load_addr = load_addr;
return fit_offset + fit_size;
}
static void dump_header_v2(imx_header_v3_t *imx_header, int index)
{
const char *ivt_name[2] = {"HDMI FW", "LOADER IMAGE"};
fprintf(stdout, "========= IVT HEADER [%s] =========\n",
ivt_name[index]);
fprintf(stdout, "header.tag: \t\t0x%x\n",
imx_header[index].fhdr.header.tag);
fprintf(stdout, "header.length: \t\t0x%x\n",
imx_header[index].fhdr.header.length);
fprintf(stdout, "header.version: \t0x%x\n",
imx_header[index].fhdr.header.version);
fprintf(stdout, "entry: \t\t\t0x%x\n",
imx_header[index].fhdr.entry);
fprintf(stdout, "reserved1: \t\t0x%x\n",
imx_header[index].fhdr.reserved1);
fprintf(stdout, "dcd_ptr: \t\t0x%x\n",
imx_header[index].fhdr.dcd_ptr);
fprintf(stdout, "boot_data_ptr: \t\t0x%x\n",
imx_header[index].fhdr.boot_data_ptr);
fprintf(stdout, "self: \t\t\t0x%x\n",
imx_header[index].fhdr.self);
fprintf(stdout, "csf: \t\t\t0x%x\n",
imx_header[index].fhdr.csf);
fprintf(stdout, "reserved2: \t\t0x%x\n",
imx_header[index].fhdr.reserved2);
fprintf(stdout, "boot_data.start: \t0x%x\n",
imx_header[index].boot_data.start);
fprintf(stdout, "boot_data.size: \t0x%x\n",
imx_header[index].boot_data.size);
fprintf(stdout, "boot_data.plugin: \t0x%x\n",
imx_header[index].boot_data.plugin);
}
#ifdef CONFIG_FSPI_CONF_HEADER
static int generate_fspi_header (int ifd)
{
int ret, i = 0;
char *val;
char lut_str[] = CONFIG_LUT_SEQUENCE;
fspi_conf fspi_conf_data = {
.tag = {0x46, 0x43, 0x46, 0x42},
.version = {0x00, 0x00, 0x01, 0x56},
.reserved_1 = {0x00, 0x00, 0x00, 0x00},
.read_sample = CONFIG_READ_CLK_SOURCE,
.datahold = 0x03,
.datasetup = 0x03,
.coladdrwidth = 0x00,
.devcfgenable = 0x00,
.reserved_2 = {0x00, 0x00, 0x00},
.devmodeseq = {0x00, 0x00, 0x00, 0x00},
.devmodearg = {0x00, 0x00, 0x00, 0x00},
.cmd_enable = 0x00,
.reserved_3 = {0x00},
.cmd_seq = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.cmd_arg = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.controllermisc = {0x00, 0x00, 0x00, 0x00},
.dev_type = CONFIG_DEVICE_TYPE,
.sflash_pad = CONFIG_FLASH_PAD_TYPE,
.serial_clk = CONFIG_SERIAL_CLK_FREQUENCY,
.lut_custom = CONFIG_LUT_CUSTOM_SEQUENCE,
.reserved_4 = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.sflashA1 = {0x00, 0x00, 0x00, 0x10},
.sflashA2 = {0x00, 0x00, 0x00, 0x00},
.sflashB1 = {0x00, 0x00, 0x00, 0x00},
.sflashB2 = {0x00, 0x00, 0x00, 0x00},
.cspadover = {0x00, 0x00, 0x00, 0x00},
.sclkpadover = {0x00, 0x00, 0x00, 0x00},
.datapadover = {0x00, 0x00, 0x00, 0x00},
.dqspadover = {0x00, 0x00, 0x00, 0x00},
.timeout = {0x00, 0x00, 0x00, 0x00},
.commandInt = {0x00, 0x00, 0x00, 0x00},
.datavalid = {0x00, 0x00, 0x00, 0x00},
.busyoffset = {0x00, 0x00},
.busybitpolarity = {0x00, 0x00},
};
for (val = strtok(lut_str, ","); val; val = strtok(NULL, ",")) {
fspi_conf_data.lut[i++] = strtoul(val, NULL, 16);
}
ret = lseek(ifd, 0, SEEK_CUR);
if (write(ifd, &fspi_conf_data, sizeof(fspi_conf_data)) == -1)
exit(EXIT_FAILURE);
ret = lseek(ifd, sizeof(fspi_conf_data), SEEK_CUR);
return ret;
}
#endif
void build_image(int ofd)
{
int file_off, header_hdmi_off = 0, header_image_off;
#ifdef CONFIG_FSPI_CONF_HEADER
int fspi_off, fspi_fd;
char *fspi;
#endif
int hdmi_fd, ap_fd, sld_fd;
uint32_t sld_load_addr = 0;
uint32_t csf_off, sld_csf_off = 0;
int ret;
struct stat sbuf;
if (!ap_img) {
fprintf(stderr, "No LOADER image specificed\n");
exit(EXIT_FAILURE);
}
file_off = 0;
if (signed_hdmi) {
header_hdmi_off = file_off + ivt_offset;
hdmi_fd = open(signed_hdmi, O_RDONLY | O_BINARY);
if (hdmi_fd < 0) {
fprintf(stderr, "%s: Can't open: %s\n",
signed_hdmi, strerror(errno));
exit(EXIT_FAILURE);
}
if (fstat(hdmi_fd, &sbuf) < 0) {
fprintf(stderr, "%s: Can't stat: %s\n",
signed_hdmi, strerror(errno));
exit(EXIT_FAILURE);
}
close(hdmi_fd);
/*
* Aligned to 104KB = 92KB FW image + 0x8000
* (IVT and alignment) + 0x4000 (second IVT + CSF)
*/
file_off += ALIGN_IMX(sbuf.st_size,
HDMI_FW_SIZE + 0x2000 + 0x1000);
}
header_image_off = file_off + ivt_offset;
#ifdef CONFIG_FSPI_CONF_HEADER
fspi = CONFIG_FSPI_CONF_FILE;
fspi_fd = open(fspi, O_RDWR | O_CREAT, S_IRWXU);
if (fspi_fd < 0) {
fprintf(stderr, "Can't open %s: %s\n",
fspi, strerror(errno));
exit(EXIT_FAILURE);
}
fspi_off = generate_fspi_header(fspi_fd);
file_off = header_image_off + fspi_off;
close(fspi_fd);
#endif
ap_fd = open(ap_img, O_RDONLY | O_BINARY);
if (ap_fd < 0) {
fprintf(stderr, "%s: Can't open: %s\n",
ap_img, strerror(errno));
exit(EXIT_FAILURE);
}
if (fstat(ap_fd, &sbuf) < 0) {
fprintf(stderr, "%s: Can't stat: %s\n",
ap_img, strerror(errno));
exit(EXIT_FAILURE);
}
close(ap_fd);
imx_header[IMAGE_IVT_ID].fhdr.header.tag = IVT_HEADER_TAG; /* 0xD1 */
imx_header[IMAGE_IVT_ID].fhdr.header.length =
cpu_to_be16(sizeof(flash_header_v2_t));
imx_header[IMAGE_IVT_ID].fhdr.header.version = IVT_VERSION_V3; /* 0x41 */
imx_header[IMAGE_IVT_ID].fhdr.entry = ap_start_addr;
imx_header[IMAGE_IVT_ID].fhdr.self = ap_start_addr -
sizeof(imx_header_v3_t);
imx_header[IMAGE_IVT_ID].fhdr.dcd_ptr = 0;
imx_header[IMAGE_IVT_ID].fhdr.boot_data_ptr =
imx_header[IMAGE_IVT_ID].fhdr.self +
offsetof(imx_header_v3_t, boot_data);
imx_header[IMAGE_IVT_ID].boot_data.start =
imx_header[IMAGE_IVT_ID].fhdr.self - ivt_offset;
imx_header[IMAGE_IVT_ID].boot_data.size =
ALIGN_IMX(sbuf.st_size + sizeof(imx_header_v3_t) + ivt_offset,
sector_size);
image_off = header_image_off + sizeof(imx_header_v3_t);
file_off += imx_header[IMAGE_IVT_ID].boot_data.size;
imx_header[IMAGE_IVT_ID].boot_data.plugin = 0;
imx_header[IMAGE_IVT_ID].fhdr.csf =
imx_header[IMAGE_IVT_ID].boot_data.start +
imx_header[IMAGE_IVT_ID].boot_data.size;
imx_header[IMAGE_IVT_ID].boot_data.size += CSF_SIZE; /* 8K region dummy CSF */
csf_off = file_off;
file_off += CSF_SIZE;
/* Second boot loader image */
if (sld_img) {
if (!using_fit) {
fprintf(stderr, "Not support no fit\n");
exit(EXIT_FAILURE);
} else {
sld_header_off = sld_src_off - rom_image_offset;
sld_fd = open(sld_img, O_RDONLY | O_BINARY);
if (sld_fd < 0) {
fprintf(stderr, "%s: Can't open: %s\n",
sld_img, strerror(errno));
exit(EXIT_FAILURE);
}
if (fstat(sld_fd, &sbuf) < 0) {
fprintf(stderr, "%s: Can't stat: %s\n",
sld_img, strerror(errno));
exit(EXIT_FAILURE);
}
close(sld_fd);
file_off = sld_header_off;
file_off += sbuf.st_size + sizeof(image_header_t);
}
}
if (signed_hdmi) {
header_hdmi_off -= ivt_offset;
ret = lseek(ofd, header_hdmi_off, SEEK_SET);
if (ret < 0) {
fprintf(stderr, "lseek ofd fail for hdmi\n");
exit(EXIT_FAILURE);
}
/* The signed HDMI FW has 0x400 IVT offset, need remove it */
copy_file(ofd, signed_hdmi, 0, header_hdmi_off, 0x400);
}
/* Main Image */
header_image_off -= ivt_offset;
image_off -= ivt_offset;
ret = lseek(ofd, header_image_off, SEEK_SET);
if (ret < 0) {
fprintf(stderr, "lseek ofd fail\n");
exit(EXIT_FAILURE);
}
/* Write image header */
if (write(ofd, &imx_header[IMAGE_IVT_ID], sizeof(imx_header_v3_t)) !=
sizeof(imx_header_v3_t)) {
fprintf(stderr, "error writing image hdr\n");
exit(1);
}
copy_file(ofd, ap_img, 0, image_off, 0);
csf_off -= ivt_offset;
fill_zero(ofd, CSF_SIZE, csf_off);
if (sld_img) {
sld_header_off -= ivt_offset;
ret = lseek(ofd, sld_header_off, SEEK_SET);
if (ret < 0) {
fprintf(stderr, "lseek ofd fail for sld_img\n");
exit(EXIT_FAILURE);
}
/* Write image header */
if (!using_fit) {
/* TODO */
} else {
copy_file(ofd, sld_img, 0, sld_header_off, 0);
sld_csf_off =
generate_ivt_for_fit(ofd, sld_header_off,
sld_start_addr,
&sld_load_addr) + 0x20;
}
}
if (!signed_hdmi)
dump_header_v2(imx_header, 0);
dump_header_v2(imx_header, 1);
fprintf(stdout, "========= OFFSET dump =========");
if (signed_hdmi) {
fprintf(stdout, "\nSIGNED HDMI FW:\n");
fprintf(stdout, " header_hdmi_off \t0x%x\n",
header_hdmi_off);
}
fprintf(stdout, "\nLoader IMAGE:\n");
fprintf(stdout, " header_image_off \t0x%x\n image_off \t\t0x%x\n csf_off \t\t0x%x\n",
header_image_off, image_off, csf_off);
fprintf(stdout, " spl hab block: \t0x%x 0x%x 0x%x\n",
imx_header[IMAGE_IVT_ID].fhdr.self, header_image_off,
csf_off - header_image_off);
fprintf(stdout, "\nSecond Loader IMAGE:\n");
fprintf(stdout, " sld_header_off \t0x%x\n",
sld_header_off);
fprintf(stdout, " sld_csf_off \t\t0x%x\n",
sld_csf_off);
fprintf(stdout, " sld hab block: \t0x%x 0x%x 0x%x\n",
sld_load_addr, sld_header_off, sld_csf_off - sld_header_off);
}
int imx8mimage_copy_image(int outfd, struct image_tool_params *mparams)
{
/*
* SECO FW is a container image, this is to calculate the
* 2nd container offset.
*/
fprintf(stdout, "parsing %s\n", mparams->imagename);
parse_cfg_file(mparams->imagename);
build_image(outfd);
return 0;
}
/*
* imx8mimage parameters
*/
U_BOOT_IMAGE_TYPE(
imx8mimage,
"NXP i.MX8M Boot Image support",
0,
NULL,
imx8mimage_check_params,
NULL,
imx8mimage_print_header,
imx8mimage_set_header,
NULL,
imx8mimage_check_image_types,
NULL,
NULL
);