diff options
author | Cyril Bur <cyril.bur@au1.ibm.com> | 2015-06-23 13:22:09 +1000 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2015-06-23 13:38:42 +1000 |
commit | 199be6b84faeff7ef03032c857766d4a023fbbb4 (patch) | |
tree | 8d03612cfd543eb37bdb8419812314525e1c7c1d | |
parent | 69b152cab30aae6188b590b2df9a6f55932c4408 (diff) | |
download | skiboot-199be6b84faeff7ef03032c857766d4a023fbbb4.zip skiboot-199be6b84faeff7ef03032c857766d4a023fbbb4.tar.gz skiboot-199be6b84faeff7ef03032c857766d4a023fbbb4.tar.bz2 |
libflash/blocklevel: Extend the block level to be able to do ecc
At the moment ECC reads and writes are still being handled by the low level
core of libflash. ECC should be none of libflashes concern, it is primarily
a hardware access backend.
It makes sense for blocklevel to take care of ECC but currently it has no
way of knowing. With some simple modifications (which are rudimentary at
the moment and will need a performance improvement) blocklevel can handle
ECC, and with a little more effort this can be extended to provide read and
write protection in blocklevel.
Reviewed-By: Alistair Popple <alistair@popple.id.au>
Signed-off-by: Cyril Bur <cyril.bur@au1.ibm.com>
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
-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; +} |