226 lines
4.7 KiB
C
226 lines
4.7 KiB
C
/* 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;
|
|
}
|