blob: fbd71018056641fbcc53b080633f0a4af8833d2c [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 <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/binding.h>
#include <ddk/iotxn.h>
#include <ddk/protocol/test.h>
#include <unittest/unittest.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
static bool test_physmap_simple(void) {
BEGIN_TEST;
iotxn_t* txn;
ASSERT_EQ(iotxn_alloc(&txn, 0, PAGE_SIZE * 3), MX_OK, "");
ASSERT_NONNULL(txn, "");
ASSERT_EQ(iotxn_physmap(txn), MX_OK, "");
ASSERT_NONNULL(txn->phys, "expected phys to be set");
ASSERT_EQ(txn->phys_count, 3u, "unexpected phys_count");
iotxn_release(txn);
END_TEST;
}
static bool test_physmap_contiguous(void) {
BEGIN_TEST;
iotxn_t* txn;
ASSERT_EQ(iotxn_alloc(&txn, IOTXN_ALLOC_CONTIGUOUS, PAGE_SIZE * 3), MX_OK, "");
ASSERT_NONNULL(txn, "");
ASSERT_EQ(iotxn_physmap(txn), MX_OK, "");
ASSERT_EQ(txn->phys, &txn->phys_inline[0], "expected phys to point to inline_phys");
ASSERT_EQ(txn->phys_count, 1u, "unexpected phys_length");
iotxn_release(txn);
END_TEST;
}
static bool test_physmap_clone(void) {
BEGIN_TEST;
iotxn_t* txn;
ASSERT_EQ(iotxn_alloc(&txn, 0, PAGE_SIZE * 3), MX_OK, "");
ASSERT_NONNULL(txn, "");
ASSERT_EQ(iotxn_physmap(txn), MX_OK, "");
ASSERT_NONNULL(txn->phys, "expected phys to be set");
ASSERT_EQ(txn->phys_count, 3u, "unexpected phys_count");
iotxn_t* clone = NULL;
ASSERT_EQ(iotxn_clone(txn, &clone), MX_OK, "");
ASSERT_EQ(txn->phys, clone->phys, "expected clone to point to the same phys");
ASSERT_EQ(txn->phys_count, clone->phys_count, "unexpected clone phys_count");
iotxn_release(txn);
iotxn_release(clone);
END_TEST;
}
static bool test_physmap_aligned_offset(void) {
BEGIN_TEST;
iotxn_t* txn;
ASSERT_EQ(iotxn_alloc(&txn, 0, PAGE_SIZE * 3), MX_OK, "");
ASSERT_NONNULL(txn, "");
txn->vmo_offset = PAGE_SIZE;
txn->vmo_length = PAGE_SIZE * 2;
ASSERT_EQ(iotxn_physmap(txn), MX_OK, "");
ASSERT_NONNULL(txn->phys, "expected phys to be set");
ASSERT_EQ(txn->phys_count, 2u, "unexpected phys_count");
iotxn_release(txn);
END_TEST;
}
static bool test_physmap_unaligned_offset(void) {
BEGIN_TEST;
iotxn_t* txn;
ASSERT_EQ(iotxn_alloc(&txn, 0, PAGE_SIZE * 3), MX_OK, "");
ASSERT_NONNULL(txn, "");
txn->vmo_offset = PAGE_SIZE / 2;
txn->vmo_length = PAGE_SIZE * 2;
ASSERT_EQ(iotxn_physmap(txn), MX_OK, "");
ASSERT_EQ(txn->phys, &txn->phys_inline[0], "expected phys to point to inline_phys");
ASSERT_EQ(txn->phys_count, 3u, "unexpected phys_count");
iotxn_release(txn);
END_TEST;
}
static bool test_physmap_unaligned_offset2(void) {
BEGIN_TEST;
iotxn_t* txn;
ASSERT_EQ(iotxn_alloc(&txn, 0, PAGE_SIZE * 4), MX_OK, "");
ASSERT_NONNULL(txn, "");
txn->vmo_offset = PAGE_SIZE - (PAGE_SIZE / 4);
txn->vmo_length = (PAGE_SIZE * 2) + (PAGE_SIZE / 2);
ASSERT_EQ(iotxn_physmap(txn), MX_OK, "");
ASSERT_NONNULL(txn->phys, "expected phys to be set");
ASSERT_NE(txn->phys, &txn->phys_inline[0], "expected phys not to point to inline_phys");
ASSERT_EQ(txn->phys_count, 4u, "unexpected phys_count");
iotxn_release(txn);
END_TEST;
}
static bool test_phys_iter(void) {
BEGIN_TEST;
iotxn_phys_iter_t iter;
iotxn_t* txn;
mx_paddr_t paddr;
size_t length;
size_t max_length;
// create 4 page contiguous iotxn
ASSERT_EQ(iotxn_alloc(&txn, IOTXN_ALLOC_CONTIGUOUS, PAGE_SIZE * 4), MX_OK, "");
txn->length = PAGE_SIZE * 4;
ASSERT_EQ(iotxn_physmap(txn), MX_OK, "");
ASSERT_EQ(txn->phys_count, 1u, "");
// simple contiguous case
max_length = txn->length + PAGE_SIZE;
iotxn_phys_iter_init(&iter, txn, max_length);
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[0], "iotxn_iter_next returned wrong paddr");
ASSERT_EQ(length, txn->length, "iotxn_iter_next returned wrong length");
ASSERT_EQ(iotxn_phys_iter_next(&iter, &paddr), 0u, "");
// contiguous case with max_length < txn->length
max_length = PAGE_SIZE;
iotxn_phys_iter_init(&iter, txn, max_length);
for (int i = 0; i < 4; i++) {
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[0] + (i * max_length), "iotxn_iter_next returned wrong paddr");
ASSERT_EQ(length, max_length, "iotxn_iter_next returned wrong length");
}
ASSERT_EQ(iotxn_phys_iter_next(&iter, &paddr), 0u, "");
// contiguous case with unaligned vmo_offset and txn->length
txn->vmo_offset = 100;
max_length = txn->length + PAGE_SIZE;
txn->length -= 1000;
iotxn_phys_iter_init(&iter, txn, max_length);
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[0] + txn->vmo_offset, "");
ASSERT_EQ(length, txn->length, "");
ASSERT_EQ(iotxn_phys_iter_next(&iter, &paddr), 0u, "");
iotxn_release(txn);
// create discontiguous iotxn
ASSERT_EQ(iotxn_alloc(&txn, 0, PAGE_SIZE * 4), MX_OK, "");
txn->length = PAGE_SIZE * 4;
ASSERT_EQ(iotxn_physmap(txn), MX_OK, "");
ASSERT_EQ(txn->phys_count, 4u, "");
// pretend that first two pages are contiguous and second two are not
txn->phys[1] = txn->phys[0] + PAGE_SIZE;
txn->phys[2] = txn->phys[0] + (PAGE_SIZE * 10);
txn->phys[3] = txn->phys[0] + (PAGE_SIZE * 20);
// simple discontiguous case
max_length = txn->length + PAGE_SIZE;
iotxn_phys_iter_init(&iter, txn, max_length);
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[0], "iotxn_iter_next returned wrong paddr");
ASSERT_EQ(length, (size_t)(PAGE_SIZE * 2), "iotxn_iter_next returned wrong length");
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[2], "iotxn_iter_next returned wrong paddr");
ASSERT_EQ(length, (size_t)PAGE_SIZE, "iotxn_iter_next returned wrong length");
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[3], "iotxn_iter_next returned wrong paddr");
ASSERT_EQ(length, (size_t)PAGE_SIZE, "iotxn_iter_next returned wrong length");
ASSERT_EQ(iotxn_phys_iter_next(&iter, &paddr), 0u, "");
// discontiguous case with max_length < txn->length
max_length = PAGE_SIZE;
iotxn_phys_iter_init(&iter, txn, max_length);
for (int i = 0; i < 4; i++) {
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[i], "iotxn_iter_next returned wrong paddr");
ASSERT_EQ(length, max_length, "iotxn_iter_next returned wrong length");
}
ASSERT_EQ(iotxn_phys_iter_next(&iter, &paddr), 0u, "");
// discontiguous case with unaligned vmo_offset and txn->length
txn->vmo_offset = 100;
max_length = txn->length + PAGE_SIZE;
txn->length -= 1000;
iotxn_phys_iter_init(&iter, txn, max_length);
size_t total_length = 0;
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[0] + txn->vmo_offset, "");
ASSERT_EQ(length, (size_t)(PAGE_SIZE * 2) - txn->vmo_offset, "iotxn_iter_next returned wrong length");
total_length += length;
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[2], "");
ASSERT_EQ(length, (size_t)PAGE_SIZE, "");
total_length += length;
length = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(paddr, txn->phys[3], "");
total_length += length;
ASSERT_EQ(total_length, txn->length, "");
ASSERT_EQ(iotxn_phys_iter_next(&iter, &paddr), 0u, "");
iotxn_release(txn);
END_TEST;
}
// Test behavior of merging adjacent single-page entries.
static bool test_phys_iter_merge(void) {
BEGIN_TEST;
iotxn_t iotxn;
const size_t buf_size = 9 * PAGE_SIZE;
iotxn_init(&iotxn, MX_HANDLE_INVALID, PAGE_SIZE, buf_size);
iotxn.phys = malloc(sizeof(iotxn.phys[0]) * 9);
ASSERT_NONNULL(iotxn.phys, "");
iotxn.phys_count = 9;
iotxn.phys[0] = 0x12345000;
iotxn.phys[1] = 0x12346000;
iotxn.phys[2] = 0x12347000;
iotxn.phys[3] = 0x12349000;
iotxn.phys[4] = 0x1234b000;
iotxn.phys[5] = 0x1234d000;
iotxn.phys[6] = 0x1234e000;
iotxn.phys[7] = 0x1234f000;
iotxn.phys[8] = 0x12350000;
iotxn_phys_iter_t iter;
// Try iterating 3 pages at a time
iotxn_phys_iter_init(&iter, &iotxn, 3 * PAGE_SIZE);
mx_paddr_t paddr;
size_t size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 3u * PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[0], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, (size_t)PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[3], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, (size_t)PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[4], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 3u * PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[5], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, (size_t)PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[8], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 0u, "");
// Now try iterating with no cap
iotxn_phys_iter_init(&iter, &iotxn, 0);
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 3u * PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[0], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, (size_t)PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[3], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, (size_t)PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[4], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 4u * PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[5], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 0u, "");
free(iotxn.phys);
END_TEST;
}
// Test processing of a non-page-aligned contiguous backing buffer.
static bool test_phys_iter_unaligned_contig(void) {
BEGIN_TEST;
iotxn_t iotxn;
const size_t buf_size = 4 * PAGE_SIZE;
iotxn_init(&iotxn, MX_HANDLE_INVALID, 128, buf_size);
iotxn.phys = malloc(sizeof(iotxn.phys[0]) * 5);
ASSERT_NONNULL(iotxn.phys, "");
iotxn.phys_count = 5;
iotxn.phys[0] = 0x12345000;
iotxn.phys[1] = 0x12346000;
iotxn.phys[2] = 0x12347000;
iotxn.phys[3] = 0x12348000;
iotxn.phys[4] = 0x12349000;
iotxn_phys_iter_t iter;
// Try iterating 3 pages at a time
iotxn_phys_iter_init(&iter, &iotxn, 3 * PAGE_SIZE);
mx_paddr_t paddr;
size_t size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 3u * PAGE_SIZE - 128, "");
ASSERT_EQ(paddr, iotxn.phys[0] + 128, "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, PAGE_SIZE + 128u, "");
ASSERT_EQ(paddr, iotxn.phys[3], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 0u, "");
// Now try iterating with no cap
iotxn_phys_iter_init(&iter, &iotxn, 0);
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 4u * PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[0] + 128, "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 0u, "");
free(iotxn.phys);
END_TEST;
}
// Test processing of a non-page-aligned non-contiguous backing buffer.
static bool test_phys_iter_unaligned_noncontig(void) {
BEGIN_TEST;
iotxn_t iotxn;
const size_t buf_size = 2 * PAGE_SIZE;
iotxn_init(&iotxn, MX_HANDLE_INVALID, 128, buf_size);
iotxn.phys = malloc(sizeof(iotxn.phys[0]) * 3);
ASSERT_NONNULL(iotxn.phys, "");
iotxn.phys_count = 3;
iotxn.phys[0] = 0x12345000;
iotxn.phys[1] = 0x12347000;
iotxn.phys[2] = 0x12349000;
iotxn_phys_iter_t iter;
iotxn_phys_iter_init(&iter, &iotxn, 0);
mx_paddr_t paddr;
size_t size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, PAGE_SIZE - 128u, "");
ASSERT_EQ(paddr, iotxn.phys[0] + 128, "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, (size_t)PAGE_SIZE, "");
ASSERT_EQ(paddr, iotxn.phys[1], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 128u, "");
ASSERT_EQ(paddr, iotxn.phys[2], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 0u, "");
free(iotxn.phys);
END_TEST;
}
// Test processing of a tiny page-aligned buffer.
static bool test_phys_iter_tiny_aligned(void) {
BEGIN_TEST;
iotxn_t iotxn;
const size_t buf_size = 128;
iotxn_init(&iotxn, MX_HANDLE_INVALID, 0, buf_size);
iotxn.phys = malloc(sizeof(iotxn.phys[0]) * 1);
ASSERT_NONNULL(iotxn.phys, "");
iotxn.phys_count = 1;
iotxn.phys[0] = 0x12345000;
iotxn_phys_iter_t iter;
iotxn_phys_iter_init(&iter, &iotxn, 0);
mx_paddr_t paddr;
size_t size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 128u, "");
ASSERT_EQ(paddr, iotxn.phys[0], "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 0u, "");
free(iotxn.phys);
END_TEST;
}
// Test processing of a tiny non-page-aligned buffer.
static bool test_phys_iter_tiny_unaligned(void) {
BEGIN_TEST;
iotxn_t iotxn;
const size_t buf_size = 128;
iotxn_init(&iotxn, MX_HANDLE_INVALID, 128, buf_size);
iotxn.phys = malloc(sizeof(iotxn.phys[0]) * 1);
ASSERT_NONNULL(iotxn.phys, "");
iotxn.phys_count = 1;
iotxn.phys[0] = 0x12345000;
iotxn_phys_iter_t iter;
iotxn_phys_iter_init(&iter, &iotxn, 0);
mx_paddr_t paddr;
size_t size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 128u, "");
ASSERT_EQ(paddr, iotxn.phys[0] + 128, "");
size = iotxn_phys_iter_next(&iter, &paddr);
ASSERT_EQ(size, 0u, "");
free(iotxn.phys);
END_TEST;
}
BEGIN_TEST_CASE(iotxn_tests)
RUN_TEST(test_physmap_simple)
RUN_TEST(test_physmap_contiguous)
RUN_TEST(test_physmap_clone)
RUN_TEST(test_physmap_aligned_offset)
RUN_TEST(test_physmap_unaligned_offset)
RUN_TEST(test_physmap_unaligned_offset2)
RUN_TEST(test_phys_iter)
RUN_TEST(test_phys_iter_merge)
RUN_TEST(test_phys_iter_unaligned_contig)
RUN_TEST(test_phys_iter_unaligned_noncontig)
RUN_TEST(test_phys_iter_tiny_aligned)
RUN_TEST(test_phys_iter_tiny_unaligned)
END_TEST_CASE(iotxn_tests)
static void iotxn_test_output_func(const char* line, int len, void* arg) {
mx_handle_t h = *(mx_handle_t*)arg;
// len is not actually the number of bytes to output
mx_socket_write(h, 0u, line, strlen(line), NULL);
}
static mx_status_t iotxn_test_func(void* cookie, test_report_t* report, const void* arg, size_t arglen) {
mx_device_t* dev = (mx_device_t*)cookie;
test_protocol_t proto;
mx_status_t status = device_get_protocol(dev, MX_PROTOCOL_TEST, &proto);
if (status != MX_OK) {
return status;
}
mx_handle_t output = proto.ops->get_output_socket(proto.ctx);
if (output != MX_HANDLE_INVALID) {
unittest_set_output_function(iotxn_test_output_func, &output);
}
bool success = unittest_run_one_test(TEST_CASE_ELEMENT(iotxn_tests), TEST_ALL);
report->n_tests = 1;
report->n_success = success ? 1 : 0;
report->n_failed = success ? 0 : 1;
return success ? MX_OK : MX_ERR_INTERNAL;
}
static mx_status_t iotxn_test_bind(void* ctx, mx_device_t* dev, void** cookie) {
test_protocol_t proto;
mx_status_t status = device_get_protocol(dev, MX_PROTOCOL_TEST, &proto);
if (status != MX_OK) {
return status;
}
proto.ops->set_test_func(proto.ctx, iotxn_test_func, dev);
return MX_OK;
}
static mx_driver_ops_t iotxn_test_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = iotxn_test_bind,
};
MAGENTA_DRIVER_BEGIN(iotxn_test, iotxn_test_driver_ops, "magenta", "0.1", 2)
BI_ABORT_IF_AUTOBIND,
BI_MATCH_IF(EQ, BIND_PROTOCOL, MX_PROTOCOL_TEST),
MAGENTA_DRIVER_END(iotxn_test)