blob: 91ecbf291fa3cb1a01a3e7034f5615b9179c39fc [file] [log] [blame]
// Copyright 2020 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 "flash_stress.h"
#include <fuchsia/hardware/block/cpp/fidl.h>
#include <fuchsia/hardware/block/volume/cpp/fidl.h>
#include <inttypes.h>
#include <lib/fdio/directory.h>
#include <lib/zx/clock.h>
#include <lib/zx/fifo.h>
#include <lib/zx/time.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <string>
#include <fbl/unique_fd.h>
#include <fs-management/fvm.h>
#include <src/lib/uuid/uuid.h>
#include "status.h"
#include "util.h"
namespace hwstress {
namespace {
constexpr uint32_t kSectorSize = 512;
constexpr uint32_t kTransferSize = 512 * 1024;
constexpr uint32_t kMinFvmFreeSpace = 100 * 1024 * 1024;
constexpr uint32_t kMinPartitionFreeSpace = 2 * 1024 * 1024;
struct BlockDevice {
fuchsia::hardware::block::BlockSyncPtr device; // Connection to the block device.
zx::fifo fifo; // FIFO used to read/write to the block device.
fuchsia::hardware::block::BlockInfo info; // Details about the block device.
zx::vmo vmo; // Shared VMO with the block device.
zx_vaddr_t vmo_addr; // Where |vmo| is mapped into our address space.
size_t vmo_size; // Size of |vmo| in bytes.
fuchsia::hardware::block::VmoId vmoid; // Identifier the used to refer to the VMO
// when communicating with the block device.
};
void WriteSectorData(zx_vaddr_t start, uint64_t value) {
uint64_t num_words = kSectorSize / sizeof(value);
uint64_t* data = reinterpret_cast<uint64_t*>(start);
for (uint64_t i = 0; i < num_words; i++) {
data[i] = value;
}
}
void VerifySectorData(zx_vaddr_t start, uint64_t value) {
uint64_t num_words = kSectorSize / sizeof(value);
uint64_t* data = reinterpret_cast<uint64_t*>(start);
for (uint64_t i = 0; i < num_words; i++) {
if (unlikely(data[i] != value)) {
ZX_PANIC("Found error: expected 0x%016" PRIX64 ", got 0x%016" PRIX64 " at offset %" PRIu64
"\n",
value, data[i], value * kSectorSize + i * sizeof(value));
}
}
}
zx_status_t OpenBlockDevice(const char* path, size_t vmo_size, BlockDevice* block_device) {
BlockDevice result{};
result.vmo_size = vmo_size;
// Create a channel, and connect to block device.
zx::channel client, server;
zx_status_t status = zx::channel::create(0, &client, &server);
if (status != ZX_OK) {
return status;
}
status = fdio_service_connect(path, server.release());
if (status != ZX_OK) {
return status;
}
result.device.Bind(std::move(client));
// Fetch information about the underlying block device, such as block size.
std::unique_ptr<fuchsia::hardware::block::BlockInfo> info;
zx_status_t io_status = result.device->GetInfo(&status, &info);
if (io_status != ZX_OK || status != ZX_OK) {
fprintf(stderr, "error: cannot get block device info for '%s'\n", path);
return ZX_ERR_INTERNAL;
}
result.info = *info;
// Fetch a FIFO for communicating with the block device over.
io_status = result.device->GetFifo(&status, &result.fifo);
if (io_status != ZX_OK || status != ZX_OK) {
fprintf(stderr, "error: cannot get fifo for '%s'\n", path);
return ZX_ERR_INTERNAL;
}
// Setup a shared VMO with the block device.
status = zx::vmo::create(vmo_size, /*options=*/0, &result.vmo);
if (status != ZX_OK) {
fprintf(stderr, "error: could not allocate memory: %s\n", zx_status_get_string(status));
return status;
}
zx::vmo shared_vmo;
status = result.vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &shared_vmo);
if (status != ZX_OK) {
fprintf(stderr, "error: cannot duplicate handle: %s\n", zx_status_get_string(status));
return status;
}
std::unique_ptr<fuchsia::hardware::block::VmoId> vmo_id;
io_status = result.device->AttachVmo(std::move(shared_vmo), &status, &vmo_id);
if (io_status != ZX_OK || status != ZX_OK || vmo_id == nullptr) {
fprintf(stderr, "error: cannot attach vmo for '%s'\n", path);
return ZX_ERR_INTERNAL;
}
result.vmoid = *vmo_id;
*block_device = std::move(result);
return ZX_OK;
}
zx_status_t SendCommandBlocking(const zx::fifo& fifo, const block_fifo_request_t& request) {
zx_status_t r;
while (true) {
r = fifo.write(sizeof(request), &request, 1, NULL);
if (r == ZX_OK) {
break;
}
if (r != ZX_ERR_SHOULD_WAIT) {
fprintf(stderr, "error: failed writing fifo: %s\n", zx_status_get_string(r));
return r;
}
r = fifo.wait_one(ZX_FIFO_WRITABLE | ZX_FIFO_PEER_CLOSED, zx::time(ZX_TIME_INFINITE), NULL);
if (r != ZX_OK) {
fprintf(stderr, "failed waiting for fifo: %s\n", zx_status_get_string(r));
return r;
}
}
block_fifo_response_t resp;
while (true) {
r = fifo.read(sizeof(resp), &resp, 1, NULL);
if (r == ZX_OK) {
if (resp.status == ZX_OK) {
break;
}
fprintf(stderr, "error: io txn failed: %s\n", zx_status_get_string(resp.status));
return resp.status;
}
if (r != ZX_ERR_SHOULD_WAIT) {
fprintf(stderr, "error: failed reading fifo: %s\n", zx_status_get_string(r));
return r;
}
r = fifo.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::time(ZX_TIME_INFINITE), NULL);
if (r != ZX_OK) {
fprintf(stderr, "failed waiting for fifo: %s\n", zx_status_get_string(r));
return r;
}
}
return ZX_OK;
}
zx_status_t FlashIo(const BlockDevice& device, size_t bytes_to_test, bool is_write_test) {
size_t vmo_byte_offset = 0;
size_t bytes_per_request =
kTransferSize > device.info.max_transfer_size ? device.info.max_transfer_size : kTransferSize;
size_t count = bytes_to_test / bytes_per_request;
size_t num_sectors = kTransferSize / kSectorSize;
size_t blksize = device.info.block_size;
size_t dev_off = 0;
uint64_t word = 0;
reqid_t next_reqid = 0;
while (count > 0) {
if (is_write_test) {
for (size_t i = 0; i < num_sectors; i++) {
WriteSectorData(device.vmo_addr + vmo_byte_offset + kSectorSize * i, word++);
}
}
block_fifo_request_t req = {};
req.reqid = next_reqid++;
req.vmoid = device.vmoid.id;
req.opcode = is_write_test ? BLOCKIO_WRITE : BLOCKIO_READ;
// |length|, |vmo_offset|, and |dev_offset| are measured in blocks.
req.length = static_cast<uint32_t>(bytes_per_request / blksize);
req.vmo_offset = vmo_byte_offset / blksize;
req.dev_offset = dev_off / blksize;
if (zx_status_t r = SendCommandBlocking(device.fifo, req); r != ZX_OK) {
return r;
}
if (!is_write_test) {
for (size_t i = 0; i < num_sectors; i++) {
VerifySectorData(device.vmo_addr + vmo_byte_offset + kSectorSize * i, word++);
}
}
dev_off += bytes_per_request;
vmo_byte_offset += bytes_per_request;
if ((vmo_byte_offset + bytes_per_request) > device.vmo_size) {
vmo_byte_offset = 0;
}
count--;
}
return ZX_OK;
}
} // namespace
std::unique_ptr<TemporaryFvmPartition> TemporaryFvmPartition::Create(int fvm_fd,
uint64_t slices_requested) {
uuid::Uuid unique_guid = uuid::Uuid::Generate();
alloc_req_t request{.slice_count = slices_requested,
.name = "flash-test-fs",
.flags = fuchsia::hardware::block::volume::ALLOCATE_PARTITION_FLAG_INACTIVE};
memcpy(request.guid, unique_guid.bytes(), sizeof(request.guid));
memcpy(request.type, kTestPartGUID.bytes(), sizeof(request.type));
// Create a new partition.
fbl::unique_fd fd(fvm_allocate_partition(fvm_fd, &request));
if (!fd) {
fprintf(stderr, "Error: Could not allocate and open FVM partition\n");
return nullptr;
}
char partition_path[PATH_MAX];
fd.reset(open_partition(unique_guid.bytes(), kTestPartGUID.bytes(), 0, partition_path));
if (!fd) {
destroy_partition(unique_guid.bytes(), kTestPartGUID.bytes());
fprintf(stderr, "Could not locate FVM partition\n");
return nullptr;
}
return std::unique_ptr<TemporaryFvmPartition>(
new TemporaryFvmPartition(partition_path, unique_guid));
}
TemporaryFvmPartition::TemporaryFvmPartition(std::string partition_path, uuid::Uuid unique_guid)
: partition_path_(partition_path), unique_guid_(unique_guid) {}
TemporaryFvmPartition::~TemporaryFvmPartition() {
ZX_ASSERT(destroy_partition(unique_guid_.bytes(), kTestPartGUID.bytes()) == ZX_OK);
}
std::string TemporaryFvmPartition::GetPartitionPath() { return partition_path_; }
// Start a stress test.
bool StressFlash(StatusLine* status, const CommandLineArgs& args, zx::duration duration) {
// Access the FVM.
fbl::unique_fd fvm_fd(open(args.fvm_path.c_str(), O_RDWR));
if (!fvm_fd) {
status->Log("Error: Could not open FVM\n");
return false;
}
// Calculate available space and number of slices needed.
fuchsia_hardware_block_volume_VolumeInfo info;
if (fvm_query(fvm_fd.get(), &info) != ZX_OK) {
status->Log("Error: Could not get FVM info\n");
return false;
}
// Default to using all available disk space.
uint64_t slices_available = info.pslice_total_count - info.pslice_allocated_count;
uint64_t bytes_to_test = slices_available * info.slice_size -
RoundUp(kMinFvmFreeSpace, info.slice_size) - kMinPartitionFreeSpace;
// If a value was specified and does not exceed the free disk space, use that.
if (args.mem_to_test_megabytes.has_value()) {
uint64_t bytes_requested = args.mem_to_test_megabytes.value() * 1024 * 1024;
if (bytes_requested <= bytes_to_test) {
bytes_to_test = bytes_requested;
} else {
status->Log("Specified disk size (%ld bytes) exceeds available disk size (%ld bytes).\n",
bytes_requested, bytes_to_test);
return false;
}
}
uint64_t slices_requested = RoundUp(bytes_to_test, info.slice_size) / info.slice_size;
std::unique_ptr<TemporaryFvmPartition> fvm_partition =
TemporaryFvmPartition::Create(fvm_fd.get(), slices_requested);
if (fvm_partition == nullptr) {
status->Log("Failed to create FVM partition");
return false;
}
std::string partition_path = fvm_partition->GetPartitionPath();
uint64_t vmo_size = info.slice_size;
BlockDevice device;
if (OpenBlockDevice(partition_path.c_str(), vmo_size, &device) != ZX_OK) {
status->Log("Error: Block device could not be opened");
return false;
}
// Map the VMO into memory.
zx_status_t rstatus = zx::vmar::root_self()->map(
/*vmar_offset=*/0, device.vmo, /*vmo_offset=*/0, /*len=*/vmo_size,
/*options=*/(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_MAP_RANGE), &device.vmo_addr);
if (rstatus != ZX_OK) {
status->Log("Error: VMO could not be mapped into memory");
return false;
}
zx::time end_time = zx::deadline_after(duration);
uint64_t num_tests = 1;
do {
zx::time test_start = zx::clock::get_monotonic();
if (FlashIo(device, bytes_to_test, /*is_write_test=*/true) != ZX_OK) {
status->Log("Error writing to vmo.");
return false;
}
zx::duration test_duration = zx::clock::get_monotonic() - test_start;
status->Log("Test %4ld: Write: %0.3fs, throughput: %0.2f MiB/s", num_tests,
DurationToSecs(test_duration),
bytes_to_test / (DurationToSecs(test_duration) * 1024 * 1024));
test_start = zx::clock::get_monotonic();
if (FlashIo(device, bytes_to_test, /*is_write_test=*/false) != ZX_OK) {
status->Log("Error reading from vmo.");
return false;
}
test_duration = zx::clock::get_monotonic() - test_start;
status->Log("Test %4ld: Read: %0.3fs, throughput: %0.2f MiB/s", num_tests,
DurationToSecs(test_duration),
bytes_to_test / (DurationToSecs(test_duration) * 1024 * 1024));
num_tests++;
} while (zx::clock::get_monotonic() < end_time);
return true;
}
void DestroyFlashTestPartitions(StatusLine* status) {
uint32_t count = 0;
// Remove any partitions from previous tests
while (destroy_partition(nullptr, kTestPartGUID.bytes()) == ZX_OK) {
count++;
}
status->Log("Deleted %u partitions", count);
}
} // namespace hwstress