/* Copyright 2013-2015 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 #include #include #include #include "blocklevel.h" #include "ecc.h" #define PROT_REALLOC_NUM 25 /* This function returns tristate values. * 1 - The region is ECC protected * 0 - The region is not ECC protected * -1 - Partially protected */ static int ecc_protected(struct blocklevel_device *bl, uint32_t pos, uint32_t len) { int i; /* Length of 0 is nonsensical so add 1 */ if (len == 0) len = 1; for (i = 0; i < bl->ecc_prot.n_prot; i++) { /* Fits entirely within the range */ if (bl->ecc_prot.prot[i].start <= pos && bl->ecc_prot.prot[i].start + bl->ecc_prot.prot[i].len >= pos + len) return 1; /* * Since we merge regions on inserting we can be sure that a * partial fit means that the non fitting region won't fit in another ecc * region */ if ((bl->ecc_prot.prot[i].start >= pos && bl->ecc_prot.prot[i].start < pos + len) || (bl->ecc_prot.prot[i].start <= pos && bl->ecc_prot.prot[i].start + bl->ecc_prot.prot[i].len > pos)) return -1; } return 0; } static int reacquire(struct blocklevel_device *bl) { if (!bl->keep_alive && bl->reacquire) return bl->reacquire(bl); return 0; } static int release(struct blocklevel_device *bl) { int rc = 0; if (!bl->keep_alive && bl->release) { /* This is the error return path a lot, preserve errno */ int err = errno; rc = bl->release(bl); errno = err; } return rc; } int blocklevel_read(struct blocklevel_device *bl, uint32_t pos, void *buf, uint32_t len) { int rc; struct ecc64 *buffer; uint32_t ecc_len = ecc_buffer_size(len); if (!bl || !bl->read || !buf) { errno = EINVAL; return FLASH_ERR_PARM_ERROR; } rc = reacquire(bl); if (rc) return rc; if (!ecc_protected(bl, pos, len)) { rc = bl->read(bl, pos, buf, len); release(bl); return rc; } buffer = malloc(ecc_len); if (!buffer) { errno = ENOMEM; rc = FLASH_ERR_MALLOC_FAILED; goto out; } rc = bl->read(bl, pos, buffer, ecc_len); if (rc) goto out; if (memcpy_from_ecc(buf, buffer, len)) { errno = EBADF; rc = FLASH_ERR_ECC_INVALID; } out: release(bl); free(buffer); return rc; } int blocklevel_write(struct blocklevel_device *bl, uint32_t pos, const void *buf, uint32_t len) { int rc; struct ecc64 *buffer; uint32_t ecc_len = ecc_buffer_size(len); if (!bl || !bl->write || !buf) { errno = EINVAL; return FLASH_ERR_PARM_ERROR; } rc = reacquire(bl); if (rc) return rc; if (!ecc_protected(bl, pos, len)) { rc = bl->write(bl, pos, buf, len); release(bl); return rc; } buffer = malloc(ecc_len); if (!buffer) { errno = ENOMEM; rc = FLASH_ERR_MALLOC_FAILED; goto out; } if (memcpy_to_ecc(buffer, buf, len)) { errno = EBADF; rc = FLASH_ERR_ECC_INVALID; goto out; } rc = bl->write(bl, pos, buffer, ecc_len); out: release(bl); free(buffer); return rc; } int blocklevel_erase(struct blocklevel_device *bl, uint32_t pos, uint32_t len) { int rc; if (!bl || !bl->erase) { errno = EINVAL; return FLASH_ERR_PARM_ERROR; } /* Programmer may be making a horrible mistake without knowing it */ if (len & bl->erase_mask) { fprintf(stderr, "blocklevel_erase: len (0x%08x) is not erase block (0x%08x) aligned\n", len, bl->erase_mask + 1); return FLASH_ERR_ERASE_BOUNDARY; } rc = reacquire(bl); if (rc) return rc; rc = bl->erase(bl, pos, len); release(bl); return rc; } int blocklevel_get_info(struct blocklevel_device *bl, const char **name, uint32_t *total_size, uint32_t *erase_granule) { int rc; if (!bl || !bl->get_info) { errno = EINVAL; return FLASH_ERR_PARM_ERROR; } rc = reacquire(bl); if (rc) return rc; rc = bl->get_info(bl, name, total_size, erase_granule); /* Check the validity of what we are being told */ if (erase_granule && *erase_granule != bl->erase_mask + 1) fprintf(stderr, "blocklevel_get_info: WARNING: erase_granule (0x%08x) and erase_mask" " (0x%08x) don't match\n", *erase_granule, bl->erase_mask + 1); release(bl); return rc; } /* * Compare flash and memory to determine if: * a) Erase must happen before write * b) Flash and memory are identical * c) Flash can simply be written to * * returns -1 for a * returns 0 for b * returns 1 for c */ static int blocklevel_flashcmp(const void *flash_buf, const void *mem_buf, uint32_t len) { int i, same = true; const uint8_t *f_buf, *m_buf; f_buf = flash_buf; m_buf = mem_buf; for (i = 0; i < len; i++) { if (m_buf[i] & ~f_buf[i]) return -1; if (same && (m_buf[i] != f_buf[i])) same = false; } return same ? 0 : 1; } int blocklevel_smart_write(struct blocklevel_device *bl, uint32_t pos, const void *buf, uint32_t len) { uint32_t erase_size; const void *write_buf = buf; void *write_buf_start = NULL; void *erase_buf; int rc = 0; if (!write_buf || !bl) { errno = EINVAL; return FLASH_ERR_PARM_ERROR; } if (!(bl->flags & WRITE_NEED_ERASE)) return blocklevel_write(bl, pos, buf, len); rc = blocklevel_get_info(bl, NULL, NULL, &erase_size); if (rc) return rc; if (ecc_protected(bl, pos, len)) { len = ecc_buffer_size(len); write_buf_start = malloc(len); if (!write_buf_start) { errno = ENOMEM; return FLASH_ERR_MALLOC_FAILED; } if (memcpy_to_ecc(write_buf_start, buf, ecc_buffer_size_minus_ecc(len))) { free(write_buf_start); errno = EBADF; return FLASH_ERR_ECC_INVALID; } write_buf = write_buf_start; } erase_buf = malloc(erase_size); if (!erase_buf) { errno = ENOMEM; rc = FLASH_ERR_MALLOC_FAILED; goto out_free; } rc = reacquire(bl); if (rc) goto out_free; while (len > 0) { uint32_t erase_block = pos & ~(erase_size - 1); uint32_t block_offset = pos & (erase_size - 1); uint32_t size = erase_size > len ? len : erase_size; int cmp; /* Write crosses an erase boundary, shrink the write to the boundary */ if (erase_size < block_offset + size) { size = erase_size - block_offset; } rc = bl->read(bl, erase_block, erase_buf, erase_size); if (rc) goto out; cmp = blocklevel_flashcmp(erase_buf + block_offset, write_buf, size); if (cmp != 0) { if (cmp == -1) bl->erase(bl, erase_block, erase_size); memcpy(erase_buf + block_offset, write_buf, size); rc = bl->write(bl, erase_block, erase_buf, erase_size); if (rc) goto out; } len -= size; pos += size; write_buf += size; } out: release(bl); out_free: free(write_buf_start); free(erase_buf); return rc; } static int insert_bl_prot_range(struct blocklevel_range *ranges, struct bl_prot_range range) { struct bl_prot_range *new_ranges; struct bl_prot_range *old_ranges = ranges->prot; int i, count = ranges->n_prot; /* Try to merge into an existing range */ for (i = 0; i < count; i++) { if (!(range.start + range.len == old_ranges[i].start || old_ranges[i].start + old_ranges[i].len == range.start)) continue; if (range.start + range.len == old_ranges[i].start) old_ranges[i].start = range.start; old_ranges[i].len += range.len; /* * Check the inserted range isn't wedged between two ranges, if it * is, merge as well */ i++; if (i < count && range.start + range.len == old_ranges[i].start) { old_ranges[i - 1].len += old_ranges[i].len; for (; i + 1 < count; i++) old_ranges[i] = old_ranges[i + 1]; ranges->n_prot--; } return 0; } if (ranges->n_prot == ranges->total_prot) { new_ranges = realloc(ranges->prot, sizeof(range) * ((ranges->n_prot) + PROT_REALLOC_NUM)); if (new_ranges) ranges->total_prot += PROT_REALLOC_NUM; } else { new_ranges = old_ranges; } if (new_ranges) { memcpy(new_ranges + ranges->n_prot, &range, sizeof(range)); ranges->prot = new_ranges; ranges->n_prot++; } return !new_ranges; } int blocklevel_ecc_protect(struct blocklevel_device *bl, uint32_t start, uint32_t len) { /* * Could implement this at hardware level by having an accessor to the * backend in struct blocklevel_device and as a result do nothing at * this level (although probably not for ecc!) */ struct bl_prot_range range = { .start = start, .len = len }; /* * Refuse to add regions that are already protected or are partially * protected */ if (len < BYTES_PER_ECC || ecc_protected(bl, start, len)) return -1; return insert_bl_prot_range(&bl->ecc_prot, range); }