aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libc/include/errno.h2
-rw-r--r--libflash/blocklevel.c178
-rw-r--r--libflash/blocklevel.h16
-rw-r--r--libflash/errors.h35
-rw-r--r--libflash/libflash.h31
-rw-r--r--libflash/test/Makefile.check4
-rw-r--r--libflash/test/test-blocklevel.c183
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;
+}