root/src/tests/system/kernel/device_manager/dma_resource_test.cpp
/*
 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */

#include <stdio.h>
#include <string.h>

#include <device_manager.h>

#include <vm/vm.h>

#include "dma_resources.h"
#include "io_requests.h"
#include "IOSchedulerSimple.h"


#define DMA_TEST_BLOCK_SIZE                             512
#define DMA_TEST_BUFFER_COUNT                   10
#define DMA_TEST_BOUNCE_BUFFER_COUNT    128


class TestSuite;

class TestSuiteContext {
public:
                                                        TestSuiteContext();
                                                        ~TestSuiteContext();

                        status_t                Init(size_t size, bool contiguous = true);

                        addr_t                  DataBase() const { return fDataBase; }
                        addr_t                  PhysicalDataBase() const
                                                                { return fPhysicalDataBase; }
                        addr_t                  SecondPhysicalDataBase() const
                                                                { return fSecondPhysicalDataBase; }

                        bool                    IsContiguous() const
                                                                { return fSecondPhysicalDataBase == 0; }

                        addr_t                  CompareBase() const { return fCompareBase; }

                        size_t                  Size() const { return fSize; }

private:
                        void                    _Uninit();

                        area_id                 fDataArea;
                        addr_t                  fDataBase;
                        addr_t                  fPhysicalDataBase;
                        addr_t                  fSecondPhysicalDataBase;
                        area_id                 fCompareArea;
                        addr_t                  fCompareBase;
                        size_t                  fSize;
};

class Test : public DoublyLinkedListLinkImpl<Test> {
public:
                                                        Test(TestSuite& suite, off_t offset, size_t length,
                                                                bool isWrite, uint32 flags);

                        Test&                   AddSource(addr_t base, size_t length);
                        Test&                   NextResult(off_t offset, bool partialBegin,
                                                                bool partialEnd);
                        Test&                   AddTarget(addr_t base, size_t length,
                                                                bool usesBounceBuffer);

                        void                    Run(DMAResource& resource);

private:
                        addr_t                  _SourceToVirtual(addr_t source);
                        addr_t                  _SourceToCompare(addr_t source);
                        void                    _Prepare();
                        void                    _CheckCompare();
                        void                    _CheckWrite();
                        void                    _CheckResults();
                        status_t                _DoIO(IOOperation& operation);
                        void                    _Panic(const char* message,...);

                        TestSuite&              fSuite;
                        off_t                   fOffset;
                        size_t                  fLength;
                        bool                    fIsWrite;
                        uint32                  fFlags;
                        generic_io_vec  fSourceVecs[32];
                        uint32                  fSourceCount;

                        struct target_t {
                                addr_t          address;
                                size_t          length;
                                bool            uses_bounce_buffer;
                        };
                        struct result_t {
                                off_t           offset;
                                target_t        targets[32];
                                uint32          count;
                                bool            partial_begin;
                                bool            partial_end;
                        };
                        result_t                fResults[32];
                        uint32                  fResultCount;
};

typedef DoublyLinkedList<Test> TestList;


class TestSuite {
public:
        TestSuite(TestSuiteContext& context, const char* name,
                        const dma_restrictions& restrictions, size_t blockSize)
                :
                fContext(context)
        {
                dprintf("----- Run \"%s\" tests ---------------------------\n", name);
                dprintf("  DMA restrictions: address %#" B_PRIxGENADDR " - %#"
                        B_PRIxGENADDR ", align %" B_PRIuGENADDR ", boundary %" B_PRIuGENADDR
                        ",\n    max transfer %" B_PRIuGENADDR ", max segs %" B_PRIx32
                        ", max seg size %" B_PRIxGENADDR ", flags %" B_PRIx32 "\n\n",
                        restrictions.low_address, restrictions.high_address,
                        restrictions.alignment, restrictions.boundary,
                        restrictions.max_transfer_size, restrictions.max_segment_count,
                        restrictions.max_segment_size, restrictions.flags);

                status_t status = fDMAResource.Init(restrictions, blockSize, 10, 10);
                if (status != B_OK)
                        panic("initializing DMA resource failed: %s\n", strerror(status));
        }

        ~TestSuite()
        {
                while (Test* test = fTests.RemoveHead()) {
                        delete test;
                }
        }

        Test& AddTest(off_t offset, size_t length, bool isWrite, uint32 flags)
        {
                Test* test = new(std::nothrow) Test(*this, offset, length, isWrite,
                        flags);
                fTests.Add(test);

                return *test;
        }

        void Run()
        {
                TestList::Iterator iterator = fTests.GetIterator();
                uint32 count = 1;
                while (Test* test = iterator.Next()) {
                        dprintf("test %lu...\n", count++);
                        test->Run(fDMAResource);
                }
        }

