예제 #1
0
static void lookup_partition(const char *name)
{
    int rc;

    if (flash_side == 1) {
        uint32_t other_side_offset;

        rc = open_partition("OTHER_SIDE");
        if (rc == FFS_ERR_PART_NOT_FOUND)
            fprintf(stderr, "side 1 was specified but there doesn't appear"
                    " to be a second side to this flash\n");
        if (rc)
            exit(1);

        /* Just need to know where it starts */
        rc = ffs_part_info(ffsh, ffs_index, NULL, &other_side_offset, NULL, NULL, NULL);
        if (rc)
            exit(1);

        ffs_close(ffsh);
        ffsh = NULL;

        ffs_toc = other_side_offset;
    }

    rc = open_partition(name);
    if (rc)
        exit(1);
}
예제 #2
0
static void print_ffs_info(uint32_t toc_offset)
{
    struct ffs_handle *ffs_handle;
    uint32_t other_side_offset = 0;
    int rc;
    uint32_t i;

    rc = ffs_init(toc_offset, fl_total_size, bl, &ffs_handle, 0);
    if (rc) {
        fprintf(stderr, "Error %d opening ffs !\n", rc);
        return;
    }

    printf("\n");
    printf("TOC@0x%08x Partitions:\n", toc_offset);
    printf("-----------\n");

    for (i = 0;; i++) {
        uint32_t start, size, act, end;
        bool ecc;
        char *name;

        rc = ffs_part_info(ffs_handle, i, &name, &start, &size, &act, &ecc);
        if (rc == FFS_ERR_PART_NOT_FOUND)
            break;
        if (rc) {
            fprintf(stderr, "Error %d scanning partitions\n", rc);
            break;
        }
        end = start + size;
        printf("ID=%02d %15s %08x..%08x (actual=%08x) %s\n",
               i, name, start, end, act, ecc ? "[ECC]" : "");

        if (strcmp(name, "OTHER_SIDE") == 0)
            other_side_offset = start;

        free(name);
    }

    ffs_close(ffs_handle);

    if (other_side_offset)
        print_ffs_info(other_side_offset);
}
예제 #3
0
int ffs_next_side(struct ffs_handle *ffs, struct ffs_handle **new_ffs,
		bool mark_ecc)
{
	int rc;
	uint32_t index, offset, max_size;

	if (!ffs || !new_ffs)
		return FLASH_ERR_PARM_ERROR;

	*new_ffs = NULL;

	rc = ffs_lookup_part(ffs, "OTHER_SIDE", &index);
	if (rc)
		return rc;

	rc = ffs_part_info(ffs, index, NULL, &offset, &max_size, NULL, NULL);
	if (rc)
		return rc;

	return ffs_init(offset, max_size, ffs->bl, new_ffs, mark_ecc);
}
예제 #4
0
파일: flash.c 프로젝트: open-power/skiboot
static int flash_nvram_probe(struct flash *flash, struct ffs_handle *ffs)
{
	uint32_t start, size, part;
	bool ecc;
	int rc;

	prlog(PR_INFO, "FLASH: probing for NVRAM\n");

	rc = ffs_lookup_part(ffs, "NVRAM", &part);
	if (rc) {
		prlog(PR_WARNING, "FLASH: no NVRAM partition found\n");
		return OPAL_HARDWARE;
	}

	rc = ffs_part_info(ffs, part, NULL,
			   &start, &size, NULL, &ecc);
	if (rc) {
		/**
		 * @fwts-label NVRAMNoPartition
		 * @fwts-advice OPAL could not find an NVRAM partition
		 *     on the system flash. Check that the system flash
		 *     has a valid partition table, and that the firmware
		 *     build process has added a NVRAM partition.
		 */
		prlog(PR_ERR, "FLASH: Can't parse ffs info for NVRAM\n");
		return OPAL_HARDWARE;
	}

	nvram_flash = flash;
	nvram_offset = start;
	nvram_size = ecc ? ecc_buffer_size_minus_ecc(size) : size;

	platform.nvram_info = flash_nvram_info;
	platform.nvram_start_read = flash_nvram_start_read;
	platform.nvram_write = flash_nvram_write;

	return 0;
}
예제 #5
0
파일: flash.c 프로젝트: open-power/skiboot
/*
 * load a resource from FLASH
 * buf and len shouldn't account for ECC even if partition is ECCed.
 *
 * The API here is a bit strange.
 * If resource has a STB container, buf will contain it
 * If loading subpartition with STB container, buff will *NOT* contain it
 * For trusted boot, the whole partition containing the subpart is measured.
 *
 * Additionally, the logic to work out how much to read from flash is insane.
 */
