[PATCH 2/5] ARM i.MX: Add barebox update handler for internal boot

Sascha Hauer s.hauer at pengutronix.de
Mon Oct 15 09:15:05 EDT 2012


This adds support for an update handler for internal boot. Currently
handled are:

- v1 MMC/SD
- v2 MMC/SD
- v2 NAND

where v1 is found on i.MX25, i.MX35 and i.MX51. v2 is found on i.MX53.

This code intentionally does not use the DCD data compiled into every
i.MX internal boot image. This makes it possible to make a pure second
stage barebox bootable on i.MX internal boot devices later.

This has been tested on the i.MX51 babbage, i.MX53 loco and i.MX53 tx53
board.

Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
 arch/arm/mach-imx/Makefile                        |    1 +
 arch/arm/mach-imx/imx-bbu-internal.c              |  543 +++++++++++++++++++++
 arch/arm/mach-imx/include/mach/bbu.h              |   51 ++
 arch/arm/mach-imx/include/mach/imx-flash-header.h |    2 +
 4 files changed, 597 insertions(+)
 create mode 100644 arch/arm/mach-imx/imx-bbu-internal.c
 create mode 100644 arch/arm/mach-imx/include/mach/bbu.h

diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
index e5ef78a..259733e 100644
--- a/arch/arm/mach-imx/Makefile
+++ b/arch/arm/mach-imx/Makefile
@@ -16,3 +16,4 @@ pbl-$(CONFIG_ARCH_IMX_EXTERNAL_BOOT_NAND) += external-nand-boot.o
 obj-$(CONFIG_COMMON_CLK) += clk-pllv1.o clk-pllv2.o clk-pllv3.o clk-pfd.o
 obj-y += devices.o imx.o
 obj-y += boot.o
