blob: 5f48d3adac39a247b7c47ae2321fd71bd54cc7c6 [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/gdc_node.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 <safemath/safe_conversions.h>
#include "src/camera/drivers/controller/graph_utils.h"
#include "src/camera/drivers/controller/stream_pipeline_info.h"
#include "src/camera/lib/format_conversion/buffer_collection_helper.h"
#include "src/devices/lib/sysmem/sysmem.h"
namespace camera {
constexpr auto kTag = "camera_controller_gdc_node";
fpromise::result<gdc_config_info, zx_status_t> LoadGdcConfiguration(
zx_device_t* device, ProductConfig& product_config, const camera::GdcConfig& config_type) {
if (config_type == GdcConfig::INVALID) {
FX_LOGST(DEBUG, kTag) << "Invalid GDC configuration type";
return fpromise::error(ZX_ERR_INVALID_ARGS);
}
gdc_config_info info;
size_t size;
auto status =
load_firmware(device, product_config.GetGdcConfigFile(config_type), &info.config_vmo, &size);
if (status != ZX_OK || size == 0) {
FX_PLOGST(ERROR, kTag, status) << "Failed to load the GDC firmware";
return fpromise::error(status);
}
info.size = safemath::checked_cast<uint32_t>(size);
return fpromise::ok(info);
}
void OnGdcFrameAvailable(void* ctx, const frame_available_info_t* info) {
auto nonce = TRACE_NONCE();
TRACE_DURATION("camera", "OnGdcFrameAvailable");
TRACE_FLOW_BEGIN("camera", "post_gdc_frame_available", nonce);
// This method is invoked by the GDC in its own thread,
// so the event must be marshalled to the
// controller's thread.
auto* gdc_node = static_cast<GdcNode*>(ctx);
gdc_node->RunOnMainThread([gdc_node, nonce, info = *info]() {
TRACE_DURATION("camera", "OnGdcFrameAvailable.task");
TRACE_FLOW_END("camera", "post_gdc_frame_available", nonce);
gdc_node->OnFrameAvailable(&info);
});
}
void OnGdcResChange(void* ctx, const frame_available_info_t* info) {
static_cast<camera::ProcessNode*>(ctx)->OnResolutionChanged(info);
}
fpromise::result<ProcessNode*, zx_status_t> GdcNode::CreateGdcNode(
const ControllerMemoryAllocator& memory_allocator, async_dispatcher_t* dispatcher,
zx_device_t* device, const ddk::GdcProtocolClient& gdc, StreamCreationData* info,
ProcessNode* parent_node, const InternalConfigNode& internal_gdc_node) {
auto& input_buffers_hlcpp = parent_node->output_buffer_collection_info();
auto result = GetBuffers(memory_allocator, internal_gdc_node, info, kTag);
if (result.is_error()) {
FX_LOGST(ERROR, kTag) << "Failed to get buffers";
return fpromise::error(result.error());
}
auto output_buffers_hlcpp = std::move(result.value());
BufferCollectionHelper output_buffer_collection_helper(output_buffers_hlcpp.buffers);
BufferCollectionHelper input_buffer_collection_helper(input_buffers_hlcpp);
// Convert the formats to C type
std::vector<image_format_2_t> output_image_formats_c;
for (const auto& format : internal_gdc_node.image_formats) {
image_format_2_t value;
auto original = GetImageFormatFromBufferCollection(*output_buffer_collection_helper.GetC(),
format.coded_width, format.coded_height);
sysmem::image_format_2_banjo_from_fidl(original, value);
output_image_formats_c.push_back(value);
}
// GDC only supports one input format and multiple output format at the
// moment. So we take the first format from the previous node.
// All existing usecases we support have only 1 format going into GDC.
auto input_image_formats_c = GetImageFormatFromBufferCollection(
*input_buffer_collection_helper.GetC(), parent_node->output_image_formats()[0].coded_width,
parent_node->output_image_formats()[0].coded_height);
// Image format index refers to the final output format list of the pipeline. If this GDC node
// only has one output format, then the image format index must not be meant for this node. If
// this assumption is false the GdcTask will fail to init. Without this filter then streams which
// have a multiple output resolution node, but only a single output resolution GDC node will fail
// to init.
//
// Note: this is highly specific to sherlock use case. It would be good to revisit how nodes
// receive their output format index when this assumption doesn't hold on another platform.
uint32_t output_format_index =
internal_gdc_node.image_formats.size() > 1 ? info->image_format_index : 0;
// Get the GDC configurations loaded
auto product_config = ProductConfig::Create();
std::vector<gdc_config_info> config_vmos_info;
for (const auto& config : internal_gdc_node.gdc_info.config_type) {
auto gdc_config = LoadGdcConfiguration(device, *product_config, config);
if (gdc_config.is_error()) {
FX_LOGST(ERROR, kTag) << "Failed to load GDC configuration";
return fpromise::error(gdc_config.error());
}
config_vmos_info.push_back(gdc_config.value());
}
auto cleanup = fit::defer([config_vmos_info]() {
for (auto info : config_vmos_info) {
ZX_ASSERT_MSG(ZX_OK == zx_handle_close(info.config_vmo), "Failed to free up Config VMOs");
}
});
// Create GDC Node
auto gdc_node = std::make_unique<camera::GdcNode>(dispatcher, gdc, parent_node, internal_gdc_node,
std::move(output_buffers_hlcpp),
info->stream_type(), output_format_index);
if (!gdc_node) {
FX_LOGST(ERROR, kTag) << "Failed to create GDC node";
return fpromise::error(ZX_ERR_NO_MEMORY);
}
// Initialize the GDC to get a unique task index
uint32_t gdc_task_index;
buffer_collection_info_2 temp_input_collection, temp_output_collection;
image_format_2_t temp_image_format;
sysmem::buffer_collection_info_2_banjo_from_fidl(*input_buffer_collection_helper.GetC(),
temp_input_collection);
sysmem::buffer_collection_info_2_banjo_from_fidl(*output_buffer_collection_helper.GetC(),
temp_output_collection);
sysmem::image_format_2_banjo_from_fidl(input_image_formats_c, temp_image_format);
auto status = gdc.InitTask(&temp_input_collection, &temp_output_collection, &temp_image_format,
output_image_formats_c.data(), output_image_formats_c.size(),
output_format_index, config_vmos_info.data(), config_vmos_info.size(),
gdc_node->frame_callback(), gdc_node->res_callback(),
gdc_node->remove_task_callback(), &gdc_task_index);
if (status != ZX_OK) {
FX_PLOGST(ERROR, kTag, status) << "Failed to initialize GDC";
return fpromise::error(status);
}
gdc_node->set_task_index(gdc_task_index);
// Add child node.
auto return_value = fpromise::ok(gdc_node.get());
parent_node->AddChildNodeInfo(std::move(gdc_node));
return return_value;
}
void GdcNode::OnFrameAvailable(const frame_available_info_t* info) {
ZX_ASSERT(thread_checker_.is_thread_valid());
TRACE_DURATION("camera", "GdcNode::OnFrameAvailable", "buffer_index", info->buffer_id);
if (shutdown_requested_ || info->frame_status != FRAME_STATUS_OK) {
return;
}
UpdateFrameCounterForAllChildren();
if (NeedToDropFrame()) {
parent_node_->OnReleaseFrame(info->metadata.input_buffer_index);
gdc_.ReleaseFrame(task_index_, info->buffer_id);
return;
}
// Free up parent's frame.
parent_node_->OnReleaseFrame(info->metadata.input_buffer_index);
ProcessNode::OnFrameAvailable(info);
}
void GdcNode::OnReleaseFrame(uint32_t buffer_index) {
TRACE_DURATION("camera", "GdcNode::OnReleaseFrame", "buffer_index", buffer_index);
std::lock_guard al(in_use_buffer_lock_);
ZX_ASSERT(buffer_index < in_use_buffer_count_.size());
in_use_buffer_count_[buffer_index]--;
if (in_use_buffer_count_[buffer_index] != 0) {
return;
}
if (!shutdown_requested_) {
gdc_.ReleaseFrame(task_index_, buffer_index);
}
}
void GdcNode::OnReadyToProcess(const frame_available_info_t* info) {
auto nonce = TRACE_NONCE();
TRACE_DURATION("camera", "GdcNode::OnReadyToProcess");
TRACE_FLOW_BEGIN("camera", "post_process_frame", nonce);
async::PostTask(dispatcher_, [this, nonce, buffer_index = info->buffer_id,
capture_timestamp = info->metadata.capture_timestamp]() {
TRACE_DURATION("camera", "GdcNode::OnReadyToProcess.task", "buffer_index", buffer_index);
TRACE_FLOW_END("camera", "post_process_frame", nonce);
if (enabled_) {
ZX_ASSERT(ZX_OK == gdc_.ProcessFrame(task_index_, buffer_index, capture_timestamp));
} else {
// Since streaming is disabled the incoming frame is released
// so it gets added back to the pool.
parent_node_->OnReleaseFrame(buffer_index);
}
});
}
void GdcNode::OnTaskRemoved(zx_status_t status) {
ZX_ASSERT(status == ZX_OK);
async::PostTask(dispatcher_, [this]() {
node_callback_received_ = true;
OnCallbackReceived();
});
}
void GdcNode::OnShutdown(fit::function<void(void)> shutdown_callback) {
shutdown_callback_ = std::move(shutdown_callback);
// After a shutdown request has been made,
// no other calls should be made to the GDC driver.
shutdown_requested_ = true;
// Request GDC to shutdown.
gdc_.RemoveTask(task_index_);
auto child_shutdown_completion_callback = [this]() {
child_node_callback_received_ = true;
OnCallbackReceived();
};
ZX_ASSERT_MSG(configured_streams().size() == 1,
"Cannot shutdown a stream which supports multiple streams");
// Forward the shutdown request to child node.
child_nodes().at(0)->OnShutdown(child_shutdown_completion_callback);
}
void GdcNode::OnResolutionChangeRequest(uint32_t output_format_index) {
if (enabled_) {
TRACE_DURATION("camera", "GdcNode::OnResolutionChangeRequest", "index", output_format_index);
gdc_.SetOutputResolution(task_index_, output_format_index);
set_current_image_format_index(output_format_index);
}
}
} // namespace camera