blob: 959119417096e784751b479c0d22c068b90bda64 [file] [log] [blame]
/* 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 <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <libflash/errors.h>
#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);
}