1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <ddk/debug.h>
6#include <string.h>
7#include <lib/zx/vmar.h>
8
9#include "video-buffer.h"
10
11namespace video {
12namespace usb {
13
14VideoBuffer::~VideoBuffer() {
15    if (virt_ != nullptr) {
16        zx::vmar::root_self().unmap(reinterpret_cast<uintptr_t>(virt_), size_);
17    }
18}
19
20zx_status_t VideoBuffer::Create(zx::vmo&& vmo,
21                                fbl::unique_ptr<VideoBuffer>* out,
22                                uint32_t max_frame_size) {
23    if (!vmo.is_valid()) {
24        zxlogf(ERROR, "invalid buffer handle\n");
25        return ZX_ERR_BAD_HANDLE;
26    }
27
28    uint64_t size;
29    zx_status_t status = vmo.get_size(&size);
30    if (status != ZX_OK) {
31        zxlogf(ERROR, "could not get vmo size, err: %d\n", status);
32        return status;
33    }
34
35    void* virt;
36    uint32_t flags =  ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
37    status = zx::vmar::root_self().map(0u, vmo,
38                                       0u, size,
39                                       flags, reinterpret_cast<uintptr_t*>(&virt));
40
41    if (status != ZX_OK) {
42        zxlogf(ERROR, "failed to map VMO, got error: %d\n", status);
43        return status;
44    }
45
46    fbl::AllocChecker ac;
47    fbl::unique_ptr<VideoBuffer> res(
48        new (&ac) VideoBuffer(fbl::move(vmo), size, virt));
49    if (!ac.check()) {
50        return ZX_ERR_NO_MEMORY;
51    }
52
53    status = res->Alloc(max_frame_size);
54    if (status != ZX_OK) {
55        zxlogf(ERROR, "failed to init video buffer, err: %d\n", status);
56        return status;
57    }
58
59    res->Init();
60
61    *out = fbl::move(res);
62    return ZX_OK;
63}
64
65zx_status_t VideoBuffer::Alloc(uint32_t max_frame_size) {
66    if (max_frame_size == 0) {
67        return ZX_ERR_INVALID_ARGS;
68    }
69    uint64_t num_frames = size() / max_frame_size;
70    zxlogf(TRACE, "buffer size: %lu, num_frames: %lu\n", size(), num_frames);
71
72    fbl::AllocChecker ac;
73    free_frames_.reserve(num_frames, &ac);
74    if (!ac.check()) {
75        return ZX_ERR_NO_MEMORY;
76    }
77    locked_frames_.reserve(num_frames, &ac);
78    if (!ac.check()) {
79        return ZX_ERR_NO_MEMORY;
80    }
81
82    for (uint64_t i = 0; i < num_frames; ++i) {
83        free_frames_.push_back(i * max_frame_size);
84    }
85    return ZX_OK;
86}
87
88void VideoBuffer::Init() {
89    if (has_in_progress_frame_) {
90        free_frames_.push_back(in_progress_frame_);
91        has_in_progress_frame_ = false;
92    }
93    for (size_t n = locked_frames_.size(); n > 0; --n) {
94        free_frames_.push_back(locked_frames_.erase(n - 1));
95    }
96    // Zero out the buffer.
97    memset(virt_, 0, size_);
98}
99
100zx_status_t VideoBuffer::GetNewFrame(FrameOffset* out_offset) {
101    if (out_offset == nullptr) {
102        return ZX_ERR_INVALID_ARGS;
103    }
104    if (has_in_progress_frame_) {
105        zxlogf(ERROR, "GetNewFrame failed, already writing to frame at offset: %lu\n",
106               in_progress_frame_);
107        return ZX_ERR_BAD_STATE;
108    }
109    if (free_frames_.is_empty()) {
110        return ZX_ERR_NOT_FOUND;
111    }
112    size_t last = free_frames_.size() - 1;
113    in_progress_frame_ = free_frames_.erase(last);
114    has_in_progress_frame_ = true;
115    *out_offset = in_progress_frame_;
116    return ZX_OK;
117}
118
119zx_status_t VideoBuffer::FrameCompleted() {
120    if (!has_in_progress_frame_) {
121        zxlogf(ERROR, "FrameCompleted failed, no frame is currently in progress\n");
122        return ZX_ERR_BAD_STATE;
123    }
124    locked_frames_.push_back(in_progress_frame_);
125    has_in_progress_frame_ = false;
126    return ZX_OK;
127}
128
129zx_status_t VideoBuffer::FrameRelease(FrameOffset req_frame_offset) {
130    size_t i = 0;
131    for (auto& locked_offset : locked_frames_) {
132        if (req_frame_offset == locked_offset) {
133            free_frames_.push_back(locked_frames_.erase(i));
134            return ZX_OK;
135        }
136        i++;
137    }
138    zxlogf(ERROR, "frame with offset %ld not found in free frames list\n",
139           req_frame_offset);
140    return ZX_ERR_NOT_FOUND;
141}
142
143} // namespace usb
144} // namespace video
145