| /* |
| * Copyright 2014 Broadcom Corporation. |
| * Copyright 2015 Free Electrons. |
| * |
| * SPDX-License-Identifier: GPL-2.0+ |
| */ |
| |
| #include <config.h> |
| #include <common.h> |
| |
| #include <fastboot.h> |
| #include <image-sparse.h> |
| #include <sparse_format.h> |
| |
| #include <linux/mtd/mtd.h> |
| #include <jffs2/jffs2.h> |
| #include <nand.h> |
| |
| static char *response_str; |
| |
| struct fb_nand_sparse { |
| struct mtd_info *nand; |
| struct part_info *part; |
| }; |
| |
| __weak int board_fastboot_erase_partition_setup(char *name) |
| { |
| return 0; |
| } |
| |
| __weak int board_fastboot_write_partition_setup(char *name) |
| { |
| return 0; |
| } |
| |
| static int fb_nand_lookup(const char *partname, char *response, |
| struct mtd_info **nand, |
| struct part_info **part) |
| { |
| struct mtd_device *dev; |
| int ret; |
| u8 pnum; |
| |
| ret = mtdparts_init(); |
| if (ret) { |
| error("Cannot initialize MTD partitions\n"); |
| fastboot_fail(response_str, "cannot init mtdparts"); |
| return ret; |
| } |
| |
| ret = find_dev_and_part(partname, &dev, &pnum, part); |
| if (ret) { |
| error("cannot find partition: '%s'", partname); |
| fastboot_fail(response_str, "cannot find partition"); |
| return ret; |
| } |
| |
| if (dev->id->type != MTD_DEV_TYPE_NAND) { |
| error("partition '%s' is not stored on a NAND device", |
| partname); |
| fastboot_fail(response_str, "not a NAND device"); |
| return -EINVAL; |
| } |
| |
| *mtd = nand_info[dev->id->num]; |
| |
| return 0; |
| } |
| |
| static int _fb_nand_erase(struct mtd_info *mtd, struct part_info *part) |
| { |
| nand_erase_options_t opts; |
| int ret; |
| |
| memset(&opts, 0, sizeof(opts)); |
| opts.offset = part->offset; |
| opts.length = part->size; |
| opts.quiet = 1; |
| |
| printf("Erasing blocks 0x%llx to 0x%llx\n", |
| part->offset, part->offset + part->size); |
| |
| ret = nand_erase_opts(mtd, &opts); |
| if (ret) |
| return ret; |
| |
| printf("........ erased 0x%llx bytes from '%s'\n", |
| part->size, part->name); |
| |
| return 0; |
| } |
| |
| static int _fb_nand_write(struct mtd_info *mtd, struct part_info *part, |
| void *buffer, unsigned int offset, |
| unsigned int length, size_t *written) |
| { |
| int flags = WITH_WR_VERIFY; |
| |
| #ifdef CONFIG_FASTBOOT_FLASH_NAND_TRIMFFS |
| flags |= WITH_DROP_FFS; |
| #endif |
| |
| return nand_write_skip_bad(mtd, offset, &length, written, |
| part->size - (offset - part->offset), |
| buffer, flags); |
| } |
| |
| static int fb_nand_sparse_write(struct sparse_storage *storage, |
| void *priv, |
| unsigned int offset, |
| unsigned int size, |
| char *data) |
| { |
| struct fb_nand_sparse *sparse = priv; |
| size_t written; |
| int ret; |
| |
| ret = _fb_nand_write(sparse->nand, sparse->part, data, |
| offset * storage->block_sz, |
| size * storage->block_sz, &written); |
| if (ret < 0) { |
| printf("Failed to write sparse chunk\n"); |
| return ret; |
| } |
| |
| return written / storage->block_sz; |
| } |
| |
| void fb_nand_flash_write(const char *partname, unsigned int session_id, |
| void *download_buffer, unsigned int download_bytes, |
| char *response) |
| { |
| struct part_info *part; |
| struct mtd_info *mtd = NULL; |
| int ret; |
| |
| /* initialize the response buffer */ |
| response_str = response; |
| |
| ret = fb_nand_lookup(partname, response, &mtd, &part); |
| if (ret) { |
| error("invalid NAND device"); |
| fastboot_fail(response_str, "invalid NAND device"); |
| return; |
| } |
| |
| ret = board_fastboot_write_partition_setup(part->name); |
| if (ret) |
| return; |
| |
| if (is_sparse_image(download_buffer)) { |
| struct fb_nand_sparse sparse_priv; |
| sparse_storage_t sparse; |
| |
| sparse_priv.nand = mtd; |
| sparse_priv.part = part; |
| |
| sparse.block_sz = mtd->writesize; |
| sparse.start = part->offset / sparse.block_sz; |
| sparse.size = part->size / sparse.block_sz; |
| sparse.name = part->name; |
| sparse.write = fb_nand_sparse_write; |
| |
| ret = store_sparse_image(&sparse, &sparse_priv, session_id, |
| download_buffer); |
| } else { |
| printf("Flashing raw image at offset 0x%llx\n", |
| part->offset); |
| |
| ret = _fb_nand_write(mtd, part, download_buffer, part->offset, |
| download_bytes, NULL); |
| |
| printf("........ wrote %u bytes to '%s'\n", |
| download_bytes, part->name); |
| } |
| |
| if (ret) { |
| fastboot_fail(response_str, "error writing the image"); |
| return; |
| } |
| |
| fastboot_okay(response_str, ""); |
| } |
| |
| void fb_nand_erase(const char *partname, char *response) |
| { |
| struct part_info *part; |
| struct mtd_info *mtd = NULL; |
| int ret; |
| |
| /* initialize the response buffer */ |
| response_str = response; |
| |
| ret = fb_nand_lookup(partname, response, &mtd, &part); |
| if (ret) { |
| error("invalid NAND device"); |
| fastboot_fail(response_str, "invalid NAND device"); |
| return; |
| } |
| |
| ret = board_fastboot_erase_partition_setup(part->name); |
| if (ret) |
| return; |
| |
| ret = _fb_nand_erase(mtd, part); |
| if (ret) { |
| error("failed erasing from device %s", mtd->name); |
| fastboot_fail(response_str, "failed erasing from device"); |
| return; |
| } |
| |
| fastboot_okay(response_str, ""); |
| } |