blob: c032421d14ace2788cce69ee0316e37527b138ef [file] [log] [blame]
/* 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 <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;
}