blob: 9eeee951535b5b7c600d5833ed65b8e1aeefd2c0 [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 <ddk/debug.h>
#include <string.h>
#include <lib/zx/vmar.h>
#include "video-buffer.h"
namespace video {
namespace usb {
VideoBuffer::~VideoBuffer() {
if (virt_ != nullptr) {
zx::vmar::root_self().unmap(reinterpret_cast<uintptr_t>(virt_), size_);
}
}
zx_status_t VideoBuffer::Create(zx::vmo&& vmo,
fbl::unique_ptr<VideoBuffer>* out,
uint32_t max_frame_size) {
if (!vmo.is_valid()) {
zxlogf(ERROR, "invalid buffer handle\n");
return ZX_ERR_BAD_HANDLE;
}
uint64_t size;
zx_status_t status = vmo.get_size(&size);
if (status != ZX_OK) {
zxlogf(ERROR, "could not get vmo size, err: %d\n", status);
return status;
}
void* virt;
uint32_t flags = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
status = zx::vmar::root_self().map(0u, vmo,
0u, size,
flags, reinterpret_cast<uintptr_t*>(&virt));
if (status != ZX_OK) {
zxlogf(ERROR, "failed to map VMO, got error: %d\n", status);
return status;
}
fbl::AllocChecker ac;
fbl::unique_ptr<VideoBuffer> res(
new (&ac) VideoBuffer(std::move(vmo), size, virt));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
status = res->Alloc(max_frame_size);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to init video buffer, err: %d\n", status);
return status;
}
res->Init();
*out = std::move(res);
return ZX_OK;
}
zx_status_t VideoBuffer::Alloc(uint32_t max_frame_size) {
if (max_frame_size == 0) {
return ZX_ERR_INVALID_ARGS;
}
uint64_t num_frames = size() / max_frame_size;
zxlogf(TRACE, "buffer size: %lu, num_frames: %lu\n", size(), num_frames);
fbl::AllocChecker ac;
free_frames_.reserve(num_frames, &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
locked_frames_.reserve(num_frames, &ac);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
for (uint64_t i = 0; i < num_frames; ++i) {
free_frames_.push_back(i * max_frame_size);
}
return ZX_OK;
}
void VideoBuffer::Init() {
if (has_in_progress_frame_) {
free_frames_.push_back(in_progress_frame_);
has_in_progress_frame_ = false;
}
for (size_t n = locked_frames_.size(); n > 0; --n) {
free_frames_.push_back(locked_frames_.erase(n - 1));
}
// Zero out the buffer.
memset(virt_, 0, size_);
}
zx_status_t VideoBuffer::GetNewFrame(FrameOffset* out_offset) {
if (out_offset == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
if (has_in_progress_frame_) {
zxlogf(ERROR, "GetNewFrame failed, already writing to frame at offset: %lu\n",
in_progress_frame_);
return ZX_ERR_BAD_STATE;
}
if (free_frames_.is_empty()) {
return ZX_ERR_NOT_FOUND;
}
size_t last = free_frames_.size() - 1;
in_progress_frame_ = free_frames_.erase(last);
has_in_progress_frame_ = true;
*out_offset = in_progress_frame_;
return ZX_OK;
}
zx_status_t VideoBuffer::FrameCompleted() {
if (!has_in_progress_frame_) {
zxlogf(ERROR, "FrameCompleted failed, no frame is currently in progress\n");
return ZX_ERR_BAD_STATE;
}
locked_frames_.push_back(in_progress_frame_);
has_in_progress_frame_ = false;
return ZX_OK;
}
zx_status_t VideoBuffer::FrameRelease(FrameOffset req_frame_offset) {
size_t i = 0;
for (auto& locked_offset : locked_frames_) {
if (req_frame_offset == locked_offset) {
free_frames_.push_back(locked_frames_.erase(i));
return ZX_OK;
}
i++;
}
zxlogf(ERROR, "frame with offset %ld not found in free frames list\n",
req_frame_offset);
return ZX_ERR_NOT_FOUND;
}
} // namespace usb
} // namespace video