/* 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 #include #include #include #ifndef __SKIBOOT__ #include #include #else static void *calloc(size_t num, size_t size) { void *ptr = malloc(num * size); if (ptr) memset(ptr, 0, num * size); return ptr; } #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; strncpy(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)); }