aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/external/opal-prd/pnor.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/external/opal-prd/pnor.c')
-rw-r--r--roms/skiboot/external/opal-prd/pnor.c217
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;
+}