9
0
Fork 0
barebox/arch/arm/mach-imx/imx-bbu-external-nand.c

211 lines
4.7 KiB
C

/*
* imx-bbu-external-nand.c - i.MX specific update functions for external
* nand boot
*
* Copyright (c) 2013 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.
*/
#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-nand.h>
#include <asm/barebox-arm-head.h>
static int imx_bbu_external_nand_update(struct bbu_handler *handler, struct bbu_data *data)
{
struct mtd_info_user meminfo;
int fd;
struct stat s;
int size_available, size_need;
int ret;
uint32_t num_bb = 0, bbt = 0;
uint64_t offset = 0;
int block = 0, len, now, blocksize;
void *image = data->image;
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;
size_need = data->len;
/*
* Collect bad blocks and construct BBT
*/
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++;
bbt |= (1 << block);
offset += blocksize;
block++;
continue;
}
size_need -= blocksize;
size_available -= blocksize;
offset += blocksize;
block++;
if (size_available < 0) {
printf("device is too small.\n"
"raw partition size: 0x%08llx\n"
"partition size w/o bad blocks: 0x%08llx\n"
"size needed: 0x%08x\n",
s.st_size,
s.st_size - num_bb * blocksize,
data->len);
ret = -ENOSPC;
goto out;
}
}
debug("total image size: 0x%08x. Space needed including bad blocks: 0x%08x\n",
data->len, data->len + num_bb * blocksize);
if (meminfo.writesize >= 2048) {
uint32_t *image_bbt = image + ARM_HEAD_SPARE_OFS;
debug(">= 2k nand detected. Creating in-image bbt\n");
if (*image_bbt != ARM_HEAD_SPARE_MARKER) {
if (!bbu_force(data, "Cannot find space for the BBT")) {
ret = -EINVAL;
goto out;
}
} else {
*image_bbt++ = IMX_NAND_BBT_MAGIC;
*image_bbt++ = bbt;
}
}
if (data->len + num_bb * blocksize > s.st_size) {
printf("needed space (0x%08x) exceeds partition space (0x%08llx)\n",
data->len + num_bb * blocksize, s.st_size);
ret = -ENOSPC;
goto out;
}
len = data->len;
offset = 0;
/* last chance before erasing the flash */
ret = bbu_confirm(data);
if (ret)
return ret;
/*
* 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;
}
/*
* Register a i.MX external nand boot update handler.
*
* For 512b page NANDs this handler simply writes the image to NAND skipping
* bad blocks.
*
* For 2K page NANDs this handler embeds a bad block table in the flashed image.
* This is necessary since we rely on an on-flash BBT for these flashes, but the
* regular mtd BBT is too complex to be handled in the 2k the i.MX is able to
* initially load from NAND. The BBT consists of a single 32bit value in which
* each bit represents a single block. With 2k NAND flashes this is enough for
* 4MiB size including bad blocks.
*/
int imx_bbu_external_nand_register_handler(const char *name, char *devicefile,
unsigned long flags)
{
struct bbu_handler *handler;
int ret;
handler = xzalloc(sizeof(*handler));
handler->devicefile = devicefile;
handler->name = name;
handler->flags = flags;
handler->handler = imx_bbu_external_nand_update;
ret = bbu_register_handler(handler);
if (ret)
free(handler);
return ret;
}