blob: 9e8f71d1622b7cbac3ff74e6e797169adb1732d7 [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 "usb-request-queue.h"
#include <lib/mmio/mmio.h>
#include <lib/mock-function/mock-function.h>
#include <lib/zx/vmo.h>
#include <zircon/types.h>
#include <array>
#include <ddk/protocol/usb/request.h>
#include <fbl/auto_lock.h>
#include <fbl/condition_variable.h>
#include <fbl/mutex.h>
#include <usb/request-cpp.h>
#include <zxtest/zxtest.h>
#include "usb-transaction.h"
namespace mt_usb_hci {
// FakeTransaction is a Transaction with stub-functionality setup for testing.
class FakeTransaction : public Transaction {
public:
mock_function::MockFunction<size_t>& m_actual() { return m_actual_; }
mock_function::MockFunction<void, bool>& m_advance() { return m_advance_; }
mock_function::MockFunction<bool>& m_ok() { return m_ok_; }
mock_function::MockFunction<void>& m_cancel() { return m_cancel_; }
mock_function::MockFunction<void>& m_wait() { return m_wait_; }
size_t actual() const { return m_actual_.Call(); }
void Advance(bool interrupt) { return m_advance_.Call(interrupt); }
bool Ok() const { return m_ok_.Call(); }
void Cancel() { return m_cancel_.Call(); }
void Wait() { return m_wait_.Call(); }
private:
mutable mock_function::MockFunction<size_t> m_actual_;
mock_function::MockFunction<void, bool> m_advance_;
mutable mock_function::MockFunction<bool> m_ok_;
mock_function::MockFunction<void> m_cancel_;
mock_function::MockFunction<void> m_wait_;
};
// TestingQueue is a class with stub DispatchRequest().
class TestingQueue : public TransactionQueue {
public:
explicit TestingQueue(ddk::MmioView view) : TransactionQueue(view, 123, {}), dispatch_ct_(0) {}
mock_function::MockFunction<zx_status_t>& m_dispatch() { return m_dispatch_; }
FakeTransaction& transaction() { return static_cast<FakeTransaction&>(*transaction_); }
void new_transaction() { transaction_.reset(new FakeTransaction); }
// Wait for n-invocations of DispatchRequest() to be invoked. This allows us to synchronize the
// tests to the iterations of the queue thread.
void Wait(int n = 1) {
fbl::AutoLock _(&lock_);
while (dispatch_ct_ < n) {
cond_.Wait(&lock_);
}
}
private:
zx_status_t DispatchRequest(usb::BorrowedRequest<> req) override {
fbl::AutoLock _(&lock_);
dispatch_ct_++;
cond_.Signal();
req.Complete(ZX_OK, 0);
return m_dispatch_.Call();
}
// The usb::BorrowedRequest supplied to TransactionEndpoint::DispatchRequest is being ignored
// here because MockFunction::Call doesn't implement perfect forwarding.
mock_function::MockFunction<zx_status_t> m_dispatch_;
int dispatch_ct_ TA_GUARDED(lock_);
fbl::Mutex lock_;
fbl::ConditionVariable cond_ TA_GUARDED(lock_);
};
class TransactionQueueTest : public zxtest::Test {
protected:
void SetUp() {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(4096, 0, &vmo));
ASSERT_OK(ddk::MmioBuffer::Create(0, 4096, std::move(vmo), ZX_CACHE_POLICY_CACHED, &mmio_));
}
std::optional<ddk::MmioBuffer> mmio_;
static constexpr usb_request_complete_t cb_ = {
.callback = [](void*, usb_request_t* r) { usb::Request<>(r, sizeof(usb_request_t)); },
.ctx = nullptr,
};
};
TEST_F(TransactionQueueTest, QueueThread_StartAndHalt) {
TestingQueue q(mmio_->View(0));
EXPECT_OK(q.StartQueueThread());
EXPECT_OK(q.Halt());
}
TEST_F(TransactionQueueTest, QueueThread_Enqueue) {
std::optional<usb::Request<>> o_req;
size_t alloc_sz = usb::BorrowedRequest<>::RequestSize(sizeof(usb_request_t));
ASSERT_OK(usb::Request<>::Alloc(&o_req, 4096, 0, alloc_sz));
usb::BorrowedRequest<> req(o_req->take(), cb_, sizeof(usb_request_t));
TestingQueue q(mmio_->View(0));
q.m_dispatch().ExpectCall(ZX_OK);
EXPECT_OK(q.StartQueueThread());
EXPECT_OK(q.QueueRequest(std::move(req)));
q.Wait();
EXPECT_OK(q.Halt());
q.m_dispatch().VerifyAndClear();
}
TEST_F(TransactionQueueTest, QueueThread_EnqueueMultiBeforeThreadStarts) {
std::optional<usb::Request<>> o_req1;
std::optional<usb::Request<>> o_req2;
std::optional<usb::Request<>> o_req3;
std::optional<usb::Request<>> o_req4;
std::optional<usb::Request<>> o_req5;
size_t alloc_sz = usb::BorrowedRequest<>::RequestSize(sizeof(usb_request_t));
ASSERT_OK(usb::Request<>::Alloc(&o_req1, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req2, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req3, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req4, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req5, 4096, 0, alloc_sz));
usb::BorrowedRequest<> req1(o_req1->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req2(o_req2->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req3(o_req3->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req4(o_req4->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req5(o_req5->take(), cb_, sizeof(usb_request_t));
TestingQueue q(mmio_->View(0));
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
EXPECT_OK(q.QueueRequest(std::move(req1)));
EXPECT_OK(q.QueueRequest(std::move(req2)));
EXPECT_OK(q.QueueRequest(std::move(req3)));
EXPECT_OK(q.QueueRequest(std::move(req4)));
EXPECT_OK(q.QueueRequest(std::move(req5)));
EXPECT_OK(q.StartQueueThread());
q.Wait(5);
EXPECT_OK(q.Halt());
q.m_dispatch().VerifyAndClear();
}
TEST_F(TransactionQueueTest, QueueThread_EnqueueMultiAfterThreadStarts) {
std::optional<usb::Request<>> o_req1;
std::optional<usb::Request<>> o_req2;
std::optional<usb::Request<>> o_req3;
std::optional<usb::Request<>> o_req4;
std::optional<usb::Request<>> o_req5;
size_t alloc_sz = usb::BorrowedRequest<>::RequestSize(sizeof(usb_request_t));
ASSERT_OK(usb::Request<>::Alloc(&o_req1, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req2, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req3, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req4, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req5, 4096, 0, alloc_sz));
usb::BorrowedRequest<> req1(o_req1->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req2(o_req2->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req3(o_req3->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req4(o_req4->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req5(o_req5->take(), cb_, sizeof(usb_request_t));
TestingQueue q(mmio_->View(0));
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
EXPECT_OK(q.StartQueueThread());
EXPECT_OK(q.QueueRequest(std::move(req1)));
EXPECT_OK(q.QueueRequest(std::move(req2)));
EXPECT_OK(q.QueueRequest(std::move(req3)));
EXPECT_OK(q.QueueRequest(std::move(req4)));
EXPECT_OK(q.QueueRequest(std::move(req5)));
q.Wait(5);
EXPECT_OK(q.Halt());
q.m_dispatch().VerifyAndClear();
}
TEST_F(TransactionQueueTest, QueueThread_EnqueueMultiDuringThreadStart) {
std::optional<usb::Request<>> o_req1;
std::optional<usb::Request<>> o_req2;
std::optional<usb::Request<>> o_req3;
std::optional<usb::Request<>> o_req4;
std::optional<usb::Request<>> o_req5;
size_t alloc_sz = usb::BorrowedRequest<>::RequestSize(sizeof(usb_request_t));
ASSERT_OK(usb::Request<>::Alloc(&o_req1, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req2, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req3, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req4, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req5, 4096, 0, alloc_sz));
usb::BorrowedRequest<> req1(o_req1->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req2(o_req2->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req3(o_req3->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req4(o_req4->take(), cb_, sizeof(usb_request_t));
usb::BorrowedRequest<> req5(o_req5->take(), cb_, sizeof(usb_request_t));
TestingQueue q(mmio_->View(0));
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
q.m_dispatch().ExpectCall(ZX_OK);
EXPECT_OK(q.QueueRequest(std::move(req1)));
EXPECT_OK(q.QueueRequest(std::move(req2)));
EXPECT_OK(q.StartQueueThread());
EXPECT_OK(q.QueueRequest(std::move(req3)));
EXPECT_OK(q.QueueRequest(std::move(req4)));
EXPECT_OK(q.QueueRequest(std::move(req5)));
q.Wait(5);
EXPECT_OK(q.Halt());
q.m_dispatch().VerifyAndClear();
}
TEST_F(TransactionQueueTest, QueueThread_CancelAll) {
typedef struct {
int idx = 0;
std::array<zx_status_t, 5> array;
} capture_t;
auto callback = [](void* ctx, usb_request_t* req) {
auto* cap = static_cast<capture_t*>(ctx);
cap->array[cap->idx++] = req->response.status;
usb::Request<>(req, sizeof(usb_request_t));
};
capture_t cap;
const usb_request_complete_t cb = {
.callback = callback,
.ctx = &cap,
};
std::optional<usb::Request<>> o_req1;
std::optional<usb::Request<>> o_req2;
std::optional<usb::Request<>> o_req3;
std::optional<usb::Request<>> o_req4;
std::optional<usb::Request<>> o_req5;
size_t alloc_sz = usb::BorrowedRequest<>::RequestSize(sizeof(usb_request_t));
ASSERT_OK(usb::Request<>::Alloc(&o_req1, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req2, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req3, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req4, 4096, 0, alloc_sz));
ASSERT_OK(usb::Request<>::Alloc(&o_req5, 4096, 0, alloc_sz));
usb::BorrowedRequest<> req1(o_req1->take(), cb, sizeof(usb_request_t));
usb::BorrowedRequest<> req2(o_req2->take(), cb, sizeof(usb_request_t));
usb::BorrowedRequest<> req3(o_req3->take(), cb, sizeof(usb_request_t));
usb::BorrowedRequest<> req4(o_req4->take(), cb, sizeof(usb_request_t));
usb::BorrowedRequest<> req5(o_req5->take(), cb, sizeof(usb_request_t));
// Note here we don't start the thread.
TestingQueue q(mmio_->View(0));
q.new_transaction();
q.transaction().m_cancel().ExpectCall();
EXPECT_OK(q.QueueRequest(std::move(req1)));
EXPECT_OK(q.QueueRequest(std::move(req2)));
EXPECT_OK(q.QueueRequest(std::move(req3)));
EXPECT_OK(q.QueueRequest(std::move(req4)));
EXPECT_OK(q.QueueRequest(std::move(req5)));
EXPECT_OK(q.CancelAll());
EXPECT_EQ(ZX_ERR_CANCELED, cap.array[0]);
EXPECT_EQ(ZX_ERR_CANCELED, cap.array[1]);
EXPECT_EQ(ZX_ERR_CANCELED, cap.array[2]);
EXPECT_EQ(ZX_ERR_CANCELED, cap.array[3]);
EXPECT_EQ(ZX_ERR_CANCELED, cap.array[4]);
q.m_dispatch().VerifyAndClear();
q.transaction().m_cancel().VerifyAndClear();
}
} // namespace mt_usb_hci
int main(int argc, char* argv[]) { return RUN_ALL_TESTS(argc, argv); }