PCie 驱动
Pcie设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。
Pce的配置空间:
PCI有三个相互独立的物理地址空间:设备存储器地址空间、I/O地址空间和配置空间。配置空间是PCI所特有的一个物理空间。由于PCI支持设备即插即用,所以PCI设备不占用固定的内存地址空间或I/O地址空间,而是由操作系统决定其映射的基址。
系统加电时,BIOS检测PCI总线,确定所有连接在PCI总线上的设备以及它们的配置要求,并进行系统配置。所以,所有的PCI设备必须实现配置空间,从而能够实现参数的自动配置,实现真正的即插即用。
PCI总线规范定义的配置空间总长度为256个字节,配置信息按一定的顺序和大小依次存放。前64个字节的配置空间称为配置头,对于所有的设备都一样,配置头的主要功能是用来识别设备、定义主机访问PCI卡的方式(I/O访问或者存储器访问,还有中断信息)。其余的192个字节称为本地配置空间,主要定义卡上局部总线的特性、本地空间基地址及范围等。
?
一般来说,基于pcie总线的驱动,需要涉及到pci_driver pci_dev pci_device_id .
pci_device_id : 用于标识pcie设备,通过上图的厂商Id 设备Id 功能号等唯一确定一个pcie 设备,内核通过这个结构体确认驱动与设备是否匹配。
pci_dev : 一般pcie 设备都具有热拔插功能,当内核检测到有pcie设备插入时,会与相应的Pci_driver : 当有相应的设备匹配会调用驱动的相关方法,驱动中通常要做的是读出Base Adrress Register1-6 的值,这是pcies设备6个内存空间的基地址,然后通过ioremap方法映射成虚拟地址,至于6个内存空间的具体含义需要依赖于设备。
在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、
设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。例如
/* 指明该驱动程序适用于哪一些PCI设备*/
static struct pci_device_id demo_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG1) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810_IG3) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82810E_IG) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82815_CGC) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82845G_IG) },
{ 0, },
};
这个 pci_device_id 结构需要被输出到用户空间, 来允许热插拔和模块加载系统知道什么模块使用什么硬件设备. 宏 MODULE_DEVICE_TABLE 完成这个. 例如:
MODULE_DEVICE_TABLE(pci, demo_ids);
struct demo_card {
unsigned int magic;
/* 使用链表保存所有同类的PCI设备*/
struct demo_card *next;
/* ... */
}
/* 中断处理模块*/
static void demo_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/* ... */
}
/* 设备文件操作接口*/
static struct file_operations demo_fops = {
owner:THIS_MODULE,/* demo_fops所属的设备模块*/
read:demo_read,/* 读设备操作*/
write:demo_write,/* 写设备操作*/
ioctl:demo_ioctl,/* 控制设备操作*/
mmap:demo_mmap,/* 内存重映射操作*/
open:demo_open,/* 打开设备操作*/
release:demo_release/* 释放设备操作*/
/* ... */
};
/* 设备模块信息*/
static struct pci_driver demo_pci_driver = {
name:demo_MODULE_NAME,/* 设备模块名称*/
id_table:demo_idsl,/* 能够驱动的设备列表*/
probe:demo_probe,/* 设备与驱动匹配时调用
remove:demo_remove/* 卸载设备模块*/
/* ... */
};
3.初始化设备模块
在Linux系统下,想要完成对一个PCI设备的初始化,需要完成以下工作:
检查PCI总线是否被Linux内核支持;
检查设备是否插在总线插槽上,如果在的话则保存它所占用的插槽的位置等信息。
读出配置头中的信息提供给驱动程序使用。
当Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有PCI设备的拓扑结构,此后当PCI驱动程序需要对设备进行初始化时,一般都会调用如下的代码:
static int __init demo_init_module (void)
{
/* 检查系统是否支持PCI总线*/
if (!pci_present())
return -ENODEV;
/* 注册硬件驱动程序*/
if (!pci_register_driver(&demo_pci_driver)) {
pci_unregister_driver(&demo_pci_driver);
return -ENODEV;
}
/* ... */
return 0;
}
驱动程序首先调用函数pci_present( )检查PCI总线是否已经被Linux内核支持,如果系统支持PCI总线结构,这个函数的返回值为0,如果驱动程序在调用这个函数时得到了一个非0的返回值,那么驱动程序就必须得中止自己的任务了。调用pci_register_driver( )函数来注册PCI设备的驱动程序,此时需要提供一个pci_driver结构,在该结构中给出的probe探测例程将负责完成对硬件的检测工作。
static int __init demo_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
{
struct demo_card *card;
/* 启动PCI设备*/
if (pci_enable_device(pci_dev))
return -EIO;
/* 设备DMA标识*/
if (pci_set_dma_mask(pci_dev, DEMO_DMA_MASK)) {
return -ENODEV;
}
/* 在内核空间中动态申请内存*/
if ((card = kmalloc(sizeof(struct demo_card), GFP_KERNEL)) == NULL) {
printk(KERN_ERR "pci_demo: out of memory\n");
return -ENOMEM;
}
memset(card, 0, sizeof(*card));
/* 读取PCI配置信息*/
card->iobase = pci_resource_start (pci_dev, 1);
card->pci_dev = pci_dev;
card->pci_id = pci_id->device;
card->irq = pci_dev->irq;
card->next = devs;
card->magic = DEMO_CARD_MAGIC;
/* 设置成总线主DMA模式*/
pci_set_master(pci_dev);
/* 申请I/O资源*/
request_region(card->iobase, 64, card_names[pci_id->driver_data]);
return 0;
}
4.打开设备模块
在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申请控制权的时候,非阻塞方式遇忙返回,否则进程主动接受调度,进入睡眠状态,等待其它进程释放对设备的控制权。
static int demo_open(struct inode *inode, struct file *file)
{
/* 申请中断,注册中断处理程序*/
request_irq(card->irq, &demo_interrupt, SA_SHIRQ,
card_names[pci_id->driver_data], card)) {
/* 检查读写模式*/
if(file->f_mode & FMODE_READ) {
/* ... */
}
if(file->f_mode & FMODE_WRITE) {
/* ... */
}
/* 申请对设备的控制权*/
down(&card->open_sem);
while(card->open_mode & file->f_mode) {
if (file->f_flags & O_NONBLOCK) {
/* NONBLOCK模式,返回-EBUSY */
up(&card->open_sem);
return -EBUSY;
} else {
/* 等待调度,获得控制权*/
card->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE);
up(&card->open_sem);
/* 设备打开计数增1 */
MOD_INC_USE_COUNT;
/* ... */
}
}
}
在对资源的访问方式上,除了有I/O指令以外,还有对外设I/O内存的访问。对这些内存的操作一方面可以通过把I/O内存重新映射后作为普通内存进行操作,另一方面也可以通过总线主DMA(Bus Master DMA)的方式让设备把数据通过DMA传送到系统内存中。
释放设备模块主要负责释放对设备的控制权,释放占用的内存和中断等,所做的事情正好与打开设备模块相反:
static int demo_release(struct inode *inode, struct file *file)
{
/* ... */
/* 释放对设备的控制权*/
card->open_mode &= (FMODE_READ | FMODE_WRITE);
/* 唤醒其它等待获取控制权的进程*/
wake_up(&card->open_wait);
up(&card->open_sem);
/* 释放中断*/
free_irq(card->irq, card);
/* 设备打开计数增1 */
MOD_DEC_USE_COUNT;
/* ... */
}
8.卸载设备模块
卸载设备模块与初始化设备模块是相对应的,实现起来相对比较简单,主要是调用函数pci_unregister_driver( )从Linux内核中注销设备驱动程序:
static void __exit demo_cleanup_module (void)
{
pci_unregister_driver(&pci_driver);
}