linux v4l2架构

一、v4l2框架分解

代码结构分解

1.buffer管理:media/common/videobuf2/

2.v4l2设备管理:media/v4l2-core/

3.mem ops:media/v4l2-core/videobuf-*
 

二、v4l2设备/子设备注册流程分析

v4l2设备注册:

int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
初始化子设备链表INIT_LIST_HEAD(&v4l2_dev->subdevs),spin_lock等变量

v4l2子设备注册:

int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd)

  • sd->v4l2_dev = v4l2_dev
  • v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler, NULL, true):将子设备控件引用都添加到v4l2_dev设备的控件句柄的ctrl_refs链表

  • media_device_register_entity(v4l2_dev->mdev, &sd->entity):初始化entity links链表,分配entity id,entity的media_gobj赋值(id分配,gobj->mdev=mdev,将media_gobj加入mdev->entity链表,同时entity下的所有pads变量media_gobj赋值

  • 将sd添加到v4l2_dev->subdevs链表

三、video设备注册流程分析

__v4l2_device_register_subdev_nodes:

为所有挂在v4l2_dev->subdevs链表上的子设备创建设备节点/dev/v4l-subdevX

  • vdev赋值

            video_set_drvdata(vdev, sd);

            strscpy(vdev->name, sd->name, sizeof(vdev->name));

            vdev->dev_parent = sd->dev;

            vdev->v4l2_dev = v4l2_dev;

            vdev->fops = &v4l2_subdev_fops;

            vdev->release = v4l2_device_release_subdev_node;

            vdev->ctrl_handler = sd->ctrl_handler;

  • 调用__video_register_device

__video_register_device:

  • v4l2文件句柄支持,初始化fh_lock,fh_list
  • vdev类型赋值,vdev->vfl_type = type;类型包括

    enum vfl_devnode_type {

        VFL_TYPE_VIDEO,

        VFL_TYPE_VBI,

        VFL_TYPE_RADIO,

        VFL_TYPE_SUBDEV,

        VFL_TYPE_SDR,

        VFL_TYPE_TOUCH,

        VFL_TYPE_MAX /* Shall be the last one */

    };

  • 控件句柄赋值

      if (vdev->ctrl_handler == NULL)

            vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;

  • 确定vdev minor
  • 初始化字符设备

        /* Part 3: Initialize the character device */

        vdev->cdev = cdev_alloc();

        if (vdev->cdev == NULL) {

            ret = -ENOMEM;

            goto cleanup;

        }

        vdev->cdev->ops = &v4l2_fops;

        vdev->cdev->owner = owner;

        ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1)

  • 注册设备

        /* Part 4: register the device with sysfs */

        vdev->dev.class = &video_class;

        vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);

        vdev->dev.parent = vdev->dev_parent;

        dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num)

四、v4l2异步通知

基本概念

异步通知器:

struct v4l2_async_notifier {

    const struct v4l2_async_notifier_operations *ops;

    struct v4l2_device *v4l2_dev;

    struct v4l2_subdev *sd;

    struct v4l2_async_notifier *parent;

    struct list_head asd_list;//挂载所有异步子设备asd->asd_list

    struct list_head waiting;//挂载等待异步子设备asd->list,等待子设备注册v4l2_async_register_subdev

    struct list_head done;//从subdev_list中匹配到设备,从subdev_list中匹配到设备就从链表删除,挂载该子设备sd->async_list到该链表

    struct list_head list;//全局链表notifier_list挂载节点

};

子设备

