第一篇博客,忆苦思甜下先,当然,我尽量长话短说,但说来话长倒也无妨......这是我研究生阶段写的第一个Linux驱动,一入Linux深似海,从此Windows是路人。那是2009年冬天的第一场雪,王老师兴冲冲地拿着一块板卡给我说,你不是会Linux吗?三个月,把它的Linux驱动写出来。而我此时所谓的“Linux”,也就是在虚拟机下装个RedHat,上上网听听歌,看看电影装装X。对linux内核一点概念都没有,更不用说驱动了,于是天天上网查资料,把师兄的新书都翻蓬松了,最后参照着一堆模板写下了人生的第一个驱动,写完了发现都不知道怎么编译......最后,当系统第一次显示驱动加载成功,打印出我的设备信息时,我高兴得一拳狠狠砸在在桌子上,把身边的同学都吓住了。其实这个时候驱动也就仅仅能加载成功而已,设备的很多功能都没实现,但是这个时候的成就感,比日后实现了驱动的全部功能与用户态应用程序时,都强烈得多。现在想来,真是感谢王老师对我的宏观指导点拨、胡师兄陪我的微观编译调试、以及黄师兄借我的那本《Linux设备驱动开发详解》。
好了,正文开始,驱动的开发环境是Linux2.4.18与实时补丁Rtai-24.1.11,硬件平台是i386,就是自己的PC机啦。最开始接到任务的时候,完全不知道干嘛,如今看来,就是要写一个PCI2040 PCI-DSP桥接卡的驱动,驱动包括设备与主机连接的PCI部分以及与板载DSP连接的HPI部分。
PCI驱动与其他驱动不同的地方主要在于PCI标准规定了每个PCI设备都有至少256字节的地址空间,其中前64字节是标准化的,其余字节是设备相关的。前64字节标准配置寄存器如下所示。驱动初始化时的很多步骤都会用到这个配置空间。
首先自然要定义驱动的入口函数和出口函数,
module_init(hpi_init_module);
module_exit(hpi_cleanup_module);
既然我们是要编写pci驱动,当然要定义一个pci_driver啦,具体实现如下
static struct pci_driver driver_details __devinitdata=
{
.name = DRV_NAME,
.id_table = compatible_ids,
.probe = pci_probe,
.remove = __devexit_p(pci_remove),
};
这里我们就第一次用到了pci配置空间了,因为系统是根据id_table这一栏来识别PCI设备的:id_table的包含数个pci_device_id,每个pci_device_id标明了对应PCI设备的制造商ID和设备ID,同样,设备上的配置空间的前四个字节包含了制造商ID和设备ID,当驱动存在一个ID与PCI设备配置空间ID相匹配时,设备驱动才会与硬件关联起来。根据PCI2040的datasheet,我定义的pci_device_id如下:
#define VENDOR_ID 0x104c
#define DEVICE_ID 0xac60
static struct pci_device_id compatible_ids [] __devinitdata=
{
{VENDOR_ID,DEVICE_ID,PCI_ANY_ID,PCI_ANY_ID,0,0,0},
{0,}
};
hpi_init_module()基本不需要干什么事情,只要调用函数pci_module_init(&driver_details)就可以了。这样,当系统检测到PCI设备时,会自动调用driver_details中的probe函数,也就是我们自己定义的pci_probe。驱动初始化的主要工作都在这个函数中进行。下面说明了一般PCI设备驱动需要的初始化工作,设备相关部分已被略去。
int __devinit pci_probe(struct pci_dev *dev, const struct pci_device_id * id)
{
......
//使能PCI设备
err=pci_enable_device(dev);
......
//申请PCI资源空间
err=pci_request_regions(dev, data->name);
......
//读取PCI设备的IO区域的首地址,每个PCI设备最多可有六个IO区域,第i个区域的首地址在PCI配置空间的0x10+(4*i)地址中,i=0~5。
start=pci_resource_start(dev, i);
//读取PCI设备的IO区域的尾地址,首尾地址相减后加一即可得到这篇IO区域的长度,长度在ioremap时会用到
end=pci_resource_end(dev, i);
//读取PCI设备的IO区域的标识,主要看是否是IO区域还是MEM区域
flags=pci_resource_flags(dev, i);
if(IS_VALID_PCI_RESOURCE(flags, IORESOURCE_MEM)){
data->mmio_addr_csr=ioremap(start, end-start+1);
}
//设备取得对PCI总线的控制
pci_set_master(dev);
......
//把设备的私有信息放入系统分配的PCI设备地址中
pci_set_drvdata(dev, data);
......
}
至此,本驱动的PCI部分已经完成,系统可以识别我们的PCI设备,但是如何与板载的DSP通信呢?我们还需要在pci_probe中完成对HPI部分的初始化。