static int flash_load_resource(enum resource_id id, uint32_t subid,
			       void *buf, size_t *len)
{
	int i;
	int rc = OPAL_RESOURCE;
	struct ffs_handle *ffs;
	struct flash *flash;
	const char *name;
	bool status = false;
	bool ecc;
	bool part_signed = false;
	void *bufp = buf;
	size_t bufsz = *len;
	int ffs_part_num, ffs_part_start, ffs_part_size;
	int content_size = 0;
	int offset = 0;

	lock(&flash_lock);

	if (!system_flash) {
		/**
		 * @fwts-label SystemFlashNotFound
		 * @fwts-advice No system flash was found. Check for missing
		 * calls flash_register(...).
		 */
		prlog(PR_WARNING, "FLASH: Can't load resource id:%i. "
		      "No system flash found\n", id);
		goto out_unlock;
	}

	flash = system_flash;

	if (flash->busy)
		goto out_unlock;

	for (i = 0, name = NULL; i < ARRAY_SIZE(part_name_map); i++) {
		if (part_name_map[i].id == id) {
			name = part_name_map[i].name;
			break;
		}
	}
	if (!name) {
		prerror("FLASH: Couldn't find partition for id %d\n", id);
		goto out_unlock;
	}
	/*
	 * If partition doesn't have a subindex but the caller specifies one,
	 * we fail.  eg. kernel partition doesn't have a subindex
	 */
	if ((part_name_map[i].subid == RESOURCE_SUBID_NONE) &&
	    (subid != RESOURCE_SUBID_NONE)) {
		prerror("PLAT: Partition %s doesn't have subindex\n", name);
		goto out_unlock;
	}

	rc = ffs_init(0, flash->size, flash->bl, &ffs, 1);
	if (rc) {
		prerror("FLASH: Can't open ffs handle: %d\n", rc);
		goto out_unlock;
	}

	rc = ffs_lookup_part(ffs, name, &ffs_part_num);
	if (rc) {
		/* This is not an error per-se, some partitions
		 * are purposefully absent, don't spam the logs
		 */
	        prlog(PR_DEBUG, "FLASH: No %s partition\n", name);
		goto out_free_ffs;
	}
	rc = ffs_part_info(ffs, ffs_part_num, NULL,
			   &ffs_part_start, NULL, &ffs_part_size, &ecc);
	if (rc) {
		prerror("FLASH: Failed to get %s partition info\n", name);
		goto out_free_ffs;
	}
	prlog(PR_DEBUG,"FLASH: %s partition %s ECC\n",
	      name, ecc  ? "has" : "doesn't have");

	if (ffs_part_size < SECURE_BOOT_HEADERS_SIZE) {
		prerror("FLASH: secboot headers bigger than "
			"partition size 0x%x\n", ffs_part_size);
		goto out_free_ffs;
	}

	rc = blocklevel_read(flash->bl, ffs_part_start, bufp,
			SECURE_BOOT_HEADERS_SIZE);
	if (rc) {
		prerror("FLASH: failed to read the first 0x%x from "
			"%s partition, rc %d\n", SECURE_BOOT_HEADERS_SIZE,
			name, rc);
		goto out_free_ffs;
	}

	part_signed = stb_is_container(bufp, SECURE_BOOT_HEADERS_SIZE);

	prlog(PR_DEBUG, "FLASH: %s partition %s signed\n", name,
	      part_signed ? "is" : "isn't");

	/*
	 * part_start/size are raw pointers into the partition.
	 *  ie. they will account for ECC if included.
	 */

	if (part_signed) {
		bufp += SECURE_BOOT_HEADERS_SIZE;
		bufsz -= SECURE_BOOT_HEADERS_SIZE;
		content_size = stb_sw_payload_size(buf, SECURE_BOOT_HEADERS_SIZE);
		*len = content_size + SECURE_BOOT_HEADERS_SIZE;

		if (content_size > bufsz) {
			prerror("FLASH: content size > buffer size\n");
			rc = OPAL_PARAMETER;
			goto out_free_ffs;
		}

		ffs_part_start += SECURE_BOOT_HEADERS_SIZE;

		rc = blocklevel_read(flash->bl, ffs_part_start, bufp,
					  content_size);
		if (rc) {
			prerror("FLASH: failed to read content size %d"
				" %s partition, rc %d\n",
				content_size, name, rc);
			goto out_free_ffs;
		}

		if (subid == RESOURCE_SUBID_NONE)
			goto done_reading;

		rc = flash_subpart_info(bufp, content_size, ffs_part_size,
					NULL, subid, &offset, &content_size);
		if (rc) {
			prerror("FLASH: Failed to parse subpart info for %s\n",
				name);
			goto out_free_ffs;
		}
		bufp += offset;
		goto done_reading;
	} else /* stb_signed */ {
		/*
		 * Back to the old way of doing things, no STB header.
		 */
		if (subid == RESOURCE_SUBID_NONE) {
			if (id == RESOURCE_ID_KERNEL ||
				id == RESOURCE_ID_INITRAMFS) {
				/*
				 * Because actualSize is a lie, we compute the
				 * size of the BOOTKERNEL based on what the ELF
				 * headers say. Otherwise we end up reading more
				 * than we should
				 */
				content_size = sizeof_elf_from_hdr(buf);
				if (!content_size) {
					prerror("FLASH: Invalid ELF header part"
						" %s\n", name);
					rc = OPAL_RESOURCE;
					goto out_free_ffs;
				}
			} else {
				content_size = ffs_part_size;
			}
			if (content_size > bufsz) {
				prerror("FLASH: %s content size %d > "
					" buffer size %lu\n", name,
					content_size, bufsz);
				rc = OPAL_PARAMETER;
				goto out_free_ffs;
			}
			prlog(PR_DEBUG, "FLASH: computed %s size %u\n",
			      name, content_size);
			rc = blocklevel_read(flash->bl, ffs_part_start,
						  buf, content_size);
			if (rc) {
				prerror("FLASH: failed to read content size %d"
					" %s partition, rc %d\n",
					content_size, name, rc);
				goto out_free_ffs;
			}
			*len = content_size;
			goto done_reading;
		}
		BUILD_ASSERT(FLASH_SUBPART_HEADER_SIZE <= SECURE_BOOT_HEADERS_SIZE);
		rc = flash_subpart_info(bufp, SECURE_BOOT_HEADERS_SIZE,
					ffs_part_size, &ffs_part_size, subid,
					&offset, &content_size);
		if (rc) {
			prerror("FLASH: FAILED reading subpart info. rc=%d\n",
				rc);
			goto out_free_ffs;
		}

		*len = ffs_part_size;
		prlog(PR_DEBUG, "FLASH: Computed %s partition size: %u "
		      "(subpart %u size %u offset %u)\n", name, ffs_part_size,
		      subid, content_size, offset);
		/*
		 * For a sub partition, we read the whole (computed)
		 * partition, and then measure that.
		 * Afterwards, we memmove() things back into place for
		 * the caller.
		 */
		rc = blocklevel_read(flash->bl, ffs_part_start,
					  buf, ffs_part_size);

		bufp += offset;
	}

done_reading:
	/*
	 * Verify and measure the retrieved PNOR partition as part of the
	 * secure boot and trusted boot requirements
	 */
	secureboot_verify(id, buf, *len);
	trustedboot_measure(id, buf, *len);

	/* Find subpartition */
	if (subid != RESOURCE_SUBID_NONE) {
		memmove(buf, bufp, content_size);
		*len = content_size;
	}

	status = true;

out_free_ffs:
	ffs_close(ffs);
out_unlock:
	unlock(&flash_lock);
	return status ? OPAL_SUCCESS : rc;
}
예제 #6
0
파일: flash.c 프로젝트: open-power/skiboot
static struct dt_node *flash_add_dt_node(struct flash *flash, int id)
{
	int i;
	int rc;
	const char *name;
	bool ecc;
	struct ffs_handle *ffs;
	int ffs_part_num, ffs_part_start, ffs_part_size;
	struct dt_node *flash_node;
	struct dt_node *partition_container_node;
	struct dt_node *partition_node;