/**

 * struct v4l2_subdev - describes a V4L2 sub-device

 *

 * @entity: pointer to &struct media_entity

 * @list: List of sub-devices

 * @owner: The owner is the same as the driver's &struct device owner.

 * @owner_v4l2_dev: true if the &sd->owner matches the owner of @v4l2_dev->dev

 *  owner. Initialized by v4l2_device_register_subdev().

 * @flags: subdev flags. Can be:

 *   %V4L2_SUBDEV_FL_IS_I2C - Set this flag if this subdev is a i2c device;

 *   %V4L2_SUBDEV_FL_IS_SPI - Set this flag if this subdev is a spi device;

 *   %V4L2_SUBDEV_FL_HAS_DEVNODE - Set this flag if this subdev needs a

 *   device node;

 *   %V4L2_SUBDEV_FL_HAS_EVENTS -  Set this flag if this subdev generates

 *   events.

 *

 * @v4l2_dev: pointer to struct &v4l2_device

 * @ops: pointer to struct &v4l2_subdev_ops

 * @internal_ops: pointer to struct &v4l2_subdev_internal_ops.

 *  Never call these internal ops from within a driver!

 * @ctrl_handler: The control handler of this subdev. May be NULL.

 * @name: Name of the sub-device. Please notice that the name must be unique.

 * @grp_id: can be used to group similar subdevs. Value is driver-specific

 * @dev_priv: pointer to private data

 * @host_priv: pointer to private data used by the device where the subdev

 *  is attached.

 * @devnode: subdev device node

 * @dev: pointer to the physical device, if any

 * @fwnode: The fwnode_handle of the subdev, usually the same as

 *      either dev->of_node->fwnode or dev->fwnode (whichever is non-NULL).

 * @async_list: Links this subdev to a global subdev_list or @notifier->done

 *  list.

 * @asd: Pointer to respective &struct v4l2_async_subdev.

 * @notifier: Pointer to the managing notifier.

 * @subdev_notifier: A sub-device notifier implicitly registered for the sub-

 *           device using v4l2_device_register_sensor_subdev().

 * @pdata: common part of subdevice platform data

 * @state: active state for the subdev (NULL for subdevs tracking the state

 *     internally)

 *

 * Each instance of a subdev driver should create this struct, either

 * stand-alone or embedded in a larger struct.

 *

 * This structure should be initialized by v4l2_subdev_init() or one of

 * its variants: v4l2_spi_subdev_init(), v4l2_i2c_subdev_init().

 */

struct v4l2_subdev {

#if defined(CONFIG_MEDIA_CONTROLLER)

    struct media_entity entity;

#endif

    struct list_head list;

    struct module *owner;

    bool owner_v4l2_dev;

    u32 flags;

    struct v4l2_device *v4l2_dev;

    const struct v4l2_subdev_ops *ops;

    const struct v4l2_subdev_internal_ops *internal_ops;

    struct v4l2_ctrl_handler *ctrl_handler;

    char name[V4L2_SUBDEV_NAME_SIZE];

    u32 grp_id;

    void *dev_priv;

    void *host_priv;

    struct video_device *devnode;

    struct device *dev;

    struct fwnode_handle *fwnode;

    struct list_head async_list;

    struct v4l2_async_subdev *asd;

    struct v4l2_async_notifier *notifier;

    struct v4l2_async_notifier *subdev_notifier;

    struct v4l2_subdev_platform_data *pdata;

    /*

     * The fields below are private, and should only be accessed via

     * appropriate functions.

     */

    /*

     * TODO: state should most likely be changed from a pointer to an

     * embedded field. For the time being it's kept as a pointer to more

     * easily catch uses of state in the cases where the driver doesn't

     * support it.

     */

    struct v4l2_subdev_state *state;

};

子设备descriptor,异步子设备

//struct v4l2_async_subdev - sub-device descriptor, as known to a bridge

struct v4l2_async_subdev {

    enum v4l2_async_match_type match_type;

    union {

        struct fwnode_handle *fwnode;

        const char *device_name;

        struct {

            int adapter_id;

            unsigned short address;

        } i2c;

        struct {

            bool (*match)(struct device *dev,

                      struct v4l2_async_subdev *sd);

            void *priv;

        } custom;

    } match;

    /* v4l2-async core private: not to be used by drivers */

    struct list_head list;

    struct list_head asd_list;

};

异步通知器v4l2 notifier使用步骤

1.初始化通知器

v4l2_async_notifier_init(&csi->notifier);

void v4l2_async_notifier_init(struct v4l2_async_notifier *notifier)

{

    INIT_LIST_HEAD(&notifier->asd_list);

}

2.赋值ops

csi->notifier.ops = &csi_async_notifier_ops;

3.添加需要的子设备,等待子设备注册,并回调bound

子设备匹配类型,共四种

/**

 * enum v4l2_async_match_type - type of asynchronous subdevice logic to be used

 *  in order to identify a match

 *

 * @V4L2_ASYNC_MATCH_CUSTOM: Match will use the logic provided by &struct

 *  v4l2_async_subdev.match ops

 * @V4L2_ASYNC_MATCH_DEVNAME: Match will use the device name

 * @V4L2_ASYNC_MATCH_I2C: Match will check for I2C adapter ID and address

 * @V4L2_ASYNC_MATCH_FWNODE: Match will use firmware node

 *

 * This enum is used by the asyncrhronous sub-device logic to define the

 * algorithm that will be used to match an asynchronous device.

 */

