linux v4l2架构

[复制链接]
发表于 2026-2-12 02:07:46 | 显示全部楼层 |阅读模式
一、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("ipe 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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表