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 <zircon/device/block.h>
6#include <fbl/algorithm.h>
7#include <fbl/macros.h>
8#include <fbl/vector.h>
9
10#include <fs/block-txn.h>
11#include <fs/vfs.h>
12
13namespace fs {
14
15BlockTxn::BlockTxn(TransactionHandler* handler) : handler_(handler) {}
16
17BlockTxn::~BlockTxn() {
18    Transact();
19}
20
21#ifdef __Fuchsia__
22
23void BlockTxn::EnqueueOperation(uint32_t op, vmoid_t id, uint64_t vmo_offset,
24                                uint64_t dev_offset, uint64_t nblocks) {
25    // TODO(ZX-2253): Remove this assertion.
26    ZX_ASSERT_MSG(nblocks < UINT32_MAX, "Too many blocks");
27    uint32_t blocks = static_cast<uint32_t>(nblocks);
28    for (size_t i = 0; i < requests_.size(); i++) {
29        if (requests_[i].vmoid != id || requests_[i].opcode != op) {
30            continue;
31        }
32
33        if (requests_[i].vmo_offset == vmo_offset) {
34            // Take the longer of the operations (if operating on the same
35            // blocks).
36            if (requests_[i].length <= blocks) {
37                requests_[i].length = blocks;
38            }
39            return;
40        } else if ((requests_[i].vmo_offset + requests_[i].length == vmo_offset) &&
41                   (requests_[i].dev_offset + requests_[i].length == dev_offset)) {
42            // Combine with the previous request, if immediately following.
43            requests_[i].length += blocks;
44            return;
45        }
46    }
47
48    block_fifo_request_t request;
49    request.opcode = op;
50    request.group = handler_->BlockGroupID();
51    request.vmoid = id;
52    // NOTE: It's easier to compare everything when dealing
53    // with blocks (not offsets!) so the following are described in
54    // terms of blocks until we Transact().
55    request.length = blocks;
56    request.vmo_offset = vmo_offset;
57    request.dev_offset = dev_offset;
58    requests_.push_back(fbl::move(request));
59}
60
61zx_status_t BlockTxn::Transact() {
62    // Fast-path for already completed transactions.
63    if (requests_.is_empty()) {
64        return ZX_OK;
65    }
66    // Convert 'filesystem block' units to 'disk block' units.
67    const size_t kBlockFactor = handler_->FsBlockSize() / handler_->DeviceBlockSize();
68    for (size_t i = 0; i < requests_.size(); i++) {
69        requests_[i].vmo_offset *= kBlockFactor;
70        requests_[i].dev_offset *= kBlockFactor;
71        // TODO(ZX-2253): Remove this assertion.
72        uint64_t length = requests_[i].length * kBlockFactor;
73        ZX_ASSERT_MSG(length < UINT32_MAX, "Too many blocks");
74        requests_[i].length = static_cast<uint32_t>(length);
75    }
76    zx_status_t status = ZX_OK;
77    if (requests_.size() != 0) {
78        status = handler_->Transaction(requests_.get(), requests_.size());
79    }
80    requests_.reset();
81    return status;
82}
83
84#else
85
86void BlockTxn::EnqueueOperation(uint32_t op, const void* id, uint64_t vmo_offset,
87                                uint64_t dev_offset, uint64_t nblocks) {
88    for (size_t b = 0; b < nblocks; b++) {
89        void* data = GetBlock(handler_->FsBlockSize(), id, vmo_offset + b);
90        if (op == BLOCKIO_WRITE) {
91            handler_->Writeblk(dev_offset + b, data);
92        } else if (op == BLOCKIO_READ) {
93            handler_->Readblk(dev_offset + b, data);
94        } else if (op == BLOCKIO_FLUSH) {
95            // No-op.
96        } else {
97            ZX_ASSERT(false); // Invalid operation.
98        }
99    }
100}
101
102// Activate the transaction (do nothing)
103zx_status_t BlockTxn::Transact() {
104    return ZX_OK;
105}
106
107#endif
108
109} // namespace fs
110