当前位置:文档之家› PCie驱动综述分析

PCie驱动综述分析

PCie驱动综述分析
PCie驱动综述分析

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);

}

相关主题
文本预览
相关文档 最新文档