blob: 9e7df4a431cb1577b9924b2422dbcab071f4bca3 [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 "../task.h"
#include <ddk/debug.h>
#include <fuchsia/sysmem/c/fidl.h>
#include <lib/fake-bti/bti.h>
#include <lib/syslog/global.h>
#include <mock-mmio-reg/mock-mmio-reg.h>
#include <stdint.h>
#include <unistd.h>
#include <zxtest/zxtest.h>
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>
#include "../gdc.h"
#include "src/camera/drivers/test_utils/fake-buffer-collection.h"
namespace gdc {
namespace {
constexpr uint32_t kWidth = 1080;
constexpr uint32_t kHeight = 764;
constexpr uint32_t kNumberOfBuffers = 4;
constexpr uint32_t kNumberOfMmios = 3;
constexpr uint32_t kConfigSize = 1000;
constexpr uint32_t kMaxTasks = 10;
// Integration test for the driver defined in zircon/system/dev/camera/arm-isp.
class TaskTest : public zxtest::Test {
public:
void ProcessFrameCallback(uint32_t output_buffer_id) {
callback_check_.push_back(output_buffer_id);
}
protected:
void SetUpBufferCollections(uint32_t buffer_collection_count) {
ASSERT_OK(fake_bti_create(bti_handle_.reset_and_get_address()));
zx_status_t status = camera::CreateContiguousBufferCollectionInfo(
&input_buffer_collection_, bti_handle_.get(), kWidth, kHeight,
buffer_collection_count);
ASSERT_OK(status);
status = camera::CreateContiguousBufferCollectionInfo(
&output_buffer_collection_, bti_handle_.get(), kWidth, kHeight,
buffer_collection_count);
ASSERT_OK(status);
status = zx_vmo_create_contiguous(bti_handle_.get(), kConfigSize, 0,
config_vmo_.reset_and_get_address());
ASSERT_OK(status);
}
void SetupForFrameProcessing() {
SetUpBufferCollections(kNumberOfBuffers);
ddk_mock::MockMmioReg fake_reg_array[kNumberOfBuffers];
ddk_mock::MockMmioRegRegion fake_regs(fake_reg_array, sizeof(uint32_t),
kNumberOfBuffers);
zx::port port;
ASSERT_OK(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port));
callback_.frame_ready = [](void* ctx, uint32_t idx) {
return static_cast<TaskTest*>(ctx)->ProcessFrameCallback(idx);
};
callback_.ctx = this;
ASSERT_OK(
zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq_));
ASSERT_OK(port.duplicate(ZX_RIGHT_SAME_RIGHTS, &port_));
gdc_device_ = std::make_unique<GdcDevice>(
nullptr, ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
ddk::MmioBuffer(fake_regs.GetMmioBuffer()), std::move(irq_),
std::move(bti_handle_), std::move(port));
// Start the thread.
EXPECT_OK(gdc_device_->StartThread());
}
void TearDown() override {
if (bti_handle_ != ZX_HANDLE_INVALID) {
fake_bti_destroy(bti_handle_.get());
}
}
zx::vmo config_vmo_;
zx::bti bti_handle_;
zx::port port_;
zx::interrupt irq_;
gdc_callback_t callback_;
buffer_collection_info_t input_buffer_collection_;
buffer_collection_info_t output_buffer_collection_;
std::unique_ptr<GdcDevice> gdc_device_;
std::vector<uint32_t> callback_check_;
};
TEST_F(TaskTest, BasicCreationTest) {
SetUpBufferCollections(kNumberOfBuffers);
std::unique_ptr<Task> task;
zx_status_t status =
gdc::Task::Create(&input_buffer_collection_, &output_buffer_collection_,
config_vmo_, &callback_, bti_handle_, &task);
EXPECT_OK(status);
}
TEST_F(TaskTest, InvalidFormatTest) {
SetUpBufferCollections(kNumberOfBuffers);
std::unique_ptr<Task> task;
input_buffer_collection_.format.image.pixel_format.type =
fuchsia_sysmem_PixelFormatType_YUY2;
EXPECT_EQ(
ZX_ERR_INVALID_ARGS,
gdc::Task::Create(&input_buffer_collection_, &output_buffer_collection_,
config_vmo_, &callback_, bti_handle_, &task));
}
TEST_F(TaskTest, InputBufferTest) {
SetUpBufferCollections(kNumberOfBuffers);
std::unique_ptr<Task> task;
zx_status_t status =
gdc::Task::Create(&input_buffer_collection_, &output_buffer_collection_,
config_vmo_, &callback_, bti_handle_, &task);
EXPECT_OK(status);
// Get the input buffers physical addresses
// Request for physical addresses of the buffers.
// Expecting to get error when requesting for invalid index.
zx_paddr_t addr;
for (uint32_t i = 0; i < kNumberOfBuffers; i++) {
EXPECT_EQ(ZX_OK, task->GetInputBufferPhysAddr(0, &addr));
}
EXPECT_EQ(ZX_ERR_INVALID_ARGS, task->GetInputBufferPhysAddr(4, &addr));
}
TEST_F(TaskTest, InvalidVmoTest) {
SetUpBufferCollections(0);
std::unique_ptr<Task> task;
zx_status_t status =
gdc::Task::Create(&input_buffer_collection_, &output_buffer_collection_,
config_vmo_, &callback_, bti_handle_, &task);
// Expecting Task setup to be returning an error when there are
// no VMOs in the buffer collection. At the moment VmoPool library
// doesn't return an error.
EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
}
TEST_F(TaskTest, InitTaskTest) {
SetUpBufferCollections(kNumberOfBuffers);
ddk_mock::MockMmioReg fake_reg_array[kNumberOfMmios];
ddk_mock::MockMmioRegRegion fake_regs(fake_reg_array, sizeof(uint32_t),
kNumberOfMmios);
ASSERT_OK(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_));
GdcDevice gdc_device(nullptr, ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
zx::interrupt(), std::move(bti_handle_),
std::move(port_));
std::vector<uint32_t> received_ids;
for (uint32_t i = 0; i < kMaxTasks; i++) {
zx::vmo config_vmo;
ASSERT_OK(config_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &config_vmo));
uint32_t task_id;
zx_status_t status = gdc_device.GdcInitTask(
&input_buffer_collection_, &output_buffer_collection_,
std::move(config_vmo), &callback_, &task_id);
EXPECT_OK(status);
// Checking to see if we are getting unique task ids.
auto entry = find(received_ids.begin(), received_ids.end(), task_id);
EXPECT_EQ(received_ids.end(), entry);
received_ids.push_back(task_id);
}
}
TEST_F(TaskTest, RemoveTaskTest) {
SetUpBufferCollections(kNumberOfBuffers);
ddk_mock::MockMmioReg fake_reg_array[kNumberOfMmios];
ddk_mock::MockMmioRegRegion fake_regs(fake_reg_array, sizeof(uint32_t),
kNumberOfMmios);
ASSERT_OK(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_));
gdc_device_ = std::make_unique<GdcDevice>(
nullptr, ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
ddk::MmioBuffer(fake_regs.GetMmioBuffer()), zx::interrupt(),
std::move(bti_handle_), std::move(port_));
uint32_t task_id;
zx_status_t status = gdc_device_->GdcInitTask(
&input_buffer_collection_, &output_buffer_collection_,
std::move(config_vmo_), &callback_, &task_id);
EXPECT_OK(status);
// Valid id.
ASSERT_NO_DEATH(([this, task_id]() { gdc_device_->GdcRemoveTask(task_id); }));
// Invalid id.
ASSERT_DEATH(
([this, task_id]() { gdc_device_->GdcRemoveTask(task_id + 1); }));
}
TEST_F(TaskTest, ProcessInvalidFrameTest) {
SetupForFrameProcessing();
// Invalid task id.
zx_status_t status = gdc_device_->GdcProcessFrame(0xFF, 0);
EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
ASSERT_OK(gdc_device_->StopThread());
}
TEST_F(TaskTest, InvalidBufferProcessFrameTest) {
SetupForFrameProcessing();
uint32_t task_id;
zx_status_t status = gdc_device_->GdcInitTask(
&input_buffer_collection_, &output_buffer_collection_,
std::move(config_vmo_), &callback_, &task_id);
EXPECT_OK(status);
// Invalid buffer id.
status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers);
EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
ASSERT_OK(gdc_device_->StopThread());
}
TEST_F(TaskTest, ProcessFrameTest) {
SetupForFrameProcessing();
uint32_t task_id;
zx_status_t status = gdc_device_->GdcInitTask(
&input_buffer_collection_, &output_buffer_collection_,
std::move(config_vmo_), &callback_, &task_id);
EXPECT_OK(status);
// Valid buffer & task id.
status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 1);
EXPECT_OK(status);
// Trigger the interrupt manually.
zx_port_packet packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
EXPECT_OK(port_.queue(&packet));
// Check if the callback was called.
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
EXPECT_EQ(1, callback_check_.size());
ASSERT_OK(gdc_device_->StopThread());
}
TEST_F(TaskTest, MultipleProcessFrameTest) {
SetupForFrameProcessing();
uint32_t task_id;
zx_status_t status = gdc_device_->GdcInitTask(
&input_buffer_collection_, &output_buffer_collection_,
std::move(config_vmo_), &callback_, &task_id);
// Process few frames, putting them in a queue
status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 1);
EXPECT_OK(status);
status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 2);
EXPECT_OK(status);
status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 3);
EXPECT_OK(status);
// Trigger the interrupt manually.
zx_port_packet packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
EXPECT_OK(port_.queue(&packet));
// Check if the callback was called once.
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
EXPECT_EQ(1, callback_check_.size());
// Trigger the interrupt manually.
packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
EXPECT_OK(port_.queue(&packet));
// Check if the callback was called one more time.
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
EXPECT_EQ(2, callback_check_.size());
// This time adding another frame to process while its
// waiting for an interrupt.
status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 3);
EXPECT_OK(status);
// Trigger the interrupt manually.
packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
EXPECT_OK(port_.queue(&packet));
// Check if the callback was called one more time.
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
EXPECT_EQ(3, callback_check_.size());
// Trigger the interrupt manually.
packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
EXPECT_OK(port_.queue(&packet));
// Check if the callback was called one more time.
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
EXPECT_EQ(4, callback_check_.size());
ASSERT_OK(gdc_device_->StopThread());
}
TEST(TaskTest, NonContigVmoTest) {
zx_handle_t bti_handle = ZX_HANDLE_INVALID;
gdc_callback_t callback;
zx_handle_t config_vmo;
buffer_collection_info_t input_buffer_collection;
buffer_collection_info_t output_buffer_collection;
ASSERT_OK(fake_bti_create(&bti_handle));
zx_status_t status = camera::CreateContiguousBufferCollectionInfo(
&input_buffer_collection, bti_handle, kWidth, kHeight, 0);
ASSERT_OK(status);
status = camera::CreateContiguousBufferCollectionInfo(
&output_buffer_collection, bti_handle, kWidth, kHeight, 0);
ASSERT_OK(status);
status = zx_vmo_create(kConfigSize, 0, &config_vmo);
ASSERT_OK(status);
std::unique_ptr<Task> task;
status = gdc::Task::Create(&input_buffer_collection,
&output_buffer_collection, zx::vmo(config_vmo),
&callback, zx::bti(bti_handle), &task);
// Expecting Task setup to be returning an error when config vmo is not
// contig.
EXPECT_NE(ZX_OK, status);
}
TEST(TaskTest, InvalidBufferCollectionTest) {
zx_handle_t bti_handle = ZX_HANDLE_INVALID;
gdc_callback_t callback;
zx_handle_t config_vmo;
ASSERT_OK(fake_bti_create(&bti_handle));
zx_status_t status =
zx_vmo_create_contiguous(bti_handle, kConfigSize, 0, &config_vmo);
ASSERT_OK(status);
std::unique_ptr<Task> task;
status = gdc::Task::Create(nullptr, nullptr, zx::vmo(config_vmo), &callback,
zx::bti(bti_handle), &task);
EXPECT_NE(ZX_OK, status);
}
} // namespace
} // namespace gdc