diff options
Diffstat (limited to 'roms/skiboot/external/opal-prd/pnor.c')
-rw-r--r-- | roms/skiboot/external/opal-prd/pnor.c | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/roms/skiboot/external/opal-prd/pnor.c b/roms/skiboot/external/opal-prd/pnor.c new file mode 100644 index 000000000..b2da7134c --- /dev/null +++ b/roms/skiboot/external/opal-prd/pnor.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * PNOR Access (/dev/mtd) for opal-prd + * + * Copyright 2013-2017 IBM Corp. + */ + +#include <libflash/libffs.h> +#include <common/arch_flash.h> + +#include <errno.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <sys/ioctl.h> +#include <mtd/mtd-user.h> + +#include "pnor.h" +#include "opal-prd.h" + +#define FDT_FLASH_PATH "/proc/device-tree/chosen/ibm,system-flash" + +bool pnor_available(struct pnor *pnor) +{ + /* --pnor is specified */ + if (pnor->path) { + if (access(pnor->path, R_OK | W_OK) == 0) + return true; + + pr_log(LOG_ERR, "PNOR: Does not have permission to read pnor: %m"); + return false; + } + + if (access(FDT_FLASH_PATH, R_OK) == 0) + return true; + + return false; +} + +int pnor_init(struct pnor *pnor) +{ + int rc; + + if (!pnor) + return -1; + + rc = arch_flash_init(&(pnor->bl), pnor->path, false); + if (rc) { + pr_log(LOG_ERR, "PNOR: Flash init failed"); + return -1; + } + + rc = blocklevel_get_info(pnor->bl, NULL, &(pnor->size), &(pnor->erasesize)); + if (rc) { + pr_log(LOG_ERR, "PNOR: blocklevel_get_info() failed. Can't use PNOR"); + goto out; + } + + rc = ffs_init(0, pnor->size, pnor->bl, &pnor->ffsh, 0); + if (rc) { + pr_log(LOG_ERR, "PNOR: Failed to open pnor partition table"); + goto out; + } + + return 0; +out: + arch_flash_close(pnor->bl, pnor->path); + pnor->bl = NULL; + return -1; +} + +void pnor_close(struct pnor *pnor) +{ + if (!pnor) + return; + + if (pnor->ffsh) + ffs_close(pnor->ffsh); + + if (pnor->bl) + arch_flash_close(pnor->bl, pnor->path); + + if (pnor->path) + free(pnor->path); +} + +void dump_parts(struct ffs_handle *ffs) { + int i, rc; + uint32_t start, size, act_size; + char *name; + + pr_debug("PNOR: %10s %8s %8s %8s", + "name", "start", "size", "act_size"); + for (i = 0; ; i++) { + rc = ffs_part_info(ffs, i, &name, &start, + &size, &act_size, NULL); + if (rc) + break; + pr_debug("PNOR: %10s %08x %08x %08x", + name, start, size, act_size); + free(name); + } +} + +static int mtd_write(struct pnor *pnor, void *data, uint64_t offset, + size_t len) +{ + int rc; + + if (len > pnor->size || offset > pnor->size || + len + offset > pnor->size) + return -ERANGE; + + rc = blocklevel_smart_write(pnor->bl, offset, data, len); + if (rc) + return -errno; + + return len; +} + +static int mtd_read(struct pnor *pnor, void *data, uint64_t offset, + size_t len) +{ + int rc; + + if (len > pnor->size || offset > pnor->size || + len + offset > pnor->size) + return -ERANGE; + + rc = blocklevel_read(pnor->bl, offset, data, len); + if (rc) + return -errno; + + return len; +} + +/* Similar to read(2), this performs partial operations where the number of + * bytes read/written may be less than size. + * + * Returns number of bytes written, or a negative value on failure. */ +int pnor_operation(struct pnor *pnor, const char *name, uint64_t offset, + void *data, size_t requested_size, enum pnor_op op) +{ + int rc; + uint32_t pstart, psize, idx; + int size; + + if (!pnor->ffsh) { + pr_log(LOG_ERR, "PNOR: ffs not initialised"); + return -EBUSY; + } + + rc = ffs_lookup_part(pnor->ffsh, name, &idx); + if (rc) { + pr_log(LOG_WARNING, "PNOR: no partiton named '%s'", name); + return -ENOENT; + } + + ffs_part_info(pnor->ffsh, idx, NULL, &pstart, &psize, NULL, NULL); + if (rc) { + pr_log(LOG_ERR, "PNOR: unable to fetch partition info for %s", + name); + return -ENOENT; + } + + if (offset > psize) { + pr_log(LOG_WARNING, "PNOR: partition %s(size 0x%x) " + "offset (0x%lx) out of bounds", + name, psize, offset); + return -ERANGE; + } + + /* Large requests are trimmed */ + if (requested_size > psize) + size = psize; + else + size = requested_size; + + if (size + offset > psize) + size = psize - offset; + + if (size < 0) { + pr_log(LOG_WARNING, "PNOR: partition %s(size 0x%x) " + "read size (0x%zx) and offset (0x%lx) " + "out of bounds", + name, psize, requested_size, offset); + return -ERANGE; + } + + switch (op) { + case PNOR_OP_READ: + rc = mtd_read(pnor, data, pstart + offset, size); + break; + case PNOR_OP_WRITE: + rc = mtd_write(pnor, data, pstart + offset, size); + break; + default: + rc = -EIO; + pr_log(LOG_ERR, "PNOR: Invalid operation"); + goto out; + } + + if (rc < 0) + pr_log(LOG_ERR, "PNOR: MTD operation failed"); + else if (rc != size) + pr_log(LOG_WARNING, "PNOR: mtd operation " + "returned %d, expected %d", + rc, size); + +out: + return rc; +} |