Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3462578
  • 博文数量: 1450
  • 博客积分: 11163
  • 博客等级: 上将
  • 技术积分: 11101
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-25 14:40
文章分类

全部博文(1450)

文章存档

2017年(5)

2014年(2)

2013年(3)

2012年(35)

2011年(39)

2010年(88)

2009年(395)

2008年(382)

2007年(241)

2006年(246)

2005年(14)

分类: LINUX

2012-01-13 17:03:22

转自:http://blog.chinaunix.net/space.php?uid=24219701&do=blog&id=3057719


一, PCI相关数据结构说明

1.1 struct pci_driver

这个数据结构在文件/linux/pci.h里,这是Linux内核版本2.4之后为新型的PCI设备驱动程序所添加的,其中最主要的是用于识别设备的id_table结构,以及用于检测设备的函数probe( )和卸载设备的函数remove( )

struct pci_driver {

     struct list_head node;

     char *name;

     const struct pci_device_id *id_table;

     int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);

     void (*remove) (struct pci_dev *dev);

     int  (*save_state) (struct pci_dev *dev, u32 state);

     int  (*suspend)(struct pci_dev *dev, u32 state);

     int  (*resume) (struct pci_dev *dev);

     int  (*enable_wake) (struct pci_dev *dev, u32 state, int enable);

}; 

为创建一个正确的struct pci_driver 结构只有4个字段需要被初始化:name,id_table,proberemove

其中id_table初始化可以用到宏PCI_DEVICE(VENDOR_ID,DEVICE_ID)VENDOR_IDDEVICE_ID分别为设备和厂商编号,由板卡生产厂家指定。

static const struct pci_device_id mypci[] =

{

{

PCI_DEVICE(VENDOR_ID,DEVICE_ID)

},

{}

};

1.2 pci_dev

这个数据结构也在文件include/linux/pci.h里,它详细描述了一个PCI设备几乎所有的硬件信息,包括厂商ID、设备ID、各种资源等。可以根据需要使用其中的数据成员。 

struct pci_dev {

    struct list_head global_list;

    struct list_head bus_list;

    struct pci_bus  *bus;

    struct pci_bus  *subordinate;

    void        *sysdata;

    struct proc_dir_entry *procent;

    unsigned int    devfn;

    unsigned short  vendor;

    unsigned short  device;

    unsigned short  subsystem_vendor;

    unsigned short  subsystem_device;

    unsigned int    class;

    u8      hdr_type;

    u8      rom_base_reg;

    struct pci_driver *driver;

    void        *driver_data;

    u64     dma_mask;

    u32             current_state;

    unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];

    unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];

    unsigned int    irq;

    struct resource resource[DEVICE_COUNT_RESOURCE];

    struct resource dma_resource[DEVICE_COUNT_DMA];

    struct resource irq_resource[DEVICE_COUNT_IRQ];

    char        name[80];

    char        slot_name[8];

    int     active;

    int     ro;

    unsigned short  regs;

    int (*prepare)(struct pci_dev *dev);

    int (*activate)(struct pci_dev *dev);

    int (*deactivate)(struct pci_dev *dev);

};

二, PCI驱动基本框架

在用模块方式实现PCI设备驱动程序时,通常至少要实现以下几个部分:初始化设备模块、设备打开模块、数据读写和控制模块、中断处理模块、设备释放模块、设备卸载模块。下面给出一个典型的PCI设备驱动程序的基本框架

/* 指明该驱动程序适用于哪一些PCI设备 */

static struct pci_device_id demo_pci_tbl [] __initdata = {

    {PCI_VENDOR_ID_DEMO, PCI_DEVICE_ID_DEMO,

     PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEMO},

    {0,}

};

/* 对特定PCI设备进行描述的数据结构 */

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 pci_driver demo_pci_driver = {

    name:       demo_MODULE_NAME,    /* 设备模块名称 */

    id_table:   demo_pci_tbl,    /* 能够驱动的设备列表 */

    probe:      demo_probe,    /* 查找并初始化设备 */

    remove:     demo_remove    /* 卸载设备模块 */

    /* ... */

};

static int __init demo_init_module (void)

{

   pci_register_driver(&demo_pci_driver);

}

static void __exit demo_cleanup_module (void)

{

    pci_unregister_driver(&demo_pci_driver);

}

/* 加载驱动程序模块入口 */

module_init(demo_init_module);

/* 卸载驱动程序模块入口 */

module_exit(demo_cleanup_module);

三, PCI设备操作实现

3.1设备初始化

demo_init_module 中,用pci_register_driver( )函数来注册PCI设备的驱动程序,此时需要提供一个pci_driver结构,在该结构中给出的probe探测例程将负责完成对硬件的检测工作。 

probe函数中,需要实现以下几个功能:

1使能PCI

在 PCI 驱动的探测函数中在驱动可存取 PCI 设备的任何设备资源(I/O 区或者中断)之前驱动必须调用 pci_enable_device 函数:

int pci_enable_device(struct pci_dev *dev); 

