blob: 29ea184690d52b8337aa6e267d8be737c93f3fae [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 "src/camera/drivers/controller/pipeline_manager.h"
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include "src/camera/drivers/controller/gdc_node.h"
#include "src/camera/drivers/controller/ge2d_node.h"
#include "src/camera/drivers/controller/graph_utils.h"
#include "src/camera/drivers/controller/input_node.h"
namespace camera {
constexpr auto kTag = "camera_controller";
fit::result<OutputNode*, zx_status_t> PipelineManager::CreateGraph(
StreamCreationData* info, const InternalConfigNode& internal_node, ProcessNode* parent_node) {
fit::result<OutputNode*, zx_status_t> result;
const auto* next_node_internal = GetNextNodeInPipeline(info->stream_type(), internal_node);
if (!next_node_internal) {
FX_LOGST(ERROR, kTag) << "Failed to get next node";
return fit::error(ZX_ERR_INTERNAL);
}
switch (next_node_internal->type) {
// Input Node
case NodeType::kInputStream: {
FX_LOGST(ERROR, kTag) << "Child node cannot be input node";
return fit::error(ZX_ERR_INVALID_ARGS);
}
// GDC
case NodeType::kGdc: {
auto gdc_result = camera::GdcNode::CreateGdcNode(
memory_allocator_, dispatcher_, device_, gdc_, info, parent_node, *next_node_internal);
if (gdc_result.is_error()) {
FX_PLOGST(ERROR, kTag, gdc_result.error()) << "Failed to configure GDC Node";
// TODO(braval): Handle already configured nodes
return fit::error(gdc_result.error());
}
return CreateGraph(info, *next_node_internal, gdc_result.value());
}
// GE2D
case NodeType::kGe2d: {
auto ge2d_result = camera::Ge2dNode::CreateGe2dNode(
memory_allocator_, dispatcher_, device_, ge2d_, info, parent_node, *next_node_internal);
if (ge2d_result.is_error()) {
FX_PLOGST(ERROR, kTag, ge2d_result.error()) << "Failed to configure GE2D Node";
// TODO(braval): Handle already configured nodes
return fit::error(ge2d_result.error());
}
return CreateGraph(info, *next_node_internal, ge2d_result.value());
}
// Output Node
case NodeType::kOutputStream: {
result =
camera::OutputNode::CreateOutputNode(dispatcher_, info, parent_node, *next_node_internal);
if (result.is_error()) {
FX_LOGST(ERROR, kTag) << "Failed to configure Output Node";
// TODO(braval): Handle already configured nodes
return result;
}
output_nodes_info_[info->stream_type()] = result.value();
break;
}
// clang-format off
default: {
return fit::error(ZX_ERR_NOT_SUPPORTED);
}
// clang-format on
}
return result;
}
fit::result<std::pair<InternalConfigNode, ProcessNode*>, zx_status_t>
PipelineManager::FindNodeToAttachNewStream(StreamCreationData* info,
const InternalConfigNode& current_internal_node,
ProcessNode* node) {
auto requested_stream_type = info->stream_type();
// Validate if this node supports the requested stream type
// to be safe.
if (!node->is_stream_supported(requested_stream_type)) {
return fit::error(ZX_ERR_INVALID_ARGS);
}
// Traverse the |node| to find a node which supports this stream
// but none of its children support this stream.
for (auto& child_node : node->child_nodes()) {
if (child_node->is_stream_supported(requested_stream_type)) {
// If we find a child node which supports the requested stream type,
// we move on to that child node.
const auto* next_internal_node =
GetNextNodeInPipeline(info->stream_type(), current_internal_node);
if (!next_internal_node) {
FX_LOGS(ERROR) << "Failed to get next node for requested stream";
return fit::error(ZX_ERR_INTERNAL);
}
return FindNodeToAttachNewStream(info, *next_internal_node, child_node.get());
}
// This is the node we need to attach the new stream pipeline to
return fit::ok(std::make_pair(current_internal_node, node));
}
// Should not reach here
FX_LOGS(ERROR) << "Failed FindNodeToAttachNewStream";
return fit::error(ZX_ERR_INTERNAL);
}
void PipelineManager::ConfigureStreamPipeline(
StreamCreationData info, fidl::InterfaceRequest<fuchsia::camera2::Stream> stream) {
auto nonce = TRACE_NONCE();
TRACE_DURATION("camera", "PipelineManager::ConfigureStreamPipeline");
TRACE_FLOW_BEGIN("camera", "post_configure_stream_pipeline_task", nonce);
PostTask([this, nonce, info = std::move(info), stream = std::move(stream)]() mutable {
TRACE_DURATION("camera", "PipelineManager::ConfigureStreamPipeline.task");
TRACE_FLOW_END("camera", "post_configure_stream_pipeline_task", nonce);
zx_status_t status = ZX_OK;
auto cleanup = fit::defer([this, &stream, &status]() {
TaskComplete();
if (status != ZX_OK) {
stream.Close(status);
}
});
ProcessNode* graph_node_to_be_appended = nullptr;
camera::InternalConfigNode internal_graph_node_to_be_appended;
std::unique_ptr<camera::ProcessNode> graph_head;
auto shutdown_graph_on_error = fit::defer([&] {
if (graph_head) {
graph_head->OnShutdown([] {});
}
});
auto* input_node = FindStream(info.node.input_stream_type);
if (input_node) {
// If the same stream is requested again, return an error..
if (HasStreamType(input_node->configured_streams(),
info.stream_config.properties.stream_type())) {
status = ZX_ERR_ALREADY_BOUND;
return;
}
// Find the node at which the new graph needs to be appended.
auto result = FindNodeToAttachNewStream(&info, info.node, input_node);
if (result.is_error()) {
FX_PLOGS(ERROR, result.error()) << "Failed FindNodeToAttachNewStream";
status = result.error();
return;
}
// If the next node is an output node, it is currently not supported.
// Currently the expectation is that the clients will request streams in a fixed order.
// TODO(fxbug.dev/42241): Remove this check when 42241 is fixed.
const auto* next_node_internal =
GetNextNodeInPipeline(info.stream_type(), result.value().first);
if (!next_node_internal) {
FX_LOGS(ERROR) << "Failed to get next node";
status = ZX_ERR_INTERNAL;
return;
}
if (next_node_internal->type == NodeType::kOutputStream) {
FX_LOGS(WARNING)
<< "Cannot create this stream due to unexpected ordering of stream create requests";
status = ZX_ERR_NOT_SUPPORTED;
return;
}
graph_node_to_be_appended = result.value().second;
internal_graph_node_to_be_appended = result.value().first;
} else {
auto input_result =
camera::InputNode::CreateInputNode(&info, memory_allocator_, dispatcher_, isp_);
if (input_result.is_error()) {
FX_PLOGST(ERROR, kTag, input_result.error()) << "Failed to ConfigureInputNode";
status = input_result.error();
return;
}
graph_head = std::move(input_result.value());
graph_node_to_be_appended = graph_head.get();
internal_graph_node_to_be_appended = info.node;
}
auto output_node_result =
CreateGraph(&info, internal_graph_node_to_be_appended, graph_node_to_be_appended);
if (output_node_result.is_error()) {
FX_PLOGST(ERROR, kTag, output_node_result.error()) << "Failed to CreateGraph";
status = output_node_result.error();
return;
}
auto* output_node = output_node_result.value();
auto stream_configured = info.stream_type();
status = output_node->Attach(stream.TakeChannel(), [this, stream_configured]() {
FX_LOGS(DEBUG) << "Stream client disconnected";
OnClientStreamDisconnect(stream_configured);
});
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to bind output stream";
return;
}
if (input_node) {
// Push this new requested stream to all pre-existing nodes |configured_streams| vector.
auto requested_stream_type = info.stream_type();
auto* current_node = graph_node_to_be_appended;
while (current_node) {
current_node->configured_streams().push_back(requested_stream_type);
current_node = current_node->parent_node();
}
} else {
status =
isp_.SetFrameRateRange(reinterpret_cast<const frame_rate_t*>(&info.frame_rate_range.min),
reinterpret_cast<const frame_rate_t*>(&info.frame_rate_range.max));
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "Failed to SetFrameRateRange";
return;
}
shutdown_graph_on_error.cancel();
streams_[info.node.input_stream_type] = std::move(graph_head);
}
});
}
static void RemoveStreamType(std::vector<fuchsia::camera2::CameraStreamType>& streams,
fuchsia::camera2::CameraStreamType stream_to_remove) {
auto it = std::find(streams.begin(), streams.end(), stream_to_remove);
if (it != streams.end()) {
streams.erase(it);
}
}
void PipelineManager::TaskComplete() {
task_in_progress_ = false;
tasks_event_.signal(0u, kTaskQueued);
}
void PipelineManager::DeleteGraphForDisconnectedStream(
ProcessNode* graph_head, fuchsia::camera2::CameraStreamType stream_to_disconnect) {
TRACE_DURATION("camera", "PipelineManager::DeleteGraphForDisconnectedStream", "stream_type",
static_cast<uint32_t>(stream_to_disconnect));
// More than one stream supported by this graph.
// Check for this nodes children to see if we can find the |stream_to_disconnect|
// as part of configured_streams.
auto& child_nodes = graph_head->child_nodes();
for (auto it = child_nodes.begin(); it != child_nodes.end(); it++) {
if (HasStreamType(it->get()->configured_streams(), stream_to_disconnect)) {
if (it->get()->configured_streams().size() == 1) {
it = child_nodes.erase(it);
auto* current_node = graph_head;
while (current_node) {
// Remove entry from configured streams.
RemoveStreamType(current_node->configured_streams(), stream_to_disconnect);
current_node = current_node->parent_node();
}
TaskComplete();
return;
}
return DeleteGraphForDisconnectedStream(it->get(), stream_to_disconnect);
}
}
}
void PipelineManager::DisconnectStream(ProcessNode* graph_head,
fuchsia::camera2::CameraStreamType input_stream_type,
fuchsia::camera2::CameraStreamType stream_to_disconnect) {
auto shutdown_callback = [this, input_stream_type, stream_to_disconnect]() {
TRACE_DURATION("camera", "PipelineManager::DisconnectStream Callback", "stream_type",
static_cast<uint32_t>(stream_to_disconnect));
ProcessNode* graph_head = nullptr;
// Remove entry from shutdown book keeping.
output_nodes_info_.erase(stream_to_disconnect);
stream_shutdown_requested_.erase(
std::remove(stream_shutdown_requested_.begin(), stream_shutdown_requested_.end(),
stream_to_disconnect),
stream_shutdown_requested_.end());
// Check if global shutdown was requested and it was complete before exiting
// this function.
auto shutdown_cleanup = fit::defer([this]() {
if (output_nodes_info_.empty() && global_shutdown_requested_) {
// Signal for pipeline manager shutdown complete.
shutdown_event_.signal(0, kPipelineManagerSignalExitDone);
}
});
auto* input_node = FindStream(input_stream_type);
if (input_node == nullptr) {
ZX_ASSERT_MSG(false, "Invalid input stream type\n");
return;
}
if (input_node->configured_streams().size() == 1) {
streams_.erase(input_stream_type);
TaskComplete();
return;
}
graph_head = input_node;
DeleteGraphForDisconnectedStream(graph_head, stream_to_disconnect);
};
// Only one stream supported by the graph.
if (graph_head->configured_streams().size() == 1 &&
HasStreamType(graph_head->configured_streams(), stream_to_disconnect)) {
graph_head->OnShutdown(shutdown_callback);
return;
}
// More than one stream supported by this graph.
// Check for this nodes children to see if we can find the |stream_to_disconnect|
// as part of configured_streams.
auto& child_nodes = graph_head->child_nodes();
for (auto& child_node : child_nodes) {
if (HasStreamType(child_node->configured_streams(), stream_to_disconnect)) {
return DisconnectStream(child_node.get(), input_stream_type, stream_to_disconnect);
}
}
}
fit::result<std::pair<ProcessNode*, fuchsia::camera2::CameraStreamType>, zx_status_t>
PipelineManager::FindGraphHead(fuchsia::camera2::CameraStreamType stream_type) {
for (auto& stream : streams_) {
if (HasStreamType(stream.second->configured_streams(), stream_type)) {
return fit::ok(std::make_pair(stream.second.get(), stream.first));
}
}
return fit::error(ZX_ERR_BAD_STATE);
}
void PipelineManager::OnClientStreamDisconnect(
fuchsia::camera2::CameraStreamType stream_to_disconnect) {
PostTask([this, stream_to_disconnect]() {
TRACE_DURATION("camera", "PipelineManager::OnClientStreamDisconnect", "stream_type",
static_cast<uint32_t>(stream_to_disconnect));
stream_shutdown_requested_.push_back(stream_to_disconnect);
auto result = FindGraphHead(stream_to_disconnect);
if (result.is_error()) {
FX_PLOGS(ERROR, result.error()) << "Failed to FindGraphHead";
ZX_ASSERT_MSG(false, "Invalid stream_to_disconnect stream type\n");
}
DisconnectStream(result.value().first, result.value().second, stream_to_disconnect);
});
}
void PipelineManager::StopStreaming() {
for (auto output_node_info : output_nodes_info_) {
if (output_node_info.second) {
output_node_info.second->client_stream()->Stop();
}
}
}
void PipelineManager::StartStreaming() {
for (auto output_node_info : output_nodes_info_) {
if (output_node_info.second) {
output_node_info.second->client_stream()->Start();
}
}
}
void PipelineManager::Shutdown() {
// No existing streams, safe to signal shutdown complete.
if (output_nodes_info_.empty()) {
// Signal for pipeline manager shutdown complete.
shutdown_event_.signal(0u, kPipelineManagerSignalExitDone);
return;
}
// First stop streaming all active streams.
StopStreaming();
// Instantiate the shutdown of all active streams before transitioning the state to
// "global_shutdown_requested".
auto output_node_info_copy = output_nodes_info_;
for (auto output_node_info : output_node_info_copy) {
if (!HasStreamType(stream_shutdown_requested_, output_node_info.first)) {
OnClientStreamDisconnect(output_node_info.first);
}
}
// Transition the state to ensure that no new tasks are posted on the task queue.
global_shutdown_requested_ = true;
}
void PipelineManager::SetupTaskWaiter() {
ZX_ASSERT_MSG(ZX_OK == zx::event::create(0, &tasks_event_), "Failed to create a task event");
tasks_event_waiter_.set_handler([this](async_dispatcher_t* dispatcher, async::Wait* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
TRACE_DURATION("camera", "PipelineManager::TaskWaiter");
// Clear the signal.
tasks_event_.signal(kTaskQueued, 0u);
if (!task_in_progress_ && !task_queue_.empty()) {
task_in_progress_ = true;
auto task = std::move(task_queue_.front());
task_queue_.pop();
async::PostTask(dispatcher_, std::move(task));
}
tasks_event_waiter_.Begin(dispatcher_);
});
tasks_event_waiter_.set_object(tasks_event_.get());
tasks_event_waiter_.set_trigger(kTaskQueued);
tasks_event_waiter_.Begin(dispatcher_);
}
void PipelineManager::PostTask(fit::closure task) {
if (global_shutdown_requested_) {
FX_LOGS(DEBUG) << "Global shutdown requested, ignoring the task posted on the task queue";
return;
}
task_queue_.emplace(std::move(task));
tasks_event_.signal(0u, kTaskQueued);
}
} // namespace camera