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, &copy_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