blob: 1399a337b06a5014b678de76d5bad9bb61028538 [file] [log] [blame]
// Copyright 2024 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 <lib/async/cpp/irq.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/driver/component/cpp/driver_base.h>
#include <lib/driver/testing/cpp/driver_runtime.h>
#include <lib/driver/testing/cpp/internal/driver_lifecycle.h>
#include <lib/driver/testing/cpp/internal/test_environment.h>
#include <lib/driver/testing/cpp/test_node.h>
#include <lib/fpromise/promise.h>
#include <lib/sync/cpp/completion.h>
#include <lib/zx/interrupt.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <gtest/gtest.h>
#include "src/graphics/display/lib/driver-framework-migration-utils/dispatcher/testing/dfv2-driver-with-dispatcher.h"
#include "src/lib/testing/predicates/status.h"
namespace display {
namespace {
// WARNING: Don't use this test as a template for new tests as it uses the old driver testing
// library.
// Tests dispatching asynchronous tasks and IRQ handler events on the
// `fdf::Dispatcher`-backed dispatcher.
//
// Note that this test doesn't test the functionality of setting the scheduler
// role for dispatcher threads. This is because `fdf::Dispatcher` always
// connects to the fucshia.scheduler.RoleManager protocol in the **component's**
// incoming service directory to set the scheduler role. The only way to test
// it is by creating a realm-manager-based integration test, which we haven't
// implemented yet.
class DriverDispatcherTest : public ::testing::Test {
public:
void SetUp() override {
// Create start args
node_server_.emplace("root");
zx::result start_args = node_server_->CreateStartArgsAndServe();
EXPECT_OK(start_args);
// Start the test environment
test_environment_.emplace();
test_environment_.SyncCall([server = std::move(start_args->incoming_directory_server)](
fdf_testing::internal::TestEnvironment* env) mutable {
zx::result result = env->Initialize(std::move(server));
EXPECT_OK(result);
});
// Start driver
zx::result start_result =
runtime_.RunToCompletion(driver_.Start(std::move(start_args->start_args)));
EXPECT_OK(start_result);
}
void TearDown() override {
StopDriver();
node_server_.reset();
}
// Stops the driver, shuts down its all dispatchers and returns true, if the
// driver is not yet stopped. Otherwise returns false.
//
// Must be called only from the main test thread.
bool StopDriver() {
if (driver_stopped_) {
return false;
}
zx::result prepare_stop_result = runtime_.RunToCompletion(driver_.PrepareStop());
EXPECT_OK(prepare_stop_result);
test_environment_.reset();
runtime_.ShutdownAllDispatchers(fdf::Dispatcher::GetCurrent()->get());
driver_stopped_ = true;
return true;
}
protected:
// Attaches a foreground dispatcher for us automatically.
fdf_testing::DriverRuntime runtime_;
async_patterns::TestDispatcherBound<fdf_testing::internal::TestEnvironment> test_environment_{
runtime_.StartBackgroundDispatcher()->async_dispatcher()};
// These will use the foreground dispatcher.
std::optional<fdf_testing::TestNode> node_server_;
fdf_testing::internal::DriverUnderTest<testing::Dfv2DriverWithDispatcher> driver_;
bool driver_stopped_ = false;
};
TEST_F(DriverDispatcherTest, DispatchAsyncTask) {
fpromise::bridge<uint32_t> bridge;
static constexpr uint32_t kValueToPass = 0xabcd1234;
zx::result<> post_task_result = driver_->PostTask(
[completer = std::move(bridge.completer)]() mutable { completer.complete_ok(kValueToPass); });
ASSERT_OK(post_task_result.status_value());
fpromise::promise<uint32_t> promise = std::move(bridge.consumer).promise();
fpromise::result<uint32_t> promise_result = runtime_.RunPromise(std::move(promise));
ASSERT_TRUE(promise_result.is_ok());
EXPECT_EQ(promise_result.value(), kValueToPass);
ASSERT_TRUE(StopDriver());
// After the driver stops, no task can be posted to the driver's async
// dispatcher.
zx::result<> post_task_after_driver_stop_result = driver_->PostTask(
[completer = std::move(bridge.completer)]() mutable { completer.complete_ok(0x1234abcd); });
EXPECT_NE(ZX_OK, post_task_after_driver_stop_result.status_value());
}
TEST_F(DriverDispatcherTest, HandleIrq) {
zx::interrupt virtual_interrupt;
zx_status_t status =
zx::interrupt::create(zx::resource{}, 0u, ZX_INTERRUPT_VIRTUAL, &virtual_interrupt);
ASSERT_OK(status);
zx::interrupt virtual_interrupt_driver_dup;
status = virtual_interrupt.duplicate(ZX_RIGHT_SAME_RIGHTS, &virtual_interrupt_driver_dup);
ASSERT_OK(status);
zx::time_boot latest_handled_irq_timestamp;
libsync::Completion irq_handler_invoked;
libsync::Completion irq_handler_canceled;
async::Irq::Handler handler = [&latest_handled_irq_timestamp, &irq_handler_invoked,
&irq_handler_canceled](async_dispatcher_t* dispatcher,
async::Irq* irq, zx_status_t status,
const zx_packet_interrupt_t* interrupt) {
ASSERT_TRUE(status == ZX_OK || status == ZX_ERR_CANCELED)
<< "Invalid async Irq wait status: " << zx_status_get_string(status);
if (status == ZX_ERR_CANCELED) {
irq_handler_canceled.Signal();
return;
}
latest_handled_irq_timestamp = zx::time_boot(interrupt->timestamp);
irq_handler_invoked.Signal();
// Acknowledges the interrupt so that it can be triggered again.
zx::unowned_interrupt(irq->object())->ack();
};
zx::result<> start_irq_handler_result =
driver_->StartIrqHandler(std::move(virtual_interrupt_driver_dup), std::move(handler));
ASSERT_OK(start_irq_handler_result.status_value());
// Manually trigger the virtual interrupt.
static constexpr zx::time_boot kIrqTimestamp1 = zx::time_boot(0x12345678);
status = virtual_interrupt.trigger(0u, kIrqTimestamp1);
ASSERT_OK(status);
// The interrupt handler is invoked when the interrupt is triggered.
irq_handler_invoked.Wait();
EXPECT_EQ(latest_handled_irq_timestamp, kIrqTimestamp1);
// Manually trigger the virtual interrupt again.
irq_handler_invoked.Reset();
static constexpr zx::time_boot kIrqTimestamp2 = zx::time_boot(0x23456789);
status = virtual_interrupt.trigger(0u, kIrqTimestamp2);
ASSERT_OK(status);
// The interrupt handler can be invoked again when the interrupt is triggered
// again.
irq_handler_invoked.Wait();
EXPECT_EQ(latest_handled_irq_timestamp, kIrqTimestamp2);
// Stop the driver and its dispatchers. The handler should receive a
// ZX_ERR_CANCELED signal.
ASSERT_TRUE(StopDriver());
irq_handler_canceled.Wait();
}
} // namespace
} // namespace display