这个函数实际上使能设备它唤醒设备以及在某些情况下也分配它的中断线和 I/O 例如这发生在 CardBus 设备上(它在驱动层次上已经完全和 PCI 等同了).

(2)请求PCI资源

在初始化中很重要的一个操作就是让系统为PCI分配资源,如I/O端口等。

pci_resource_regions(struct pci_dev  *dev, char * name);

(3)存取配置空间

因为微处理器无法直接存取配置空间计算机供应商不得不提供一个方法来完成它为存取配置空间, CPU 必须写和读 PCI 控制器中的寄存器但是确切的实现是依赖于供应商的并且和这个讨论无关因为 Linux提供了一个标准接口来存取配置空间.

对于驱动配置空间可通过8-, 16-或者 32-位数据传输来存取相关的函数原型定义于 :

int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val); 

int pci_read_config_word(struct pci_dev *dev, int where, u16 *val); 

int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val); 

从由 dev 所标识出的设备的配置空间读 , 2 个或者 个字节. where 参数是从配置空间开始的字节偏移从配置空间取得的值通过 val 指针返回并且这个函数的返回值是一个错误码. word 和 dword 函数转换刚刚读的值从小端到处理器的本地字节序因此你不必处理字节序.

int pci_write_config_byte(struct pci_dev *dev, int where, u8 val); 

int pci_write_config_word(struct pci_dev *dev, int where, u16 val); 

int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);

4)映射 I/O 和内存空间

每个PCI可以有1-6I/O或者内存空间,并且每块空间都有一个BAR寄存器与其空间首地址想对应。BAR0-6寄存器的最后一位为只读,为1则说明该空间为I/O,否则为MEM

下面以BAR0为例介绍如何映射空间到虚拟内存,以便用户访问。

LocalAddr0 = pci_resource_start(dev,0); //得到BAR0区域的开始地址

Map0 = (unsigned char *)ioremap(LocalAddr0, pci_resource_len(dev,0)); //将BAR0区域影射到内存虚拟地址

如果ioremap函数出现问题,可以尝试ioport_mappci_iomap。这样以后就可以针对Map0进行读写,而忽略具体硬件是I/O或是MEM的细节。

5)注册中断

int request_irq( unsigned int irq,

                irqreturn_t (*handler)(int, void *, struct pt_regs *),

                unsigned long flags,

                const char *dev_name,

                void *dev_id);

从 request_irq 返回给请求函数的返回值或者是 指示成功或者是一个负的错误码如同平常函数返回 -EBUSY 来指示另一个驱动已经使用请求的中断线是不寻常的.

3.2数据读写

使用在初始化过程中影射后的虚拟内存地址(Map0)进行读写,并转换到用户空间,最后传给应用程序。可以使用以下读写函数:

unsigned int ioread8(void *addr);

unsigned int ioread16(void *addr);

unsigned int ioread32(void *addr);

这里, addr 应当是从 ioremap 获得的地址(也许与一个整型偏移); 返回值是从给定 I/O 内存读取的.有类似的一系列函数来写 I/O 内存:

void iowrite8(u8 value, void *addr);

void iowrite16(u16 value, void *addr);

void iowrite32(u32 value, void *addr);

你可以使用这些函数的重复版本,

void ioread8_rep(void *addr, void *buf, unsigned long count);

void ioread16_rep(void *addr, void *buf, unsigned long count);

void ioread32_rep(void *addr, void *buf, unsigned long count);

void iowrite8_rep(void *addr, const void *buf, unsigned long count);

void iowrite16_rep(void *addr, const void *buf, unsigned long count);

void iowrite32_rep(void *addr, const void *buf, unsigned long count);

这些函数读或写从给定的 buf 到 给定的 addrcount 

需要操作一块 I/O 地址你可使用下列之一:

void memset_io(void *addr, u8 value, unsigned int count);

void memcpy_fromio(void *dest, void *source, unsigned int count);

void memcpy_toio(void *dest, void *source, unsigned int count);

3.3中断处理

PC的中断资源比较有限,只有0~15的中断号,因此大部分外部设备都是以共享的形式申请中断号的。当中断发生的时候,中断处理程序首先负责对中断进行识别,然后再做进一步的处理。 

irqreturn_t short_interrupt(int irq, void *dev, struct pt_regs *regs);

在该函数中实现中断服务,每次硬件检测到中断,都会调用该函数。

3.4释放设备

释放设备模块主要负责释放对设备的控制权,释放占用的内存和中断等,所做的事情正好与设备初始化相反

demo_cleanup_module中卸载PCI驱动:

pci_unregister_driver()

pci_driverremove中释放所请求的资源:

iounmap(Map0);

free_irq(int irq,pci_dev *dev)

pci_release_regions(pci_dev *dev)

3.5Makefile文件的编写

PWD = $(shell pwd)

KERNEL_SRC=/usr/src/linux

obj-m :=mypci.o

module_objs := mypci.o

all:

$(MAKE) C $( KERNEL_SRC) M=$(PWD) modules

clean:

rm *.ro

rm *.o
阅读(1053) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~