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 5#include "skip-block.h" 6 7#include <string.h> 8 9#include <ddk/debug.h> 10#include <ddk/metadata.h> 11#include <ddk/protocol/bad-block.h> 12#include <ddk/protocol/nand.h> 13 14#include <fbl/algorithm.h> 15#include <fbl/alloc_checker.h> 16#include <fbl/unique_ptr.h> 17#include <lib/zx/vmo.h> 18#include <lib/sync/completion.h> 19#include <zircon/boot/image.h> 20 21namespace nand { 22 23namespace { 24 25struct BlockOperationContext { 26 skip_block_rw_operation_t op; 27 nand_info_t* nand_info; 28 LogicalToPhysicalMap* block_map; 29 ddk::NandProtocolProxy* nand; 30 uint32_t copy; 31 uint32_t current_block; 32 uint32_t physical_block; 33 sync_completion_t* completion_event; 34 zx_status_t status; 35 bool mark_bad; 36}; 37 38// Called when all page reads in a block finish. If another block still needs 39// to be read, it queues it up as another operation. 40void ReadCompletionCallback(nand_op_t* op, zx_status_t status) { 41 auto* ctx = static_cast<BlockOperationContext*>(op->cookie); 42 if (status != ZX_OK || ctx->current_block + 1 == ctx->op.block + ctx->op.block_count) { 43 ctx->status = status; 44 ctx->mark_bad = false; 45 sync_completion_signal(ctx->completion_event); 46 return; 47 } 48 ctx->current_block += 1; 49 50 status = ctx->block_map->GetPhysical(ctx->copy, ctx->current_block, &ctx->physical_block); 51 if (status != ZX_OK) { 52 ctx->status = status; 53 ctx->mark_bad = false; 54 sync_completion_signal(ctx->completion_event); 55 return; 56 } 57 58 op->rw.offset_nand = ctx->physical_block * ctx->nand_info->pages_per_block; 59 op->rw.offset_data_vmo += ctx->nand_info->pages_per_block; 60 ctx->nand->Queue(op); 61 return; 62} 63 64void EraseCompletionCallback(nand_op_t* op, zx_status_t status); 65 66// Called when all page writes in a block finish. If another block still needs 67// to be written, it queues up an erase. 68void WriteCompletionCallback(nand_op_t* op, zx_status_t status) { 69 auto* ctx = static_cast<BlockOperationContext*>(op->cookie); 70 71 if (status != ZX_OK || ctx->current_block + 1 == ctx->op.block + ctx->op.block_count) { 72 ctx->status = status; 73 ctx->mark_bad = status != ZX_OK; 74 sync_completion_signal(ctx->completion_event); 75 return; 76 } 77 ctx->current_block += 1; 78 ctx->op.vmo_offset += ctx->nand_info->pages_per_block; 79 80 status = ctx->block_map->GetPhysical(ctx->copy, ctx->current_block, &ctx->physical_block); 81 if (status != ZX_OK) { 82 ctx->status = status; 83 ctx->mark_bad = false; 84 sync_completion_signal(ctx->completion_event); 85 return; 86 } 87 op->erase.command = NAND_OP_ERASE; 88 op->erase.first_block = ctx->physical_block; 89 op->erase.num_blocks = 1; 90 op->completion_cb = EraseCompletionCallback; 91 ctx->nand->Queue(op); 92 return; 93} 94 95// Called when a block erase operation finishes. Subsequently queues up writes 96// to the block. 97void EraseCompletionCallback(nand_op_t* op, zx_status_t status) { 98 auto* ctx = static_cast<BlockOperationContext*>(op->cookie); 99 100 if (status != ZX_OK) { 101 ctx->status = status; 102 ctx->mark_bad = true; 103 sync_completion_signal(ctx->completion_event); 104 return; 105 } 106 op->rw.command = NAND_OP_WRITE; 107 op->rw.data_vmo = ctx->op.vmo; 108 op->rw.oob_vmo = ZX_HANDLE_INVALID; 109 op->rw.length = ctx->nand_info->pages_per_block; 110 op->rw.offset_nand = ctx->physical_block * ctx->nand_info->pages_per_block; 111 op->rw.offset_data_vmo = ctx->op.vmo_offset; 112 op->rw.pages = nullptr; 113 op->completion_cb = WriteCompletionCallback; 114 ctx->nand->Queue(op); 115 return; 116} 117 118} // namespace 119 120zx_status_t SkipBlockDevice::Create(zx_device_t* parent) { 121 // Get NAND protocol. 122 nand_protocol_t nand_proto; 123 if (device_get_protocol(parent, ZX_PROTOCOL_NAND, &nand_proto) != ZX_OK) { 124 zxlogf(ERROR, "skip-block: parent device '%s': does not support nand protocol\n", 125 device_get_name(parent)); 126 return ZX_ERR_NOT_SUPPORTED; 127 } 128 129 // Get bad block protocol. 130 bad_block_protocol_t bad_block_proto; 131 if (device_get_protocol(parent, ZX_PROTOCOL_BAD_BLOCK, &bad_block_proto) != ZX_OK) { 132 zxlogf(ERROR, "skip-block: parent device '%s': does not support bad_block protocol\n", 133 device_get_name(parent)); 134 return ZX_ERR_NOT_SUPPORTED; 135 } 136 137 uint32_t copy_count; 138 size_t actual; 139 zx_status_t status = device_get_metadata(parent, DEVICE_METADATA_PRIVATE, ©_count, 140 sizeof(copy_count), &actual); 141 if (status != ZX_OK) { 142 zxlogf(ERROR, "skip-block: parent device '%s' has no private metadata\n", 143 device_get_name(parent)); 144 return status; 145 } 146 if (actual != sizeof(copy_count)) { 147 zxlogf(ERROR, "skip-block: Private metadata is of size %zu, expected to be %zu\n", actual, 148 sizeof(copy_count)); 149 return ZX_ERR_INTERNAL; 150 } 151 152 fbl::AllocChecker ac; 153 fbl::unique_ptr<SkipBlockDevice> device(new (&ac) SkipBlockDevice(parent, nand_proto, 154 bad_block_proto, copy_count)); 155 if (!ac.check()) { 156 return ZX_ERR_NO_MEMORY; 157 } 158 159 status = device->Bind(); 160 if (status != ZX_OK) { 161 return status; 162 } 163 164 // devmgr is now in charge of the device. 165 __UNUSED auto* dummy = device.release(); 166 return ZX_OK; 167} 168 169zx_status_t SkipBlockDevice::GetBadBlockList(fbl::Array<uint32_t>* bad_blocks) { 170 uint32_t bad_block_count; 171 zx_status_t status = bad_block_.GetBadBlockList(nullptr, 0, &bad_block_count); 172 if (status != ZX_OK) { 173 return status; 174 } 175 if (bad_block_count == 0) { 176 bad_blocks->reset(); 177 return ZX_OK; 178 } 179 const uint32_t bad_block_list_len = bad_block_count; 180 fbl::unique_ptr<uint32_t[]> bad_block_list(new uint32_t[bad_block_count]); 181 status = bad_block_.GetBadBlockList(bad_block_list.get(), bad_block_list_len, &bad_block_count); 182 if (status != ZX_OK) { 183 return status; 184 } 185 if (bad_block_list_len != bad_block_count) { 186 return ZX_ERR_INTERNAL; 187 } 188 *bad_blocks = fbl::move(fbl::Array<uint32_t>(bad_block_list.release(), bad_block_count)); 189 return ZX_OK; 190} 191 192zx_status_t SkipBlockDevice::Bind() { 193 zxlogf(INFO, "skip-block: Binding to %s\n", device_get_name(parent())); 194 195 fbl::AutoLock al(&lock_); 196 197 if (sizeof(nand_op_t) > parent_op_size_) { 198 zxlogf(ERROR, "skip-block: parent op size, %zu, is smaller than minimum op size: %zu\n", 199 sizeof(nand_op_t), parent_op_size_); 200 return ZX_ERR_INTERNAL; 201 } 202 203 fbl::AllocChecker ac; 204 fbl::Array<uint8_t> nand_op(new (&ac) uint8_t[parent_op_size_], parent_op_size_); 205 if (!ac.check()) { 206 return ZX_ERR_NO_MEMORY; 207 } 208 nand_op_ = fbl::move(nand_op); 209 210 // TODO(surajmalhotra): Potentially make this lazy instead of in the bind. 211 fbl::Array<uint32_t> bad_blocks; 212 const zx_status_t status = GetBadBlockList(&bad_blocks); 213 if (status != ZX_OK) { 214 zxlogf(ERROR, "skip-block: Failed to get bad block list\n"); 215 return status; 216 } 217 block_map_ = fbl::move(LogicalToPhysicalMap(copy_count_, nand_info_.num_blocks, 218 fbl::move(bad_blocks))); 219 220 return DdkAdd("skip-block"); 221} 222 223zx_status_t SkipBlockDevice::GetPartitionInfo(skip_block_partition_info_t* info) const { 224 info->block_size_bytes = GetBlockSize(); 225 uint32_t logical_block_count = UINT32_MAX; 226 for (uint32_t copy = 0; copy < copy_count_; copy++) { 227 logical_block_count = fbl::min(logical_block_count, block_map_.LogicalBlockCount(copy)); 228 } 229 info->partition_block_count = logical_block_count; 230 memcpy(info->partition_guid, nand_info_.partition_guid, ZBI_PARTITION_GUID_LEN); 231 232 return ZX_OK; 233} 234 235zx_status_t SkipBlockDevice::ValidateVmo(const skip_block_rw_operation_t& op) const { 236 uint64_t vmo_size; 237 238 zx_status_t status = zx_vmo_get_size(op.vmo, &vmo_size); 239 if (status != ZX_OK) { 240 return ZX_ERR_INVALID_ARGS; 241 } 242 if (vmo_size < op.vmo_offset + op.block_count * GetBlockSize()) { 243 return ZX_ERR_OUT_OF_RANGE; 244 } 245 return ZX_OK; 246} 247 248zx_status_t SkipBlockDevice::Read(const skip_block_rw_operation_t& op) { 249 auto vmo = zx::vmo(op.vmo); 250 zx_status_t status = ValidateVmo(op); 251 if (status != ZX_OK) { 252 return status; 253 } 254 255 // TODO(surajmalhotra): We currently only read from the first copy. Given a 256 // good use case, we could improve this to read from other copies in the 257 // case or read failures, or perhaps expose ability to chose which copy gets 258 // read to the user. 259 constexpr uint32_t kReadCopy = 0; 260 uint32_t physical_block; 261 status = block_map_.GetPhysical(kReadCopy, op.block, &physical_block); 262 if (status != ZX_OK) { 263 return status; 264 } 265 sync_completion_t completion; 266 BlockOperationContext op_context = { 267 .op = op, 268 .nand_info = &nand_info_, 269 .block_map = &block_map_, 270 .nand = &nand_, 271 .copy = kReadCopy, 272 .current_block = op.block, 273 .physical_block = physical_block, 274 .completion_event = &completion, 275 .status = ZX_OK, 276 .mark_bad = false, 277 }; 278 279 auto* nand_op = reinterpret_cast<nand_op_t*>(nand_op_.get()); 280 nand_op->rw.command = NAND_OP_READ; 281 nand_op->rw.data_vmo = op.vmo; 282 nand_op->rw.oob_vmo = ZX_HANDLE_INVALID; 283 nand_op->rw.length = nand_info_.pages_per_block; 284 nand_op->rw.offset_nand = physical_block * nand_info_.pages_per_block; 285 nand_op->rw.offset_data_vmo = op.vmo_offset; 286 // The read callback will enqueue subsequent reads. 287 nand_op->completion_cb = ReadCompletionCallback; 288 nand_op->cookie = &op_context; 289 nand_.Queue(nand_op); 290 291 // Wait on completion. 292 sync_completion_wait(&completion, ZX_TIME_INFINITE); 293 return op_context.status; 294} 295 296zx_status_t SkipBlockDevice::Write(const skip_block_rw_operation_t& op, bool* bad_block_grown) { 297 auto vmo = zx::vmo(op.vmo); 298 zx_status_t status = ValidateVmo(op); 299 if (status != ZX_OK) { 300 return status; 301 } 302 303 *bad_block_grown = false; 304 for (uint32_t copy = 0; copy < copy_count_; copy++) { 305 for (;;) { 306 uint32_t physical_block; 307 status = block_map_.GetPhysical(copy, op.block, &physical_block); 308 if (status != ZX_OK) { 309 return status; 310 } 311 312 sync_completion_t completion; 313 BlockOperationContext op_context = { 314 .op = op, 315 .nand_info = &nand_info_, 316 .block_map = &block_map_, 317 .nand = &nand_, 318 .copy = copy, 319 .current_block = op.block, 320 .physical_block = physical_block, 321 .completion_event = &completion, 322 .status = ZX_OK, 323 .mark_bad = false, 324 }; 325 326 auto* nand_op = reinterpret_cast<nand_op_t*>(nand_op_.get()); 327 nand_op->erase.command = NAND_OP_ERASE; 328 nand_op->erase.first_block = physical_block; 329 nand_op->erase.num_blocks = 1; 330 // The erase callback will enqueue subsequent writes and erases. 331 nand_op->completion_cb = EraseCompletionCallback; 332 nand_op->cookie = &op_context; 333 nand_.Queue(nand_op); 334 335 // Wait on completion. 336 sync_completion_wait(&completion, ZX_TIME_INFINITE); 337 if (op_context.mark_bad) { 338 zxlogf(ERROR, "Failed to erase/write block %u, marking bad\n", 339 op_context.physical_block); 340 status = bad_block_.MarkBlockBad(op_context.physical_block); 341 if (status != ZX_OK) { 342 zxlogf(ERROR, "skip-block: Failed to mark block bad\n"); 343 return status; 344 } 345 // Logical to physical mapping has changed, so we need to re-initialize block_map_. 346 fbl::Array<uint32_t> bad_blocks; 347 // TODO(surajmalhotra): Make it impossible for this to fail. 348 ZX_ASSERT(GetBadBlockList(&bad_blocks) == ZX_OK); 349 block_map_ = fbl::move(LogicalToPhysicalMap(copy_count_, nand_info_.num_blocks, 350 fbl::move(bad_blocks))); 351 *bad_block_grown = true; 352 continue; 353 } 354 if (op_context.status != ZX_OK) { 355 return op_context.status; 356 } 357 break; 358 } 359 } 360 return ZX_OK; 361} 362 363zx_off_t SkipBlockDevice::DdkGetSize() { 364 fbl::AutoLock al(&lock_); 365 uint32_t logical_block_count = UINT32_MAX; 366 for (uint32_t copy = 0; copy < copy_count_; copy++) { 367 logical_block_count = fbl::min(logical_block_count, block_map_.LogicalBlockCount(copy)); 368 } 369 return GetBlockSize() * logical_block_count; 370} 371 372zx_status_t SkipBlockDevice::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, 373 void* out_buf, size_t out_len, size_t* out_actual) { 374 fbl::AutoLock lock(&lock_); 375 376 zxlogf(TRACE, "skip-block: IOCTL %x\n", op); 377 378 switch (op) { 379 case IOCTL_SKIP_BLOCK_GET_PARTITION_INFO: 380 if (!out_buf || out_len < sizeof(skip_block_partition_info_t)) { 381 return ZX_ERR_INVALID_ARGS; 382 } 383 *out_actual = sizeof(skip_block_partition_info_t); 384 return GetPartitionInfo(static_cast<skip_block_partition_info_t*>(out_buf)); 385 386 case IOCTL_SKIP_BLOCK_READ: 387 if (!in_buf || in_len < sizeof(skip_block_rw_operation_t)) { 388 return ZX_ERR_INVALID_ARGS; 389 } 390 return Read(*static_cast<const skip_block_rw_operation_t*>(in_buf)); 391 392 case IOCTL_SKIP_BLOCK_WRITE: { 393 if (!in_buf || in_len < sizeof(skip_block_rw_operation_t) || 394 !out_buf || out_len < sizeof(bool)) { 395 return ZX_ERR_INVALID_ARGS; 396 } 397 zx_status_t status = Write(*static_cast<const skip_block_rw_operation_t*>(in_buf), 398 static_cast<bool*>(out_buf)); 399 if (status == ZX_OK) { 400 *out_actual = sizeof(bool); 401 } 402 return status; 403 } 404 default: 405 return ZX_ERR_NOT_SUPPORTED; 406 } 407} 408 409} // namespace nand 410 411extern "C" zx_status_t skip_block_bind(void* ctx, zx_device_t* parent) { 412 return nand::SkipBlockDevice::Create(parent); 413} 414