enum v4l2_async_match_type {

    V4L2_ASYNC_MATCH_CUSTOM,

    V4L2_ASYNC_MATCH_DEVNAME,

    V4L2_ASYNC_MATCH_I2C,

    V4L2_ASYNC_MATCH_FWNODE,

};

以V4L2_ASYNC_MATCH_FWNODE设备节点匹配方式为例,注册函数如下:

asd = v4l2_async_notifier_add_fwnode_subdev(&csi->notifier, fwnode,

                            sizeof(struct v4l2_async_subdev));

v4l2_async_notifier_add_fwnode_subdev(struct v4l2_async_notifier *notifier,

                      struct fwnode_handle *fwnode,

                      unsigned int asd_struct_size)

{

    struct v4l2_async_subdev *asd;

    int ret;

    asd = kzalloc(asd_struct_size, GFP_KERNEL);

    if (!asd)

        return ERR_PTR(-ENOMEM);

    asd->match_type = V4L2_ASYNC_MATCH_FWNODE;

    asd->match.fwnode = fwnode_handle_get(fwnode);

//asd加入notifier 异步子设备asd链表,list_add_tail(&asd->asd_list, &notifier->asd_list);

    ret = v4l2_async_notifier_add_subdev(notifier, asd);

    if (ret) {

        fwnode_handle_put(fwnode);

        kfree(asd);

        return ERR_PTR(ret);

    }

    return asd;

}

fwnode的匹配方法,match_fwnode:

1.sd->fwnode == asd->match.fwnode相等返回匹配成功

示例:

96712驱动注册通知器rxport->fwnode为max96705_out_0节点

max96712_v4l2_notifier_register

asd = v4l2_async_notifier_add_fwnode_subdev(&priv->notifier, rxport->fwnode,sizeof(*ubasd));

96705子设备注册,priv->sd.fwnode即为max96705_out_0,注册子设备时与96712注册的通知器中的异步子设备相匹配则执行匹配后相关动作

    priv->tx_ep_np = of_graph_get_endpoint_by_regs(dev->of_node, 1, 0);

    priv->sd.fwnode = of_fwnode_handle(priv->tx_ep_np);

    ......

    ret = v4l2_async_register_subdev(&priv->sd);

gmsl-deser@69 {
  compatible = "maxim,max96712";

  reg-names = "main", "ser0", "ser1", "ser2", "ser3";
  reg = <0x69>, <0x41>, <0x42>, <0x43>, <0x44>;

  clocks = <&clk_fusion_25M_fixed>;

  i2c-alias-pool = /bits/ 16 <0x4a 0x4b 0x4c 0x4d 0x4e 0x4f>;

  data-rate = <1500000000>;

  pinctrl-names = "default";
  pinctrl-0 = <&deser0_power_gpio_pins_default &deser0_index_pins_default>;

  int-gpios = <&main_gpio0 97 0>;

  index-gpios = <&main_gpio0 103 0>, <&main_gpio0 102 0>;

  max96712_0_ports: ports {
   #address-cells = <1>;
   #size-cells = <0>;


   port@4 {
    reg = <4>;
    max96712_0_csi_out: endpoint {
     clock-lanes = <0>;
     data-lanes = <1 2 3 4>;
     remote-endpoint = <&csi2rx0_in_sensor>;
    };
   };
  };

  max96712_0_atr: i2c-atr {
   #address-cells = <1>;
   #size-cells = <0>;
  };
 };

&max96712_0_ports {
    #address-cells = <1>;
    #size-cells = <0>;

    gmsl-type = <1>;
    gmsl-link-rate = <3>; /* Gbps */

    /* GMSL RX 0 */
    port@0 {
        reg = <0>;

        max96712_in_0: endpoint {
            remote-endpoint = <&max96705_out_0>;

            serializer_0: remote-chip {
                compatible = "maxim,max96705";

                gpio-controller;
                #gpio-cells = <2>;

                ports {
                    #address-cells = <1>;
                    #size-cells = <0>;

                    port@0 {
                        reg = <0>;
                        max96705_in_0: endpoint@0 {
                            remote-endpoint = <&sensor_out_0>;
                        };
                    };

                    port@1 {
                        reg = <1>;

                        max96705_out_0: endpoint@0 {
                            remote-endpoint = <&max96712_in_0>;
                        };
                    };
                };
            };
        };
    };

96705驱动注册通知器,ep_node为max96705_in_0节点,v4l2_async_notifier_add_fwnode_remote_subdev通过ep_node会找到sensor@22节点,然后注册向异步通知器添加异步子设备asd = v4l2_async_notifier_add_fwnode_subdev(notif, remote, asd_struct_size);

    ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);

    if (!ep_node) {

        dev_err(dev, "No graph endpoint\n");

        return -ENODEV;

    }

    v4l2_async_notifier_init(&priv->notifier);

    asd = v4l2_async_notifier_add_fwnode_remote_subdev(

        &priv->notifier, of_fwnode_handle(ep_node),

        sizeof(*asd));

