[v2,2/5] libflash/blocklevel: Add blocklevel_smart_erase()

Message ID 20170419041143.12042-3-cyril.bur@au1.ibm.com
State New
Headers show

Commit Message

Cyril Bur April 19, 2017, 4:11 a.m.
With recent changes to flash drivers in linux not all erase blocks are
4K anymore. While most level of the pflash/gard tool stacks were written
to not mind, it turns out there are bugs which means not 4K erase block
backing stores aren't handled all that well. Part of the problem is the
FFS layout that is 4K aligned and with larger block sizes pflash and the
gard tool don't check if their erase commands are erase block aligned -
which they are usually not with 64K erase blocks.

This patch aims to add common functionality to blocklevel so that (at
least) pflash and the gard tool don't need to worry about the problem
anymore.

Signed-off-by: Cyril Bur <cyril.bur@au1.ibm.com>
---
 libflash/blocklevel.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++--
 libflash/blocklevel.h |  10 +++-
 2 files changed, 139 insertions(+), 5 deletions(-)

Patch

diff --git a/libflash/blocklevel.c b/libflash/blocklevel.c
index 79ff00f4..36c7b99f 100644
--- a/libflash/blocklevel.c
+++ b/libflash/blocklevel.c
@@ -1,4 +1,4 @@ 
-/* Copyright 2013-2015 IBM Corp.
+/* Copyright 2013-2017 IBM Corp.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,17 @@ 
  * limitations under the License.
  */
 
+#include <errno.h>
+#include <inttypes.h>
 #include <stdlib.h>
-#include <unistd.h>
 #include <stdio.h>
 #include <stdbool.h>
-#include <errno.h>
 #include <string.h>
-#include <inttypes.h>
+#include <unistd.h>
 
 #include <libflash/errors.h>
 
+#include "libflash.h"
 #include "blocklevel.h"
 #include "ecc.h"
 
@@ -198,6 +199,12 @@  int blocklevel_erase(struct blocklevel_device *bl, uint64_t pos, uint64_t len)
 	}
 
 	/* Programmer may be making a horrible mistake without knowing it */
