| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #pragma once |
| |
| #include <zircon/device/block.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/macros.h> |
| |
| #include <fs/vfs.h> |
| |
| namespace fs { |
| |
| // Access the "blkno"-th block within data. |
| // "blkno = 0" corresponds to the first block within data. |
| template <size_t BlockSize> |
| void* GetBlock(const void* data, uint64_t blkno) { |
| assert(BlockSize <= (blkno + 1) * BlockSize); // Avoid overflow |
| return (void*)((uintptr_t)(data) + (uintptr_t)(BlockSize * blkno)); |
| } |
| |
| // Enqueue multiple writes (or reads) to the underlying block device |
| // by shoving them into a simple array, to avoid duplicated ops |
| // within a single operation. |
| // |
| // TODO(smklein): This obviously has plenty of room for |
| // improvement, including: |
| // - Sorting blocks, combining ranges |
| // - Writing from multiple buffers (instead of one) |
| // - Cross-operation writeback delays |
| template <typename IdType, bool Write, size_t BlockSize, typename TxnHandler> |
| class BlockTxn; |
| |
| #ifdef __Fuchsia__ |
| |
| template <bool Write, size_t BlockSize, typename TxnHandler> |
| class BlockTxn <vmoid_t, Write, BlockSize, TxnHandler> { |
| public: |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(BlockTxn); |
| explicit BlockTxn(TxnHandler* handler) : handler_(handler), count_(0) {} |
| ~BlockTxn() { |
| Flush(); |
| } |
| |
| // Identify that a block should be written to disk |
| // as a later point in time. |
| void Enqueue(vmoid_t id, uint64_t relative_block, uint64_t absolute_block, uint64_t nblocks) { |
| for (size_t i = 0; i < count_; i++) { |
| if (requests_[i].vmoid != id) { |
| continue; |
| } |
| |
| if (requests_[i].vmo_offset == relative_block) { |
| // Take the longer of the operations (if operating on the same |
| // blocks). |
| requests_[i].length = (requests_[i].length > nblocks) ? requests_[i].length : nblocks; |
| return; |
| } else if ((requests_[i].vmo_offset + requests_[i].length == relative_block) && |
| (requests_[i].dev_offset + requests_[i].length == absolute_block)) { |
| // Combine with the previous request, if immediately following. |
| requests_[i].length += nblocks; |
| return; |
| } |
| } |
| |
| requests_[count_].txnid = handler_->TxnId(); |
| requests_[count_].vmoid = id; |
| // NOTE: It's easier to compare everything when dealing |
| // with blocks (not offsets!) so the following are described in |
| // terms of blocks until we Flush(). |
| requests_[count_].vmo_offset = relative_block; |
| requests_[count_].dev_offset = absolute_block; |
| requests_[count_].length = nblocks; |
| count_++; |
| |
| if (count_ == MAX_TXN_MESSAGES) { |
| // TODO(smklein): Maybe panic (on write) instead, for metadata? |
| // TODO(smklein): We could buffer more messages than this -- just |
| // send then in MAX_TXN_MESSAGES increments. |
| Flush(); |
| } |
| } |
| |
| // Activate the transaction |
| zx_status_t Flush(); |
| |
| private: |
| TxnHandler* handler_; |
| size_t count_; |
| block_fifo_request_t requests_[MAX_TXN_MESSAGES]; |
| }; |
| |
| template <bool Write, size_t BlockSize, typename TxnHandler> |
| inline zx_status_t BlockTxn<vmoid_t, Write, BlockSize, TxnHandler>::Flush() { |
| for (size_t i = 0; i < count_; i++) { |
| requests_[i].opcode = Write ? BLOCKIO_WRITE : BLOCKIO_READ; |
| requests_[i].vmo_offset *= BlockSize; |
| requests_[i].dev_offset *= BlockSize; |
| requests_[i].length *= BlockSize; |
| } |
| zx_status_t status = ZX_OK; |
| if (count_ != 0) { |
| status = handler_->Txn(requests_, count_); |
| } |
| count_ = 0; |
| return status; |
| } |
| |
| template <size_t BlockSize, typename TxnHandler> |
| using WriteTxn = BlockTxn<vmoid_t, true, BlockSize, TxnHandler>; |
| template <size_t BlockSize, typename TxnHandler> |
| using ReadTxn = BlockTxn<vmoid_t, false, BlockSize, TxnHandler>; |
| |
| #else |
| |
| // To simplify host-side requests, they are written |
| // through immediately, and cannot be buffered. |
| template <bool Write, size_t BlockSize, typename TxnHandler> |
| class BlockTxn<const void*, Write, BlockSize, TxnHandler> { |
| public: |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(BlockTxn); |
| BlockTxn(TxnHandler* handler) : handler_(handler) {} |
| ~BlockTxn() { Flush(); } |
| |
| // Identify that a block should be written to disk |
| // as a later point in time. |
| void Enqueue(const void* id, uint64_t relative_block, |
| uint64_t absolute_block, uint64_t nblocks) { |
| for (size_t b = 0; b < nblocks; b++) { |
| if (Write) { |
| handler_->Writeblk(absolute_block + b, GetBlock<BlockSize>(id, relative_block + b)); |
| } else { |
| handler_->Readblk(absolute_block + b, GetBlock<BlockSize>(id, relative_block + b)); |
| } |
| } |
| } |
| |
| // Activate the transaction (do nothing) |
| zx_status_t Flush() { return ZX_OK; } |
| |
| private: |
| TxnHandler* handler_; |
| }; |
| |
| template <size_t BlockSize, typename TxnHandler> |
| using WriteTxn = BlockTxn<const void*, true, BlockSize, TxnHandler>; |
| template <size_t BlockSize, typename TxnHandler> |
| using ReadTxn = BlockTxn<const void*, false, BlockSize, TxnHandler>; |
| |
| #endif |
| |
| } // namespace fs |