本系列文章基于linux内核版本
4.1.15
分析media子系统下的V4L2。先分析组成V4L2的核心数据结构以及各组成元素的含义和作用。相关文章:
❤(1)《【V4L2】v4l2框架分析之video_device》
❤(2)《V4L2-PCI驱动程序样例分析(一)》
相关源码文件:
对于想要接入V4L2子系统的设备,每个设备实例都由一个struct v4l2_device
表示。对于简单的设备可以只分配这个结构,但大多数情况下,可以把这个结构嵌入到更大的结构中。v4l2_device
定义如下(/include/media/v4l2-device.h):
struct v4l2_device {
struct device *dev; //指向struct device的指针。
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_device *mdev; //指向struct media_device的指针,值可以为NULL。struct media_device表示一个媒体设备。
#endif
struct list_head subdevs; //用来跟踪注册的子设备。
spinlock_t lock; //自旋锁。用于锁定这个结构体;如果这个结构体被嵌入到一个更大的结构体中,也可以被驱动程序使用。
char name[V4L2_DEVICE_NAME_SIZE]; //唯一的设备名称,默认是驱动程序名称+总线ID
void (*notify)(struct v4l2_subdev *sd,
unsigned int notification, void *arg); //被一些子设备调用,可用于执行通知操作。
struct v4l2_ctrl_handler *ctrl_handler; //control处理程序,可以为NULL。
struct v4l2_prio_state prio; //设备优先级状态。
struct mutex ioctl_lock; //互斥锁。
struct kref ref; //用于跟踪对这个结构体的引用。
void (*release)(struct v4l2_device *v4l2_dev); //当ref的计数值变为0时调用这个函数。
};
在实际开发中,需使用下列函数:
v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
注册v4l2_device
设备实例。
函数实现如下:
该函数会初始化v4l2_device
结构。如果dev->driver_data
字段是NULL,他将被连接到v4l2_dev
。如果驱动程序想要与媒体设备框架集成,则需要手动设置dev->driver_data
,用于指向v4l2_device
实例的驱动程序的特定设备结构。这是通过在注册V4L2设备实例之前调用dev_set_drvdata()
实现的。还必须设置v4l2_device
结构的mdev字段,用于指向初始化和注册的媒体设备实例。
如果v4l2_dev->name
为空,那么它将被设置为派生自dev的值,如果在调用v4l2_device_register()之前已经设置了v4l2_dev->name
,那么它将不受到影响。如果dev为NULL,那么在调用v4l2_device_register
之前,则必须设置v4l2_dev->name
参数。
对于v4l2_device_register()
函数,第一个’dev’参数通常是指向pci_dev
、usb_interface
或platform_device
的设备指针。dev很少为NULL,但是ISA设备或一个设备创建多个PCI设备时会出现dev为NULL的情况,因此不可能将v4l2_dev与特定的父设备进行关联。
在实际驱动程序设计中,可以提供一个
notify()
回调函数,子设备可以调用它来通知事件。是否需要设置notify回调函数取决于子设备。子设备支持的任何通知都必须在include/media/subdevice.h
的头文件中定义。
在驱动程序设计中,使用:
v4l2_device_unregister(struct v4l2_device *v4l2_dev);
来移除注册的设备。如果dev->driver_data
字段指向v4l2_dev
,那么dev->driver_data
将被重置为NULL。移除操作也会自动从设备上移除所有子设备。
如果有一个可热插拔的设备(例如USB设备),那么当断开连接时,父设备就会失效。因为v4l2_device有一个指向父设备的指针,所以它也必须被清除,以标记父设备已经消失。这时候需要调用:
v4l2_device_disconnect(struct v4l2_device *v4l2_dev);
该函数不会取消注册的子设备,所以仍然需要调用v4l2_device_unregister()
函数。如果驱动程序不可以热插拔,则不需要调用v4l2_device_disconnect()
。
有时需要遍历由特定驱动程序注册的所有设备。如果多个设备驱动程序使用相同的硬件,通常会出现这种情况:例如,ivtvfb驱动程序是一个使用ivtv硬件的帧缓冲区驱动程序。同样的情况也适用于alsa驱动。
可以使用下列所示的代码遍历所有已经注册的设备:
static int callback(struct device *dev, void *p)
{
struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);
/* 检测该设备是否已经被初始化 */
if (v4l2_dev == NULL)
return 0;
...
return 0;
}
int iterate(void *p)
{
struct device_driver *drv;
int err;
/* Find driver 'ivtv' on the PCI bus.
pci_bus_type is a global. For USB busses use usb_bus_type. */
drv = driver_find("ivtv", &pci_bus_type);
/* iterate over all ivtv device instances */
err = driver_for_each_device(drv, NULL, p, callback);
put_driver(drv);
return err;
}
如果有多个设备节点,那么很难知道何时为可热插拔设备注销v4l2_device是安全的操作。因此,v4l2_device
支持引用计数:在video_register_device()被调用时增加引用计数值,在设备节点被释放时减少引用计数值,当引用计数值达到零时,则会调用v4l2_device
的release()
回调。在实际驱动程序设计中,则可以在这个回调函数中执行最后的清理操作。
如果创建了其他设备节点(例如ALSA),那么也可以通过调用:
void v4l2_device_get(struct v4l2_device *v4l2_dev);
或者:
int v4l2_device_put(struct v4l2_device *v4l2_dev);
手动增加、减少引用计数。
由于初始引用计数是1,所以还需要在
disconnect()
或remove()
回调函数(例如PCI设备)中调用v4l2_device_put()
,否则引用计数将不会达到0。
参考资料:
https://docs.kernel.org/driver-api/media/v4l2-device.html