ov2775驱动注册子设备,sd->fwnode未赋值,则v4l2_async_register_subdev以dev_fwnode(sd->dev)进行赋值

    /* Finally, register the subdev. */

    ret = v4l2_async_register_subdev(sd);

......    

        serializer_0: remote-chip {
                compatible = "maxim,max96705";

                gpio-controller;
                #gpio-cells = <2>;

                ports {
                    #address-cells = <1>;
                    #size-cells = <0>;

                    port@0 {
                        reg = <0>;
                        max96705_in_0: endpoint@0 {
                            remote-endpoint = <&sensor_out_0>;
                        };
                    };

                    port@1 {
                        reg = <1>;

                        max96705_out_0: endpoint@0 {
                            remote-endpoint = <&max96712_in_0>;
                        };
                    };
                };
            };

......

......

        sensor@22 {
            compatible = "ovti,ov2775";
            reg = <0x22>;

            #address-cells = <1>;
            #size-cells = <0>;

            port@0 {
                reg = <0>;
                sensor_out_0: endpoint {
                    remote-endpoint = <&max96705_in_0>;
                };
            };
        };

......

2.若不相等,fwnode_graph_is_endpoint判断是否存在remote-endpoint属性,若都存在或都不存在则直接返回不匹配,若一个是endpoint节点,一个不是,则将endpoint节点转换成设备节点

转换如下:先查找remote-endpoint属性节点的父节点也就是port节点,找port节点的父节点也就是ports节点并判断名称是否是ports, 如果不是说明是最终设备节点,若name是ports,说明最终节点是ports的父节点,然后返回ports的父节点也就是最终的设备节点

static inline bool fwnode_graph_is_endpoint(struct fwnode_handle *fwnode)

{

    return fwnode_property_present(fwnode, "remote-endpoint");

}

struct fwnode_handle *

fwnode_graph_get_port_parent(const struct fwnode_handle *endpoint)

{

    struct fwnode_handle *port, *parent;

    port = fwnode_get_parent(endpoint);

    parent = fwnode_call_ptr_op(port, graph_get_port_parent);

    fwnode_handle_put(port);

    return parent;

}

EXPORT_SYMBOL_GPL(fwnode_graph_get_port_parent);

4.异步通知器注册/子设备异步通知器注册

注册过程:

1).通知器v4l2_deviece/v4l2_subdev赋值

2).初始化v4l2_async_subdev waiting,done链表,将通知器的asd_list链表上的异步子设备asd->list添加到waiting链表上,

3).必须v4l2设备通知器已经存在的情况下才能开始扫描链表,也就是整个v4l2设备链路是从顶层到底层进行的,如果底层通知器和设备先注册,必须等到顶层通知器注册才能拿到v4l2_dev

    struct v4l2_device *v4l2_dev =

        v4l2_async_notifier_find_v4l2_dev(notifier);

    struct v4l2_subdev *sd;

    if (!v4l2_dev)

        return 0;

4)通知器注册/异步子设备注册

a.v4l2_async_notifier_register

轮询子设备链表static LIST_HEAD(subdev_list),看链表上的子设备是否跟通知器里的异步子设备是否相匹配,如果相匹配,调用v4l2_async_match_notify,此函数调用v4l2_device_register_subdev,将sd加入到v4l2_dev->subdevs链表,并调用v4l2_async_notifier_call_bound,该函数讲回调通知器的bound:n->ops->bound,

将asd->list从waiting链表上清除,并将sd->async_list从全局链表subdev_list移除到通知器的done链表上。然后从notifier_list查找子设备sd的通知器,给子设备通知器赋值parent,重新扫描所有子设备v4l2_async_notifier_try_all_subdevs,从顶层到底层查找子设备所有通知器并轮询子设备。

