blob: 71dff3106961da5f60bb6a688646033c20d916fb [file] [log] [blame]
// 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.
#include <climits>
#include <errno.h>
#include <fcntl.h>
#include <limits>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <threads.h>
#include <time.h>
#include <unistd.h>
#include <block-client/client.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <fbl/array.h>
#include <fbl/unique_fd.h>
#include <fbl/unique_ptr.h>
#include <fuchsia/hardware/block/c/fidl.h>
#include <lib/fzl/fdio.h>
#include <lib/zx/fifo.h>
#include <lib/zx/vmo.h>
#include <pretty/hexdump.h>
#include <unittest/unittest.h>
#include <zircon/device/block.h>
#include <zircon/syscalls.h>
#include <blktest/blktest.h>
namespace tests {
static int get_testdev(uint64_t* blk_size, uint64_t* blk_count) {
const char* blkdev_path = getenv(BLKTEST_BLK_DEV);
ASSERT_NONNULL(blkdev_path, "No test device specified");
// Open the block device
int fd = open(blkdev_path, O_RDWR);
if (fd < 0) {
printf("OPENING BLKDEV (path=%s) FAILURE. Errno: %d\n", blkdev_path, errno);
}
ASSERT_GE(fd, 0, "Could not open block device");
fzl::UnownedFdioCaller disk_caller(fd);
fuchsia_hardware_block_BlockInfo info;
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(disk_caller.borrow_channel(), &status,
&info), ZX_OK);
ASSERT_EQ(status, ZX_OK);
*blk_size = info.block_size;
*blk_count = info.block_count;
return fd;
}
static bool blkdev_test_simple(void) {
BEGIN_TEST;
uint64_t blk_size, blk_count;
fbl::unique_fd fd(get_testdev(&blk_size, &blk_count));
int64_t buffer_size = blk_size * 2;
fbl::AllocChecker checker;
fbl::unique_ptr<uint8_t[]> buf(new (&checker) uint8_t[buffer_size]);
ASSERT_TRUE(checker.check());
fbl::unique_ptr<uint8_t[]> out(new (&checker) uint8_t[buffer_size]);
ASSERT_TRUE(checker.check());
memset(buf.get(), 'a', sizeof(buf));
memset(out.get(), 0, sizeof(out));
// Write three blocks.
ASSERT_EQ(write(fd.get(), buf.get(), buffer_size), buffer_size);
ASSERT_EQ(write(fd.get(), buf.get(), buffer_size / 2), buffer_size / 2);
// Seek to the start of the device and read the contents
ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0, "");
ASSERT_EQ(read(fd.get(), out.get(), buffer_size), buffer_size);
ASSERT_EQ(memcmp(out.get(), buf.get(), buffer_size), 0);
ASSERT_EQ(read(fd.get(), out.get(), buffer_size / 2), buffer_size / 2);
ASSERT_EQ(memcmp(out.get(), buf.get(), buffer_size / 2), 0);
END_TEST;
}
bool blkdev_test_bad_requests(void) {
BEGIN_TEST;
uint64_t blk_size, blk_count;
fbl::unique_fd fd(get_testdev(&blk_size, &blk_count));
fbl::AllocChecker checker;
fbl::unique_ptr<uint8_t[]> buf(new (&checker) uint8_t[blk_size * 4]);
ASSERT_TRUE(checker.check());
memset(buf.get(), 'a', blk_size * 4);
// Read / write non-multiples of the block size
ASSERT_EQ(write(fd.get(), buf.get(), blk_size - 1), -1);
ASSERT_EQ(write(fd.get(), buf.get(), blk_size / 2), -1);
ASSERT_EQ(write(fd.get(), buf.get(), blk_size * 2 - 1), -1);
ASSERT_EQ(read(fd.get(), buf.get(), blk_size - 1), -1);
ASSERT_EQ(read(fd.get(), buf.get(), blk_size / 2), -1);
ASSERT_EQ(read(fd.get(), buf.get(), blk_size * 2 - 1), -1);
// Read / write from unaligned offset
ASSERT_EQ(lseek(fd.get(), 1, SEEK_SET), 1);
ASSERT_EQ(write(fd.get(), buf.get(), blk_size), -1);
ASSERT_EQ(errno, EINVAL);
ASSERT_EQ(read(fd.get(), buf.get(), blk_size), -1);
ASSERT_EQ(errno, EINVAL);
// Read / write from beyond end of device
off_t dev_size = blk_size * blk_count;
ASSERT_EQ(lseek(fd.get(), dev_size, SEEK_SET), dev_size);
ASSERT_EQ(write(fd.get(), buf.get(), blk_size), -1);
ASSERT_EQ(read(fd.get(), buf.get(), blk_size), -1);
END_TEST;
}
#if 0
bool blkdev_test_multiple(void) {
uint8_t buf[PAGE_SIZE];
uint8_t out[PAGE_SIZE];
BEGIN_TEST;
int fd1 = get_testdev("blkdev-test-A", PAGE_SIZE, 512);
int fd2 = get_testdev("blkdev-test-B", PAGE_SIZE, 512);
// Write 'a' to fd1, write 'b', to fd2
memset(buf, 'a', sizeof(buf));
ASSERT_EQ(write(fd1, buf, sizeof(buf)), (ssize_t) sizeof(buf), "");
memset(buf, 'b', sizeof(buf));
ASSERT_EQ(write(fd2, buf, sizeof(buf)), (ssize_t) sizeof(buf), "");
ASSERT_EQ(lseek(fd1, 0, SEEK_SET), 0, "");
ASSERT_EQ(lseek(fd2, 0, SEEK_SET), 0, "");
// Read 'b' from fd2, read 'a' from fd1
ASSERT_EQ(read(fd2, out, sizeof(buf)), (ssize_t) sizeof(buf), "");
ASSERT_EQ(memcmp(out, buf, sizeof(out)), 0, "");
close(fd2);
memset(buf, 'a', sizeof(buf));
ASSERT_EQ(read(fd1, out, sizeof(buf)), (ssize_t) sizeof(buf), "");
ASSERT_EQ(memcmp(out, buf, sizeof(out)), 0, "");
close(fd1);
END_TEST;
}
#endif
bool blkdev_test_fifo_no_op(void) {
// Get a FIFO connection to a blkdev and immediately close it
BEGIN_TEST;
uint64_t blk_size, blk_count;
fbl::unique_fd fd(get_testdev(&blk_size, &blk_count));
fzl::FdioCaller disk_connection(std::move(fd));
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
END_TEST;
}
static void fill_random(uint8_t* buf, uint64_t size) {
for (size_t i = 0; i < size; i++) {
buf[i] = static_cast<uint8_t>(rand());
}
}
bool blkdev_test_fifo_basic(void) {
BEGIN_TEST;
uint64_t blk_size, blk_count;
// Set up the initial handshake connection with the blkdev
fbl::unique_fd fd(get_testdev(&blk_size, &blk_count));
fzl::FdioCaller disk_connection(std::move(fd));
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
groupid_t group = 0;
// Create an arbitrary VMO, fill it with some stuff
const uint64_t vmo_size = blk_size * 3;
zx::vmo vmo;
ASSERT_EQ(zx::vmo::create(vmo_size, 0, &vmo), ZX_OK, "Failed to create VMO");
fbl::unique_ptr<uint8_t[]> buf(new uint8_t[vmo_size]);
fill_random(buf.get(), vmo_size);
ASSERT_EQ(vmo.write(buf.get(), 0, vmo_size), ZX_OK, "");
// Send a handle to the vmo to the block device, get a vmoid which identifies it
zx::vmo xfer_vmo;
ASSERT_EQ(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &xfer_vmo), ZX_OK);
fuchsia_hardware_block_VmoID vmoid;
ASSERT_EQ(fuchsia_hardware_block_BlockAttachVmo(channel->get(), xfer_vmo.release(),
&status, &vmoid), ZX_OK);
ASSERT_EQ(status, ZX_OK);
// Batch write the VMO to the blkdev
// Split it into two requests, spread across the disk
block_fifo_request_t requests[2];
requests[0].group = group;
requests[0].vmoid = vmoid.id;
requests[0].opcode = BLOCKIO_WRITE;
requests[0].length = 1;
requests[0].vmo_offset = 0;
requests[0].dev_offset = 0;
requests[1].group = group;
requests[1].vmoid = vmoid.id;
requests[1].opcode = BLOCKIO_WRITE;
requests[1].length = 2;
requests[1].vmo_offset = 1;
requests[1].dev_offset = 100;
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
ASSERT_EQ(block_fifo_txn(client, &requests[0], fbl::count_of(requests)), ZX_OK, "");
// Empty the vmo, then read the info we just wrote to the disk
fbl::unique_ptr<uint8_t[]> out(new uint8_t[vmo_size]());
ASSERT_EQ(vmo.write(out.get(), 0, vmo_size), ZX_OK, "");
requests[0].opcode = BLOCKIO_READ;
requests[1].opcode = BLOCKIO_READ;
ASSERT_EQ(block_fifo_txn(client, &requests[0], fbl::count_of(requests)), ZX_OK, "");
ASSERT_EQ(vmo.read(out.get(), 0, vmo_size), ZX_OK, "");
ASSERT_EQ(memcmp(buf.get(), out.get(), blk_size * 3), 0, "Read data not equal to written data");
// Close the current vmo
requests[0].opcode = BLOCKIO_CLOSE_VMO;
ASSERT_EQ(block_fifo_txn(client, &requests[0], 1), ZX_OK, "");
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
block_fifo_release_client(client);
END_TEST;
}
bool blkdev_test_fifo_whole_disk(void) {
BEGIN_TEST;
uint64_t blk_size, blk_count;
// Set up the initial handshake connection with the blkdev
fbl::unique_fd fd(get_testdev(&blk_size, &blk_count));
fzl::FdioCaller disk_connection(std::move(fd));
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
groupid_t group = 0;
// Create an arbitrary VMO, fill it with some stuff
uint64_t vmo_size = blk_size * blk_count;
zx::vmo vmo;
ASSERT_EQ(zx::vmo::create(vmo_size, 0, &vmo), ZX_OK, "Failed to create VMO");
fbl::unique_ptr<uint8_t[]> buf(new uint8_t[vmo_size]);
fill_random(buf.get(), vmo_size);
ASSERT_EQ(vmo.write(buf.get(), 0, vmo_size), ZX_OK, "");
// Send a handle to the vmo to the block device, get a vmoid which identifies it
zx::vmo xfer_vmo;
ASSERT_EQ(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &xfer_vmo), ZX_OK);
fuchsia_hardware_block_VmoID vmoid;
ASSERT_EQ(fuchsia_hardware_block_BlockAttachVmo(channel->get(), xfer_vmo.release(),
&status, &vmoid), ZX_OK);
ASSERT_EQ(status, ZX_OK);
// Batch write the VMO to the blkdev
block_fifo_request_t request;
request.group = group;
request.vmoid = vmoid.id;
request.opcode = BLOCKIO_WRITE;
request.length = static_cast<uint32_t>(blk_count);
request.vmo_offset = 0;
request.dev_offset = 0;
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_OK, "");
// Empty the vmo, then read the info we just wrote to the disk
fbl::unique_ptr<uint8_t[]> out(new uint8_t[vmo_size]());
ASSERT_EQ(vmo.write(out.get(), 0, vmo_size), ZX_OK, "");
request.opcode = BLOCKIO_READ;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_OK, "");
ASSERT_EQ(vmo.read(out.get(), 0, vmo_size), ZX_OK, "");
ASSERT_EQ(memcmp(buf.get(), out.get(), blk_size * 3), 0, "Read data not equal to written data");
// Close the current vmo
request.opcode = BLOCKIO_CLOSE_VMO;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_OK, "");
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
block_fifo_release_client(client);
END_TEST;
}
typedef struct {
uint64_t vmo_size;
zx::vmo vmo;
fuchsia_hardware_block_VmoID vmoid;
fbl::unique_ptr<uint8_t[]> buf;
} test_vmo_object_t;
// Creates a VMO, fills it with data, and gives it to the block device.
bool create_vmo_helper(const zx::channel& channel, test_vmo_object_t* obj, size_t kBlockSize) {
obj->vmo_size = kBlockSize + (rand() % 5) * kBlockSize;
ASSERT_EQ(zx::vmo::create(obj->vmo_size, 0, &obj->vmo), ZX_OK, "Failed to create vmo");
obj->buf.reset(new uint8_t[obj->vmo_size]);
fill_random(obj->buf.get(), obj->vmo_size);
ASSERT_EQ(obj->vmo.write(obj->buf.get(), 0, obj->vmo_size), ZX_OK, "Failed to write to vmo");
zx::vmo xfer_vmo;
ASSERT_EQ(obj->vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &xfer_vmo), ZX_OK);
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_BlockAttachVmo(channel.get(), xfer_vmo.release(),
&status, &obj->vmoid), ZX_OK);
ASSERT_EQ(status, ZX_OK);
return true;
}
// Write all vmos in a striped pattern on disk.
// For objs.size() == 10,
// i = 0 will write vmo block 0, 1, 2, 3... to dev block 0, 10, 20, 30...
// i = 1 will write vmo block 0, 1, 2, 3... to dev block 1, 11, 21, 31...
bool write_striped_vmo_helper(fifo_client_t* client, test_vmo_object_t* obj, size_t i, size_t objs,
groupid_t group, size_t kBlockSize) {
// Make a separate request for each block
size_t blocks = obj->vmo_size / kBlockSize;
fbl::Array<block_fifo_request_t> requests(new block_fifo_request_t[blocks], blocks);
for (size_t b = 0; b < blocks; b++) {
requests[b].group = group;
requests[b].vmoid = obj->vmoid.id;
requests[b].opcode = BLOCKIO_WRITE;
requests[b].length = 1;
requests[b].vmo_offset = b;
requests[b].dev_offset = i + b * objs;
}
// Write entire vmos at once
ASSERT_EQ(block_fifo_txn(client, &requests[0], requests.size()), ZX_OK, "");
return true;
}
// Verifies the result from "write_striped_vmo_helper"
bool read_striped_vmo_helper(fifo_client_t* client, test_vmo_object_t* obj, size_t i, size_t objs,
groupid_t group, size_t kBlockSize) {
// First, empty out the VMO
fbl::unique_ptr<uint8_t[]> out(new uint8_t[obj->vmo_size]());
ASSERT_EQ(obj->vmo.write(out.get(), 0, obj->vmo_size), ZX_OK);
// Next, read to the vmo from the disk
size_t blocks = obj->vmo_size / kBlockSize;
fbl::Array<block_fifo_request_t> requests(new block_fifo_request_t[blocks], blocks);
for (size_t b = 0; b < blocks; b++) {
requests[b].group = group;
requests[b].vmoid = obj->vmoid.id;
requests[b].opcode = BLOCKIO_READ;
requests[b].length = 1;
requests[b].vmo_offset = b;
requests[b].dev_offset = i + b * objs;
}
// Read entire vmos at once
ASSERT_EQ(block_fifo_txn(client, &requests[0], requests.size()), ZX_OK, "");
// Finally, write from the vmo to an out buffer, where we can compare
// the results with the input buffer.
ASSERT_EQ(obj->vmo.read(out.get(), 0, obj->vmo_size), ZX_OK);
ASSERT_EQ(memcmp(obj->buf.get(), out.get(), obj->vmo_size), 0,
"Read data not equal to written data");
return true;
}
// Tears down an object created by "create_vmo_helper".
bool close_vmo_helper(fifo_client_t* client, test_vmo_object_t* obj, groupid_t group) {
block_fifo_request_t request;
request.group = group;
request.vmoid = obj->vmoid.id;
request.opcode = BLOCKIO_CLOSE_VMO;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_OK, "");
obj->vmo.reset();
return true;
}
bool blkdev_test_fifo_multiple_vmo(void) {
BEGIN_TEST;
// Set up the initial handshake connection with the blkdev
uint64_t blk_size, blk_count;
fbl::unique_fd fd(get_testdev(&blk_size, &blk_count));
fzl::FdioCaller disk_connection(std::move(fd));
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
groupid_t group = 0;
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
// Create multiple VMOs
fbl::Array<test_vmo_object_t> objs(new test_vmo_object_t[10](), 10);
for (size_t i = 0; i < objs.size(); i++) {
ASSERT_TRUE(create_vmo_helper(*channel, &objs[i], blk_size), "");
}
for (size_t i = 0; i < objs.size(); i++) {
ASSERT_TRUE(write_striped_vmo_helper(client, &objs[i], i, objs.size(), group, blk_size), "");
}
for (size_t i = 0; i < objs.size(); i++) {
ASSERT_TRUE(read_striped_vmo_helper(client, &objs[i], i, objs.size(), group, blk_size), "");
}
for (size_t i = 0; i < objs.size(); i++) {
ASSERT_TRUE(close_vmo_helper(client, &objs[i], group), "");
}
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
block_fifo_release_client(client);
END_TEST;
}
typedef struct {
test_vmo_object_t* obj;
size_t i;
size_t objs;
zx::unowned_channel channel;
fifo_client_t* client;
groupid_t group;
size_t kBlockSize;
} test_thread_arg_t;
int fifo_vmo_thread(void* arg) {
test_thread_arg_t* fifoarg = (test_thread_arg_t*) arg;
test_vmo_object_t* obj = fifoarg->obj;
size_t i = fifoarg->i;
size_t objs = fifoarg->objs;
zx::unowned_channel& channel = fifoarg->channel;
fifo_client_t* client = fifoarg->client;
groupid_t group = fifoarg->group;
size_t kBlockSize = fifoarg->kBlockSize;
ASSERT_TRUE(create_vmo_helper(*channel, obj, kBlockSize), "");
ASSERT_TRUE(write_striped_vmo_helper(client, obj, i, objs, group, kBlockSize), "");
ASSERT_TRUE(read_striped_vmo_helper(client, obj, i, objs, group, kBlockSize), "");
ASSERT_TRUE(close_vmo_helper(client, obj, group), "");
return 0;
}
bool blkdev_test_fifo_multiple_vmo_multithreaded(void) {
BEGIN_TEST;
// Set up the initial handshake connection with the blkdev
uint64_t kBlockSize, blk_count;
fbl::unique_fd fd(get_testdev(&kBlockSize, &blk_count));
fzl::FdioCaller disk_connection(std::move(fd));
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
// Create multiple VMOs
size_t num_threads = MAX_TXN_GROUP_COUNT;
fbl::Array<test_vmo_object_t> objs(new test_vmo_object_t[num_threads](), num_threads);
fbl::Array<thrd_t> threads(new thrd_t[num_threads](), num_threads);
fbl::Array<test_thread_arg_t> thread_args(new test_thread_arg_t[num_threads](),
num_threads);
for (size_t i = 0; i < num_threads; i++) {
// Yes, this does create a bunch of duplicate fields, but it's an easy way to
// transfer some data without creating global variables.
thread_args[i].obj = &objs[i];
thread_args[i].i = i;
thread_args[i].objs = objs.size();
thread_args[i].channel = channel;
thread_args[i].client = client;
thread_args[i].group = static_cast<groupid_t>(i);
thread_args[i].kBlockSize = kBlockSize;
ASSERT_EQ(thrd_create(&threads[i], fifo_vmo_thread, &thread_args[i]),
thrd_success, "");
}
for (size_t i = 0; i < num_threads; i++) {
int res;
ASSERT_EQ(thrd_join(threads[i], &res), thrd_success, "");
ASSERT_EQ(res, 0, "");
}
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
block_fifo_release_client(client);
END_TEST;
}
bool blkdev_test_fifo_unclean_shutdown(void) {
BEGIN_TEST;
// Set up the blkdev
uint64_t kBlockSize, blk_count;
fbl::unique_fd fd(get_testdev(&kBlockSize, &blk_count));
fbl::Array<test_vmo_object_t> objs(new test_vmo_object_t[10](), 10);
groupid_t group = 0;
{
fzl::UnownedFdioCaller disk_connection(fd.get());
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
// Create multiple VMOs
for (size_t i = 0; i < objs.size(); i++) {
ASSERT_TRUE(create_vmo_helper(*channel, &objs[i], kBlockSize), "");
}
// Now that we've set up the connection for a few VMOs, shut down the fifo
block_fifo_release_client(client);
}
// Give the block server a moment to realize our side of the fifo has been closed
usleep(10000);
// The block server should still be functioning. We should be able to re-bind to it
{
fzl::UnownedFdioCaller disk_connection(fd.get());
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
for (size_t i = 0; i < objs.size(); i++) {
ASSERT_TRUE(create_vmo_helper(*channel, &objs[i], kBlockSize), "");
}
for (size_t i = 0; i < objs.size(); i++) {
ASSERT_TRUE(write_striped_vmo_helper(client, &objs[i], i, objs.size(), group,
kBlockSize), "");
}
for (size_t i = 0; i < objs.size(); i++) {
ASSERT_TRUE(read_striped_vmo_helper(client, &objs[i], i, objs.size(), group,
kBlockSize), "");
}
for (size_t i = 0; i < objs.size(); i++) {
ASSERT_TRUE(close_vmo_helper(client, &objs[i], group), "");
}
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
block_fifo_release_client(client);
}
END_TEST;
}
bool blkdev_test_fifo_bad_client_vmoid(void) {
// Try to flex the server's error handling by sending 'malicious' client requests.
BEGIN_TEST;
// Set up the blkdev
uint64_t kBlockSize, blk_count;
fbl::unique_fd fd(get_testdev(&kBlockSize, &blk_count));
fzl::FdioCaller disk_connection(std::move(fd));
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
groupid_t group = 0;
// Create a vmo
test_vmo_object_t obj;
ASSERT_TRUE(create_vmo_helper(*channel, &obj, kBlockSize), "");
// Bad request: Writing to the wrong vmoid
block_fifo_request_t request;
request.group = group;
request.vmoid = static_cast<vmoid_t>(obj.vmoid.id + 5);
request.opcode = BLOCKIO_WRITE;
request.length = 1;
request.vmo_offset = 0;
request.dev_offset = 0;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_ERR_IO, "Expected IO error with bad vmoid");
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
block_fifo_release_client(client);
END_TEST;
}
bool blkdev_test_fifo_bad_client_unaligned_request(void) {
// Try to flex the server's error handling by sending 'malicious' client requests.
BEGIN_TEST;
// Set up the blkdev
uint64_t kBlockSize, blk_count;
fbl::unique_fd fd(get_testdev(&kBlockSize, &blk_count));
fzl::FdioCaller disk_connection(std::move(fd));
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
groupid_t group = 0;
// Create a vmo of at least size "kBlockSize * 2", since we'll
// be reading "kBlockSize" bytes from an offset below, and we want it
// to fit within the bounds of the VMO.
test_vmo_object_t obj;
ASSERT_TRUE(create_vmo_helper(*channel, &obj, kBlockSize * 2), "");
block_fifo_request_t request;
request.group = group;
request.vmoid = static_cast<vmoid_t>(obj.vmoid.id);
request.opcode = BLOCKIO_WRITE;
// Send a request that has zero length
request.length = 0;
request.vmo_offset = 0;
request.dev_offset = 0;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_ERR_INVALID_ARGS, "");
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
block_fifo_release_client(client);
END_TEST;
}
bool blkdev_test_fifo_bad_client_overflow(void) {
// Try to flex the server's error handling by sending 'malicious' client requests.
BEGIN_TEST;
// Set up the blkdev
uint64_t kBlockSize, blk_count;
fbl::unique_fd fd(get_testdev(&kBlockSize, &blk_count));
fzl::FdioCaller disk_connection(std::move(fd));
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
groupid_t group = 0;
// Create a vmo of at least size "kBlockSize * 2", since we'll
// be reading "kBlockSize" bytes from an offset below, and we want it
// to fit within the bounds of the VMO.
test_vmo_object_t obj;
ASSERT_TRUE(create_vmo_helper(*channel, &obj, kBlockSize * 2), "");
block_fifo_request_t request;
request.group = group;
request.vmoid = static_cast<vmoid_t>(obj.vmoid.id);
request.opcode = BLOCKIO_WRITE;
// Send a request that is barely out-of-bounds for the device
request.length = 1;
request.vmo_offset = 0;
request.dev_offset = blk_count;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_ERR_OUT_OF_RANGE);
// Send a request that is half out-of-bounds for the device
request.length = 2;
request.vmo_offset = 0;
request.dev_offset = blk_count - 1;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_ERR_OUT_OF_RANGE);
// Send a request that is very out-of-bounds for the device
request.length = 1;
request.vmo_offset = 0;
request.dev_offset = blk_count + 1;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_ERR_OUT_OF_RANGE);
// Send a request that tries to overflow the VMO
request.length = 2;
request.vmo_offset = std::numeric_limits<uint64_t>::max();
request.dev_offset = 0;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_ERR_OUT_OF_RANGE);
// Send a request that tries to overflow the device
request.length = 2;
request.vmo_offset = 0;
request.dev_offset = std::numeric_limits<uint64_t>::max();
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_ERR_OUT_OF_RANGE);
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
block_fifo_release_client(client);
END_TEST;
}
bool blkdev_test_fifo_bad_client_bad_vmo(void) {
// Try to flex the server's error handling by sending 'malicious' client requests.
BEGIN_TEST;
// Set up the blkdev
uint64_t kBlockSize, blk_count;
fbl::unique_fd fd(get_testdev(&kBlockSize, &blk_count));
fzl::FdioCaller disk_connection(std::move(fd));
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(fuchsia_hardware_block_BlockGetFifo(channel->get(), &status,
fifo.reset_and_get_address()), ZX_OK);
ASSERT_EQ(status, ZX_OK);
fifo_client_t* client;
ASSERT_EQ(block_fifo_create_client(fifo.release(), &client), ZX_OK, "");
groupid_t group = 0;
// Create a vmo of one block.
//
// The underlying VMO may be rounded up to the nearest PAGE_SIZE.
test_vmo_object_t obj;
obj.vmo_size = kBlockSize;
ASSERT_EQ(zx::vmo::create(obj.vmo_size, 0, &obj.vmo), ZX_OK, "Failed to create vmo");
obj.buf.reset(new uint8_t[obj.vmo_size]);
fill_random(obj.buf.get(), obj.vmo_size);
ASSERT_EQ(obj.vmo.write(obj.buf.get(), 0, obj.vmo_size), ZX_OK, "Failed to write to vmo");
zx::vmo xfer_vmo;
ASSERT_EQ(obj.vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &xfer_vmo), ZX_OK);
ASSERT_EQ(fuchsia_hardware_block_BlockAttachVmo(channel->get(), xfer_vmo.release(),
&status, &obj.vmoid), ZX_OK);
ASSERT_EQ(status, ZX_OK);
// Send a request to write to write multiple blocks -- enough that
// the request is larger than the VMO.
const uint64_t length = 1 + (fbl::round_up(obj.vmo_size,
static_cast<uint64_t>(PAGE_SIZE))
/ kBlockSize);
block_fifo_request_t request;
request.group = group;
request.vmoid = static_cast<vmoid_t>(obj.vmoid.id);
request.opcode = BLOCKIO_WRITE;
request.length = static_cast<uint32_t>(length);
request.vmo_offset = 0;
request.dev_offset = 0;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_ERR_OUT_OF_RANGE, "");
// Do the same thing, but for reading
request.opcode = BLOCKIO_READ;
ASSERT_EQ(block_fifo_txn(client, &request, 1), ZX_ERR_OUT_OF_RANGE, "");
ASSERT_EQ(fuchsia_hardware_block_BlockCloseFifo(channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
block_fifo_release_client(client);
END_TEST;
}
BEGIN_TEST_CASE(blkdev_tests)
RUN_TEST(blkdev_test_simple)
RUN_TEST(blkdev_test_bad_requests)
#if 0
RUN_TEST(blkdev_test_multiple)
#endif
RUN_TEST(blkdev_test_fifo_no_op)
RUN_TEST(blkdev_test_fifo_basic)
//RUN_TEST(blkdev_test_fifo_whole_disk)
RUN_TEST(blkdev_test_fifo_multiple_vmo)
RUN_TEST(blkdev_test_fifo_multiple_vmo_multithreaded)
// TODO(smklein): Test ops across different vmos
RUN_TEST(blkdev_test_fifo_unclean_shutdown)
RUN_TEST(blkdev_test_fifo_bad_client_vmoid)
RUN_TEST(blkdev_test_fifo_bad_client_unaligned_request)
RUN_TEST(blkdev_test_fifo_bad_client_overflow)
RUN_TEST(blkdev_test_fifo_bad_client_bad_vmo)
END_TEST_CASE(blkdev_tests)
} // namespace tests