Venus驱动框架
Venus是QCOM的编解码驱动,用于对camera输出的视频流进行编码传输或解码处理
为什么Venus被设计成v4l2_m2m(Memory-to-Memory)设备呢?
因为它的核心功能是处理视频流,而非对输入设备的流控
- Camss (Camera Sensor to Memory) :是 数据源 。它负责与物理世界交互,从 Sensor 拉取光信号数据,经过 ISP 处理,把数据"推"进内存。它是数据流的 起点 。
- Venus (Memory to Memory) :是 处理中心 。它不直接连接 Sensor,而是从内存中拿 YUV 数据,编码成 H.264/HEVC,再把数据"吐"回内存。它既不是绝对的源头,也不是绝对的终点,而是中间的 转换器 。
硬件方面
输入:从系统内存(DDR)读取 YUV 原始图像
输出:将编码后的 H.264/H.265 码流写回系统内存
从内存中来,再回到内存
软件架构方面
输入数据灵活:YUV 数据可能来自 Camss,也可能来自 GPU 渲染的屏幕录制,或者是文件解码后的帧
输出数据灵活::编码后的数据可能写入文件,也可能通过网络发送
M2M设备的优势:用户空间程序可以先 read() 原始数据喂给它,或者直接在内核态通过 poll() 和 dqbuf 进行高速流转,而不需要关心底层硬件差异
资源管理方面
M2M 架构允许 Venus 作为一个独立的服务运行。它可以独立于 Camera 驱动进行电源管理(Runtime PM),独立创建会话
使得多个进程可以并发请求编码服务,虽然通常硬件只允许一个实例运行,但驱动层保留了这种多实例管理的灵活性
Venus和Camss的区别总结:
| 特性维度 | Camss (Camera Subsystem) | Venus (Encoder/Decoder) |
|---|---|---|
| V4L2 设备类型 | 直连设备 (通常为 Capture) | Memory-to-Memory (M2M) |
| 数据流向 | Sensor -> 内存 (单向输入) | 内存 <-> 内存 (双向:读取输入,写入输出) |
| 硬件接口 | CSI-2 接口 (连接物理 Sensor) | AXI 总线 (连接系统内存 DDR) |
| 控制逻辑 | 强时序驱动:必须严格控制 Sensor 的 VSYNC/HSYNC 时序。 | 事件驱动:只要有数据就处理,没有数据就休眠。 |
| Buffer 管理 | 通常由驱动/ISP 硬件决定 Buffer 大小和数量。 | 输入输出 Buffer 均由用户空间协商,非常灵活。 |
| 典型 IOCTL | VIDIOC_STREAMON后,硬件自动开始 DMA。 |
VIDIOC_STREAMON后,需手动 QBUF输入数据,硬件才开始工作。 |
| 代码体现 | 在 camss.c中直接操作 PHY/VFE 寄存器。 |
在 venc.c中通过 HFI (Hexagon Firmware Interface) 发送命令给固件。 |
Venus代码架构
venus负责视频编解码硬件的驱动,分为核心、固件、HFI 通信、调试、电源管理、解码/编码等模块,驱动源代码文件架构:
core.c / core.h:
venus 驱动核心,负责整体初始化、资源管理。
firmware.c / firmware.h:
固件加载与管理。
helpers.c / helpers.h:
辅助函数,简化硬件操作。
hfi_*:
HFI(Host Firmware Interface)相关文件,负责与硬件固件通信,包括命令、消息、解析、平台相关缓冲区等。
hfi_platform*.c / hfi_platform*.h:
不同硬件平台的 HFI 支持。
hfi_venus*.c / hfi_venus*.h:
venus 特定的 HFI 实现。
dbgfs.c / dbgfs.h:
调试文件系统支持,便于调试和状态查看。
pm_helpers.c / pm_helpers.h:
电源管理辅助。
vdec.c / vdec.h / vdec_ctrls.c:
视频解码器实现及控制接口。
venc.c / venc.h / venc_ctrls.c:
视频编码器实现及控制接口。
Kconfig / Makefile:
venus 子模块的配置和编译规则。
Core
core.c不负责直接具体的编码工作,主要做资源申请,电源时钟管理,中断处理以及初始化子设备的工作
主要数据结构
struct venus_core是资源池的设计概念,它不处理具体的帧数据,帧数据由venus_inst和硬件做的,主要持有所有实例共享的物理资源,如clock,memory,irq等,并维护着与固件通信的HFI通道
c
// venus设备核心控制器
struct venus_core {
// --- 硬件寄存器映射 ---
void __iomem *base; // 硬件寄存器基地址
void __iomem *vbif_base; // VBIF (总线接口) 寄存器
void __iomem *cpu_base; // CPU 配置寄存器
void __iomem *cpu_cs_base; // 特定的寄存器基地址
void __iomem *cpu_ic_base;
void __iomem *wrapper_base;
void __iomem *wrapper_tz_base;
void __iomem *aon_base;
int irq; // 中断号
// --- 时钟与电源 ---
struct clk *clks[VIDC_CLKS_NUM_MAX]; // 通用时钟
struct clk *vcodec0_clks[VIDC_VCODEC_CLKS_NUM_MAX]; // 编解码核心0的时钟
struct clk *vcodec1_clks[VIDC_VCODEC_CLKS_NUM_MAX]; // 编解码核心1的时钟
struct icc_path *video_path; // 视频总线路径 (用于申请 DDR 带宽)
struct icc_path *cpucfg_path; // CPU配置带宽路径
bool has_opp_table; // 是否支持 OPP (Operating Performance Points)
struct dev_pm_domain_list *pmdomains; // 电源域设备列表
struct device_link *opp_dl_venus;
struct device *opp_pmdomain;
struct reset_control *resets[VIDC_RESETS_NUM_MAX]; // 复位控制器,用于硬重启模块
// --- 设备模型 ---
struct video_device *vdev_dec; // V4L2 视频设备节点 (Decoder)
struct video_device *vdev_enc; // V4L2 视频设备节点 (Encoder)
struct v4l2_device v4l2_dev; // V4L2 设备结构体,用于设备管理和日志
// --- 资源和状态 ---
const struct venus_resources *res; // 指向硬件资源表,包括频率表、带宽表
struct device *dev; // 父设备指针
struct device *dev_dec; // 解码器运行时设备
struct device *dev_enc; // 编码器运行时设备
unsigned int use_tz; // 是否使用 TrustZone
struct video_firmware { // 固件相关信息 (加载后的内存地址等)
struct device *dev;
struct iommu_domain *iommu_domain;
size_t mapped_mem_size;
phys_addr_t mem_phys; // 内存物理地址
size_t mem_size; // 内存尺寸
} fw;
// --- 并发与同步 ---
struct mutex lock; // 全局互斥锁,保护 Core 数据结构的并发访问
struct list_head instances; // 实例链表头,挂载所有正在运行的 venus_inst
atomic_t insts_count; // 当前活跃的实例计数
unsigned int state; // Core 当前状态 (INIT, ACTIVE, SUSPEND 等)
struct completion done; // 用于同步 HFI (硬件-固件接口) 命令的完成量
unsigned int error; // 最后一次 HFI 错误码
unsigned long sys_error; // 系统错误标志 (用于标记致命错误)
wait_queue_head_t sys_err_done; // 等待系统错误恢复的队列
const struct hfi_core_ops *core_ops; // 核心操作函数集 (错误处理、事件通知)
const struct venus_pm_ops *pm_ops; // 电源管理操作函数集
struct mutex pm_lock; // PM 操作锁
unsigned long enc_codecs; // 位图:支持的编码器格式 (H.264, HEVC 等)
unsigned long dec_codecs; // 位图:支持的解码器格式
unsigned int max_sessions_supported; // 最大会话数
// --- HFI (硬件-固件接口) 管理 ---
// Venus 依赖固件 (Firmware) 运行,驱动host只负责调度,复杂算法由固件在专用核心上运行,venus_core 负责管理与固件的通信
void *priv; // HFI 私有数据 ,通常是 HFI 上下文结构体指针
const struct hfi_ops *ops; // HFI 操作函数指针,发送命令、读取消息
struct delayed_work work; // 延迟工作队列,用于处理系统错误恢复 (SSR)
struct hfi_plat_caps caps[MAX_CODEC_NUM]; // 平台能力表,查询到的硬件支持格式
unsigned int codecs_count; // 编解码器数量
unsigned int core0_usage_count; // 核心0 使用计数
unsigned int core1_usage_count; // 核心1 使用计数
// --- 调试 ---
struct dentry *root; // Debugfs 根目录
struct venus_img_version { // 固件版本号
u32 major;
u32 minor;
u32 rev;
} venus_ver;
unsigned long dump_core; // 核心转储标志
};
struct venus_resources是Venus驱动的硬件抽象层配置表,用来屏蔽不同soc之间的硬件差异
c
// 硬件资源描述
// venus_resources定义了不同高通芯片上的Venus硬件差异性参数
struct venus_resources {
// DMA 地址掩码,定义硬件能访问的物理内存范围
u64 dma_mask;
// 频率表,定义不同负载下所需的 Clock 频率
const struct freq_tbl *freq_tbl;
unsigned int freq_tbl_size;
// 带宽表,定义编码/解码时所需的总线带宽
const struct bw_tbl *bw_tbl_enc;
unsigned int bw_tbl_enc_size;
const struct bw_tbl *bw_tbl_dec;
unsigned int bw_tbl_dec_size;
// 寄存器预设值,某些硬件需要在启动时写入特定的寄存器值
const struct reg_val *reg_tbl;
unsigned int reg_tbl_size;
// UBWC (Universal Bandwidth Compression) 配置,用于描述压缩格式的元数据配置
const struct hfi_ubwc_config *ubwc_conf;
// 时钟定义,列出该 SoC 上 Venus 所需的所有时钟名称
const char * const clks[VIDC_CLKS_NUM_MAX];
unsigned int clks_num;
// 不同版本 Venus 架构(Iris1, Iris2)特有的时钟和电源域
const char * const vcodec0_clks[VIDC_VCODEC_CLKS_NUM_MAX];
const char * const vcodec1_clks[VIDC_VCODEC_CLKS_NUM_MAX];
unsigned int vcodec_clks_num;
// 电源管理域
const char **vcodec_pmdomains;
unsigned int vcodec_pmdomains_num;
// 运行点域
const char **opp_pmdomain;
// 编解码核心数量
unsigned int vcodec_num;
const char * const resets[VIDC_RESETS_NUM_MAX];
unsigned int resets_num;
// HFI (固件接口) 版本与 VPU 版本,决定驱动与固件通信的协议版本
enum hfi_version hfi_version;
enum vpu_version vpu_version;
// 视频后处理管道数量
u8 num_vpp_pipes;
// 固件相关配置
u32 max_load; // 最大负载能力
unsigned int vmem_id; // 虚拟内存 ID
u32 vmem_size; // 内存大小
u32 vmem_addr; // 内存地址
u32 cp_start; // 安全区域起始
u32 cp_size; // 安全区域大小
u32 cp_nonpixel_start; // 非像素数据起始
u32 cp_nonpixel_size; // 非像素数据大小
const char *fwname; // 固件文件名
};
struct venus_inst的设计是为每一个打开的文件句柄分配一个独立的上下文,确保进程A的数据不会覆盖进程B的数据,每个实例都有自己独立的状态机,缓冲区队列和控制参数
在Linux v4l2 m2m框架下,驱动必须支持多实例并发,需要支持以下特性:
1.用户空间可以同事打开/dev/videox多次
2.进程1可能在进行1080p的H264解码
3.进程2可能在进行4k的HEVC编码
c
// 会话实例
// 是驱动的多实例对象。每当用户空间打开 /dev/video10 进行一次编码或解码任务时,内核就会创建一个 venus_inst。它是 V4L2 m2m(Memory-to-Memory)框架的核心承载者。
struct venus_inst {
struct list_head list; // 链表节点,用于将该实例挂载到 venus_core->instances 全局链表中
struct mutex lock; // 实例锁,保护本实例内部数据的并发访问
struct venus_core *core; // 指回全局的 venus_core,用于访问共享硬件资源
struct clock_data clk_data; // 时钟数据,当前实例所需的频率/带宽数据
// 缓冲区管理
struct list_head dpbbufs; // 解码图片缓冲区,存放解码后的 YUV 帧
struct list_head internalbufs; // 内部缓冲区,驱动或固件内部使用的辅助 Buffer,运动向量,元数据等
struct list_head registeredbufs; // 注册缓冲区,用户空间通过 VIDIOC_QBUF 注册的 Capture 端缓冲区
struct list_head delayed_process; // 延迟缓冲区,因资源不足或依赖关系暂时无法处理的缓冲区
struct work_struct delayed_process_work; // 用于处理上述延迟队列的内核工作队列
bool nonblock;
struct v4l2_ctrl_handler ctrl_handler; // V4L2 控制框架的句柄
union { // 控制参数联合体
struct vdec_controls dec;
struct venc_controls enc;
} controls;
struct v4l2_fh fh; // V4L2 标准结构,关联打开的文件描述符和事件处理
unsigned int streamon_cap, streamon_out; // 捕获/输出队列是否已启动 (STREAMON的情况下)
// 格式与分辨率
u32 width;
u32 height;
struct v4l2_rect crop; // 裁剪矩形,定义有效图像区域
u32 fw_min_cnt; // 固件要求的最小缓冲区数量
u32 out_width;
u32 out_height;
u32 colorspace;
u8 ycbcr_enc;
u8 quantization;
u8 xfer_func;
enum venus_dec_state codec_state; // 当前解码状态,如 INIT, DRAIN, FLUSH
enum venus_enc_state enc_state; // 当前编码状态
wait_queue_head_t reconf_wait; // 用户空间等待分辨率变化事件完成的队列
unsigned int subscriptions;
int buf_count; // 缓冲区计数
struct venus_ts_metadata tss[VIDEO_MAX_FRAME]; // 时间戳元数据数组,用于管理每一帧的 PTS/DTS
unsigned long payloads[VIDEO_MAX_FRAME]; // 缓存平面负载数据,用于动态计算时钟/带宽需求
u64 fps;
struct v4l2_fract timeperframe; // 每帧的时间,用于计算帧率
const struct venus_format *fmt_out; // 输出格式,指向输出端的格式定义,如H.264
const struct venus_format *fmt_cap; // 捕获格式,指向捕获端的格式定义,如NV12
unsigned int num_input_bufs; // 输入缓冲区数量
unsigned int num_output_bufs; // 输出缓冲区数量
unsigned int input_buf_size; // 输入缓冲区大小
unsigned int output_buf_size; // 输出缓冲区大小
unsigned int output2_buf_size; // 辅助输出缓冲区大小,可用于解码后元数据输出等用途
u32 dpb_buftype;
u32 dpb_fmt;
u32 opb_buftype;
u32 opb_fmt;
bool reconfig; // 解码器检测到分辨率变化,需要重新协商缓冲区
u32 hfi_codec; // hfi编码,固件识别的编码器ID
u32 sequence_cap; // 序列号,捕获队列的帧计数器
u32 sequence_out; // 序列号,输出队列的帧计数器
struct v4l2_m2m_dev *m2m_dev; // M2M设备,指向 V4L2 M2M 框架的设备指针
struct v4l2_m2m_ctx *m2m_ctx; // M2M上下文,管理输出队列和捕获队列的交互
struct mutex ctx_q_lock; // 队列锁,专门用于序列化视频设备 ioctl 调用,防止队列竞争
unsigned int state;
struct completion done;
unsigned int error;
bool session_error;
const struct hfi_inst_ops *ops; // HFI 操作集,定义如何处理 HFI 事件
u32 session_type; // 会话类型,标识是编码器还是解码器
union hfi_get_property hprop; // 属性获取,用于 get_property 操作的临时存储
unsigned int core_acquired: 1;
unsigned int bit_depth;
unsigned int pic_struct;
bool next_buf_last; // 标记下一个入队的缓冲区为最后一帧
bool drain_active;
enum venus_inst_modes flags; // 位掩码,描述实例的特定模式(如低延迟模式)
struct ida dpb_ids; // 用于管理 DPB 缓冲区的唯一 ID
};
当一个视频解码任务开始时,venus_inst的状态变化图
硬件/固件空间 Firmware
内核空间 Kernel Space
用户空间 User Space
Venus Driver
- open / ioctl
传递请求 - 创建/管理实例
- 申请资源 Clock/Power
- 发送命令 HFI_CMD
执行任务 - 返回响应 HFI_MSG
更新状态/事件 - 唤醒队列 DQBUF
返回数据
获取解码帧
应用程序 App / MediaCodec
V4L2 Framework
V4L2 M2M Framework
venus_inst 会话实例
venus_core 全局核心
HFI Interface
VPU Hardware
状态流转: INIT -> STREAMON -> RECONFIG/DRAIN
其他的一些重要数据结构
c
// Venus 硬件支持的像素格式和压缩格式
struct venus_format;
// 管理解码器运行时参数的控制结构体
struct vdec_controls;
// 管理编码器运行时参数的控制结构体
struct venc_controls;
// 封装视频帧缓冲区
struct venus_buffer
// 封装视频帧时间戳和同步元数据
struct venus_ts_metadata
驱动代码分析
平台驱动注册
c
static int venus_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev; // 获取设备结构体
struct venus_core *core;
int ret;
// 1. 分配核心数据结构内存
core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL);
if (!core)
return -ENOMEM;
core->dev = dev;
// 2. 资源映射与获取
// 2.1 映射硬件寄存器 (MMIO)
core->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(core->base))
return PTR_ERR(core->base);
// 2.2 获取互连路径 (Interconnect) - 用于带宽管理
core->video_path = devm_of_icc_get(dev, "video-mem");
if (IS_ERR(core->video_path))
return PTR_ERR(core->video_path);
// "cpu-cfg": CPU 配置总线路径
core->cpucfg_path = devm_of_icc_get(dev, "cpu-cfg");
if (IS_ERR(core->cpucfg_path))
return PTR_ERR(core->cpucfg_path);
// 2.3 获取中断号
core->irq = platform_get_irq(pdev, 0);
if (core->irq < 0)
return core->irq;
// 3. 硬件资源匹配
// 根据设备树的 compatible 属性,获取对应的 venus_resources 结构
core->res = of_device_get_match_data(dev);
if (!core->res)
return -ENODEV;
// 4. 初始化锁与电源管理
mutex_init(&core->pm_lock);
// 根据 HFI 版本获取对应的电源操作函数集
core->pm_ops = venus_pm_get(core->res->hfi_version);
if (!core->pm_ops)
return -ENODEV;
// 如果有特定的 Core 初始化 Get 操作
if (core->pm_ops->core_get) {
ret = core->pm_ops->core_get(core);
if (ret)
return ret;
}
// 5. DMA 配置
// 设置 DMA 掩码,限制 Venus 可以访问的物理内存范围
ret = dma_set_mask_and_coherent(dev, core->res->dma_mask);
if (ret)
goto err_core_put;
dma_set_max_seg_size(dev, UINT_MAX); // 设置 DMA 段大小为最大
INIT_LIST_HEAD(&core->instances);
mutex_init(&core->lock);
// 初始化系统错误处理工作队列
INIT_DELAYED_WORK(&core->work, venus_sys_error_handler);
init_waitqueue_head(&core->sys_err_done); // 初始化等待队列
// 7. HFI (固件接口) 初始化
ret = hfi_create(core, &venus_core_ops);
if (ret)
goto err_core_put;
// 8. 中断注册
// hfi_isr是中断处理的上半部,清中断
// venus_isr_thread是下半部,处理具体逻辑
ret = devm_request_threaded_irq(dev, core->irq, hfi_isr, venus_isr_thread,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"venus", core);
if (ret)
goto err_core_put;
// 9. 寄存器偏移分配
venus_assign_register_offsets(core);
// 10. V4L2 框架注册
ret = v4l2_device_register(dev, &core->v4l2_dev);
if (ret)
goto err_hfi_destroy;
// 11. 驱动数据绑定
platform_set_drvdata(pdev, core);
// 12. 运行时电源管理
pm_runtime_enable(dev);
// 获取电源使用权,增加引用计数并唤醒硬件
ret = pm_runtime_get_sync(dev);
if (ret < 0)
goto err_runtime_disable;
// 13. 子设备创建
// 解析设备树子节点,创建具体的编解码器子设备
ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
if (ret)
goto err_runtime_disable;
// 14. 固件与硬件初始化,这是venus设备的关键部分
// 14.1 固件初始化:加载 venus.mbn 文件到内存,并配置 FW 内存区域
ret = venus_firmware_init(core);
if (ret)
goto err_of_depopulate;
// 14.2 启动 Venus 硬件:释放复位信号,配置时钟,启动固件运行
ret = venus_boot(core);
if (ret)
goto err_firmware_deinit;
// 14.3 HFI 核心恢复:配置寄存器,使能 HFI 通信
ret = hfi_core_resume(core, true);
if (ret)
goto err_venus_shutdown;
// 14.4 HFI 核心初始化:发送 SysInit 命令给固件,建立通信握手
ret = hfi_core_init(core);
if (ret)
goto err_venus_shutdown;
// 15. 能力枚举
// 15.1 枚举解码器支持的格式 (H.264, VP9 等)
ret = venus_enumerate_codecs(core, VIDC_SESSION_TYPE_DEC);
if (ret)
goto err_core_deinit;
// 15.2 枚举编码器支持的格式
ret = venus_enumerate_codecs(core, VIDC_SESSION_TYPE_ENC);
if (ret)
goto err_core_deinit;
// 16. 完成与清理
// 释放电源引用,硬件进入低功耗空闲状态,等待用户空间打开设备
ret = pm_runtime_put_sync(dev);
if (ret) {
pm_runtime_get_noresume(dev);
goto err_core_deinit;
}
// 创建 debugfs 节点,用于调试信息导出
venus_dbgfs_init(core);
return 0;
// 以下为错误标签处理
err_core_deinit:
hfi_core_deinit(core, false);
err_venus_shutdown:
venus_shutdown(core);
err_firmware_deinit:
venus_firmware_deinit(core);
err_of_depopulate:
of_platform_depopulate(dev);
err_runtime_disable:
pm_runtime_put_noidle(dev);
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
v4l2_device_unregister(&core->v4l2_dev);
err_hfi_destroy:
hfi_destroy(core);
err_core_put:
if (core->pm_ops->core_put)
core->pm_ops->core_put(core);
return ret;
}
static const struct freq_tbl sc7280_freq_table[] = {
{ 0, 460000000 },
{ 0, 424000000 },
{ 0, 335000000 },
{ 0, 240000000 },
{ 0, 133333333 },
};
static const struct bw_tbl sc7280_bw_table_enc[] = {
{ 1944000, 1896000, 0, 3657000, 0 }, /* 3840x2160@60 */
{ 972000, 968000, 0, 1848000, 0 }, /* 3840x2160@30 */
{ 489600, 618000, 0, 941000, 0 }, /* 1920x1080@60 */
{ 244800, 318000, 0, 480000, 0 }, /* 1920x1080@30 */
};
static const struct bw_tbl sc7280_bw_table_dec[] = {
{ 2073600, 2128000, 0, 3831000, 0 }, /* 4096x2160@60 */
{ 1036800, 1085000, 0, 1937000, 0 }, /* 4096x2160@30 */
{ 489600, 779000, 0, 998000, 0 }, /* 1920x1080@60 */
{ 244800, 400000, 0, 509000, 0 }, /* 1920x1080@30 */
};
static const struct reg_val sm7280_reg_preset[] = {
{ 0xb0088, 0 },
};
static const struct hfi_ubwc_config sc7280_ubwc_config = {
0, 0, {1, 1, 1, 0, 0, 0}, 8, 32, 14, 0, 0, {0, 0}
};
static const struct venus_resources sc7280_res = {
// 频率与寄存器配置
.freq_tbl = sc7280_freq_table, // 指向频率查找表,定义了不同负载下的核心频率
.freq_tbl_size = ARRAY_SIZE(sc7280_freq_table), // 表的大小
.reg_tbl = sm7280_reg_preset, // 引用 sm7280 的寄存器预设
.reg_tbl_size = ARRAY_SIZE(sm7280_reg_preset), // 寄存器表大小
// 带宽配置
.bw_tbl_enc = sc7280_bw_table_enc, // 编码任务的总线带宽需求表
.bw_tbl_enc_size = ARRAY_SIZE(sc7280_bw_table_enc),
.bw_tbl_dec = sc7280_bw_table_dec, // 解码任务的总线带宽需求表
.bw_tbl_dec_size = ARRAY_SIZE(sc7280_bw_table_dec),
// 内存压缩与总线
.ubwc_conf = &sc7280_ubwc_config, // 指向 UBWC (通用带宽压缩) 配置,用于减少内存带宽占用
.clks = {"core", "bus", "iface"}, // 核心主时钟名称列表
.clks_num = 3, // 时钟数量
// 编解码器时钟与电源域
.vcodec0_clks = {"vcodec_core", "vcodec_bus"}, // VCodec 硬件模块的专用时钟
.vcodec_clks_num = 2, // 专用时钟数量
.vcodec_pmdomains = (const char *[]) { "venus", "vcodec0" }, // 电源管理域名称
.vcodec_pmdomains_num = 2, // 电源域数量
.opp_pmdomain = (const char *[]) { "cx", NULL }, // 关联的电压域
// 硬件能力
.vcodec_num = 1, // VCodec 硬件单元的数量
.hfi_version = HFI_VERSION_6XX, // HFI (Hexagon Firmware Interface) 协议版本
.vpu_version = VPU_VERSION_IRIS2_1, // VPU (视频处理单元) 的硬件版本号
.num_vpp_pipes = 1, // VPP (视频后处理) 管道数量
// 内存与安全
.vmem_id = VIDC_RESOURCE_NONE, // 虚拟内存 ID (未使用)
.vmem_size = 0, // 虚拟内存大小
.vmem_addr = 0, // 虚拟内存地址
.dma_mask = 0xe0000000 - 1, // DMA 掩码,定义了 Venus 可以寻址的物理内存范围
// 安全区域配置
.cp_start = 0, // 安全/非安全内存分区起始地址
.cp_size = 0x25800000, // 总大小,约600M
.cp_nonpixel_start = 0x1000000, // 非像素数据起始 (16MB)
.cp_nonpixel_size = 0x24800000, // 非像素数据大小
// 固件路径
.fwname = "qcom/vpu-2.0/venus.mbn",
};
// 设备树匹配表
static const struct of_device_id venus_dt_match[] = {
// 每一项包含一个 "compatible" 字符串和一个指向资源数据的指针,compatible从设备树中查找
{ .compatible = "qcom,msm8916-venus", .data = &msm8916_res, },
{ .compatible = "qcom,msm8996-venus", .data = &msm8996_res, },
{ .compatible = "qcom,msm8998-venus", .data = &msm8998_res, },
{ .compatible = "qcom,sdm660-venus", .data = &sdm660_res, },
{ .compatible = "qcom,sdm845-venus", .data = &sdm845_res, },
{ .compatible = "qcom,sdm845-venus-v2", .data = &sdm845_res_v2, },
{ .compatible = "qcom,sc7180-venus", .data = &sc7180_res, },
{ .compatible = "qcom,sc7280-venus", .data = &sc7280_res, },
{ .compatible = "qcom,sm8250-venus", .data = &sm8250_res, },
{ }
};
MODULE_DEVICE_TABLE(of, venus_dt_match);
// 电源管理,注册str suspend和resume
static const struct dev_pm_ops venus_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(venus_runtime_suspend, venus_runtime_resume, NULL)
};
static struct platform_driver qcom_venus_driver = {
.probe = venus_probe,
.remove_new = venus_remove,
.driver = {
.name = "qcom-venus", // 驱动名称
.of_match_table = venus_dt_match, // 关联匹配表
.pm = &venus_pm_ops, // 关联电源管理操作函数
},
.shutdown = venus_core_shutdown, // 系统下电时调用,用于安全关闭硬件
};
// 将这个驱动注册到 Linux Platform Driver 框架中
module_platform_driver(qcom_venus_driver);
错误处理
用于发现并隔离错误,防止因视频硬件卡死,出现的中断风暴导致系统彻底死机,或者上层应用无限挂起
隔离:通过禁用中断和设置错误标志,防止崩溃扩大。
通知:告知上层应用会话已中断。
恢复准备:启动后台线程venus_sys_error_handler去执行复杂的硬件重启和固件重载操作。
c
static void venus_event_notify(struct venus_core *core, u32 event)
{
struct venus_inst *inst;
switch (event) {
case EVT_SYS_WATCHDOG_TIMEOUT: // 看门狗超时(固件卡死)
case EVT_SYS_ERROR: // 通用系统错误
break;
default:
return;
}
mutex_lock(&core->lock);
set_bit(0, &core->sys_error); // 标记核心状态为"发生错误"
set_bit(0, &core->dump_core); // 标记需要抓取固件日志 (Coredump)
// 遍历所有活跃的视频会话实例 (inst)
list_for_each_entry(inst, &core->instances, list)
// // 通知每个实例发生了会话错误,让上层应用收到错误回调
inst->ops->event_notify(inst, EVT_SESSION_ERROR, NULL);
mutex_unlock(&core->lock);
// 禁用中断
disable_irq_nosync(core->irq);
// 触发异步恢复工作
schedule_delayed_work(&core->work, msecs_to_jiffies(10));
}
static const struct hfi_core_ops venus_core_ops = {
.event_notify = venus_event_notify,
};
看门狗/系统错误
其他事件
HFI 收到固件事件
事件类型?
加锁 core->lock
直接返回
标记 sys_error 和 dump_core
遍历所有实例 inst
通知 inst: EVT_SESSION_ERROR
解锁 core->lock
禁用硬件中断
调度延迟工作: venus_sys_error_handler
返回,等待后台恢复
vdec
vdec主要负责解码器设备的驱动
驱动代码分析
ioctl控制接口
定义vdec_ioctl_ops,供用户空间直接调用的接口函数
c
static const struct v4l2_ioctl_ops vdec_ioctl_ops = {
.vidioc_querycap = vdec_querycap, // 基础能力查询,查询设备的基本能力,对应命令: VIDIOC_QUERYCAP
.vidioc_enum_fmt_vid_cap = vdec_enum_fmt, // 格式枚举,枚举解码器输出(捕获)端支持的像素格式(如 NV12),对应命令: VIDIOC_ENUM_FMT
.vidioc_enum_fmt_vid_out = vdec_enum_fmt, // 格式枚举,枚举解码器输入(输出)端支持的压缩格式(如 H.264/HEVC),对应命令: VIDIOC_ENUM_FMT
.vidioc_s_fmt_vid_cap_mplane = vdec_s_fmt, // 格式设置,设置解码器输出端的格式(分辨率/像素格式),对应命令: VIDIOC_S_FMT
.vidioc_s_fmt_vid_out_mplane = vdec_s_fmt, // 格式设置,设置解码器输入端的格式(编码格式/分辨率),对应命令: VIDIOC_S_FMT
.vidioc_g_fmt_vid_cap_mplane = vdec_g_fmt, // 格式获取,获取当前解码器输出端的格式配置,对应命令: VIDIOC_G_FMT
.vidioc_g_fmt_vid_out_mplane = vdec_g_fmt, // 格式获取,获取当前解码器输入端的格式配置,对应命令: VIDIOC_G_FMT
.vidioc_try_fmt_vid_cap_mplane = vdec_try_fmt, // 格式校验,验证输出端格式参数是否合法(不实际修改硬件状态),对应命令: VIDIOC_TRY_FMT
.vidioc_try_fmt_vid_out_mplane = vdec_try_fmt, // 格式校验,验证输入端格式参数是否合法,对应命令: VIDIOC_TRY_FMT
.vidioc_g_selection = vdec_g_selection, // 构图获取,获取视频画面的裁剪或构图区域(如可见区域),对应命令: VIDIOC_G_SELECTION
.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, // 缓冲区申请,申请并分配视频缓冲区内存(调用 M2M 框架通用函数),对应命令: VIDIOC_REQBUFS
.vidioc_querybuf = v4l2_m2m_ioctl_querybuf, // 缓冲区查询,查询指定缓冲区的状态、大小和偏移量,对应命令: VIDIOC_QUERYBUF
.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, // 缓冲区创建,动态创建额外的缓冲区,对应命令: VIDIOC_CREATE_BUFS
.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, // 缓冲区预准备,预先准备缓冲区以便快速入队,对应命令: VIDIOC_PREPARE_BUF
.vidioc_qbuf = v4l2_m2m_ioctl_qbuf, // 缓冲区入队,将填充好数据的缓冲区提交给驱动处理,对应命令: VIDIOC_QBUF
.vidioc_expbuf = v4l2_m2m_ioctl_expbuf, // 缓冲区导出,将缓冲区导出为文件描述符(用于 DMABUF 零拷贝),对应命令: VIDIOC_EXPBUF
.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, // 缓冲区出队,从驱动取回处理完成的缓冲区,对应命令: VIDIOC_DQBUF
.vidioc_streamon = v4l2_m2m_ioctl_streamon, // 流控制开启,启动解码数据流传输,对应命令: VIDIOC_STREAMON
.vidioc_streamoff = v4l2_m2m_ioctl_streamoff, // 流控制关闭,停止解码数据流传输,对应命令: VIDIOC_STREAMOFF
.vidioc_s_parm = vdec_s_parm, // 参数设置,设置解码器的流参数(如帧率),对应命令: VIDIOC_S_PARM
.vidioc_enum_framesizes = vdec_enum_framesizes, // 帧尺寸枚举,枚举硬件支持的分辨率范围(步进/离散值),对应命令: VIDIOC_ENUM_FRAMESIZES
.vidioc_subscribe_event = vdec_subscribe_event, // 事件订阅,订阅特定的内核事件(如分辨率改变事件),对应命令: VIDIOC_SUBSCRIBE_EVENT
.vidioc_unsubscribe_event = v4l2_event_unsubscribe, // 事件取消订阅,取消对特定事件的订阅,对应命令: VIDIOC_UNSUBSCRIBE_EVENT
.vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, // 解码命令校验,验证解码器命令(如停止/暂停)是否合法,对应命令: VIDIOC_TRY_DECODER_CMD
.vidioc_decoder_cmd = vdec_decoder_cmd, // 解码命令执行,执行具体的解码控制命令(如发送 EOS 结束流),对应命令: VIDIOC_DECODER_CMD
};
以上结构体可以看出缓冲区管理部分直接指向了v4l2_m2m_ioctl_*等M2M缓冲管理框架提供的通用辅助函数,这样的好处是驱动开发不需要再写链表管理,锁机制和队列逻辑,只需要关注和硬件强相关的逻辑即可
很多函数都有_vid_out和_vid_cap两个版本,vid_out代表output queue,是输入给解压器的压缩码流,如H264,_vid_cap代表capture Queue,是解码器输出的原始图像,如NV12数据
.vidioc_try_decoder_cmd和.vidioc_decoder_cmd是现代解码器驱动的高级功能,允许应用层处理动态分辨率切换(DRC)。当视频流中间分辨率发生变化时,驱动通过事件通知应用层重新配置缓冲区,而不是直接报错崩溃
vb2缓冲区管理
定义在vdec_vb2_ops中,用于管理内存和数据流
c
static const struct vb2_ops vdec_vb2_ops = {
.queue_setup = vdec_queue_setup, // 缓冲区配置,计算并协商所需缓冲区的数量和大小,由 VIDIOC_REQBUFS 触发
.buf_init = vdec_buf_init, // 缓冲区初始化,分配 Buffer 私有数据并初始化状态,每个 Buffer 创建时调用
.buf_cleanup = vdec_buf_cleanup, // 缓冲区清理,释放 Buffer 私有数据,每个 Buffer 销毁时调用
.buf_prepare = venus_helper_vb2_buf_prepare, // 缓冲区预处理,实际是做边界检查,防止硬件崩溃
.start_streaming = vdec_start_streaming, // 启动流传输,真正启动硬件解码引擎,由 VIDIOC_STREAMON 触发
.stop_streaming = vdec_stop_streaming, // 停止流传输,停止硬件并归还所有已占用的缓冲区,由 VIDIOC_STREAMOFF 触发
.buf_queue = vdec_vb2_buf_queue, // 缓冲区入队,接收用户空间提交的数据,并通知固件开始处理,由 VIDIOC_QBUF 触发
};
HFI硬件抽象接口回调
vdec_hfi_ops结构体是Venus驱动中硬件抽象层(HFI)与驱动层之间的回调接口
c
static const struct hfi_inst_ops vdec_hfi_ops = {
.buf_done = vdec_buf_done, // 缓冲区处理完成回调,硬件处理完一个缓冲区后触发,驱动在这里将缓冲区标记为"完成",并将其归还给用户空间DQBUF
.event_notify = vdec_event_notify, // 事件通知回调,处理硬件上报的状态变化
.flush_done = vdec_flush_done, // 刷新(Flush)完成回调,vdec_flush_done只做了log打印
};
buf_done处理视频流有两种情况,输入缓冲区完成:说明码流被消费,驱动可以释放或重用,输出缓冲区完成:说明YUV图像好了,驱动将其标记为DONE.用户空间可以通过qbuf取走图像
平台驱动注册
| 特性 | probe(平台驱动层) |
open(M2M 实例层) |
|---|---|---|
| 初始化对象 | venus_core |
venus_inst&v4l2_m2m_dev |
| 生命周期 | 系统启动时一次,直到卸载 | 每次打开 /dev/videoX时创建,关闭时销毁 |
| 管理范围 | 全局资源:时钟、电源、中断、固件加载 | 会话资源:输入输出队列、当前视频参数、状态机 |
文件操作接口
负责编码器实例的创建、初始化及资源释放,vdec_open构建了一个完整的解码上下文,vdec_close负责清理这些资源
c
// 当用户空间调用open("/dev/videox")时触发
static int vdec_open(struct file *file)
{
// 1. 获取核心上下文,从video_device中获取
struct venus_core *core = video_drvdata(file);
// 会话实例
struct venus_inst *inst;
int ret;
// 2. 分配解码实例内存
// 每个打开的文件句柄对应一个独立的解码实例(inst)
inst = kzalloc(sizeof(*inst), GFP_KERNEL);
if (!inst)
return -ENOMEM;
// 3. 初始化链表和锁
// dpbbufs: 解码图像缓冲区链表,参考帧
INIT_LIST_HEAD(&inst->dpbbufs);
// registeredbufs: 已注册的缓冲区链表
INIT_LIST_HEAD(&inst->registeredbufs);
// internalbufs: 内部缓冲区链表
INIT_LIST_HEAD(&inst->internalbufs);
INIT_LIST_HEAD(&inst->list);
// 初始化互斥锁,保护实例状态和队列操作
mutex_init(&inst->lock);
mutex_init(&inst->ctx_q_lock);
// 4. 初始化实例基础属性
inst->core = core; // 关联核心结构体
inst->session_type = VIDC_SESSION_TYPE_DEC; // 标记为解码会话
inst->num_output_bufs = 1; // 默认输出缓冲区数量
inst->codec_state = VENUS_DEC_STATE_DEINIT; // 初始状态:未初始化
inst->buf_count = 0;
inst->clk_data.core_id = VIDC_CORE_ID_DEFAULT;
inst->core_acquired = false;
inst->bit_depth = VIDC_BITDEPTH_8; // 默认位深 8bit
inst->pic_struct = HFI_INTERLACE_FRAME_PROGRESSIVE; // 默认逐行扫描
init_waitqueue_head(&inst->reconf_wait); // 初始化重配置等待队列
inst->nonblock = file->f_flags & O_NONBLOCK; // 检查是否是非阻塞模式
// 5. 辅助初始化
venus_helper_init_instance(inst);
// 6. 初始化控件 (Controls)
// 处理 V4L2 控件,如码率、量化参数等
ret = vdec_ctrl_init(inst);
if (ret)
goto err_free;
// 7. 创建 HFI 会话
// 与底层固件 (Firmware) 建立会话连接
ret = hfi_session_create(inst, &vdec_hfi_ops);
if (ret)
goto err_ctrl_deinit;
// 8. 实例特定初始化
vdec_inst_init(inst);
// 9. 初始化 ID 分配器 (用于 DPB 缓冲区管理)
ida_init(&inst->dpb_ids);
/*
* create m2m device for every instance, the m2m context scheduling
* is made by firmware side so we do not need to care about.
*/
// 10. 创建 M2M (Memory-to-Memory) 设备
// 每个实例都有一个 m2m_dev,实际的硬件调度由固件侧完成,驱动侧只需管理上下文
inst->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops);
if (IS_ERR(inst->m2m_dev)) {
ret = PTR_ERR(inst->m2m_dev);
goto err_session_destroy;
}
// 11. 初始化 M2M 上下文
// 创建具体的处理上下文,并初始化队列 (m2m_queue_init)
inst->m2m_ctx = v4l2_m2m_ctx_init(inst->m2m_dev, inst, m2m_queue_init);
if (IS_ERR(inst->m2m_ctx)) {
ret = PTR_ERR(inst->m2m_ctx);
goto err_m2m_release;
}
// 12. 初始化 V4L2 File Handle
v4l2_fh_init(&inst->fh, core->vdev_dec);
// 13. 关联控件处理器
inst->fh.ctrl_handler = &inst->ctrl_handler;
// 将文件句柄添加到核心列表
v4l2_fh_add(&inst->fh);
// 关联 M2M 上下文
inst->fh.m2m_ctx = inst->m2m_ctx;
// 将实例句柄保存到 file->private_data,供后续 ioctl 使用
file->private_data = &inst->fh;
return 0;
// 错误处理路径:逐级释放已分配的资源
err_m2m_release:
v4l2_m2m_release(inst->m2m_dev);
err_session_destroy:
hfi_session_destroy(inst);
err_ctrl_deinit:
vdec_ctrl_deinit(inst);
err_free:
kfree(inst);
return ret;
}
static int vdec_close(struct file *file)
{
// 获取当前会话实例
struct venus_inst *inst = to_inst(file);
// 1. 获取电源管理权限,防止关闭过程中设备被挂起
vdec_pm_get(inst);
// 2. 取消延迟工作
// 确保没有挂起的任务在处理此实例
cancel_work_sync(&inst->delayed_process_work);
// 3. 释放 M2M 上下文和设备
v4l2_m2m_ctx_release(inst->m2m_ctx);
v4l2_m2m_release(inst->m2m_dev);
// 4. 反初始化控件
vdec_ctrl_deinit(inst);
// 5. 销毁 ID 分配器
ida_destroy(&inst->dpb_ids);
// 6. 销毁 HFI 会话 (通知固件释放资源)
hfi_session_destroy(inst);
// 7. 销毁锁
mutex_destroy(&inst->lock);
mutex_destroy(&inst->ctx_q_lock);
// 8. 移除并退出 V4L2 文件句柄
v4l2_fh_del(&inst->fh);
v4l2_fh_exit(&inst->fh);
// 9. 释放电源管理权限
vdec_pm_put(inst, false);
// 10. 释放实例内存
kfree(inst);
return 0;
}
static const struct v4l2_file_operations vdec_fops = {
.owner = THIS_MODULE, // 模块所有者
.open = vdec_open, // 打开回调
.release = vdec_close, // 关闭回调
.unlocked_ioctl = video_ioctl2, // IOCTL 入口,标准 V4L2 处理函数
.poll = v4l2_m2m_fop_poll, // 轮询支持,用于 select/poll 系统调用
.mmap = v4l2_m2m_fop_mmap, // 内存映射,用于用户空间访问缓冲区
};
开始 vdec_open
获取核心上下文 core
分配解码实例内存 inst
初始化 V4L2 控件
创建 HFI 会话
实例特定初始化 vdec_inst_init
创建 M2M 设备 v4l2_m2m_init
初始化 M2M 上下文
初始化 V4L2 File Handle
将句柄加入核心列表 v4l2_fh_add
保存 inst->fh 到 file->private_data
成功返回 0
驱动探针
c
static int vdec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev; // 获取设备结构体
struct video_device *vdev;
struct venus_core *core;
int ret;
if (!dev->parent)
return -EPROBE_DEFER;
// 获取父设备的驱动数据
core = dev_get_drvdata(dev->parent);
if (!core)
return -EPROBE_DEFER;
// 将核心上下文保存到当前平台设备的驱动数据中
platform_set_drvdata(pdev, core);
// 电源管理,获取解码器资源
if (core->pm_ops->vdec_get) {
ret = core->pm_ops->vdec_get(dev);
if (ret)
return ret;
}
// 分配视频设备内存
vdev = video_device_alloc();
if (!vdev)
return -ENOMEM;
strscpy(vdev->name, "qcom-venus-decoder", sizeof(vdev->name)); // 设备名称
vdev->release = video_device_release; // 释放回调
vdev->fops = &vdec_fops; // 文件操作
vdev->ioctl_ops = &vdec_ioctl_ops; // IOCTL 操作集
vdev->vfl_dir = VFL_DIR_M2M; // 方向,从内存到内存
vdev->v4l2_dev = &core->v4l2_dev; // 关联v4l2设备
vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; // 支持的设备能力
// 注册视频设备到内核
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
if (ret)
goto err_vdev_release;
// 保存设备引用到核心上下文,以便于其他模块访问此解码器设备
core->vdev_dec = vdev;
core->dev_dec = dev;
// 设置设备驱动数据,用于设备与驱动的绑定
video_set_drvdata(vdev, core);
pm_runtime_set_autosuspend_delay(dev, 2000); // 设置自动挂起延时
pm_runtime_use_autosuspend(dev); // 启用自动挂起
pm_runtime_enable(dev); // 启用运行时 PM
return 0;
err_vdev_release:
video_device_release(vdev); // 释放已分配的视频设备结构体
return ret;
}
static const struct of_device_id vdec_dt_match[] = {
{ .compatible = "venus-decoder" },
{ }
};
MODULE_DEVICE_TABLE(of, vdec_dt_match);
static struct platform_driver qcom_venus_dec_driver = {
.probe = vdec_probe,
.remove_new = vdec_remove,
.driver = {
.name = "qcom-venus-decoder",
.of_match_table = vdec_dt_match,
.pm = &vdec_pm_ops,
},
};
module_platform_driver(qcom_venus_dec_driver);
配置详情
名称: qcom-venus-decoder
操作集: vdec_fops, vdec_ioctl_ops
类型: VFL_DIR_M2M
能力: M2M, STREAMING
关联核心: core->v4l2_dev
开始 vdec_probe
获取父设备驱动数据 core
保存 core 到 pdev 驱动数据
调用 vdec_get 获取电源/时钟资源
分配 video_device 内存
配置 vdev 结构体
注册视频设备 video_register_device
保存 vdev/dev 到 core 上下文
配置运行时电源管理
延时2000ms/启用自动挂起
成功返回 0
venc
编码器的驱动架构整体与解码器的几乎一致,就不做分析了
HFI
HFI是Venus驱动中的核心通信机制,主要功能是为驱动程序与底层硬件加速固件之间提供标准化的双向通信桥梁
HFI架构分析
HFI代码架构:
5.硬件 I/O 层
4.平台适配层
3.HFI 通信层
2.Venus 核心层
1.V4L2 接口层
0.应用层
V4L2 IOCTL
调用
关联
依赖
依赖
使用
封装
解析
引用
引用
查询
调用
调用
读写
应用程序
Application
V4L2 接口层
(应用交互)
V4L2 M2M 框架
(队列调度)
Venus 核心
(会话管理)
firmware.h
固件/电源管理
hfi_venus.h
HFI 环境初始化
hfi.h
接口/结构体定义
hfi_cmds.h
驱动 -> 固件命令
hfi_msgs.h
固件 -> 驱动消息
hfi_helper.h
协议常量/工具
hfi_platform.h
平台能力描述
hfi_plat_bufs.h
缓冲区计算
hfi_parser.h
能力集解析
hfi_venus_io.h
寄存器/中断
HFI核心功能:
固件层 - Hexagon DSP
HFI 通信层 - 核心功能
Linux 驱动层
配置/请求
构建请求
引用格式
写入共享内存
触发中断/信号
控制
产生状态
构建消息
引用格式
写入共享内存
触发中断
唤醒/回调
应用程序
V4L2 IOCTL
Venus 核心层
会话管理
HFI 命令封装
hfi_cmds.h
HFI 消息解析
hfi_msgs.h
协议定义与常量
hfi_helper.h
传输机制
共享内存/中断
固件命令处理器
视频硬件引擎
编码/解码
异步事件源
帧完成/错误
对于HFI我们主要分析hfi.c(通用接口)和hfi_venus.c(硬件抽象),hfi负责指定规则,管理状态和流程控制,hfi_venus负责执行具体的动作,如读写寄存器,操作内存等
代码结构映射表
| 功能模块 | hfi.c(通用接口) |
hfi_venus.c(底层实现) |
职责说明 |
|---|---|---|---|
| 核心初始化 | hfi_core_init() |
venus_core_init() |
初始化硬件核心,发送启动命令 |
| 核心去初始化 | hfi_core_deinit() |
venus_core_deinit() |
停止核心 |
| 会话创建 | hfi_session_create() |
无直接对应,由 hfi.c 管理 | 管理会话生命周期 |
| 会话初始化 | hfi_session_init() |
venus_session_init() |
发送 SESSION_INIT命令 |
| 资源加载 | hfi_session_load_res() |
venus_session_load_res() |
发送 LOAD_RESOURCES命令 |
| 消息发送 | 内部逻辑 | venus_iface_cmdq_write() |
核心差异:hfi.c决定"发什么",hfi_venus.c决定"怎么写入内存/寄存器" |
| 中断处理 | 无 | venus_isr()/venus_isr_thread() |
底层处理硬件中断信号 |
完整硬件能力表:
c
// 将hfi.c和hfi_venus.c连接起来,提供硬件能力清单
static const struct hfi_ops venus_hfi_ops = {
.core_init = venus_core_init, // 初始化硬件核心,开启时钟、电源,复位硬件,初始化共享内存队列
.core_deinit = venus_core_deinit, // 关闭硬件核心,停止硬件,释放资源,断电
.core_ping = venus_core_ping, // 检查硬件是否还"活着",发送一个简单的 Ping 命令,看硬件是否返回 ACK
.core_trigger_ssr = venus_core_trigger_ssr, // 触发系统级故障恢复,强制让硬件进入错误状态,触发内核重新加载固件
.session_init = venus_session_init, // 创建一个新的编解码会话,分配会话 ID,向固件发送初始化命令
.session_end = venus_session_end, // 正常结束会话,通知硬件处理完剩余帧后停止
.session_abort = venus_session_abort, // 强制中止会话,立即停止硬件处理(通常用于出错或关闭时)
.session_flush = venus_session_flush, // 清空缓冲,丢弃所有正在排队未处理的输入/输出帧(如快进时)
.session_start = venus_session_start, // 启动会话处理,告诉硬件可以开始接收数据流了
.session_stop = venus_session_stop, // 暂停会话处理,停止接收新数据但保留上下文
.session_continue = venus_session_continue, // 从中断/暂停状态恢复处理
.session_etb = venus_session_etb, // 输入数据(Empty This Buffer),将编码流推入硬件输入队列
.session_ftb = venus_session_ftb, // 输出数据(Fill This Buffer),给硬件提供空缓冲区以接收解码后的 YUV 帧
.session_set_buffers = venus_session_set_buffers, // 注册缓冲区,将 V4L2 分配的内存物理地址告知硬件
.session_unset_buffers = venus_session_unset_buffers, // 注销缓冲区,通知硬件不再使用这些内存
.session_load_res = venus_session_load_res, // 加载资源,分配硬件内部所需的 SRAM 或特定内存(如上下文存储)
.session_release_res = venus_session_release_res, // 释放资源,释放上述分配的硬件内部资源
.session_parse_seq_hdr = venus_session_parse_seq_hdr, // 解析序列头,让硬件读取 SPS/PPS 以获取视频流参数
.session_get_seq_hdr = venus_session_get_seq_hdr, // 获取序列头,读取解析后的序列头信息
.session_set_property = venus_session_set_property,// 设置属性,配置分辨率、帧率、量化参数 (QP) 等
.session_get_property = venus_session_get_property,// 获取属性,查询硬件当前的配置或状态信息
.resume = venus_resume, // 系统唤醒,恢复寄存器状态,重新初始化硬件
.suspend = venus_suspend, // 系统休眠,保存状态,停止硬件,允许系统进入低功耗模式
.isr = venus_isr, // 硬中断顶层处理,快速响应中断,唤醒中断线程
.isr_thread = venus_isr_thread, // 硬中断底层处理,读取消息队列,解析消息,调用业务回调
};
解码时序流程
我们以解码过程为例,解码器从读取编码帧到输出解码帧的过程是一个生产者-消费者模型,通过以下时序完成
加载资源 -> 填充输入输出区(ETB) -> 硬件解码 -> 获取输出缓冲区(FTB)
以上时序过程分为控制流(Setup)和数据流(runtime)两个阶段
1.HFI初始化与资源加载
在开始解码前,必须先建立通信通道和内存池
| 步骤 | 调用栈 | 硬件/内存动作 |
|---|---|---|
| 1. 核心初始化 | hfi_core_init() --> core->ops->core_init() -->venus_core_init() | CPU 通过寄存器启动 Venus 硬件,初始化共享内存队列 (Queue Table)。 |
| 2. 会话初始化 | hfi_session_init() --> core->ops->session_init --> venus_session_init() | 建立一个解码会话 (Session),告诉 Firmware 要解码的 Codec 类型 (H.264/HEVC等)。 |
| 3. 加载资源 | hfi_session_load_res() --> core->ops->session_load_res() --> venus_session_load_res( | 分配 Codec 需要的内部内存 (Internal Buffers)。 |
| 4. 设置缓冲区 | hfi_session_set_buffers() --> core->ops->session_set_buffers --> venus_session_set_buffers() | 关键步骤:上层(驱动)将输入队列和输出队列的内存地址告诉 Firmware。 |
2.解码运行时的数据流
读取编码帧到输出解码帧的核心循环
| 步骤 | 时序动作 | 代码调用 (Producer: CPU -> Hardware) | 代码调用 (Consumer: Hardware -> CPU) |
|---|---|---|---|
| 1. 生产输入(喂编码帧) | CPU 准备好一帧编码数据,推给硬件。 | hfi_session_process_buf() --> venus_session_etb() --> pkt_session_etb_decoder() --> venus_iface_cmdq_write() | (硬件接收中) |
| 2. 硬件解码(硬件处理) | Venus 硬件从共享内存读取编码流,开始解码。 | (CPU 空闲或做其他事) | ISR (中断触发)venus_isr()->venus_isr_thread()硬件解码完成后拉高电平,通知 CPU。 |
| 3. 消费输出(取解码帧) | 硬件通知 CPU 解码完成,CPU 获取图像。 | hfi_session_process_buf()(下一轮调用)或hfi_session_flush() | venus_session_ftb()(在中断线程中被触发)``硬件将解码后的帧信息放入输出队列。 |
关键代码解析
1.生产输入 : venus_session_etb
venus_session_etb实现了将CPU编码后的视频帧推入硬件的过程:CPU 将包含编码数据物理地址的指针放入 Command Queue ,然后触发软中断venus_soft_int,向硬件的某个特定寄存器写入一个值,硬件(Firmware)检测到这个寄存器变化,产生内部中断,醒来读取 Command Queue 中的任务,开始解码
c
static int venus_session_etb(struct venus_inst *inst,
struct hfi_frame_data *in_frame)
{
struct venus_hfi_device *hdev = to_hfi_priv(inst->core);
u32 session_type = inst->session_type;
int ret;
// 解码通道类型
if (session_type == VIDC_SESSION_TYPE_DEC) {
struct hfi_session_empty_buffer_compressed_pkt pkt;
// 1. 打包数据包:包含输入缓冲区地址、偏移、长度、PTS
ret = pkt_session_etb_decoder(&pkt, inst, in_frame);
if (ret)
return ret;
// 2. 写入共享内存队列 (Command Queue)
ret = venus_iface_cmdq_write(hdev, &pkt, false);
} else if (session_type == VIDC_SESSION_TYPE_ENC) { // 编码通道类型
struct hfi_session_empty_buffer_uncompressed_plane0_pkt pkt;
ret = pkt_session_etb_encoder(&pkt, inst, in_frame);
if (ret)
return ret;
ret = venus_iface_cmdq_write(hdev, &pkt, false);
} else {
ret = -EINVAL;
}
return ret;
}
2.硬件处理与中断 venus_isr_thread
Venus 硬件 从DMA 读取内存中的编码数据,硬件解码完成后,更新内部状态,并触发IRQ中断,通知驱动
c
static irqreturn_t venus_isr_thread(struct venus_core *core)
{
// 将通用的 core 结构体转换为 Venus 专用的 HFI 设备结构体
struct venus_hfi_device *hdev = to_hfi_priv(core);
const struct venus_resources *res;
void *pkt;
u32 msg_ret;
// 安全检查:如果设备还没初始化好,直接返回
if (!hdev)
return IRQ_NONE;
res = hdev->core->res; // 获取硬件资源描述符
pkt = hdev->pkt_buf; // 获取用于接收消息的数据包缓冲区指针
// 从 Message Queue 中读取硬件返回的消息
while (!venus_iface_msgq_read(hdev, pkt)) {
// 解析包头,判断是什么类型的消息,并调用回调
msg_ret = hfi_process_msg_packet(core, pkt);
switch (msg_ret) {
case HFI_MSG_EVENT_NOTIFY: // 系统级事件通知,通常包含错误信息
venus_process_msg_sys_error(hdev, pkt);
break;
case HFI_MSG_SYS_INIT: // 固件初始化完成的消息
venus_hfi_core_set_resource(core, res->vmem_id,
res->vmem_size,
res->vmem_addr,
hdev);
break;
case HFI_MSG_SYS_RELEASE_RESOURCE: // 资源释放确认消息
complete(&hdev->release_resource);
break;
case HFI_MSG_SYS_PC_PREP: // 电源塌陷准备完成,唤醒正在等待进入低功耗模式的进程
complete(&hdev->pwr_collapse_prep);
break;
default:
break;
}
}
venus_flush_debug_queue(hdev);
return IRQ_HANDLED;
}
3.消费输出 venus_session_ftb
硬件将解码后的视频帧(YUV/RGB)返回给 CPU
C
static int venus_session_ftb(struct venus_inst *inst,
struct hfi_frame_data *out_frame)
{
struct venus_hfi_device *hdev = to_hfi_priv(inst->core);
struct hfi_session_fill_buffer_pkt pkt;
int ret;
// 1. 打包数据包:包含输出缓冲区信息
ret = pkt_session_ftb(&pkt, inst, out_frame);
if (ret)
return ret;
// 2. 写入队列(通常是硬件将数据放入,但 CPU 需要读取确认)
return venus_iface_cmdq_write(hdev, &pkt, false);
}
4.小结
准备阶段: hfi_session_set_buffers建立共享内存池,
循环阶段:
CPU (ETB):调用 venus_session_etb 将 编码帧指针 写入命令队列-触发软中断。
Hardware:DMA 读取编码帧-解码-DMA 写入输出缓冲区-触发硬件 IRQ。
CPU (ISR):响应 IRQ,在 venus_isr_thread 中读取消息队列
CPU (FTB):解析消息,获取 解码帧指针,通知上层应用(V4L2)取帧
CPU 和 Venus 硬件通过操作物理内存地址指针来交换数据,而不是搬运大量像素数据,用零拷贝实现编解码高性能
Venus 硬件 (Firmware) 共享内存 (HFI Queues) 硬件抽象层 (hfi_venus.c) CPU驱动 (hfi.c) 上层应用/V4L2 Venus 硬件 (Firmware) 共享内存 (HFI Queues) 硬件抽象层 (hfi_venus.c) CPU驱动 (hfi.c) 上层应用/V4L2 阶段二:解码循环 (Runtime Loop) 1. 推入编码帧 (ETB) 2. 硬件解码 (异步) 3. 获取解码帧 (FTB) 4. 业务处理 - 解析时间戳/长度 - 转换 V4L2 标志位 - 调用 buf_done() hfi_session_process_buf(in_frame) venus_session_etb() 写入 Command Queue (包含输入地址/长度) 触发软中断 (Doorbell) DMA 读取编码数据 (Bitstream) 硬件解码引擎工作 DMA 写入解码数据 (YUV) 到输出缓冲区 触发硬件中断 (IRQ) venus_isr_thread() 读取 Message Queue (获取输出地址/状态) hfi_session_ftb_done() 返回解码后的帧 (out_frame)