v4l2_async_notifier_try_complete,查看整个notifier链路是否waiting都空了且有v4l2_dev,如果所有notifer都完成了子设备注册,回调n->ops->complete

    /* Remove from the waiting list */

    list_del(&asd->list);

    sd->asd = asd;

    sd->notifier = notifier;

    /* Move from the global subdevice list to notifier's done */

    list_move(&sd->async_list, &notifier->done);

    subdev_notifier = v4l2_async_find_subdev_notifier(sd);

    if (!subdev_notifier || subdev_notifier->parent)

        return 0;

    /*

     * Proceed with checking for the sub-device notifier's async

     * sub-devices, and return the result. The error will be handled by the

     * caller.

     */

    subdev_notifier->parent = notifier;

b.v4l2_async_register_subdev

轮询notifier_list上所有的通知器,查看是否通知器链路上v4l2_dev已经存在,如果不存在,则查询下一个通知器,其他步骤同a

v4l2异步通知器注册

ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);

v4l2子设备通知器注册

ret = v4l2_async_subdev_notifier_register(&priv->sd, &priv->notifier);

int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,

                 struct v4l2_async_notifier *notifier)

{

    int ret;

    if (WARN_ON(!v4l2_dev || notifier->sd))

        return -EINVAL;

    notifier->v4l2_dev = v4l2_dev;

    ret = __v4l2_async_notifier_register(notifier);

    if (ret)

        notifier->v4l2_dev = NULL;

    return ret;

}

int v4l2_async_subdev_notifier_register(struct v4l2_subdev *sd,

                    struct v4l2_async_notifier *notifier)

{

    int ret;

    if (WARN_ON(!sd || notifier->v4l2_dev))

        return -EINVAL;

    notifier->sd = sd;

    ret = __v4l2_async_notifier_register(notifier);

    if (ret)

        notifier->sd = NULL;

    return ret;

}

static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier)

{

    struct v4l2_async_subdev *asd;

    int ret, i = 0;

    INIT_LIST_HEAD(&notifier->waiting);

    INIT_LIST_HEAD(&notifier->done);

    mutex_lock(&list_lock);

    list_for_each_entry(asd, &notifier->asd_list, asd_list) {

        ret = v4l2_async_notifier_asd_valid(notifier, asd, i++);

        if (ret)

            goto err_unlock;

        list_add_tail(&asd->list, &notifier->waiting);

    }

    ret = v4l2_async_notifier_try_all_subdevs(notifier);

    if (ret < 0)

        goto err_unbind;

    ret = v4l2_async_notifier_try_complete(notifier);

    if (ret < 0)

        goto err_unbind;

    /* Keep also completed notifiers on the list */

    list_add(&notifier->list, &notifier_list);

    mutex_unlock(&list_lock);

    return 0;

err_unbind:

    /*

     * On failure, unbind all sub-devices registered through this notifier.

     */

    v4l2_async_notifier_unbind_all_subdevs(notifier);

err_unlock:

    mutex_unlock(&list_lock);

    return ret;

}

static int v4l2_async_notifier_call_bound(struct v4l2_async_notifier *n,

                      struct v4l2_subdev *subdev,

                      struct v4l2_async_subdev *asd)

{

    if (!n->ops || !n->ops->bound)

        return 0;

    return n->ops->bound(n, subdev, asd);

}

完整的v4l2设备链路

五、v4l2控件

作用

控件是对v4l2整个设备链路上一些设备的参数的操作,如sensor的曝光时长,白平衡等

基本概念

1.控件句柄:子设备或者v4l2_dev所有控件,控件引用都将添加到句柄中

/**

 * struct v4l2_ctrl_handler - The control handler keeps track of all the

 *  controls: both the controls owned by the handler and those inherited

 *  from other handlers.

 *

 * @_lock:  Default for "lock".

 * @lock:   Lock to control access to this handler and its controls.

 *      May be replaced by the user right after init.

 * @ctrls:  The list of controls owned by this handler.

 * @ctrl_refs:  The list of control references.

 * @cached: The last found control reference. It is common that the same

 *      control is needed multiple times, so this is a simple

 *      optimization.

 * @buckets:    Buckets for the hashing. Allows for quick control lookup.

 * @notify: A notify callback that is called whenever the control changes

 *      value.

 *      Note that the handler's lock is held when the notify function

 *      is called!

 * @notify_priv: Passed as argument to the v4l2_ctrl notify callback.

 * @nr_of_buckets: Total number of buckets in the array.

 * @error:  The error code of the first failed control addition.

 * @request_is_queued: True if the request was queued.

 * @requests:   List to keep track of open control handler request objects.

 *      For the parent control handler (@req_obj.ops == NULL) this

 *      is the list header. When the parent control handler is

 *      removed, it has to unbind and put all these requests since

 *      they refer to the parent.

 * @requests_queued: List of the queued requests. This determines the order

 *      in which these controls are applied. Once the request is

 *      completed it is removed from this list.

 * @req_obj:    The &struct media_request_object, used to link into a

 *      &struct media_request. This request object has a refcount.

 */

