Chinaunix首页 | 论坛 | 博客
  • 博客访问: 613494
  • 博文数量: 75
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 100
  • 用 户 组: 普通用户
  • 注册时间: 2014-08-25 16:47
个人简介

嵌入式linux爱好者

文章分类
文章存档

2020年(1)

2019年(39)

2018年(3)

2017年(20)

2016年(7)

2015年(1)

2014年(4)

我的朋友

分类: 嵌入式

2019-03-28 08:55:54

原文地址:pci 学习笔记 作者:pauloxu

  PCI是Peripheral Component Interconnect(外设部件互连标准)的缩写,它是目前个人电脑中使用最为广泛的接口,其位宽为32位或64位,工作频率为33MHz,最大数据传输率为133MB/sec(32位)和266MB/sec(64位)。可插接显卡、声卡、网卡、内置Modem、内置ADSL Modem、USB2.0卡、IEEE1394卡、IDE接口卡、RAID卡、电视卡、视频采集卡以及其它种类繁多的扩展卡.
  目前PCI-E是PCI最新的发展方向,串行,点对点传输,每个传输通道独享带宽;支持双向传输模式和数据分通道传输模式;在PCI-E 3.0规范中,X32端口的双向速率高达320Gbps,可以满足新一代的I/O接口,比如:千兆(GE)、万兆(10GE)的以太网技术、4G/8G的FC技术
一. PCI 引脚


1. 接口控制管脚 (出问题时常测这些管脚)

FRAME#:帧周期信号。Master驱动,表示一次访问的开始和持续时间。 FRAME#无效时,是传输的最后一个数据周期。

IRDY#:Master准备好信号。
TRDY#:Slave准备好信号。

当这两者同时有效时,才能进行完整的数据传输,否则即为等待周期。

在写周期,IRDY#信号有效时,表示有效的数据信号已在AD0~AD31中建立;
在读周期,IRDY#信号有效时,表示Master已做好接收数据的准备。

在写周期,TRDY#信号有效,表示Slave已做好了接收数据的准备。
在读周期,TRDY#信号有效,表示有效数据已被送入AD0~AD31中,


STOP#:停止数据传送信号,由Slave发出。当它有效时,表示Slave请求Master终止当前的数据传送。

IDSEL:初始化设备选择信号。在读写配置空间时,用作Slave的片选信号(Slave通常把IDSEL连到AD[31:0]上的一根,PFA中的device id就是这么确定的)

DEVSEL#:设备选择信号,由Slave驱动,该信号有效时,当前Slave设备已被选中
 
二.时序
读时序


写时序:


 
 
 
三.PCI配置空间

256字节的PCI配置空间分为64字节的头标区和192字节的设备相关区两部分。头标区的各个寄存器用来唯一地识别设备;设备相关区则保存一些与设备相关的数据。

 

配置空间的头标区又分为两部分:前16个字节的定义在各种类型的PCI设备中都是一样的;剩余的字节随设备类型不同而有所不同。位于偏移地址0EH处的头标类型字段规定了头标区的布局结构。目前,规范定义了三种头标类型。

 

头标类型

设备

2

PCI-CardBus

1

PCI-PCI

0

除上述桥外的所有设备

 

因为PCI网卡的头标类型是0,所以下面我们就来详细说说其布局结构,至于其他类型的头标请读者自行阅读。图3就是头标类型0的头标区的布局。头标区中的寄存器根据功能可分成下面几组:

 

1. 设备的识别

1       供应商代码:该寄存器用于识别PCI设备的制造商,具体代码由PCI SIG)分配。0FFFFH是无效的供应商代码。

2       设备代码。该寄存器用来标识某供应商生产的具体设备,代码由各供应商定义。供应商代码和设备代码,读者可以到网站查阅。

 

3 头标类型0的头标区的布局

 

3       版本号。该寄存器用来定义指定设备的版本信息。

