diff options
Diffstat (limited to 'arch/arm/mach-imx/imx-bbu-internal.c')
-rw-r--r-- | arch/arm/mach-imx/imx-bbu-internal.c | 543 |
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); +} |