summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-imx/imx-bbu-internal.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-imx/imx-bbu-internal.c')
-rw-r--r--arch/arm/mach-imx/imx-bbu-internal.c543
1 files changed, 543 insertions, 0 deletions
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 0000000000..85d10cf4e7
--- /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@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);
+}