        addr_t DataBase() const { return fContext.DataBase(); }
        addr_t PhysicalDataBase() const { return fContext.PhysicalDataBase(); }
        addr_t SecondPhysicalDataBase() const
                { return fContext.SecondPhysicalDataBase(); }
        bool IsContiguous() const { return fContext.IsContiguous(); }
        addr_t CompareBase() const { return fContext.CompareBase(); }
        size_t Size() const { return fContext.Size(); }

private:
        TestSuiteContext& fContext;
        DMAResource             fDMAResource;
        uint8*                  fBase;
        uint8*                  fPhysicalBase;
        size_t                  fSize;
        TestList                fTests;
};


struct device_manager_info* sDeviceManager;

static area_id sArea;
static size_t sAreaSize;
static void* sAreaAddress;
static DMAResource* sDMAResource;
static IOScheduler* sIOScheduler;


status_t
do_io(void* data, IOOperation* operation)
{
        uint8* disk = (uint8*)sAreaAddress;
        off_t offset = operation->Offset();

        for (uint32 i = 0; i < operation->VecCount(); i++) {
                const generic_io_vec& vec = operation->Vecs()[i];
                generic_addr_t base = vec.base;
                generic_size_t length = vec.length;

                if (operation->IsWrite())
                        vm_memcpy_from_physical(disk + offset, base, length, false);
                else
                        vm_memcpy_to_physical(base, disk + offset, length, false);
        }

        if (sIOScheduler != NULL)
                sIOScheduler->OperationCompleted(operation, B_OK, operation->Length());
        return B_OK;
}


//      #pragma mark -


TestSuiteContext::TestSuiteContext()
        :
        fDataArea(-1),
        fCompareArea(-1),
        fSize(0)
{
}


TestSuiteContext::~TestSuiteContext()
{
        _Uninit();
}


void
TestSuiteContext::_Uninit()
{
        delete_area(fDataArea);
        delete_area(fCompareArea);
}