4       头标类型。该字段的第7位为“1”标识该设备是多功能设备,为“0”标识为单功能设备;该字段的06位就是上文表中所述的头标类型。

5       设备分类代码。用来标识设备的总体功能和特定的寄存器级编程接口。

上面5个字段均为只读类型,所有的PCI设备都必须实现其功能。

 

2. 设备控制和设备状态

1       命令寄存器为一个设备发出和响应PCI总线命令提供粗略的控制。图4就是命令寄存器格式。

4 命令寄存器格式

 

我们比较感兴趣的位有:

a.0I/O空间控制):控制对I/O空间访问的响应。该位为0时,禁止设备响应对I/O空间的访问;该位为1时,允许设备响应I/O空间的访问。缺省设置为0

b.1(存储器空间控制):控制一个设备对存储器空间访问的响应。该位为0时,禁止响应;该位为1时,允许设备响应对存储器空间的访问。缺省设置为0

 

至于其他位的功能,读者若有兴趣可以自行查阅。

 

2       状态寄存器用来记录PCI总线有关的状态信息。

 

3. 基址寄存器

PCI设备中,除了配置空间外,还有两个物理空间:内存空间和I/O空间。为了访问这两个地址空间,就必须使用基址寄存器。头标类型0中涉及3种基址寄存器:内存空间基址寄存器、I/O空间基址寄存器和扩展ROM基址寄存器。关于基址寄存器,我们下一章重点讲述。

 

4. 其他寄存器

其他寄存器包括一些本文不涉及到的寄存器,如中断引脚、中断线等等。有兴趣的读者可以自行阅读。

 

四.PCI配置空间的访问

上面说过,PCI规范使用从0CF8H~0CFFH 8I/O地址来访问所有设备的PCI配置空间。这8个字节实际上构成了两个32位寄存器:0CF8H寄存器叫做“配置地址寄存器”;0CFCH叫做“配置数据寄存器”。当要访问配置空间的寄存器时,先向地址寄存器写上目标地址,然后就可以从数据寄存器中读写数据了J

 

我们说过,PCI配置空间对应于一个PCI逻辑设备,所以要访问一个配置空间的某个寄存器,必须要指定:PCI总线号、PCI设备号、PCI设备功能号和寄存器号。配置地址寄存器的格式如下:

 

31

30   24

23     16

15     11

10    8

7       2

1

0

使能位

保留

总线号

设备号

功能号

寄存器号

0

0

 

01位上的“0”是用来要求你只能按双字(4字节)来读写配置空间寄存器。第31位“使能位”用来决定是否允许访问配置空间:为“1”时表示可以访问;为“0”时表示不可以访问。

 

例如,为了读总线号0、设备号17H、功能号030H寄存器,你可以使用下面的代码:

 

MOV  EAX, 8000B830H

MOV  DX, 0CF8H

OUT   DX, EAX

 

MOV DX, 0CFCH

IN  EAX, DA

 

五.PCI配置空间的遍历

从上面的配置地址寄存器的格式我们可以看出:总线号从0255、设备号从031、功能号从07。根据配置空间的第0个寄存器是否返回0FFFFH值来判断是否存在该PCI设备。下面是PCI配置空间遍历的流程图:

5遍历PCI配置空间流程图

六.基址寄存器

PCI设备中,除了配置空间外,还有两个物理空间:内存空间和I/O空间。为了访问这两个地址空间,就必须使用基址寄存器。头标类型0中涉及3种基址寄存器:内存空间基址寄存器、I/O空间基址寄存器和扩展ROM基址寄存器。

 

PCI设备可以在地址空间中浮动是PCI局部总线中最重要的功能之一。它能够简化设备的配置过程。在系统上电时,与设备无关的系统软件必须确定有哪些设备存在,同时建立一个统一的地址映射关系,并确定一个设备是否有扩展ROM

 

1.   地址映射