struct v4l2_ctrl_handler {

    struct mutex _lock;

    struct mutex *lock;

    struct list_head ctrls;

    struct list_head ctrl_refs;

    struct v4l2_ctrl_ref *cached;

    struct v4l2_ctrl_ref **buckets;

    v4l2_ctrl_notify_fnc notify;

    void *notify_priv;

    u16 nr_of_buckets;

    int error;

    bool request_is_queued;

    struct list_head requests;

    struct list_head requests_queued;

    struct media_request_object req_obj;

};

2.控件:为一个实际的控件,有相应的ops,参数的范围等信息

/**

 * struct v4l2_ctrl - The control structure.

 *

 * @node:   The list node.

 * @ev_subs:    The list of control event subscriptions.

 * @handler:    The handler that owns the control.

 * @cluster:    Point to start of cluster array.

 * @ncontrols:  Number of controls in cluster array.

 * @done:   Internal flag: set for each processed control.

 * @is_new: Set when the user specified a new value for this control. It

 *      is also set when called from v4l2_ctrl_handler_setup(). Drivers

 *      should never set this flag.

 * @has_changed: Set when the current value differs from the new value. Drivers

 *      should never use this flag.

 * @is_private: If set, then this control is private to its handler and it

 *      will not be added to any other handlers. Drivers can set

 *      this flag.

 * @is_auto:   If set, then this control selects whether the other cluster

 *      members are in 'automatic' mode or 'manual' mode. This is

 *      used for autogain/gain type clusters. Drivers should never

 *      set this flag directly.

 * @is_int:    If set, then this control has a simple integer value (i.e. it

 *      uses ctrl->val).

 * @is_string: If set, then this control has type %V4L2_CTRL_TYPE_STRING.

 * @is_ptr: If set, then this control is an array and/or has type >=

 *      %V4L2_CTRL_COMPOUND_TYPES

 *      and/or has type %V4L2_CTRL_TYPE_STRING. In other words, &struct

 *      v4l2_ext_control uses field p to point to the data.

 * @is_array: If set, then this control contains an N-dimensional array.

 * @has_volatiles: If set, then one or more members of the cluster are volatile.

 *      Drivers should never touch this flag.

 * @call_notify: If set, then call the handler's notify function whenever the

 *      control's value changes.

 * @manual_mode_value: If the is_auto flag is set, then this is the value

 *      of the auto control that determines if that control is in

 *      manual mode. So if the value of the auto control equals this

 *      value, then the whole cluster is in manual mode. Drivers should

 *      never set this flag directly.

 * @ops:    The control ops.

 * @type_ops:   The control type ops.

 * @id: The control ID.

 * @name:   The control name.

 * @type:   The control type.

 * @minimum:    The control's minimum value.

 * @maximum:    The control's maximum value.

 * @default_value: The control's default value.

 * @step:   The control's step value for non-menu controls.

 * @elems:  The number of elements in the N-dimensional array.

 * @elem_size:  The size in bytes of the control.

 * @dims:   The size of each dimension.

 * @nr_of_dims:The number of dimensions in @dims.

 * @menu_skip_mask: The control's skip mask for menu controls. This makes it

 *      easy to skip menu items that are not valid. If bit X is set,

 *      then menu item X is skipped. Of course, this only works for

 *      menus with <= 32 menu items. There are no menus that come

 *      close to that number, so this is OK. Should we ever need more,

 *      then this will have to be extended to a u64 or a bit array.

 * @qmenu:  A const char * array for all menu items. Array entries that are

 *      empty strings ("") correspond to non-existing menu items (this

 *      is in addition to the menu_skip_mask above). The last entry

 *      must be NULL.

 *      Used only if the @type is %V4L2_CTRL_TYPE_MENU.

 * @qmenu_int:  A 64-bit integer array for with integer menu items.

 *      The size of array must be equal to the menu size, e. g.:

 *      :math:`ceil(\frac{maximum - minimum}{step}) + 1`.

 *      Used only if the @type is %V4L2_CTRL_TYPE_INTEGER_MENU.

 * @flags:  The control's flags.

 * @cur:    Structure to store the current value.

 * @cur.val:    The control's current value, if the @type is represented via

 *      a u32 integer (see &enum v4l2_ctrl_type).

 * @val:    The control's new s32 value.

 * @priv:   The control's private pointer. For use by the driver. It is

 *      untouched by the control framework. Note that this pointer is

 *      not freed when the control is deleted. Should this be needed

 *      then a new internal bitfield can be added to tell the framework

 *      to free this pointer.

 * @p_def:  The control's default value represented via a union which

 *      provides a standard way of accessing control types

 *      through a pointer (for compound controls only).

 * @p_cur:  The control's current value represented via a union which

 *      provides a standard way of accessing control types

 *      through a pointer.

 * @p_new:  The control's new value represented via a union which provides

 *      a standard way of accessing control types

 *      through a pointer.

 */

