1// Copyright 2018 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4#include "nandpart.h" 5 6#include <assert.h> 7#include <inttypes.h> 8#include <stdio.h> 9#include <string.h> 10 11#include <ddk/binding.h> 12#include <ddk/debug.h> 13#include <ddk/driver.h> 14#include <ddk/metadata.h> 15#include <ddk/metadata/nand.h> 16#include <ddk/protocol/bad-block.h> 17 18#include <fbl/algorithm.h> 19#include <fbl/alloc_checker.h> 20#include <fbl/unique_ptr.h> 21#include <lib/sync/completion.h> 22#include <zircon/boot/image.h> 23#include <zircon/hw/gpt.h> 24#include <zircon/types.h> 25 26#include "nandpart-utils.h" 27 28namespace nand { 29namespace { 30 31constexpr uint8_t fvm_guid[] = GUID_FVM_VALUE; 32 33// Shim for calling sub-partition's callback. 34void CompletionCallback(nand_op_t* op, zx_status_t status) { 35 op = static_cast<nand_op_t*>(op->cookie); 36 op->completion_cb(op, status); 37} 38 39} // namespace 40 41zx_status_t NandPartDevice::Create(zx_device_t* parent) { 42 zxlogf(INFO, "NandPartDevice::Create: Starting...!\n"); 43 44 nand_protocol_t nand_proto; 45 if (device_get_protocol(parent, ZX_PROTOCOL_NAND, &nand_proto) != ZX_OK) { 46 zxlogf(ERROR, "nandpart: parent device '%s': does not support nand protocol\n", 47 device_get_name(parent)); 48 return ZX_ERR_NOT_SUPPORTED; 49 } 50 51 // Query parent to get its nand_info_t and size for nand_op_t. 52 nand_info_t nand_info; 53 size_t parent_op_size; 54 nand_proto.ops->query(nand_proto.ctx, &nand_info, &parent_op_size); 55 // Make sure parent_op_size is aligned, so we can safely add our data at the end. 56 parent_op_size = fbl::round_up(parent_op_size, 8u); 57 58 // Query parent for nand configuration info. 59 size_t actual; 60 nand_config_t nand_config; 61 zx_status_t status = device_get_metadata(parent, DEVICE_METADATA_PRIVATE, &nand_config, 62 sizeof(nand_config), &actual); 63 if (status != ZX_OK) { 64 zxlogf(ERROR, "nandpart: parent device '%s' has no device metadata\n", 65 device_get_name(parent)); 66 return status; 67 } 68 if (actual < sizeof(nand_config_t)) { 69 zxlogf(ERROR, "nandpart: Expected metadata is of size %zu, needs to at least be %zu\n", 70 actual, sizeof(nand_config_t)); 71 return ZX_ERR_INTERNAL; 72 } 73 // Create a bad block instance. 74 BadBlock::Config config = { 75 .bad_block_config = nand_config.bad_block_config, 76 .nand_proto = nand_proto, 77 }; 78 fbl::RefPtr<BadBlock> bad_block; 79 status = BadBlock::Create(config, &bad_block); 80 if (status != ZX_OK) { 81 zxlogf(ERROR, "nandpart: Failed to create BadBlock object\n"); 82 return status; 83 } 84 85 // Query parent for partition map. 86 uint8_t buffer[METADATA_PARTITION_MAP_MAX]; 87 status = device_get_metadata(parent, DEVICE_METADATA_PARTITION_MAP, buffer, sizeof(buffer), 88 &actual); 89 if (status != ZX_OK) { 90 zxlogf(ERROR, "nandpart: parent device '%s' has no parititon map\n", 91 device_get_name(parent)); 92 return status; 93 } 94 if (actual < sizeof(zbi_partition_map_t)) { 95 zxlogf(ERROR, "nandpart: Partition map is of size %zu, needs to at least be %zu\n", actual, 96 sizeof(zbi_partition_t)); 97 return ZX_ERR_INTERNAL; 98 } 99 100 auto* pmap = reinterpret_cast<zbi_partition_map_t*>(buffer); 101 102 const size_t minimum_size = 103 sizeof(zbi_partition_map_t) + (sizeof(zbi_partition_t) * pmap->partition_count); 104 if (actual < minimum_size) { 105 zxlogf(ERROR, "nandpart: Partition map is of size %zu, needs to at least be %zu\n", actual, 106 minimum_size); 107 return ZX_ERR_INTERNAL; 108 } 109 110 // Sanity check partition map and transform into expected form. 111 status = SanitizePartitionMap(pmap, nand_info); 112 if (status != ZX_OK) { 113 return status; 114 } 115 116 // Create a device for each partition. 117 for (unsigned i = 0; i < pmap->partition_count; i++) { 118 const auto* part = &pmap->partitions[i]; 119 120 nand_info.num_blocks = static_cast<uint32_t>(part->last_block - part->first_block + 1); 121 memcpy(&nand_info.partition_guid, &part->type_guid, sizeof(nand_info.partition_guid)); 122 // We only use FTL for the FVM partition. 123 if (memcmp(part->type_guid, fvm_guid, sizeof(fvm_guid)) == 0) { 124 nand_info.nand_class = NAND_CLASS_FTL; 125 } else { 126 nand_info.nand_class = NAND_CLASS_BBS; 127 } 128 129 fbl::AllocChecker ac; 130 fbl::unique_ptr<NandPartDevice> device(new (&ac) NandPartDevice( 131 parent, nand_proto, bad_block, parent_op_size, nand_info, 132 static_cast<uint32_t>(part->first_block))); 133 if (!ac.check()) { 134 continue; 135 } 136 // Find optional partition_config information. 137 uint32_t copy_count = 1; 138 for (uint32_t i = 0; i < nand_config.extra_partition_config_count; i++) { 139 if (memcmp(nand_config.extra_partition_config[i].type_guid, part->type_guid, 140 sizeof(part->type_guid)) == 0 && 141 nand_config.extra_partition_config[i].copy_count > 0) { 142 copy_count = nand_config.extra_partition_config[i].copy_count; 143 break; 144 } 145 } 146 status = device->Bind(part->name, copy_count); 147 if (status != ZX_OK) { 148 zxlogf(ERROR, "Failed to bind %s with error %d\n", part->name, status); 149 150 continue; 151 } 152 // devmgr is now in charge of the device. 153 __UNUSED auto* dummy = device.release(); 154 } 155 156 return ZX_OK; 157} 158 159zx_status_t NandPartDevice::Bind(const char* name, uint32_t copy_count) { 160 zxlogf(INFO, "nandpart: Binding %s to %s\n", name, device_get_name(parent())); 161 162 zx_device_prop_t props[] = { 163 {BIND_PROTOCOL, 0, ZX_PROTOCOL_NAND}, 164 {BIND_NAND_CLASS, 0, nand_info_.nand_class}, 165 }; 166 167 zx_status_t status = DdkAdd(name, DEVICE_ADD_INVISIBLE, props, fbl::count_of(props)); 168 if (status != ZX_OK) { 169 return status; 170 } 171 172 // Add empty partition map metadata to prevent this driver from binding to its child devices 173 status = DdkAddMetadata(DEVICE_METADATA_PARTITION_MAP, nullptr, 0); 174 if (status != ZX_OK) { 175 DdkRemove(); 176 return status; 177 } 178 179 status = DdkAddMetadata(DEVICE_METADATA_PRIVATE, ©_count, sizeof(copy_count)); 180 if (status != ZX_OK) { 181 DdkRemove(); 182 return status; 183 } 184 185 DdkMakeVisible(); 186 return ZX_OK; 187} 188 189void NandPartDevice::Query(nand_info_t* info_out, size_t* nand_op_size_out) { 190 memcpy(info_out, &nand_info_, sizeof(*info_out)); 191 // Add size of translated_op. 192 *nand_op_size_out = parent_op_size_ + sizeof(nand_op_t); 193} 194 195void NandPartDevice::Queue(nand_op_t* op) { 196 auto* translated_op = 197 reinterpret_cast<nand_op_t*>(reinterpret_cast<uintptr_t>(op) + parent_op_size_); 198 uint32_t command = op->command; 199 200 // Copy client's op to translated op 201 memcpy(translated_op, op, sizeof(*translated_op)); 202 203 // Make offset relative to full underlying device 204 switch (command) { 205 case NAND_OP_READ: 206 case NAND_OP_WRITE: 207 translated_op->rw.offset_nand += (erase_block_start_ * nand_info_.pages_per_block); 208 break; 209 case NAND_OP_ERASE: 210 translated_op->erase.first_block += erase_block_start_; 211 break; 212 default: 213 op->completion_cb(op, ZX_ERR_NOT_SUPPORTED); 214 return; 215 } 216 217 translated_op->completion_cb = CompletionCallback; 218 translated_op->cookie = op; 219 220 // Call parent's queue 221 nand_.Queue(translated_op); 222} 223 224zx_status_t NandPartDevice::GetFactoryBadBlockList(uint32_t* bad_blocks, uint32_t bad_block_len, 225 uint32_t* num_bad_blocks) { 226 // TODO implement this. 227 *num_bad_blocks = 0; 228 return ZX_ERR_NOT_SUPPORTED; 229} 230 231zx_status_t NandPartDevice::GetBadBlockList(uint32_t* bad_block_list, uint32_t bad_block_list_len, 232 uint32_t* bad_block_count) { 233 234 if (!bad_block_list_) { 235 const zx_status_t status = bad_block_->GetBadBlockList( 236 erase_block_start_, erase_block_start_ + nand_info_.num_blocks, &bad_block_list_); 237 if (status != ZX_OK) { 238 return status; 239 } 240 for (uint32_t i = 0; i < bad_block_list_.size(); i++) { 241 bad_block_list_[i] -= erase_block_start_; 242 } 243 } 244 245 *bad_block_count = static_cast<uint32_t>(bad_block_list_.size()); 246 zxlogf(TRACE, "nandpart: %s: Bad block count: %u\n", name(), *bad_block_count); 247 248 if (bad_block_list_len == 0 || bad_block_list_.size() == 0) { 249 return ZX_OK; 250 } 251 if (bad_block_list == NULL) { 252 return ZX_ERR_INVALID_ARGS; 253 } 254 255 const size_t size = sizeof(uint32_t) * fbl::min(*bad_block_count, bad_block_list_len); 256 memcpy(bad_block_list, bad_block_list_.get(), size); 257 return ZX_OK; 258} 259 260zx_status_t NandPartDevice::MarkBlockBad(uint32_t block) { 261 if (block >= nand_info_.num_blocks) { 262 return ZX_ERR_OUT_OF_RANGE; 263 } 264 265 // First, invalidate our cached copy. 266 bad_block_list_.reset(); 267 268 // Second, "write-through" to actually persist. 269 block += erase_block_start_; 270 return bad_block_->MarkBlockBad(block); 271} 272 273zx_status_t NandPartDevice::DdkGetProtocol(uint32_t proto_id, void* protocol) { 274 auto* proto = static_cast<ddk::AnyProtocol*>(protocol); 275 proto->ctx = this; 276 switch (proto_id) { 277 case ZX_PROTOCOL_NAND: 278 proto->ops = &nand_proto_ops_; 279 break; 280 case ZX_PROTOCOL_BAD_BLOCK: 281 proto->ops = &bad_block_proto_ops_; 282 break; 283 default: 284 return ZX_ERR_NOT_SUPPORTED; 285 } 286 return ZX_OK; 287} 288 289} // namespace nand 290 291extern "C" zx_status_t nandpart_bind(void* ctx, zx_device_t* parent) { 292 return nand::NandPartDevice::Create(parent); 293} 294