status_t
TestSuiteContext::Init(size_t size, bool contiguous)
{
        if (!contiguous) {
                // we can't force this, so we have to try and see

                if (size != B_PAGE_SIZE * 2)
                        return B_NOT_SUPPORTED;

                while (true) {
                        fDataArea = create_area("data buffer", (void**)&fDataBase,
                                B_ANY_KERNEL_ADDRESS, size, B_FULL_LOCK,
                                B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
                        if (fDataArea < B_OK)
                                return fDataArea;

                        // get memory map to see if we succeeded

                        physical_entry entry[2];
                        get_memory_map((void*)fDataBase, 2 * B_PAGE_SIZE, entry, 2);

                        if (entry[0].size == B_PAGE_SIZE) {
                                fPhysicalDataBase = (addr_t)entry[0].address;
                                fSecondPhysicalDataBase = (addr_t)entry[1].address;

                                dprintf("DMA Test area %p, physical %#" B_PRIxPHYSADDR
                                        ", second physical %#" B_PRIxPHYSADDR "\n",
                                        (void*)fDataBase, entry[0].address, entry[1].address);
                                break;
                        }

                        // try again
                        delete_area(fDataArea);
                }
        } else {
                fDataArea = create_area("data buffer", (void**)&fDataBase,
                        B_ANY_KERNEL_ADDRESS, size, B_CONTIGUOUS,
                        B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
                if (fDataArea < B_OK)
                        return fDataArea;

                physical_entry entry;
                get_memory_map((void*)fDataBase, B_PAGE_SIZE, &entry, 1);

                fPhysicalDataBase = (addr_t)entry.address;
                fSecondPhysicalDataBase = 0;

                dprintf("DMA Test area %p, physical %#" B_PRIxPHYSADDR "\n",
                        (void*)fDataBase, entry.address);
        }

        fCompareArea = create_area("compare buffer", (void**)&fCompareBase,
                B_ANY_KERNEL_ADDRESS, size, B_FULL_LOCK,
                B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
        if (fCompareArea < B_OK)
                return fCompareArea;

        fSize = size;
        return B_OK;
}


//      #pragma mark -


Test::Test(TestSuite& suite, off_t offset, size_t length, bool isWrite,
                uint32 flags)
        :
        fSuite(suite),
        fOffset(offset),
        fLength(length),
        fIsWrite(isWrite),
        fFlags(flags),
        fSourceCount(0),
        fResultCount(0)
{
}


Test&
Test::AddSource(addr_t address, size_t length)
{
        if (fSuite.IsContiguous() || (fFlags & B_PHYSICAL_IO_REQUEST) != 0
                || address < B_PAGE_SIZE) {
                fSourceVecs[fSourceCount].base
                        = ((fFlags & B_PHYSICAL_IO_REQUEST) == 0
                                ? fSuite.DataBase() : fSuite.PhysicalDataBase()) + address;
        } else {
                fSourceVecs[fSourceCount].base
                        = fSuite.SecondPhysicalDataBase() + address;
        }
        fSourceVecs[fSourceCount].length = length;
        fSourceCount++;

        return *this;
}


Test&
Test::NextResult(off_t offset, bool partialBegin, bool partialEnd)
{
        fResults[fResultCount].offset = offset;
        fResults[fResultCount].count = 0;
        fResults[fResultCount].partial_begin = partialBegin;
        fResults[fResultCount].partial_end = partialEnd;
        fResultCount++;

        return *this;
}


Test&
Test::AddTarget(addr_t base, size_t length, bool usesBounceBuffer)
{
        struct result_t& result = fResults[fResultCount - 1];
        struct target_t& target = result.targets[result.count++];

        target.address = base;
        target.length = length;
        target.uses_bounce_buffer = usesBounceBuffer;

        return *this;
}


addr_t
Test::_SourceToVirtual(addr_t source)
{
        if ((fFlags & B_PHYSICAL_IO_REQUEST) != 0) {
                if (!fSuite.IsContiguous() && source >= B_PAGE_SIZE)
                        return source - fSuite.SecondPhysicalDataBase() + fSuite.DataBase();

                return source - fSuite.PhysicalDataBase() + fSuite.DataBase();
        }

        return source;
}


addr_t
Test::_SourceToCompare(addr_t source)
{
        if ((fFlags & B_PHYSICAL_IO_REQUEST) != 0) {
                if (!fSuite.IsContiguous() && source >= B_PAGE_SIZE) {
                        return source - fSuite.SecondPhysicalDataBase()
                                + fSuite.CompareBase();
                }

                return source - fSuite.PhysicalDataBase() + fSuite.CompareBase();
        }

        return source - fSuite.DataBase() + fSuite.CompareBase();
}


void
Test::_Prepare()
{
        // prepare disk

        uint8* disk = (uint8*)sAreaAddress;
        for (size_t i = 0; i < sAreaSize; i++) {
                disk[i] = i % 26 + 'a';
        }

        // prepare data

        memset((void*)fSuite.DataBase(), 0xcc, fSuite.Size());

        if (fIsWrite) {
                off_t offset = fOffset;
                size_t length = fLength;

                for (uint32 i = 0; i < fSourceCount; i++) {
                        uint8* data = (uint8*)_SourceToVirtual(fSourceVecs[i].base);
                        size_t vecLength = min_c(fSourceVecs[i].length, length);

                        for (uint32 j = 0; j < vecLength; j++) {
                                data[j] = (offset + j) % 10 + '0';
                        }
                        offset += vecLength;
                        length -= vecLength;
                }
        }

        // prepare compare data

        memset((void*)fSuite.CompareBase(), 0xcc, fSuite.Size());

        if (fIsWrite) {
                // copy data from source
                off_t offset = fOffset;
                size_t length = fLength;

                for (uint32 i = 0; i < fSourceCount; i++) {
                        uint8* compare = (uint8*)_SourceToCompare(fSourceVecs[i].base);
                        size_t vecLength = min_c(fSourceVecs[i].length, length);

                        memcpy(compare, (void*)_SourceToVirtual(fSourceVecs[i].base),
                                vecLength);
                        offset += vecLength;
                        length -= vecLength;
                }
        } else {
                // copy data from drive
                off_t offset = fOffset;
                size_t length = fLength;

                for (uint32 i = 0; i < fSourceCount; i++) {
                        uint8* compare = (uint8*)_SourceToCompare(fSourceVecs[i].base);
                        size_t vecLength = min_c(fSourceVecs[i].length, length);

                        memcpy(compare, disk + offset, vecLength);
                        offset += vecLength;
                        length -= vecLength;
                }
        }

        if (fIsWrite)
                _CheckCompare();
}


void
Test::_CheckCompare()
{
        uint8* data = (uint8*)fSuite.DataBase();
        uint8* compare = (uint8*)fSuite.CompareBase();

        for (size_t i = 0; i < fSuite.Size(); i++) {
                if (data[i] != compare[i]) {
                        dprintf("offset %lu differs, %s:\n", i,
                                fIsWrite ? "write" : "read");
                        i &= ~63;
                        dump_block((char*)&data[i], min_c(64, fSuite.Size() - i), "  ");
                        dprintf("should be:\n");
                        dump_block((char*)&compare[i], min_c(64, fSuite.Size() - i), "  ");

                        _Panic("Data %s differs", fIsWrite ? "write" : "read");
                }
        }
}


void
Test::_CheckWrite()
{
        _CheckCompare();

        // check if we overwrote parts we shouldn't have

        uint8* disk = (uint8*)sAreaAddress;
        for (size_t i = 0; i < sAreaSize; i++) {
                if (i >= fOffset && i < fOffset + fLength)
                        continue;

                if (disk[i] != i % 26 + 'a') {
                        dprintf("disk[i] %c, expected %c, i %lu, fLength + fOffset %lld\n",
                                disk[i], (int)(i % 26 + 'a'), i, fLength + fOffset);
                        dprintf("offset %lu differs, touched innocent data:\n", i);
                        i &= ~63;
                        dump_block((char*)&disk[i], min_c(64, fSuite.Size() - i), "  ");

                        _Panic("Data %s differs", fIsWrite ? "write" : "read");
                }
        }

        // check if the data we wanted to have on disk ended up there

        off_t offset = fOffset;
        size_t length = fLength;

        for (uint32 i = 0; i < fSourceCount; i++) {
                uint8* data = (uint8*)_SourceToVirtual(fSourceVecs[i].base);
                size_t vecLength = min_c(fSourceVecs[i].length, length);

                for (uint32 j = 0; j < vecLength; j++) {
                        if (disk[offset + j] != data[j]) {
                                dprintf("offset %lu differs, found on disk:\n", j);
                                j &= ~63;
                                dump_block((char*)&disk[offset + j],
                                        min_c(64, fSuite.Size() - i), "  ");
                                dprintf("should be:\n");
                                dump_block((char*)&data[j], min_c(64, fSuite.Size() - j), "  ");

                                _Panic("Data write differs");
                        }
                }

                offset += vecLength;
                length -= vecLength;
        }
}


void
Test::_CheckResults()
{
        if (fIsWrite)
                _CheckWrite();
        else
                _CheckCompare();
}


status_t
Test::_DoIO(IOOperation& operation)
{
        return do_io(NULL, &operation);
}


void
Test::Run(DMAResource& resource)
{
        _Prepare();

        IORequest request;
        status_t status = request.Init(fOffset, fSourceVecs, fSourceCount,
                fLength, fIsWrite, fFlags);
        if (status != B_OK)
                _Panic("request init failed: %s\n", strerror(status));

        uint32 resultIndex = 0;

        IOOperation operation;
        while (request.RemainingBytes() > 0) {
                if (resultIndex >= fResultCount)
                        _Panic("no results left");

                status_t status = resource.TranslateNext(&request, &operation, 0);
                if (status != B_OK) {
                        _Panic("DMAResource::TranslateNext() failed: %s\n",
                                strerror(status));
                        break;
                }

                DMABuffer* buffer = operation.Buffer();

                dprintf("IOOperation: offset %" B_PRIdOFF ", length %" B_PRIuGENADDR
                        " (%" B_PRIdOFF "/%" B_PRIuGENADDR ")\n", operation.Offset(),
                        operation.Length(), operation.OriginalOffset(),
                        operation.OriginalLength());
                dprintf("  DMABuffer %p, %lu vecs, bounce buffer: %p (%p) %s\n", buffer,
                        buffer->VecCount(), buffer->BounceBufferAddress(),
                        (void*)buffer->PhysicalBounceBufferAddress(),
                        operation.UsesBounceBuffer() ? "used" : "unused");
                for (uint32 i = 0; i < buffer->VecCount(); i++) {
                        dprintf("    [%" B_PRIu32 "] base %#" B_PRIxGENADDR ", length %"
                                B_PRIuGENADDR "%s\n", i, buffer->VecAt(i).base,
                                buffer->VecAt(i).length,
                                buffer->UsesBounceBufferAt(i) ? ", bounce" : "");
                }

                dprintf("  remaining bytes: %" B_PRIuGENADDR "\n",
                        request.RemainingBytes());

                // check results

                const result_t& result = fResults[resultIndex];
                if (result.count != buffer->VecCount())
                        panic("result count differs (expected %lu)\n", result.count);

                for (uint32 i = 0; i < result.count; i++) {
                        const target_t& target = result.targets[i];
                        const generic_io_vec& vec = buffer->VecAt(i);

                        if (target.length != vec.length)
                                _Panic("[%lu] length differs", i);

                        generic_addr_t address;
                        if (target.uses_bounce_buffer) {
                                address = target.address
                                        + (addr_t)buffer->PhysicalBounceBufferAddress();
                        } else if (fSuite.IsContiguous() || target.address < B_PAGE_SIZE) {
                                address = target.address + fSuite.PhysicalDataBase();
                        } else {
                                address = target.address - B_PAGE_SIZE
                                        + fSuite.SecondPhysicalDataBase();
                        }

                        if (address != vec.base) {
                                _Panic("[%" B_PRIu32 "] address differs: %#" B_PRIxGENADDR
                                        ", should be %#" B_PRIuGENADDR "", i, vec.base, address);
                        }
                }

                _DoIO(operation);
                operation.SetStatus(B_OK);
                bool finished = operation.Finish();
                bool isPartial = result.partial_begin || result.partial_end;
                if (finished == (isPartial && fIsWrite))
                        _Panic("partial finished %s", finished ? "early" : "late");

                if (!finished) {
                        dprintf("  operation not done yet!\n");
                        _DoIO(operation);
                        operation.SetStatus(B_OK);

                        isPartial = result.partial_begin && result.partial_end;
                        finished = operation.Finish();
                        if (finished == result.partial_begin && result.partial_end)
                                _Panic("partial finished %s", finished ? "early" : "late");

                        if (!finished) {
                                dprintf("  operation not done yet!\n");
                                _DoIO(operation);
                                operation.SetStatus(B_OK);

                                if (!operation.Finish())
                                        _Panic("operation doesn't finish");
                        }
                }

                request.OperationFinished(&operation, operation.Status(),
                        false,
                        operation.OriginalOffset() - operation.Parent()->Offset()
                                + operation.OriginalLength());

                resultIndex++;
        }

        _CheckResults();
}


void
Test::_Panic(const char* message,...)
{
        char buffer[1024];

        va_list args;
        va_start(args, message);
        vsnprintf(buffer, sizeof(buffer), message, args);
        va_end(args);

        dprintf("test failed\n");
        dprintf("  offset:  %lld\n", fOffset);
        dprintf("  base:    %p (physical: %p)\n", (void*)fSuite.DataBase(),
                (void*)fSuite.PhysicalDataBase());
        dprintf("  length:  %lu\n", fLength);
        dprintf("  write:   %d\n", fIsWrite);
        dprintf("  flags:   %#lx\n", fFlags);
        dprintf("  sources:\n");
        for (uint32 i = 0; i < fSourceCount; i++) {
                dprintf("    [%#" B_PRIxGENADDR ", %" B_PRIuGENADDR "]\n",
                        fSourceVecs[i].base, fSourceVecs[i].length);
        }
        for (uint32 i = 0; i < fResultCount; i++) {
                const result_t& result = fResults[i];
                dprintf("  result %lu:\n", i);
                dprintf("    offset:  %lld\n", result.offset);
                dprintf("    partial: %d/%d\n", result.partial_begin,
                        result.partial_end);

                for (uint32 k = 0; k < result.count; k++) {
                        const target_t& target = result.targets[k];
                        dprintf("    [%p, %lu, %d]\n", (void*)target.address, target.length,
                                target.uses_bounce_buffer);
                }
        }

        panic("%s", buffer);
}


static void
run_tests_no_restrictions(TestSuiteContext& context)
{
        const dma_restrictions restrictions = {
                0x0,    // low
                0x0,    // high
                0,              // alignment
                0,              // boundary
                0,              // max transfer
                0,              // max segment count
                0,              // max segment size
                0               // flags
        };

        TestSuite suite(context, "no restrictions", restrictions, 512);

        suite.AddTest(0, 1024, false, 0)
                .AddSource(0, 1024)
                .NextResult(0, false, false)
                        .AddTarget(0, 1024, false);

        // read partial begin/end
        suite.AddTest(23, 1024, false, 0)
                .AddSource(0, 1024)
                .NextResult(0, true, true)
                        .AddTarget(0, 23, true)
                        .AddTarget(0, 1024, false)
                        .AddTarget(23, 512 - 23, true);

        // read less than a block
        suite.AddTest(23, 30, false, 0)
                .AddSource(0, 1024)
                .NextResult(0, true, true)
                        .AddTarget(0, 23, true)
                        .AddTarget(0, 30, false)
                        .AddTarget(23, 512 - 53, true);

        // write begin/end
        suite.AddTest(23, 1024, true, 0)
                .AddSource(0, 1024)
                .NextResult(0, true, true)
                        .AddTarget(0, 512, true)
                        .AddTarget(489, 512, false)
                        .AddTarget(512, 512, true);

        // read partial end, length < iovec length
        suite.AddTest(0, 1028, false, 0)
                .AddSource(0, 512)
                .AddSource(1024, 1024)
                .NextResult(0, false, true)
                        .AddTarget(0, 512, false)
                        .AddTarget(1024, 516, false)
                        .AddTarget(0, 508, true);

        // write partial end, length < iovec length
        suite.AddTest(0, 1028, true, 0)
                .AddSource(0, 512)
                .AddSource(1024, 1024)
                .NextResult(0, false, true)
                        .AddTarget(0, 512, false)
                        .AddTarget(1024, 512, false)
                        .AddTarget(0, 512, true);

        suite.Run();
}


static void
run_tests_address_restrictions(TestSuiteContext& context)
{
        const dma_restrictions restrictions = {
                context.PhysicalDataBase() + 512,       // low
                0,              // high
                0,              // alignment
                0,              // boundary
                0,              // max transfer
                0,              // max segment count
                0,              // max segment size
                0               // flags
        };

        TestSuite suite(context, "address", restrictions, 512);

        suite.AddTest(0, 1024, false, 0)
                .AddSource(0, 1024)
                .NextResult(0, false, false)
                        .AddTarget(0, 512, true)
                        .AddTarget(512, 512, false);

        suite.Run();
}


static void
run_tests_alignment_restrictions(TestSuiteContext& context)
{
        const dma_restrictions restrictions = {
                0x0,    // low
                0x0,    // high
                32,             // alignment
                0,              // boundary
                0,              // max transfer
                0,              // max segment count
                0,              // max segment size
                0               // flags
        };

        TestSuite suite(context, "alignment", restrictions, 512);

        suite.AddTest(0, 1024, false, B_PHYSICAL_IO_REQUEST)
                .AddSource(16, 1024)
                .NextResult(0, false, false)
                        .AddTarget(0, 1024, true);

        suite.AddTest(0, 2559, true, B_PHYSICAL_IO_REQUEST)
                .AddSource(0, 2559)
                .NextResult(0, false, true)
                        .AddTarget(0, 2048, false)
                        .AddTarget(0, 512, true);

        suite.AddTest(0, 51, true, B_PHYSICAL_IO_REQUEST)
                .AddSource(0, 4096)
                .NextResult(0, false, true)
                        .AddTarget(0, 512, true);

        suite.AddTest(32, 51, true, B_PHYSICAL_IO_REQUEST)
                .AddSource(0, 4096)
                .NextResult(0, true, false)
                        .AddTarget(0, 512, true);
                        // Note: The operation has actually both partial begin and end, but
                        // our Test is not clever enough to realize that it is only one
                        // block and thus only one read phase is needed.

        suite.Run();
}


static void
run_tests_boundary_restrictions(TestSuiteContext& context)
{
        const dma_restrictions restrictions = {
                0x0,    // low
                0x0,    // high
                0,              // alignment
                1024,   // boundary
                0,              // max transfer
                0,              // max segment count
                0,              // max segment size
                0               // flags
        };

        TestSuite suite(context, "boundary", restrictions, 512);

        suite.AddTest(0, 2000, false, 0)
                .AddSource(0, 2048)
                .NextResult(0, false, false)
                        .AddTarget(0, 1024, false)
                        .AddTarget(1024, 976, false)
                        .AddTarget(0, 48, true);

        suite.Run();
}


static void
run_tests_segment_restrictions(TestSuiteContext& context)
{
        const dma_restrictions restrictions = {
                0x0,    // low
                0x0,    // high
                0,              // alignment
                0,              // boundary
                0,              // max transfer
                4,              // max segment count
                1024,   // max segment size
                0               // flags
        };

        TestSuite suite(context, "segment", restrictions, 512);

        suite.AddTest(0, 4096, false, 0)
                .AddSource(0, 4096)
                .NextResult(0, false, false)
                        .AddTarget(0, 1024, false)
                        .AddTarget(1024, 1024, false)
                        .AddTarget(2048, 1024, false)
                        .AddTarget(3072, 1024, false);

        suite.AddTest(0, 2560, false, B_PHYSICAL_IO_REQUEST)
                .AddSource(0, 512)
                .AddSource(1024, 512)
                .AddSource(2048, 512)
                .AddSource(3072, 512)
                .AddSource(4096, 512)
                .NextResult(0, false, false)
                        .AddTarget(0, 512, false)
                        .AddTarget(1024, 512, false)
                        .AddTarget(2048, 512, false)
                        .AddTarget(3072, 512, false)
                .NextResult(2048, false, false)
                        .AddTarget(4096, 512, false);

        suite.Run();
}


static void
run_tests_transfer_restrictions(TestSuiteContext& context)
{
        const dma_restrictions restrictions = {
                0x0,    // low
                0x0,    // high
                0,              // alignment
                0,              // boundary
                1024,   // max transfer
                0,              // max segment count
                0,              // max segment size
                0               // flags
        };

        TestSuite suite(context, "transfer", restrictions, 512);

        suite.AddTest(0, 4000, false, 0)
                .AddSource(0, 4096)
                .NextResult(0, false, false)
                        .AddTarget(0, 1024, false)
                .NextResult(0, false, false)
                        .AddTarget(1024, 1024, false)
                .NextResult(0, false, false)
                        .AddTarget(2048, 1024, false)
                .NextResult(0, false, false)
                        .AddTarget(3072, 1024 - 96, false)
                        .AddTarget(0, 96, true);

        suite.Run();
}


static void
run_tests_interesting_restrictions(TestSuiteContext& context)
{
        dma_restrictions restrictions = {
                0x0,    // low
                0x0,    // high
                32,             // alignment
                512,    // boundary
                0,              // max transfer
                0,              // max segment count
                0,              // max segment size
                0               // flags
        };

        TestSuite suite(context, "interesting", restrictions, 512);

        // read with partial begin/end
        suite.AddTest(32, 1000, false, 0)
                .AddSource(0, 1024)
                .NextResult(0, true, true)
                        .AddTarget(0, 32, true)
                        .AddTarget(0, 512, false)
                        .AddTarget(512, 480, false)
                        .AddTarget(32, 480, true)
                        .AddTarget(512, 32, true);

        // write with partial begin/end
        suite.AddTest(32, 1000, true, 0)
                .AddSource(0, 1024)
                .NextResult(0, true, true)
                        .AddTarget(0, 512, true)
                        .AddTarget(480, 32, false)
                        .AddTarget(512, 480, false)
                        .AddTarget(512, 512, true);

        suite.Run();

        restrictions = (dma_restrictions){
                0x0,    // low
                0x0,    // high
                32,             // alignment
                512,    // boundary
                0,              // max transfer
                4,              // max segment count
                0,              // max segment size
                0               // flags
        };

        TestSuite suite2(context, "interesting2", restrictions, 512);

        suite2.AddTest(32, 1000, false, 0)
                .AddSource(0, 1024)
                .NextResult(0, true, false)
                        .AddTarget(0, 32, true)
                        .AddTarget(0, 512, false)
                        .AddTarget(512, 480, false)
                .NextResult(0, false, true)
                        .AddTarget(0, 512, true);

        suite2.Run();
}


static void
run_tests_mean_restrictions(TestSuiteContext& context)
{
        const dma_restrictions restrictions = {
                context.PhysicalDataBase() + 1024,      // low
                0x0,    // high
                32,             // alignment
                1024,   // boundary
                0,              // max transfer
                2,              // max segment count
                512,    // max segment size
                0               // flags
        };

        TestSuite suite(context, "mean", restrictions, 512);

        suite.AddTest(0, 1024, false, 0)
                .AddSource(0, 1024)
                .NextResult(0, false, false)
                        .AddTarget(0, 512, true)
                        .AddTarget(512, 512, true);

        suite.AddTest(0, 1024, false, 0)
                .AddSource(1024 + 32, 1024)
                .NextResult(0, false, false)
                        .AddTarget(1024 + 32, 512, false)
                .NextResult(0, false, false)
                        .AddTarget(1568, 480, false)
                        .AddTarget(1568 + 480, 32, false);

        suite.Run();
}


static void
run_tests_non_contiguous_no_restrictions(TestSuiteContext& context)
{
        const dma_restrictions restrictions = {
                0x0,    // low
                0x0,    // high
                0,              // alignment
                0,              // boundary
                0,              // max transfer
                0,              // max segment count
                0,              // max segment size
                0               // flags
        };

        TestSuite suite(context, "non contiguous, no restrictions", restrictions,
                512);

        suite.AddTest(0, 8192, false, 0)
                .AddSource(0, 8192)
                .NextResult(0, false, false)
                        .AddTarget(0, 4096, false)
                        .AddTarget(4096, 4096, false);

        suite.Run();
}


static void
run_test()
{
        TestSuiteContext context;
        status_t status = context.Init(4 * B_PAGE_SIZE);
        if (status != B_OK)
                return;

        run_tests_no_restrictions(context);
        run_tests_address_restrictions(context);
        run_tests_alignment_restrictions(context);
        run_tests_boundary_restrictions(context);
        run_tests_segment_restrictions(context);
        run_tests_transfer_restrictions(context);
        run_tests_interesting_restrictions(context);
        run_tests_mean_restrictions(context);

        // physical non-contiguous

        status = context.Init(2 * B_PAGE_SIZE, false);
        if (status != B_OK)
                return;

        run_tests_non_contiguous_no_restrictions(context);

        dprintf("All tests passed!\n");
}


//      #pragma mark - driver


static float
dma_test_supports_device(device_node *parent)
{
        const char* bus = NULL;
        if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)
                        == B_OK && !strcmp(bus, "generic"))
                return 0.8;

        return -1;
}


static status_t
dma_test_register_device(device_node *parent)
{
        device_attr attrs[] = {
                {B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {string: "DMA Test"}},
                {NULL}
        };

        return sDeviceManager->register_node(parent,
                "drivers/disk/dma_resource_test/driver_v1", attrs, NULL, NULL);
}


static status_t
dma_test_init_driver(device_node *node, void **_driverCookie)
{
        sAreaSize = 10 * 1024 * 1024;
        sArea = create_area("dma test", &sAreaAddress, B_ANY_KERNEL_ADDRESS,
                sAreaSize, B_LAZY_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
        if (sArea < B_OK)
                return sArea;

        *_driverCookie = node;

        run_test();
        return B_OK;
}


static void
dma_test_uninit_driver(void *driverCookie)
{
        delete_area(sArea);
}


static status_t
dma_test_register_child_devices(void *driverCookie)
{
        return sDeviceManager->publish_device((device_node*)driverCookie,
                "disk/virtual/dma_test/raw",
                "drivers/disk/dma_resource_test/device_v1");
}


//      #pragma mark - device


static status_t
dma_test_init_device(void *driverCookie, void **_deviceCookie)
{
        const dma_restrictions restrictions = {
                0x0,    // low
                0x0,    // high
                4,              // alignment
                0,              // boundary
                0,              // max transfer
                0,              // max segment count
                B_PAGE_SIZE, // max segment size
                0               // flags
        };

        *_deviceCookie = driverCookie;
        sDMAResource = new(std::nothrow) DMAResource;
        if (sDMAResource == NULL)
                return B_NO_MEMORY;

        status_t status = sDMAResource->Init(restrictions, DMA_TEST_BLOCK_SIZE,
                DMA_TEST_BUFFER_COUNT, DMA_TEST_BOUNCE_BUFFER_COUNT);
        if (status != B_OK) {
                delete sDMAResource;
                return status;
        }

        sIOScheduler = new(std::nothrow) IOSchedulerSimple(sDMAResource);
        if (sIOScheduler == NULL) {
                delete sDMAResource;
                return B_NO_MEMORY;
        }

        status = sIOScheduler->Init("dma test scheduler");
        if (status != B_OK) {
                delete sIOScheduler;
                delete sDMAResource;
                return status;
        }

        sIOScheduler->SetCallback(&do_io, NULL);
        return B_OK;
}


static void
dma_test_uninit_device(void *deviceCookie)
{
}


static status_t
dma_test_open(void *deviceCookie, const char *path, int openMode,
        void **_cookie)
{
        return B_OK;
}


static status_t
dma_test_close(void *cookie)
{
        return B_OK;
}


static status_t
dma_test_free(void *cookie)
{
        return B_OK;
}


static status_t
dma_test_read(void *cookie, off_t pos, void *buffer, size_t *_length)
{
        size_t length = *_length;

        if (pos >= sAreaSize)
                return B_BAD_VALUE;
        if (pos + length > sAreaSize)
                length = sAreaSize - pos;

#if 1
        IORequest request;
        status_t status = request.Init(pos, (addr_t)buffer, length, false, 0);
        if (status != B_OK)
                return status;

        status = sIOScheduler->ScheduleRequest(&request);
        if (status != B_OK)
                return status;

        status = request.Wait(0, 0);
        dprintf("dma_test_read(): request.Wait() returned: %s\n", strerror(status));
#else
        status_t status = user_memcpy(buffer, (uint8*)sAreaAddress + pos, length);
#endif

        if (status == B_OK)
                *_length = length;
        return status;
}


static status_t
dma_test_write(void *cookie, off_t pos, const void *buffer, size_t *_length)
{
        size_t length = *_length;

        if (pos >= sAreaSize)
                return B_BAD_VALUE;
        if (pos + length > sAreaSize)
                length = sAreaSize - pos;

#if 1
        IORequest request;
        status_t status = request.Init(pos, (addr_t)buffer, length, true, 0);
        if (status != B_OK)
                return status;

        status = sIOScheduler->ScheduleRequest(&request);
        if (status != B_OK)
                return status;

        status = request.Wait(0, 0);
        dprintf("dma_test_write(): request.Wait() returned: %s\n",
                strerror(status));
#else
        status_t status = user_memcpy((uint8*)sAreaAddress + pos, buffer, length);
#endif

        if (status == B_OK)
                *_length = length;

        return status;
}


static status_t
dma_test_io(void *cookie, io_request *request)
{
        dprintf("dma_test_io(%p)\n", request);

        return sIOScheduler->ScheduleRequest(request);
}


static status_t
dma_test_control(void *cookie, uint32 op, void *buffer, size_t length)
{
        switch (op) {
                case B_GET_DEVICE_SIZE:
                        return user_memcpy(buffer, &sAreaSize, sizeof(size_t));

                case B_SET_NONBLOCKING_IO:
                case B_SET_BLOCKING_IO:
                        return B_OK;

                case B_GET_READ_STATUS:
                case B_GET_WRITE_STATUS:
                {
                        bool value = true;
                        return user_memcpy(buffer, &value, sizeof(bool));
                }

                case B_GET_GEOMETRY:
                case B_GET_BIOS_GEOMETRY:
                {
                        device_geometry geometry;
                        geometry.bytes_per_sector = DMA_TEST_BLOCK_SIZE;
                        geometry.sectors_per_track = 1;
                        geometry.cylinder_count = sAreaSize / DMA_TEST_BLOCK_SIZE;
                        geometry.head_count = 1;
                        geometry.device_type = B_DISK;
                        geometry.removable = true;
                        geometry.read_only = false;
                        geometry.write_once = false;

                        return user_memcpy(buffer, &geometry, sizeof(device_geometry));
                }

                case B_GET_MEDIA_STATUS:
                {
                        status_t status = B_OK;
                        return user_memcpy(buffer, &status, sizeof(status_t));
                }

                case B_SET_UNINTERRUPTABLE_IO:
                case B_SET_INTERRUPTABLE_IO:
                case B_FLUSH_DRIVE_CACHE:
                        return B_OK;
        }
        return B_BAD_VALUE;
}


module_dependency module_dependencies[] = {
        {B_DEVICE_MANAGER_MODULE_NAME, (module_info **)&sDeviceManager},
        {}
};


static const struct driver_module_info sDMATestDriverModule = {
        {
                "drivers/disk/dma_resource_test/driver_v1",
                0,
                NULL
        },

        dma_test_supports_device,
        dma_test_register_device,
        dma_test_init_driver,
        dma_test_uninit_driver,
        dma_test_register_child_devices
};

static const struct device_module_info sDMATestDeviceModule = {
        {
                "drivers/disk/dma_resource_test/device_v1",
                0,
                NULL
        },

        dma_test_init_device,
        dma_test_uninit_device,
        NULL,

        dma_test_open,
        dma_test_close,
        dma_test_free,

        dma_test_read,
        dma_test_write,
        dma_test_io,

        dma_test_control,

        NULL,   // select
        NULL    // deselect
};

const module_info* modules[] = {
        (module_info*)&sDMATestDriverModule,
        (module_info*)&sDMATestDeviceModule,
        NULL
};