加电软件在引导操作系统之前必须建立一个统一的地址映射。也就是说,必须确定在系统中有多少存储器以及系统中的I/O控制器要求多少地址空间。当这些信息确定之后,加电软件便可以把I/O控制器映射到合理的地址空间并引导系统。为了使这种映射能够做到与响应的设备无关,从而在配置空间的头标区中安排了一个供映射时使用的基址寄存器。

 

在所有的基址寄存器中,位0均为只读位并且用来决定能够是存储器空间还是I/O空间。如果该位为0,则表示映射到存储器空间;若为1则表示映射到I/O地址空间。

 

2.   存储器基地址寄存器

映射到存储器空间的基址寄存器可以是32位宽度,也可以是64位宽度(支持映射到一个64位地址空间时)(如图6)。

6  32/64存储器基地址寄存器格式

 

其中位0要用硬件方法使其恒为0。而位2和位1两位用来表示映射类型,具体如下:

 

2和位1

映射类型

00

基地址寄存器为32位宽,可以在32位表示的存储器地址范围的任何地方进行映射

01

保留

10

基地址寄存器为64位宽,可以映射到以64位表示的存储器空间的任何地方

11

保留

 

至于位3,若数据是可预取的,就应将它置为1,否则清0。该寄存器的区域各位用来将一设备映射到存储器空间。

 

基地址寄存器中用于32位存储器译码器的位【31::4】和用于64位存储器译码器的位【63::4】称为基地址单元。它的作用是:确定与译码器相关的存储器的大小;给译码器分配地址。如果存储器设备需要小于4K的存储空间,规范建议存储器范围强行设为4KB

 

3.   I/O基地址寄存器

映射到I/O空间的基址寄存器宽度总是32位(如图7):

 

7  I/O基地址寄存器格式

 

其中位0值为1(用硬件实现的),位1为保留位并且其读出值必须为0,其余各位用来把设备映射到I/O空间。

 

当基地址寄存器位0的返回值为1时,表示这是一个I/O译码器,而不是存储器译码器,位1保留并总是返回0,【31::2】是基地址单元,并用于确定需要的I/O块容量,设置它的起始地址。

 

规范要求映射它的控制寄存器组到I/O空间的 设备不必请求每个I/O基地址寄存器超过256个单元。

 

4.   确定块容量和分配地址范围

要确定存储器的容量或I/O空间大小可以通过简单地向基地址寄存器写入全“1”并回读来确定。若返回一个是0值,则表示未实现基地址寄存器;如果读回地值为非0,则编程人员通过从基地址单元的最低有效位向上扫描返回值以找到第一个被成功置“1”的位来确定所需存储器的容量或I/0空间的大小。假设寄存器的位0是一个加权二进制1,那么位1的值就是2,位2的值就是4,依此类推。这样,在基地址单元中第一个发现的1所对应的加权二进制值便是所需的空间数。这也是寄存器的第一个可读/可写位,在它之上的所有位钧定义为可读/可写位。这个信息发现后,程序将32/64位存储器起始地址或32I/O地址写入基地址寄存器中。

 

5.   扩展ROM基地址寄存器

有些PCI设备,尤其是那些准备用于PC结构扩展板上的设备,需要EPROM作为扩展ROM。为此,在配置空间偏移地址30H处开始定义了四个字节的寄存器,用来处理这个扩展ROM的基地址和大小。(如图8)

 

8 扩展ROM基地址寄存器格式

 

该寄存器和32位基地址寄存器相比,除了位的编码和用途不同之外,其它功能完全相似。它的高21位对应于扩展ROM基地址的高21位。一个设备实际实现的位数取决于该设备要求多大的地址空间。例如,一个设备要求它的扩展ROM映射到一64KB存储区域时,它就应该实现此寄存器的高16位,其它5位用硬件方法使它们恒为0。凡是支持扩展ROM的设备必须实现这个寄存器。

