blob: 6f3a17ffd9c532ed94b29a397c954e51ee66bdff [file] [log] [blame]
// Copyright 2022 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.
#ifndef SRC_GRAPHICS_DISPLAY_DRIVERS_AMLOGIC_DISPLAY_RDMA_H_
#define SRC_GRAPHICS_DISPLAY_DRIVERS_AMLOGIC_DISPLAY_RDMA_H_
#include <fidl/fuchsia.hardware.platform.device/cpp/wire.h>
#include <lib/async/cpp/irq.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/mmio/mmio-buffer.h>
#include <lib/zx/bti.h>
#include <lib/zx/interrupt.h>
#include <lib/zx/pmt.h>
#include <lib/zx/result.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <zircon/compiler.h>
#include <zircon/syscalls/port.h>
#include <zircon/types.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include "src/graphics/display/lib/api-types-cpp/config-stamp.h"
#include "src/graphics/display/lib/driver-framework-migration-utils/dispatcher/dispatcher-factory.h"
#include "src/graphics/display/lib/driver-framework-migration-utils/dispatcher/dispatcher.h"
namespace amlogic_display {
struct RdmaTable {
uint32_t reg;
uint32_t val;
};
/*
* This is the RDMA table index. Each index points to a specific VPU register.
* RDMA engine will be programmed to update all those registers at vsync time.
* Since all the fields will be updated at vsync time, we need to make sure all
* the fields are updated with a valid value when FlipOnVsync is called.
*/
enum {
IDX_BLK0_CFG_W0,
IDX_CTRL_STAT,
IDX_CTRL_STAT2,
IDX_MATRIX_COEF00_01,
IDX_MATRIX_COEF02_10,
IDX_MATRIX_COEF11_12,
IDX_MATRIX_COEF20_21,
IDX_MATRIX_COEF22,
IDX_MATRIX_OFFSET0_1,
IDX_MATRIX_OFFSET2,
IDX_MATRIX_PRE_OFFSET0_1,
IDX_MATRIX_PRE_OFFSET2,
IDX_MATRIX_EN_CTRL,
IDX_BLK2_CFG_W4,
IDX_MALI_UNPACK_CTRL,
IDX_PATH_MISC_CTRL,
IDX_AFBC_HEAD_BUF_ADDR_LOW,
IDX_AFBC_HEAD_BUF_ADDR_HIGH,
IDX_AFBC_SURFACE_CFG,
IDX_RDMA_CFG_STAMP_HIGH,
IDX_RDMA_CFG_STAMP_LOW,
IDX_MAX,
};
static_assert(IDX_RDMA_CFG_STAMP_HIGH == (IDX_MAX - 2), "Invalid RDMA Index Table");
// This should be the last element to make sure the entire config
// was written
static_assert(IDX_RDMA_CFG_STAMP_LOW == (IDX_MAX - 1), "Invalid RDMA Index Table");
// Table size for non-AFBC RDMA
constexpr size_t kTableSize = (IDX_MAX * sizeof(RdmaTable));
// Single element table for AFBC (ARM Frame Buffer Compression) RDMA
constexpr size_t kAfbcTableSize = sizeof(RdmaTable);
// Non-AFBC RDMA Region size
constexpr size_t kRdmaRegionSize = ZX_PAGE_SIZE;
// AFBC RDMA Region Size
constexpr size_t kAfbcRdmaRegionSize = ZX_PAGE_SIZE;
// Arbitrarily limit table size to maximum 16
constexpr uint32_t kNumberOfTables = std::min(16ul, (kRdmaRegionSize / (kTableSize)));
// We should have space for at least 3 tables. If RDMA table has grown too large and cannot
// fit more than 3 tables within a PAGE_SIZE, we need to either:
// - Re-evaluate why RDMA table has grown so large
// - Create a larger RDMA table allocation
static_assert(kNumberOfTables >= 3, "RDMA table is too large");
// This value indicates an available entry into the RDMA table
constexpr uint64_t kRdmaTableReady = UINT64_MAX - 1;
// An entry is marked as unavailable temporarily when there are unapplied configs. This will ensure
// new configs are added to end of table since RDMA requires physical contiguous entries
constexpr uint64_t kRdmaTableUnavailable = UINT64_MAX;
// RDMA Channel 1 is used to track the application of image layers from queued configs to the
// display hardware
constexpr uint8_t kRdmaChannel = 1;
// RDMA Channel 7 will be dedicated to AFBC Trigger
constexpr uint8_t kAfbcRdmaChannel = 7;
struct RdmaChannelContainer {
zx_paddr_t phys_offset; // offset into physical address
uint8_t* virt_offset; // offset into virtual address (vmar buf)
};
/*
* RDMA Operation Design (non-AFBC):
* Allocate kRdmaRegionSize of physical contiguous memory. This region will include
* kNumberOfTables of RDMA Tables.
* RDMA Tables will get populated with <reg><val> pairs. The last element will be a unique
* stamp for a given configuration. The stamp is used to verify how far the RDMA channel was able
* to write at vsync time.
* _______________
* |<reg><val> |
* |<reg><val> |
* |... |
* |<Config Stamp> |
* |_______________|
* |<reg><val> |
* |<reg><val> |
* |... |
* |<Config Stamp> |
* |_______________|
* .
* .
* .
* |<reg><val> |
* |<reg><val> |
* |... |
* |<Config Stamp> |
* |_______________|
* |<reg><val> |
* |<reg><val> |
* |... |
* |<Config Stamp> |
* |_______________|
*
* The physical and virtual addresses of the above tables are stored in rdma_chnl_container_
*
* Each table contains a specific configuration to be applied at Vsync time. RDMA is programmed
* to read from [start_index_used_ end_index_used_] inclusive. If more than one configuration is
* applied within a vsync period, the new configuration will get added at the
* next sequential index and RDMA end_index_used_ will get updated.
*
* rdma_usage_table_ is used to keep track of tables being used by RDMA. rdma_usage_table_ may
* contain three possible values:
* kRdmaTableReady: This index may be used by RDMA
* kRdmaTableUnavailable: This index is unavailble
* <config stamp>: This index includes a valid config. The stored value corresponds to the first
* image handle that is contained in the config (we currently assume 1 image per
* config).
*
* The client of the Osd instance is expected to call Osd::GetLastConfigStampApplied() on every
* vsync interrupt to obtain the most recently applied config. This method checks if a previously
* scheduled RDMA (via Osd::FlipOnVsync) has completed, and if so, checks how far the RDMA was able
* to write by comparing the "Config Stamp" in a scratch register to rdma_usage_table_. If RDMA did
* not apply all the configs, it will re-schedule a new RDMA transaction.
*/
class RdmaEngine {
public:
// Factory method intended for production use.
//
// `platform_device` must be valid.
//
// `video_input_unit_node` must outlive the RdmaEngine instance.
static zx::result<std::unique_ptr<RdmaEngine>> Create(
display::DispatcherFactory& dispatcher_factory,
fidl::UnownedClientEnd<fuchsia_hardware_platform_device::Device> platform_device,
inspect::Node* video_input_unit_node);
// Production code should prefer the `Create()` factory method.
//
// `vpu_mmio` is the region documented as "VPU" in Section 8.1 "Memory Map"
// of the AMLogic A311D datasheet. It must be valid.
//
// `dma_bti` maps to the DMA BTI board resource. It must be valid.
//
// `rdma_done_interrupt` is the interrupt documented as "rdma_done_int" in
// Section 8.10.2 "Interrupt Source" of the AMLogic A311D datasheet. It must
// be valid.
//
// `irq_handler_dispatcher` must not be null.
//
// `node` must outlive the RdmaEngine.
RdmaEngine(fdf::MmioBuffer vpu_mmio, zx::bti dma_bti, zx::interrupt rdma_done_interrupt,
std::unique_ptr<display::Dispatcher> irq_handler_dispatcher, inspect::Node* node);
// This must be called before any other methods.
zx_status_t SetupRdma();
// Drop all hardware resources prior to destruction.
void Release();
void StopRdma();
void ResetRdmaTable();
void SetRdmaTableValue(uint32_t table_index, uint32_t idx, uint32_t val);
void FlushRdmaTable(uint32_t table_index);
void ExecRdmaTable(uint32_t next_table_idx, display::ConfigStamp config_stamp, bool use_afbc);
int GetNextAvailableRdmaTableIndex() __TA_EXCLUDES(rdma_lock_);
display::ConfigStamp GetLastConfigStampApplied() __TA_EXCLUDES(rdma_lock_);
void ResetConfigStamp(display::ConfigStamp config_stamp) __TA_EXCLUDES(rdma_lock_);
// The following functions move the current RDMA state machine forward. If TryResolvePendingRdma
// determines that RDMA has completed, it
// - records the image handle of the most recently applied config based on scratch register
// content,
// - updates the RDMA usage table and reschedules RDMA for remaining configs that the RDMA
// engine has not applied,
// - writes to the RDMA control registers to clear and/or reschedule the RDMA interrupts.
//
// This method must be called when RDMA is active.
void TryResolvePendingRdma() __TA_REQUIRES(rdma_lock_);
void ProcessRdmaUsageTable() __TA_REQUIRES(rdma_lock_);
void SetAfbcRdmaTableValue(uint32_t val) const;
void FlushAfbcRdmaTable() const;
int RdmaIrqThread() __TA_EXCLUDES(rdma_lock_);
void DumpLocked() __TA_REQUIRES(rdma_lock_);
void DumpRdmaRegisters();
void DumpRdmaState() __TA_REQUIRES(rdma_lock_);
private:
zx::result<> InitializeIrqHandler();
void InterruptHandler(async_dispatcher_t* dispatcher, async::IrqBase* irq, zx_status_t status,
const zx_packet_interrupt_t* interrupt);
void OnTransactionFinished();
fdf::MmioBuffer vpu_mmio_;
zx::bti bti_;
// RDMA IRQ handle used for diagnostic purposes.
zx::interrupt rdma_irq_;
std::unique_ptr<display::Dispatcher> rdma_irq_handler_dispatcher_;
async::IrqMethod<RdmaEngine, &RdmaEngine::InterruptHandler> rdma_irq_handler_{this};
fbl::Mutex rdma_lock_;
uint64_t rdma_usage_table_[kNumberOfTables] __TA_GUARDED(rdma_lock_);
size_t start_index_used_ __TA_GUARDED(rdma_lock_) = 0;
size_t end_index_used_ __TA_GUARDED(rdma_lock_) = 0;
bool rdma_active_ __TA_GUARDED(rdma_lock_) = false;
display::ConfigStamp latest_applied_config_ __TA_GUARDED(rdma_lock_) =
display::kInvalidConfigStamp;
RdmaChannelContainer rdma_channels_[kNumberOfTables];
// use a single vmo for all channels
zx::vmo rdma_vmo_;
zx::pmt rdma_pmt_;
// Container that holds AFBC specific trigger register
RdmaChannelContainer afbc_rdma_channel_;
zx::vmo afbc_rdma_vmo_;
zx::pmt afbc_rdma_pmt_;
inspect::UintProperty rdma_allocation_failures_;
inspect::UintProperty rdma_irq_count_;
inspect::UintProperty rdma_begin_count_;
inspect::UintProperty rdma_pending_in_vsync_count_;
inspect::UintProperty last_rdma_pending_in_vsync_interval_ns_;
inspect::UintProperty last_rdma_pending_in_vsync_timestamp_ns_prop_;
zx::time last_rdma_pending_in_vsync_timestamp_;
};
} // namespace amlogic_display
#endif // SRC_GRAPHICS_DISPLAY_DRIVERS_AMLOGIC_DISPLAY_RDMA_H_