1.  介绍

Peripheral ComponentInterconnect (PCI,外围设备互联)。总线由电气接口、编程接口组成。主要讨论编程接口。最常用的总线,内核支持最好的总线。ISA裸金属总线,电子爱好者偏爱。

2.  PCI的特点

是一种完整的规范,定义计算机计算机不同部分之间的通信。

获取、访问PCI设备。

对比ISA总线三个目标:

比ISA有更好的性能。

尽可能平台无关的。

简化系统添加、删除外设。

支持32位、64位数据总线。

对驱动编写者最相关的是接口板的自动发现。PCI设备是无跳线设备,在系统引导阶段自动配置。包括设备的配置信息等一些工作都是自动完成的,不需要任何的探测。之后,驱动编写者就能够访问设备的配置信息,以便初始化设备。

3.  PCI寻址

每个PCI外设由bus:device.function一个16位地址标识。bus(8位)、device(5位)、function(3位)。单个总共256个总线、每个总线最多32个设备、每个设备最多8个功能(比如声音功能)。linux为了扩展总线数量,提供domain(16位)。系统把PCI设备抽象为pci_dev结构,因此不需要访问这些二进制地址。

当前的工作站一般都配有2个以上的pci总线。不同PCI总线之间通过PCI桥连接(一个特殊的PCI设备)。PCI系统的整体布局是一个树状的结构。每个总线都连接上一级总线,一直到根总线0.

可以使用lspci命令查看当前系统的pci。或者在文件系统/proc/pci和/proc/bus/pci中。

外设板电路响应三种地址空间:内存、IO、配置空间。前两种地址空间在同一个PCI总线上是共享的。配置空间是物理寻址的,每次只对一个槽寻址。

内存和IO空间通常通过inb、readb等方式访问。配置空间需要通过特殊的内核函数访问配置寄存器。每个PCI槽有4个中断引脚,每个设备功能使用其中的一个。

1个PCI总线使用32位的地址总线用于IO寻址(4G),32位的地址总线(现在设备有的支持64位)用于内存寻址。在系统启动阶段,固件初始化PCI硬件的时候,把每个区域映射到不同的地址。驱动程序不需要探测,而从配置空间读取映射的地址。

对于每个设备功能,PCI配置空间由256字节组成(PCIE的是64KB),并且配置空间的布局是标准的。配置空间的4个字节(哪4个字节?)标识唯一的功能ID。

4.  引导阶段

主板上的固件(比如BIOS),读写PCI设备中的寄存器,访问配置空间。

系统引导阶段,linux内核为每个PCI地址区域申请安全的处理器地址。后续驱动可以从/sys/bus/pci/devices/*目录中读取映射的地址。

$ tree /sys/bus/pci/devices/0000:00:10.0
/sys/bus/pci/devices/0000:00:10.0
|-- class
|-- config
|-- detach_state
|-- device
|-- irq
|-- power
|  `-- state
|-- resource
|-- subsystem_device
|-- subsystem_vendor
`-- vendor

其中,config包含配置信息,resource包含分配给该设备的内存资源。irq包含了该PCI设备的中断号。

5.  配置寄存器和初始化

所有的PCI设备至少包含256字节的配置地址空间(PCIE是64KB)。其中前64字节是标志的。

PCI配置寄存器包括可选和必需两部分,必需的部分声明功能和其他字段是否可用。

PCI寄存器是小端字节序。

  • vendorID

全局性、全球性。16位标识。比如intel的0x8086.

  • deviceID

厂商定义的16位标识。通常使用vendorID+deviceID 32位标识一个设备。驱动根据该32位标识,定位到一个设备。

  • class

16位的标识,高8位标识基本类(group)。比如,以太网、令牌环网属于网络group,串行、并行属于通信group。一些驱动支持多种相同类型的设备,驱动可以根据类型区分支持的设备。

  • subsystem vendorID
  • subsystem deviceID

subsystem类型的标识,用于进一步识别设备。当一个芯片是连接到本地板载上的通用芯片时,它可能有多用用途。驱动使用subsystem标识,识别具体连接的设备。

内核标识设备ID的结构是:

struct pci_device_id {

__u32vendor, device;               /* Vendorand device ID or PCI_ANY_ID*/

__u32subvendor, subdevice;  /* Subsystem ID'sor PCI_ANY_ID */

__u32class, class_mask; /*(class,subclass,prog-if) triplet */

kernel_ulong_tdriver_data;   /* Data private to thedriver */

};

6.  MODULE_DEVICE_TABLE

通过把pci_device_id结构导出到用户空间中,使热插拔和模块加载系统知道什么模块对应什么设备。

例如:

MODULE_DEVICE_TABLE(pci, i810_ids);

具体的实现是:

extern const typeof(name)__mod_##type##__##name##_device_table              \

__attribute__ ((unused, alias(__stringify(name))))

其中pci是模块名,i810_ids是pci_device_id变量名。MODULE_DEVICE_TABLE宏把例如i810_ids的变量名,起一个__mod_pci_device_table结构的别名。模块编译之后,在对应的模块ELF文件中会有相应的__mod_pci_device_table结构符号。在内核构建时,depmod搜索所有模块的类似__mod_pci_device_table结构的符号,从中解析出type和name,并取出pci_device_id数据导出到/lib/modules/KERNEL_VERSION/modules.pcimap文件中。之后内核所有模块支持的设备和模块的名字可在该文件中找到。当内核告知热插拔系统,发现一个新的设备时,热插拔系统根据modules.pcimap文件找到对应的驱动。

注:模块的概念。PCI是一个模块。

7.  PCI驱动注册

为了正确的注册到内核,PCI驱动必须创建一个结构:

struct pci_driver {

struct list_head node;

const char *name;

const struct pci_device_id *id_table;  /* must be non-NULL for probe to be called*/

int  (*probe) (struct pci_dev*dev,const struct pci_device_id*id);  /* New device inserted */

void (*remove)(struct pci_dev*dev);  /* Device removed (NULL if not a hot-plugcapable driver) */

int  (*suspend)(struct pci_dev*dev, pm_message_tstate); /* Device suspended */

int  (*suspend_late)(struct pci_dev*dev, pm_message_tstate);

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

int  (*resume)(struct pci_dev*dev);                  /* Device woken up */

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

int (*sriov_configure)(struct pci_dev*dev,int num_vfs);/* PF pdev */

const struct pci_error_handlers *err_handler;

struct device_driver    driver;

struct pci_dynids dynids;

};

包括一些回调函数和描述PCI驱动与PCI核心对应的变量。

其中一些区域需要PCI驱动注意。

const 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  (*suspend)(struct pci_dev*dev, pm_message_tstate);

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

总的来说,一个PCI驱动结构只需要4个区域被初始化。

static struct pci_driver pci_driver = {

.name ="pci_skel",

.id_table = ids,

.probe = probe,

.remove =remove,
};

通常在模块初始化代码中,注册pci驱动。比如:

static int __init pci_skel_init(void)

{

return pci_register_driver(&pci_driver);

}

2.6更新后,在支持PCI热插拔、或CardBus系统上,PCI设备可以出现在任何时刻。

在系统运行时刻,通过写值到驱动的new_id中,指定驱动支持的新设备(原内核未认知的设备)。

当PCI驱动被卸载时,需要调用pci_unregister_driver。例如:

static void __exit pci_skel_exit(void)

{

pci_unregister_driver(&pci_driver);

}

8.  使能PCI设备

在PCI的探测函数中,在驱动访问PCI设备的任何资源之前(IO区域或资源),驱动程序必须调用函数:

int pci_enable_device(struct pci_dev *dev);

用来激活设备。

9.  访问配置空间

在驱动监测到设备之后,通常需要访问三个区域:内存、IO区域、配置空间。

访问配置空间尤其重要,因为需要通过配置空间找到内存区域映射和IO区域映射。

linux提供了一套访问配置空间的标准接口。

对驱动而言,可通过8、16、32位数据传输访问配置空间。相关的函数定义在<linux/
pci.h>
:中:

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:访问设备的逻辑表示

where:要读取位置在配置空间中的位移

*val:读取的值

不需要考虑字节序,会自动转换。

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

dev:写入设备的逻辑表示

where:要写入位置在配置空间中的位移

*val:写入的值

不需要考虑字节序,会自动转换。

在驱动未获得pci_dev时,可使用如上函数读写配置空间

int pci_bus_read_config_byte (structpci_bus *bus, unsigned int devfn, int
where, u8 *val);
int pci_bus_read_config_word (struct pci_bus *bus, unsigned int devfn, int
where, u16 *val);
int pci_bus_read_config_dword (struct pci_bus *bus, unsigned int devfn, int
where, u32 *val);

int pci_bus_write_config_byte (structpci_bus *bus, unsigned int devfn, int
where, u8 val);
int pci_bus_write_config_word (struct pci_bus *bus, unsigned int devfn, int
where, u16 val);
int pci_bus_write_config_dword (struct pci_bus *bus, unsigned int devfn, int
where, u32 val);

访问配置空间的最好方式是通过pci_read_系列函数,例如:

static unsigned charskel_get_revision(struct pci_dev *dev)
{

u8 revision;

pci_read_config_byte(dev,PCI_REVISION_ID, &revision);

return revision;

}

10.  访问IO和内存空间

一个PCI设备最多可实现6个IO地址区域。每个区域可以是内存或者IO地址。大多数设备在内存区域实现IO寄存器,这也是一个明智的方法。需要注意的是,和常规内存不同,IO寄存器不应该由CPU缓存,因为每次访问都可能边缘效应。为了取消这个默认设置,内存区域实现的IO寄存器,可以通过在其配置寄存器中设置“memory-is-prefetchable”。若是可预取的,CPU可缓存其内容并进行各种优化。若不是可预取的,则不能优化,因为每次访问都有边际效应,就行IO端口一样。

接口板(PCI设备)通过6个32位的寄存器(PCI_BASE_ADDRESS_0到PCI_BASE_ADDRESS_5)声明区域的大小和位置。所以最多实现6个IO地址区域。因为PCI的IO地址空间是32位的,所以不管是内存或IO区域使用相同的配置接口是有道理的。如果设备的数据总线是64位的,那么每个区域使用两个连续的32位寄存器实现。一个PCI设备既提供32位区域又提供64位区域是有可能的。

内核已经把PCI设备的IO区域信息映射进了通用资源管理中。所以,不需要通过访问配置寄存器来获取IO区域信息。可以通过访问/sys/bus/pci/devices/*/resource的内容获取。但首选的方法是通过下列函数获取。

unsigned long pci_resource_start(structpci_dev *dev, int bar);

unsigned long pci_resource_end(structpci_dev *dev, int bar);

bar:指定要获取的区域(0到5)

unsigned long pci_resource_flags(structpci_dev *dev, int bar);

资源flag用来定义某个区域的特性。其中几个重要的标志如下:

IORESOURCE_IO

IORESOURCE_MEM

IORESOURCE_PREFETCH

IORESOURCE_READONLY(PCI资源从不设置该标志)

驱动程序不需要访问配置寄存器去获得这些资源信息,因为系统已经构建了这些资源信息,驱动直接使用pci_resource_系列函数获取即可。

11.  PCI中断

在linux系统启动时,已经为PCI设备分配了一个唯一的中断号,位于配置空间的第60寄存器(PCI_INTERRUPT_LINE),一个字节长度,最多256个中断号。第61个寄存器(PCI_INTERRUPT_PIN)说明PCI设备是否支持中断,如果不支持,则为0,如果支持,非0.

如果是非0的,PCI_INTERRUPT_PIN的值是中断引脚的编号()。

驱动通过下面代码读取中断号,以便使用:

result = pci_read_config_byte(dev,PCI_INTERRUPT_LINE, &myirq);

if (result) {

/* deal witherror */

}

12.  总结归纳

linux中:

先module初始化,内含pci初始化(对于pci设备而言)

linux 内核PCI驱动总结记录相关推荐

  1. Linux内核网络设备驱动

    本文首先从宏观上介绍数据包的接收过程,然后详细介绍了Linux网络设备驱动的工作过程,最后介绍网卡监控与调优,包括网络数据包总数.丢包.错包数量的相关统计. 1. 接收数据包过程概述 介绍数据包收包过 ...

  2. Linux GPIO键盘驱动开发记录_OMAPL138

    Linux GPIO键盘驱动开发记录_OMAPL138 Linux基本配置完毕了,这几天开始着手Linux驱动的开发,从一个最简单的键盘驱动开始,逐步的了解开发驱动的过程有哪些.看了一下Linux3. ...

  3. Android系统 linux内核按键驱动开发

    Android系统 linux内核按键驱动开发 前言 刚入门的小白,在csdn的帮助下完成了第一个按键驱动,特写此文记录学习并分享给有需要的人. 1.修改设备树.dts 我是用的开发板是rp-rk32 ...

  4. 深入讲解Linux内核网络设备驱动(图例解析)

    1. 接收数据包过程概述 介绍数据包收包过程,有助于我们了解Linux内核网络设备在数据收包过程中的位置,下面从宏观的角度介绍数据包从被网卡接收到进入 socket 接收队列的整个过程: 加载网卡驱动 ...

  5. linux内核led驱动开发,从Linux内核LED驱动来理解字符设备驱动开发流程

    目录 博客说明 开发环境 1. Linux字符设备驱动的组成 1.1 字符设备驱动模块加载与卸载函数 1.2 字符设备驱动的file_operations 结构体中的成员函数 2. 字符设备驱动--设 ...

  6. 【嵌入式环境下linux内核及驱动学习笔记-(16)linux总线、设备、驱动模型之input框架】

    目录 1.Linux内核输入子系统概念导入 1.1 输入设备工作机制 1.2 运行框架 1.3 分层思想 2.驱动开发步骤 2.1 在init()或probe()函数中 2.2 在exit()或rem ...

  7. Linux内核IOREMAP驱动

    1 Linux内核IOREMAP驱动 在内核驱动的代码中,存在大量代码使用ioremap进行物理地址和虚拟地址映射,使得内核更加容易操作硬件,对比于简单的gpio控制,实际的代码同样是使用了iorem ...

  8. 【嵌入式环境下linux内核及驱动学习笔记-(15-1)例程】

    目录 1.在APP直接调用标准文件IO操作I2C(针对学习笔记-15的15.3节) 1.1 mail.c 1.2 mpu6050.h 1.3 mpu6050.c 1.4 Makefile 2.以外称i ...

  9. 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Oops在Linux 2.6内核+PowerPC架构下的前世今生

    Oops在Linux 2.6内核+PowerPC架构下的前世今生 Sailor_forever  sailing_9806#163.com (本原创文章发表于Sailor_forever 的个人blo ...

  10. 【嵌入式环境下linux内核及驱动学习笔记-(11-设备树)】

    目录 1.设备树体系 1.1 DTS /DTSI / DTC / DTB 2.基础语法 2.1 节点语法 2.1.1 通用名称建议 2.2 属性语法 2.2.1 属性值 2.3 关于label 2.4 ...

最新文章

  1. Windows下的Memcache安装
  2. jfinal为weebox弹出框传递参数
  3. 脱式计算机在线使用,脱式计算,
  4. JavaSE(六)包装类、基本类型和字符串之间的转换、==和equals的区别
  5. Qtcreator快速入门
  6. 查询去重_【Freya的MySQL课堂】DQL基础查询
  7. 用Swashbuckle给ASP.NET Core的项目自动生成Swagger的API帮助文档
  8. 那一年,我考入了西北师范大学GIS专业,然而我很迷茫,GISer的职业规划到底是怎样的?
  9. else应输入一个语句是什么意思_Python基础知识储备,关于if-else使用性能的一点感悟...
  10. 设计模式之禅读书笔记
  11. mysql 存储过程改用户_Mysql修改存储过程相关权限问题
  12. 用Excel教会你PID算法
  13. 自定义srv消息之ros
  14. visio2010下载地址中文版本32位中文版本64位和激活密钥方法分享哦
  15. C语言简单程序编写(一)
  16. 自行搭建 Bitwarden 服务
  17. 电阻应用电路之运放如何消除偏置电流的影响
  18. 数学建模用python分析gdp_数学建模·中国GDP趋势分析与预测
  19. 【C语言】求Sn=a+aa+aaa+aaaa+aaaaa的前5项之和,其中a是一个数字
  20. vue中methods、mounted等使用方法整理

热门文章

  1. 查看MySQL初始密码并修改
  2. MySQL初始密码的查看
  3. 1元课,学会小学数学应用题,你的孩子也能秒解“鸡兔同笼”
  4. 如何选择云主机或者VPS挂EA?
  5. 控件ShowWindow(SW_HIDE)不起作用
  6. 怎样打印计算机桌面,敬业签电脑桌面便签软件怎么打印便签内容?
  7. Java流(Stream)操作实例-筛选、映射、查找匹配
  8. 2019矿大软件工程考试记录
  9. Tensorrt笔记(七)Tensorrt使用问题整理
  10. gae代码_GAE中的Java EE