备注:
1. Kernel版本:5.4
2. 使用工具:Source Insight 4.0
V4L2主设备实例使用struct v4l2_device结构体表示,v4l2_device是V4L2子系统的入口,管理着V4L2子系统的主设备和从设备。简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体嵌入到一个更大的结构体中。需要与媒体框架整合的驱动必须手动设置dev->driver_data,指向包含v4l2_device结构体实例的驱动特定设备结构体。这可以在注册V4L2设备实例前通过dev_set_drvdata()函数完成。同时必须设置v4l2_device结构体的mdev域,指向适当的初始化并注册过的media_device实例。
对于视频设备,Camera控制器可以视为主设备,接在Camera控制器上的摄像头可以视为从设备。V4L2子系统使用v4l2_device结构体管理设备,设备的具体操作方法根据设备类型决定,若是视频设备,则需要注册video_device结构体,并提供相应的操作方法。
//源码:include/media/4l2-device.h
struct v4l2_ctrl_handler;
/**
* struct v4l2_device - main struct to for V4L2 device drivers
*
* @dev: pointer to struct device.
* @mdev: pointer to struct media_device, may be NULL.
* @subdevs: used to keep track of the registered subdevs
* @lock: lock this struct; can be used by the driver as well
* if this struct is embedded into a larger struct.
* @name: unique device name, by default the driver name + bus ID
* @notify: notify operation called by some sub-devices.
* @ctrl_handler: The control handler. May be %NULL.
* @prio: Device's priority state
* @ref: Keep track of the references to this struct.
* @release: Release function that is called when the ref count
* goes to 0.
*
* Each instance of a V4L2 device should create the v4l2_device struct,
* either stand-alone or embedded in a larger struct.
*
* It allows easy access to sub-devices (see v4l2-subdev.h) and provides
* basic V4L2 device-level support.
*
* .. note::
*
* #) @dev->driver_data points to this struct.
* #) @dev might be %NULL if there is no parent device
*/
struct v4l2_device {
/* dev->driver_data points to this struct.
Note: dev might be NULL if there is no parent device
as is the case with e.g. ISA devices. */
struct device *dev; // 父设备
struct media_device *mdev; // 指向media设备
/* used to keep track of the registered subdevs */
// 注册的子设备的v4l2_subdev结构体都挂载此链表中
struct list_head subdevs; // subdev链表
/* lock this struct; can be used by the driver as well if this
struct is embedded into a larger struct. */
// 同步用的自旋锁
spinlock_t lock;
/* unique device name, by default the driver name + bus ID */
// 独一无二的设备名称,默认使用driver name + bus ID
char name[V4L2_DEVICE_NAME_SIZE]; //设备名
/* notify callback called by some sub-devices. */
// 被一些子设备回调的通知函数,但这个设置与子设备相关。子设备支持的任何通知必须在
// include/media/subdevice.h 中定义一个消息头。
void (*notify)(struct v4l2_subdev *sd,
unsigned int notification, void *arg);//subdev回调函数
/* The control handler. May be NULL. */
// 提供子设备(主要是video和ISP设备)在用户空间的特效操作接口,
// 比如改变输出图像的亮度、对比度、饱和度等等
struct v4l2_ctrl_handler *ctrl_handler; //私有的控制接口
/* Device's priority state */
struct v4l2_prio_state prio;
/* Keep track of the references to this struct. */
// struct v4l2_device结构体的引用计数,等于0时才释放
struct kref ref;
/* Release function that is called when the ref count goes to 0. */
// 引用计数ref为0时,调用release函数进行释放资源和清理工作
void (*release)(struct v4l2_device *v4l2_dev);
};
框架结构体(media_device)
与驱动结构体非常类似,参考上面的解释,这里不再赘述。v4l2 框架也可以整合到 media framework 里面。如果驱动程序设置了 v4l2_device 的 mdev 成员,那么子设备与 video 节点都会被自动当作 media framework 里的 entitiy 抽象。
v4l2_device 结构体
每一个设备实例都被抽象为一个 v4l2_device 结构体。一些简单的设备可以仅分配一个 v4l2_device 结构体即可,但是
大多数情况下需要将该结构体嵌入到一个更大的结构体(custom_v4l2_dev)里面。必须用 v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev); 来注册设备实例。该函数会初始化传入的 v4l2_device 结构体,如果 dev->driver_data 成员为空的话,该函数就会设置其指向传入的 v4l2_dev 参数。
集成 media framework
如果驱动想要集成 media framework 的话,就需要人为地设置 dev->driver_data 指向驱动适配的结构体(该结构体由
驱动自定义- custom_v4l2_dev,里面嵌入 v4l2_device 结构体)。在注册 v4l2_device 之前就需要调用 dev_set_drvdata 来完成设置。并且必须设置 v4l2_decice 的 mdev 成员指向注册的 media_device 结构体实例。
设备节点的命名
如果 v4l2_device 的 name 成员为空的话,就按照 dev 成员的名称来命名,如果 dev 成员也为空的话,就必须在注册 v4l2_device 之前设置它的 name 成员。可以使用 v4l2_device_set_name 函数来设置 name 成员,该函数会基于驱动名以及驱动实例的索引号来生成 name 成员的名称,类似于 ivtv0、ivtv1 等等,如果驱动名的最后一个字母是整数的话,生成的名称就类似于cx18-0、cx18-1等等,该函数的返回值是驱动实例的索引号。
回调函数与设备卸载
还可以提供一个 notify() 回调函数给 v4l2_device 接收来自子设备的事件通知。当然,是否需要设置该回调函数取决于子设备是否有向主设备发送通知事件的需求。v4l2_device 的卸载需调用到 v4l2_device_unregister 函数。在该函数被调用之后,如果 dev->driver_data 指向 v4l2_device 的话,该指针将会被设置为NULL。该函数会将所有的子设备全部卸载掉。如果设备是热拔插属性的话,当 disconnect 发生的时候,父设备就会失效,同时 v4l2_device 指向父设备的指针也必须被清除,可以调用 v4l2_device_disconnect 函数来清除指针,该函数并不卸载子设备,子设备的卸载还是需要调用到 v4l2_device_unregister 来完成。如果不是热拔插设备的话,就不必关注这些。
//源码:drivers/media/v4l2-core/v4l2-device.c
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
if (v4l2_dev == NULL)
return -EINVAL;
INIT_LIST_HEAD(&v4l2_dev->subdevs); //初始化subdev链表
spin_lock_init(&v4l2_dev->lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref); //初始化v4l2_dev计数值为1
get_device(dev);
v4l2_dev->dev = dev;
if (dev == NULL) { // 条件检查
/* If dev == NULL, then name must be filled in by the caller */
if (WARN_ON(!v4l2_dev->name[0]))
return -EINVAL;
return 0;
}
/* Set name to driver name + device name if it is empty. */
// 初始化v4l2_device设备名
if (!v4l2_dev->name[0])
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
dev->driver->name, dev_name(dev));
if (!dev_get_drvdata(dev))
dev_set_drvdata(dev, v4l2_dev);
return 0;
}
//源码:drivers/media/v4l2-core/v4l2-device.c
static void v4l2_device_release(struct kref *ref)
{
struct v4l2_device *v4l2_dev =
container_of(ref, struct v4l2_device, ref);
if (v4l2_dev->release)
v4l2_dev->release(v4l2_dev);
}
//源码:drivers/media/platform/sunxi/sun6i/sun6i_csi.c
static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
{
int ret;
csi->media_dev.dev = csi->dev;
strscpy(csi->media_dev.model, "Allwinner Video Capture Device",
sizeof(csi->media_dev.model));
csi->media_dev.hw_revision = 0;
media_device_init(&csi->media_dev);
v4l2_async_notifier_init(&csi->notifier);
ret = v4l2_ctrl_handler_init(&csi->ctrl_handler, 0);
if (ret) {
dev_err(csi->dev, "V4L2 controls handler init failed (%d)\n",
ret);
goto clean_media;
}
csi->v4l2_dev.mdev = &csi->media_dev;
csi->v4l2_dev.ctrl_handler = &csi->ctrl_handler;
ret = v4l2_device_register(csi->dev, &csi->v4l2_dev);
if (ret) {
dev_err(csi->dev, "V4L2 device registration failed (%d)\n",
ret);
goto free_ctrl;
}
......
csi->notifier.ops = &sun6i_csi_async_ops;
ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier);
if (ret) {
dev_err(csi->dev, "notifier registration failed\n");
goto clean_video;
}
return 0;
......
return ret;
}