首页 » 智能 » 浅谈Linux PCI设备驱动(上)_装备_总线

浅谈Linux PCI设备驱动(上)_装备_总线

乖囧猫 2024-11-16 17:47:25 0

扫一扫用手机浏览

文章目录 [+]

要弄清楚Linux PCI设备驱动,首先要明白,所谓的Linux PCI设备驱动实际包括Linux PCI设备驱动和设备本身驱动两部分。

不知道读者理不理解这句话,本人以为这句话很主要,对付PCI、USB这样的驱动来说,必须要理解这个观点,才能明白该如何看待Linux下的PCI和USB以及类似的总线型的驱动。
情由也很大略,便是Linux PCI驱动是内核自带的,或者说内核帮你写好了。
而我们须要完成的便是设备本身的驱动,比如网卡驱动等。

浅谈Linux PCI设备驱动(上)_装备_总线 智能

当然,并不是说内核帮咱们写好了Linux PCI驱动我们什么就不用做了,至少你要明白内核大致都干了些什么,这样你才能明白你该干什么,如何完成设备本身的驱动。

这跟我们学习操作系统时要学习很多系统调用接口是一样的道理,不知道这些接口,怎么用操作系统或者说操作系统给你供应的功能呢? 以是这里我们就来研究下Linux PCI驱动到底都干了些什么,以便我们在此根本上完成我们设备本身的驱动。

在http://tldp.org/LDP/tlk/dd/pci.html这篇文章里提到了:

Linux PCI 初始化代码逻辑上分为三个部分:

(1)PCI设备驱动程序(即上面提到的Linux PCI设备驱动)

这个假装备驱动程序从总线0开始查询PCI系统并且定位系统中所有的PCI设备和PCI桥。
它建立一个可以用来描述这个PCI系统拓朴层次的数据构造链表。
并且对所有的创造的PCI桥编号。

(2)PCI BIOS

这个软件层供应在bib-pci-bios归约中描述的做事。
虽然Alpha AXP不供应BIOS做事,在其Linux版本中包含了相应的功能。

(3)PCI Fixup

与特定系统干系的PCI初始化修补代码

而这里紧张便是磋商Linux PCI设备驱动,会在末了列出一段包含设备本身驱动的示例代码,仅供参考。

一、概述及简介

PCI(Periheral Component Interconnect)有三种地址空间:PCI I/O空间、PCI内存地址空间和PCI配置空间。
个中,PCI I/O空间和PCI内存地址空间由设备驱动程序(即上面提到的设备本身驱动)利用,而PCI配置空间由Linux PCI初始化代码利用,这些代码用于配置PCI设备,比如中断号以及I/O或内存基地址。
以是这里的PCI设备驱动便是要大致描述对付PCI设备驱动,Linux内核都帮我们做了什么(主),接着便是我们该当完成什么(次)。

(1)Linux内核做了什么

大略的说,Linux内核紧张就做了对PCI设备的列举和配置;这些事情都是在Linux内核初始化时完成的。

列举:对付PCI总线,有一个叫做PCI桥的设备用来将父总线与子总线连接。
作为一种分外的PCI设备,PCI桥紧张包括以下三种:

(1). Host/PCI桥: 用于连接CPU与PCI根总线,第1个根总线的编号为0。
在PC中,内存掌握器也常日被集成到Host/PCI桥设备芯片中,因此Host/PCI桥常日也被称为“北桥芯片组(North Bridge Chipset)”。

(2). PCI/ISA桥: 用于连接旧的ISA总线。
常日,PCI中类似i8359A中断掌握器这样的设备也会被集成到PCI/ISA桥设备中。
因此,PCI/ISA桥常日也被称为“南桥芯片组(South Bridge Chipset)”

(3). PCI-to-PCI桥(以下称为PCI-PCI桥): 用于连接PCI主总线(Primary Bus)和次总线(Secondary Bus)。
PCI-PCI桥所处的PCI总线称为主总线,即次总线的父总线;PCI-PCI桥所连接的PCI总线称为次总线,即主总线的子总线。

图1,PCI系统示意图

下图摘自PCI Local Bus Specification Revision 2.1,可以看到PCI-PCI桥的Class Code(见图3)便是0x060400。

CPU通过Host/PCI桥与一条PCI总线相连,处在这种位置上的PCI总线称为根总线。
PC机中常日只有一个Host/PCI桥,在一条PCI总线的根本上,可以再通过PCI桥连接到其他次一层的总线,例如通过PCI-PCI桥可以连接到另一条PCI总线,通过PCI-ISA桥可以连接到一条ISA总线。

