| // 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 |