与设备无关的配置软件通过对扩展ROM基址寄存器的地址位上写入全“1”,然后再读回以确定设备要求多大的地址范围。所有的无关位上都返回0,从而有效地指出了地址边界,也就知道了设备要求的这一块地址空间的大小。一个设备要求的地址空间范围不能超过16MB

 

这个寄存器的位0用来控制相应的设备是否能够接受对其扩展ROM的访问。当该位为0时,禁止访问设备的扩展ROM地址空间;当该位为1时,允许将本寄存器的其它位作为参数进行地址译码。命令寄存器中的存储器空间位优先于扩展ROM的使能位,但是,如果存储器空间位和扩展ROM的的使能位同时为1时,设备就必须响应对其扩展ROM的访问。扩展ROM的使能位在复位后应该为0

 

七.PCI拓扑结构
PCI拓扑结构,有如下例子



    在上图的总线结构中,ethernet设备和pci-pci bridge的资源空间必须要是pci bus0的一个子集
    同理,SCSI和VIDEO同类型资源必须要是pci_bus1的子集。
    CPU访问PCI的过程是这样的(只有一个根总线和pci-pci bridge过滤窗口功能打开的情况):
    1.cpu向pci发出一个I/O请求。
    首先经过根总线.它会判断是否在它的资源范围内.如果在它的范围,它就会丢向总线所在的每一个设备.包括pci bridge. 如果没有在根总线的资源范围,则不会处理这个请求.
    2.如果pci设备判断该地址属于它的资源范围,则处理后发出应答
    3.pci bridge接收到这个请求,它会判断I/O地址是否在它的资源范围内.如果在它的范围,它会把它丢到它的下层子线.
    4.下层总线经过经过相同的处理后,就会找到这个PCI设备了
    一个PCI设备访问其它PCI设备或者其它外设的过程:
    1.首先这个PCI发出一个请求,这个请求会在总线上广播
    2.如果要请求的设备是在同级总线,就会产生应答
    3.请求的设备不是在同层总线,就会进行pci bridge.pci桥判断该请求不在它的范围内(目的地不是它下层的设备),就会将它丢向上层.
    4.这样辗转之后,就能找到对应的设备了


八.PCI枚举过程
通过PCI枚举,CPU知道当前系统上有多少PCI设备,多少根PCI总线,PCI配置空间初始化。
    PCI 总线扫描的原理是从总线 0 扫描到总线 255,对于每条总线,系统都会扫描所有(总线号,设备号,功能号),读出每个设备配置空间的Device ID和Vendor ID寄存器,如果这两个寄存器的值是个无效值(0xFFFF),则说明当前位置上没有设备,接着扫描下一个位置。
    如果是有效值(非0xFFFF),当前位置是个有效的 PCI 设备/桥。进而再读取该设备的 Header Type 寄存器,如果该寄存器为 1,则表示当前设备是 PCI 桥,否则是 PCI 设备。
Register Number:配置空间寄存器偏移量
Function Number:多功能设备有多个功能号
Device Number:设备编号
Bus Number:总线编号

对所有 PCI 总线进行编号
    PCI 桥如何知道它所连接的 PCI 总线情况呢?这就需要对 PCI 桥进行总线编号。前面介绍过 PCI 桥提供了 Primary Bus Number、Secondary Bus Number 和 Subordinate Bus Number 三个寄存器用于标志该桥所连接的 PCI 总线,下面通过一个示例来说明内核对于 PCI 总线是如何进行编号的。

