blob: 9e974f59a468314b424423eba4bd79c6082af73d [file] [log] [blame]
// Copyright 2020 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/bin/device/device_impl.h"
#include <lib/async/cpp/task.h>
#include <lib/fit/bridge.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <sstream>
#include "src/camera/bin/device/messages.h"
#include "src/camera/bin/device/util.h"
#include "src/lib/fsl/handles/object_info.h"
namespace camera {
namespace {
using ConfigPtr = std::unique_ptr<fuchsia::camera2::hal::Config>;
fit::promise<fuchsia::camera2::DeviceInfo> FetchDeviceInfo(
const fuchsia::camera2::hal::ControllerPtr& controller) {
fit::bridge<fuchsia::camera2::DeviceInfo> bridge;
controller->GetDeviceInfo([completer = std::move(bridge.completer)](auto device_info) mutable {
completer.complete_ok(std::move(device_info));
});
return bridge.consumer.promise();
}
fit::promise<std::vector<ConfigPtr>, zx_status_t> FetchConfigs(
const fuchsia::camera2::hal::ControllerPtr& controller,
std::vector<ConfigPtr> configs = std::vector<ConfigPtr>()) {
fit::bridge<ConfigPtr, zx_status_t> bridge;
controller->GetNextConfig(
[completer = std::move(bridge.completer)](auto config, auto status) mutable {
if (status == ZX_OK) {
completer.complete_ok(std::move(config));
} else {
completer.complete_error(status);
}
});
return bridge.consumer.promise().then([&controller, configs = std::move(configs)](
fit::result<ConfigPtr, zx_status_t>& result) mutable
-> fit::promise<std::vector<ConfigPtr>, zx_status_t> {
if (result.is_ok()) {
// If we received a config, we need to call FetchConfigs again to get the next config.
configs.push_back(result.take_value());
return FetchConfigs(controller, std::move(configs));
} else if (result.error() == ZX_ERR_STOP) {
// This means we've already fetched all configs successfully.
return fit::make_result_promise<std::vector<ConfigPtr>, zx_status_t>(
fit::ok(std::move(configs)));
} else {
// Unexpected zx_status_t values will be a failure.
return fit::make_result_promise<std::vector<ConfigPtr>, zx_status_t>(
fit::error(result.take_error()));
}
});
}
} // namespace
DeviceImpl::DeviceImpl(async_dispatcher_t* dispatcher, fit::executor& executor,
MetricsReporter metrics, fuchsia::sysmem::AllocatorHandle allocator,
zx::event bad_state_event)
: dispatcher_(dispatcher),
executor_(executor),
metrics_(std::move(metrics)),
sysmem_allocator_(std::move(allocator)),
bad_state_event_(std::move(bad_state_event)),
button_listener_binding_(this) {}
DeviceImpl::~DeviceImpl() = default;
fit::promise<std::unique_ptr<DeviceImpl>, zx_status_t> DeviceImpl::Create(
async_dispatcher_t* dispatcher, fit::executor& executor, MetricsReporter metrics,
fuchsia::camera2::hal::ControllerHandle controller, fuchsia::sysmem::AllocatorHandle allocator,
fuchsia::ui::policy::DeviceListenerRegistryHandle registry, zx::event bad_state_event) {
auto device = std::make_unique<DeviceImpl>(dispatcher, executor, std::move(metrics),
std::move(allocator), std::move(bad_state_event));
ZX_ASSERT(device->controller_.Bind(std::move(controller)) == ZX_OK);
// Bind the controller interface and get some initial startup information.
device->controller_.set_error_handler([device = device.get()](zx_status_t status) {
FX_PLOGS(ERROR, status) << "Controller server disconnected during initialization.";
ZX_ASSERT(device->bad_state_event_.signal(0, ZX_EVENT_SIGNALED) == ZX_OK);
});
using DeviceInfoResult = fit::result<>;
auto device_info_promise =
FetchDeviceInfo(device->controller_)
.and_then([device = device.get()](fuchsia::camera2::DeviceInfo& device_info) mutable {
device->device_info_ = std::move(device_info);
});
using FetchConfigsResult = fit::result<void, zx_status_t>;
auto configs_promise =
FetchConfigs(device->controller_)
.then([device = device.get()](fit::result<std::vector<ConfigPtr>, zx_status_t>& result)
-> FetchConfigsResult {
if (result.is_error()) {
return fit::error(result.error());
}
for (uint32_t config_index = 0; config_index < result.value().size(); ++config_index) {
const auto& config = result.value()[config_index];
auto result = Convert(*config);
if (result.is_error()) {
FX_PLOGS(ERROR, result.error()) << "Failed to convert config";
return fit::error(result.error());
}
auto num_streams = result.value().streams().size();
auto config_metrics = device->metrics_.CreateConfiguration(config_index, num_streams);
for (uint32_t stream_index = 0; stream_index < result.value().streams().size();
++stream_index) {
config_metrics->stream(stream_index)
.SetProperties(result.value().streams()[stream_index]);
}
device->configuration_metrics_.push_back(std::move(config_metrics));
device->configurations_.push_back(result.take_value());
device->configs_.push_back(std::move(*config));
}
device->SetConfiguration(0);
return fit::ok();
});
// Wait for all expected callbacks to occur.
return fit::join_promises(std::move(device_info_promise), std::move(configs_promise))
.then([device = std::move(device), registry = std::move(registry)](
fit::result<std::tuple<DeviceInfoResult, FetchConfigsResult>>& results) mutable
-> fit::result<std::unique_ptr<DeviceImpl>, zx_status_t> {
FX_CHECK(results.is_ok());
if (std::get<1>(results.value()).is_error()) {
return fit::error(std::get<1>(results.value()).error());
}
// Bind the registry interface and register the device as a listener.
ZX_ASSERT(device->registry_.Bind(std::move(registry)) == ZX_OK);
device->registry_->RegisterMediaButtonsListener(
device->button_listener_binding_.NewBinding());
// Rebind the controller error handler.
device->controller_.set_error_handler(
fit::bind_member(device.get(), &DeviceImpl::OnControllerDisconnected));
return fit::ok(std::move(device));
});
}
fidl::InterfaceRequestHandler<fuchsia::camera3::Device> DeviceImpl::GetHandler() {
return fit::bind_member(this, &DeviceImpl::OnNewRequest);
}
void DeviceImpl::OnNewRequest(fidl::InterfaceRequest<fuchsia::camera3::Device> request) {
Bind(std::move(request));
}
void DeviceImpl::Bind(fidl::InterfaceRequest<fuchsia::camera3::Device> request) {
bool first_client = clients_.empty();
auto client = std::make_unique<Client>(*this, client_id_next_, std::move(request));
auto [it, emplaced] = clients_.emplace(client_id_next_++, std::move(client));
auto& [id, new_client] = *it;
if (first_client) {
SetConfiguration(0);
} else {
new_client->ConfigurationUpdated(current_configuration_index_);
}
new_client->MuteUpdated(mute_state_);
}
void DeviceImpl::OnControllerDisconnected(zx_status_t status) {
FX_PLOGS(ERROR, status) << "Controller disconnected unexpectedly.";
ZX_ASSERT(bad_state_event_.signal(0, ZX_EVENT_SIGNALED) == ZX_OK);
}
void DeviceImpl::RemoveClient(uint64_t id) { clients_.erase(id); }
void DeviceImpl::SetConfiguration(uint32_t index) {
configuration_metrics_[current_configuration_index_]->SetActive(false);
current_configuration_index_ = index;
configuration_metrics_[current_configuration_index_]->SetActive(true);
std::vector<fit::promise<void, zx_status_t>> deallocation_promises;
for (auto& event : deallocation_events_) {
fit::bridge<void, zx_status_t> bridge;
auto wait = std::make_shared<async::WaitOnce>(event.release(), ZX_EVENTPAIR_PEER_CLOSED, 0);
wait->Begin(dispatcher_, [wait_ref = wait, completer = std::move(bridge.completer)](
async_dispatcher_t* dispatcher, async::WaitOnce* wait,
zx_status_t status, const zx_packet_signal_t* signal) mutable {
if (status != ZX_OK) {
completer.complete_error(status);
return;
}
completer.complete_ok();
});
deallocation_promises.push_back(bridge.consumer.promise());
}
deallocation_events_.clear();
deallocation_promises_ = std::move(deallocation_promises);
for (auto& stream : streams_) {
if (stream) {
stream->CloseAllClients(ZX_OK);
}
}
streams_.clear();
streams_.resize(configurations_[index].streams().size());
stream_to_pending_legacy_stream_request_params_.clear();
stream_request_sent_to_controller_.clear();
FX_LOGS(DEBUG) << "Configuration set to " << index << ".";
for (auto& client : clients_) {
client.second->ConfigurationUpdated(current_configuration_index_);
}
}
void DeviceImpl::SetSoftwareMuteState(
bool muted, fuchsia::camera3::Device::SetSoftwareMuteStateCallback callback) {
mute_state_.software_muted = muted;
UpdateControllerStreamingState();
for (auto& stream : streams_) {
if (stream) {
stream->SetMuteState(mute_state_);
}
}
callback();
for (auto& client : clients_) {
client.second->MuteUpdated(mute_state_);
}
}
void DeviceImpl::UpdateControllerStreamingState() {
if (mute_state_.muted() && controller_streaming_) {
controller_->DisableStreaming();
controller_streaming_ = false;
}
if (!mute_state_.muted() && !controller_streaming_) {
controller_->EnableStreaming();
controller_streaming_ = true;
}
}
void DeviceImpl::ConnectToStream(uint32_t index,
fidl::InterfaceRequest<fuchsia::camera3::Stream> request) {
if (index > streams_.size()) {
request.Close(ZX_ERR_INVALID_ARGS);
return;
}
if (streams_[index]) {
request.Close(ZX_ERR_ALREADY_BOUND);
return;
}
// Once the necessary token is received, post a task to send the request to the controller.
auto on_stream_requested =
[this, index](fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token,
fidl::InterfaceRequest<fuchsia::camera2::Stream> request,
fit::function<void(uint32_t)> max_camping_buffers_callback,
uint32_t format_index) {
FX_LOGS(DEBUG) << "New request for legacy stream.";
OnStreamRequested(index, std::move(token), std::move(request),
std::move(max_camping_buffers_callback), format_index);
};
// When the last client disconnects, post a task to the device thread to destroy the stream.
auto on_no_clients = [this, index]() {
stream_request_sent_to_controller_[index] = false;
streams_[index] = nullptr;
};
streams_[index] = std::make_unique<StreamImpl>(
dispatcher_, configuration_metrics_[current_configuration_index_]->stream(index),
configurations_[current_configuration_index_].streams()[index],
configs_[current_configuration_index_].stream_configs[index], std::move(request),
std::move(on_stream_requested), std::move(on_no_clients));
}
void DeviceImpl::OnStreamRequested(
uint32_t index, fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token,
fidl::InterfaceRequest<fuchsia::camera2::Stream> request,
fit::function<void(uint32_t)> max_camping_buffers_callback, uint32_t format_index) {
// Assign friendly names to each buffer for debugging and profiling.
std::ostringstream oss;
oss << "camera_c" << current_configuration_index_ << "_s" << index;
auto allocate_buffers =
[this, token = std::move(token), name = oss.str(),
index](const fit::result<std::vector<fit::result<void, zx_status_t>>>& results) mutable
-> fit::promise<BufferCollectionWithLifetime, zx_status_t> {
return sysmem_allocator_.BindSharedCollection(
std::move(token), configs_[current_configuration_index_].stream_configs[index].constraints,
name);
};
auto connect_to_stream =
[this, index, format_index, request = std::move(request),
max_camping_buffers_callback = std::move(max_camping_buffers_callback)](
fit::result<BufferCollectionWithLifetime, zx_status_t>& result) mutable {
if (result.is_error()) {
request.Close(result.error());
return;
}
auto buffer_collection_lifetime = result.take_value();
auto buffers = std::move(buffer_collection_lifetime.buffers);
deallocation_events_.push_back(std::move(buffer_collection_lifetime.deallocation_complete));
// Inform the stream of the maxmimum number of buffers it may hand out.
uint32_t max_camping_buffers =
buffers.buffer_count - configs_[current_configuration_index_]
.stream_configs[index]
.constraints.min_buffer_count_for_camping;
max_camping_buffers_callback(max_camping_buffers);
// Get the legacy stream using the negotiated buffers.
stream_to_pending_legacy_stream_request_params_.insert_or_assign(
index,
ControllerCreateStreamParams{format_index, std::move(buffers), std::move(request)});
MaybeConnectToLegacyStreams();
};
// Wait for any previous configurations buffers to finish deallocation, then allocate and connect
// to stream.
executor_.schedule_task(fit::join_promise_vector(std::move(deallocation_promises_))
.then(std::move(allocate_buffers))
.then(std::move(connect_to_stream))
.wrap_with(streams_[index]->Scope()));
}
// HUGE CAVEAT: Please see b/181345355 comment #24 for details.
constexpr uint32_t kMaxLegacyStreamRequestRequeueCount = 64;
constexpr zx::duration kLegacyStreamRequestDelay = zx::msec(1500);
// TODO(fxbug.dev/42241): Remove workaround once ordering constraint is removed.
void DeviceImpl::MaybeConnectToLegacyStreams() {
if (stream_to_pending_legacy_stream_request_params_.empty()) {
return;
}
bool preceding_streams_bound = true;
for (uint32_t i = 0; i < streams_.size(); ++i) {
auto it = stream_to_pending_legacy_stream_request_params_.find(i);
if (it != stream_to_pending_legacy_stream_request_params_.end()) {
auto& [index, params] = *it;
if (preceding_streams_bound ||
params.requeue_count++ == kMaxLegacyStreamRequestRequeueCount) {
// Definitely need an error log message if timed out because this affects the normal
// Sherlock use cases!
if (!preceding_streams_bound) {
FX_LOGS(ERROR) << "Legacy stream re-order wait timed out for stream_index=" << i;
FX_LOGS(ERROR) << "Controller behavior could be problematic from here on!";
}
// If all preceding streams are bound, or the threshold requeue count has been reached,
// immediately send the creation request and delete the pending map element.
controller_->CreateStream(current_configuration_index_, index, params.format_index,
std::move(params.buffers), std::move(params.request));
stream_request_sent_to_controller_[index] = true;
stream_to_pending_legacy_stream_request_params_.erase(it);
}
}
if (!stream_request_sent_to_controller_[i]) {
preceding_streams_bound = false;
}
}
// If any pending requests still exist, retry after a delay.
async::PostDelayedTask(
dispatcher_, [this] { MaybeConnectToLegacyStreams(); }, kLegacyStreamRequestDelay);
}
void DeviceImpl::OnMediaButtonsEvent(fuchsia::ui::input::MediaButtonsEvent event) {
if (event.has_mic_mute()) {
mute_state_.hardware_muted = event.mic_mute();
UpdateControllerStreamingState();
for (auto& client : clients_) {
client.second->MuteUpdated(mute_state_);
}
}
}
} // namespace camera