【V4L2】V4L2框架-media device

系列文章目录

【V4L2】V4L2框架简述
【V4L2】V4L2框架之驱动结构体
【V4L2】V4L2子设备
【V4L2】V4L2框架-media device



media framework

运行时设备控制 也就是设备启动之后的数据流线路控制,就像一个工厂流水线一样,流水线上面的一个个节点(贴商标、喷丝印、打包)就形同于输入设备中的一个个子设备,运行时设备控制就是要达到能够控制节点的效果,比如贴商标的机器有好几台,应该选择哪一台进行此次流水线处理,要不要把喷丝印加上去,加哪一个机子等等。

作用:提供实时的 pipeline 管理,pipeline 就理解为管道,想象一下水管,里面的水就是数据流,输入设备中的 csi->isp->video 就组成了一个 pipeline 线路。media framework 提供 pipeline 的开启、关停、效果控制、节点控制等功能。

如何使用 内核当中主要利用四个结构体把众多的节点组织起来:media_device,media_entity,media_link,media_pad。整个 media framework 都是围绕这四个结构体来进行使用的,下文会对这些进行详细介绍。

抽象设备模型 media framework 其中一个目的是:在运行时状态下发现设备拓扑并对其进行配置。为了达到这个目的,media framework将 硬件设备抽象为一个个的entity,它们之间通过links连接。

entity:硬件设备模块抽象(类比电路板上面的各个元器件、芯片)

pad:硬件设备端口抽象(类比元器件、芯片上面的管脚)

link:硬件设备的连线抽象,link的两端是pad(类比元器件管脚之间的连线)

如果各个 entity 之间需要建立连接的话,就需要在 pad 中存储 link 以及 entity 信息,link 中需要存储 pad 与 entity 信息,entity 里面需要存储 link 与 pad 信息,属于你中有我,我中有你的情况。

media 设备

一个 media 设备用一个 media_device 结构体来表示,通常情况下该结构体要嵌入到一个更大的设备自定义的结构体里面,并且大多数时候 media_devicev4l2_device 是处于并列的级别,还是以 omap3isp 的代码为例:

struct isp_device {
    struct v4l2_device v4l2_dev;
    struct v4l2_async_notifier notifier;
    struct media_device media_dev;
    struct device *dev;
    u32 revision;
    ... ...
}

使用以下函数进行 meida 设备的注册:media_device_register(struct media_device *mdev);函数的调用者需要在注册之前设置以下结构体成员(提前初始化该结构体是调用者的责任):

dev:必须指向一个父设备,通常是平台设备的device成员。

model:模型名字。 以下的成员是可选的:

serial:序列号,必须是唯一的

bus_info:总线信息,如果是PCI设备的话就可以设置成”PCI:”

hw_revision:硬件版本。可以的话,应该用KERNEL_VERSION宏定义进行格式化

driver_version:驱动版本。最终生成的设备节点的名称是media[0-9],节点号由内核自动生成。

使用以下函数进行设备卸载:media_device_unregister(struct media_device *mdev);需要注意的是,卸载一个并没有注册过的设备是不安全的。个人查看代码猜想不安全的原因主要有几个:

  1. 如果没有被注册,那么 media_device 内部的 entity 成员就有可能没有被初始化,如果其值为一个不确定的值,那么就会引起非法访问;2. 如果没有注册,内部的 devnode 成员就没有初始化,卸载时就会出现问题。

entities、pads、links

entities

entities 用一个 media_entity 结构体来表示,该结构体通常被嵌入到一个更大的结构体里面,比如 v4l2_subdev 或者 video_device 结构体(不必自行分配空间,结构体内部已经包含),当然也可以直接分配一个 entities。使用以下函数对 entity 进行初始化:

media_entity_init(struct media_entity *entity, u16 num_pads, struct
    media_pad *pads, u16 extra_links);

在执行初始化函数之前需要注意的参数有:

num_pads:pad的数量,与驱动子设备结构相关。

pads:media_pad结构体数组,通常pad被嵌入到驱动自定义的结构体里面,数组地址被传递给该参数,pad需提前初始化。

extra_links:该函数会根据num_pads分配link数目,该参数则指明除了预分配的数量之外还需要多少额外的links。

entity:media_entitynametypeflagsrevisiongroup_id 需要在初始化之前或者之后进行设置,如果结构体被嵌入到更高级的结构体里面,这些成员也可能被更高级的框架代码所设置,entity的id在注册的时候被填充(如果提前设置了id成员,则注册的时候就保持预设的值)。entity有相关的标志位「flags」来标识它的状态与功能,MEDIA_ENT_FL_DEFAULT 就表示这是一个默认的 entity。可以设置多个 entity 的组 ID 为同一个整数来标识它们是属于同一类别的,对于内核来说,组ID是没有用处的,但是组 ID 会在枚举 entity 的时候被传递到用户空间,可能在用户空间的某种情况下用得上。

pads

pad 使用一个 media_pad 结构体来表示,pads 数据被驱动程序管理(数组形式)。pads 使用 entity 与数组下标来进行唯一标识,entity 内部的 id 不会重复,但是不同 entity 之间的 pad id 可能会重复,所以 pad 的索引要 entity 与 id 联合确认。

由于 pads 的数量是提前获知的(你做的芯片,你肯定知道它有几个管脚),所以 media_pad 结构体不再动态分配,并且驱动应负责对该结构体数组进行管理(避免动态分配)。驱动必须在 media_entity_init 函数被调用之前对 pads 的方向属性进行设置,pads 有 flags 位来标识它的属性,在初始化的时候仅需要设置该成员即可,其余的交由 media_entity_init 函数来完成:

MEDIA_PAD_FL_SINK:目的pad
MEDIA_PAD_FL_SOURCE:源pad

links

links 用一个 media_link 结构体来表示,每一个 entity 的所有 pads 里面都存储了与之相关的所有 links,一个 link 会分别被源 pad 以及目的 pad 存储,以便实现正反两个方向的遍历。使用以下函数创建 links:

media_entity_create_link(struct media_entity *source, u16 source_pad,
    struct media_entity *sink, u16 sink_pad, u32 flags);

links 有一些 flags 位来标识其属性:

MEDIA_LNK_FL_ENABLED:link被使能,可以用来传输数据,多个link连接到同一个sink
pad时,只有一个link可以被使能。
MEDIA_LNK_FL_IMMUTABLE:link的使能状态不能在运行时被改变,一般情况下这两个标志位同时被设置。
MEDIA_LNK_FL_DYNAMIC:link的状态是动态可变的。

和 pads 不一样,links 的数量并不总是提前确定的(电路板上面有时候你也无法完全确认需要管脚连到多少个设备上面,极有可能出现临时变更的情况),所以 media_entity_init 函数根据传入的参数预分配一定数量的 media_link 结构体,如果不够用的话会在 media_entity_create_link 中动态分配(如果 link 数量大于等于 max_link 的话就会扩充 link 数量)。

注册与卸载

驱动需要使用以下函数对 entity 进行注册与卸载(不需要手动执行,在 v4l2_device_un/register_subdev 函数里面完成):

media_device_register_entity(struct media_device *mdev, struct 
    media_entity *entity);
media_device_unregister_entity(struct media_entity *entity);

内核里面使用一个唯一的正整数来表示每一个 entity(同一个 media_device 下唯一),驱动也可以通过填充 media_entity->id 成员来指定 entity 的 ID 值,但是必须保证唯一。如果 ID 由内核自动生成,则不能保证它们是连续的,事实上内核自动生成的 id 是由 entity 的 media_device->entity_id++ 来实现赋值的,该值在 media_device_register 函数里面被初始化为1。

在卸载 entity 之后需要调用以下函数来释放申请到的相关资源,主要是释放动态分配的 media_link 结构体内存:

media_entity_cleanup(struct media_entity *entity); //与media_entity_init结对使用

要想遍历 entities 可以在用户空间进行 MEDIA_IOC_ENUM_ENTITIES 系统调用,需要设置 media_entity_desc 的 id 为 (0|MEDIA_ENT_ID_FLAG_NEXT),循环的过程中只需要设置 id |= MEDIA_ENT_ID_FLAG_NEXT 即可完成 entity 的遍历过程,如果需要枚举指定的 entitiy,需要设置 id 为指定 entity 的 id 值(内核 entity 的 id 是从1开始),这个在 entity 注册函数里面可以看到。代码实例如下,我尽量精简了贴出来的代码,防止占用过大篇幅:

int enum_media_device_entities(int iFd)
{
    int iRet;
    struct media_entity_desc desc;

    desc.id = 0 | MEDIA_ENT_ID_FLAG_NEXT;
    while (1) {
        iRet = ioctl(iFd, MEDIA_IOC_ENUM_ENTITIES, &desc);
        if (iRet < 0) {
            MODULE_WRN("enum media entities end\n");
            break;
        }
        MODULE_DBG("entity name[%s]\n", desc.name);
        desc.id |= MEDIA_ENT_ID_FLAG_NEXT;
    }

    return 0;
}

int main(int argc, char *argv[])
{
    int iErr = 0, ivFd;

    ivFd = open("/dev/media0", O_RDWR);
    iErr = enum_media_device_entities(ivFd);

    close(ivFd);
open_err:
    return iErr;
}

图遍历(深度优先)

图遍历是干嘛的?它为我们提供在运行时访问每一个、指定的 entities 的方法,至于为什么需要访问,是因为我们可能会需要在运行时去管理它们。

可以使用下面的函数对同属于一个media设备的entities进行遍历(线性遍历,非典型图遍历,也就是跟链表一样的遍历方式):

struct media_entity *entity;
media_device_for_each_entity(entity, mdev) {
    /* entity will point to each entity in turn */
    ...
}

驱动可能需要从一个给定的 entity,通过使能的 links 对所有的可访问到的 entities 进行遍历,meida 框架提供了一个深度优先的 API 来完成这个任务。需要注意的是,要避免对闭环的图进行遍历,否则会陷入死循环,为了避免这种情况,函数限制了最大遍历深度为 MEDIA_ENTITY_ENUM_MAX_DEPTH,该宏最新的定义是16**「截至 Linux-4.4.138」**。

media_entity_graph_walk_start(struct media_entity_graph *graph,
    struct media_entity *entity);
media_entity_graph_walk_next(struct media_entity_graph *graph);

使用时先用第一个函数初始化图,然后循环调用第二个函数进行遍历,遍历全部完成之后第二个函数会返回NULL。遍历过程可以在任意一个时刻中断,并且无需调用清理函数。

有相应的帮助函数用来寻找两个给定的 pads 的 link,或者通过一个 pad 来找到与之相连的另一个 pad。

media_entity_find_link(struct media_pad *source, struct media_pad *sink);
media_entity_remote_pad(struct media_pad *pad);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yasin墨染锦年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值