int flash_register(struct blocklevel_device *bl) { uint64_t size; uint32_t block_size; struct ffs_handle *ffs; struct dt_node *node; struct flash *flash; const char *name; int rc; rc = blocklevel_get_info(bl, &name, &size, &block_size); if (rc) return rc; if (!name) name = "(unnamed)"; prlog(PR_INFO, "FLASH: registering flash device %s " "(size 0x%llx, blocksize 0x%x)\n", name, size, block_size); flash = malloc(sizeof(struct flash)); if (!flash) { prlog(PR_ERR, "FLASH: Error allocating flash structure\n"); return OPAL_RESOURCE; } flash->busy = false; flash->bl = bl; flash->no_erase = !(bl->flags & WRITE_NEED_ERASE); flash->size = size; flash->block_size = block_size; flash->id = num_flashes(); rc = ffs_init(0, flash->size, bl, &ffs, 1); if (rc) { /** * @fwts-label NoFFS * @fwts-advice System flash isn't formatted as expected. * This could mean several OPAL utilities do not function * as expected. e.g. gard, pflash. */ prlog(PR_WARNING, "FLASH: No ffs info; " "using raw device only\n"); ffs = NULL; } node = flash_add_dt_node(flash, flash->id); setup_system_flash(flash, node, name, ffs); if (ffs) ffs_close(ffs); lock(&flash_lock); list_add(&flashes, &flash->list); unlock(&flash_lock); return OPAL_SUCCESS; }
int blocklevel_smart_write(struct blocklevel_device *bl, uint64_t pos, const void *buf, uint64_t len) { uint32_t erase_size; const void *write_buf = buf; void *write_buf_start = NULL; uint64_t ecc_start; void *erase_buf; int rc = 0; if (!write_buf || !bl) { errno = EINVAL; return FLASH_ERR_PARM_ERROR; } FL_DBG("%s: 0x%" PRIx64 "\t0x%" PRIx64 "\n", __func__, pos, len); if (!(bl->flags & WRITE_NEED_ERASE)) { FL_DBG("%s: backend doesn't need erase\n", __func__); return blocklevel_write(bl, pos, buf, len); } rc = blocklevel_get_info(bl, NULL, NULL, &erase_size); if (rc) return rc; if (ecc_protected(bl, pos, len, &ecc_start)) { FL_DBG("%s: region has ECC\n", __func__); len = ecc_buffer_size(len); write_buf_start = malloc(len); if (!write_buf_start) { errno = ENOMEM; return FLASH_ERR_MALLOC_FAILED; } if (memcpy_to_ecc(write_buf_start, buf, ecc_buffer_size_minus_ecc(len))) { free(write_buf_start); errno = EBADF; return FLASH_ERR_ECC_INVALID; } write_buf = write_buf_start; } erase_buf = malloc(erase_size); if (!erase_buf) { errno = ENOMEM; rc = FLASH_ERR_MALLOC_FAILED; goto out_free; } rc = reacquire(bl); if (rc) goto out_free; while (len > 0) { uint32_t erase_block = pos & ~(erase_size - 1); uint32_t block_offset = pos & (erase_size - 1); uint32_t size = erase_size > len ? len : erase_size; int cmp; /* Write crosses an erase boundary, shrink the write to the boundary */ if (erase_size < block_offset + size) { size = erase_size - block_offset; } rc = bl->read(bl, erase_block, erase_buf, erase_size); if (rc) goto out; cmp = blocklevel_flashcmp(erase_buf + block_offset, write_buf, size); FL_DBG("%s: region 0x%08x..0x%08x ", __func__, erase_block, erase_size); if (cmp != 0) { FL_DBG("needs "); if (cmp == -1) { FL_DBG("erase and "); bl->erase(bl, erase_block, erase_size); } FL_DBG("write\n"); memcpy(erase_buf + block_offset, write_buf, size); rc = bl->write(bl, erase_block, erase_buf, erase_size); if (rc) goto out; } len -= size; pos += size; write_buf += size; } out: release(bl); out_free: free(write_buf_start); free(erase_buf); return rc; }
int blocklevel_smart_write(struct blocklevel_device *bl, uint32_t pos, const void *buf, uint32_t len) { uint32_t erase_size; const void *write_buf = buf; void *write_buf_start = NULL; void *erase_buf; int rc = 0; if (!write_buf || !bl) { errno = EINVAL; return FLASH_ERR_PARM_ERROR; } if (!(bl->flags & WRITE_NEED_ERASE)) return blocklevel_write(bl, pos, buf, len); rc = blocklevel_get_info(bl, NULL, NULL, &erase_size); if (rc) return rc; if (ecc_protected(bl, pos, len)) { len = ecc_buffer_size(len); write_buf_start = malloc(len); if (!write_buf_start) { errno = ENOMEM; return FLASH_ERR_MALLOC_FAILED; } if (memcpy_to_ecc(write_buf_start, buf, ecc_buffer_size_minus_ecc(len))) { free(write_buf_start); errno = EBADF; return FLASH_ERR_ECC_INVALID; } write_buf = write_buf_start; } erase_buf = malloc(erase_size); if (!erase_buf) { errno = ENOMEM; rc = FLASH_ERR_MALLOC_FAILED; goto out; } while (len > 0) { uint32_t erase_block = pos & ~(erase_size - 1); uint32_t block_offset = pos & (erase_size - 1); uint32_t size = erase_size > len ? len : erase_size; int cmp; /* Write crosses an erase boundary, shrink the write to the boundary */ if (erase_size < block_offset + size) { size = erase_size - block_offset; } rc = bl->read(bl, erase_block, erase_buf, erase_size); if (rc) goto out; cmp = blocklevel_flashcmp(erase_buf + block_offset, write_buf, size); if (cmp != 0) { if (cmp == -1) bl->erase(bl, erase_block, erase_size); memcpy(erase_buf + block_offset, write_buf, size); rc = bl->write(bl, erase_block, erase_buf, erase_size); if (rc) goto out; } len -= size; pos += size; write_buf += size; } out: free(write_buf_start); free(erase_buf); return rc; }
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; }
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; }