blob: 74c87ac92194c559c4ff97e59162c7b92214f316 [file] [log] [blame]
// Copyright 2018 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 <garnet/lib/media/camera/simple_camera_lib/camera_client.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/limits.h>
#include <lib/async/default.h>
#include <lib/fdio/io.h>
#include <zircon/assert.h>
#include <zircon/device/audio.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zx/channel.h>
#include <zx/handle.h>
#include <zx/vmar.h>
#include <zx/vmo.h>
namespace simple_camera {
#define CHECK_RESP_RESULT(_resp, _cmd_name) \
if (ZX_OK != _resp.result) { \
FXL_LOG(ERROR) << _cmd_name << " failure (result: " << resp.result << ")"; \
return _resp.result; \
}
CameraClient::CameraClient() = default;
CameraClient::~CameraClient() {
// Destruction is the one case where we don't need to notify the caller
// about shutting down.
Close();
}
zx_status_t CameraClient::Open(uint32_t dev_id,
OnShutdownCallback shutdown_callback) {
char dev_path[64] = {0};
snprintf(dev_path, sizeof(dev_path), "/dev/class/camera/%03u", dev_id);
fxl::UniqueFD dev_node(::open(dev_path, O_RDONLY));
if (!dev_node.is_valid()) {
FXL_LOG(ERROR) << "CameraClient failed to open device node at \""
<< dev_path << "\". (" << strerror(errno) << " : " << errno
<< ")";
return ZX_ERR_IO;
}
return OpenChannel(std::move(dev_node), std::move(shutdown_callback));
}
zx_status_t CameraClient::Open(int dir_fd, const std::string& name,
OnShutdownCallback shutdown_callback) {
// Open the device node.
fxl::UniqueFD dev_node(::openat(dir_fd, name.c_str(), O_RDONLY));
if (!dev_node.is_valid()) {
FXL_LOG(WARNING) << "CameraClient failed to open device node at \"" << name
<< "\". (" << strerror(errno) << " : " << errno << ")";
return ZX_ERR_BAD_STATE;
}
return OpenChannel(std::move(dev_node), std::move(shutdown_callback));
}
zx_status_t CameraClient::OpenChannel(fxl::UniqueFD dev_node,
OnShutdownCallback shutdown_callback) {
if (!IsClosed()) {
FXL_LOG(ERROR) << "Bad State";
return ZX_ERR_BAD_STATE;
}
if (stream_ch_.is_valid()) {
FXL_LOG(ERROR) << "channel has already been opened!";
return ZX_ERR_BAD_STATE;
}
if (!shutdown_callback) {
return ZX_ERR_INVALID_ARGS;
}
client_shutdown_notifier_ = std::move(shutdown_callback);
ssize_t res = ::fdio_ioctl(dev_node.get(), CAMERA_IOCTL_GET_CHANNEL, nullptr,
0, &stream_ch_, sizeof(stream_ch_));
if (res != sizeof(stream_ch_)) {
FXL_LOG(ERROR) << "Failed to obtain channel (res " << res << ")";
return static_cast<zx_status_t>(res);
}
// Set up waiter to wait for messages on this channel:
cmd_msg_waiter_.set_object(stream_ch_.get());
cmd_msg_waiter_.set_trigger(ZX_CHANNEL_READABLE);
zx_status_t status = cmd_msg_waiter_.Begin(async_get_default());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to start AutoWaiter";
return status;
}
SetConfigurationState(CameraState::Closed, CameraState::CommandChannelOpen);
return ZX_OK;
}
zx_status_t CameraClient::GetSupportedFormats(
GetFormatCallback get_formats_callback) {
// Check the state. This state check enforces a strict calling order for
// camera configuration. Technically, this call should be supported pretty
// much any time, but currently we require GetSupportedFormats only be called
// after the channel is open, and before the format is set.
zx_status_t status = CheckConfigurationState(CameraState::CommandChannelOpen);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Bad State";
return status;
}
if (!get_formats_callback) {
return ZX_ERR_INVALID_ARGS;
}
get_formats_callback_ = fbl::move(get_formats_callback);
camera_stream_cmd_get_formats_req req;
req.hdr.cmd = CAMERA_STREAM_CMD_GET_FORMATS;
zx_status_t write_status = stream_ch_.write(0, &req, sizeof(req), nullptr, 0);
if (write_status != ZX_OK) {
FXL_LOG(ERROR) << "Cmd write failure (cmd " << req.hdr.cmd << ", res "
<< write_status << ")";
return write_status;
}
SetConfigurationState(CameraState::CommandChannelOpen,
CameraState::FormatsRequested);
return ZX_OK;
}
zx_status_t CameraClient::OnGetFormatsResp(
camera::camera_proto::GetFormatsResp resp) {
zx_status_t status = CheckConfigurationState(CameraState::FormatsRequested);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Unexpected message response (cmd " << resp.hdr.cmd
<< ", GetFormats)";
return ZX_ERR_BAD_STATE;
}
// If we just began receiving formats:
if (resp.already_sent_count == 0) {
out_formats_.clear();
}
uint32_t expected_formats = resp.total_format_count;
FXL_VLOG(3) << "expected_formats: " << expected_formats;
if (!expected_formats) {
// done grabbing formats
SetConfigurationState(CameraState::FormatsRequested,
CameraState::FormatsReceived);
zx_status_t ret = get_formats_callback_(out_formats_);
get_formats_callback_ = nullptr;
return ret;
}
if (out_formats_.size() == 0) {
out_formats_.reserve(expected_formats);
}
// Check for out of order:
if (out_formats_.size() != resp.already_sent_count) {
FXL_LOG(ERROR) << "Bad format index while fetching formats (expected "
<< out_formats_.size() << ", got " << resp.already_sent_count
<< ")";
return ZX_ERR_INTERNAL;
}
// Calculate how many frames to grab. If there are more than
// CAMERA_STREAM_CMD_GET_FORMATS_MAX_FORMATS_PER_RESPONSE formats,
// we will be getting multiple messages. Each message, with the possible
// exeption of the last message will have the max number of formats.
// The last message will have (total messages) - (already received)
// messages.
uint32_t to_grab =
fbl::min(static_cast<uint32_t>(expected_formats - out_formats_.size()),
CAMERA_STREAM_CMD_GET_FORMATS_MAX_FORMATS_PER_RESPONSE);
for (uint16_t i = 0; i < to_grab; ++i) {
out_formats_.push_back(resp.formats[i]);
}
if (out_formats_.size() == expected_formats) {
// done grabbing formats.
FXL_VLOG(4)
<< "CameraClient::OnGetFormatsResp grabbed formats, calling callback";
SetConfigurationState(CameraState::FormatsRequested,
CameraState::FormatsReceived);
zx_status_t ret = get_formats_callback_(out_formats_);
get_formats_callback_ = nullptr;
return ret;
}
return ZX_OK;
}
zx_status_t CameraClient::SetFormat(const camera_video_format_t& format,
SetFormatCallback set_format_callback) {
zx_status_t status = CheckConfigurationState(CameraState::FormatsReceived);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Bad State";
return status;
}
FXL_DCHECK(stream_ch_.is_valid() && !vb_ch_.is_valid())
<< "Channels in wrong state for SetFormat";
if (!set_format_callback) {
FXL_LOG(ERROR) << "set_format_callback is invalid";
return ZX_ERR_INVALID_ARGS;
}
set_format_callback_ = fbl::move(set_format_callback);
camera_stream_cmd_set_format_req_t req;
req.hdr.cmd = CAMERA_STREAM_CMD_SET_FORMAT;
req.video_format = format;
zx_status_t write_status = stream_ch_.write(0, &req, sizeof(req), nullptr, 0);
if (write_status != ZX_OK) {
FXL_LOG(ERROR) << "Cmd write failure (cmd " << req.hdr.cmd << ", res "
<< write_status << ")";
return write_status;
}
SetConfigurationState(CameraState::FormatsReceived,
CameraState::SetFormatRequested);
return ZX_OK;
}
zx_status_t CameraClient::OnSetFormatResp(
camera::camera_proto::SetFormatResp resp, zx::channel resp_handle_out) {
CHECK_RESP_RESULT(resp, "SetFormat");
zx_status_t status = CheckConfigurationState(CameraState::SetFormatRequested);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Unexpected message response (cmd " << resp.hdr.cmd
<< ", SetFormat)";
return ZX_ERR_BAD_STATE;
}
vb_ch_.reset(resp_handle_out.release());
// Now that our buffer is recognized, set up our waiter on the buffer
// channel:
buff_msg_waiter_.set_object(vb_ch_.get());
buff_msg_waiter_.set_trigger(ZX_CHANNEL_READABLE);
status = buff_msg_waiter_.Begin(async_get_default());
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to start AutoWaiter";
return status;
}
SetConfigurationState(CameraState::SetFormatRequested,
CameraState::SetFormatReceived);
SetFormatCallback set_format_callback = fbl::move(set_format_callback_);
return set_format_callback(resp.max_frame_size);
}
zx_status_t CameraClient::SetBuffer(const zx::vmo& buffer_vmo) {
zx_status_t status = CheckConfigurationState(CameraState::SetFormatReceived);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Bad State";
return status;
}
camera_vb_cmd_set_buffer_req_t req;
req.hdr.cmd = CAMERA_VB_CMD_SET_BUFFER;
zx_handle_t vmo_handle;
zx_handle_duplicate(buffer_vmo.get(), ZX_RIGHT_SAME_RIGHTS, &vmo_handle);
zx_status_t write_status = vb_ch_.write(0, &req, sizeof(req), &vmo_handle, 1);
if (write_status != ZX_OK) {
FXL_LOG(ERROR) << "Cmd write failure (cmd " << req.hdr.cmd << ", res "
<< write_status << ")";
return write_status;
}
SetConfigurationState(CameraState::SetFormatReceived,
CameraState::SetBufferRequested);
return ZX_OK;
}
zx_status_t CameraClient::ReleaseFrame(uint64_t data_offset) {
if (IsClosed()) {
// We shut down, so ignore these notifications.
return ZX_OK;
}
if (!IsStreaming()) {
FXL_LOG(ERROR) << "ReleaseFrame called while not streaming.";
return ZX_ERR_BAD_STATE;
}
if (!vb_ch_.is_valid()) {
FXL_LOG(ERROR) << "ReleaseFrame called without an open buffer channel";
return ZX_ERR_BAD_STATE;
}
camera_vb_cmd_frame_release_req req;
req.hdr.cmd = CAMERA_VB_CMD_FRAME_RELEASE;
req.data_vb_offset = data_offset;
zx_status_t write_status = vb_ch_.write(0, &req, sizeof(req), nullptr, 0);
if (write_status != ZX_OK) {
FXL_LOG(ERROR) << "Cmd write failure (cmd " << req.hdr.cmd << ", res "
<< write_status << ")";
}
return write_status;
}
zx_status_t CameraClient::Start(FrameNotifyCallback frame_notify_callback) {
zx_status_t status = CheckConfigurationState(CameraState::SetBufferRequested);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Bad State";
return status;
}
if (!frame_notify_callback) {
return ZX_ERR_INVALID_ARGS;
}
frame_notify_callback_ = fbl::move(frame_notify_callback);
camera_vb_cmd_start_req_t req;
req.hdr.cmd = CAMERA_VB_CMD_START;
zx_status_t write_status = vb_ch_.write(0, &req, sizeof(req), nullptr, 0);
if (write_status != ZX_OK) {
FXL_LOG(ERROR) << "Cmd write failure (cmd " << req.hdr.cmd << ", res "
<< write_status << ")";
return write_status;
}
SetConfigurationState(CameraState::SetBufferRequested,
CameraState::StartRequested);
return ZX_OK;
}
zx_status_t CameraClient::OnFrameNotify(
camera::camera_proto::VideoBufFrameNotify resp) {
if (!IsStreaming()) {
FXL_LOG(ERROR) << "Unexpected message response (cmd " << resp.hdr.cmd
<< ", FrameNotify)";
return ZX_ERR_BAD_STATE;
}
// frame_notify_callback_ is the one callback we don't clear after calling.
return frame_notify_callback_(resp);
}
zx_status_t CameraClient::SendStop() {
camera_vb_cmd_stop_req_t req;
req.hdr.cmd = CAMERA_VB_CMD_STOP;
zx_status_t write_status = vb_ch_.write(0, &req, sizeof(req), nullptr, 0);
if (write_status != ZX_OK) {
FXL_LOG(ERROR) << "Cmd write failure (cmd " << req.hdr.cmd << ", res "
<< write_status << ")";
return write_status;
}
return ZX_OK;
}
// You can call Stop anytime. Go nuts! It will only send a command if you are
// streaming though.
zx_status_t CameraClient::Stop() {
if (!IsStreaming() && !IsConfiguring()) {
return ZX_OK;
}
// There is one configuration state where we accept a stop command, and that
// if we have sent Start, but not received a response:
if (IsConfiguring() &&
CheckConfigurationState(CameraState::StartRequested) != ZX_OK) {
return ZX_ERR_BAD_STATE;
}
if (!vb_ch_.is_valid()) {
FXL_LOG(ERROR) << "Stop called without an open buffer channel";
return ZX_ERR_BAD_STATE;
}
zx_status_t write_status = SendStop();
if (write_status != ZX_OK) {
return write_status;
}
// TODO(garratt): Make sure the driver is able to transition:
// Start->Stop->Start. If it just shoots itself in the head, we will
// have to reset everything back to the beginning.
CameraState state;
{
fbl::AutoLock lock(&state_lock_);
state = state_;
}
SetConfigurationState(state, CameraState::SetBufferRequested);
return ZX_OK;
}
struct JustResultResp {
camera_cmd_hdr_t hdr;
zx_status_t result;
};
typedef union {
camera::camera_proto::CmdHdr hdr;
camera::camera_proto::GetFormatsResp get_format;
camera::camera_proto::SetFormatResp set_format;
JustResultResp just_result;
} CameraCmdResponse;
typedef union {
camera::camera_proto::CmdHdr hdr;
camera::camera_proto::VideoBufSetBufferResp set_buffer;
camera::camera_proto::VideoBufStartResp start;
camera::camera_proto::VideoBufStopResp stop;
camera::camera_proto::VideoBufFrameReleaseResp release_frame;
camera::camera_proto::VideoBufFrameNotify frame_notify;
JustResultResp just_result;
} CameraBufferResponse;
// We use just_result instead of payload to avoid compile errors for payloads
// without a result field...
#define CHECK_RESP(_ioctl, _payload, check_result_string) \
do { \
if (resp_size != sizeof(resp._payload)) { \
FXL_LOG(ERROR) << "Bad " #_ioctl " response length (" << resp_size \
<< " != " << sizeof(resp._payload) << ")"; \
return ZX_ERR_INVALID_ARGS; \
} \
if (strlen(check_result_string) > 1) { \
if (resp.just_result.result != ZX_OK) { \
FXL_LOG(ERROR) << "Failed to " << check_result_string \
<< " Shutting down!"; \
return resp.just_result.result; \
} \
} \
} while (0);
zx_status_t CameraClient::ProcessBufferChannel() {
CameraBufferResponse resp;
static_assert(sizeof(resp) <= ZX_CHANNEL_MAX_MSG_BYTES,
"Response buffer is getting to be too large!");
uint32_t resp_size;
zx_status_t res =
vb_ch_.read(0, &resp, sizeof(resp), &resp_size, nullptr, 0, nullptr);
if (resp_size < sizeof(resp.hdr) || res != ZX_OK) {
return res == ZX_OK ? ZX_ERR_INVALID_ARGS : res;
}
auto cmd = static_cast<camera::camera_proto::Cmd>(resp.hdr.cmd);
switch (cmd) {
case CAMERA_VB_FRAME_NOTIFY:
CHECK_RESP(CAMERA_VB_FRAME_NOTIFY, frame_notify, "");
return OnFrameNotify(resp.frame_notify);
case CAMERA_VB_CMD_SET_BUFFER:
CHECK_RESP(CAMERA_VB_CMD_SET_BUFFER, set_buffer, "SetBuffer");
return ZX_OK;
case CAMERA_VB_CMD_START:
CHECK_RESP(CAMERA_VB_CMD_START, start, "Start");
SetStreaming();
return ZX_OK;
case CAMERA_VB_CMD_STOP:
// The driver will close the channel(s) shortly which will trigger a
// channel read error soon, so no need to trigger anything here.
CHECK_RESP(CAMERA_VB_CMD_STOP, stop, "Stop");
return ZX_OK;
case CAMERA_VB_CMD_FRAME_RELEASE:
CHECK_RESP(CAMERA_VB_CMD_FRAME_RELEASE, release_frame, "Release");
return ZX_OK;
default:
FXL_LOG(ERROR) << "Unrecognized stream command " << resp.hdr.cmd;
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t CameraClient::ProcessCmdChannel() {
CameraCmdResponse resp;
static_assert(sizeof(resp) <= ZX_CHANNEL_MAX_MSG_BYTES,
"Response buffer is getting to be too large!");
uint32_t resp_size = 0, num_rxed_handles = 0;
zx_handle_t rxed_handle;
zx_status_t res = stream_ch_.read(0, &resp, sizeof(resp), &resp_size,
&rxed_handle, 1, &num_rxed_handles);
if (resp_size < sizeof(resp.hdr) || res != ZX_OK) {
if (num_rxed_handles != 0) {
zx_handle_close(rxed_handle);
}
return res == ZX_OK ? ZX_ERR_INVALID_ARGS : res;
}
FXL_VLOG(4) << "Received command response. cmd: " << resp.hdr.cmd << " "
<< resp_size << " resp_size, " << rxed_handle << " handle, "
<< num_rxed_handles << " num_handles";
auto cmd = static_cast<camera::camera_proto::Cmd>(resp.hdr.cmd);
switch (cmd) {
case CAMERA_STREAM_CMD_GET_FORMATS:
CHECK_RESP(CAMERA_STREAM_CMD_GET_FORMAT, get_format, "");
if (num_rxed_handles != 0) {
FXL_LOG(ERROR) << "received unexpected channel on GetFormatResponse";
zx_handle_close(rxed_handle);
return ZX_ERR_INTERNAL;
}
return OnGetFormatsResp(resp.get_format);
break;
case CAMERA_STREAM_CMD_SET_FORMAT:
CHECK_RESP(CAMERA_STREAM_CMD_SET_FORMAT, set_format, "");
if (num_rxed_handles != 1) {
FXL_LOG(ERROR) << "Failed to receive channel on SetFormatResponse";
return ZX_ERR_INTERNAL;
}
return OnSetFormatResp(resp.set_format, zx::channel(rxed_handle));
break;
default:
FXL_LOG(ERROR) << "Unrecognized command response " << resp.hdr.cmd;
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_ERR_NOT_SUPPORTED;
}
#undef CHECK_RESP
void CameraClient::OnNewCmdMessage(
async_t* async,
async::WaitBase* wait,
zx_status_t status,
const zx_packet_signal* signal) {
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Error: CameraClient received an error. Exiting.";
return;
}
// Read channel
zx_status_t ret_status = ProcessCmdChannel();
if (ret_status != ZX_OK) {
FXL_LOG(ERROR) << "Error: Got bad status when processing channel ("
<< ret_status << ")";
CloseAndNotify();
return;
}
status = wait->Begin(async);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Error: CameraClient wait failed. Exiting.";
}
}
void CameraClient::OnNewBufferMessage(
async_t* async,
async::WaitBase* wait,
zx_status_t status,
const zx_packet_signal* signal) {
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Error: CameraClient received an error. Exiting.";
return;
}
// Read channel
zx_status_t ret_status = ProcessBufferChannel();
if (ret_status != ZX_OK) {
FXL_LOG(ERROR) << "Error: Got bad status when processing channel ("
<< ret_status << ")";
// TODO(garratt): Shut down only this stream, instead of whole process
CloseAndNotify();
return;
}
status = wait->Begin(async);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Error: CameraClient wait failed. Exiting.";
}
}
zx_status_t CameraClient::CheckConfigurationState(CameraState required_state) {
fbl::AutoLock lock(&state_lock_);
if (0 == (state_ & CameraState::Configuring)) {
FXL_LOG(ERROR) << "Camera Client is not in configuration state! "
"Current state: "
<< (int)state_;
return ZX_ERR_BAD_STATE;
}
if (state_ != required_state) {
FXL_LOG(ERROR) << "CameraClient in wrong configuration state! Expected:"
<< " " << (int)required_state
<< " current state: " << (int)state_;
return ZX_ERR_BAD_STATE;
}
// Make sure the right channels are open:
// Clear the configuration bit, or else we will match everything:
uint16_t required_channels = required_state & ChannelsMask;
// If buffer command, make sure buffer channel is open:
if ((required_channels & BufferChannelOpen) && !vb_ch_.is_valid()) {
FXL_LOG(ERROR) << "Buffer command called without an open buffer channel";
return ZX_ERR_BAD_STATE;
}
// if we require a command channel is open:
if ((required_channels & CommandChannelOpen) && !stream_ch_.is_valid()) {
FXL_LOG(ERROR) << "Stream command called without an open cmd channel";
return ZX_ERR_BAD_STATE;
}
return ZX_OK;
}
bool CameraClient::IsStreaming() {
fbl::AutoLock lock(&state_lock_);
return state_ == CameraState::Streaming;
}
bool CameraClient::IsConfiguring() {
fbl::AutoLock lock(&state_lock_);
return state_ & CameraState::Configuring;
}
bool CameraClient::IsClosed() {
fbl::AutoLock lock(&state_lock_);
return state_ == CameraState::Closed;
}
void CameraClient::SetStreaming() {
fbl::AutoLock lock(&state_lock_);
state_ = CameraState::Streaming;
}
void CameraClient::Close() {
fbl::AutoLock lock(&state_lock_);
state_ = CameraState::Closed;
// Kill the AutoWaiters:
cmd_msg_waiter_.Cancel();
buff_msg_waiter_.Cancel();
// close streams
vb_ch_.reset();
stream_ch_.reset();
}
void CameraClient::CloseAndNotify() {
Close();
OnShutdownCallback callback = nullptr;
{
fbl::AutoLock lock(&state_lock_);
if (client_shutdown_notifier_) {
callback = std::move(client_shutdown_notifier_);
}
}
if (callback) {
callback();
}
}
void CameraClient::SetConfigurationState(CameraState current_state,
CameraState next_state) {
fbl::AutoLock lock(&state_lock_);
// First check that we are still in the state we want to be in:
FXL_DCHECK(state_ == current_state)
<< "Unexpected state encountered. "
<< "Expected: " << (int)current_state
<< " current state: " << (int)state_
<< " This probably means the state changed during the function.";
state_ = next_state;
}
} // namespace simple_camera