+obj-$(CONFIG_BAREBOX_UPDATE) += imx-bbu-internal.o
diff --git a/arch/arm/mach-imx/imx-bbu-internal.c b/arch/arm/mach-imx/imx-bbu-internal.c
new file mode 100644
index 0000000..85d10cf
--- /dev/null
+++ b/arch/arm/mach-imx/imx-bbu-internal.c
@@ -0,0 +1,543 @@
+/*
+ * imx-bbu-internal.c - i.MX specific update functions for internal boot
+ *
+ * Copyright (c) 2012 Sascha Hauer <s.hauer at pengutronix.de>, Pengutronix
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define IMX_INTERNAL_NAND_BBU
+
+#include <common.h>
+#include <malloc.h>
+#include <bbu.h>
+#include <filetype.h>
+#include <errno.h>
+#include <fs.h>
+#include <fcntl.h>
+#include <sizes.h>
+#include <linux/mtd/mtd-abi.h>
+#include <linux/stat.h>
+#include <ioctl.h>
+#include <mach/bbu.h>
+#include <mach/imx-flash-header.h>
+
+#define FLASH_HEADER_OFFSET_MMC		0x400
+
+#define IMX_INTERNAL_FLAG_NAND		(1 << 0)
+#define IMX_INTERNAL_FLAG_KEEP_DOSPART	(1 << 1)
+
+struct imx_internal_bbu_handler {
+	struct bbu_handler handler;
+	const void *dcd;
+	int dcdsize;
+	unsigned long app_dest;
+	unsigned long flash_header_offset;
+	size_t device_size;
+	unsigned long flags;
+};
+
+/*
+ * Actually write an image to the target device, eventually keeping a
+ * DOS partition table on the device
+ */
+static int imx_bbu_write_device(struct imx_internal_bbu_handler *imx_handler,
+		struct bbu_data *data, void *buf, int image_len)
+{
+	int fd, ret;
+
+	fd = open(data->devicefile, O_RDWR | O_CREAT);
+	if (fd < 0)
+		return fd;
+
+	if (imx_handler->flags & IMX_INTERNAL_FLAG_KEEP_DOSPART) {
+		void *mbr = xzalloc(512);
+
+		debug("%s: reading DOS partition table in order to keep it\n");
+
+		ret = read(fd, mbr, 512);
+		if (ret < 0) {
+			free(mbr);
+			goto err_close;
+		}
+
+		memcpy(buf + 0x1b8, mbr + 0x1b8, 0x48);
+		free(buf);
+
+		ret = lseek(fd, 0, SEEK_SET);
+		if (ret)
+			goto err_close;
+	}
+
+	ret = write(fd, buf, image_len);
+	if (ret < 0)
+		goto err_close;
+
+	ret = 0;
+
+err_close:
+	close(fd);
+
+	return ret;
+}
+
+/*
+ * Update barebox on a v1 type internal boot (i.MX25, i.MX35, i.MX51)
+ *
+ * This constructs a DCD header, adds the specific DCD data and writes
+ * the resulting image to the device. Currently this handles MMC/SD
+ * devices.
+ */
+static int imx_bbu_internal_v1_update(struct bbu_handler *handler, struct bbu_data *data)
+{
+	struct imx_internal_bbu_handler *imx_handler =
+		container_of(handler, struct imx_internal_bbu_handler, handler);
+	struct imx_flash_header *flash_header;
+	unsigned long flash_header_offset = imx_handler->flash_header_offset;
+	u32 *dcd_image_size;
+	void *imx_pre_image;
+	int imx_pre_image_size = 0x2000;
+	int ret, image_len;
+	void *buf;
+
+	if (file_detect_type(data->image) != filetype_arm_barebox) {
+		if (!bbu_force(data, "Not an ARM barebox image"))
+			return -EINVAL;
+	}
+
+	ret = bbu_confirm(data);
+	if (ret)
+		return ret;
+
+	printf("updating to %s\n", data->devicefile);
+
+	imx_pre_image = xzalloc(imx_pre_image_size);
+	flash_header = imx_pre_image + flash_header_offset;
+
+	flash_header->app_code_jump_vector = imx_handler->app_dest + 0x1000;
+	flash_header->app_code_barker = APP_CODE_BARKER;
+	flash_header->app_code_csf = 0;
+	flash_header->dcd_ptr_ptr = imx_handler->app_dest + flash_header_offset +
+		offsetof(struct imx_flash_header, dcd);
+	flash_header->super_root_key = 0;
+	flash_header->dcd = imx_handler->app_dest + flash_header_offset +
+		offsetof(struct imx_flash_header, dcd_barker);
+	flash_header->app_dest = imx_handler->app_dest;
+	flash_header->dcd_barker = DCD_BARKER;
+	flash_header->dcd_block_len = imx_handler->dcdsize;
+
+	memcpy((void *)flash_header + sizeof(*flash_header), imx_handler->dcd, imx_handler->dcdsize);
+
+	dcd_image_size = (imx_pre_image + flash_header_offset + sizeof(*flash_header) + imx_handler->dcdsize);
+
+	*dcd_image_size = ALIGN(imx_pre_image_size + data->len, 4096);
+
+	/* Create a buffer containing header and image data */
+	image_len = data->len + imx_pre_image_size;
+	buf = xzalloc(image_len);
+	memcpy(buf, imx_pre_image, imx_pre_image_size);
+	memcpy(buf + imx_pre_image_size, data->image, data->len);
+
+	ret = imx_bbu_write_device(imx_handler, data, buf, image_len);
+
+	free(buf);
+
+	free(imx_pre_image);
+
+	return ret;
+}
+
+#define DBBT_MAGIC	0x44424254
+#define FCB_MAGIC	0x20424346
+
+/*
+ * Write an image to NAND. This creates a FCB header and a DBBT (Discovered Bad
+ * Block Table). The DBBT is initialized with the bad blocks known from the mtd
+ * layer.
+ */
+static int imx_bbu_internal_v2_write_nand_dbbt(struct imx_internal_bbu_handler *imx_handler,
+		struct bbu_data *data, void *image, int image_len)
+{
+	struct mtd_info_user meminfo;
+	int fd;
+	struct stat s;
+	int size_available, size_need;
+	int ret;
+	uint32_t *ptr, *num_bb, *bb;
+	uint64_t offset;
+	int block = 0, len, now, blocksize;
+
+	ret = stat(data->devicefile, &s);
+	if (ret)
+		return ret;
+
+	size_available = s.st_size;
+
+	fd = open(data->devicefile, O_RDWR);
+	if (fd < 0)
+		return fd;
+
+	ret = ioctl(fd, MEMGETINFO, &meminfo);
+	if (ret)
+		goto out;
+
+	blocksize = meminfo.erasesize;
+
+	ptr = image + 0x4;
+	*ptr++ = FCB_MAGIC;	/* FCB */
+	*ptr++ = 1;		/* FCB version */
+
+	ptr = image + 0x78; /* DBBT start page */
+	*ptr = 4;
+
+	ptr = image + 4 * 2048 + 4;
+	*ptr++ = DBBT_MAGIC;	/* DBBT */
+	*ptr = 1;		/* DBBT version */
+
+	ptr = (u32*)(image + 0x2010);
+	/*
+	 * This is marked as reserved in the i.MX53 reference manual, but
+	 * must be != 0. Otherwise the ROM ignores the DBBT
+	 */
+	*ptr = 1;
+
+	ptr = (u32*)(image + 0x4004); /* start of DBBT */
+	num_bb = ptr;
+	bb = ptr + 1;
+	offset = 0;
+
+	size_need = data->len + 0x8000;
+
+	/*
+	 * Collect bad blocks and construct DBBT
+	 */
+	while (size_need > 0) {
+		ret = ioctl(fd, MEMGETBADBLOCK, &offset);
+		if (ret < 0)
+			goto out;
+
+		if (ret) {
+			if (!offset) {
+				printf("1st block is bad. This is not supported\n");
+				ret = -EINVAL;
+				goto out;
+			}
+
+			debug("bad block at 0x%08llx\n", offset);
+			*num_bb += 1;
+			if (*num_bb == 425) {
+				/* Maximum number of bad blocks the ROM supports */
+				printf("maximum number of bad blocks reached\n");
+				ret = -ENOSPC;
+				goto out;
+			}
+			*bb++ = block;
+			offset += blocksize;
+			block++;
+			continue;
+		}
+		size_need -= blocksize;
+		size_available -= blocksize;
+		offset += blocksize;
+		block++;
+
+		if (size_available < 0) {
+			printf("device is too small");
+			ret = -ENOSPC;
+			goto out;
+		}
+	}
+
+	debug("total image size: 0x%08x. Space needed including bad blocks: 0x%08x\n",
+			data->len + 0x8000,
+			data->len + 0x8000 + *num_bb * blocksize);
+
+	if (data->len + 0x8000 + *num_bb * blocksize > imx_handler->device_size) {
+		printf("needed space (0x%08x) exceeds partition space (0x%08x)\n",
+				data->len + 0x8000 + *num_bb * blocksize,
+				imx_handler->device_size);
+		ret = -ENOSPC;
+		goto out;
+	}
+
+	len = data->len + 0x8000;
+	offset = 0;
+
+	/*
+	 * Write image to NAND skipping bad blocks
+	 */
+	while (len > 0) {
+		now = min(len, blocksize);
+
+		ret = ioctl(fd, MEMGETBADBLOCK, &offset);
+		if (ret < 0)
+			goto out;
+
+		if (ret) {
+			ret = lseek(fd, offset + blocksize, SEEK_SET);
+			if (ret < 0)
+				goto out;
+			offset += blocksize;
+			continue;
+		}
+
+		debug("writing %d bytes at 0x%08llx\n", now, offset);
+
+		ret = erase(fd, blocksize, offset);
+		if (ret)
+			goto out;
+
+		ret = write(fd, image, now);
+		if (ret < 0)
+			goto out;
+
+		len -= now;
+		image += now;
+		offset += now;
+	}
+
+	ret = 0;
+
+out:
+	close(fd);
+
+	return ret;
+}
+
+/*
+ * Update barebox on a v2 type internal boot (i.MX53)
+ *
+ * This constructs a DCD header, adds the specific DCD data and writes
+ * the resulting image to the device. Currently this handles MMC/SD
+ * and NAND devices.
+ */
+static int imx_bbu_internal_v2_update(struct bbu_handler *handler, struct bbu_data *data)
+{
+	struct imx_internal_bbu_handler *imx_handler =
+		container_of(handler, struct imx_internal_bbu_handler, handler);
+	struct imx_flash_header_v2 *flash_header;
+	unsigned long flash_header_offset = imx_handler->flash_header_offset;
+	void *imx_pre_image;
+	int imx_pre_image_size;
+	int ret, image_len;
+	void *buf;
+
+	if (file_detect_type(data->image) != filetype_arm_barebox) {
+		if (!bbu_force(data, "Not an ARM barebox image"))
+			return -EINVAL;
+	}
+
+	ret = bbu_confirm(data);
+	if (ret)
+		return ret;
+
+	printf("updating to %s\n", data->devicefile);
+
+	if (imx_handler->flags & IMX_INTERNAL_FLAG_NAND)
+		/* NAND needs additional space for the DBBT */
+		imx_pre_image_size = 0x8000;
+	else
+		imx_pre_image_size = 0x2000;
+
+	imx_pre_image = xzalloc(imx_pre_image_size);
+	flash_header = imx_pre_image + flash_header_offset;
+
+	flash_header->header.tag = IVT_HEADER_TAG;
+	flash_header->header.length = cpu_to_be16(32);
+	flash_header->header.version = IVT_VERSION;
+
+	flash_header->entry = imx_handler->app_dest + imx_pre_image_size;
+	flash_header->dcd_ptr = imx_handler->app_dest + flash_header_offset +
+		offsetof(struct imx_flash_header_v2, dcd);
+	flash_header->boot_data_ptr = imx_handler->app_dest +
+		flash_header_offset + offsetof(struct imx_flash_header_v2, boot_data);
+	flash_header->self = imx_handler->app_dest + flash_header_offset;
+
+	flash_header->boot_data.start = imx_handler->app_dest;
+	flash_header->boot_data.size = ALIGN(imx_pre_image_size + data->len, 4096);;
+
+	flash_header->dcd.header.tag = DCD_HEADER_TAG;
+	flash_header->dcd.header.length = cpu_to_be16(sizeof(struct imx_dcd) +
+			imx_handler->dcdsize);
+	flash_header->dcd.header.version = DCD_VERSION;
+
+	/* Add dcd data */
+	memcpy((void *)flash_header + sizeof(*flash_header), imx_handler->dcd, imx_handler->dcdsize);
+
+	/* Create a buffer containing header and image data */
+	image_len = data->len + imx_pre_image_size;
+	buf = xzalloc(image_len);
+	memcpy(buf, imx_pre_image, imx_pre_image_size);
+	memcpy(buf + imx_pre_image_size, data->image, data->len);
+
+	if (imx_handler->flags & IMX_INTERNAL_FLAG_NAND) {
+		ret = imx_bbu_internal_v2_write_nand_dbbt(imx_handler, data, buf,
+				image_len);
+		goto out_free_buf;
+	}
+
+	ret = imx_bbu_write_device(imx_handler, data, buf, image_len);
+
+out_free_buf:
+	free(buf);
+
+	free(imx_pre_image);
+	return ret;
+}
+
+/*
+ * On the i.MX53 the dcd data can contain several commands. Each of them must
+ * have its length encoded into it. We can't express that during compile time,
+ * so use this function if you are using multiple dcd commands and wish to
+ * concatenate them together to a single dcd table with the correct sizes for
+ * each command.
+ */
+void *imx53_bbu_internal_concat_dcd_table(struct dcd_table *table, int num_entries)
+{
+	int i;
+	unsigned int dcdsize = 0, pos = 0;
+	void *dcdptr;
+
+	for (i = 0; i < num_entries; i++)
+		dcdsize += table[i].size;
+
+	dcdptr = xmalloc(dcdsize);
+
+	for (i = 0; i < num_entries; i++) {
+		u32 *current = dcdptr + pos;
+		memcpy(current, table[i].data, table[i].size);
+		*current |= cpu_to_be32(table[i].size << 8);
+		pos += table[i].size;
+	}
+
+	return dcdptr;
+}
+
+static struct imx_internal_bbu_handler *__init_handler(const char *name, char *devicefile,
+		unsigned long flags)
+{
+	struct imx_internal_bbu_handler *imx_handler;
+	struct bbu_handler *handler;
+
+	imx_handler = xzalloc(sizeof(*imx_handler));
+	handler = &imx_handler->handler;
+	handler->devicefile = devicefile;
+	handler->name = name;
+	handler->flags = flags;
+
+	return imx_handler;
+}
+
+static int __register_handler(struct imx_internal_bbu_handler *imx_handler)
+{
+	int ret;
+
+	ret = bbu_register_handler(&imx_handler->handler);
+	if (ret)
+		free(imx_handler);
+
+	return ret;
+}
+
+/*
+ * Register a i.MX51 internal boot update handler for MMC/SD
+ */
+int imx51_bbu_internal_mmc_register_handler(const char *name, char *devicefile,
+		unsigned long flags, struct imx_dcd_entry *dcd, int dcdsize)
+{
+	struct imx_internal_bbu_handler *imx_handler;
+
+	imx_handler = __init_handler(name, devicefile, flags);
+	imx_handler->dcd = dcd;
+	imx_handler->dcdsize = dcdsize;
+	imx_handler->flash_header_offset = FLASH_HEADER_OFFSET_MMC;
+	imx_handler->app_dest = 0x90000000;
+	imx_handler->flags = IMX_INTERNAL_FLAG_KEEP_DOSPART;
+	imx_handler->handler.handler = imx_bbu_internal_v1_update;
+
+	return __register_handler(imx_handler);
+}
+
+#define DCD_WR_CMD(len)		cpu_to_be32(0xcc << 24 | (((len) & 0xffff) << 8) | 0x04)
+
+static int imx53_bbu_internal_init_dcd(struct imx_internal_bbu_handler *imx_handler,
+		void *dcd, int dcdsize)
+{
+	uint32_t *dcd32 = dcd;
+
+	/*
+	 * The DCD data we have compiled in does not have a DCD_WR_CMD at
+	 * the beginning. Instead it is contained in struct imx_flash_header_v2.
+	 * This is necessary to generate the DCD size at compile time. If
+	 * we are passed such a DCD data here, prepend a DCD_WR_CMD.
+	 */
+	if ((*dcd32 & 0xff0000ff) != DCD_WR_CMD(0)) {
+		__be32 *buf;
+
+		debug("%s: dcd does not have a DCD_WR_CMD. Prepending one\n");
+
+		buf = xmalloc(dcdsize + sizeof(__be32));
+
+		*buf = DCD_WR_CMD(dcdsize + sizeof(__be32));
+		memcpy(&buf[1], dcd, dcdsize);
+
+		imx_handler->dcd = buf;
+		imx_handler->dcdsize = dcdsize + sizeof(__be32);
+	} else {
+		debug("%s: dcd already has a DCD_WR_CMD. Using original dcd data\n");
+
+		imx_handler->dcd = dcd;
+		imx_handler->dcdsize = dcdsize;
+	}
+
+	return 0;
+}
+
+/*
+ * Register a i.MX53 internal boot update handler for MMC/SD
+ */
+int imx53_bbu_internal_mmc_register_handler(const char *name, char *devicefile,
+		unsigned long flags, struct imx_dcd_v2_entry *dcd, int dcdsize)
+{
+	struct imx_internal_bbu_handler *imx_handler;
+
+	imx_handler = __init_handler(name, devicefile, flags);
+	imx53_bbu_internal_init_dcd(imx_handler, dcd, dcdsize);
+	imx_handler->flash_header_offset = FLASH_HEADER_OFFSET_MMC;
+	imx_handler->app_dest = 0x70000000;
+	imx_handler->flags = IMX_INTERNAL_FLAG_KEEP_DOSPART;
+	imx_handler->handler.handler = imx_bbu_internal_v2_update;
+
+	return __register_handler(imx_handler);
+}
+
+/*
+ * Register a i.MX53 internal boot update handler for NAND
+ */
+int imx53_bbu_internal_nand_register_handler(const char *name,
+		unsigned long flags, struct imx_dcd_v2_entry *dcd, int dcdsize,
+		int partition_size)
+{
+	struct imx_internal_bbu_handler *imx_handler;
+
+	imx_handler = __init_handler(name, NULL, flags);
+	imx53_bbu_internal_init_dcd(imx_handler, dcd, dcdsize);
+	imx_handler->flash_header_offset = 0x400;
+	imx_handler->app_dest = 0x70000000;
+	imx_handler->handler.handler = imx_bbu_internal_v2_update;
+	imx_handler->flags = IMX_INTERNAL_FLAG_NAND;
+	imx_handler->handler.devicefile = "/dev/nand0";
+	imx_handler->device_size = partition_size;
+
+	return __register_handler(imx_handler);
+}
diff --git a/arch/arm/mach-imx/include/mach/bbu.h b/arch/arm/mach-imx/include/mach/bbu.h
new file mode 100644
index 0000000..f9ec1cc
--- /dev/null
+++ b/arch/arm/mach-imx/include/mach/bbu.h
@@ -0,0 +1,51 @@
+#ifndef __MACH_BBU_H
+#define __MACH_BBU_H
+
+#include <bbu.h>
+
+struct imx_dcd_entry;
+struct imx_dcd_v2_entry;
+
+#ifdef CONFIG_BAREBOX_UPDATE
+
+int imx51_bbu_internal_mmc_register_handler(const char *name, char *devicefile,
+		unsigned long flags, struct imx_dcd_entry *, int dcdsize);
+
+int imx53_bbu_internal_mmc_register_handler(const char *name, char *devicefile,
+		unsigned long flags, struct imx_dcd_v2_entry *, int dcdsize);
+
+int imx53_bbu_internal_nand_register_handler(const char *name,
+		unsigned long flags, struct imx_dcd_v2_entry *, int dcdsize,
+		int partition_size);
+
+#else
+
+static inline int imx51_bbu_internal_mmc_register_handler(const char *name, char *devicefile,
+		unsigned long flags, struct imx_dcd_entry *dcd, int dcdsize)
+{
+	return -ENOSYS;
+}
+
+static inline int imx53_bbu_internal_mmc_register_handler(const char *name, char *devicefile,
+		unsigned long flags, struct imx_dcd_v2_entry *dcd, int dcdsize)
+{
+	return -ENOSYS;
+}
+
+static inline int imx53_bbu_internal_nand_register_handler(const char *name,
+		unsigned long flags, struct imx_dcd_v2_entry *dcd, int dcdsize,
+		int partition_size)
+{
+	return -ENOSYS;
+}
+
+#endif
+
+struct dcd_table {
+	void *data;
+	unsigned int size;
+};
+
+void *imx53_bbu_internal_concat_dcd_table(struct dcd_table *table, int num_entries);
+
+#endif
diff --git a/arch/arm/mach-imx/include/mach/imx-flash-header.h b/arch/arm/mach-imx/include/mach/imx-flash-header.h
index 7d048df..9a351ad 100644
--- a/arch/arm/mach-imx/include/mach/imx-flash-header.h
+++ b/arch/arm/mach-imx/include/mach/imx-flash-header.h
@@ -120,7 +120,9 @@ struct imx_dcd_command {
 
 struct imx_dcd {
 	struct imx_ivt_header header;
+#ifndef IMX_INTERNAL_NAND_BBU
 	struct imx_dcd_command command;
+#endif
 };
 
 struct imx_boot_data {
-- 
1.7.10.4




More information about the barebox mailing list