diff options
-rw-r--r-- | libc/include/errno.h | 2 | ||||
-rw-r--r-- | libflash/blocklevel.c | 178 | ||||
-rw-r--r-- | libflash/blocklevel.h | 16 | ||||
-rw-r--r-- | libflash/errors.h | 35 | ||||
-rw-r--r-- | libflash/libflash.h | 31 | ||||
-rw-r--r-- | libflash/test/Makefile.check | 4 | ||||
-rw-r--r-- | libflash/test/test-blocklevel.c | 183 |
7 files changed, 413 insertions, 36 deletions
diff --git a/libc/include/errno.h b/libc/include/errno.h index d585934..c2bd987 100644 --- a/libc/include/errno.h +++ b/libc/include/errno.h @@ -21,6 +21,7 @@ extern int errno; #define EPERM 1 /* not permitted */ #define ENOENT 2 /* file or directory not found */ #define EIO 5 /* input/output error */ +#define EBADF 9 /* Bad file number */ #define ENOMEM 12 /* not enough space */ #define EACCES 13 /* permission denied */ #define EFAULT 14 /* bad address */ @@ -30,5 +31,6 @@ extern int errno; #define EINVAL 22 /* invalid argument */ #define EDOM 33 /* math argument out of domain of func */ #define ERANGE 34 /* math result not representable */ +#define ENOSYS 38 /* Function not implemented */ #endif diff --git a/libflash/blocklevel.c b/libflash/blocklevel.c index bc03b9b..002bb8b 100644 --- a/libflash/blocklevel.c +++ b/libflash/blocklevel.c @@ -14,32 +14,119 @@ * limitations under the License. */ +#include <stdlib.h> #include <unistd.h> #include <stdio.h> +#include <errno.h> +#include <string.h> + +#include <libflash/errors.h> -#include <libflash/libflash.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; +} int blocklevel_read(struct blocklevel_device *bl, uint32_t pos, void *buf, uint32_t len) { - if (!bl || !bl->read || !buf) - return -1; + 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; + } - return bl->read(bl, pos, buf, len); + if (!ecc_protected(bl, pos, len)) { + return bl->read(bl, pos, buf, len); + } + + buffer = malloc(ecc_len); + if (!buffer) { + errno = ENOMEM; + return FLASH_ERR_MALLOC_FAILED; + } + + rc = bl->read(bl, pos, buffer, ecc_len); + if (rc) + goto out; + + rc = memcpy_from_ecc(buf, buffer, len); + +out: + free(buffer); + return rc; } int blocklevel_write(struct blocklevel_device *bl, uint32_t pos, const void *buf, uint32_t len) { - if (!bl || !bl->write || !buf) - return -1; + 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; + } + + if (!ecc_protected(bl, pos, len)) { + return bl->write(bl, pos, buf, len); + } - return bl->write(bl, pos, buf, len); + buffer = malloc(ecc_len); + if (!buffer) { + errno = ENOMEM; + return FLASH_ERR_MALLOC_FAILED; + } + + 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: + free(buffer); + return rc; } int blocklevel_erase(struct blocklevel_device *bl, uint32_t pos, uint32_t len) { - if (!bl || !bl->erase) - return -1; + 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) { @@ -56,8 +143,10 @@ int blocklevel_get_info(struct blocklevel_device *bl, const char **name, uint32_ { int rc; - if (!bl || !bl->get_info) - return -1; + if (!bl || !bl->get_info) { + errno = EINVAL; + return FLASH_ERR_PARM_ERROR; + } rc = bl->get_info(bl, name, total_size, erase_granule); @@ -68,3 +157,70 @@ int blocklevel_get_info(struct blocklevel_device *bl, const char **name, uint32_ 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 aswell + */ + 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); +} diff --git a/libflash/blocklevel.h b/libflash/blocklevel.h index a22ecb4..837e67e 100644 --- a/libflash/blocklevel.h +++ b/libflash/blocklevel.h @@ -18,6 +18,17 @@ #include <stdint.h> +struct bl_prot_range { + uint32_t start; + uint32_t len; +}; + +struct blocklevel_range { + struct bl_prot_range *prot; + int n_prot; + int total_prot; +}; + /* * libffs may be used with different backends, all should provide these for * libflash to get the information it needs @@ -34,6 +45,8 @@ struct blocklevel_device { * Keep the erase mask so that blocklevel_erase() can do sanity checking */ uint32_t erase_mask; + + struct blocklevel_range ecc_prot; }; int blocklevel_read(struct blocklevel_device *bl, uint32_t pos, void *buf, uint32_t len); @@ -42,4 +55,7 @@ int blocklevel_erase(struct blocklevel_device *bl, uint32_t pos, uint32_t len); int blocklevel_get_info(struct blocklevel_device *bl, const char **name, uint32_t *total_size, uint32_t *erase_granule); +/* Implemented in software at this level */ +int blocklevel_ecc_protect(struct blocklevel_device *bl, uint32_t start, uint32_t len); + #endif /* __LIBFLASH_BLOCKLEVEL_H */ diff --git a/libflash/errors.h b/libflash/errors.h new file mode 100644 index 0000000..99dcfc2 --- /dev/null +++ b/libflash/errors.h @@ -0,0 +1,35 @@ +/* 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. + */ +#ifndef __LIBFLASH_ERRORS_H +#define __LIBFLASH_ERRORS_H + +#define FLASH_ERR_MALLOC_FAILED 1 +#define FLASH_ERR_CHIP_UNKNOWN 2 +#define FLASH_ERR_PARM_ERROR 3 +#define FLASH_ERR_ERASE_BOUNDARY 4 +#define FLASH_ERR_WREN_TIMEOUT 5 +#define FLASH_ERR_WIP_TIMEOUT 6 +#define FLASH_ERR_BAD_PAGE_SIZE 7 +#define FLASH_ERR_VERIFY_FAILURE 8 +#define FLASH_ERR_4B_NOT_SUPPORTED 9 +#define FLASH_ERR_CTRL_CONFIG_MISMATCH 10 +#define FLASH_ERR_CHIP_ER_NOT_SUPPORTED 11 +#define FLASH_ERR_CTRL_CMD_UNSUPPORTED 12 +#define FLASH_ERR_CTRL_TIMEOUT 13 +#define FLASH_ERR_ECC_INVALID 14 +#define FLASH_ERR_BAD_READ 15 + +#endif /* __LIBFLASH_ERRORS_H */ diff --git a/libflash/libflash.h b/libflash/libflash.h index 30f984d..f3973ab 100644 --- a/libflash/libflash.h +++ b/libflash/libflash.h @@ -20,6 +20,14 @@ #include <stdbool.h> #include <libflash/blocklevel.h> +/* API status/return: + * + * <0 = flash controller errors passed through, + * 0 = success + * >0 = libflash error + */ +#include <libflash/errors.h> + #ifdef __SKIBOOT__ #include <skiboot.h> #define FL_INF(fmt...) do { prlog(PR_INFO, fmt); } while(0) @@ -33,29 +41,6 @@ extern bool libflash_debug; #define FL_ERR(fmt...) do { printf(fmt); } while(0) #endif -/* API status/return: - * - * <0 = flash controller errors passed through, - * 0 = success - * >0 = libflash error - */ - -#define FLASH_ERR_MALLOC_FAILED 1 -#define FLASH_ERR_CHIP_UNKNOWN 2 -#define FLASH_ERR_PARM_ERROR 3 -#define FLASH_ERR_ERASE_BOUNDARY 4 -#define FLASH_ERR_WREN_TIMEOUT 5 -#define FLASH_ERR_WIP_TIMEOUT 6 -#define FLASH_ERR_BAD_PAGE_SIZE 7 -#define FLASH_ERR_VERIFY_FAILURE 8 -#define FLASH_ERR_4B_NOT_SUPPORTED 9 -#define FLASH_ERR_CTRL_CONFIG_MISMATCH 10 -#define FLASH_ERR_CHIP_ER_NOT_SUPPORTED 11 -#define FLASH_ERR_CTRL_CMD_UNSUPPORTED 12 -#define FLASH_ERR_CTRL_TIMEOUT 13 -#define FLASH_ERR_ECC_INVALID 14 -#define FLASH_ERR_BAD_READ 15 - /* Flash chip, opaque */ struct flash_chip; struct spi_flash_ctrl; diff --git a/libflash/test/Makefile.check b/libflash/test/Makefile.check index a852f29..05453b2 100644 --- a/libflash/test/Makefile.check +++ b/libflash/test/Makefile.check @@ -1,5 +1,5 @@ # -*-Makefile-*- -LIBFLASH_TEST := libflash/test/test-flash libflash/test/test-ecc +LIBFLASH_TEST := libflash/test/test-flash libflash/test/test-ecc libflash/test/test-blocklevel LCOV_EXCLUDE += $(LIBFLASH_TEST:%=%.c) @@ -16,7 +16,7 @@ $(LIBFLASH_TEST:%=%-check) : %-check: % libflash/test/stubs.o: libflash/test/stubs.c $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) -g -c -o $@ $<, $<) -$(LIBFLASH_TEST) : libflash/test/stubs.o libflash/libflash.c libflash/ecc.c +$(LIBFLASH_TEST) : libflash/test/stubs.o libflash/libflash.c libflash/ecc.c libflash/blocklevel.c $(LIBFLASH_TEST) : % : %.c $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) -O0 -g -I include -I . -o $@ $< libflash/test/stubs.o, $<) diff --git a/libflash/test/test-blocklevel.c b/libflash/test/test-blocklevel.c new file mode 100644 index 0000000..ef5d9b5 --- /dev/null +++ b/libflash/test/test-blocklevel.c @@ -0,0 +1,183 @@ +/* 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 <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include <libflash/blocklevel.h> + +#include "../ecc.c" +#include "../blocklevel.c" + +#define __unused __attribute__((unused)) + +#define ERR(fmt...) fprintf(stderr, fmt) + +int main(void) +{ + int i; + struct blocklevel_device bl_mem = { 0 }; + struct blocklevel_device *bl = &bl_mem; + + if (blocklevel_ecc_protect(bl, 0, 0x1000)) { + ERR("Failed to blocklevel_ecc_protect!\n"); + return 1; + } + + /* 0x1000 -> 0x3000 should remain unprotected */ + + if (blocklevel_ecc_protect(bl, 0x3000, 0x1000)) { + ERR("Failed to blocklevel_ecc_protect(0x3000, 0x1000)\n"); + return 1; + } + + /* Zero length protection */ + if (!blocklevel_ecc_protect(bl, 0x4000, 0)) { + ERR("Shouldn't have succeeded blocklevel_ecc_protect(0x4000, 0)\n"); + return 1; + } + + /* Minimum creatable size */ + if (blocklevel_ecc_protect(bl, 0x4000, BYTES_PER_ECC)) { + ERR("Failed to blocklevel_ecc_protect(0x4000, BYTES_PER_ECC)\n"); + return 1; + } + + /* Deal with overlapping protections */ + if (!blocklevel_ecc_protect(bl, 0x100, 0x1000)) { + ERR("Shouldn't have succeeded blocklevel_ecc_protect(0x100, 0x1000)\n"); + return 1; + } + + /* Deal with protections greater than max size */ + if (!blocklevel_ecc_protect(bl, 0, 0xFFFFFFFF)) { + ERR("Failed to blocklevel_ecc_protect(0, 0xFFFFFFFF)\n"); + return 1; + } + + if (ecc_protected(bl, 0, 1) != 1) { + ERR("Invaid result for ecc_protected(0, 1)\n"); + return 1; + } + + if (ecc_protected(bl, 0, 0x1000) != 1) { + ERR("Invalid result for ecc_protected(0, 0x1000)\n"); + return 1; + } + + if (ecc_protected(bl, 0x100, 0x100) != 1) { + ERR("Invalid result for ecc_protected(0x0100, 0x100)\n"); + return 1; + } + + if (ecc_protected(bl, 0x1000, 0) != 0) { + ERR("Invalid result for ecc_protected(0x1000, 0)\n"); + return 1; + } + + if (ecc_protected(bl, 0x1000, 0x1000) != 0) { + ERR("Invalid result for ecc_protected(0x1000, 0x1000)\n"); + return 1; + } + + if (ecc_protected(bl, 0x1000, 0x100) != 0) { + ERR("Invalid result for ecc_protected(0x1000, 0x100)\n"); + return 1; + } + + if (ecc_protected(bl, 0x2000, 0) != 0) { + ERR("Invalid result for ecc_protected(0x2000, 0)\n"); + return 1; + } + + if (ecc_protected(bl, 0x4000, 1) != 1) { + ERR("Invalid result for ecc_protected(0x4000, 1)\n"); + return 1; + } + + /* Check for asking for a region with mixed protection */ + if (ecc_protected(bl, 0x100, 0x2000) != -1) { + ERR("Invalid result for ecc_protected(0x100, 0x2000)\n"); + return 1; + } + + /* Test the auto extending of regions */ + if (blocklevel_ecc_protect(bl, 0x5000, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x5000, 0x100)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x5100, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x5100, 0x100)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x5200, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x5200, 0x100)\n"); + return 1; + } + + if (ecc_protected(bl, 0x5120, 0x10) != 1) { + ERR("Invalid result for ecc_protected(0x5120, 0x10)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x4900, 0x100)) { + ERR("Failed to blocklevel_ecc_protected(0x4900, 0x100)\n"); + return 1; + } + + if (ecc_protected(bl, 0x4920, 0x10) != 1) { + ERR("Invalid result for ecc_protected(0x4920, 0x10)\n"); + return 1; + } + + if (!blocklevel_ecc_protect(bl, 0x5290, 0x10)) { + ERR("Shouldn't have been able to blocklevel_ecc_protect(0x5290, 0x10)\n"); + return 1; + } + + /* Test the auto extending of regions */ + if (blocklevel_ecc_protect(bl, 0x6000, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x6000, 0x100)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x6200, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x6200, 0x100)\n"); + return 1; + } + /*This addition should cause this one to merge the other two together*/ + if (blocklevel_ecc_protect(bl, 0x6100, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x6100, 0x100)\n"); + return 1; + } + + /* Check that the region merging works */ + for (i = 0; i < bl->ecc_prot.n_prot - 1; i++) { + if (bl->ecc_prot.prot[i].start + bl->ecc_prot.prot[i].len == bl->ecc_prot.prot[i + 1].start || + bl->ecc_prot.prot[i + 1].start + bl->ecc_prot.prot[i + 1].len == bl->ecc_prot.prot[i].start) { + ERR("Problem with protection range merge code, region starting at 0x%08x for 0x%08x appears " + "to touch region 0x%08x for 0x%08x\n", bl->ecc_prot.prot[i].start, bl->ecc_prot.prot[i].len, + bl->ecc_prot.prot[i + 1].start, bl->ecc_prot.prot[i + 1].len); + return 1; + } + } + + + return 0; +} |