	flash_node = dt_new_addr(opal_node, "flash", id);
	dt_add_property_strings(flash_node, "compatible", "ibm,opal-flash");
	dt_add_property_cells(flash_node, "ibm,opal-id", id);
	dt_add_property_u64(flash_node, "reg", flash->size);
	dt_add_property_cells(flash_node, "ibm,flash-block-size",
			flash->block_size);
	if (flash->no_erase)
		dt_add_property(flash_node, "no-erase", NULL, 0);

	/* we fix to 32-bits */
	dt_add_property_cells(flash_node, "#address-cells", 1);
	dt_add_property_cells(flash_node, "#size-cells", 1);

	/* Add partition container node */
	partition_container_node = dt_new(flash_node, "partitions");
	dt_add_property_strings(partition_container_node, "compatible", "fixed-partitions");

	/* we fix to 32-bits */
	dt_add_property_cells(partition_container_node, "#address-cells", 1);
	dt_add_property_cells(partition_container_node, "#size-cells", 1);

	/* Add partitions */
	for (i = 0, name = NULL; i < ARRAY_SIZE(part_name_map); i++) {
		name = part_name_map[i].name;

		rc = ffs_init(0, flash->size, flash->bl, &ffs, 1);
		if (rc) {
			prerror("FLASH: Can't open ffs handle\n");
			continue;
		}

		rc = ffs_lookup_part(ffs, name, &ffs_part_num);
		if (rc) {
			/* This is not an error per-se, some partitions
			 * are purposefully absent, don't spam the logs
			 */
		        prlog(PR_DEBUG, "FLASH: No %s partition\n", name);
			continue;
		}
		rc = ffs_part_info(ffs, ffs_part_num, NULL,
				   &ffs_part_start, NULL, &ffs_part_size, &ecc);
		if (rc) {
			prerror("FLASH: Failed to get %s partition info\n", name);
			continue;
		}

		partition_node = dt_new_addr(partition_container_node, "partition", ffs_part_start);
		dt_add_property_strings(partition_node, "label", name);
		dt_add_property_cells(partition_node, "reg", ffs_part_start, ffs_part_size);
		if (part_name_map[i].id != RESOURCE_ID_KERNEL_FW) {
			/* Mark all partitions other than the full PNOR and the boot kernel
			 * firmware as read only.  These two partitions are the only partitions
			 * that are properly erase block aligned at this time.
			 */
			dt_add_property(partition_node, "read-only", NULL, 0);
		}
	}