1.系统运行初始,Bus A 为 0,通过上面的 PCI 总线扫描得到连接在 Bus A 上的 PCI 桥(即图中Bridge 1)
2.下面开始设置 Bridge 1 的 Bus 寄存器。将 Primary Bus Number 寄存器设置成 Bus A 的编号,即 0。将 Secondary Bus Number 寄存器设置成 Bus B 的编号,它的值等于(Bus A + 1),也就是 1。由于暂时无法知道该桥所能访问的所有下行总线数目,Subordinate Bus Number 寄存器暂时设置成 0xFF。
3.当扫描完所有 Bus A 上所有(设备号,功能号)后,开始扫描 Bus B,Bus B 的编号在扫描完 Bus A 后已经得到,为 1。Bus B 的扫描方法同步骤(1),先扫描出 Bus B 上的 PCI 桥(即图中的 Bridge 2),然后配置 Primary Bus Number 寄存器为 1,Secondary Bus Number 寄存器为 2,而 Subordinate Bus Number 寄存器依然为 0xFF。
4.Bus B 扫描完后得到 Bus C 的编号,为2。下面开始扫描 Bus C,因为 Bus C 上没有 PCI 桥,于是在扫描完其它(设备号,功能号)后,Bus C 的扫描结束。
5.由于 Bridge 2 所能访问到的最大 Bus 编号是 2,因此重新设置 Bridge 2 的 Subordinate Bus Number 寄存器为 2。
6.由于 Bridge 1 所能访问到的最大 Bus 编号也是 2,因此重新设置 Bridge 1 的 Subordinate Bus Number 寄存器为 2。
7.总线编号结束。


九.Linux内核PCI数据结构
内核(linux-2.6.24) 提供了三类数据结构用以描述 PCI 控制器、PCI 设备以及 PCI 总线。
数据结构关系如下所示

PCI 控制器 用 pci_controller 结构来描述,它有以下几个主要的属性:
index:该属性标志 PCI 控制器的编号。
next:该属性指向下一个 PCI 控制器,通过 next 属性,PCI 控制器可以形成一个单向链表。
first_busno:该属性标志了连接在该控制器上第一条总线的编号。
last_busno:该属性标志了连接在该控制器上最后一条总线的编号。
ops:该属性标志了当前 PCI 控制器所对应的 PCI 配制空间读写操作函数。
io_space:该属性标志了当前 PCI 控制器所支持的 IO 地址空间。
mem_space:该属性标志了当前 PCI 控制器所支持的 Memory 地址区间。
cfg_addr:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要写入的地址空间。
cfg_data:该属性标志了当前 PCI 控制器发起 Configuration 访问方式时所需要读写的数据空间。
bus:该属性标志了当前 PCI 控制器所连接的 PCI 总线,它对应的数据结构是 pci_bus。
PCI 总线 用 pci_bus 结构来描述,它有以下几个主要的属性:
parent:可通过该属性索引到上层 PCI 总线。
self:该属性标志了连接的上行 PCI 桥(对应的数据结构是 pci_dev)。
children:该属性标志了总线连接的所有 PCI 子总线链表。
devices:该属性标志了总线连接的所有 PCI 设备链表。
ops:该属性标志了总线上所有 PCI 设备的配制空间读写操作函数。
number:该属性标志了当前 PCI 总线的编号。
primary:该属性标志了 PCI 上行总线编号。
secondary:该属性标志了 PCI 下行总线编号。
subordinate:该属性标志了能够访问到的最大总线编号。
resource:该属性标志了 Memory/IO 地址空间。
PCI 设备 用 pci_dev 结构来描述,它有以下几个主要的属性:
global_list:Linux 定义了一个全局列表来索引所有PCI 设备,该属性标志了这个全局列表的首指针。
bus:该属性标志了当前设备所在的 PCI 总线(对应的数据结构是 pci_bus)。
devfn:该属性标志了设备编号和功能编号。
vendor:该属性标志了供应商编号。
device:该属性标志了设备编号。
driver:该属性标志了设备对应的驱动代码(对应的数据结构是 pci_driver)。
irq:该属性标志了中断号。
resource:该属性标志了 Memory/IO 地址区间。
当 Linux 内核在做 PCI 初始化工作时,它会根据建立一个由 pci_controller、pci_bus 和 pci_dev 三者组成的一个组织结构图。根据这个结构,软件开发者可以很方便的通过 PCI 控制器索引到每个 PCI 设备或者 PCI 总线。


 

  十.linux的PCI子系统初始化流程
