blob: aa9ada8443049000ee4d3bfa65d27ef810acd560 [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 "server.h"
#include <lib/fit/defer.h>
#include <lib/sync/completion.h>
#include <unistd.h>
#include <thread>
#include <zxtest/zxtest.h>
#include "test/stub-block-device.h"
namespace {
class ServerTestFixture : public zxtest::Test {
public:
ServerTestFixture() : client_(blkdev_.proto()) {}
protected:
void SetUp() override;
void TearDown() override;
void CreateThread();
void WaitForThreadStart();
void WaitForThreadExit();
void JoinThread();
StubBlockDevice blkdev_;
ddk::BlockProtocolClient client_;
std::unique_ptr<Server> server_;
private:
sync_completion_t thread_started_;
sync_completion_t thread_exited_;
std::thread thread_;
};
void ServerTestFixture::SetUp() {
zx::result server = Server::Create(&client_);
ASSERT_OK(server);
server_ = std::move(server.value());
}
void ServerTestFixture::TearDown() { ASSERT_FALSE(thread_.joinable()); }
void ServerTestFixture::CreateThread() {
thread_ = std::thread([this]() {
sync_completion_signal(&thread_started_);
[[maybe_unused]] zx_status_t status = server_->Serve();
sync_completion_signal(&thread_exited_);
return 0;
});
}
void ServerTestFixture::WaitForThreadStart() {
ASSERT_OK(sync_completion_wait(&thread_started_, ZX_SEC(5)));
}
void ServerTestFixture::WaitForThreadExit() {
ASSERT_OK(sync_completion_wait(&thread_exited_, ZX_SEC(5)));
}
void ServerTestFixture::JoinThread() { thread_.join(); }
TEST_F(ServerTestFixture, CreateServer) {}
TEST_F(ServerTestFixture, StartServer) {
CreateThread();
WaitForThreadStart();
// This code is racy with Serve() being called. This is expected.
// The server should handle shutdown commands at any time.
server_->Shutdown();
WaitForThreadExit();
JoinThread();
}
TEST_F(ServerTestFixture, SplitRequestAfterFailedRequestReturnsFailure) {
zx::result fifo_result = server_->GetFifo();
ASSERT_OK(fifo_result);
fzl::fifo<block_fifo_request_t, block_fifo_response_t> fifo(std::move(fifo_result.value()));
CreateThread();
auto cleanup = fit::defer([&] {
server_->Shutdown();
JoinThread();
});
zx::vmo vmo;
constexpr int kTestBlockCount = 257;
ASSERT_OK(zx::vmo::create(kTestBlockCount * kBlockSize, 0, &vmo));
zx::result vmoid = server_->AttachVmo(std::move(vmo));
ASSERT_OK(vmoid);
block_fifo_request_t request = {
.command = {.opcode = BLOCK_OPCODE_WRITE, .flags = BLOCK_IO_FLAG_GROUP_ITEM},
.reqid = 100,
.group = 5,
.vmoid = vmoid.value(),
.length = 4,
.vmo_offset = 0,
.dev_offset = 0,
};
blkdev_.set_callback([&](const block_op_t&) { return ZX_ERR_IO; });
size_t actual_count = 0;
ASSERT_OK(fifo.write(&request, 1, &actual_count));
ASSERT_EQ(actual_count, 1);
request = {
.command = {.opcode = BLOCK_OPCODE_READ, .flags = 0},
.reqid = 101,
.vmoid = vmoid.value(),
};
ASSERT_OK(fifo.write(&request, 1, &actual_count));
ASSERT_EQ(actual_count, 1);
block_fifo_response_t response;
zx_signals_t seen;
ASSERT_OK(fifo.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::time::infinite(), &seen));
ASSERT_OK(fifo.read_one(&response));
// Should get the response for the read.
EXPECT_EQ(response.reqid, 101);
request = {
.command = {.opcode = BLOCK_OPCODE_WRITE,
.flags = BLOCK_IO_FLAG_GROUP_ITEM | BLOCK_IO_FLAG_GROUP_LAST},
.reqid = 102,
.group = 5,
.vmoid = vmoid.value(),
.length = kTestBlockCount,
.vmo_offset = 0,
.dev_offset = 0,
};
ASSERT_OK(fifo.write(&request, 1, &actual_count));
ASSERT_EQ(actual_count, 1);
// It's the last one so it should trigger a response.
ASSERT_OK(fifo.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::time::infinite(), &seen));
ASSERT_OK(fifo.read_one(&response));
EXPECT_EQ(response.reqid, 102);
// Make sure the group is correctly cleaned up and able to be used for another request.
blkdev_.set_callback({});
block_fifo_request_t requests[] = {
{
.command = {.opcode = BLOCK_OPCODE_WRITE, .flags = BLOCK_IO_FLAG_GROUP_ITEM},
.reqid = 103,
.group = 5,
.vmoid = vmoid.value(),
.length = 257,
.vmo_offset = 0,
.dev_offset = 0,
},
{
.command = {.opcode = BLOCK_OPCODE_WRITE,
.flags = BLOCK_IO_FLAG_GROUP_ITEM | BLOCK_IO_FLAG_GROUP_LAST},
.reqid = 104,
.group = 5,
.vmoid = vmoid.value(),
.length = 257,
.vmo_offset = 0,
.dev_offset = 0,
},
};
ASSERT_OK(fifo.write(requests, 2, &actual_count));
ASSERT_EQ(actual_count, 2);
ASSERT_OK(fifo.wait_one(ZX_FIFO_READABLE | ZX_FIFO_PEER_CLOSED, zx::time::infinite(), &seen));
ASSERT_OK(fifo.read_one(&response));
EXPECT_EQ(response.reqid, 104);
}
TEST(OffsetMap, InvalidMapping) {
// Zero-length
ASSERT_NOT_OK(OffsetMap::Create(fuchsia_hardware_block::wire::BlockOffsetMapping{
.source_block_offset = 0,
.target_block_offset = 1000,
.length = 0,
}));
// Source overflow
ASSERT_NOT_OK(OffsetMap::Create(fuchsia_hardware_block::wire::BlockOffsetMapping{
.source_block_offset = std::numeric_limits<uint64_t>::max(),
.target_block_offset = 0,
.length = 100,
}));
// Target overflow
ASSERT_NOT_OK(OffsetMap::Create(fuchsia_hardware_block::wire::BlockOffsetMapping{
.source_block_offset = 0,
.target_block_offset = std::numeric_limits<uint64_t>::max(),
.length = 100,
}));
}
TEST(OffsetMap, RemapRequests) {
zx::result map = OffsetMap::Create(fuchsia_hardware_block::wire::BlockOffsetMapping{
.source_block_offset = 10,
.target_block_offset = 1000,
.length = 100,
});
ASSERT_OK(map);
block_fifo_request_t request{
.command = {.opcode = BLOCK_OPCODE_WRITE},
.reqid = 1,
.group = 2,
.vmoid = 3,
.length = 10,
.vmo_offset = 0x2000,
};
const block_fifo_request_t orig_request = request;
auto AssertUnchangedExceptOffset = [&orig_request](block_fifo_request_t& request) {
request.dev_offset = orig_request.dev_offset;
ASSERT_BYTES_EQ(&request, &orig_request, sizeof(request));
};
request.dev_offset = 10;
ASSERT_TRUE(map->AdjustRequest(request));
ASSERT_EQ(request.dev_offset, 1000);
AssertUnchangedExceptOffset(request);
request.dev_offset = 40;
ASSERT_TRUE(map->AdjustRequest(request));
ASSERT_EQ(request.dev_offset, 1030);
AssertUnchangedExceptOffset(request);
request.dev_offset = 100;
ASSERT_TRUE(map->AdjustRequest(request));
ASSERT_EQ(request.dev_offset, 1090);
AssertUnchangedExceptOffset(request);
// Past end of map
request.dev_offset = 101;
ASSERT_FALSE(map->AdjustRequest(request));
AssertUnchangedExceptOffset(request);
// Before start of map
request.dev_offset = 9;
ASSERT_FALSE(map->AdjustRequest(request));
AssertUnchangedExceptOffset(request);
// Source beyond end of target is OK too
map = OffsetMap::Create(fuchsia_hardware_block::wire::BlockOffsetMapping{
.source_block_offset = 1000,
.target_block_offset = 0,
.length = 100,
});
ASSERT_OK(map);
request.dev_offset = 1000;
ASSERT_TRUE(map->AdjustRequest(request));
ASSERT_EQ(request.dev_offset, 0);
AssertUnchangedExceptOffset(request);
request.dev_offset = 1090;
ASSERT_TRUE(map->AdjustRequest(request));
ASSERT_EQ(request.dev_offset, 90);
AssertUnchangedExceptOffset(request);
}
} // namespace