| /* 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); |
| } |
| |