标签:
一. 文档位置:linux-3.08/Documentation/video4linux/v4l2-framework.txt
二.翻译总结:
V4L2驱动程序往往是很复杂的,其原因是硬件的复杂性:大多数设备有多个IC,从而导出多个设备节点/dev,并且创建了non-V4L2设备如DVB、ALSA,FB,I2C和输入(IR)设备。
特别的是,V4L2驱动必须组织IC支持完成音/视频的混流/编码/解码,这就导致V4L2驱动更加复杂。通常,这些IC是由一个或多个I2C总线连接到主桥驱动,其他总线也可以被使用。所有这些被连接到主桥驱动的设备我们称为“sub-devices”【例如:Camera就是通过I2C总线连接到主CPU上,那么Camera可以被称为“sub-device”】。
很长一段时间,V4L2架构在V4L设备节点创建和video_buf对视频buffer的处理上受到video_device结构体的限制。
这意味着,所有的Driver必须完成设备实例的安装并且连接到sub-devices。
目前仍然由于框架的缺乏而存在大量的代码无法重构。
因此,该框架建立的基本构建模块所有Driver都会使用。并且和此相同的框架使得重构common code为可以被所有驱动是用的统一的函数更加容易。
1. 驱动框架:
所有的驱动需要以下结构体:
1) A struct for each device instance containing the device state.
2) A way of initializing and commanding sub-devices (if any).
3) Creating V4L2 device nodes (/dev/videoX, /dev/vbiX and /dev/radioX) and keeping track of device-node specific data.
4) Filehandle-specific structs containing per-filehandle data;
5) video buffer handling.
粗略示意图:
device instances
|
+-sub-device instances
|
\-V4L2 device nodes
|
\-filehandle instances
2. 框架框架
框架架构极其类似驱动架构:存在一个v4l2_device结构来表示device实例的数据,一个v4l2_subdev结构表示sud-device实例,video_device 结构体存储V4L2设备节点数据并且将来一个v4l2_fh结构体将处理跟踪文件操作句柄实例。
V4L2架构也可选的集成了media framework。如果一个驱动设置了结构体v4l2_device的mdev成员,sub-devices那么video nodes将会自动显示在media framework的入口处。
3. struct v4l2_device
每个设备实例是由一个v4l2_device代表(v4l2-device.h)。对于非常简单的设备可以仅仅分配这个结构体,但是通常你需要将这个结构体嵌入到一个更大的结构体中。
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; #if defined(CONFIG_MEDIA_CONTROLLER) struct media_device *mdev; #endif /* used to keep track of the registered subdevs */ 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 */ char name[V4L2_DEVICE_NAME_SIZE]; /* notify callback called by some sub-devices. */ void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg); /* The control handler. May be NULL. */ struct v4l2_ctrl_handler *ctrl_handler; /* Device‘s priority state */ struct v4l2_prio_state prio; /* BKL replacement mutex. Temporary solution only. */ struct mutex ioctl_lock; /* Keep track of the references to this struct. */ struct kref ref; /* Release function that is called when the ref count goes to 0. */ void (*release)(struct v4l2_device *v4l2_dev); };
注册设备实例:
v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
注册操作将会初始化v4l2_device结构体,如果dev->driver_data != NULL,那么将会被挂到v4l2_dev上。
/* kernel/drivers/media/video/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); // 初始化一个将要挂在v4l2_device上的子设备列表 spin_lock_init(&v4l2_dev->lock); // 初始化一个spin lock mutex_init(&v4l2_dev->ioctl_lock); // 初始化一个mutex,看来会在ioctrl上使用的mutex v4l2_prio_init(&v4l2_dev->prio); // 优先级 kref_init(&v4l2_dev->ref); // 将v4l2_dev的父设备指定为struct device *dev v4l2_dev->dev = dev; // 父设备 if (dev == NULL) { // 如果dev为空,v4l2->dev->name必须设置 /* If dev == NULL, then name must be filled in by the caller */ WARN_ON(!v4l2_dev->name[0]); return 0; } /* Set name to driver name + device name if it is empty. */ if (!v4l2_dev->name[0]) snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s", // 如果v4l2_dev->name为空,那么由dev_name(dev)来设置 driver name + device name dev->driver->name, dev_name(dev)); if (!dev_get_drvdata(dev)) dev_set_drvdata(dev, v4l2_dev); // 将dev和v4l2_dev关联:dev->p->driver_data = v4l2_dev return 0; } EXPORT_SYMBOL_GPL(v4l2_device_register);
int dev_set_drvdata(struct device *dev, void *data) { int error; if (!dev->p) { error = device_private_init(dev); if (error) return error; } dev->p->driver_data = data; return 0; } EXPORT_SYMBOL(dev_set_drvdata);
想要和media device framework集成的驱动程序需要手动设置dev->driver_data。如上述代码所看到的。同时也必须设置v4l2_device的medv成员指向正确初始化的且注册过的media_device实例。【关于struct media_device不是很懂】。
你也可以使用v4l2_device_set_name()来基于驱动名称设置一个名称和一个驱动全局的原子实例。这将会产生名称类似:ivtv0, ivtv1等。如果名称以数字结尾,那么会插入一个“-”:cx18-0,cx18-1等。这个函数返回最后的那个数字。【没用过】
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)的第一个参数“dev”通常是一个pci_dev,usb_interface or platform_device指针。dev为空是不常见的,但是在ISA设备或者当一个设备创建多个PCI设备,因此关联v4l2_dev作为一个特定的父设备是不可能的。
你也可以提供notify()回调函数,可以被sub-devices调用来通知你事件。是否设置取决于你的sub-device。sub-device支持的所有事件类型定义在头文件:include/media/<subdevice>.h中。
v4l2_device_unregister(struct v4l2_device *v4l2_dev);
/* kernel/drivers/media/video/v4l2-device.c */
void v4l2_device_disconnect(struct v4l2_device *v4l2_dev) { if (v4l2_dev->dev == NULL) return; if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev) // 如果v4l2->dev->p->driver_data 指向v4l2_dev dev_set_drvdata(v4l2_dev->dev, NULL); // 设置v4l2->dev->p->driver_data为NULL v4l2_dev->dev = NULL; // 设置v4l2->dev为NULL } EXPORT_SYMBOL_GPL(v4l2_device_disconnect);
/*
* 如果是一个热插拔设备(如USB设备),当断开连接的时候父设备变为无效。v4l2_device->dev将设置为空,并且标记父设备消失。这就通过v4l2_device_disconnect实现。
* 从代码可以看出,这个方法并不注销subdev,所以需要注销subdev需要调用v4l2_device_unregister函数。如果你的设备不支持热插拔无需调用v4l2_device_disconnect.
*/ void v4l2_device_unregister(struct v4l2_device *v4l2_dev) { struct v4l2_subdev *sd, *next; if (v4l2_dev == NULL) return; v4l2_device_disconnect(v4l2_dev); /* Unregister subdevs */ list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) { // 遍历v4l2_dev->subdevs这个链表,这个链表是组织所有的sub-dev v4l2_device_unregister_subdev(sd); #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) if (sd->flags & V4L2_SUBDEV_FL_IS_I2C) { struct i2c_client *client = v4l2_get_subdevdata(sd); /* We need to unregister the i2c client explicitly. We cannot rely on i2c_del_adapter to always unregister clients for us, since if the i2c bus is a platform bus, then it is never deleted. */ if (client) i2c_unregister_device(client); continue; } #endif #if defined(CONFIG_SPI) if (sd->flags & V4L2_SUBDEV_FL_IS_SPI) { struct spi_device *spi = v4l2_get_subdevdata(sd); if (spi) spi_unregister_device(spi); continue; } #endif } } EXPORT_SYMBOL_GPL(v4l2_device_unregister); void v4l2_device_unregister_subdev(struct v4l2_subdev *sd) { struct v4l2_device *v4l2_dev; /* return if it isn‘t registered */ if (sd == NULL || sd->v4l2_dev == NULL) return; v4l2_dev = sd->v4l2_dev; spin_lock(&v4l2_dev->lock); list_del(&sd->list); // 将sd从v4l2_dev->subdev链表中删除 spin_unlock(&v4l2_dev->lock); if (sd->internal_ops && sd->internal_ops->unregistered) sd->internal_ops->unregistered(sd); // 回掉 sd->internal_ops->unregistered sd->v4l2_dev = NULL; #if defined(CONFIG_MEDIA_CONTROLLER) if (v4l2_dev->mdev) media_device_unregister_entity(&sd->entity); #endif video_unregister_device(&sd->devnode); // 这个方法很关键,解除v4l2_subdev和video_device之间的关系 module_put(sd->owner); } EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev);
/* kernel/drivers/media/video/v4l2-dev.c */ /** * video_unregister_device - unregister a video4linux device * @vdev: the device to unregister * * This unregisters the passed device. Future open calls will * be met with errors. */ void video_unregister_device(struct video_device *vdev) { /* Check if vdev was ever registered at all */ if (!vdev || !video_is_registered(vdev)) return; mutex_lock(&videodev_lock); /* This must be in a critical section to prevent a race with v4l2_open. * Once this bit has been cleared video_get may never be called again. */ clear_bit(V4L2_FL_REGISTERED, &vdev->flags); mutex_unlock(&videodev_lock); device_unregister(&vdev->dev); } EXPORT_SYMBOL(video_unregister_device);
上边函数,目前看不懂。
有时,你需要迭代由驱动注册的所有设备。这通常是多个设备驱动程序使用相同的硬件。例如:ivtvfb驱动是一个使用ivtv硬件的framebuffer驱动。相同的比如ALSA驱动是真实的。
/* 用如下代码迭代所有的注册设备 */ static int callback(struct device *dev, void *p) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); /* test if this device was inited */ 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; }
有时,需要维护一个运行设备实例计数器。推荐方法如下:
static atomic_t drv_instance = ATOMIC_INIT(0); static int __devinit drv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id) { ... state->instance = atomic_inc_return(&drv_instance) - 1; } #define atomic_inc_return(v) (atomic_add_return(1, v)) /*将v原子自加1*/
如果有多个设备节点,那么安全的注销v4l2_device是困难的。因此,v4l2_device支持refcounting计数,就是v4l2_device的ref成员。refcount当调用video_register_device时会增加,当设备节点释放后会减小。当refcount为0时,v4l2_device release()被调用,可以在这个函数中做最后的清理工作。
如果其他设备节点(如:ALSA)被创建,你可以手动的增加和减少refcount,使用如下方法:
void v4l2_device_get(struct v4l2_device *v4l2_dev); // 增加引用计数 int v4l2_device_put(struct v4l2_device *v4l2_dev); // 较小引用计数
/* kernel/include/media/v4l2-device.h */ static inline void v4l2_device_get(struct v4l2_device *v4l2_dev) { kref_get(&v4l2_dev->ref); }
/* kernel/drivers/media/video/v4l2-device.c */ int v4l2_device_put(struct v4l2_device *v4l2_dev) { return kref_put(&v4l2_dev->ref, v4l2_device_release); // 当引用计数为0时,调用v4l2_device_release函数 } EXPORT_SYMBOL_GPL(v4l2_device_put);
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); }
标签:
原文地址:http://www.cnblogs.com/ronnydm/p/5771221.html