blob: 5ec4cea978e9ce562d22af53d3a6adec4a4d3ea5 [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 "mpeg12_decoder.h"
#include "firmware_blob.h"
#include "macros.h"
#include "src/media/lib/memory_barriers/memory_barriers.h"
using MregSeqInfo = AvScratch4;
using MregPicInfo = AvScratch5;
using MregPicWidth = AvScratch6;
using MregPicHeight = AvScratch7;
// MregBufferIn is used to return buffers to the firmware.
using MregBufferIn = AvScratch8;
// MregBufferOut receives the index of the newest decoded frame from the
// firmware.
using MregBufferOut = AvScratch9;
using MregCmd = AvScratchA;
using MregCoMvStart = AvScratchB;
using MregErrorCount = AvScratchC;
// This is the byte offset within the compressed stream of the data used for the
// currently decoded frame. It can be used to find the PTS.
using MregFrameOffset = AvScratchD;
// MregWaitBuffer is 1 if the hardware is waiting for a buffer to be returned
// before decoding a new frame.
using MregWaitBuffer = AvScratchE;
using MregFatalError = AvScratchF;
Mpeg12Decoder::~Mpeg12Decoder() {
owner_->core()->StopDecoding();
owner_->core()->WaitForIdle();
BarrierBeforeRelease();
io_buffer_release(&workspace_buffer_);
}
void Mpeg12Decoder::ResetHardware() {
auto old_vld = PowerCtlVld::Get().ReadFrom(owner_->dosbus());
DosSwReset0::Get().FromValue((1 << 7) | (1 << 6) | (1 << 4)).WriteTo(owner_->dosbus());
DosSwReset0::Get().FromValue(0).WriteTo(owner_->dosbus());
// Reads are used to give the hardware time to finish the operation.
for (uint32_t i = 0; i < 3; i++) {
DosSwReset0::Get().ReadFrom(owner_->dosbus());
}
DosSwReset0::Get().FromValue((1 << 7) | (1 << 6) | (1 << 4)).WriteTo(owner_->dosbus());
DosSwReset0::Get().FromValue(0).WriteTo(owner_->dosbus());
DosSwReset0::Get().FromValue((1 << 9) | (1 << 8)).WriteTo(owner_->dosbus());
DosSwReset0::Get().FromValue(0).WriteTo(owner_->dosbus());
// Reads are used to give the hardware time to finish the operation.
for (uint32_t i = 0; i < 3; i++) {
DosSwReset0::Get().ReadFrom(owner_->dosbus());
}
MdecSwReset::Get().FromValue(1 << 7).WriteTo(owner_->dosbus());
MdecSwReset::Get().FromValue(0).WriteTo(owner_->dosbus());
old_vld.WriteTo(owner_->dosbus());
}
void Mpeg12Decoder::InitializedFrames(std::vector<CodecFrame> frames, uint32_t width,
uint32_t height, uint32_t stride) {}
zx_status_t Mpeg12Decoder::Initialize() {
uint8_t* data;
uint32_t firmware_size;
zx_status_t status =
owner_->SetProtected(VideoDecoder::Owner::ProtectableHardwareUnit::kVdec, false);
if (status != ZX_OK)
return status;
status = owner_->firmware_blob()->GetFirmwareData(FirmwareBlob::FirmwareType::kDec_Mpeg12, &data,
&firmware_size);
if (status != ZX_OK)
return status;
status = owner_->core()->LoadFirmware(data, firmware_size);
if (status != ZX_OK)
return status;
ResetHardware();
status = InitializeVideoBuffers();
if (status != ZX_OK)
return status;
enum { kWorkspaceSize = 2 * (1 << 16) }; // 128 kB
status = io_buffer_init(&workspace_buffer_, owner_->bti()->get(), kWorkspaceSize,
IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Failed to make workspace buffer");
return status;
}
io_buffer_cache_flush(&workspace_buffer_, 0, kWorkspaceSize);
BarrierAfterFlush();
// The first part of the workspace buffer is used for the CC buffer, which
// stores metadata that was encoded in the stream.
enum { kCcBufSize = 5 * 1024 };
MregCoMvStart::Get()
.FromValue(truncate_to_32(io_buffer_phys(&workspace_buffer_)) + kCcBufSize)
.WriteTo(owner_->dosbus());
Mpeg12Reg::Get().FromValue(0).WriteTo(owner_->dosbus());
PscaleCtrl::Get().FromValue(0).WriteTo(owner_->dosbus());
PicHeadInfo::Get().FromValue(0x380).WriteTo(owner_->dosbus());
M4ControlReg::Get().FromValue(0).WriteTo(owner_->dosbus());
VdecAssistMbox1ClrReg::Get().FromValue(1).WriteTo(owner_->dosbus());
MregBufferIn::Get().FromValue(0).WriteTo(owner_->dosbus());
MregBufferOut::Get().FromValue(0).WriteTo(owner_->dosbus());
// This is the frame size if it's known, or 0 otherwise.
MregCmd::Get().FromValue(0).WriteTo(owner_->dosbus());
MregErrorCount::Get().FromValue(0).WriteTo(owner_->dosbus());
MregFatalError::Get().FromValue(0).WriteTo(owner_->dosbus());
MregWaitBuffer::Get().FromValue(0).WriteTo(owner_->dosbus());
MdecPicDcCtrl::Get().ReadFrom(owner_->dosbus()).set_nv12_output(true).WriteTo(owner_->dosbus());
owner_->core()->StartDecoding();
return ZX_OK;
}
// Firmware assumes 8 output buffers.
constexpr uint32_t kBuffers = 8;
// Maximum MPEG2 values
constexpr uint32_t kMaxWidth = 1920;
constexpr uint32_t kMaxHeight = 1152;
void Mpeg12Decoder::HandleInterrupt() {
VdecAssistMbox1ClrReg::Get().FromValue(1).WriteTo(owner_->dosbus());
auto bufferout = MregBufferOut::Get().ReadFrom(owner_->dosbus());
auto info = MregPicInfo::Get().ReadFrom(owner_->dosbus());
auto offset = MregFrameOffset::Get().ReadFrom(owner_->dosbus());
// Assume frame is progressive.
uint32_t index = ((bufferout.reg_value() & 0xf) - 1) & (kBuffers - 1);
uint32_t width = MregPicWidth::Get().ReadFrom(owner_->dosbus()).reg_value();
uint32_t height = MregPicHeight::Get().ReadFrom(owner_->dosbus()).reg_value();
DLOG("Received buffer index: %d info: %x, offset: %x, width: %d, height: %d\n", index,
info.reg_value(), offset.reg_value(), width, height);
uint32_t coded_width = std::min(width, kMaxWidth);
uint32_t coded_height = std::min(height, kMaxHeight);
auto& frame = video_frames_[index].frame;
frame->hw_width = coded_width;
frame->coded_width = coded_width;
frame->display_width = coded_width;
frame->hw_height = coded_height;
frame->coded_height = coded_height;
frame->display_height = coded_height;
client_->OnFrameReady(frame);
MregBufferOut::Get().FromValue(0).WriteTo(owner_->dosbus());
// Some returned frames may have been buffered up earlier, so try to return
// them now that the firmware had a chance to do some work.
TryReturnFrames();
if (AvScratchM::Get().ReadFrom(owner_->dosbus()).reg_value() & (1 << 16)) {
DLOG("ccbuf has new data");
}
}
void Mpeg12Decoder::ReturnFrame(std::shared_ptr<VideoFrame> video_frame) {
returned_frames_.push_back(video_frame);
TryReturnFrames();
}
void Mpeg12Decoder::TryReturnFrames() {
while (!returned_frames_.empty()) {
std::shared_ptr<VideoFrame> frame = returned_frames_.back();
assert(frame->index < video_frames_.size());
assert(video_frames_[frame->index].frame == frame);
// Return buffer to decoder.
if (MregBufferIn::Get().ReadFrom(owner_->dosbus()).reg_value() == 0) {
MregBufferIn::Get().FromValue(frame->index + 1).WriteTo(owner_->dosbus());
} else {
// No return slots are free, so give up for now.
return;
}
returned_frames_.pop_back();
}
}
zx_status_t Mpeg12Decoder::InitializeVideoBuffers() {
for (uint32_t i = 0; i < kBuffers; i++) {
// These have to be allocated before the size of the video is known, so
// they have to be big enough to contain every possible video.
size_t buffer_size = kMaxWidth * kMaxHeight * 3 / 2;
auto frame = std::make_unique<VideoFrame>();
zx_status_t status = io_buffer_init(&frame->buffer, owner_->bti()->get(), buffer_size,
IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Failed to make frame: %d", status);
return status;
}
frame->stride = kMaxWidth;
frame->uv_plane_offset = kMaxWidth * kMaxHeight;
frame->index = i;
io_buffer_cache_flush(&frame->buffer, 0, buffer_size);
auto y_canvas = owner_->ConfigureCanvas(&frame->buffer, 0, frame->stride, kMaxHeight, 0, 0);
auto uv_canvas = owner_->ConfigureCanvas(&frame->buffer, frame->uv_plane_offset, frame->stride,
kMaxHeight / 2, 0, 0);
if (!y_canvas || !uv_canvas) {
DECODE_ERROR("Failed to allocate canvases");
return ZX_ERR_NO_MEMORY;
}
AvScratch::Get(i)
.FromValue(y_canvas->index() | (uv_canvas->index() << 8) | (uv_canvas->index() << 16))
.WriteTo(owner_->dosbus());
video_frames_.push_back({std::move(frame), std::move(y_canvas), std::move(uv_canvas)});
}
BarrierAfterFlush();
return ZX_OK;
}