[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