	partition_node = dt_new_addr(partition_container_node, "partition", 0);
	dt_add_property_strings(partition_node, "label", "PNOR");
	dt_add_property_cells(partition_node, "reg", 0, flash->size);

	return flash_node;
}
예제 #7
0
int main(int argc, char *argv[])
{
    const char *pname = argv[0];
    uint32_t address = 0, read_size = 0, write_size = 0;
    uint32_t erase_start = 0, erase_size = 0;
    bool erase = false, do_clear = false;
    bool program = false, erase_all = false, info = false, do_read = false;
    bool enable_4B = false, disable_4B = false;
    bool show_help = false, show_version = false;
    bool no_action = false, tune = false;
    char *write_file = NULL, *read_file = NULL, *part_name = NULL;
    bool ffs_toc_seen = false;
    int rc;

    while(1) {
        static struct option long_opts[] = {
            {"address",	required_argument,	NULL,	'a'},
            {"size",	required_argument,	NULL,	's'},
            {"partition",	required_argument,	NULL,	'P'},
            {"bmc",		no_argument,		NULL,	'b'},
            {"enable-4B",	no_argument,		NULL,	'4'},
            {"disable-4B",	no_argument,		NULL,	'3'},
            {"read",	required_argument,	NULL,	'r'},
            {"erase-all",	no_argument,		NULL,	'E'},
            {"erase",	no_argument,		NULL,	'e'},
            {"program",	required_argument,	NULL,	'p'},
            {"force",	no_argument,		NULL,	'f'},
            {"info",	no_argument,		NULL,	'i'},
            {"tune",	no_argument,		NULL,	't'},
            {"dummy",	no_argument,		NULL,	'd'},
            {"help",	no_argument,		NULL,	'h'},
            {"version",	no_argument,		NULL,	'v'},
            {"debug",	no_argument,		NULL,	'g'},
            {"side",	required_argument,	NULL,	'S'},
            {"toc",		required_argument,	NULL,	'T'},
            {"clear",   no_argument,        NULL,   'c'}
        };
        int c, oidx = 0;

        c = getopt_long(argc, argv, "a:s:P:r:43Eep:fdihvbtgS:T:c",
                        long_opts, &oidx);
        if (c == EOF)
            break;
        switch(c) {
        case 'a':
            address = strtoul(optarg, NULL, 0);
            break;
        case 's':
            read_size = write_size = strtoul(optarg, NULL, 0);
            break;
        case 'P':
            part_name = strdup(optarg);
            break;
        case '4':
            enable_4B = true;
            break;
        case '3':
            disable_4B = true;
            break;
        case 'r':
            do_read = true;
            read_file = strdup(optarg);
            break;
        case 'E':
            erase_all = erase = true;
            break;
        case 'e':
            erase = true;
            break;
        case 'p':
            program = true;
            write_file = strdup(optarg);
            break;
        case 'f':
            must_confirm = false;
            break;
        case 'd':
            must_confirm = false;
            dummy_run = true;
            break;
        case 'i':
            info = true;
            break;
        case 'b':
            bmc_flash = true;
            break;
        case 't':
            tune = true;
            break;
        case 'v':
            show_version = true;
            break;
        case 'h':
            show_help = show_version = true;
            break;
        case 'g':
            libflash_debug = true;
            break;
        case 'S':
            flash_side = atoi(optarg);
            break;
        case 'T':
            ffs_toc_seen = true;
            ffs_toc = strtoul(optarg, NULL, 0);
            break;
        case 'c':
            do_clear = true;
            break;
        default:
            exit(1);
        }
    }

    /* Check if we need to access the flash at all (which will
     * also tune them as a side effect
     */
    no_action = !erase && !program && !info && !do_read &&
                !enable_4B && !disable_4B && !tune && !do_clear;

    /* Nothing to do, if we didn't already, print usage */
    if (no_action && !show_version)
        show_help = show_version = true;

    if (show_version)
        print_version();
    if (show_help)
        print_help(pname);

    if (no_action)
        return 0;

    /* --enable-4B and --disable-4B are mutually exclusive */
    if (enable_4B && disable_4B) {
        fprintf(stderr, "--enable-4B and --disable-4B are mutually"
                " exclusive !\n");
        exit(1);
    }

    /* 4B not supported on BMC flash */
    if (enable_4B && bmc_flash) {
        fprintf(stderr, "--enable-4B not supported on BMC flash !\n");
        exit(1);
    }

    /* partitions not supported on BMC flash */
    if (part_name && bmc_flash) {
        fprintf(stderr, "--partition not supported on BMC flash !\n");
        exit(1);
    }

    /* part-name and erase-all make no sense together */
    if (part_name && erase_all) {
        fprintf(stderr, "--partition and --erase-all are mutually"
                " exclusive !\n");
        exit(1);
    }

    /* Read command should always come with a file */
    if (do_read && !read_file) {
        fprintf(stderr, "Read with no file specified !\n");
        exit(1);
    }

    /* Program command should always come with a file */
    if (program && !write_file) {
        fprintf(stderr, "Program with no file specified !\n");
        exit(1);
    }

    /* If both partition and address specified, error out */
    if (address && part_name) {
        fprintf(stderr, "Specify partition or address, not both !\n");
        exit(1);
    }

    if (do_clear && !part_name) {
        fprintf(stderr, "--clear only supported on a partition name\n");
        exit(1);
    }

    /* Explicitly only support two sides */
    if (flash_side != 0 && flash_side != 1) {
        fprintf(stderr, "Unexpected value for --side '%d'\n", flash_side);
        exit(1);
    }

    if (ffs_toc_seen && flash_side) {
        fprintf(stderr, "--toc and --side are exclusive");
        exit(1);
    }

    /* If file specified but not size, get size from file
     */
    if (write_file && !write_size) {
        struct stat stbuf;

        if (stat(write_file, &stbuf)) {
            perror("Failed to get file size");
            exit(1);
        }
        write_size = stbuf.st_size;
    }

    if (bmc_flash) {
        if (arch_flash_bmc(NULL, 1) == -1) {
            fprintf(stderr, "Can't switch to BMC flash on this architecture\n");
            exit(1);
        }
    }

    if (arch_flash_init(&bl, NULL, true))
        exit(1);

    on_exit(exiting, NULL);


    rc = blocklevel_get_info(bl, &fl_name,
                             &fl_total_size, &fl_erase_granule);
    if (rc) {
        fprintf(stderr, "Error %d getting flash info\n", rc);
        exit(1);
    }

    /* If -t is passed, then print a nice message */
    if (tune)
        printf("Flash and controller tuned\n");

    /* If read specified and no read_size, use flash size */
    if (do_read && !read_size && !part_name)
        read_size = fl_total_size;

    /* We have a partition specified, grab the details */
    if (part_name)
        lookup_partition(part_name);

    /* We have a partition, adjust read/write size if needed */
    if (ffsh && ffs_index >= 0) {
        uint32_t pstart, pmaxsz, pactsize;
        bool ecc;
        int rc;

        rc = ffs_part_info(ffsh, ffs_index, NULL,
                           &pstart, &pmaxsz, &pactsize, &ecc);
        if (rc) {
            fprintf(stderr,"Failed to get partition info\n");
            exit(1);
        }

        if (!ecc && do_clear) {
            fprintf(stderr, "The partition on which to do --clear "
                    "does not have ECC, are you sure?\n");
            check_confirm();
            /* Still confirm later on */
            must_confirm = true;
        }

        /* Read size is obtained from partition "actual" size */
        if (!read_size)
            read_size = pactsize;

        /* Write size is max size of partition */
        if (!write_size)
            write_size = pmaxsz;

        /* Crop write size to partition size */
        if (write_size > pmaxsz) {
            printf("WARNING: Size (%d bytes) larger than partition"
                   " (%d bytes), cropping to fit\n",
                   write_size, pmaxsz);
            write_size = pmaxsz;
        }

        /* If erasing, check partition alignment */
        if (erase && ((pstart | pmaxsz) & 0xfff)) {
            fprintf(stderr,"Partition not aligned properly\n");
            exit(1);
        }

        /* Set address */
        address = pstart;
    }

    /* Align erase boundaries */
    if (erase && !erase_all) {
        uint32_t mask = 0xfff;
        uint32_t erase_end;

        /* Dummy size for erase, will be adjusted later */
        if (!write_size)
            write_size = 1;
        erase_start = address & ~mask;
        erase_end = ((address + write_size) + mask) & ~mask;
        erase_size = erase_end - erase_start;

        if (erase_start != address || erase_size != write_size)
            fprintf(stderr, "WARNING: Erase region adjusted"
                    " to 0x%08x..0x%08x\n",
                    erase_start, erase_end);
    }

    /* Process commands */
    if (enable_4B)
        enable_4B_addresses();
    if (disable_4B)
        disable_4B_addresses();
    if (info) {
        /*
         * Don't pass through modfied TOC value if the modification was done
         * because of --size, but still respect if it came from --toc (we
         * assume the user knows what they're doing in that case)
         */
        print_flash_info(flash_side ? 0 : ffs_toc);
    }

    /* Unlock flash (PNOR only) */
    if ((erase || program || do_clear) && !bmc_flash) {
        need_relock = arch_flash_set_wrprotect(bl, false);
        if (need_relock == -1) {
            fprintf(stderr, "Architecture doesn't support write protection on flash\n");
            need_relock = 0;
            exit (1);
        }
    }
    if (do_read)
        do_read_file(read_file, address, read_size);
    if (erase_all)
        erase_chip();
    else if (erase)
        erase_range(erase_start, erase_size, program);
    if (program)
        program_file(write_file, address, write_size);
    if (do_clear)
        set_ecc(address, write_size);
    return 0;
}
예제 #8
0
int ffs_init(uint32_t offset, uint32_t max_size, struct blocklevel_device *bl,
		struct ffs_handle **ffs, bool mark_ecc)
{
	struct ffs_hdr hdr;
	struct ffs_hdr blank_hdr;
	struct ffs_handle *f;
	uint64_t total_size;
	int rc, i;