事实上,当代PC机中的ISA总线正是通过PCI-ISA桥连接在PCI总线上的。
这样,通过利用PCI-PCI桥,就构筑起了一个层次的、树状的PCI系统构造。
对付上层的总线而言,连接在这条总线上的PCI桥也是一个设备。
但是这是一种分外的设备,它既是上层总线上的一个设备,实际上又是上层总线的延伸。

所谓列举,便是从Host/PCI桥开始进行探测和扫描,逐个“列举”连接在第一条PCI总线上的所有设备并记录在案。
如果个中的某个设备是PCI-PCI桥,则又进一步再探测和扫描连在这个桥上的次级PCI总线。
就这样递归下去,直到穷尽系统中的所有PCI设备。

其结果,是在内存中建立起一棵代表着这些PCI总线和设备的PCI树。
每个PCI设备(包括PCI桥设备)都由一个pci_dev构造体来表示,而每条PCI总线则由pci_bus构造来表示。
你有通过PCI桥建立起的硬件设备树,我有内存中通过数据构造构建的软件树,很和谐。

配置:PCI设备中一样平常都带有一些RAM和ROM 空间,常日的掌握/状态寄存器和数据寄存器也每每以RAM区间的形式涌现,而这些区间的地址在设备内部一样平常都是从0开始编址的,那么当总线上挂接了多个设备时,对这些空间的访问就会产生冲突。

以是,这些地址都要先映射到系统总线上,再进一步映射到内核的虚拟地址空间。
而所谓的配置便是通过对PCI配置空间的寄存器进行操作从而完成地址的映射(只完成内部编址映射到总线地址的事情,而映射到内核的虚拟地址空间是由设备本身的驱动要做的事情)。

(2)Linux内核怎么做的

这里首先要解释的是,对付PCI的设备初始化(即上面提到的列举和配置事情),PC机的BIOS和Linux内核都可以做。
一样平常而言,只假如采取PCI总线的PC机,其BIOS就必须供应对PCI总线操作的支持,因而称为PCI BIOS。

而且最早Linux内核也是通过这种BIOS调用的办法来获取系统中的PCI设备信息的,但是不是所有的平台都有BIOS(比如某些嵌入式系统),并且在实践中也创造有些母板上的PCI BIOS存在这样那样的问题,所往后来就改由Linux内核自己动手了,自己动手丰衣足食呵呵。

不过,Linux内核还是很谅解的在make menuconfig的选项里为我们供应了自己选择的权利,即PCI access mode,里面供应了四个选项分别是BIOS、MMconfig、Direct和Any。
Direct办法便是抛开BIOS而由内核自己完成初始化事情的意思。

二、开始我们的列举与配置之路

注:为了更清晰,大略的描述PCI设备的初始化过程(由于2.4.18中还没有引入设备驱动模型,这样可以让我们专心研究PCI设备驱动本身)。
这里是对Linux-2.4.18的内核进行的剖析,紧张缘故原由大家从参考资料中也该当能明白,这里很多便是参考资料[1]中的资料来剖析的。
如果想学PCI设备驱动,那么该当好好看看[1]的第八章中的PCI总线一节。
然后再能找到一个驱动的例子代码看看,就可以说算是对PCI设备驱动入门了,当然,条件是都看懂了 ,呵呵。

废话少说,下面进入正题。
前面提到了PCI有三种地址空间,个中的PCI配置空间是给Linux内核中的PCI初始化代码用的,也便是我们这里的列举与配置时用到的。
那么这个PCI配置空间里放的是什么东西呢,显然该当是寄存器,称为配置寄存器组。
当PCI设备上电时,硬件保持未激活状态。
即该设备只会对配置事务做出相应。
上电时,设备上不会有内存和I/O端口映射到打算机的地址空间;其他设备干系的功能,例如中断报告,也被禁止。