struct v4l2_ctrl {

    /* Administrative fields */

    struct list_head node;

    struct list_head ev_subs;

    struct v4l2_ctrl_handler *handler;

    struct v4l2_ctrl **cluster;

    unsigned int ncontrols;

    unsigned int done:1;

    unsigned int is_new:1;

    unsigned int has_changed:1;

    unsigned int is_private:1;

    unsigned int is_auto:1;

    unsigned int is_int:1;

    unsigned int is_string:1;

    unsigned int is_ptr:1;

    unsigned int is_array:1;

    unsigned int has_volatiles:1;

    unsigned int call_notify:1;

    unsigned int manual_mode_value:8;

    const struct v4l2_ctrl_ops *ops;

    const struct v4l2_ctrl_type_ops *type_ops;

    u32 id;

    const char *name;

    enum v4l2_ctrl_type type;

    s64 minimum, maximum, default_value;

    u32 elems;

    u32 elem_size;

    u32 dims[V4L2_CTRL_MAX_DIMS];

    u32 nr_of_dims;

    union {

        u64 step;

        u64 menu_skip_mask;

    };

    union {

        const char * const *qmenu;

        const s64 *qmenu_int;

    };

    unsigned long flags;

    void *priv;

    s32 val;

    struct {

        s32 val;

    } cur;

    union v4l2_ctrl_ptr p_def;

    union v4l2_ctrl_ptr p_new;

    union v4l2_ctrl_ptr p_cur;

};

3.控件引用:对一个控件的引用

/**

 * struct v4l2_ctrl_ref - The control reference.

 *

 * @node:   List node for the sorted list.

 * @next:   Single-link list node for the hash.

 * @ctrl:   The actual control information.

 * @helper: Pointer to helper struct. Used internally in

 *      ``prepare_ext_ctrls`` function at ``v4l2-ctrl.c``.

 * @from_other_dev: If true, then @ctrl was defined in another

 *      device than the &struct v4l2_ctrl_handler.

 * @req_done:   Internal flag: if the control handler containing this control

 *      reference is bound to a media request, then this is set when

 *      the control has been applied. This prevents applying controls

 *      from a cluster with multiple controls twice (when the first

 *      control of a cluster is applied, they all are).

 * @valid_p_req: If set, then p_req contains the control value for the request.

 * @p_req:  If the control handler containing this control reference

 *      is bound to a media request, then this points to the

 *      value of the control that must be applied when the request

 *      is executed, or to the value of the control at the time

 *      that the request was completed. If @valid_p_req is false,

 *      then this control was never set for this request and the

 *      control will not be updated when this request is applied.

 *

 * Each control handler has a list of these refs. The list_head is used to

 * keep a sorted-by-control-ID list of all controls, while the next pointer

 * is used to link the control in the hash's bucket.

 */

struct v4l2_ctrl_ref {

    struct list_head node;

    struct v4l2_ctrl_ref *next;

    struct v4l2_ctrl *ctrl;

    struct v4l2_ctrl_helper *helper;

    bool from_other_dev;

    bool req_done;

    bool valid_p_req;

    union v4l2_ctrl_ptr p_req;

};

使用

1.内核底层数据结构填充

v4l2_device_register_subdev注册子设备时会调用如下接口:

    /* This just returns 0 if either of the two args is NULL */

    err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler,

                    NULL, true);

v4l2_dev将子设备挂载在ctrl_handler下的所有ctrl控件新建ctrl_ref(handler_new_ref),加入到自己ctrl_refs链表及buckets桶数据结构中,至此v4l2_dev可以拿到所有控件

2.上层使用接口

使用cmd=VIDIOC_G_CTRL/VIDIOC_S_CTRL,调用ioctl,参数传入struct v4l2_control,id填入对应的控件id,V4L2_CID_XXXX

/*

 *  C O N T R O L S

 */

struct v4l2_control {

    __u32            id;

    __s32            value;

};

   

IOCTL_INFO(VIDIOC_G_CTRL, v4l_g_ctrl, v4l_print_control, INFO_FL_CTRL | INFO_FL_CLEAR(v4l2_control, id)),

    IOCTL_INFO(VIDIOC_S_CTRL, v4l_s_ctrl, v4l_print_control, INFO_FL_PRIO | INFO_FL_CTRL),

3.调用过程

set调用过程

ioctl---->v4l_s_ctrl------->v4l2_s_ctrl,v4l2_s_ctrl会通过传入的控件句柄查找对应的控件id,然后拿到相应的控件进行后续的参数判断,设置,调用控件的ops->s_ctrl

struct v4l2_ctrl *ctrl = v4l2_ctrl_find(hdl, control->id);

六、media_entity

entity

嵌入到video_device/v4l2_subdev结构体中,是对一个硬件模块的媒体抽象,用于描述pipe连接

pad

entity模块中端口抽象,相当于一个硬件模块的一个数据流入口或出口,一个entity中有一个或多个pad

pad连接

1.最顶层节点回调complete创建vdev的sink pad与子设备source pad的连接

2.对于摄像头这类设备,子设备sink节点连接在bound回调中创建其与下一层子设备source pad的连接

3.创建连接使用函数media_create_pad_link

__media_entity_setup_link:可以改变link的flags

media查看工具

media-ctl可以查看pipeline,并可以以文本或者图形化的拓扑结构展示

1.media-ctl -d /dev/media0 -p

显示entity及entity下的pads,并展示pads之间的连接情况

2.media-ctl -d /dev/media0 --print-dot >./media0.dot

sudo apt install graphviz安装dot工具,转化成图形方式

dot -Tpng ./media0.dot -o /media0.png

图6-1:

图6-2:

pipeline

/**

 * struct media_graph - Media graph traversal state

 *

 * @stack:      Graph traversal stack; the stack contains information

 *          on the media pads to be walked and the links through

 *          which they were reached.

 * @stack.pad:      pointer to &struct media_pad at the graph.

 * @stack.link:     pointer to &struct list_head.

 * @ent_enum:       Visited entities

 * @top:        The top of the stack

 */

struct media_graph {

    struct {

        struct media_pad *pad;

        struct list_head *link;

    } stack[MEDIA_ENTITY_ENUM_MAX_DEPTH];

    struct media_entity_enum ent_enum;

    int top;

};

/**

 * struct media_pipeline - Media pipeline related information

 *

 * @streaming_count:    Streaming start count - streaming stop count

 * @graph:      Media graph walk during pipeline start / stop

 */

struct media_pipeline {

    int streaming_count;

    struct media_graph graph;

};

media_pipeline_start最终动作:

        media_entity_for_each_routed_pad(pad, iter) {

            if (iter->pipe && iter->pipe != pipe) {

                pr_err("Pipe active for %s. Can't start for %s\n",

                       entity->name, iter->entity->name);

                ret = -EBUSY;

            } else {

                iter->pipe = pipe;

            }

            iter->stream_count++;

        }

给链路上的pad标记stream状态也就是stream_count++,赋值struct media_pipeline指针,成员graph可以查到所有相关entity,那这个状态标记有什么用呢,可以看到在setup_link时会判断该值是否为0,也就是说pipe开启下,整个链路不允许改变。

int __media_entity_setup_link(struct media_link *link, u32 flags)

......

    if (!(link->flags & MEDIA_LNK_FL_DYNAMIC) &&

        (source->stream_count || sink->stream_count))

        return -EBUSY;

......

media_pipeline_start调用的相关函数:

media_graph_walk_start:将pad入栈,pad对应entity的第一个link入栈

media_graph_walk_iter:扫描当前栈顶entity link链路与当前pad有相连的远端pad并入栈

media_graph_walk_next:扫描一条完整链路

七、v4l2 buffer管理

见图8-2

八、完整数据结构

图复杂,分两个图,一个是设备间的关系图,一个是buffer的关系图

图8-1:

图8-2:

参考文章

Linux内核V4L2架构-CSDN博客

V4L2驱动框架详解_linux_代码撸起-华为云开发者联盟

https://www.cnblogs.com/LoyenWang/p/15456230.html

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值