[nandpart][bad-block] Implement bad-block protocol.
bad-block protocol implementation is specific to the methodology used
in the AML u-boot bootloader. We preserve the same semantics so as to
allow the bootloader to continue to read the system partitions even
after additional bad blocks have been grown.
Change-Id: I37954fccf67822459f9c7522c0299a5568c65fb1
Tested: TODO
diff --git a/system/dev/nand/nandpart/nandpart.cpp b/system/dev/nand/nandpart/nandpart.cpp
index f533f5d..52ecc05 100644
--- a/system/dev/nand/nandpart/nandpart.cpp
+++ b/system/dev/nand/nandpart/nandpart.cpp
@@ -13,6 +13,7 @@
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
+#include <ddk/protocol/bad-block.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
@@ -111,11 +112,38 @@
// Make sure parent_op_size is aligned, so we can safely add our data at the end.
parent_op_size = fbl::round_up(parent_op_size, 8u);
- // Query parent for partition map.
+ // Query parent for bad block configuration info.
size_t actual;
+ bad_block_config_t bad_block_config;
+ zx_status_t status = device_get_metadata(parent, DEVICE_METADATA_DRIVER_DATA, &bad_block_config,
+ sizeof(bad_block_config), &actual);
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "nandpart: parent device '%s' has no device metadata\n",
+ device_get_name(parent));
+ return status;
+ }
+ if (actual != sizeof(bad_block_config)) {
+ zxlogf(ERROR, "nandpart: Expected metadata of size %zu, got %zu\n",
+ sizeof(bad_block_config), actual);
+ return ZX_ERR_INTERNAL;
+ }
+
+ // Create a bad block instance.
+ BadBlock::Config config = {
+ .bad_block_config = bad_block_config,
+ .nand_proto = nand_proto,
+ };
+ fbl::RefPtr<BadBlock> bad_block;
+ status = BadBlock::Create(config, &bad_block);
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "nandpart: Failed to create BadBlock object\n");
+ return status;
+ }
+
+ // Query parent for partition map.
uint8_t buffer[METADATA_PARTITION_MAP_MAX];
- zx_status_t status = device_get_metadata(parent, DEVICE_METADATA_PARTITION_MAP, buffer,
- sizeof(buffer), &actual);
+ status = device_get_metadata(parent, DEVICE_METADATA_PARTITION_MAP, buffer, sizeof(buffer),
+ &actual);
if (status != ZX_OK) {
zxlogf(ERROR, "nandpart: parent device '%s' has no parititon map\n",
device_get_name(parent));
@@ -161,7 +189,7 @@
fbl::AllocChecker ac;
fbl::unique_ptr<NandPartDevice> device(new (&ac) NandPartDevice(
- parent, nand_proto, parent_op_size, nand_info,
+ parent, nand_proto, bad_block, parent_op_size, nand_info,
static_cast<uint32_t>(part->first_block)));
if (!ac.check()) {
continue;
@@ -239,6 +267,105 @@
*num_bad_blocks = 0;
}
+zx_status_t NandPartDevice::GetBadBlockList2(uint32_t* bad_block_list, uint32_t bad_block_list_len,
+ uint32_t* bad_block_count) {
+
+ if (!bad_block_list_) {
+ const zx_status_t status = bad_block_->GetBadBlockList(
+ &bad_block_list_, erase_block_start_, erase_block_start_ + nand_info_.num_blocks);
+ if (status != ZX_OK) {
+ return status;
+ }
+ }
+
+ *bad_block_count = static_cast<uint32_t>(bad_block_list_.size());
+ zxlogf(TRACE, "nandpart: %s: Bad block count: %u\n", name(), *bad_block_count);
+
+ if (bad_block_list_len == 0 || bad_block_list_.size() == 0) {
+ return ZX_OK;
+ }
+ if (bad_block_list == NULL) {
+ return ZX_ERR_INVALID_ARGS;
+ }
+
+ if (bad_block_list_.size() > 0) {
+ const size_t size = sizeof(uint32_t) * bad_block_list_.size();
+ memcpy(bad_block_list, bad_block_list_.get(), size);
+ }
+ return ZX_OK;
+}
+
+zx_status_t NandPartDevice::IsBlockBad(uint32_t block, bool* is_bad) {
+ if (block >= nand_info_.num_blocks) {
+ return ZX_ERR_OUT_OF_RANGE;
+ }
+
+ if (!bad_block_list_) {
+ const zx_status_t status = bad_block_->GetBadBlockList(
+ &bad_block_list_, erase_block_start_, erase_block_start_ + nand_info_.num_blocks);
+ if (status != ZX_OK) {
+ return status;
+ }
+ }
+
+ *is_bad = false;
+ // bad_block_list should be relatively small and we don't keep the
+ // bad_block_list sorted, so we simply iterate through entire list.
+ for (const auto& bad_block : bad_block_list_) {
+ if (bad_block == block) {
+ *is_bad = true;
+ break;
+ }
+ }
+ return ZX_OK;
+}
+
+zx_status_t NandPartDevice::MarkBlockBad(uint32_t block) {
+ if (block >= nand_info_.num_blocks) {
+ return ZX_ERR_OUT_OF_RANGE;
+ }
+
+ if (!bad_block_list_) {
+ const zx_status_t status = bad_block_->GetBadBlockList(
+ &bad_block_list_, erase_block_start_, erase_block_start_ + nand_info_.num_blocks);
+ if (status != ZX_OK) {
+ return status;
+ }
+ }
+
+ // First, update our cached copy of the bad block list.
+ fbl::AllocChecker ac;
+ const size_t new_size = bad_block_list_.size() + 1;
+ fbl::Array<uint32_t> new_bad_block_list(new (&ac) uint32_t[new_size], new_size);
+ if (!ac.check()) {
+ return ZX_ERR_NO_MEMORY;
+ }
+ memcpy(new_bad_block_list.get(), bad_block_list_.get(),
+ bad_block_list_.size() * sizeof(uint32_t));
+ new_bad_block_list[bad_block_list_.size()] = block;
+ bad_block_list_ = fbl::move(new_bad_block_list);
+
+ // Second, "write-through" to actually persist.
+ block += erase_block_start_;
+ return bad_block_->MarkBlockBad(block);
+}
+
+zx_status_t NandPartDevice::DdkGetProtocol(uint32_t proto_id, void* protocol) {
+ auto* proto = static_cast<ddk::AnyProtocol*>(protocol);
+ proto->ctx = this;
+ switch (proto_id) {
+ case ZX_PROTOCOL_NAND:
+ proto->ops = &nand_proto_ops_;
+ break;
+ case ZX_PROTOCOL_BAD_BLOCK:
+ proto->ops = &bad_block_proto_ops_;
+ break;
+ default:
+ return ZX_ERR_NOT_SUPPORTED;
+ }
+ return ZX_OK;
+}
+
} // namespace nand
extern "C" zx_status_t nandpart_bind(void* ctx, zx_device_t* parent) {
diff --git a/system/dev/nand/nandpart/nandpart.h b/system/dev/nand/nandpart/nandpart.h
index 35a6b0b..7c8af42 100644
--- a/system/dev/nand/nandpart/nandpart.h
+++ b/system/dev/nand/nandpart/nandpart.h
@@ -7,6 +7,7 @@
#include <ddk/device.h>
#include <ddk/protocol/nand.h>
#include <ddktl/device.h>
+#include <ddktl/protocol/bad-block.h>
#include <ddktl/protocol/nand.h>
#include <fbl/array.h>
@@ -14,13 +15,16 @@
#include <fbl/ref_ptr.h>
#include <zircon/types.h>
+#include "bad-block.h"
+
namespace nand {
class NandPartDevice;
-using DeviceType = ddk::Device<NandPartDevice, ddk::GetSizable>;
+using DeviceType = ddk::Device<NandPartDevice, ddk::GetSizable, ddk::GetProtocolable>;
class NandPartDevice : public DeviceType,
- public ddk::NandProtocol<NandPartDevice> {
+ public ddk::NandProtocol<NandPartDevice>,
+ public ddk::BadBlockable<NandPartDevice> {
public:
// Spawns device nodes based on parent node.
static zx_status_t Create(zx_device_t* parent);
@@ -42,13 +46,19 @@
void Queue(nand_op_t* op);
void GetBadBlockList(uint32_t* bad_blocks, uint32_t bad_block_len, uint32_t* num_bad_blocks);
+ // bad block protocol implementation.
+ zx_status_t GetBadBlockList2(uint32_t* bad_block_list, uint32_t bad_block_list_len,
+ uint32_t* bad_block_count);
+ zx_status_t IsBlockBad(uint32_t block, bool* is_bad);
+ zx_status_t MarkBlockBad(uint32_t block);
+
private:
explicit NandPartDevice(zx_device_t* parent, const nand_protocol_t& nand_proto,
- size_t parent_op_size,
+ fbl::RefPtr<BadBlock> bad_block, size_t parent_op_size,
const nand_info_t& nand_info, uint32_t erase_block_start)
: DeviceType(parent), nand_proto_(nand_proto), nand_(&nand_proto_),
parent_op_size_(parent_op_size), nand_info_(nand_info),
- erase_block_start_(erase_block_start) {}
+ erase_block_start_(erase_block_start), bad_block_(fbl::move(bad_block)) {}
DISALLOW_COPY_ASSIGN_AND_MOVE(NandPartDevice);
@@ -61,6 +71,11 @@
nand_info_t nand_info_;
// First erase block for the partition.
uint32_t erase_block_start_;
+ // Device specific bad block info. Shared between all devices for a given
+ // parent device.
+ fbl::RefPtr<BadBlock> bad_block_;
+ // Cached list of bad blocks for this partition. Lazily instantiated.
+ fbl::Array<uint32_t> bad_block_list_;
};
} // namespace nand
diff --git a/system/dev/nand/nandpart/rules.mk b/system/dev/nand/nandpart/rules.mk
index ee32c0a..384d43b 100644
--- a/system/dev/nand/nandpart/rules.mk
+++ b/system/dev/nand/nandpart/rules.mk
@@ -9,20 +9,23 @@
MODULE_TYPE := driver
MODULE_SRCS := \
- $(LOCAL_DIR)/binding.c \
- $(LOCAL_DIR)/nandpart.cpp \
+ $(LOCAL_DIR)/aml-bad-block.cpp \
+ $(LOCAL_DIR)/bad-block.cpp \
+ $(LOCAL_DIR)/binding.c \
+ $(LOCAL_DIR)/nandpart.cpp \
MODULE_STATIC_LIBS := \
- system/ulib/ddk \
+ system/ulib/ddk \
system/ulib/ddktl \
system/ulib/fbl \
- system/ulib/sync \
+ system/ulib/pretty \
+ system/ulib/sync \
system/ulib/zx \
system/ulib/zxcpp \
MODULE_LIBS := \
- system/ulib/c \
- system/ulib/driver \
- system/ulib/zircon \
+ system/ulib/c \
+ system/ulib/driver \
+ system/ulib/zircon \
include make/module.mk