一、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(¬ifier->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, ¬ifier->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, ¬ifier->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(¬ifier->waiting);
INIT_LIST_HEAD(¬ifier->done);
mutex_lock(&list_lock);
list_for_each_entry(asd, ¬ifier->asd_list, asd_list) {
ret = v4l2_async_notifier_asd_valid(notifier, asd, i++);
if (ret)
goto err_unlock;
list_add_tail(&asd->list, ¬ifier->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(¬ifier->list, ¬ifier_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: