Linux下PCI设备驱动开发详解

日期 内核版本 架构 作者 内容
2018-8-13 Linux-2.6.32

X86

Bystander Linux中PCI驱动开发

一、PCI总线描述

     PCI是CPU和外围设备通信的高速传输总线。普通PCI总线带宽一般为132MB/s(在32bit/33Mhz下)或者264MB/s(在32bit/66Mhz下)。

       PCI总线体系结构是一种层次式的体系结构,PCI桥设备占据着重要的地位,它将父总线与子总线连接在一起,从而使整个系统看起来像一颗倒置的树形结构。

       PCI桥包括以下几种:

       Host/PCI桥:用于连接CPU与PCI根总线,在PC中,内存控制器也通常被集成到Host/PCI桥设备芯片,Host/PCI桥通常被称  为“北桥芯片组”。

       PCI/ISA桥:用于连接旧的ISA总线。PCI/ISA桥也被称为“南桥芯片组”。

       PCI-to-PCI桥:用于连接PCI主总线与次总线。

二、PCI配置空间访问

PCI有3种地址空间:PCI/IO空间、PCI内存地址空间和PCI配置空间。

PCI配置空间(共为256字节):

  1. 制造商标识:由PCI组织分配给厂家。
  2. 设备标识:按产品分类给本卡编号。
  3. 分类码:本卡功能分类码。
  4. 申请存储器空间:PCI卡内有存储器或以存储器编址的寄存器和I/O空间,为使驱动程序和应用程序能访问它们。配置空间的基地址寄存器用于此目的。
  5. 申请I/O空间:配置空间基地址寄存器用来进行系统I/O空间申请。
  6. 中断资源申请:向系统申请中断资源。

内核为驱动提供的函数访问配置空间:

pci_read_config_byte/word/dword(struct pci_dev *pdev, int offset, int *value);
pci_write_config_byte/word/dword(struct pci_dev *pdev, int offset, int *value);

PCII/O和内存空间:

获取I/O或内存资源:

pci_resource_start(struct pci_dev *dev,  int bar);/*Bar值的范围为0-5*/

  该函数返回六个 PCI I/O 区域之一的第一个地址(内存地址或 I/O 端口编号)。

pci_resource_end(struct pci_dev *dev,  int bar)   /* Bar值的范围为0-5*/

  该函数返回第 bar 个 I/O 区域的后一个地址。注意这是最后一个可用的地址,而不是该区域之后的第一个地址。

pci_resource_flags(struct pci_dev *dev,  int bar)  /* Bar值的范围为0-5 */

该函数返回资源关联的标志。资源标志用来定义单个资源的某些特性。对与 PCI I/O 区域关联的 PCI 资源,该信息从基地址寄存器中获得,但对其它与 PCI 设备无关的资源,它可能来自任何地方。所有的资源标志定义在 <linux/ioport.h> 中,下面列出其中最重要的几个:

IORESOURCE_IO 

IORESOURCE_MEM 

如果对应的 I/O 区域存在,将设置上面标志中的一个,而且只有一个。 

IORESOURCE_PREFETCH 

IORESOURCE_READONLY 

上述标志定义内存区域是可预取的,或者是写保护的。对 PCI 资源来讲,从来不会设置后面的那个标志。通过使用 pci_resource_flags函数,设备驱动程序可完全忽略底层的 PCI 寄存器,因为系统已经使用这些寄存器构建了资源信息。

申请/释放I/O或内存资源:

int pci_request_regions(struct pci_dev *pdev, const char *res_name);

void pci_release_regions(struct pci_dev *pdev);

获取/设置驱动私有数据:

void *pci_get_drvdata(struct pci_dev *pdev);

void pci_set_drvdata((struct pci_dev *pdev, void *data);

    使能/禁止PCI设备:

int pci_enable_device(struct pci_dev *pdev);

int pci_disable_device(struct pci_dev *pdev);

设置主总线为DMA模式:

void pci_set_master(struct pci_dev *pdev);

      寻找指定总线指定槽位的PCI设备:

struct pci_dev *pci_find_slot (unsigned int bus,unsigned int devfn);

设置PCI能量管理状态:

int pci_set_power_state(struct pci_dev *pdev, pci_power_t state);

在设备的能力中表中找出指定能力:

int pci_find_capability(struct pci_dev *pdev, int cap);

启用设备内存写无效事务:

int pci_set_mwi(struct pci_dev *pdev);

禁用设备内存写无效事务:

void pci_clear_mwi(struct pci_dev *pdev);

三、PCI设备驱动组成

PCI本质上就是一种总线,具体的PCI设备可以是字符设备、网络设备、USB等,所以PCI设备驱动应该包含两部分:

  1. PCI驱动
  2. 根据需求的设备驱动

根据需求的设备驱动是最终目的,PCI驱动只是手段帮助需求设备驱动达到最终目的而已。换句话说PCI设备驱动不仅要实现PCI驱动还要实现根据需求的设备驱动。

 

PCI驱动注册与注销:

int pci_register_driver(struct pci_driver * driver);

int pci_unregister_driver(struct pci_driver * driver);

pci_driver结构体:

struct pci_driver {
struct list_head node;

char *name;/* 描述该驱动程序的名字*/

struct moudule *owner;

const struct pci_device_id *id_table;/* 指向设备驱动程序感兴趣的设备ID的一个列表,包括:厂商ID、设备ID、子厂商ID、子设备ID、类别、类别掩码、私有数据*/

int   (*probe)   (struct pci_dev *dev, const struct pci_device_id *id);/* 指向一个函数对于每一个与id_table中的项匹配的且未被其他驱动程序处理的设备在执行pci_register_driver时候调用此函数或者如果是以后插入的一个新设备的话,只要满足上述条件也调用此函数*/

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);/* 使设备能够从挂起态产生唤醒事件*/
};

四、小结

       PCI总线是目前应用广泛的计算机总线标准,而且是一种兼容性最强、功能最全的计算机总线。而Linux作为一种新的操作系统,同时也为PCI总线与各种新型设备互连成为可能。由于Linux源码开放,因此给连接到PCI总线上的任何设备编写驱动程序变得相对容易。本文介绍如何编写Linux下的PCI驱动程序的关键函数接口。

 

 

 

发布了15 篇原创文章 · 获赞 21 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_42092278/article/details/81638530