第一步:Linux分配数据结构pci_contoller,并初始化,包括PCI的mem/io空间范围和访问PCI配置空间所需的handler。
第二步:PCI设备的枚举:扫描系统上所有PCI设备,初始化它们的配置空间。(硬件上的初始化)
第三步:用数据结构将PCI设备信息联系起来,构建PCI树。(软件上的初始化)
第四步:加载PCI设备驱动。
1 初始化PCI控制器
pci_controller结构是内核描述PCI子系统信息的数据结构,里面定义了可供PCI设备使用的mem资源和io资源的范围,访问pci设备配置空间的handler等等。
函数调用关系:

 

  1. start_kernel --> mpc8560ads_setup_arch --> mpc85xx_setup_hose()
  2. 此函数分配并初始化了pci_controller
  3. mpc85xx_setup_hose()
  4.     -->pcibios_alloc_controller() //分配数据结构pci_controller

  5.     ppc_md.pci_map_irq = mpc85xx_map_irq; //用来获得pci设备irq号的handler

  6.     -->setup_indirect_pci() //设置pci_controller里访问PCI配置空间的钩子函数

  7.                             //使用ioremap后的CFG_ADDR和CFG_DATA的虚拟地址

  8.     -->mpc85xx_setup_pci1() //设置PCI inbound和outbound窗口寄存器。

  9. PCI子系统的mem和io地址空间范围也在函数mpc85xx_setup_hose()中定义:
  10.     pci_controller *hose_a;
  11.     hose_a->mem_space.start = MPC85XX_PCI1_LOWER_MEM;
  12.     hose_a->mem_space.end = MPC85XX_PCI1_UPPER_MEM;
  13.     hose_a->io_space.start = MPC85XX_PCI1_LOWER_IO;
  14.     hose_a->io_space.end = MPC85XX_PCI1_UPPER_IO;


2 PCI枚举过程
目前为止,内核只知道PCI子系统总的mem资源和io资源地址范围。
接下来,内核需要扫描所有PCI设备,把这些资源分配给PCI设备,具体方法参考前面讲的PCI枚举过程。
内核分配mem资源时是从高地址开始分配的。

 

  1. mpc85xx_setup_hose()
  2.     -->pciauto_bus_scan()
  3.     for (pci_devfn = 0; pci_devfn < 0xff; pci_devfn++) { //遍历0号总线上所有PCI设备

  4.         if (读当前设备配置空间PCI_HEADER_TYPE失败)
  5.             continue; //当前设备不存在,扫描下一个

  6.         读当前设备配置空间PCI_CLASS_REVISION
  7.         If(当前设备是PCI桥){
  8.             pciauto_setup_bars() //分配pci_controller的资源的子集

  9.             pciauto_prescan_setup_bridge() //写PCI桥配置空间 PCI_PRIMARY_BUS

  10.                                                 //PCI_SECONDARY_BUS

  11.                                                 //PCI_SUBORDINATE_BUS

  12.             pciauto_bus_scan() //递归扫描下一条pci bus

  13.             pciauto_postscan_setup_bridge() //写PCI桥配置空间PCI_SUBORDINATE_BUS

  14.         }
  15.         。。。。。略去
  16.         else {
  17.             //当前设备是PCI设备

  18.             pciauto_setup_bars()
  19.         }
  20.     }


 

    注意:执行完 pciauto_bus_scan后,所有pci设备(包括桥)的配置空间都已经傻瓜式的简单初始化
      但这些桥和设备还没有通过数据结构组织起来,这些工作要在第四步 pcibios_init里来完成
