blob: 96b7c5571750e8259a5ba9a9f42351713a6b1b92 [file] [log] [blame]
/* Copyright 2013-2014 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 <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef __SKIBOOT__
#include <sys/types.h>
#include <unistd.h>
#endif
#include <ccan/endian/endian.h>
#include "libffs.h"
enum ffs_type {
ffs_type_flash,
ffs_type_image,
};
struct ffs_handle {
struct ffs_hdr hdr; /* Converted header */
enum ffs_type type;
uint32_t toc_offset;
uint32_t max_size;
void *cache;
uint32_t cached_size;
struct blocklevel_device *bl;
};
static uint32_t ffs_checksum(void* data, size_t size)
{
uint32_t i, csum = 0;
for (i = csum = 0; i < (size/4); i++)
csum ^= ((uint32_t *)data)[i];
return csum;
}
static int ffs_check_convert_header(struct ffs_hdr *dst, struct ffs_hdr *src)
{
dst->magic = be32_to_cpu(src->magic);
if (dst->magic != FFS_MAGIC)
return FFS_ERR_BAD_MAGIC;
dst->version = be32_to_cpu(src->version);
if (dst->version != FFS_VERSION_1)
return FFS_ERR_BAD_VERSION;
if (ffs_checksum(src, FFS_HDR_SIZE) != 0)
return FFS_ERR_BAD_CKSUM;
dst->size = be32_to_cpu(src->size);
dst->entry_size = be32_to_cpu(src->entry_size);
dst->entry_count = be32_to_cpu(src->entry_count);
dst->block_size = be32_to_cpu(src->block_size);
dst->block_count = be32_to_cpu(src->block_count);
return 0;
}
int ffs_init(uint32_t offset, uint32_t max_size, struct blocklevel_device *bl,
struct ffs_handle **ffs, bool mark_ecc)
{
struct ffs_hdr hdr;
struct ffs_hdr blank_hdr;
struct ffs_handle *f;
uint64_t total_size;
int rc, i;
if (!ffs || !bl)
return FLASH_ERR_PARM_ERROR;
*ffs = NULL;
rc = blocklevel_get_info(bl, NULL, &total_size, NULL);
if (rc) {
FL_ERR("FFS: Error %d retrieving flash info\n", rc);
return rc;
}
if (total_size > UINT_MAX)
return FLASH_ERR_VERIFY_FAILURE;
if ((offset + max_size) < offset)
return FLASH_ERR_PARM_ERROR;
if ((max_size > total_size))
return FLASH_ERR_PARM_ERROR;
/* Read flash header */
rc = blocklevel_read(bl, offset, &hdr, sizeof(hdr));
if (rc) {
FL_ERR("FFS: Error %d reading flash header\n", rc);
return rc;
}
/*
* Flash controllers can get deconfigured or otherwise upset, when this
* happens they return all 0xFF bytes.
* An ffs_hdr consisting of all 0xFF cannot be valid and it would be
* nice to drop a hint to the user to help with debugging. This will
* help quickly differentiate between flash corruption and standard
* type 'reading from the wrong place' errors vs controller errors or
* reading erased data.
*/
memset(&blank_hdr, UINT_MAX, sizeof(struct ffs_hdr));
if (memcmp(&blank_hdr, &hdr, sizeof(struct ffs_hdr)) == 0) {
FL_ERR("FFS: Reading the flash has returned all 0xFF.\n");
FL_ERR("Are you reading erased flash?\n");
FL_ERR("Is something else using the flash controller?\n");
return FLASH_ERR_BAD_READ;
}
/* Allocate ffs_handle structure and start populating */
f = malloc(sizeof(*f));
if (!f)
return FLASH_ERR_MALLOC_FAILED;
memset(f, 0, sizeof(*f));
f->toc_offset = offset;
f->max_size = max_size;
f->bl = bl;
/* Convert and check flash header */
rc = ffs_check_convert_header(&f->hdr, &hdr);
if (rc) {
FL_INF("FFS: Flash header not found. Code: %d\n", rc);
goto out;
}
/* Check header is sane */
if ((f->hdr.block_size * f->hdr.size) > max_size) {
rc = FLASH_ERR_PARM_ERROR;
FL_ERR("FFS: Flash header exceeds max flash size\n");
goto out;
}
if ((f->hdr.entry_size * f->hdr.entry_count) >
(f->hdr.block_size * f->hdr.size)) {
rc = FLASH_ERR_PARM_ERROR;
FL_ERR("FFS: Flash header entries exceeds available blocks\n");
goto out;
}
/*
* Decide how much of the image to grab to get the whole
* partition map.
*/
f->cached_size = f->hdr.block_size * f->hdr.size;
/* Check for overflow or a silly size */
if (!f->hdr.size || f->cached_size / f->hdr.size != f->hdr.block_size) {
rc= FLASH_ERR_MALLOC_FAILED;
FL_ERR("FFS: Cache size overflow (0x%x * 0x%x)\n",
f->hdr.block_size, f->hdr.size);
goto out;
}
FL_DBG("FFS: Partition map size: 0x%x\n", f->cached_size);
/* Allocate cache */
f->cache = malloc(f->cached_size);
if (!f->cache) {
rc = FLASH_ERR_MALLOC_FAILED;
goto out;
}
/* Read the cached map */
rc = blocklevel_read(bl, offset, f->cache, f->cached_size);
if (rc) {
FL_ERR("FFS: Error %d reading flash partition map\n", rc);
goto out;
}
if (mark_ecc) {
uint32_t start, total_size;
bool ecc;
for (i = 0; i < f->hdr.entry_count; i++) {
rc = ffs_part_info(f, i, NULL, &start, &total_size,
NULL, &ecc);
if (rc) {
FL_ERR("FFS: Failed to read ffs partition %d\n",
i);
goto out;
}
if (ecc) {
rc = blocklevel_ecc_protect(bl, start, total_size);
if (rc) {
FL_ERR("FFS: Failed to blocklevel_ecc_protect(0x%08x, 0x%08x)\n",
start, total_size);
goto out;
}
} /* ecc */
} /* for */
}
out:
if (rc == 0)
*ffs = f;
else
free(f);
return rc;
}
void ffs_close(struct ffs_handle *ffs)
{
if (ffs->cache)
free(ffs->cache);
free(ffs);
}
static struct ffs_entry *ffs_get_part(struct ffs_handle *ffs, uint32_t index,
uint32_t *out_offset)
{
uint32_t esize = ffs->hdr.entry_size;
uint32_t offset = FFS_HDR_SIZE + index * esize;
if (index > ffs->hdr.entry_count)
return NULL;
if (out_offset)
*out_offset = ffs->toc_offset + offset;
return (struct ffs_entry *)(ffs->cache + offset);
}
static int ffs_check_convert_entry(struct ffs_entry *dst, struct ffs_entry *src)
{
if (ffs_checksum(src, FFS_ENTRY_SIZE) != 0)
return FFS_ERR_BAD_CKSUM;
memcpy(dst->name, src->name, sizeof(dst->name));
dst->base = be32_to_cpu(src->base);
dst->size = be32_to_cpu(src->size);
dst->pid = be32_to_cpu(src->pid);
dst->id = be32_to_cpu(src->id);
dst->type = be32_to_cpu(src->type);
dst->flags = be32_to_cpu(src->flags);
dst->actual = be32_to_cpu(src->actual);
dst->user.datainteg = be16_to_cpu(src->user.datainteg);
return 0;
}
int ffs_lookup_part(struct ffs_handle *ffs, const char *name,
uint32_t *part_idx)
{
struct ffs_entry ent;
uint32_t i;
int rc;
/* Lookup the requested partition */
for (i = 0; i < ffs->hdr.entry_count; i++) {
struct ffs_entry *src_ent = ffs_get_part(ffs, i, NULL);
rc = ffs_check_convert_entry(&ent, src_ent);
if (rc) {
FL_ERR("FFS: Bad entry %d in partition map\n", i);
continue;
}
if (!strncmp(name, ent.name, sizeof(ent.name)))
break;
}
if (i >= ffs->hdr.entry_count)
return FFS_ERR_PART_NOT_FOUND;
if (part_idx)
*part_idx = i;
return 0;
}
int ffs_part_info(struct ffs_handle *ffs, uint32_t part_idx,
char **name, uint32_t *start,
uint32_t *total_size, uint32_t *act_size, bool *ecc)
{
struct ffs_entry *raw_ent;
struct ffs_entry ent;
char *n;
int rc;
if (part_idx >= ffs->hdr.entry_count)
return FFS_ERR_PART_NOT_FOUND;
raw_ent = ffs_get_part(ffs, part_idx, NULL);
if (!raw_ent)
return FFS_ERR_PART_NOT_FOUND;
rc = ffs_check_convert_entry(&ent, raw_ent);
if (rc) {
FL_ERR("FFS: Bad entry %d in partition map\n", part_idx);
return rc;
}
if (start)
*start = ent.base * ffs->hdr.block_size;
if (total_size)
*total_size = ent.size * ffs->hdr.block_size;
if (act_size)
*act_size = ent.actual;
if (ecc)
*ecc = ((ent.user.datainteg & FFS_ENRY_INTEG_ECC) != 0);
if (name) {
n = malloc(PART_NAME_MAX + 1);
if (!n)
return FLASH_ERR_MALLOC_FAILED;
memset(n, 0, PART_NAME_MAX + 1);
strncpy(n, ent.name, PART_NAME_MAX);
*name = n;
}
return 0;
}
/*
* There are quite a few ways one might consider two ffs_handles to be the
* same. For the purposes of this function we are trying to detect a fairly
* specific scenario:
* Consecutive calls to ffs_next_side() may succeed but have gone circular.
* It is possible that the OTHER_SIDE partition in one TOC actually points
* back to the TOC to first ffs_handle.
* This function compares for this case, therefore the requirements are
* simple, the underlying blocklevel_devices must be the same along with
* the toc_offset and the max_size.
*/
bool ffs_equal(struct ffs_handle *one, struct ffs_handle *two)
{
return (!one && !two) || (one && two && one->bl == two->bl
&& one->toc_offset == two->toc_offset
&& one->max_size == two->max_size);
}
int ffs_next_side(struct ffs_handle *ffs, struct ffs_handle **new_ffs,
bool mark_ecc)
{
int rc;
uint32_t index, offset, max_size;
if (!ffs || !new_ffs)
return FLASH_ERR_PARM_ERROR;
*new_ffs = NULL;
rc = ffs_lookup_part(ffs, "OTHER_SIDE", &index);
if (rc)
return rc;
rc = ffs_part_info(ffs, index, NULL, &offset, &max_size, NULL, NULL);
if (rc)
return rc;
return ffs_init(offset, max_size, ffs->bl, new_ffs, mark_ecc);
}
int ffs_update_act_size(struct ffs_handle *ffs, uint32_t part_idx,
uint32_t act_size)
{
struct ffs_entry *ent;
uint32_t offset;
if (part_idx >= ffs->hdr.entry_count) {
FL_DBG("FFS: Entry out of bound\n");
return FFS_ERR_PART_NOT_FOUND;
}
ent = ffs_get_part(ffs, part_idx, &offset);
if (!ent) {
FL_DBG("FFS: Entry not found\n");
return FFS_ERR_PART_NOT_FOUND;
}
FL_DBG("FFS: part index %d at offset 0x%08x\n",
part_idx, offset);
/*
* NOTE: We are accessing the unconverted ffs_entry from the PNOR here
* (since we are going to write it back) so we need to be endian safe.
*/
if (ent->actual == cpu_to_be32(act_size)) {
FL_DBG("FFS: ent->actual alrady matches: 0x%08x==0x%08x\n",
cpu_to_be32(act_size), ent->actual);
return 0;
}
ent->actual = cpu_to_be32(act_size);
ent->checksum = ffs_checksum(ent, FFS_ENTRY_SIZE_CSUM);
return blocklevel_smart_write(ffs->bl, offset, ent, FFS_ENTRY_SIZE);
}