857 lines
21 KiB
C
857 lines
21 KiB
C
|
/* 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 "ffs.h"
|
||
|
|
||
|
#define __unused __attribute__((unused))
|
||
|
#define HDR_ENTRIES_NUM 30
|
||
|
|
||
|
struct ffs_handle {
|
||
|
struct ffs_hdr hdr; /* Converted header */
|
||
|
uint32_t toc_offset;
|
||
|
uint32_t max_size;
|
||
|
/* The converted header knows how big this is */
|
||
|
struct __ffs_hdr *cache;
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/* Helper functions for typesafety and size safety */
|
||
|
static uint32_t ffs_hdr_checksum(struct __ffs_hdr *hdr)
|
||
|
{
|
||
|
return ffs_checksum(hdr, sizeof(struct __ffs_hdr));
|
||
|
}
|
||
|
|
||
|
static uint32_t ffs_entry_checksum(struct __ffs_entry *ent)
|
||
|
{
|
||
|
return ffs_checksum(ent, sizeof(struct __ffs_entry));
|
||
|
}
|
||
|
|
||
|
static size_t ffs_hdr_raw_size(int num_entries)
|
||
|
{
|
||
|
return sizeof(struct __ffs_hdr) + num_entries * sizeof(struct __ffs_entry);
|
||
|
}
|
||
|
|
||
|
static int ffs_num_entries(struct ffs_hdr *hdr)
|
||
|
{
|
||
|
if (hdr->count == 0)
|
||
|
FL_DBG("%s returned zero!\n", __func__);
|
||
|
return hdr->count;
|
||
|
}
|
||
|
|
||
|
static int ffs_check_convert_header(struct ffs_hdr *dst, struct __ffs_hdr *src)
|
||
|
{
|
||
|
if (be32_to_cpu(src->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_hdr_checksum(src) != 0)
|
||
|
return FFS_ERR_BAD_CKSUM;
|
||
|
if (be32_to_cpu(src->entry_size) != sizeof(struct __ffs_entry))
|
||
|
return FFS_ERR_BAD_SIZE;
|
||
|
if ((be32_to_cpu(src->entry_size) * be32_to_cpu(src->entry_count)) >
|
||
|
(be32_to_cpu(src->block_size) * be32_to_cpu(src->size)))
|
||
|
return FLASH_ERR_PARM_ERROR;
|
||
|
|
||
|
dst->block_size = be32_to_cpu(src->block_size);
|
||
|
dst->size = be32_to_cpu(src->size) * dst->block_size;
|
||
|
dst->block_count = be32_to_cpu(src->block_count);
|
||
|
dst->entries_size = be32_to_cpu(src->entry_count);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ffs_entry_user_to_flash(struct ffs_hdr *hdr __unused,
|
||
|
struct __ffs_entry_user *dst, struct ffs_entry_user *src)
|
||
|
{
|
||
|
memset(dst, 0, sizeof(struct __ffs_entry_user));
|
||
|
dst->datainteg = cpu_to_be16(src->datainteg);
|
||
|
dst->vercheck = src->vercheck;
|
||
|
dst->miscflags = src->miscflags;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ffs_entry_user_to_cpu(struct ffs_hdr *hdr __unused,
|
||
|
struct ffs_entry_user *dst, struct __ffs_entry_user *src)
|
||
|
{
|
||
|
memset(dst, 0, sizeof(struct ffs_entry_user));
|
||
|
dst->datainteg = be16_to_cpu(src->datainteg);
|
||
|
dst->vercheck = src->vercheck;
|
||
|
dst->miscflags = src->miscflags;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ffs_entry_to_flash(struct ffs_hdr *hdr,
|
||
|
struct __ffs_entry *dst, struct ffs_entry *src)
|
||
|
{
|
||
|
int rc, index;
|
||
|
|
||
|
if (!hdr || !dst || !src)
|
||
|
return -1;
|
||
|
|
||
|
for (index = 0; index < hdr->count && hdr->entries[index] != src; index++);
|
||
|
|
||
|
if (index == hdr->count)
|
||
|
return FFS_ERR_PART_NOT_FOUND;
|
||
|
index++; /* On flash indexes start at 1 */
|
||
|
/*
|
||
|
* So that the checksum gets calculated correctly at least the
|
||
|
* dst->checksum must be zero before calling ffs_entry_checksum()
|
||
|
* memset()ting the entire struct to zero is probably wise as it
|
||
|
* appears the reserved fields are always zero.
|
||
|
*/
|
||
|
memset(dst, 0, sizeof(*dst));
|
||
|
|
||
|
memcpy(dst->name, src->name, sizeof(dst->name));
|
||
|
dst->name[FFS_PART_NAME_MAX] = '\0';
|
||
|
dst->base = cpu_to_be32(src->base / hdr->block_size);
|
||
|
dst->size = cpu_to_be32(src->size / hdr->block_size);
|
||
|
dst->pid = cpu_to_be32(src->pid);
|
||
|
dst->id = cpu_to_be32(index);
|
||
|
dst->type = cpu_to_be32(src->type); /* TODO: Check that it is valid? */
|
||
|
dst->flags = cpu_to_be32(src->flags);
|
||
|
dst->actual = cpu_to_be32(src->actual);
|
||
|
rc = ffs_entry_user_to_flash(hdr, &dst->user, &src->user);
|
||
|
dst->checksum = ffs_entry_checksum(dst);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int ffs_entry_to_cpu(struct ffs_hdr *hdr,
|
||
|
struct ffs_entry *dst, struct __ffs_entry *src)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
if (ffs_entry_checksum(src) != 0)
|
||
|
return FFS_ERR_BAD_CKSUM;
|
||
|
|
||
|
memcpy(dst->name, src->name, sizeof(dst->name));
|
||
|
dst->name[FFS_PART_NAME_MAX] = '\0';
|
||
|
dst->base = be32_to_cpu(src->base) * hdr->block_size;
|
||
|
dst->size = be32_to_cpu(src->size) * hdr->block_size;
|
||
|
dst->actual = be32_to_cpu(src->actual);
|
||
|
dst->pid = be32_to_cpu(src->pid);
|
||
|
dst->type = be32_to_cpu(src->type); /* TODO: Check that it is valid? */
|
||
|
dst->flags = be32_to_cpu(src->flags);
|
||
|
rc = ffs_entry_user_to_cpu(hdr, &dst->user, &src->user);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
char *ffs_entry_user_to_string(struct ffs_entry_user *user)
|
||
|
{
|
||
|
char *ret;
|
||
|
|
||
|
if (!user)
|
||
|
return NULL;
|
||
|
|
||
|
ret = strdup("----------");
|
||
|
if (!ret)
|
||
|
return NULL;
|
||
|
|
||
|
if (user->datainteg & FFS_ENRY_INTEG_ECC)
|
||
|
ret[0] = 'E';
|
||
|
|
||
|
if (user->vercheck & FFS_VERCHECK_SHA512V)
|
||
|
ret[1] = 'L';
|
||
|
|
||
|
if (user->vercheck & FFS_VERCHECK_SHA512EC)
|
||
|
ret[2] = 'I';
|
||
|
|
||
|
if (user->miscflags & FFS_MISCFLAGS_PRESERVED)
|
||
|
ret[3] = 'P';
|
||
|
|
||
|
if (user->miscflags & FFS_MISCFLAGS_READONLY)
|
||
|
ret[4] = 'R';
|
||
|
|
||
|
if (user->miscflags & FFS_MISCFLAGS_BACKUP)
|
||
|
ret[5] = 'B';
|
||
|
|
||
|
if (user->miscflags & FFS_MISCFLAGS_REPROVISION)
|
||
|
ret[6] = 'F';
|
||
|
|
||
|
if (user->miscflags & FFS_MISCFLAGS_GOLDEN)
|
||
|
ret[7] = 'G';
|
||
|
|
||
|
if (user->miscflags & FFS_MISCFLAGS_CLEARECC)
|
||
|
ret[8] = 'C';
|
||
|
|
||
|
if (user->miscflags & FFS_MISCFLAGS_VOLATILE)
|
||
|
ret[9] = 'V';
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int ffs_string_to_entry_user(const char *flags, int nflags,
|
||
|
struct ffs_entry_user *user)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if (!user || !flags)
|
||
|
return FLASH_ERR_PARM_ERROR;
|
||
|
|
||
|
memset(user, 0, sizeof(struct ffs_entry_user));
|
||
|
for (i = 0; i < nflags; i++) {
|
||
|
switch (flags[i]) {
|
||
|
case 'E':
|
||
|
user->datainteg |= FFS_ENRY_INTEG_ECC;
|
||
|
break;
|
||
|
case 'L':
|
||
|
user->vercheck |= FFS_VERCHECK_SHA512V;
|
||
|
break;
|
||
|
case 'I':
|
||
|
user->vercheck |= FFS_VERCHECK_SHA512EC;
|
||
|
break;
|
||
|
case 'P':
|
||
|
user->miscflags |= FFS_MISCFLAGS_PRESERVED;
|
||
|
break;
|
||
|
case 'R':
|
||
|
user->miscflags |= FFS_MISCFLAGS_READONLY;
|
||
|
break;
|
||
|
case 'B':
|
||
|
user->miscflags |= FFS_MISCFLAGS_BACKUP;
|
||
|
break;
|
||
|
case 'F':
|
||
|
user->miscflags |= FFS_MISCFLAGS_REPROVISION;
|
||
|
break;
|
||
|
case 'G':
|
||
|
user->miscflags |= FFS_MISCFLAGS_GOLDEN;
|
||
|
break;
|
||
|
case 'C':
|
||
|
user->miscflags |= FFS_MISCFLAGS_CLEARECC;
|
||
|
break;
|
||
|
case 'V':
|
||
|
user->miscflags |= FFS_MISCFLAGS_VOLATILE;
|
||
|
break;
|
||
|
default:
|
||
|
FL_DBG("Unknown flag '%c'\n", flags[i]);
|
||
|
return FLASH_ERR_PARM_ERROR;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
bool has_flag(struct ffs_entry *ent, uint16_t flag)
|
||
|
{
|
||
|
return ((ent->user.miscflags & flag) != 0);
|
||
|
}
|
||
|
|
||
|
static struct ffs_entry *__ffs_entry_get(struct ffs_handle *ffs, uint32_t index)
|
||
|
{
|
||
|
if (index >= ffs->hdr.count)
|
||
|
return NULL;
|
||
|
return ffs->hdr.entries[index];
|
||
|
}
|
||
|
|
||
|
struct ffs_entry *ffs_entry_get(struct ffs_handle *ffs, uint32_t index)
|
||
|
{
|
||
|
struct ffs_entry *ret = __ffs_entry_get(ffs, index);
|
||
|
if (ret)
|
||
|
ret->ref++;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
struct ffs_entry *ffs_entry_put(struct ffs_entry *ent)
|
||
|
{
|
||
|
if (!ent)
|
||
|
return NULL;
|
||
|
|
||
|
ent->ref--;
|
||
|
if (ent->ref == 0) {
|
||
|
free(ent);
|
||
|
ent = NULL;
|
||
|
}
|
||
|
|
||
|
return ent;
|
||
|
}
|
||
|
|
||
|
bool has_ecc(struct ffs_entry *ent)
|
||
|
{
|
||
|
return ((ent->user.datainteg & FFS_ENRY_INTEG_ECC) != 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 blank_hdr;
|
||
|
struct __ffs_hdr raw_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, &raw_hdr, sizeof(raw_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, &raw_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 = calloc(1, sizeof(*f));
|
||
|
if (!f)
|
||
|
return FLASH_ERR_MALLOC_FAILED;
|
||
|
|
||
|
f->toc_offset = offset;
|
||
|
f->max_size = max_size;
|
||
|
f->bl = bl;
|
||
|
|
||
|
/* Convert and check flash header */
|
||
|
rc = ffs_check_convert_header(&f->hdr, &raw_hdr);
|
||
|
if (rc) {
|
||
|
FL_INF("FFS: Flash header not found. Code: %d\n", rc);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* Check header is sane */
|
||
|
if ((f->hdr.block_count * f->hdr.block_size) > max_size) {
|
||
|
rc = FLASH_ERR_PARM_ERROR;
|
||
|
FL_ERR("FFS: Flash header exceeds max flash size\n");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
f->hdr.entries = calloc(f->hdr.entries_size, sizeof(struct ffs_entry *));
|
||
|
|
||
|
/*
|
||
|
* Grab the entire partition header
|
||
|
*/
|
||
|
/* Check for overflow or a silly size */
|
||
|
if (!f->hdr.size || f->hdr.size % f->hdr.block_size != 0) {
|
||
|
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->hdr.size);
|
||
|
|
||
|
/* Allocate cache */
|
||
|
f->cache = malloc(f->hdr.size);
|
||
|
if (!f->cache) {
|
||
|
rc = FLASH_ERR_MALLOC_FAILED;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* Read the cached map */
|
||
|
rc = blocklevel_read(bl, offset, f->cache, f->hdr.size);
|
||
|
if (rc) {
|
||
|
FL_ERR("FFS: Error %d reading flash partition map\n", rc);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < f->hdr.entries_size; i++) {
|
||
|
struct ffs_entry *ent = calloc(1, sizeof(struct ffs_entry));
|
||
|
if (!ent) {
|
||
|
rc = FLASH_ERR_MALLOC_FAILED;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
f->hdr.entries[f->hdr.count++] = ent;
|
||
|
ent->ref = 1;
|
||
|
rc = ffs_entry_to_cpu(&f->hdr, ent, &f->cache->entries[i]);
|
||
|
if (rc) {
|
||
|
FL_DBG("FFS: Failed checksum for partition %s\n",
|
||
|
f->cache->entries[i].name);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (mark_ecc && has_ecc(ent)) {
|
||
|
rc = blocklevel_ecc_protect(bl, ent->base, ent->size);
|
||
|
if (rc) {
|
||
|
FL_ERR("Failed to blocklevel_ecc_protect(0x%08x, 0x%08x)\n",
|
||
|
ent->base, ent->size);
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
if (rc == 0)
|
||
|
*ffs = f;
|
||
|
else
|
||
|
ffs_close(f);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static void __hdr_free(struct ffs_hdr *hdr)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if (!hdr)
|
||
|
return;
|
||
|
|
||
|
for (i = 0; i < hdr->count; i++)
|
||
|
ffs_entry_put(hdr->entries[i]);
|
||
|
free(hdr->entries);
|
||
|
}
|
||
|
|
||
|
void ffs_hdr_free(struct ffs_hdr *hdr)
|
||
|
{
|
||
|
__hdr_free(hdr);
|
||
|
free(hdr);
|
||
|
}
|
||
|
|
||
|
void ffs_close(struct ffs_handle *ffs)
|
||
|
{
|
||
|
__hdr_free(&ffs->hdr);
|
||
|
|
||
|
if (ffs->cache)
|
||
|
free(ffs->cache);
|
||
|
|
||
|
free(ffs);
|
||
|
}
|
||
|
|
||
|
int ffs_lookup_part(struct ffs_handle *ffs, const char *name,
|
||
|
uint32_t *part_idx)
|
||
|
{
|
||
|
struct ffs_entry **ents = ffs->hdr.entries;
|
||
|
int i;
|
||
|
|
||
|
for (i = 0;
|
||
|
i < ffs->hdr.count &&
|
||
|
strncmp(name, ents[i]->name, FFS_PART_NAME_MAX);
|
||
|
i++);
|
||
|
|
||
|
if (i == ffs->hdr.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 *ent;
|
||
|
char *n;
|
||
|
|
||
|
ent = __ffs_entry_get(ffs, part_idx);
|
||
|
if (!ent)
|
||
|
return FFS_ERR_PART_NOT_FOUND;
|
||
|
|
||
|
if (start)
|
||
|
*start = ent->base;
|
||
|
if (total_size)
|
||
|
*total_size = ent->size;
|
||
|
if (act_size)
|
||
|
*act_size = ent->actual;
|
||
|
if (ecc)
|
||
|
*ecc = has_ecc(ent);
|
||
|
|
||
|
if (name) {
|
||
|
n = calloc(1, FFS_PART_NAME_MAX + 1);
|
||
|
if (!n)
|
||
|
return FLASH_ERR_MALLOC_FAILED;
|
||
|
memcpy(n, ent->name, FFS_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_entry_add(struct ffs_hdr *hdr, struct ffs_entry *entry)
|
||
|
{
|
||
|
const char *smallest_name;
|
||
|
uint32_t smallest_base, toc_base;
|
||
|
int i;
|
||
|
|
||
|
FL_DBG("LIBFFS: Adding '%s' at 0x%08x..0x%08x\n",
|
||
|
entry->name, entry->base, entry->base + entry->size);
|
||
|
|
||
|
if (hdr->count == 0) {
|
||
|
FL_DBG("LIBFFS: Adding an entry to an empty header\n");
|
||
|
hdr->entries[hdr->count++] = entry;
|
||
|
}
|
||
|
if (entry->base + entry->size > hdr->block_size * hdr->block_count)
|
||
|
return FFS_ERR_BAD_PART_SIZE;
|
||
|
|
||
|
smallest_base = entry->base;
|
||
|
smallest_name = entry->name;
|
||
|
toc_base = 0;
|
||
|
/*
|
||
|
* TODO: This may have assumed entries was sorted
|
||
|
*/
|
||
|
for (i = 0; i < hdr->count; i++) {
|
||
|
struct ffs_entry *ent = hdr->entries[i];
|
||
|
|
||
|
/* Don't allow same names to differ only by case */
|
||
|
if (strncasecmp(entry->name, ent->name, FFS_PART_NAME_MAX) == 0)
|
||
|
return FFS_ERR_BAD_PART_NAME;
|
||
|
|
||
|
if (entry->base >= ent->base && entry->base < ent->base + ent->size)
|
||
|
return FFS_ERR_BAD_PART_BASE;
|
||
|
|
||
|
if (entry->base + entry->size > ent->base &&
|
||
|
entry->base + entry->size < ent->base + ent->size)
|
||
|
return FFS_ERR_BAD_PART_SIZE;
|
||
|
|
||
|
if (entry->actual > entry->size)
|
||
|
return FFS_ERR_BAD_PART_SIZE;
|
||
|
|
||
|
if (entry->pid != FFS_PID_TOPLEVEL)
|
||
|
return FFS_ERR_BAD_PART_PID;
|
||
|
|
||
|
/* First partition is the partition table */
|
||
|
if (i == 0) {
|
||
|
toc_base = ent->base;
|
||
|
} else {
|
||
|
/*
|
||
|
* We're looking for the partition directly
|
||
|
* after the toc to make sure we don't
|
||
|
* overflow onto it.
|
||
|
*/
|
||
|
if (ent->base < smallest_base && ent->base > toc_base) {
|
||
|
smallest_base = ent->base;
|
||
|
smallest_name = ent->name;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/* If the smallest base is before the TOC, don't worry */
|
||
|
if (smallest_base > toc_base && (hdr->count + 1) * sizeof(struct __ffs_entry) +
|
||
|
sizeof(struct __ffs_hdr) + toc_base > smallest_base) {
|
||
|
fprintf(stderr, "Adding partition '%s' would cause partition '%s' at "
|
||
|
"0x%08x to overlap with the header\n", entry->name, smallest_name,
|
||
|
smallest_base);
|
||
|
return FFS_ERR_BAD_PART_BASE;
|
||
|
}
|
||
|
|
||
|
if (hdr->count == hdr->entries_size) {
|
||
|
struct ffs_entry **old = hdr->entries;
|
||
|
|
||
|
hdr->entries = realloc(hdr->entries,
|
||
|
(HDR_ENTRIES_NUM + hdr->entries_size) * sizeof(struct ffs_entry *));
|
||
|
if (!hdr->entries) {
|
||
|
hdr->entries = old;
|
||
|
return FLASH_ERR_MALLOC_FAILED;
|
||
|
}
|
||
|
hdr->entries_size += HDR_ENTRIES_NUM;
|
||
|
}
|
||
|
entry->ref++;
|
||
|
hdr->entries[hdr->count++] = entry;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int ffs_hdr_finalise(struct blocklevel_device *bl, struct ffs_hdr *hdr)
|
||
|
{
|
||
|
int num_entries, i, rc = 0;
|
||
|
struct __ffs_hdr *real_hdr;
|
||
|
|
||
|
num_entries = ffs_num_entries(hdr);
|
||
|
|
||
|
/* A TOC shouldn't have zero partitions */
|
||
|
if (num_entries == 0)
|
||
|
return FFS_ERR_BAD_SIZE;
|
||
|
|
||
|
real_hdr = malloc(ffs_hdr_raw_size(num_entries));
|
||
|
if (!real_hdr)
|
||
|
return FLASH_ERR_MALLOC_FAILED;
|
||
|
|
||
|
/*
|
||
|
* So that the checksum gets calculated correctly at least the
|
||
|
* real_hdr->checksum must be zero before calling ffs_hdr_checksum()
|
||
|
* memset()ting the entire struct to zero is probably wise as it
|
||
|
* appears the reserved fields are always zero.
|
||
|
*/
|
||
|
memset(real_hdr, 0, sizeof(*real_hdr));
|
||
|
|
||
|
hdr->part->size = ffs_hdr_raw_size(num_entries) + hdr->block_size;
|
||
|
/*
|
||
|
* So actual is in bytes. ffs_entry_to_flash() don't do the
|
||
|
* block_size division that we're relying on
|
||
|
*/
|
||
|
hdr->part->actual = (hdr->part->size / hdr->block_size) * hdr->block_size;
|
||
|
real_hdr->magic = cpu_to_be32(FFS_MAGIC);
|
||
|
real_hdr->version = cpu_to_be32(hdr->version);
|
||
|
real_hdr->size = cpu_to_be32(hdr->part->size / hdr->block_size);
|
||
|
real_hdr->entry_size = cpu_to_be32(sizeof(struct __ffs_entry));
|
||
|
real_hdr->entry_count = cpu_to_be32(num_entries);
|
||
|
real_hdr->block_size = cpu_to_be32(hdr->block_size);
|
||
|
real_hdr->block_count = cpu_to_be32(hdr->block_count);
|
||
|
real_hdr->checksum = ffs_hdr_checksum(real_hdr);
|
||
|
|
||
|
for (i = 0; i < hdr->count; i++) {
|
||
|
rc = ffs_entry_to_flash(hdr, real_hdr->entries + i, hdr->entries[i]);
|
||
|
if (rc) {
|
||
|
fprintf(stderr, "Couldn't format all entries for new TOC\n");
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Don't really care if this fails */
|
||
|
blocklevel_erase(bl, hdr->part->base, hdr->size);
|
||
|
rc = blocklevel_write(bl, hdr->part->base, real_hdr,
|
||
|
ffs_hdr_raw_size(num_entries));
|
||
|
if (rc)
|
||
|
goto out;
|
||
|
|
||
|
out:
|
||
|
free(real_hdr);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
int ffs_entry_user_set(struct ffs_entry *ent, struct ffs_entry_user *user)
|
||
|
{
|
||
|
if (!ent || !user)
|
||
|
return -1;
|
||
|
|
||
|
/*
|
||
|
* Don't allow the user to specify anything we dont't know about.
|
||
|
* Rationale: This is the library providing access to the FFS structures.
|
||
|
* If the consumer of the library knows more about FFS structures then
|
||
|
* questions need to be asked.
|
||
|
* The other possibility is that they've unknowningly supplied invalid
|
||
|
* flags, we should tell them.
|
||
|
*/
|
||
|
if (user->chip)
|
||
|
return -1;
|
||
|
if (user->compresstype)
|
||
|
return -1;
|
||
|
if (user->datainteg & ~(FFS_ENRY_INTEG_ECC))
|
||
|
return -1;
|
||
|
if (user->vercheck & ~(FFS_VERCHECK_SHA512V | FFS_VERCHECK_SHA512EC))
|
||
|
return -1;
|
||
|
if (user->miscflags & ~(FFS_MISCFLAGS_PRESERVED | FFS_MISCFLAGS_BACKUP |
|
||
|
FFS_MISCFLAGS_READONLY | FFS_MISCFLAGS_REPROVISION |
|
||
|
FFS_MISCFLAGS_VOLATILE | FFS_MISCFLAGS_GOLDEN |
|
||
|
FFS_MISCFLAGS_CLEARECC))
|
||
|
return -1;
|
||
|
|
||
|
memcpy(&ent->user, user, sizeof(*user));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
struct ffs_entry_user ffs_entry_user_get(struct ffs_entry *ent)
|
||
|
{
|
||
|
struct ffs_entry_user user = { 0 };
|
||
|
|
||
|
if (ent)
|
||
|
memcpy(&user, &ent->user, sizeof(user));
|
||
|
|
||
|
return user;
|
||
|
}
|
||
|
|
||
|
int ffs_entry_new(const char *name, uint32_t base, uint32_t size, struct ffs_entry **r)
|
||
|
{
|
||
|
struct ffs_entry *ret;
|
||
|
|
||
|
ret = calloc(1, sizeof(*ret));
|
||
|
if (!ret)
|
||
|
return FLASH_ERR_MALLOC_FAILED;
|
||
|
|
||
|
strncpy(ret->name, name, FFS_PART_NAME_MAX);
|
||
|
ret->name[FFS_PART_NAME_MAX] = '\0';
|
||
|
ret->base = base;
|
||
|
ret->size = size;
|
||
|
ret->actual = size;
|
||
|
ret->pid = FFS_PID_TOPLEVEL;
|
||
|
ret->type = FFS_TYPE_DATA;
|
||
|
ret->ref = 1;
|
||
|
|
||
|
*r = ret;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int ffs_entry_set_act_size(struct ffs_entry *ent, uint32_t actual_size)
|
||
|
{
|
||
|
if (!ent)
|
||
|
return -1;
|
||
|
|
||
|
if (actual_size > ent->size)
|
||
|
return FFS_ERR_BAD_PART_SIZE;
|
||
|
|
||
|
ent->actual = actual_size;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int ffs_hdr_new(uint32_t block_size, uint32_t block_count,
|
||
|
struct ffs_entry **e, struct ffs_hdr **r)
|
||
|
{
|
||
|
struct ffs_hdr *ret;
|
||
|
struct ffs_entry *part_table;
|
||
|
int rc;
|
||
|
|
||
|
ret = calloc(1, sizeof(*ret));
|
||
|
if (!ret)
|
||
|
return FLASH_ERR_MALLOC_FAILED;
|
||
|
|
||
|
ret->version = FFS_VERSION_1;
|
||
|
ret->block_size = block_size;
|
||
|
ret->block_count = block_count;
|
||
|
ret->entries = calloc(HDR_ENTRIES_NUM, sizeof(struct ffs_entry *));
|
||
|
ret->entries_size = HDR_ENTRIES_NUM;
|
||
|
|
||
|
if (!e || !(*e)) {
|
||
|
/* Don't know how big it will be, ffs_hdr_finalise() will fix */
|
||
|
rc = ffs_entry_new("part", 0, 0, &part_table);
|
||
|
if (rc) {
|
||
|
free(ret);
|
||
|
return rc;
|
||
|
}
|
||
|
if (e)
|
||
|
*e = part_table;
|
||
|
} else {
|
||
|
part_table = *e;
|
||
|
}
|
||
|
|
||
|
/* If the user still holds a ref to e, then inc the refcount */
|
||
|
if (e)
|
||
|
part_table->ref++;
|
||
|
|
||
|
ret->part = part_table;
|
||
|
|
||
|
part_table->pid = FFS_PID_TOPLEVEL;
|
||
|
part_table->type = FFS_TYPE_PARTITION;
|
||
|
part_table->flags = FFS_FLAGS_PROTECTED;
|
||
|
|
||
|
ret->entries[0] = part_table;
|
||
|
ret->count = 1;
|
||
|
|
||
|
*r = ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int ffs_update_act_size(struct ffs_handle *ffs, uint32_t part_idx,
|
||
|
uint32_t act_size)
|
||
|
{
|
||
|
struct ffs_entry *ent;
|
||
|
struct __ffs_entry raw_ent;
|
||
|
uint32_t offset;
|
||
|
int rc;
|
||
|
|
||
|
ent = __ffs_entry_get(ffs, part_idx);
|
||
|
if (!ent) {
|
||
|
FL_DBG("FFS: Entry not found\n");
|
||
|
return FFS_ERR_PART_NOT_FOUND;
|
||
|
}
|
||
|
offset = ffs->toc_offset + ffs_hdr_raw_size(part_idx);
|
||
|
FL_DBG("FFS: part index %d at offset 0x%08x\n",
|
||
|
part_idx, offset);
|
||
|
|
||
|
if (ent->actual == act_size) {
|
||
|
FL_DBG("FFS: ent->actual alrady matches: 0x%08x==0x%08x\n",
|
||
|
act_size, ent->actual);
|
||
|
return 0;
|
||
|
}
|
||
|
ent->actual = act_size;
|
||
|
|
||
|
rc = ffs_entry_to_flash(&ffs->hdr, &raw_ent, ent);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
return blocklevel_smart_write(ffs->bl, offset, &raw_ent, sizeof(struct __ffs_entry));
|
||
|
}
|