3 创建PCI树
上面说了,PCI设备配置空间都初始化差不多了,但是PCI设备还没有通过数据结构组织起来。   

 

  1. do_initcalls()
  2.   -->pcibios_init() //所在文件 arch/ppc/kernel/pci.c

  3. pcibios_init()
  4.     (1):为PCI设备构造数据结构,组织成PCI树

  5. ->pci_scan_bus(hose->first_busno,hose->ops,hose)//hose就是上面的pci_controller结构


  6.  -->pci_scan_bus_parented

  7.    -->pci_create_bus // 建立 PCI bus 0 对应的数据结构,这个bus的资源尚未初始化

  8.  -->pci_scan_child_bus // 从PCI bus 0 开始扫描生成PCI树,使用了递归


  9.      -->pci_scan_slot

  10.       -->pci_scan_single_dev
  11.     -->pci_scan_device() //创建 pci_dev结构

  12.      -->pci_setup_device() //区分桥与设备,分别进行初始化

  13.       -->pci_read_bases();


 

pci_dev->resouce[]中保存的才是cpu internal address(EA),可以对这些地址进行用ioremap,在驱动程序对bar所指位置读写的时候一定要用这       
    (2)给PCI设备分配IRQ号

 

  1. -->pci_fixup_irqs()
  2.         //遍历所有pci设备,调用pdev_fixup_irq()

  3.    -->pdev_fixup_irq
  4.        -->ppc_md.pci_swizzle() //实际调用common_swizzle(),获得pci所在slot编号

  5. 读PCI设备配置空间PCI_INTERRUPT_PIN,获得中断pin编号[1到4]

  6.     之所以是1到4,因为PCI规范里设备最多4个管脚(除第一个外,其他3个仅用于多功能设备)
  7.        -->ppc_md.pci_map_irq() //实际调用mpc85xx_map_irq()

  8.                                     //根据slot,pin 和 pci_irq_table[][4]

  9.                                     //来计算出irq号

  10.        -->pcibios_update_irq(pci_dev,irq)
  11.         //将irq号写入pci设备的配置空间PCI_INTERRUPT_LINE

  12.         //注意,这里寄存器只是用来保存结果,例如把其值8改为9,并不能改变中断号

  13.     (3)PCI结构树有了,现在构建PCI的资源树,有冲突就修改
  14.     -->pcibios_allocate_bus_resource()
  15.         //只考虑pci_bus,形成bus级资源树(并同时check,资源冲突了就修改)

  16.     -->pcibios_allocate_resources()
  17.         


 

4 加载PCI设备驱动
以e1000 PCI 网卡 82546GB为例,讲解一个PCI驱动的加载过程
驱动里有如下代码:

 

  1. static struct pci_driver e1000_driver = {
  2.     .name = e1000_driver_name,
  3.     .id_table = e1000_pci_tbl,
  4.     .probe = e1000_probe,
  5.     .remove = __devexit_p(e1000_remove),
  6. #ifdef CONFIG_PM
  7.     /* Power Managment Hooks */
  8.     .suspend = e1000_suspend,
  9.     .resume = e1000_resume,
  10. #endif
  11.     .shutdown = e1000_shutdown,
  12.     .err_handler = &e1000_err_handler
  13. };
  14. module_init(e1000_init_module);
  15. do_initcalls()
  16.     -->e1000_init_module
  17.         pci_register_driver(&e1000_driver)
  18.         // e1000_driver.driver.bus = &pci_bus_type;

  19.     -->driver_register(e1000_driver.driver);
  20.     //pci_bus_type.probe() 非空,即调用pci_device_probe()

  21. pci_device_probe(*device)
  22.     -->__pci_device_probe(*pci_driver,*pci_dev)
  23.         -->pci_match_device(*pci_driver,*pci_dev)
  24.         -->pci_call_probe(*pci_driver,*pci_dev,pci_device_id)
  25.             -->drv->probe(*pci_device,*pci_device_id)
  26.             即执行 e1000_probe(*pci_device,*pci_device_id)


接下来就是e1000 PCI 网卡驱动的具体代码。

阅读(1984) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~