linux V4L2子系统——v4l2架构(2)之v4l2_device
备注:
1. Kernel版本:5.4
2. 使用工具:Source Insight 4.0
3. 参考博客:
(1)Linux V4L2子系统分析(一)
(2)linux v4l2 学习之-v4l2设备注册过程及各个设备之间的联系
(3)V4L2框架-v4l2 device
v4l2_device结构体概述
详见:
linux V4L2子系统——v4l2的结构体(1)之v4l2_device
V4L2主设备实例使用struct v4l2_device结构体表示,v4l2_device是V4L2子系统的入口,管理着V4L2子系统的主设备和从设备。简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体嵌入到一个更大的结构体中。需要与媒体框架整合的驱动必须手动设置dev->driver_data,指向包含v4l2_device结构体实例的驱动特定设备结构体。这可以在注册V4L2设备实例前通过dev_set_drvdata()函数完成。同时必须设置v4l2_device结构体的mdev域,指向适当的初始化并注册过的media_device实例。
在文件 drivers/media/platform/sunxi/sun6i-csi/sun6i-csi.c
中的 sun6i_csi_probe 函数将调用 sun6i_csi_v4l2_init
函数中的第一个参数为 struct sun6i_csi
,此为驱动自定义的结构体,那就是它了,那么它的定义如下:
// 源码:drivers/media/platform/sunxi/sun6i-csi/sun6i-csi.c
struct sun6i_csi {
struct device *dev;
struct v4l2_ctrl_handler ctrl_handler;
struct v4l2_device v4l2_dev;
struct media_device media_dev;
struct v4l2_async_notifier notifier;
/* video port settings */
struct v4l2_fwnode_endpoint v4l2_ep;
struct sun6i_csi_config config;
struct sun6i_video video;
};
主要函数解析
// 源码:include/media/v4l2-device.h
// 注册v4l2_device结构体,并初始化v4l2_device结构体
// dev-父设备结构体指针,若为NULL,在注册之前设备名称name必须被设置,
// v4l2_dev-v4l2_device结构体指针
// 返回值-0成功,小于0-失败
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
// 卸载注册的v4l2_device结构体
// v4l2_dev-v4l2_device结构体指针
void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
// 设置设备名称,填充v4l2_device结构体中的name成员
// v4l2_dev-v4l2_device结构体指针
// basename-设备名称基本字符串
// instance-设备计数,调用v4l2_device_set_name后会自加1
// 返回值-返回设备计数自加1的值
int v4l2_device_set_name(struct v4l2_device *v4l2_dev,
const char *basename, atomic_t *instance)
// 热插拔设备断开时调用此函数
// v4l2_dev-v4l2_device结构体指针
void v4l2_device_disconnect(struct v4l2_device *v4l2_dev);
v4l2_device_register函数
// 源码:drivers/media/platform/sunxi/sun6i-csi/sun6i-csi.c
static int sun6i_csi_v4l2_init(struct sun6i_csi *csi)
{
int ret;
......
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;
}
......
}
v4l2的设备注册其实没有注册设备或者设备驱动,只是将v4l2的大结构体与其他设备进行捆绑,
并保存v4l2设备对象到当前设备的driverdata中。此函数的作用就是将当前设备驱动和v4l2对象进行捆绑,便于挂接子设备。
如下面是v4l2注册设备的过程。
//源码:drivers/media/v4l2-core/v4l2-device.c
// 注册v4l2_device结构体,并初始化v4l2_device结构体
// dev-父设备结构体指针,若为NULL,在注册之前设备名称name必须被设置,
// v4l2_dev-v4l2_device结构体指针
// 返回值-0成功,小于0-失败
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中,
// 这样的话当前的设备就成了v4l2设备,
// 由于当前设备已经注册过了设备文件,
// 所以后续不需要在重新注册设备文件。
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设备的名字为空,则会将当前设备的名字拷贝为v4l2设备中。*/
if (!v4l2_dev->name[0])
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
dev->driver->name, dev_name(dev));
// 下面是非常重要的一步,即将v4l2设备对象,保存到dev设备中,
// 这个dev可以是platform、char、block等设,
// 我们可以使用dev_get_drvdata(dev)获取到v4l2对象。
if (!dev_get_drvdata(dev))
dev_set_drvdata(dev, v4l2_dev);
return 0;
}
使用v4l2_device_register注册v4l2_device结构体。如果v4l2_dev->name为空,则它将被设置为从dev中衍生出的值(为了更加精确,形式为驱动名后跟bus_id)。如果在调用v4l2_device_register前已经设置好了,则不会被修改。如果dev为NULL,则必须在调用v4l2_device_register前设置v4l2_dev->name。可以基于驱动名和驱动的全局atomic_t类型的实例编号,通过 v4l2_device_set_name() 设置name。这样会生成类似ivtv0、ivtv1等名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如:cx18-0、cx18-1等。dev参数通常是一个指向pci_dev、usb_interface或platform_device的指针,很少使其为NULL,除非是一个ISA设备或者当一个设备创建了多个PCI设备,使得v4l2_dev无法与一个特定的父设备关联。
使用v4l2_device_unregister卸载v4l2_device结构体。如果dev->driver_data域指向 v4l2_dev,将会被重置为NULL。主设备注销的同时也会自动注销所有子设备。如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。由于v4l2_device有一个指向父设备的指针必须被清除,同时标志父设备
已消失,所以必须调用v4l2_device_disconnect函数清理v4l2_device中指向父设备的dev指针。
v4l2_device_disconnect 并不注销主设备,因此依然要调用 v4l2_device_unregister 函数注销主设备。
其他相关函数
//源码:drivers/media/v4l2-core/v4l2-device.c
// 设置设备名称,填充v4l2_device结构体中的name成员
// v4l2_dev-v4l2_device结构体指针
// basename-设备名称基本字符串
// instance-设备计数,调用v4l2_device_set_name后会自加1
// 返回值-返回设备计数自加1的值
int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename,
atomic_t *instance)
{
int num = atomic_inc_return(instance) - 1;
int len = strlen(basename);
if (basename[len - 1] >= '0' && basename[len - 1] <= '9')
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
"%s-%d", basename, num);
else
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
"%s%d", basename, num);
return num;
}
//源码:drivers/media/v4l2-core/v4l2-device.c
// 热插拔设备断开时调用此函数
// v4l2_dev-v4l2_device结构体指针
void v4l2_device_disconnect(struct v4l2_device *v4l2_dev)
{
if (v4l2_dev->dev == NULL)
return;
if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev)
dev_set_drvdata(v4l2_dev->dev, NULL);
put_device(v4l2_dev->dev);
v4l2_dev->dev = NULL;
}
EXPORT_SYMBOL_GPL(v4l2_device_disconnect);
//源码:drivers/media/v4l2-core/v4l2-device.c
// 卸载注册的v4l2_device结构体
// v4l2_dev-v4l2_device结构体指针
void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
{
struct v4l2_subdev *sd, *next;
/* Just return if v4l2_dev is NULL or if it was already
* unregistered before. */
if (v4l2_dev == NULL || !v4l2_dev->name[0])
return;
v4l2_device_disconnect(v4l2_dev);
/* Unregister subdevs */
list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) {
v4l2_device_unregister_subdev(sd);
if (sd->flags & V4L2_SUBDEV_FL_IS_I2C)
v4l2_i2c_subdev_unregister(sd);
else if (sd->flags & V4L2_SUBDEV_FL_IS_SPI)
v4l2_spi_subdev_unregister(sd);
}
/* Mark as unregistered, thus preventing duplicate unregistrations */
v4l2_dev->name[0] = '\0';
}
EXPORT_SYMBOL_GPL(v4l2_device_unregister);