blob: 054ab581b331af11c8095b2cacf93cba0b79f189 [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 <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fbl/unique_ptr.h>
#include <hypervisor/vcpu.h>
#include <machina/block.h>
#include <machina/virtio.h>
#include <unittest/unittest.h>
#include <virtio/block.h>
#include <virtio/virtio_ring.h>
#include "virtio_queue_fake.h"
#define QUEUE_SIZE 8u
#define DATA_SIZE 512u
class VirtioBlockTest {
public:
VirtioBlockTest() : block_(0, UINTPTR_MAX), queue_(&block_.queue()) {}
~VirtioBlockTest() {
if (fd_ > 0)
close(fd_);
}
zx_status_t Init(char* block_path) {
fd_ = CreateBlockFile(block_path);
if (fd_ < 0)
return ZX_ERR_IO;
PhysMem phys_mem;
zx_status_t status = block_.Init(block_path, phys_mem);
if (status != ZX_OK)
return status;
return queue_.Init(QUEUE_SIZE);
}
zx_status_t WriteSector(uint8_t value, uint32_t sector, size_t len) {
if (len > VirtioBlock::kSectorSize)
return ZX_ERR_OUT_OF_RANGE;
uint8_t buffer[VirtioBlock::kSectorSize];
memset(buffer, value, len);
ssize_t ret = lseek(fd_, sector * VirtioBlock::kSectorSize, SEEK_SET);
if (ret < 0)
return ZX_ERR_IO;
ret = write(fd_, buffer, len);
if (ret < 0)
return ZX_ERR_IO;
return ZX_OK;
}
VirtioBlock& block() { return block_; }
VirtioQueueFake& queue() { return queue_; }
int fd() const { return fd_; }
private:
int CreateBlockFile(char* path) {
int fd = mkstemp(path);
if (fd >= 0) {
uint8_t zeroes[VirtioBlock::kSectorSize * 8];
memset(zeroes, 0, sizeof(zeroes));
ssize_t ret = write(fd, zeroes, sizeof(zeroes));
if (ret < 0)
return static_cast<int>(ret);
}
return fd;
}
int fd_ = 0;
VirtioBlock block_;
VirtioQueueFake queue_;
};
static bool file_block_device_bad_header(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-bad-header.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t req = {};
uint8_t status;
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&req, sizeof(req) - 1)
.AppendWriteable(&status, 1)
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
ASSERT_EQ(status, VIRTIO_BLK_S_IOERR);
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&req, sizeof(req) + 1)
.AppendWriteable(&status, 1)
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
ASSERT_EQ(status, VIRTIO_BLK_S_IOERR);
END_TEST;
}
static bool file_block_device_bad_payload(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-bad-payload.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t req = {};
uint8_t status;
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&req, sizeof(req))
.AppendReadable(UINTPTR_MAX, 1)
.AppendWriteable(&status, 1)
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
END_TEST;
}
static bool file_block_device_bad_status(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-bad-status.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t header = {};
uint8_t data[DATA_SIZE];
uint8_t status = 0xff;
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&header, sizeof(header))
.AppendReadable(data, DATA_SIZE)
.AppendReadable(&status, 0)
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
ASSERT_EQ(status, 0xff);
END_TEST;
}
static bool file_block_device_bad_request(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-bad-request.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
// Build a request with an invalid 'type'. The device will handle the
// request successfully but indicate an error to the driver via the
// status field in the request.
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t header = {};
uint8_t data[DATA_SIZE];
uint8_t status = 0;
header.type = UINT32_MAX;
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&header, sizeof(header))
.AppendReadable(data, sizeof(data))
.AppendWriteable(&status, sizeof(status))
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
ASSERT_EQ(status, VIRTIO_BLK_S_UNSUPP);
END_TEST;
}
static bool file_block_device_bad_flush(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-bad-flush.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t req = {};
req.type = VIRTIO_BLK_T_FLUSH;
req.sector = 1;
uint8_t status;
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&req, sizeof(req))
.AppendWriteable(&status, sizeof(status))
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
ASSERT_EQ(status, VIRTIO_BLK_S_IOERR);
END_TEST;
}
static bool file_block_device_read(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-read.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t header = {};
uint8_t data[DATA_SIZE];
uint8_t status = 0;
header.type = VIRTIO_BLK_T_IN;
memset(data, UINT8_MAX, sizeof(data));
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&header, sizeof(header))
.AppendWriteable(data, sizeof(data))
.AppendWriteable(&status, sizeof(status))
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
uint8_t expected[DATA_SIZE];
ASSERT_EQ(status, VIRTIO_BLK_S_OK);
memset(expected, 0, DATA_SIZE);
ASSERT_EQ(memcmp(data, expected, DATA_SIZE), 0);
END_TEST;
}
static bool file_block_device_read_chain(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-read-chain.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t header = {};
uint8_t data1[DATA_SIZE];
uint8_t data2[DATA_SIZE];
uint8_t status = 0;
header.type = VIRTIO_BLK_T_IN;
memset(data1, UINT8_MAX, sizeof(data1));
memset(data2, UINT8_MAX, sizeof(data2));
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&header, sizeof(header))
.AppendWriteable(data1, sizeof(data1))
.AppendWriteable(data2, sizeof(data2))
.AppendWriteable(&status, sizeof(status))
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
uint8_t expected[DATA_SIZE];
memset(expected, 0, DATA_SIZE);
ASSERT_EQ(status, VIRTIO_BLK_S_OK);
ASSERT_EQ(memcmp(data1, expected, DATA_SIZE), 0);
ASSERT_EQ(memcmp(data2, expected, DATA_SIZE), 0);
ASSERT_EQ(used, sizeof(data1) + sizeof(data2) + sizeof(status));
END_TEST;
}
static bool file_block_device_write(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-write.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t header = {};
uint8_t data[DATA_SIZE];
uint8_t status = 0;
header.type = VIRTIO_BLK_T_OUT;
memset(data, UINT8_MAX, sizeof(data));
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&header, sizeof(header))
.AppendReadable(data, sizeof(data))
.AppendWriteable(&status, sizeof(status))
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
int fd = test.fd();
uint8_t actual[DATA_SIZE];
ASSERT_EQ(status, VIRTIO_BLK_S_OK);
ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
ASSERT_EQ(read(fd, actual, DATA_SIZE), DATA_SIZE);
uint8_t expected[DATA_SIZE];
memset(expected, UINT8_MAX, DATA_SIZE);
ASSERT_EQ(memcmp(actual, expected, DATA_SIZE), 0);
END_TEST;
}
static bool file_block_device_write_chain(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-write-chain.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t header = {};
uint8_t data1[DATA_SIZE];
uint8_t data2[DATA_SIZE];
uint8_t status = 0;
header.type = VIRTIO_BLK_T_OUT;
memset(data1, UINT8_MAX, sizeof(data1));
memset(data2, UINT8_MAX, sizeof(data2));
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&header, sizeof(header))
.AppendReadable(data1, sizeof(data1))
.AppendReadable(data2, sizeof(data2))
.AppendWriteable(&status, sizeof(status))
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
int fd = test.fd();
uint8_t actual[DATA_SIZE];
ASSERT_EQ(status, VIRTIO_BLK_S_OK);
ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
ASSERT_EQ(read(fd, actual, DATA_SIZE), DATA_SIZE);
uint8_t expected[DATA_SIZE];
memset(expected, UINT8_MAX, DATA_SIZE);
ASSERT_EQ(memcmp(actual, expected, DATA_SIZE), 0);
ASSERT_EQ(used, sizeof(status));
END_TEST;
}
static bool file_block_device_flush(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-flush.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t header = {};
header.type = VIRTIO_BLK_T_FLUSH;
uint8_t status = 0;
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&header, sizeof(header))
.AppendWriteable(&status, sizeof(status))
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
ASSERT_EQ(status, VIRTIO_BLK_S_OK);
END_TEST;
}
static bool file_block_device_flush_data(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-flush-data.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t header = {};
uint8_t data[DATA_SIZE];
uint8_t status = 0;
header.type = VIRTIO_BLK_T_FLUSH;
memset(data, UINT8_MAX, sizeof(data));
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&header, sizeof(header))
.AppendWriteable(data, sizeof(data))
.AppendWriteable(&status, sizeof(status))
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
ASSERT_EQ(status, VIRTIO_BLK_S_OK);
ASSERT_EQ(used, sizeof(status));
END_TEST;
}
struct TestBlockRequest {
uint16_t desc;
uint32_t used;
virtio_blk_req_t header;
uint8_t data[DATA_SIZE];
uint8_t status;
};
// Queue up 2 read requests for different sectors and verify both will be
// handled correctly.
static bool file_block_device_multiple_descriptors(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-multiple-descriptors.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
// Request 1 (descriptors 0,1,2).
TestBlockRequest request1;
const uint8_t request1_bitpattern = 0xaa;
memset(request1.data, UINT8_MAX, DATA_SIZE);
request1.used = 0;
request1.header.type = VIRTIO_BLK_T_IN;
request1.header.sector = 0;
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&request1.header, sizeof(request1.header))
.AppendWriteable(request1.data, sizeof(request1.data))
.AppendWriteable(&request1.status, sizeof(request1.status))
.Build(&request1.desc),
ZX_OK);
// Request 2 (descriptors 3,4,5).
TestBlockRequest request2;
const uint8_t request2_bitpattern = 0xdd;
memset(request2.data, UINT8_MAX, DATA_SIZE);
request2.used = 0;
request2.header.type = VIRTIO_BLK_T_IN;
request2.header.sector = 1;
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&request2.header, sizeof(request2.header))
.AppendWriteable(request2.data, sizeof(request2.data))
.AppendWriteable(&request2.status, sizeof(request2.status))
.Build(&request2.desc),
ZX_OK);
// Initalize block device. Write unique bit patterns to sector 1 and 2.
ASSERT_EQ(test.WriteSector(request1_bitpattern, 0, DATA_SIZE), ZX_OK);
ASSERT_EQ(test.WriteSector(request2_bitpattern, 1, DATA_SIZE), ZX_OK);
ASSERT_EQ(
test.block().HandleBlockRequest(&test.block().queue(), request1.desc, &request1.used),
ZX_OK);
ASSERT_EQ(
test.block().HandleBlockRequest(&test.block().queue(), request2.desc, &request2.used),
ZX_OK);
// Verify request 1.
uint8_t expected[DATA_SIZE];
memset(expected, request1_bitpattern, DATA_SIZE);
ASSERT_EQ(memcmp(request1.data, expected, DATA_SIZE), 0);
ASSERT_EQ(request1.status, VIRTIO_BLK_S_OK);
ASSERT_EQ(request1.used, DATA_SIZE + sizeof(request1.status));
// Verify request 2.
memset(expected, request2_bitpattern, DATA_SIZE);
ASSERT_EQ(memcmp(request2.data, expected, DATA_SIZE), 0);
ASSERT_EQ(request2.status, VIRTIO_BLK_S_OK);
ASSERT_EQ(request2.used, DATA_SIZE + sizeof(request2.status));
END_TEST;
}
static bool file_block_device_read_only(void) {
BEGIN_TEST;
char path[] = "/tmp/file-block-device-read-only.XXXXXX";
VirtioBlockTest test;
ASSERT_EQ(test.Init(path), ZX_OK);
uint16_t desc;
uint32_t used = 0;
virtio_blk_req_t header = {};
uint8_t data[DATA_SIZE];
uint8_t status = 0;
header.type = VIRTIO_BLK_T_OUT;
test.block().add_device_features(VIRTIO_BLK_F_RO);
ASSERT_EQ(
test.queue().BuildDescriptor()
.AppendReadable(&header, sizeof(header))
.AppendReadable(data, sizeof(data))
.AppendWriteable(&status, sizeof(status))
.Build(&desc),
ZX_OK);
ASSERT_EQ(test.block().HandleBlockRequest(&test.block().queue(), desc, &used), ZX_OK);
// No bytes written and error status set.
ASSERT_EQ(status, VIRTIO_BLK_S_IOERR);
ASSERT_EQ(used, sizeof(status));
// Read back bytes from the file.
int fd = test.fd();
uint8_t actual[DATA_SIZE];
ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
ASSERT_EQ(read(fd, actual, DATA_SIZE), DATA_SIZE);
// The image file is initialized to all 0's and we attempted to write all
// 1's. Verify that the file contents are unchanged.
uint8_t expected[DATA_SIZE];
memset(expected, 0, DATA_SIZE);
ASSERT_EQ(memcmp(actual, expected, DATA_SIZE), 0);
END_TEST;
}
BEGIN_TEST_CASE(virtio_block)
RUN_TEST(file_block_device_bad_header)
RUN_TEST(file_block_device_bad_payload)
RUN_TEST(file_block_device_bad_status)
RUN_TEST(file_block_device_bad_request)
RUN_TEST(file_block_device_bad_flush)
RUN_TEST(file_block_device_read)
RUN_TEST(file_block_device_read_chain)
RUN_TEST(file_block_device_write)
RUN_TEST(file_block_device_write_chain)
RUN_TEST(file_block_device_flush)
RUN_TEST(file_block_device_flush_data)
RUN_TEST(file_block_device_multiple_descriptors)
RUN_TEST(file_block_device_read_only)
END_TEST_CASE(virtio_block)