/* Copyright 2013-2015 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #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; }