blob: a664d5df84e5931726a68c7eeb56be6d678583e2 [file] [log] [blame]
// Copyright 2019 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 "storage/buffer/ring_buffer.h"
#include <lib/zx/vmo.h>
#include <zircon/assert.h>
#include <memory>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <storage/operation/unbuffered_operations_builder.h>
namespace storage {
namespace {
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Field;
const uint32_t kBlockSize = 8192;
class MockVmoidRegistry : public VmoidRegistry {
public:
vmoid_t default_vmoid() const { return 1; }
const zx::vmo& get_vmo() const { return vmo_; }
private:
zx_status_t BlockAttachVmo(const zx::vmo& vmo, Vmoid* out) override {
*out = Vmoid(default_vmoid());
EXPECT_EQ(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_), ZX_OK);
return ZX_OK;
}
zx_status_t BlockDetachVmo(Vmoid vmoid) override {
EXPECT_EQ(default_vmoid(), vmoid.TakeId());
vmo_.reset();
return ZX_OK;
}
zx::vmo vmo_;
};
TEST(RingBufferTest, EmptyRingBuffer) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
EXPECT_EQ(ZX_ERR_INVALID_ARGS,
RingBuffer::Create(&vmoid_registry, 0, kBlockSize, "test-buffer", &buffer));
}
TEST(RingBufferTest, MakeRingBuffer) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
const size_t kBlocks = 5;
ASSERT_EQ(RingBuffer::Create(&vmoid_registry, kBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
EXPECT_EQ(kBlocks, buffer->capacity());
}
TEST(RingBufferTest, ReserveOne) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
const size_t kBlocks = 5;
ASSERT_EQ(RingBuffer::Create(&vmoid_registry, kBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferReservation reservation;
EXPECT_EQ(reservation.length(), 0ul);
EXPECT_EQ(buffer->Reserve(1, &reservation), ZX_OK);
EXPECT_EQ(vmoid_registry.default_vmoid(), reservation.vmoid());
EXPECT_EQ(reservation.start(), 0ul);
EXPECT_EQ(reservation.length(), 1ul);
}
TEST(RingBufferTest, ReserveMove) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
const size_t kBlocks = 5;
ASSERT_EQ(RingBuffer::Create(&vmoid_registry, kBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferReservation reservation_a;
ASSERT_EQ(buffer->Reserve(1, &reservation_a), ZX_OK);
EXPECT_EQ(reservation_a.length(), 1ul);
// Move Construction.
RingBufferReservation reservation_b(std::move(reservation_a));
EXPECT_EQ(reservation_a.length(), 0ul);
EXPECT_EQ(reservation_b.length(), 1ul);
// Move Assignment.
reservation_a = std::move(reservation_b);
EXPECT_EQ(reservation_a.length(), 1ul);
EXPECT_EQ(reservation_b.length(), 0ul);
}
TEST(RingBufferTest, ReservationBufferView) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
const size_t kBlocks = 5;
ASSERT_EQ(RingBuffer::Create(&vmoid_registry, kBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferReservation reservation_a;
RingBufferReservation reservation_b;
EXPECT_EQ(buffer->Reserve(2, &reservation_a), ZX_OK);
EXPECT_EQ(buffer->Reserve(1, &reservation_b), ZX_OK);
EXPECT_EQ(vmoid_registry.default_vmoid(), reservation_a.vmoid());
EXPECT_EQ(vmoid_registry.default_vmoid(), reservation_a.buffer_view().vmoid());
EXPECT_EQ(reservation_a.start(), 0ul);
EXPECT_EQ(reservation_a.length(), 2ul);
EXPECT_EQ(reservation_b.start(), 2ul);
EXPECT_EQ(reservation_b.length(), 1ul);
}
TEST(RingBufferTest, ReserveAndFreeOutOfOrder) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
const size_t kBlocks = 10;
ASSERT_EQ(RingBuffer::Create(&vmoid_registry, kBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferReservation reservations[4];
ASSERT_EQ(buffer->Reserve(1, &reservations[0]), ZX_OK);
ASSERT_EQ(buffer->Reserve(2, &reservations[1]), ZX_OK);
ASSERT_EQ(buffer->Reserve(3, &reservations[2]), ZX_OK);
ASSERT_EQ(buffer->Reserve(4, &reservations[3]), ZX_OK);
// Although we would ordinarily prefer to free in the order we allocated:
// 0, 1, 2, 3
//
// We will instead free in the following order:
// 3, 1, 2, 0
{ auto unused = std::move(reservations[3]); }
{ auto unused = std::move(reservations[1]); }
{ auto unused = std::move(reservations[2]); }
// No space is actually freed until the reservations are freed in-order.
RingBufferReservation failed_reservation;
EXPECT_EQ(ZX_ERR_NO_SPACE, buffer->Reserve(1, &failed_reservation));
{ auto unused = std::move(reservations[0]); }
// Now ALL the blocks are freed.
RingBufferReservation reservation;
EXPECT_EQ(buffer->Reserve(kBlocks, &reservation), ZX_OK);
}
// Create a test VMO. Write the following pattern to the vmo:
// Block N: value + N
void MakeTestVmo(size_t blocks, int value, zx::vmo* out_vmo) {
zx::vmo vmo;
ASSERT_EQ(zx::vmo::create(blocks * kBlockSize, 0, &vmo), ZX_OK);
uint8_t buf[kBlockSize];
for (size_t i = 0; i < blocks; i++) {
memset(buf, value++, sizeof(buf));
ASSERT_EQ(vmo.write(buf, i * kBlockSize, kBlockSize), ZX_OK);
}
*out_vmo = std::move(vmo);
}
// Check that block |offset| in |vmo| equals |value|.
// Additionally, check that |value| is also set in |addr|.
void CheckVmoEquals(const zx::vmo& vmo, const void* addr, size_t offset, int value) {
uint8_t buf[kBlockSize];
ASSERT_EQ(vmo.read(buf, offset * kBlockSize, kBlockSize), ZX_OK);
EXPECT_EQ(0, memcmp(buf, addr, kBlockSize)) << "VMO data not equal to addr";
memset(buf, value, sizeof(buf));
EXPECT_EQ(0, memcmp(buf, addr, kBlockSize)) << "VMO data not equal to value";
}
// Checks that, for the portion of data accessible in |reservation|, the |operation| is
// accessible at |offset| within the reservation.
void CheckOperationInRingBuffer(const zx::vmo& vmo, RingBufferReservation* reservation,
const storage::UnbufferedOperation& operation, size_t offset,
int value) {
for (size_t i = 0; i < operation.op.length; i++) {
CheckVmoEquals(vmo, reservation->Data(i + offset), operation.op.vmo_offset + i,
value + static_cast<int>(operation.op.vmo_offset + i));
}
}
void ReserveAndCopyRequests(const std::unique_ptr<RingBuffer>& buffer,
std::vector<storage::UnbufferedOperation> requests,
RingBufferRequests* out) {
RingBufferReservation reservation;
ASSERT_EQ(buffer->Reserve(BlockCount(requests), &reservation), ZX_OK);
std::vector<storage::BufferedOperation> buffer_request;
ASSERT_TRUE(reservation.CopyRequests(requests, 0, &buffer_request).is_ok());
*out = RingBufferRequests(std::move(buffer_request), std::move(reservation));
}
// VMO: [ A, B, C ]
// DEV: [ A, B, C ]
// BUFFER: [ A, B, C, _, _ ]
TEST(RingBufferTest, OneRequestAtOffsetZero) {
zx::vmo vmo;
const size_t kVmoBlocks = 3;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operation;
operation.vmo = zx::unowned_vmo(vmo.get());
operation.op.type = storage::OperationType::kWrite;
operation.op.vmo_offset = 0;
operation.op.dev_offset = 0;
operation.op.length = kVmoBlocks;
builder.Add(operation);
const size_t kRingBufferBlocks = 5;
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(
RingBuffer::Create(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferRequests request;
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &request));
ASSERT_EQ(request.Operations().size(), 1ul);
// Start of RingBuffer.
EXPECT_EQ(request.Operations()[0].op.vmo_offset, 0ul);
// Same location on dev.
EXPECT_EQ(operation.op.dev_offset, request.Operations()[0].op.dev_offset);
// Same length.
EXPECT_EQ(operation.op.length, request.Operations()[0].op.length);
EXPECT_EQ(request.Reservation()->start(), 0ul);
EXPECT_EQ(operation.op.length, request.Reservation()->length());
CheckOperationInRingBuffer(vmo, request.Reservation(), operation, 0, seed);
}
// VMO: [ _, A, B ]
// DEV: [ _, _, A, B ]
// BUFFER: [ A, B, _, _, _ ]
TEST(RingBufferTest, OneRequestAtNonZeroOffset) {
zx::vmo vmo;
const size_t kVmoBlocks = 3;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operation;
operation.vmo = zx::unowned_vmo(vmo.get());
operation.op.type = storage::OperationType::kWrite;
operation.op.vmo_offset = 1;
operation.op.dev_offset = 2;
operation.op.length = kVmoBlocks - operation.op.vmo_offset;
builder.Add(operation);
const size_t kRingBufferBlocks = 5;
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(
RingBuffer::Create(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferRequests request;
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &request));
ASSERT_EQ(request.Operations().size(), 1ul);
// Start of RingBuffer.
EXPECT_EQ(request.Operations()[0].op.vmo_offset, 0ul);
// Same location on dev.
EXPECT_EQ(operation.op.dev_offset, request.Operations()[0].op.dev_offset);
// Same length.
EXPECT_EQ(operation.op.length, request.Operations()[0].op.length);
EXPECT_EQ(request.Reservation()->start(), 0ul);
EXPECT_EQ(operation.op.length, request.Reservation()->length());
CheckOperationInRingBuffer(vmo, request.Reservation(), operation, 0, seed);
}
// VMO 1: [ A, _, _, _ ]
// VMO 2: [ _, _, B, C ]
// DEV: [ _, _, A, _, B, C ]
// BUFFER: [ A, B, C, _, _ ]
TEST(RingBufferTest, TwoRequestsToTheSameVmoSameReservation) {
zx::vmo vmo;
const size_t kVmoBlocks = 4;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operations[2];
operations[0].vmo = zx::unowned_vmo(vmo.get());
operations[0].op.type = storage::OperationType::kWrite;
operations[0].op.vmo_offset = 0;
operations[0].op.dev_offset = 2;
operations[0].op.length = 1;
builder.Add(operations[0]);
operations[1].vmo = zx::unowned_vmo(vmo.get());
operations[1].op.type = storage::OperationType::kWrite;
operations[1].op.vmo_offset = 2;
operations[1].op.dev_offset = 4;
operations[1].op.length = 2;
builder.Add(operations[1]);
const size_t kRingBufferBlocks = 5;
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(
RingBuffer::Create(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferRequests request;
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &request));
ASSERT_EQ(request.Operations().size(), 2ul);
// Start of RingBuffer, and then immediately following the previous request.
EXPECT_EQ(request.Operations()[0].op.vmo_offset, 0ul);
EXPECT_EQ(operations[0].op.length, request.Operations()[1].op.vmo_offset);
// Same location on dev.
EXPECT_EQ(operations[0].op.dev_offset, request.Operations()[0].op.dev_offset);
EXPECT_EQ(operations[1].op.dev_offset, request.Operations()[1].op.dev_offset);
// Same length.
EXPECT_EQ(operations[0].op.length, request.Operations()[0].op.length);
EXPECT_EQ(operations[1].op.length, request.Operations()[1].op.length);
EXPECT_EQ(request.Reservation()->start(), 0ul);
EXPECT_EQ(operations[0].op.length + operations[1].op.length, request.Reservation()->length());
CheckOperationInRingBuffer(vmo, request.Reservation(), operations[0], 0, seed);
CheckOperationInRingBuffer(vmo, request.Reservation(), operations[1], operations[0].op.length,
seed);
}
// VMO 1: [ A, _, _, _ ]
// VMO 2: [ _, _, B, C ]
// DEV: [ _, _, A, _, B, C ]
// BUFFER: [ A, B, C, _, _ ]
TEST(RingBufferTest, TwoRequestsToTheSameVmoDifferentReservations) {
zx::vmo vmo;
const size_t kVmoBlocks = 4;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
const size_t kRingBufferBlocks = 5;
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(
RingBuffer::Create(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operations[2];
RingBufferRequests requests[2];
operations[0].vmo = zx::unowned_vmo(vmo.get());
operations[0].op.type = storage::OperationType::kWrite;
operations[0].op.vmo_offset = 0;
operations[0].op.dev_offset = 2;
operations[0].op.length = 1;
builder.Add(operations[0]);
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &requests[0]));
operations[1].vmo = zx::unowned_vmo(vmo.get());
operations[1].op.type = storage::OperationType::kWrite;
operations[1].op.vmo_offset = 2;
operations[1].op.dev_offset = 4;
operations[1].op.length = 2;
builder.Add(operations[1]);
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &requests[1]));
ASSERT_EQ(requests[0].Operations().size(), 1ul);
ASSERT_EQ(requests[1].Operations().size(), 1ul);
// Start of RingBuffer, and then immediately following the previous request.
EXPECT_EQ(requests[0].Operations()[0].op.vmo_offset, 0ul);
EXPECT_EQ(operations[0].op.length, requests[1].Operations()[0].op.vmo_offset);
// Same location on dev.
EXPECT_EQ(operations[0].op.dev_offset, requests[0].Operations()[0].op.dev_offset);
EXPECT_EQ(operations[1].op.dev_offset, requests[1].Operations()[0].op.dev_offset);
// Same length.
EXPECT_EQ(operations[0].op.length, requests[0].Operations()[0].op.length);
EXPECT_EQ(operations[1].op.length, requests[1].Operations()[0].op.length);
EXPECT_EQ(requests[0].Reservation()->start(), 0ul);
EXPECT_EQ(operations[0].op.length, requests[1].Reservation()->start());
EXPECT_EQ(operations[0].op.length, requests[0].Reservation()->length());
EXPECT_EQ(operations[1].op.length, requests[1].Reservation()->length());
CheckOperationInRingBuffer(vmo, requests[0].Reservation(), operations[0], 0, seed);
CheckOperationInRingBuffer(vmo, requests[1].Reservation(), operations[1], 0, seed);
}
// VMO: [ A, B, C ]
// DEV: [ A, B, C ]
// BUFFER: [ A, B, C ]
TEST(RingBufferTest, OneRequestFullRingBuffer) {
zx::vmo vmo;
const size_t kVmoBlocks = 3;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operation;
operation.vmo = zx::unowned_vmo(vmo.get());
operation.op.type = storage::OperationType::kWrite;
operation.op.vmo_offset = 0;
operation.op.dev_offset = 0;
operation.op.length = kVmoBlocks;
builder.Add(operation);
const size_t kRingBufferBlocks = 3;
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(
RingBuffer::Create(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferRequests request;
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &request));
ASSERT_EQ(request.Operations().size(), 1ul);
// Start of RingBuffer.
EXPECT_EQ(request.Operations()[0].op.vmo_offset, 0ul);
// Same location on dev.
EXPECT_EQ(operation.op.dev_offset, request.Operations()[0].op.dev_offset);
// Same length.
EXPECT_EQ(operation.op.length, request.Operations()[0].op.length);
EXPECT_EQ(request.Reservation()->start(), 0ul);
EXPECT_EQ(operation.op.length, request.Reservation()->length());
CheckOperationInRingBuffer(vmo, request.Reservation(), operation, 0, seed);
}
// VMO: [ A, B, C, D ]
// DEV: [ A, B, C, D ]
// BUFFER: [ <Too Small> ]
TEST(RingBufferTest, OneRequestWithRingBufferFull) {
zx::vmo vmo;
const size_t kVmoBlocks = 4;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operation;
operation.vmo = zx::unowned_vmo(vmo.get());
operation.op.type = storage::OperationType::kWrite;
operation.op.vmo_offset = 0;
operation.op.dev_offset = 0;
operation.op.length = kVmoBlocks;
builder.Add(operation);
const size_t kRingBufferBlocks = 3;
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(
RingBuffer::Create(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferRequests request;
ASSERT_EQ(ZX_ERR_NO_SPACE, buffer->Reserve(BlockCount(builder.TakeOperations()), nullptr));
ASSERT_EQ(request.Operations().size(), 0ul);
}
// VMO 1: [ A, B, C, _, _, _ ]
// VMO 2: [ _, _, _, D, E, F ]
// VMO 3: [ _, _, _, _, _, _, G, H, I ]
// DEV: [ A, B, C, D, E, F, G, H, I ]
// BUFFER: [ A, B, C, D, E, F ]
// BUFFER: [ <Too Small for third request> ]
// BUFFER: [ _, _, _, D, E, F ] After completing first request.
// BUFFER: [ G, H, I, D, E, F ]
TEST(RingBufferTest, RingBufferWraparoundCleanly) {
zx::vmo vmo;
const size_t kVmoBlocks = 9;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
const size_t kRingBufferBlocks = 6;
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(
RingBuffer::Create(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operations[3];
RingBufferRequests requests[3];
operations[0].vmo = zx::unowned_vmo(vmo.get());
operations[0].op.type = storage::OperationType::kWrite;
operations[0].op.vmo_offset = 0;
operations[0].op.dev_offset = 0;
operations[0].op.length = 3;
builder.Add(operations[0]);
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &requests[0]));
operations[1].vmo = zx::unowned_vmo(vmo.get());
operations[1].op.type = storage::OperationType::kWrite;
operations[1].op.vmo_offset = 3;
operations[1].op.dev_offset = 3;
operations[1].op.length = 3;
builder.Add(operations[1]);
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &requests[1]));
operations[2].vmo = zx::unowned_vmo(vmo.get());
operations[2].op.type = storage::OperationType::kWrite;
operations[2].op.vmo_offset = 6;
operations[2].op.dev_offset = 6;
operations[2].op.length = 3;
builder.Add(operations[2]);
ASSERT_EQ(ZX_ERR_NO_SPACE, buffer->Reserve(BlockCount(builder.TakeOperations()), nullptr));
CheckOperationInRingBuffer(vmo, requests[0].Reservation(), operations[0], 0, seed);
CheckOperationInRingBuffer(vmo, requests[1].Reservation(), operations[1], 0, seed);
// Releasing the first request makes enough room in the buffer.
{ auto released = std::move(requests[0]); }
builder.Add(operations[2]);
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &requests[2]));
CheckOperationInRingBuffer(vmo, requests[1].Reservation(), operations[1], 0, seed);
CheckOperationInRingBuffer(vmo, requests[2].Reservation(), operations[2], 0, seed);
}
// VMO 1: [ A, B, C, _, _, _ ]
// VMO 2: [ _, _, _, _, D, _, _ ]
// VMO 3: [ _, _, _, _, _, _, E, F, G, H, I]
// DEV: [ A, B, C, _, D, _, E, F, G, H, I]
// BUFFER: [ A, B, C, D, _, _ ]
// BUFFER: [ _, _, _, D, _, _ ] After completing first request.
// BUFFER: [ G, H, I, D, E, F ]
TEST(RingBufferTest, RingBufferWraparoundSplitRequest) {
zx::vmo vmo;
const size_t kVmoBlocks = 11;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
const size_t kRingBufferBlocks = 6;
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(
RingBuffer::Create(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operations[3];
RingBufferRequests requests[3];
operations[0].vmo = zx::unowned_vmo(vmo.get());
operations[0].op.type = storage::OperationType::kWrite;
operations[0].op.vmo_offset = 0;
operations[0].op.dev_offset = 0;
operations[0].op.length = 3;
builder.Add(operations[0]);
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &requests[0]));
operations[1].vmo = zx::unowned_vmo(vmo.get());
operations[1].op.type = storage::OperationType::kWrite;
operations[1].op.vmo_offset = 4;
operations[1].op.dev_offset = 4;
operations[1].op.length = 1;
builder.Add(operations[1]);
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &requests[1]));
operations[2].vmo = zx::unowned_vmo(vmo.get());
operations[2].op.type = storage::OperationType::kWrite;
operations[2].op.vmo_offset = 6;
operations[2].op.dev_offset = 6;
operations[2].op.length = 5;
builder.Add(operations[2]);
ASSERT_EQ(ZX_ERR_NO_SPACE, buffer->Reserve(BlockCount(builder.TakeOperations()), nullptr));
CheckOperationInRingBuffer(vmo, requests[0].Reservation(), operations[0], 0, seed);
CheckOperationInRingBuffer(vmo, requests[1].Reservation(), operations[1], 0, seed);
// Releasing the first request makes enough room in the buffer.
{ auto released = std::move(requests[0]); }
builder.Add(operations[2]);
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, builder.TakeOperations(), &requests[2]));
CheckOperationInRingBuffer(vmo, requests[1].Reservation(), operations[1], 0, seed);
CheckOperationInRingBuffer(vmo, requests[2].Reservation(), operations[2], 0, seed);
}
// Tests copying requests at an offset, where the offset wraps around the ring buffer.
//
// RESERVATION 1: [ A, B, _, _ ]
// RESERVATION 2: [ _, _, C, _ ]
// RESERVATION 3: [ _, _, _, D ]
// RING-BUFFER: [ A, B, C, _ ]
// RING-BUFFER: [ _, _, C, _ ] After releasing first request.
// RING-BUFFER: [ _, D, C, _ ] Writing "VMO 3" at an offset within the reservation.
TEST(RingBufferTest, CopyRequestAtOffsetWraparound) {
zx::vmo vmo;
const size_t kVmoBlocks = 4;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
const size_t kRingBufferBlocks = 4;
MockVmoidRegistry vmoid_registry;
VmoBuffer vmo_buffer;
ASSERT_EQ(vmo_buffer.Initialize(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer"),
ZX_OK);
auto buffer = std::make_unique<RingBuffer>(std::move(vmo_buffer));
RingBufferReservation reservations[3];
ASSERT_EQ(buffer->Reserve(2, &reservations[0]), ZX_OK);
ASSERT_EQ(buffer->Reserve(1, &reservations[1]), ZX_OK);
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operations[3];
// "A, B"
operations[0].vmo = zx::unowned_vmo(vmo.get());
operations[0].op.type = storage::OperationType::kWrite;
operations[0].op.vmo_offset = 0;
operations[0].op.dev_offset = 0;
operations[0].op.length = 2;
builder.Add(operations[0]);
std::vector<storage::BufferedOperation> buffer_operation;
ASSERT_TRUE(reservations[0].CopyRequests(builder.TakeOperations(), 0, &buffer_operation).is_ok());
// "C"
operations[1].vmo = zx::unowned_vmo(vmo.get());
operations[1].op.type = storage::OperationType::kWrite;
operations[1].op.vmo_offset = 2;
operations[1].op.dev_offset = 2;
operations[1].op.length = 1;
builder.Add(operations[1]);
ASSERT_TRUE(reservations[1].CopyRequests(builder.TakeOperations(), 0, &buffer_operation).is_ok());
ASSERT_NO_FATAL_FAILURE(CheckVmoEquals(vmo, reservations[0].Data(0), 0, seed));
ASSERT_NO_FATAL_FAILURE(CheckVmoEquals(vmo, reservations[1].Data(0), 2, seed + 2));
ASSERT_EQ(ZX_ERR_NO_SPACE, buffer->Reserve(3, &reservations[2]));
{ auto released = std::move(reservations[0]); }
ASSERT_EQ(buffer->Reserve(3, &reservations[2]), ZX_OK);
// "D"
operations[2].vmo = zx::unowned_vmo(vmo.get());
operations[2].op.type = storage::OperationType::kWrite;
operations[2].op.vmo_offset = 3;
operations[2].op.dev_offset = 3;
operations[2].op.length = 1;
builder.Add(operations[2]);
const size_t reservation_offset = 2;
ASSERT_TRUE(reservations[2]
.CopyRequests(builder.TakeOperations(), reservation_offset, &buffer_operation)
.is_ok());
ASSERT_NO_FATAL_FAILURE(CheckVmoEquals(vmo, reservations[1].Data(0), 2, seed + 2));
ASSERT_NO_FATAL_FAILURE(
CheckVmoEquals(vmo, reservations[2].Data(reservation_offset), 3, seed + 3));
}
// Tests manually adding header and footer around a payload.
//
// VMO 1: [ A, _, C ] (Copied into buffer via Data)
// VMO 2: [ _, B, _ ] (Copied into buffer via CopyRequests)
// VMO-BUFFER: [ A, B, C ]
// DEV: [ A, B, C ]
// RING-BUFFER: [ A, B, C ]
TEST(RingBufferTest, CopyRequestAtOffsetWithHeaderAndFooter) {
zx::vmo vmo_a, vmo_b;
const size_t kVmoBlocks = 3;
int seed_a = 0xAB;
MakeTestVmo(kVmoBlocks, seed_a, &vmo_a);
int seed_b = 0xCD;
MakeTestVmo(kVmoBlocks, seed_b, &vmo_b);
const size_t kRingBufferBlocks = 5;
MockVmoidRegistry vmoid_registry;
VmoBuffer vmo_buffer;
ASSERT_EQ(vmo_buffer.Initialize(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer"),
ZX_OK);
auto buffer = std::make_unique<RingBuffer>(std::move(vmo_buffer));
RingBufferReservation reservation;
ASSERT_EQ(buffer->Reserve(3, &reservation), ZX_OK);
// Write header from source VMO into reservation.
ASSERT_EQ(vmo_a.read(reservation.Data(0), 0, kBlockSize), ZX_OK);
// Write footer.
ASSERT_EQ(vmo_a.read(reservation.Data(2), 2 * kBlockSize, kBlockSize), ZX_OK);
// Data "B" of the VMO.
storage::UnbufferedOperationsBuilder builder;
storage::UnbufferedOperation operation;
operation.vmo = zx::unowned_vmo(vmo_b.get());
operation.op.type = storage::OperationType::kWrite;
operation.op.vmo_offset = 1;
operation.op.dev_offset = 1;
operation.op.length = 1;
builder.Add(operation);
std::vector<storage::BufferedOperation> buffer_operation;
ASSERT_TRUE(reservation.CopyRequests(builder.TakeOperations(), 1, &buffer_operation).is_ok());
ASSERT_EQ(buffer_operation.size(), 1ul);
ASSERT_EQ(buffer_operation[0].op.vmo_offset, 1u);
ASSERT_EQ(buffer_operation[0].op.dev_offset, 1ul);
ASSERT_EQ(buffer_operation[0].op.length, 1ul);
ASSERT_NO_FATAL_FAILURE(CheckVmoEquals(vmo_a, reservation.Data(0), 0, seed_a));
ASSERT_NO_FATAL_FAILURE(CheckVmoEquals(vmo_b, reservation.Data(1), 1, seed_b + 1));
ASSERT_NO_FATAL_FAILURE(CheckVmoEquals(vmo_a, reservation.Data(2), 2, seed_a + 2));
}
TEST(RingBufferTest, ReleaseReservationDecommitsMemory) {
zx::vmo vmo;
const size_t kVmoBlocks = 1;
int seed = 0xAB;
MakeTestVmo(kVmoBlocks, seed, &vmo);
const size_t kRingBufferBlocks = 128;
MockVmoidRegistry vmoid_registry;
VmoBuffer vmo_buffer;
ASSERT_EQ(vmo_buffer.Initialize(&vmoid_registry, kRingBufferBlocks, kBlockSize, "test-buffer"),
ZX_OK);
auto buffer = std::make_unique<RingBuffer>(std::move(vmo_buffer));
zx_info_vmo_t info;
auto write_blocks = [&](size_t count) {
RingBufferReservation reservation;
ASSERT_EQ(buffer->Reserve(count, &reservation), ZX_OK);
// Write header from the source VMO into the reservation.
for (size_t i = 0; i < count; i++) {
ASSERT_EQ(vmo.read(reservation.Data(i), 0, kBlockSize), ZX_OK);
}
ASSERT_EQ(vmoid_registry.get_vmo().get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr),
ZX_OK);
ASSERT_EQ(count * kBlockSize, info.committed_bytes);
};
// First issue a write that uses half of the buffer.
write_blocks(kRingBufferBlocks / 2);
// Now issue a write that uses all of the buffer, which should test wraparound.
write_blocks(kRingBufferBlocks);
// All committed bytes of the buffer should be released after the reservation
// goes out of scope.
ASSERT_EQ(vmoid_registry.get_vmo().get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr),
ZX_OK);
ASSERT_EQ(info.committed_bytes, 0ul);
}
TEST(RingBufferTest, ReserveZeroBlocksReturnsError) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
const size_t kBlocks = 5;
ASSERT_EQ(RingBuffer::Create(&vmoid_registry, kBlocks, kBlockSize, "test-buffer", &buffer),
ZX_OK);
RingBufferReservation reservation;
EXPECT_EQ(buffer->Reserve(0, &reservation), ZX_ERR_INVALID_ARGS);
}
TEST(RingBufferTest, OutOfOrderReleaseSucceeds) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(RingBuffer::Create(&vmoid_registry, 5, kBlockSize, "test-buffer", &buffer), ZX_OK);
{
// Three reservations that we free out-of-order.
RingBufferReservation reservation1;
RingBufferReservation reservation2;
RingBufferReservation reservation3;
EXPECT_EQ(buffer->Reserve(1, &reservation1), ZX_OK);
EXPECT_EQ(buffer->Reserve(1, &reservation2), ZX_OK);
// Should have wrapped now.
EXPECT_EQ(buffer->Reserve(1, &reservation3), ZX_OK);
}
RingBufferReservation reservation;
EXPECT_EQ(buffer->Reserve(4, &reservation), ZX_OK);
}
TEST(RingBufferTest, WrappedOutOfOrderReleaseSucceeds) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(RingBuffer::Create(&vmoid_registry, 5, kBlockSize, "test-buffer", &buffer), ZX_OK);
{
RingBufferReservation reservation;
EXPECT_EQ(buffer->Reserve(3, &reservation), ZX_OK);
}
{
// Three reservations that we free out-of-order and where they wrap (the buffer has 5 blocks).
RingBufferReservation reservation1;
RingBufferReservation reservation2;
RingBufferReservation reservation3;
EXPECT_EQ(buffer->Reserve(1, &reservation1), ZX_OK);
EXPECT_EQ(buffer->Reserve(1, &reservation2), ZX_OK);
// Should have wrapped now.
EXPECT_EQ(buffer->Reserve(1, &reservation3), ZX_OK);
}
RingBufferReservation reservation;
EXPECT_EQ(buffer->Reserve(4, &reservation), ZX_OK);
}
TEST(RingBufferTest, CopyWithPointersSucceeds) {
MockVmoidRegistry vmoid_registry;
std::unique_ptr<RingBuffer> buffer;
ASSERT_EQ(RingBuffer::Create(&vmoid_registry, 5, kBlockSize, "test-buffer", &buffer), ZX_OK);
// Reserve and then discard 4 blocks worth so that we can test wrapping.
{
RingBufferReservation reservation;
buffer->Reserve(4, &reservation);
}
std::vector<uint8_t> source_buffer(2 * kBlockSize);
// Fill the buffer with a pattern.
uint8_t value = 57;
for (uint8_t& c : source_buffer) {
c = value;
value += 3;
}
RingBufferRequests requests;
std::vector<UnbufferedOperation> operations = {storage::UnbufferedOperation{
.data = source_buffer.data(),
.op = {.type = OperationType::kWrite, .dev_offset = 1, .length = 2}}};
ASSERT_NO_FATAL_FAILURE(ReserveAndCopyRequests(buffer, operations, &requests));
EXPECT_THAT(
requests.Operations(),
ElementsAre(Field(&BufferedOperation::op,
AllOf(Field(&Operation::type, OperationType::kWrite),
Field(&Operation::vmo_offset, 4), Field(&Operation::dev_offset, 1),
Field(&Operation::length, 1))),
Field(&BufferedOperation::op,
AllOf(Field(&Operation::type, OperationType::kWrite),
Field(&Operation::vmo_offset, 0), Field(&Operation::dev_offset, 2),
Field(&Operation::length, 1)))));
EXPECT_EQ(memcmp(requests.Reservation()->Data(0), &source_buffer[0], kBlockSize), 0);
EXPECT_EQ(memcmp(requests.Reservation()->Data(1), &source_buffer[kBlockSize], kBlockSize), 0);
}
} // namespace
} // namespace storage