	if (!ffs || !bl)
		return FLASH_ERR_PARM_ERROR;
	*ffs = NULL;

	rc = blocklevel_get_info(bl, NULL, &total_size, NULL);
	if (rc) {
		FL_ERR("FFS: Error %d retrieving flash info\n", rc);
		return rc;
	}
	if (total_size > UINT_MAX)
		return FLASH_ERR_VERIFY_FAILURE;
	if ((offset + max_size) < offset)
		return FLASH_ERR_PARM_ERROR;

	if ((max_size > total_size))
		return FLASH_ERR_PARM_ERROR;

	/* Read flash header */
	rc = blocklevel_read(bl, offset, &hdr, sizeof(hdr));
	if (rc) {
		FL_ERR("FFS: Error %d reading flash header\n", rc);
		return rc;
	}

	/*
	 * Flash controllers can get deconfigured or otherwise upset, when this
	 * happens they return all 0xFF bytes.
	 * An ffs_hdr consisting of all 0xFF cannot be valid and it would be
	 * nice to drop a hint to the user to help with debugging. This will
	 * help quickly differentiate between flash corruption and standard
	 * type 'reading from the wrong place' errors vs controller errors or
	 * reading erased data.
	 */
	memset(&blank_hdr, UINT_MAX, sizeof(struct ffs_hdr));
	if (memcmp(&blank_hdr, &hdr, sizeof(struct ffs_hdr)) == 0) {
		FL_ERR("FFS: Reading the flash has returned all 0xFF.\n");
		FL_ERR("Are you reading erased flash?\n");
		FL_ERR("Is something else using the flash controller?\n");
		return FLASH_ERR_BAD_READ;
	}