PCI标准规定每个设备的配置寄存器组最多可以有256字节的连续空间,个中开头的64字节的用场和格式是标准的,称为配置寄存器的头部。
系统中供应一些与硬件有关的机制,使得PCI配置代码可以检测在一个给定的PCI总线上所有可能的PCI配置寄存器头部,从而知道哪个PCI插槽上目前有设备,哪个插槽上暂无设备。
这是通过读PCI配置寄存器头部上的某个域完成的(一样平常是“Vendor ID\"大众 域)。
如果一个插槽上为空,上述操作会返回一些缺点返回值,如0xFFFFFFFF。

这种头部(指64字节头部)又有三种,个中“0型”(type 0)头部用于一样平常的PCI设备,“1型”头部用于各种PCI-PCI桥, “2型”头部是用于PCI-CardBus桥的,CardBus是条记本电脑中利用的总线,我们不关心。

而64字节头部中的16个字节中又包含着有关头部的类型、设备的种类、设备的一些性子、由谁制造等等信息。
根据这16个字节中供应的信息,来确定该当若何进一步阐明和处理剩余头部中的48个字节。
对付这16个字节的地址,include/linux/pci.h中定义了这样一些常数:

#define PCI_VENDOR_ID 0x00 / 16 bits /#define PCI_DEVICE_ID 0x02 / 16 bits /#define PCI_COMMAND 0x04 / 16 bits /#define PCI_STATUS 0x06 / 16 bits /#define PCI_CLASS_REVISION 0x08 / High 24 bits are class, low 8 revision /#define PCI_REVISION_ID 0x08 / Revision ID /#define PCI_CLASS_PROG 0x09 / Reg. Level Programming Interface /#define PCI_CLASS_DEVICE 0x0a / Device class /#define PCI_CACHE_LINE_SIZE 0x0c / 8 bits /#define PCI_LATENCY_TIMER 0x0d / 8 bits /#define PCI_HEADER_TYPE 0x0e / 8 bits /

对应我们的图3(见下)中的前16字节。
而且我们也看到了紧挨着PCI_HEADER_TYPE(即存放头部类型的寄存器)下面定义的便是上面提到的三种类型的头部:

#define PCI_HEADER_TYPE_NORMAL 0#define PCI_HEADER_TYPE_BRIDGE 1#define PCI_HEADER_TYPE_CARDBUS 2

在Linux系统上,可以通过cat /proc/pci 等命令查看系统中所有PCI设备的种别、型号以及厂商等信息,那便是从这些寄存器来的。
下面是在虚拟机中用lspci -x命令的信息截取(lspci命令也是利用/proc文件作为其信息来源):

00:00.0 Host bridge: Intel Corp. 440BX/ZX/DX - 82443BX/ZX/DX Host bridge (rev 01)00: 86 80 90 71 06 00 00 02 01 00 00 06 00 00 00 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0020: 00 00 00 00 00 00 00 00 00 00 00 00 ad 15 76 1930: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

首先要解释的是PCI寄存器是小端字节序格式的。
那么根据最下面的PCI配置寄存器组的构造(图3),显然这个Host bridge的Vendor ID是0x8086,我不说你也能猜到这个Vendor便是Intel。

这里有个问题要先说清楚,便是这些寄存器的地址问题,不然今后就进行不下去了。
配置寄存器可以让我们来进行配置以便完成PCI设备上的存储空间的访问,但这些配置寄存器本身也位于PCI设备地址空间中,如何访问这部分空间也就成了我们全体初始化事情的一个入口点,就像每个可实行程序都要有入口点一样。

PCI采取的办法是让所有设备的配置寄存器组都采取相同的地址,由所在总线的PCI桥在访问时附加上其他条件来区分。
而CPU则通过一个统一的入口地址向“宿主--PCI桥”发出命令,由相应的PCI桥间接的完成详细的读写。
对付i386构造的处理器,PCI总线的设计者在I/O地址空间保留了8个字节用于这个目的,那便是0xCF8~0xCFF。

这8个字节构成了两个32位的寄存器,第一个是“地址寄存器”0xCF8,第二个是“数据寄存器”0xCFC。
要访问某个设备中的某个配置寄存器时,CPU先往地址寄存器中写入目标地址,然后通过数据寄存器读写数据。
不过,写入地址寄存器的目标地址是一种总线号、设备号、功能号以及设备寄存器地址在内的综合地址。
格式如图2:

图2 ,写入地址寄存器0xCF8的综合地址

这里的总线号、设备号和功能号是对配置寄存器地址的扩充,便是上面提到的附加的其他条件。
首先每个PCI总线都有个总线号,主总线的总线号为0,别的的则由CPU在列举阶段每当探测到一个PCI桥时便为其指定一个,依次递增。
设备号一样平常代表着一块PCI接口卡(更确切的说是PCI总线接口芯片),常日取决于插槽的位置。
每块PCI接口卡上可以有多少个功能模块,这些功能模块共用一个PCI总线接口芯片,包括个顶用于地址映射的电子线路,以降落本钱。

从逻辑的角度说,每个“功能”实际上便是一个设备(看过USB设备驱动的人很眼熟吧 ,呵呵),以是设备号和功能号合在一起又可以称作“逻辑设备号”,而每块卡上最多可以容纳8个设备。

显然,这些字段(指全体32bit)结合在一起就惟一确定了系统中的一项PCI逻辑设备。
开始时,只有0号总线可以访问,在扫描0号总线时如果创造上面某个设备是PCI桥,就为之指定一个新的总线号,例如1,这样1号总线就可以访问了,这便是列举阶段的任务之一。

现在请读者考虑一个问题:当我们拿到一块PCI网卡,把它插到PC的主板上,打算写个这个网卡的驱动。
那么第一步该干什么呢?读者可以回顾前面的内容,既然我们说Linux内核帮我们做了设备的列举和配置事情,那么我在写网卡驱动之前是不是可以先看看Linux内核对我们的这个PCI网卡设备完成的列举事情的结果呢?或者直白些说,我把网卡插上了,现在Linux内核有没有识别出这块设备呢? 把稳识别出设备跟能正常利用设备是不同的观点,这很好理解。

安装过PC网卡驱动的人都知道,当设备的驱动没有安装时,我们在设备管理器中是可以看到这个设备的,不过上面是一个黄色的大问号。
而在Linux系统中,我们可以通过lspci命令来查看。

下面是在LDD3的PCI驱动那一章截取的一段内容: lspci 的输出( pciutils 的一部分, 在大部分发布中都有)和在 /proc/pci 和 /porc/bus/pci 中的信息排布. PCI 设备的 sysfs 表示也显示了这种寻址方案, 还有 PCI 域信息,当显示硬件地址时, 它可被显示为 2 个值( 一个 8-位总线号和一个 8-位 设备和功能号), 作为 3 个值( bus, device, 和 function), 或者作为 4 个值(domain, bus, device, 和 function); 所有的值常常用 16 进制显示.

例如, /proc/bus/pci/devices 利用一个单个16位字段(来便于剖析和排序), 而 /proc/bus/busnumber 划分地址为3个字段. 下面内容显示了这些地址如何显示, 只显示了输出行的开始 :

$ lspci | cut -d: -f1-30000:00:00.0 Host bridge0000:00:00.1 RAM memory0000:00:00.2 RAM memory0000:00:02.0 USB Controller0000:00:04.0 Multimedia audio controller0000:00:06.0 Bridge0000:00:07.0 ISA bridge0000:00:09.0 USB Controller0000:00:09.1 USB Controller0000:00:09.2 USB Controller0000:00:0c.0 CardBus bridge0000:00:0f.0 IDE interface0000:00:10.0 Ethernet controller0000:00:12.0 Network controller0000:00:13.0 FireWire (IEEE 1394)0000:00:14.0 VGA compatible controller

$ cat /proc/bus/pci/devices | cut -f1000000010002001000200030003800480049004a0060007800800090009800a0

$ tree /sys/bus/pci/devices//sys/bus/pci/devices/|-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0|-- 0000:00:00.1 -> ../../../devices/pci0000:00/0000:00:00.1|-- 0000:00:00.2 -> ../../../devices/pci0000:00/0000:00:00.2|-- 0000:00:02.0 -> ../../../devices/pci0000:00/0000:00:02.0|-- 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0|-- 0000:00:06.0 -> ../../../devices/pci0000:00/0000:00:06.0|-- 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0|-- 0000:00:09.0 -> ../../../devices/pci0000:00/0000:00:09.0|-- 0000:00:09.1 -> ../../../devices/pci0000:00/0000:00:09.1|-- 0000:00:09.2 -> ../../../devices/pci0000:00/0000:00:09.2|-- 0000:00:0c.0 -> ../../../devices/pci0000:00/0000:00:0c.0|-- 0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0|-- 0000:00:10.0 -> ../../../devices/pci0000:00/0000:00:10.0|-- 0000:00:12.0 -> ../../../devices/pci0000:00/0000:00:12.0|-- 0000:00:13.0 -> ../../../devices/pci0000:00/0000:00:13.0`-- 0000:00:14.0 -> ../../../devices/pci0000:00/0000:00:14.0

所有的 3 个设备列表都以相同顺序排列, 由于 lspci 利用 /proc 文件作为它的信息源。
拿 VGA 视频掌握器作一个例子, 0x00a0 意思是 0000:00:14.0 当划分为域(16位), 总线(8位), 设备(5位)和功能(3位).为什么0x00a0对应的是0000:00:14.0呢,这就要看图2中的内容了,根据图2中的寄存器对应0x00a0就代表着总线(8位), 设备(5位)和功能(3位).0x00a0=0000000010100000,很随意马虎看出高8位是总线号也便是0。
剩下的0xa0=10100000,可以看出如果低3位表示功能号,那么剩下的10100便是设备号,补全成8位的值便是00010100即0x14.

图3 ,PCI配置寄存器组

参考资料:

[1] Linux内核源代码情景剖析(下册)

[2] Linux设备驱动开拓详解

[3] Linux设备驱动(第三版)

[4] 内核Documentation下的pci.txt

[5] 精通Linux设备驱动开拓

[6] http://tldp.org/LDP/tlk/dd/pci.html

[7] http://linux.die.net/man/8/lspci

[8] http://www.ibm.com/developerworks/cn/linux/l-pci

标签:

相关文章