blob: c59ad6b89cd53fc399e1f2a089191e8391975712 [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-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include "src/camera/bin/device/messages.h"
#include "src/camera/bin/device/util.h"
#include "src/lib/fsl/handles/object_info.h"
DeviceImpl::DeviceImpl()
: loop_(&kAsyncLoopConfigNoAttachToCurrentThread), button_listener_binding_(this) {}
DeviceImpl::~DeviceImpl() {
Unbind(controller_);
Unbind(allocator_);
Unbind(registry_);
async::PostTask(loop_.dispatcher(), [this] { loop_.Quit(); });
loop_.JoinThreads();
}
fit::result<std::unique_ptr<DeviceImpl>, zx_status_t> DeviceImpl::Create(
fidl::InterfaceHandle<fuchsia::camera2::hal::Controller> controller,
fidl::InterfaceHandle<fuchsia::sysmem::Allocator> allocator,
fuchsia::ui::policy::DeviceListenerRegistryHandle registry) {
auto device = std::make_unique<DeviceImpl>();
ZX_ASSERT(zx::event::create(0, &device->bad_state_event_) == ZX_OK);
ZX_ASSERT(device->allocator_.Bind(std::move(allocator), device->loop_.dispatcher()) == ZX_OK);
constexpr auto kControllerDisconnected = ZX_USER_SIGNAL_0;
constexpr auto kGetDeviceInfoReturned = ZX_USER_SIGNAL_1;
constexpr auto kGetConfigsReturned = ZX_USER_SIGNAL_2;
zx::event event;
ZX_ASSERT(zx::event::create(0, &event) == ZX_OK);
// Bind the controller interface and get some initial startup information.
ZX_ASSERT(device->controller_.Bind(std::move(controller), device->loop_.dispatcher()) == ZX_OK);
zx_status_t controller_status = ZX_OK;
device->controller_.set_error_handler([&](zx_status_t status) {
FX_PLOGS(ERROR, status) << "Controller server disconnected during initialization.";
controller_status = status;
ZX_ASSERT(event.signal(0, kControllerDisconnected) == ZX_OK);
});
device->controller_->GetDeviceInfo(
[&, device = device.get()](fuchsia::camera2::DeviceInfo device_info) {
device->device_info_ = std::move(device_info);
ZX_ASSERT(event.signal(0, kGetDeviceInfoReturned) == ZX_OK);
});
zx_status_t get_next_config_status = ZX_OK;
fit::function<void(std::unique_ptr<fuchsia::camera2::hal::Config>, zx_status_t)>
get_next_config_callback =
[&, device = device.get()](std::unique_ptr<fuchsia::camera2::hal::Config> config,
zx_status_t status) {
get_next_config_status = status;
if (status == ZX_OK) {
auto result = Convert(*config);
if (result.is_error()) {
get_next_config_status = result.error();
FX_PLOGS(ERROR, get_next_config_status);
return;
}
device->configurations_.push_back(result.take_value());
device->configs_.push_back(std::move(*config));
// Call again to get remaining configs.
device->controller_->GetNextConfig(get_next_config_callback.share());
return;
}
if (status == ZX_ERR_STOP) {
get_next_config_status = ZX_OK;
device->SetConfiguration(0);
} else {
get_next_config_status = ZX_ERR_INTERNAL;
FX_PLOGS(ERROR, status)
<< "Controller unexpectedly returned error or null/empty configs list.";
}
ZX_ASSERT(event.signal(0, kGetConfigsReturned) == ZX_OK);
};
device->controller_->GetNextConfig(get_next_config_callback.share());
// Bind the registry interface and register the device as a listener.
ZX_ASSERT(device->registry_.Bind(std::move(registry), device->loop_.dispatcher()) == ZX_OK);
device->registry_->RegisterMediaButtonsListener(
device->button_listener_binding_.NewBinding(device->loop_.dispatcher()));
// Start the device thread and begin processing messages.
ZX_ASSERT(device->loop_.StartThread("Camera Device Thread") == ZX_OK);
// Wait for either an error, or for all expected callbacks to occur.
zx_signals_t signaled{};
ZX_ASSERT(WaitMixed(event, kGetDeviceInfoReturned | kGetConfigsReturned, kControllerDisconnected,
zx::time::infinite(), &signaled) == ZX_OK);
if (signaled & kControllerDisconnected) {
FX_PLOGS(ERROR, controller_status);
return fit::error(controller_status);
}
// Rebind the controller error handler.
ZX_ASSERT(async::PostTask(device->loop_.dispatcher(), [device = device.get()]() {
device->controller_.set_error_handler(
fit::bind_member(device, &DeviceImpl::OnControllerDisconnected));
}) == ZX_OK);
return fit::ok(std::move(device));
}
fidl::InterfaceRequestHandler<fuchsia::camera3::Device> DeviceImpl::GetHandler() {
return fit::bind_member(this, &DeviceImpl::OnNewRequest);
}
zx::event DeviceImpl::GetBadStateEvent() {
zx::event event;
ZX_ASSERT(bad_state_event_.duplicate(ZX_RIGHTS_BASIC, &event) == ZX_OK);
return event;
}
void DeviceImpl::OnNewRequest(fidl::InterfaceRequest<fuchsia::camera3::Device> request) {
PostBind(std::move(request), true);
}
void DeviceImpl::PostBind(fidl::InterfaceRequest<fuchsia::camera3::Device> request,
bool exclusive) {
auto task = [this, request = std::move(request), exclusive]() mutable {
if (exclusive && !clients_.empty()) {
#if CAMERA_POLICY_ALLOW_REPLACEMENT_CONNECTIONS
FX_LOGS(WARNING) << "!!!! CAMERA POLICY OVERRIDE FORCING DISCONNECT OF " << clients_.size()
<< " EXISTING CLIENTS !!!!";
clients_.clear();
#else
request.Close(ZX_ERR_ALREADY_BOUND);
return;
#endif
}
auto client = std::make_unique<Client>(*this, client_id_next_, std::move(request));
clients_.emplace(client_id_next_++, std::move(client));
if (exclusive) {
SetConfiguration(0);
}
};
ZX_ASSERT(async::PostTask(loop_.dispatcher(), std::move(task)) == ZX_OK);
}
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::PostRemoveClient(uint64_t id) {
ZX_ASSERT(async::PostTask(loop_.dispatcher(), [this, id]() { clients_.erase(id); }) == ZX_OK);
}
void DeviceImpl::PostSetConfiguration(uint32_t index) {
ZX_ASSERT(async::PostTask(loop_.dispatcher(), [this, index]() { SetConfiguration(index); }) ==
ZX_OK);
}
void DeviceImpl::SetConfiguration(uint32_t index) {
streams_.clear();
streams_.resize(configurations_[index].streams().size());
current_configuration_index_ = index;
FX_LOGS(DEBUG) << "Configuration set to " << index << ".";
for (auto& client : clients_) {
client.second->PostConfigurationUpdated(current_configuration_index_);
client.second->PostMuteUpdated(mute_state_);
}
}
void DeviceImpl::PostSetSoftwareMuteState(
bool muted, fuchsia::camera3::Device::SetSoftwareMuteStateCallback callback) {
zx_status_t status =
async::PostTask(loop_.dispatcher(), [this, muted, callback = std::move(callback)]() mutable {
mute_state_.software_muted = muted;
UpdateControllerStreamingState();
auto counter = std::make_shared<std::atomic_uint32_t>(streams_.size());
for (auto& stream : streams_) {
stream->PostSetMuteState(
mute_state_, [this, counter, callback = callback.share()]() mutable {
if (counter->fetch_sub(1) == 1) {
async::PostTask(loop_.dispatcher(), [this, callback = callback.share()] {
callback();
for (auto& client : clients_) {
client.second->PostMuteUpdated(mute_state_);
}
});
}
});
}
});
ZX_ASSERT(status == ZX_OK);
}
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::PostConnectToStream(uint32_t index,
fidl::InterfaceRequest<fuchsia::camera3::Stream> request) {
ZX_ASSERT(
async::PostTask(loop_.dispatcher(), [this, index, request = std::move(request)]() mutable {
ConnectToStream(index, std::move(request));
}) == ZX_OK);
}
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;
}
auto check_token = [this](zx_koid_t token_server_koid, fit::function<void(bool)> callback) {
async::PostTask(loop_.dispatcher(),
[this, token_server_koid, callback = std::move(callback)]() mutable {
allocator_->ValidateBufferCollectionToken(
token_server_koid,
[callback = std::move(callback)](bool is_known) { callback(is_known); });
});
};
// 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.";
ZX_ASSERT(async::PostTask(
loop_.dispatcher(),
[this, index, token = std::move(token), request = std::move(request),
max_camping_buffers_callback = std::move(max_camping_buffers_callback),
format_index]() mutable {
OnStreamRequested(index, std::move(token), std::move(request),
std::move(max_camping_buffers_callback), format_index);
}) == ZX_OK);
};
// When the last client disconnects, post a task to the device thread to destroy the stream.
auto on_no_clients = [this, index]() {
ZX_ASSERT(async::PostTask(loop_.dispatcher(), [this, index]() { streams_[index] = nullptr; }) ==
ZX_OK);
};
streams_[index] = std::make_unique<StreamImpl>(
configurations_[current_configuration_index_].streams()[index],
configs_[current_configuration_index_].stream_configs[index], std::move(request),
std::move(check_token), std::move(on_stream_requested), std::move(on_no_clients));
}
// Returns when the provided allocator will likely succeed in allocating a collection with the given
// constraints and some number of other participants. This is necessary prior to a solution for
// fxbug.dev/53305. Returns true iff the wait completed successfully.
bool WaitForFreeSpace(fuchsia::sysmem::AllocatorPtr& allocator_ptr,
fuchsia::sysmem::BufferCollectionConstraints constraints) {
// Incorporate typical client constraints.
constexpr uint32_t kMaxClientBuffers = 5;
constexpr uint32_t kBytesPerRowDivisor = 32 * 16; // GPU-optimal stride.
constraints.min_buffer_count_for_camping += kMaxClientBuffers;
for (auto& format_constraints : constraints.image_format_constraints) {
format_constraints.bytes_per_row_divisor =
std::max(format_constraints.bytes_per_row_divisor, kBytesPerRowDivisor);
}
// Adopt the allocator channel as SyncPtr. This is only safe because the call is performed in the
// thread owned by the previously associated loop dispatcher, and the entire function is
// synchronous.
ZX_ASSERT(async_get_default_dispatcher() == allocator_ptr.dispatcher());
fuchsia::sysmem::AllocatorSyncPtr allocator;
allocator.Bind(allocator_ptr.Unbind());
// Repeatedly attempt allocation as long as it fails due to lack of memory.
fuchsia::sysmem::BufferCollectionInfo_2 buffers;
zx_status_t status = ZX_OK;
uint32_t num_attempts = 0;
constexpr uint32_t kMaxAttempts = 10;
constexpr uint32_t kInitialDelayMs = 200;
constexpr uint32_t kFinalDelayMs = 1000;
do {
fuchsia::sysmem::BufferCollectionSyncPtr collection;
status = allocator->AllocateNonSharedCollection(collection.NewRequest());
if (status != ZX_OK) {
// Skip loop to shorten test execution.
break;
}
collection->SetName(0, "FreeSpaceProbe");
collection->SetConstraints(true, constraints);
// After calling SetConstraints, allocation may fail. This results in Wait returning NO_MEMORY
// followed by channel closure. Because the client may observe these in either order, treat
// channel closure as if it were NO_MEMORY.
zx_status_t status_out = ZX_OK;
status = collection->WaitForBuffersAllocated(&status_out, &buffers);
if (status == ZX_ERR_PEER_CLOSED) {
status = ZX_ERR_NO_MEMORY;
} else {
ZX_ASSERT(status == ZX_OK);
status = status_out;
collection->Close();
}
// Free any allocated buffers and wait enough time for them to be freed by sysmem as well.
collection = nullptr;
buffers = {};
uint32_t delay_ms =
kInitialDelayMs + ((kFinalDelayMs - kInitialDelayMs) * num_attempts) / (kMaxAttempts - 1);
zx::nanosleep(zx::deadline_after(zx::msec(delay_ms)));
} while (++num_attempts < kMaxAttempts && status == ZX_ERR_NO_MEMORY);
// Restore the channel to the async binding.
zx_status_t restore_status = allocator_ptr.Bind(allocator.Unbind());
if (restore_status != ZX_OK) {
ZX_ASSERT(restore_status == ZX_ERR_CANCELED);
// Thread is shutting down.
return false;
}
if (status != ZX_OK) {
FX_PLOGS(INFO, status) << "Timeout waiting for free space.";
return false;
}
return true;
}
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) {
// TODO(fxbug.dev/53305): sysmem should help transition between collections that cannot exist
// concurrently
std::unique_lock sysmem_lock(sysmem_mutex_, std::try_to_lock);
if (!sysmem_lock.owns_lock()) {
async::PostTask(loop_.dispatcher(),
[this, index, token = std::move(token), request = std::move(request),
max_camping_buffers_callback = std::move(max_camping_buffers_callback),
format_index]() mutable {
OnStreamRequested(index, std::move(token), std::move(request),
std::move(max_camping_buffers_callback), format_index);
});
return;
}
// Negotiate buffers for this stream.
// TODO(fxbug.dev/44770): Watch for buffer collection events.
fuchsia::sysmem::BufferCollectionPtr collection;
allocator_->BindSharedCollection(std::move(token), collection.NewRequest(loop_.dispatcher()));
if (!WaitForFreeSpace(allocator_,
configs_[current_configuration_index_].stream_configs[index].constraints)) {
request.Close(ZX_ERR_NO_MEMORY);
return;
}
// Assign friendly names to each buffer for debugging and profiling.
std::ostringstream oss;
oss << "camera_c" << current_configuration_index_ << "_s" << index;
constexpr uint32_t kNamePriority = 30; // Higher than Scenic but below the maximum.
collection->SetName(kNamePriority, oss.str());
collection->SetConstraints(
true, configs_[current_configuration_index_].stream_configs[index].constraints);
collection->WaitForBuffersAllocated(
[this, index, format_index, request = std::move(request),
max_camping_buffers_callback = std::move(max_camping_buffers_callback),
collection = std::move(collection), sysmem_lock = std::move(sysmem_lock)](
zx_status_t status, fuchsia::sysmem::BufferCollectionInfo_2 buffers) mutable {
sysmem_lock.unlock();
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to allocate buffers for stream.";
request.Close(status);
return;
}
// 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.
controller_->CreateStream(current_configuration_index_, index, format_index,
std::move(buffers), std::move(request));
collection->Close();
});
}
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->PostMuteUpdated(mute_state_);
}
}
}