| // 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)); |
| } |