blob: 71e655465c684ae4c9673b9d23f70016757bbd73 [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 "controller_stream_provider.h"
#include <fcntl.h>
#include <fuchsia/hardware/camera/cpp/fidl.h>
#include <lib/fdio/fdio.h>
#include <lib/fzl/vmo-mapper.h>
#include <zircon/errors.h>
#include <fbl/unique_fd.h>
#include "src/lib/syslog/cpp/logger.h"
static constexpr const char* kDevicePath = "/dev/camera-controller/camera-controller-device";
ControllerStreamProvider::~ControllerStreamProvider() {
if (controller_ && streaming_) {
zx_status_t status = controller_->DisableStreaming();
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "Failed to stop streaming via the controller";
}
}
for (auto& buffer_collection : buffer_collections_) {
if (buffer_collection.second) {
zx_status_t status = buffer_collection.second->Close();
if (status != ZX_OK) {
FX_PLOGS(ERROR, status);
}
}
}
}
std::unique_ptr<StreamProvider> ControllerStreamProvider::Create() {
auto provider = std::make_unique<ControllerStreamProvider>();
// Connect to sysmem.
zx_status_t status =
sys::ComponentContext::Create()->svc()->Connect(provider->allocator_.NewRequest());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to connect to sysmem allocator service";
return nullptr;
}
if (!provider->allocator_) {
FX_LOGS(ERROR) << "Failed to connect to sysmem allocator service";
return nullptr;
}
// Connect to the controller device.
int result = open(kDevicePath, O_RDONLY);
if (result < 0) {
FX_LOGS(ERROR) << "Error opening " << kDevicePath;
return nullptr;
}
fbl::unique_fd controller_fd(result);
zx::channel channel;
status = fdio_get_service_handle(controller_fd.get(), channel.reset_and_get_address());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to get service handle";
return nullptr;
}
fuchsia::hardware::camera::DevicePtr device;
device.Bind(std::move(channel));
// Connect to the controller interface.
device->GetChannel2(provider->controller_.NewRequest().TakeChannel());
if (!provider->controller_) {
FX_LOGS(ERROR) << "Failed to get controller interface from device";
return nullptr;
}
// Get the list of valid configs as reported by the controller.
zx_status_t status_return = ZX_OK;
status = provider->controller_->GetConfigs(&provider->configs_, &status_return);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to call GetConfigs";
return nullptr;
}
if (status_return != ZX_OK) {
FX_PLOGS(ERROR, status_return) << "Failed to get configs";
return nullptr;
}
// Immediately enable streaming.
status = provider->controller_->EnableStreaming();
if (status != ZX_OK) {
FX_LOGS(WARNING) << "Failed to start streaming via the controller";
}
provider->streaming_ = true;
return std::move(provider);
}
// Offer a stream as served through the controller service provided by the driver.
fit::result<
std::tuple<fuchsia::sysmem::ImageFormat_2, fuchsia::sysmem::BufferCollectionInfo_2, bool>,
zx_status_t>
ControllerStreamProvider::ConnectToStream(fidl::InterfaceRequest<fuchsia::camera2::Stream> request,
uint32_t index) {
uint32_t config_index = 0;
uint32_t stream_config_index = 0;
uint32_t image_format_index = 0;
switch (index) {
case 0:
config_index = 1;
stream_config_index = 1;
image_format_index = 0;
break;
default:
return fit::error(ZX_ERR_OUT_OF_RANGE);
}
auto it = buffer_collections_.find(index);
if (it == buffer_collections_.end()) {
buffer_collections_.emplace(index, nullptr);
}
if (buffer_collections_[index].is_bound()) {
FX_PLOGS(ERROR, ZX_ERR_ALREADY_BOUND) << "Stream already bound by caller.";
return fit::error(ZX_ERR_ALREADY_BOUND);
}
if (config_index >= configs_->size()) {
FX_LOGS(ERROR) << "Invalid config index " << config_index;
return fit::error(ZX_ERR_BAD_STATE);
}
auto& config = configs_->at(config_index);
if (stream_config_index >= config.stream_configs.size()) {
FX_LOGS(ERROR) << "Invalid stream config index " << stream_config_index;
return fit::error(ZX_ERR_BAD_STATE);
}
auto& stream_config = config.stream_configs[stream_config_index];
if (image_format_index >= stream_config.image_formats.size()) {
FX_LOGS(ERROR) << "Invalid image format index " << image_format_index;
return fit::error(ZX_ERR_BAD_STATE);
}
auto& image_format = stream_config.image_formats[image_format_index];
// Attempt to create a buffer collection using controller-provided constraints.
if (!allocator_) {
FX_LOGS(ERROR) << "Allocator is dead!";
}
zx_status_t status =
allocator_->AllocateNonSharedCollection(buffer_collections_[index].NewRequest());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to allocate new collection";
return fit::error(status);
}
status = buffer_collections_[index]->SetConstraints(true, stream_config.constraints);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to set constraints to those reported by the controller";
return fit::error(status);
}
zx_status_t status_return = ZX_OK;
fuchsia::sysmem::BufferCollectionInfo_2 buffers;
status = buffer_collections_[index]->WaitForBuffersAllocated(&status_return, &buffers);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to call WaitForBuffersAllocated";
return fit::error(status);
}
if (status_return != ZX_OK) {
FX_PLOGS(ERROR, status_return) << "Failed to allocate buffers";
return fit::error(status_return);
}
// TODO(fxb/37296): remove ISP workarounds
// The ISP does not currently write the chroma layer, so initialize all VMOs to 128 (grayscale).
// This avoids the resulting image from appearing as 100% saturated green.
for (uint32_t i = 0; i < buffers.buffer_count; ++i) {
fzl::VmoMapper mapper;
status = mapper.Map(buffers.buffers[i].vmo, 0, buffers.settings.buffer_settings.size_bytes,
ZX_VM_PERM_READ | ZX_VM_PERM_WRITE);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Error mapping vmo";
return fit::error(status);
}
memset(mapper.start(), 128, mapper.size());
mapper.Unmap();
}
// Duplicate the collection info so it can be returned to the caller.
fuchsia::sysmem::BufferCollectionInfo_2 buffers_for_caller;
status = buffers.Clone(&buffers_for_caller);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to clone buffer collection";
return fit::error(status);
}
// Create the stream using the created buffer collection.
status = controller_->CreateStream(config_index, stream_config_index, image_format_index,
std::move(buffers), std::move(request));
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to create stream";
return fit::error(status);
}
return fit::ok(std::make_tuple(std::move(image_format), std::move(buffers_for_caller), false));
}