| // Copyright 2018 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. |
| |
| #include "garnet/bin/guest/vmm/device/block_dispatcher.h" |
| |
| #include <bitmap/rle-bitmap.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/zx/vmo.h> |
| |
| #include "garnet/bin/guest/vmm/device/block.h" |
| #include "garnet/bin/guest/vmm/device/qcow.h" |
| |
| static_assert(fuchsia::io::MAX_BUF % kBlockSectorSize == 0, |
| "Maximum buffer size is not a multiple of sector size"); |
| static constexpr size_t kMaxBufSectors = |
| fuchsia::io::MAX_BUF / kBlockSectorSize; |
| |
| // Dispatcher that fulfills block requests using Fuchsia IO. |
| class RawBlockDispatcher : public BlockDispatcher { |
| public: |
| explicit RawBlockDispatcher(fuchsia::io::FilePtr file) |
| : file_(std::move(file)) {} |
| |
| private: |
| fuchsia::io::FilePtr file_; |
| |
| void Sync(Callback callback) override { file_->Sync(std::move(callback)); } |
| |
| void ReadAt(void* data, uint64_t size, uint64_t off, |
| Callback callback) override { |
| auto io_guard = fbl::MakeRefCounted<IoGuard>(std::move(callback)); |
| auto addr = static_cast<uint8_t*>(data); |
| for (uint64_t at = 0; at < size; at += fuchsia::io::MAX_BUF) { |
| auto len = std::min<uint64_t>(size - at, fuchsia::io::MAX_BUF); |
| auto read = [io_guard, len, begin = addr + at](zx_status_t status, |
| std::vector<uint8_t> buf) { |
| if (status != ZX_OK) { |
| io_guard->SetStatus(status); |
| } else if (buf.size() != len) { |
| io_guard->SetStatus(ZX_ERR_IO); |
| } else { |
| memcpy(begin, buf.data(), buf.size()); |
| } |
| }; |
| file_->ReadAt(len, off + at, read); |
| } |
| } |
| |
| void WriteAt(const void* data, uint64_t size, uint64_t off, |
| Callback callback) override { |
| auto io_guard = fbl::MakeRefCounted<IoGuard>(std::move(callback)); |
| auto addr = static_cast<const uint8_t*>(data); |
| for (uint64_t at = 0; at < size; at += fuchsia::io::MAX_BUF) { |
| auto len = std::min<uint64_t>(size - at, fuchsia::io::MAX_BUF); |
| auto write = [io_guard, len](zx_status_t status, uint64_t actual) { |
| if (status != ZX_OK) { |
| io_guard->SetStatus(status); |
| } else if (actual != len) { |
| io_guard->SetStatus(ZX_ERR_IO); |
| } |
| }; |
| auto begin = addr + at; |
| fidl::VectorPtr<uint8_t> buf{{begin, begin + len}}; |
| // TODO(MAC-174): Add support for channel back-pressure. |
| file_->WriteAt(std::move(buf), off + at, write); |
| } |
| } |
| }; |
| |
| // Dispatcher that fulfills block requests using Fuchsia IO and a VMO. |
| class VmoBlockDispatcher : public BlockDispatcher { |
| public: |
| VmoBlockDispatcher(fuchsia::io::FilePtr file, zx::vmo vmo, size_t vmo_size, |
| uintptr_t vmar_addr) |
| : file_(std::move(file)), |
| vmo_(std::move(vmo)), |
| vmo_size_(vmo_size), |
| vmar_addr_(vmar_addr) {} |
| |
| private: |
| fuchsia::io::FilePtr file_; |
| const zx::vmo vmo_; |
| const size_t vmo_size_; |
| const uintptr_t vmar_addr_; |
| |
| void Sync(Callback callback) override { file_->Sync(std::move(callback)); } |
| |
| void ReadAt(void* data, uint64_t size, uint64_t off, |
| Callback callback) override { |
| if (size + off < size || size + off > vmo_size_) { |
| callback(ZX_ERR_OUT_OF_RANGE); |
| return; |
| } |
| memcpy(data, reinterpret_cast<const void*>(vmar_addr_ + off), size); |
| callback(ZX_OK); |
| } |
| |
| void WriteAt(const void* data, uint64_t size, uint64_t off, |
| Callback callback) override { |
| if (size + off < size || size + off > vmo_size_) { |
| callback(ZX_ERR_OUT_OF_RANGE); |
| return; |
| } |
| memcpy(reinterpret_cast<void*>(vmar_addr_ + off), data, size); |
| callback(ZX_OK); |
| } |
| }; |
| |
| void CreateRawBlockDispatcher(fuchsia::io::FilePtr file, uint32_t vmo_flags, |
| NestedBlockDispatcherCallback callback) { |
| auto make_disp = [vmo_flags, callback = std::move(callback)]( |
| fuchsia::io::FilePtr file, size_t size) mutable { |
| file->GetBuffer(vmo_flags, [file = std::move(file), vmo_flags, size, |
| callback = std::move(callback)]( |
| zx_status_t status, |
| fuchsia::mem::BufferPtr buffer) mutable { |
| if (status != ZX_OK) { |
| auto disp = std::make_unique<RawBlockDispatcher>(std::move(file)); |
| callback(size, std::move(disp)); |
| return; |
| } |
| uintptr_t addr; |
| // TODO: Use the |buffer->size| rather than calling |GetAttr|. |
| status = |
| zx::vmar::root_self()->map(0, buffer->vmo, 0, size, vmo_flags, &addr); |
| FXL_CHECK(status == ZX_OK) << "Failed to map VMO " << status; |
| auto disp = std::make_unique<VmoBlockDispatcher>( |
| std::move(file), std::move(buffer->vmo), size, addr); |
| callback(size, std::move(disp)); |
| }); |
| }; |
| file->GetAttr( |
| [file = std::move(file), make_disp = std::move(make_disp)]( |
| zx_status_t status, fuchsia::io::NodeAttributes attrs) mutable { |
| FXL_CHECK(status == ZX_OK) << "Failed to get attributes " << status; |
| make_disp(std::move(file), attrs.content_size); |
| }); |
| } |
| |
| // Dispatcher that retains writes in-memory and delegates reads to another |
| // dispatcher. |
| class VolatileWriteBlockDispatcher : public BlockDispatcher { |
| public: |
| VolatileWriteBlockDispatcher(std::unique_ptr<BlockDispatcher> disp, |
| zx::vmo vmo, size_t vmo_size, |
| uintptr_t vmar_addr) |
| : disp_(std::move(disp)), |
| vmo_(std::move(vmo)), |
| vmo_size_(vmo_size), |
| vmar_addr_(vmar_addr) {} |
| |
| ~VolatileWriteBlockDispatcher() override { |
| zx_status_t status = zx::vmar::root_self()->unmap(vmar_addr_, vmo_size_); |
| FXL_DCHECK(status == ZX_OK); |
| } |
| |
| void Sync(Callback callback) override { |
| // Writes are synchronous, so sync is a no-op. |
| callback(ZX_OK); |
| } |
| |
| void ReadAt(void* data, uint64_t size, uint64_t off, |
| Callback callback) override { |
| if (!IsAccessValid(size, off)) { |
| callback(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| auto io_guard = fbl::MakeRefCounted<IoGuard>(std::move(callback)); |
| auto addr = static_cast<uint8_t*>(data); |
| while (size > 0) { |
| size_t sector = off / kBlockSectorSize; |
| size_t num_sectors = size / kBlockSectorSize; |
| size_t first_sector; |
| bitmap_.Get(sector, sector + num_sectors, &first_sector); |
| bool unallocated = first_sector == sector; |
| if (unallocated) { |
| // Not allocated, therefore calculate maximum unallocated read. |
| num_sectors = std::min(kMaxBufSectors, num_sectors); |
| bitmap_.Find(true, sector, sector + num_sectors, 1, &first_sector); |
| } |
| |
| size_t read_size = (first_sector - sector) * kBlockSectorSize; |
| if (unallocated) { |
| // Not Allocated, delegate to dispatcher. |
| auto callback = [io_guard](zx_status_t status) { |
| if (status != ZX_OK) { |
| io_guard->SetStatus(status); |
| } |
| }; |
| disp_->ReadAt(addr, read_size, off, callback); |
| } else { |
| // Region is at least partially cached. |
| auto mapped_addr = reinterpret_cast<const void*>(vmar_addr_ + off); |
| memcpy(addr, mapped_addr, read_size); |
| } |
| |
| off += read_size; |
| addr += read_size; |
| FXL_DCHECK(size >= read_size); |
| size -= read_size; |
| } |
| } |
| |
| void WriteAt(const void* data, uint64_t size, uint64_t off, |
| Callback callback) override { |
| if (!IsAccessValid(size, off)) { |
| callback(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| size_t sector = off / kBlockSectorSize; |
| size_t num_sectors = size / kBlockSectorSize; |
| zx_status_t status = bitmap_.Set(sector, sector + num_sectors); |
| if (status != ZX_OK) { |
| callback(status); |
| return; |
| } |
| |
| auto mapped_addr = reinterpret_cast<void*>(vmar_addr_ + off); |
| memcpy(mapped_addr, data, size); |
| callback(ZX_OK); |
| } |
| |
| private: |
| std::unique_ptr<BlockDispatcher> disp_; |
| zx::vmo vmo_; |
| const size_t vmo_size_; |
| const uintptr_t vmar_addr_; |
| bitmap::RleBitmap bitmap_; |
| |
| bool IsAccessValid(uint64_t size, uint64_t off) { |
| return size % kBlockSectorSize == 0 && off % kBlockSectorSize == 0 && |
| off < vmo_size_ && size <= vmo_size_ - off; |
| } |
| }; |
| |
| void CreateVolatileWriteBlockDispatcher( |
| size_t vmo_size, std::unique_ptr<BlockDispatcher> base, |
| NestedBlockDispatcherCallback callback) { |
| zx::vmo vmo; |
| zx_status_t status = zx::vmo::create(vmo_size, ZX_VMO_NON_RESIZABLE, &vmo); |
| FXL_CHECK(status == ZX_OK) << "Failed to create VMO " << status; |
| |
| const char name[] = "volatile-block"; |
| status = vmo.set_property(ZX_PROP_NAME, name, sizeof(name)); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to set name of VMO " << status; |
| } |
| |
| uintptr_t addr; |
| status = zx::vmar::root_self()->map( |
| 0, vmo, 0, vmo_size, |
| ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_REQUIRE_NON_RESIZABLE, &addr); |
| FXL_CHECK(status == ZX_OK) << "Failed to map VMO " << status; |
| |
| auto disp = std::make_unique<VolatileWriteBlockDispatcher>( |
| std::move(base), std::move(vmo), vmo_size, addr); |
| callback(vmo_size, std::move(disp)); |
| } |
| |
| // Dispatcher that reads from a QCOW image. |
| class QcowBlockDispatcher : public BlockDispatcher { |
| public: |
| QcowBlockDispatcher(std::unique_ptr<BlockDispatcher> disp, |
| std::unique_ptr<QcowFile> file) |
| : disp_(std::move(disp)), file_(std::move(file)) {} |
| |
| private: |
| std::unique_ptr<BlockDispatcher> disp_; |
| std::unique_ptr<QcowFile> file_; |
| |
| void Sync(Callback callback) override { |
| // Writes are not supported, so sync is a no-op. |
| callback(ZX_OK); |
| } |
| |
| void ReadAt(void* data, uint64_t size, uint64_t off, |
| Callback callback) override { |
| file_->ReadAt(disp_.get(), data, size, off, std::move(callback)); |
| } |
| |
| void WriteAt(const void* data, uint64_t size, uint64_t off, |
| Callback callback) override { |
| callback(ZX_ERR_NOT_SUPPORTED); |
| } |
| }; |
| |
| void CreateQcowBlockDispatcher(std::unique_ptr<BlockDispatcher> base, |
| NestedBlockDispatcherCallback callback) { |
| auto base_ptr = base.get(); |
| auto file = std::make_unique<QcowFile>(); |
| auto file_ptr = file.get(); |
| auto load = [base = std::move(base), file = std::move(file), |
| callback = std::move(callback)](zx_status_t status) mutable { |
| size_t size = file->size(); |
| auto disp = |
| std::make_unique<QcowBlockDispatcher>(std::move(base), std::move(file)); |
| callback(size, std::move(disp)); |
| }; |
| file_ptr->Load(base_ptr, std::move(load)); |
| } |