+	if (pos & bl->erase_mask) {
+		fprintf(stderr, "blocklevel_erase: pos (0x%"PRIx64") is not erase block (0x%08x) aligned\n",
+				pos, bl->erase_mask + 1);
+		return FLASH_ERR_ERASE_BOUNDARY;
+	}
+
 	if (len & bl->erase_mask) {
 		fprintf(stderr, "blocklevel_erase: len (0x%"PRIx64") is not erase block (0x%08x) aligned\n",
 				len, bl->erase_mask + 1);
@@ -270,6 +277,125 @@  static int blocklevel_flashcmp(const void *flash_buf, const void *mem_buf, uint6
 	return same ? 0 : 1;
 }
 
+int blocklevel_smart_erase(struct blocklevel_device *bl, uint64_t pos, uint64_t len)
+{
+	uint64_t block_size;
+	void *erase_buf;
+	int rc;
+
+	if (!bl) {
+		errno = EINVAL;
+		return FLASH_ERR_PARM_ERROR;
+	}
+
+	FL_DBG("%s: pos: 0x%" PRIx64 ", len: 0x%" PRIx64 "\n", __func__, pos, len);
+
+	/* Nothing smart needs to be done, pos and len are aligned */
+	if ((pos & bl->erase_mask) == 0 && (len & bl->erase_mask) == 0) {
+		FL_DBG("No smarts needed, calling blocklevel_erase()\n");
+		return blocklevel_erase(bl, pos, len);
+	}
+
+	block_size = bl->erase_mask + 1;
+	erase_buf = malloc(block_size);
+	if (!erase_buf) {
+		errno = ENOMEM;
+		return FLASH_ERR_MALLOC_FAILED;
+	}
+
+	rc = reacquire(bl);
+	if (rc) {
+		free(erase_buf);
+		return rc;
+	}
+
+	if (pos & bl->erase_mask) {
+		/*
+		 * base_pos and base_len are the values in the first erase
+		 * block that we need to preserve: the region up to pos.
+		 */
+		uint64_t base_pos = pos & ~(bl->erase_mask);
+		uint64_t base_len = pos - base_pos;
+
+		/*
+		 * Read the entire block in case this is the ONLY block we're
+		 * modifying, we may need the end chunk of it later
+		 */
+		rc = bl->read(bl, base_pos, erase_buf, block_size);
+		if (rc)
+			goto out;
+
+		rc = bl->erase(bl, base_pos, block_size);
+		if (rc)
+			goto out;
+
+		rc = bl->write(bl, base_pos, erase_buf, base_len);
+		if (rc)
+			goto out;
+
+		/*
+		 * The requested erase fits entirely into this erase block and
+		 * so we need to write back the chunk at the end of the block
+		 */
+		if (base_pos + base_len + len < base_pos + block_size) {
+			rc = bl->write(bl, pos + len, erase_buf + pos + len,
+					block_size - base_len - len);
+			goto out;
+		}
+
+		pos += block_size - base_len;
+		len -= block_size - base_len;
+	}
+
+	/* Now we should be aligned, best to double check */
+	if (pos & bl->erase_mask) {
+		rc = FLASH_ERR_PARM_ERROR;
+		goto out;
+	}
+
+	if (len & ~(bl->erase_mask)) {
+		rc = bl->erase(bl, pos, len & ~(bl->erase_mask));
+		if (rc)
+			goto out;
+
+		pos += len & ~(bl->erase_mask);
+		len -= len & ~(bl->erase_mask);
+	}
+
+	/* Length should be less than a block now */
+	if (len > block_size) {
+		rc = FLASH_ERR_PARM_ERROR;
+		goto out;
+	}
+
+	if (len & bl->erase_mask) {
+		/*
+		 * top_pos is the first byte that must be preserved and
+		 * top_len is the length from top_pos to the end of the erase
+		 * block: the region that must be preserved
+		 */
+		uint64_t top_pos = pos + len;
+		uint64_t top_len = block_size - len;
+
+		rc = bl->read(bl, top_pos, erase_buf, top_len);
+		if (rc)
+			goto out;
+
+		rc = bl->erase(bl, pos, block_size);
+		if (rc)
+			goto out;
+
+		rc = bl->write(bl, top_pos, erase_buf, top_len);
+		if (rc)
+			goto out;
+	}
+
+out:
+	free(erase_buf);
+	release(bl);
+	return rc;
+}
+
 int blocklevel_smart_write(struct blocklevel_device *bl, uint64_t pos, const void *buf, uint64_t len)
 {
 	uint32_t erase_size;
diff --git a/libflash/blocklevel.h b/libflash/blocklevel.h
index 09f0096a..ba42c83d 100644
--- a/libflash/blocklevel.h
+++ b/libflash/blocklevel.h
@@ -1,4 +1,4 @@ 
-/* Copyright 2013-2015 IBM Corp.
+/* Copyright 2013-2017 IBM Corp.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -75,6 +75,14 @@  int blocklevel_get_info(struct blocklevel_device *bl, const char **name, uint64_
  */
 int blocklevel_smart_write(struct blocklevel_device *bl, uint64_t pos, const void *buf, uint64_t len);
 
+/*
+ * blocklevel_smart_erase() will handle unaligned erases.
+ * blocklevel_erase() expects a erase_granule aligned buffer and the
+ * erase length to be an exact multiple of erase_granule,
+ * blocklevel_smart_erase() solves this requirement by performing a
+ * read erase write under the hood.
+ */
+int blocklevel_smart_erase(struct blocklevel_device *bl, uint64_t pos, uint64_t len);
 /* Implemented in software at this level */
 int blocklevel_ecc_protect(struct blocklevel_device *bl, uint32_t start, uint32_t len);