	/* Allocate ffs_handle structure and start populating */
	f = malloc(sizeof(*f));
	if (!f)
		return FLASH_ERR_MALLOC_FAILED;
	memset(f, 0, sizeof(*f));

	f->toc_offset = offset;
	f->max_size = max_size;
	f->bl = bl;

	/* Convert and check flash header */
	rc = ffs_check_convert_header(&f->hdr, &hdr);
	if (rc) {
		FL_INF("FFS: Flash header not found. Code: %d\n", rc);
		goto out;
	}

	/* Check header is sane */
	if ((f->hdr.block_size * f->hdr.size) > max_size) {
		rc = FLASH_ERR_PARM_ERROR;
		FL_ERR("FFS: Flash header exceeds max flash size\n");
		goto out;
	}

	if ((f->hdr.entry_size * f->hdr.entry_count) >
			(f->hdr.block_size * f->hdr.size)) {
		rc = FLASH_ERR_PARM_ERROR;
		FL_ERR("FFS: Flash header entries exceeds available blocks\n");
		goto out;
	}

	/*
	 * Decide how much of the image to grab to get the whole
	 * partition map.
	 */
	f->cached_size = f->hdr.block_size * f->hdr.size;
	/* Check for overflow or a silly size */
	if (!f->hdr.size || f->cached_size / f->hdr.size != f->hdr.block_size) {
		rc= FLASH_ERR_MALLOC_FAILED;
		FL_ERR("FFS: Cache size overflow (0x%x * 0x%x)\n",
				f->hdr.block_size, f->hdr.size);
		goto out;
	}

	FL_DBG("FFS: Partition map size: 0x%x\n", f->cached_size);

	/* Allocate cache */
	f->cache = malloc(f->cached_size);
	if (!f->cache) {
		rc = FLASH_ERR_MALLOC_FAILED;
		goto out;
	}

	/* Read the cached map */
	rc = blocklevel_read(bl, offset, f->cache, f->cached_size);
	if (rc) {
		FL_ERR("FFS: Error %d reading flash partition map\n", rc);
		goto out;
	}

	if (mark_ecc) {
		uint32_t start, total_size;
		bool ecc;
		for (i = 0; i < f->hdr.entry_count; i++) {
			rc = ffs_part_info(f, i, NULL, &start, &total_size,
					NULL, &ecc);
			if (rc) {
				FL_ERR("FFS: Failed to read ffs partition %d\n",
						i);
				goto out;
			}
			if (ecc) {
				rc = blocklevel_ecc_protect(bl, start, total_size);
				if (rc) {
					FL_ERR("FFS: Failed to blocklevel_ecc_protect(0x%08x, 0x%08x)\n",
					       start, total_size);
					goto out;
				}
			}  /* ecc */
		} /* for */
	}

out:
	if (rc == 0)
		*ffs = f;
	else
		free(f);

	return rc;
}