| // 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 |