1 引言
Linux 操作系统以其本身强大的性能、卓越的稳定性和开放源代码的优点正在得到越来越广泛的应用。设备驱动程序在linux内核中扮演着特殊的角色,它们是一个个独立的“黑盒子”,使得特定的硬件响应一个定义良好的内部编程接口,这些接口完全隐藏了设备的工作细节。用户的操作通过一组标准化的调用执行,而这些调用独立于特定的驱动程序。将这些调用映射到作用于实际硬件的设备特有操作上,则是驱动程序的任务[1]。
本文撰写的背景是源于我们自己开发的CDMA无线视频传输系统,该传输系统的视频采集模块使用Philip SAA7146+ SAA7111a,本文主要介绍linux环境下视频采集设备驱动程序的编写。
2 采集芯片简介[2][3]
开发驱动程序的第一步就是详细了解硬件设备的规格,这样才能具体操纵硬件,实现硬件特定的操作,因此首先介绍一下Philip SAA7146及7111A芯片。
Philip SAA7146是PCI总线控制设备,它负责初始化并处理PCI总线上的数据传输,在这个意义上说它是主设备;而7111a是从设备,它只是所谓的视频解码器,负责将模拟视频信号解码为数字比特流。对于后者,只需要通过I2C总线进行编程,而不必直接控制该设备。它并不具有总线控制能力,因此不能在PCI 总线上找到它。因此7111a并不需要专门的驱动程序,它是通过主控芯片(SAA7146)经由I2C总线来驱动的,我们只需要编写主控芯片的驱动程序即可。
3 V4L规范[4]
V4L与V4L2是Linux下开发视频采集设备驱动程序的一套规范,这套规范使用分层的方法给驱动程序的开发提供了清晰的模型和一致的接口。应用程序处于最上层,V4L或V4L2处于中间层,而实际的硬件设备处于下层。
3.1 V4L
V4L是Video for Linux的简写,它是Alan Cox为了给Linux下视频采集设备驱动程序的编写提供统一的接口而提出的一套规范(API),它将所有的视频采集设备的驱动程序都纳入它的管理之中,给驱动程序编写者带来极大的方便。
3.2 V4L2
鉴于V4L的种种不足,Bill Dirks重新设计了一套API和数据结构,并把它称作Video for Linux Two(V4L2)。与V4L相比,它的扩展性和灵活性都得到了极大的提高,并且支持的硬件设备也更多。但是也由于它对V4L做了彻底的改造,使得它与 V4L并不兼容。
V4L2是一个两层驱动结构:上层是videodev模块,当videodev初始化后,它把自己注册一个主设备号为81的字符设备,同时注册自己的字符驱动成员函数;下层是V4L2驱动程序,它实际上是videodev的客户端,videodev通过V4L2驱动程序的成员函数来调用V4L2驱动程序。当V4L2驱动程序初始化后,它把一个包含V4L2 驱动程序成员函数,次设备号以及其他相关信息的结构传递给videodev,从而把它要处理的设备注册到videodev。当应用程序触发了一个驱动程序调用时,控制权首先传递给videodev中的函数,videodev负责将应用程序传递的文件或i节点结构指针转化为相应V4L2结构的指针,并调用 V4L2驱动中的处理函数。
当V4L2驱动程序初始化时候,它首先会枚举它将处理的系统中的设备,然后为每个设备填充struct v4l2_device结构,并把指向该结构的指针传递给v4l2_register _device ()函数,该函数调用v4l2_device结构中的初始化函数对设备进行初始化。
Struct v4l2_device结构中的主要域说明如下:
Char name[32]:设备的名字,该名字会出现在/proc/Videodev文件中;
Int type:V4L2设备类型;
Int minor:设备得次设备号;
Int(*open)():当打开新的文件描述符时调用;
Int(*close)():当关闭文件描述符时调用;
Int(*read)():调用read();
Int(*write)():调用write();
Int(*ioctl)():调用ioctl();
Int(*mmap)():调用mmap();
Int(*poll)():调用select();
Int(*initialize)():当设备注册时调用;
Int busy:设备的打开计数,由videodev维护;
设备通过函数v4l2_unregister_device()取消注册;V4L2允许设备多次打开,上面的v4l2_device结构中的成员函数都具有一个id参数,该参数可以把设备的多次打开区分开来。
4 采集卡驱动程序的基本结构及实现
4.1 驱动程序基本结构
整个驱动程序分为三层:
模块Saa7146_v4l2直接操作硬件设备,它主要提供基于SAA7146芯片的采集设备的核心功能,也可将其称作核心驱动程序;同时驱动程序还提供了扩展机制,用于扩充核心驱动程序的功能,这样做的好处就是可以在extension模块当中实现自己想要的附加功能,而不用修改核心部分。显然,该扩展模块也对硬件设备具有完全的控制权。[page]
4.2 驱动程序的实现
下面讨论视频采集卡驱动程序的具体实现。
4.2.1 PCI驱动程序
本文采用的视频采集卡是基于PCI总线的,因此在这里首先讨论PCI驱动程序的结构。
PCI设备是无跳线设备,可在引导阶段自动配置。这样设备驱动程序必须能够访问设备中的配置信息以便完成初始化。对于PCI设备来说,这些工作无需探测就可以完成[1][5]。
所有的PCI设备都有至少256字节的地址空间,前64字节是标准化的,其余的是设备相关的。这个空间也叫做PCI配置空间,它包含厂商标识,设备标识,设备类别,基地址寄存器,中断引脚等等,编写PCI的驱动程序就是利用这些域与设备进行“沟通”。
尽管PCI设备千差万别,但是控制他们的结构基本是类似的。
1)为了正确注册到内核,所有的PCI驱动程序需要创建pci_driver结构体,该结构体由许多回调函数和变量组成。初始化该结构体如下:
struct pci_driver saa7146_v4l2_driver = {
name: "saa7146 v4l2",
id_table: saa7146_v4l2_pci_tbl,
probe: saa7146_v4l2_init_one,
remove: saa7146_v4l2_remove_one,
suspend: saa7146_v4l2_suspend,
resume: saa7146_v4l2_resume,
};
为了把struct pci_driver注册到PCI核心,需要调用以其为参数的pci_module_init函数:
pci_module_init(&saa7146_v4l2_driver);
2)如果上述函数返回为0,表示初始化成功,此时在驱动程序可以访问PCI设备的任何设备资源之前(I/O区域或中断),驱动程序必须调用pci_enable_device函数:
int pci_enable_device(struct pci_dev *dev);
3)上述函数将激活设备,此时驱动程序就需要读取或写入三个地址空间:内存,I/O端口,配置空间。Linux把I/O空间和内存空间都看作是系统的资源,使用前必须申请,即在系统中进行登记,避免资源使用的混乱。简述如下:
首先调用pci_resource_start()和pci_resource_len()函数获取资源信息,然后调用 request_mem_region()函数分配I/O内存区域,为了确保该内存对内核而言是可访问的,必须还要建立映射,映射的建立由 ioremap()函数完成,这样设备驱动程序就可以访问任意的I/O内存地址。
4)与设备通信,即通过访问I/O内存的函数,诸如writel(),readl()等;以及进行I2C操作的函数对SAA7111a进行初始化。
4.2.2 驱动模块的设计与实现
采集卡驱动程序主要由saa7146_v4l2,v4l2_extension,saa7111a三个模块构成,这三个模块都要调用i2c- core模块(Kernel提供)中的函数,即它们依赖于i2c-core模块;saa7111a模块具体负责通过I2C控制SAA7111a芯片,v4l2_extension模块依赖于saa7146_v4l2模块,它把自己注册到saa7146核心模块中。
重要数据结构声明
采集设备的扩展部分结构:
struct saa7146_v4l2_extension{
char name[64]; /* 设备名 */
struct saa7146_v4l2_extension_ioctls* ioctls;
u32 irq_mask; /* 扩展部分处理的IRQ */
void (*irq_func)(struct capture_device*, u32* irq_mask);
/* 扩展部分的函数主要就是ioctl()用于实现各种类型硬件控制 */
int (*ioctl)(void *id, unsigned int cmd, void *arg);
};
采集设备核心结构:
struct capture_device{
struct v4l2_device v4l2;
struct v4l2_capability capability;
struct pci_dev *pci_device;
……
}[page]
DMA结构:
struct saa7146_video_dma {
u32 base_odd;
u32 base_even;
u32 prot_addr;
u32 pitch;
u32 base_page;
u32 num_line_byte;
};
工作流程
v4l2_extension调用v4l2_register_device()函数注册设备,V4l2_register_device()函数进而调用v4l2_init_done()函数(v4l2_device结构中的int(*initialize)()字段已被初始化为该函数)通过写 I/O地址空间具体的初始化设备,设置采集图像的默认参数等。这时候设备已经做好了采集图像的准备工作。
下面通过典型的read一桢图像来分析具体的工作流程:
应用程序首先调用系统调用open()来打开设备,v4l2将该调用映射为初始化设备时已经设置好的v4l2_device结构中的 int(*open)(),在本文中即为v4l2_open();打开设备成功read一桢图像数据的命令,此时系统通过v4l2_device结构中已经设置好的int(*open)()字段调用相应的函数v4l2_read(),该调用负责分配内核内存缓冲区,并将采集到的数据从内核空间复制到用户空间,这样应用程序就获得了一桢数据;
当 v4l2_capability结构中的V4L2_CAP_STREAMING标志被设置时,这表明设备支持流采集。V4L2 的流驱动程序维护两个组织成FIFO的缓冲区队列:发送队列和接收队列。由于应用程序受到网络延迟,进程抢占或随机磁盘存储的影响,维护两个队列就可以把异步的视频采集或输出操作与应用程序分离开,从而降低丢失数据的可能性。设备采集到图像后可以用DMA 方式直接将数据放入应用程序分配好的缓冲区中,这就大大提升了整个系统的性能。
4.2.3 测试驱动程序[6]
首先编译上述模块,然后通过命令insmod链接进内核。用于测试的简单应用程序主体部分如下所示:
vid = open(device, O_RDONLY);/*打开设备*/
err = ioctl(vid, VIDIOC_QUERYCAP, &cap); /*查询设备支持的功能*/
err = ioctl(vid, VIDIOC_G_FMT, &fmt);/*设置采集图像的格式*/
data = malloc(fmt.fmt.pix.sizeimage);/*分配用户空间缓冲区*/
n = read(vid, data, fmt.fmt.pix.sizeimage);/*获取一桢数据*/
该应用程序运行后经检查得到了预期的结果,并且在基于该驱动程序的CDMA无线视频传输系统中满足了应用的需要,获得了理想的效果。
5 结论
本文作者创新点:详细阐述了Linux环境下利用V4L2API开发视频采集设备驱动程序的流程,并将该驱动程序实际的应用到我们自己研发的CDMA无线视频传输系统中,获得了满意的实时效果,在此也希望对从事同类开发的人员有所裨益。
参考文献
1 Alessandro Rubini & Jonathan Corbet,Linux device driver,2nd Edition,O’Reilly,2001.7
2 Philip SAA7146A datasheet,1998.4
3 Philip SAA7111A datasheet,1998.5
4 http://linux.bytesex.org/v4l2/
5 PCI SIG,PCI Local Bus Specification Revision 2.2,1998.12
6 王多智,嵌入式linux下sram驱动程序的开发原理及应用,微计算机信息,2005年第5期
上一篇:基于嵌入式WEB的AllLightSYS系统的设计与实现
下一篇:基于ETX与VxWorks的嵌入式车辆导航系统的设计
推荐阅读最新更新时间:2024-05-02 22:04