/* 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)) 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) { struct ffs_entry *ent; int num_entries = 0; list_for_each(&hdr->entries, ent, list) num_entries++; if (num_entries == 0) FL_DBG("%s returned zero!\n", __func__); return num_entries; } 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); 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 = 1; /* On flash indexes start at 1 */ struct ffs_entry *ent = NULL; if (!hdr || !dst || !src) return -1; list_for_each(&hdr->entries, ent, list) { if (ent == src) break; index++; } if (!ent) return FFS_ERR_PART_NOT_FOUND; 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; } static struct ffs_entry *ffs_get_part(struct ffs_handle *ffs, uint32_t index) { int i = 0; struct ffs_entry *ent = NULL; list_for_each(&ffs->hdr.entries, ent, list) if (i++ == index) return ent; /* Didn't find partition */ return NULL; } 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; list_head_init(&f->hdr.entries); /* 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; } /* * 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 < be32_to_cpu(raw_hdr.entry_count); i++) { struct ffs_entry *ent = calloc(1, sizeof(struct ffs_entry)); if (!ent) { rc = FLASH_ERR_MALLOC_FAILED; goto out; } list_add_tail(&f->hdr.entries, &ent->list); rc = ffs_entry_to_cpu(&f->hdr, ent, &f->cache->entries[i]); if (rc) 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; } void ffs_close(struct ffs_handle *ffs) { struct ffs_entry *ent, *next; list_for_each_safe(&ffs->hdr.entries, ent, next, list) { list_del(&ent->list); free(ent); } if (ffs->cache) free(ffs->cache); free(ffs); } int ffs_lookup_part(struct ffs_handle *ffs, const char *name, uint32_t *part_idx) { int i = 0; struct ffs_entry *ent = NULL; list_for_each(&ffs->hdr.entries, ent, list) { if (strncmp(name, ent->name, sizeof(ent->name)) == 0) break; i++; } if (part_idx) *part_idx = i; return ent ? 0 : FFS_ERR_PART_NOT_FOUND; } 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_get_part(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); } static int __ffs_entry_add(struct ffs_hdr *hdr, struct ffs_entry *entry) { struct ffs_entry *ent; uint32_t smallest_base; const char *smallest_name; int count = 0; assert(!list_empty(&hdr->entries)); 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; /* Input validate first to a) fail early b) do it all together */ list_for_each(&hdr->entries, ent, list) { /* 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; /* Skip the first partition as it IS the partition table */ if (ent->base < smallest_base && count > 0) { smallest_base = ent->base; smallest_name = ent->name; } count++; } if (count * sizeof(struct __ffs_entry) + sizeof(struct __ffs_hdr) > 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; } /* * A header can't have zero entries. Was asserted. */ list_for_each(&hdr->entries, ent, list) if (entry->base < ent->base) break; if (ent == list_top(&hdr->entries, struct ffs_entry, list)) { /* * This should never happen because the partition entry * should ALWAYS be here */ fprintf(stderr, "Warning: replacing first entry in FFS header\n"); list_add(&hdr->entries, &entry->list); } else if (!ent) { list_add_tail(&hdr->entries, &entry->list); } else { list_add_before(&hdr->entries, &entry->list, &ent->list); } return 0; } int ffs_entry_add(struct ffs_hdr *hdr, struct ffs_entry *entry, unsigned int side) { int rc; /* * Refuse to add anything after BACKUP_PART has been added, not * sure why this is needed anymore */ if (hdr->backup) return FLASH_ERR_PARM_ERROR; if (side == 0) { /* Sideless... */ rc = __ffs_entry_add(hdr, entry); if (!rc && hdr->side) { struct ffs_entry *other_ent; /* * A rather sneaky copy is hidden here. * It doesn't make sense for a consumer to be aware that structures * must be duplicated. The entries list in the header could have * been an array of pointers and no copy would have been required. */ other_ent = calloc(1, sizeof (struct ffs_entry)); if (!other_ent) /* TODO Remove the added entry from side 1 */ return FLASH_ERR_PARM_ERROR; memcpy(other_ent, entry, sizeof(struct ffs_entry)); rc = __ffs_entry_add(hdr->side, other_ent); if (rc) /* TODO Remove the added entry from side 1 */ free(other_ent); } } else if (side == 1) { rc = __ffs_entry_add(hdr, entry); } else if (side == 2 && hdr->side) { rc = __ffs_entry_add(hdr->side, entry); } else { rc = FLASH_ERR_PARM_ERROR; } return rc; } /* This should be done last! */ int ffs_hdr_create_backup(struct ffs_hdr *hdr) { struct ffs_entry *ent; struct ffs_entry *backup; int rc = 0; ent = list_tail(&hdr->entries, struct ffs_entry, list); if (!ent) { return FLASH_ERR_PARM_ERROR; } rc = ffs_entry_new("BACKUP_PART", hdr->base + (hdr->block_size * (hdr->block_count - 1 )) - hdr->size, hdr->size, &backup); if (rc) return rc; rc = __ffs_entry_add(hdr, backup); if (rc) { free(backup); return rc; } hdr->backup = backup; /* Do we try to roll back completely if that fails or leave what we've added? */ if (hdr->side && hdr->base == 0) rc = ffs_hdr_create_backup(hdr->side); return rc; } int ffs_hdr_add_side(struct ffs_hdr *hdr) { int rc; /* Only a second side for now */ if (hdr->side) return FLASH_ERR_PARM_ERROR; rc = ffs_hdr_new(hdr->size, hdr->block_size, hdr->block_count, &hdr->side); if (rc) return rc; hdr->side->base = hdr->block_size * hdr->block_count; /* Sigh */ hdr->side->side = hdr; return rc; } int ffs_hdr_finalise(struct blocklevel_device *bl, struct ffs_hdr *hdr) { int num_entries, i, rc = 0; struct ffs_entry *ent; 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; if (hdr->side) { struct ffs_entry *other_side; /* TODO: Change the hard coded 0x8000 */ rc = ffs_entry_new("OTHER_SIDE", hdr->side->base, 0x8000, &other_side); if (rc) return rc; list_add_tail(&hdr->entries, &other_side->list); num_entries++; } real_hdr = malloc(ffs_hdr_raw_size(num_entries)); if (!real_hdr) return FLASH_ERR_MALLOC_FAILED; real_hdr->magic = cpu_to_be32(FFS_MAGIC); real_hdr->version = cpu_to_be32(hdr->version); real_hdr->size = cpu_to_be32(hdr->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); i = 0; list_for_each(&hdr->entries, ent, list) { rc = ffs_entry_to_flash(hdr, real_hdr->entries + i, ent); if (rc) { fprintf(stderr, "Couldn't format all entries for new TOC\n"); goto out; } i++; } /* Don't really care if this fails */ blocklevel_erase(bl, hdr->base, hdr->size); rc = blocklevel_write(bl, hdr->base, real_hdr, ffs_hdr_raw_size(num_entries)); if (rc) goto out; if (hdr->backup) { fprintf(stderr, "Actually writing backup part @ 0x%08x\n", hdr->backup->base); blocklevel_erase(bl, hdr->backup->base, hdr->size); rc = blocklevel_write(bl, hdr->backup->base, real_hdr, ffs_hdr_raw_size(num_entries)); } if (rc) goto out; if (hdr->side && hdr->base == 0) rc = ffs_hdr_finalise(bl, hdr->side); 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)) return -1; memcpy(&ent->user, user, sizeof(*user)); return 0; } 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; *r = ret; return 0; } int ffs_hdr_new(uint32_t size, uint32_t block_size, uint32_t block_count, struct ffs_hdr **r) { struct ffs_hdr *ret; struct ffs_entry *part_table; int rc; if (size % block_size || size > block_size * block_count) return FFS_ERR_BAD_SIZE; ret = calloc(1, sizeof(*ret)); if (!ret) return FLASH_ERR_MALLOC_FAILED; ret->version = FFS_VERSION_1; ret->size = size; ret->block_size = block_size; ret->block_count = block_count; list_head_init(&ret->entries); rc = ffs_entry_new("part", 0, size, &part_table); if (rc) { free(ret); return rc; } part_table->pid = FFS_PID_TOPLEVEL; part_table->type = FFS_TYPE_PARTITION; part_table->flags = FFS_FLAGS_PROTECTED; list_add(&ret->entries, &part_table->list); *r = ret; return 0; } int ffs_hdr_free(struct ffs_hdr *hdr) { struct ffs_entry *ent, *next; printf("Freeing hdr\n"); list_for_each_safe(&hdr->entries, ent, next, list) { list_del(&ent->list); free(ent); } if (hdr->side) { hdr->side->side = NULL; ffs_hdr_free(hdr->side); } free(hdr); 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_get_part(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)); }