话说,小弟接触计算机(不是“电脑”)也有很多个年头了,很多名词也能朗朗上口,但仅仅限于名词,对其中的原理却没有深入的了解。像MS DOS,其实也是前段时间才真正“安装”了一个用来更新设备的BIOS。而如USB、ISA、PCI这些简称,同样地不了解,以至于不敢与人谈自己是嵌入式工程师。大学时没有计算接口这一类的课程,自己找书看了,但发现里面讲的和平时所学的差别太大,又感觉自己后面不会用到,所以也就囫囵吞枣一遍,后来终于还是有所接触,只不过徒知其名,不知其理。
去年年底移植了一个x86平台的SPI flash的驱动——其实是参考开源项目,加以修改整合到实际应用中。一开始就接触了PCI,因为SPI寄存器基地址就是从PCI配置空间中拿到的。于是上网找资料。还借了本公司的PCI局部总线原理的书来看。打开那本书,发现都是讲硬件电气特性、通信协议这些细节,对于配置空间却涉及很少。我只是想了解怎么从手上这块板子的PCI中拿到基地址。于是果然放弃书本——这也暴露了本人不求甚解的本质。然后决定还是从网络找资料,看代码、看配置空间,等等。慢慢有一点点了解。鉴于自己写文章一向是根据自己实际情况来写“流水账”,还是继续吧。 我所关心的PCI有2点:配置空间(pci configurationspace)是长怎么样的;如何获取到PCI设备。 网上有很多文章都讲有配置空间。下面是我从网上找到一张讲配置空间的图: 这个图只是表述了一般的内容,有些不在上述图中却有用处,这要具体看手册的说明才行。比如,前段时间搞的flash驱动,spi的寄存器基地址位于pci的0x54处。 访问PCI配置空间是通过2个IO端口来进行的(这点承认写得不到位,对微机接口了解不深)。 CONFIG_ADDRESS – 地址寄存器, PCI配置空间地址写到该寄存器,IO端口为CF8h。 CONFIG_DATA - 数据寄存器,来用读取PCI数据,IO端口为CFCh上述端口均为32位寄存器。 访问地址组成格式如下图: 有了配置空间的认识,有了访问的方法和地址,就可以访问PCI设备了。代码表示如下:
1 2 3 4 5 // 格式 31 Enabled 23:16 总线编号 15:11 设备编号 10:8 功能编号 7:2 配置空间寄存器编号 1:0 为0 io_addr = 0x80000000 + (bus<<16) + (device<<11) + (function<<8); // 将地址写到CONFIG_ADDR寄存器 outl(io_addr, PCI_CONFIG_ADDR); result = inl(PCI_CONFIG_DATA); // 读data数据寄存器,得到id
在Linux上,可以使用lspci命令(有的系统可能没安装lspci)来查找PCI设备。下面是一个例子:
1 2 3 4 5 6 7 8 9 10 # lspci 00:00.0 Class 0600: Device 8086:0f00 (rev 0c) 00:02.0 Class 0300: Device 8086:0f31 (rev 0c) ... 00:1c.3 Class 0604: Device 8086:0f4e (rev 0c) 00:1d.0 Class 0c03: Device 8086:0f34 (rev 0c) 00:1f.0 Class 0601: Device 8086:0f1c (rev 0c) 00:1f.3 Class 0c05: Device 8086:0f12 (rev 0c) 01:00.0 Class 0200: Device 8086:1539 (rev 03) 02:00.0 Class 0200: Device 8086:1539 (rev 03)
lspci还可以列出具体某一个PCI设备的信息,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # lspci -v -s 00:1f.3 -xxx 00:1f.3 Class 0c05: Device 8086:0f12 (rev 0c) Subsystem: Device 8086:7270 Flags: medium devsel, IRQ 11 Memory at d0816000 (32-bit, non-prefetchable) [size=32] I/O ports at 3000 [size=32] Capabilities: [50] Power Management version 3 00: 86 80 12 0f 03 00 90 02 0c 00 05 0c 00 00 00 00 10: 00 60 81 d0 00 00 00 00 00 00 00 00 00 00 00 00 20: 01 30 00 00 00 00 00 00 00 00 00 00 86 80 70 72 30: 00 00 00 00 50 00 00 00 00 00 00 00 0b 02 00 00 40: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 50: 01 00 03 00 08 00 00 00 00 00 00 00 00 00 00 00 60: 03 04 04 00 00 00 08 08 00 00 00 00 00 00 00 00 70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80: 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f0: 00 00 00 00 00 00 00 00 1a 0f 0c 01 03 01 00 00
注意,命令的“-xxx”就是“-xxx”,是一个命令选项,不是表示未知或按实际输入的意思。每一偏移的地址、数值都可以在里面找到。如果有闲情,可以对照着PCI配置空间来慢慢了解个中的数值的含义。 下面参照一个intel芯片手册来了解PCI。以SMBUS为具体例子,它的配置空间说明如下: 红框是我画的,是我很关心的字段。前面2个字段就是PCI的VID和DID,是只读的。通过它,就可以判断出是不是自己想要的PCI设备,因为系统中的PCI设备是很多的,需要根据ID来区别不同的设备。 设备ID(DevID)寄存器如下: 右下方面的RO表示是只读的。左上方红框的SMB_Config_DID: [B:0, D:31, F:3] + 2h表述了PCI设备地址和偏移量。B、D、F分别表示bus、device、function,它的值对应着前面用lspci查找到的地址。+2h表示偏移量。这在intel手册中经常见,我也是问了一个PCI高手才知道的。 参考网上资料写的一个遍历PCI设备的程序,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <inttypes.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/io.h> // inb/outb,etc... #define PCI_CONFIG_ADDR 0xCF8 #define PCI_CONFIG_DATA 0xCFC int pci_autodetect() { uint32_t result; uint32_t io_addr; int function = 0; int bus = 0; int device = 0; int rev = 0; uint32_t vendor_id, dev_id, class1, class2, class3; for (bus = 0; bus<5; bus++) { for (device = 0; device<32; device++) { for(function = 0; function<8 ; function++ ) { // 格式 31 Enabled 23:16 总线编号 15:11 设备编号 10:8 功能编号 7:2 配置空间寄存器编号 1:0 为0 io_addr = 0x80000000 + (bus<<16) + (device<<11) + (function<<8); // 将地址写到CONFIG_ADDR寄存器 outl(io_addr, PCI_CONFIG_ADDR); result = inl(PCI_CONFIG_DATA); // 读data数据寄存器,得到id if (result != 0xffffffff) // not the bad one print it { vendor_id = result & 0xFFFF; dev_id = result >> 16; outl(io_addr+0x08, PCI_CONFIG_ADDR); result = inl(PCI_CONFIG_DATA); rev = result&0xff; class3 = (result>>8)&0xff; class2 = (result>>16)&0xff; class1 = (result>>24)&0xff; printf("0x%08x %02x:%02x.%x ", io_addr, bus, device, function); //printf("PCI Read result: 0x%08x ioaddr: 0x%08x ", result, io_addr); printf("Class %02x%02x: Device %04x:%04x (rev: %02x)\n", class1, class2, vendor_id, dev_id,rev); } } } } return 0; } int main(void) { iopl(3); // 必须要这句 pci_autodetect(); return 0; }
上面的程序,在虚拟机上运行结果的一个示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 0x80000000 00:00.0 Class 0600: Device 8086:7190 (rev: 01) 0x80000800 00:01.0 Class 0604: Device 8086:7191 (rev: 01) 0x80003800 00:07.0 Class 0601: Device 8086:7110 (rev: 08) 0x80003900 00:07.1 Class 0101: Device 8086:7111 (rev: 01) 0x80003b00 00:07.3 Class 0680: Device 8086:7113 (rev: 08) 0x80003f00 00:07.7 Class 0880: Device 15ad:0740 (rev: 10) 0x80007800 00:0f.0 Class 0300: Device 15ad:0405 (rev: 00) 0x80008000 00:10.0 Class 0100: Device 1000:0030 (rev: 01) 0x80008800 00:11.0 Class 0604: Device 15ad:0790 (rev: 02) 0x8000a800 00:15.0 Class 0604: Device 15ad:07a0 (rev: 01) 0x8000a900 00:15.1 Class 0604: Device 15ad:07a0 (rev: 01) 0x8000aa00 00:15.2 Class 0604: Device 15ad:07a0 (rev: 01) 0x8000ab00 00:15.3 Class 0604: Device 15ad:07a0 (rev: 01) 0x8000ac00 00:15.4 Class 0604: Device 15ad:07a0 (rev: 01) 0x8000ad00 00:15.5 Class 0604: Device 15ad:07a0 (rev: 01) 0x8000ae00 00:15.6 Class 0604: Device 15ad:07a0 (rev: 01) 0x8000af00 00:15.7 Class 0604: Device 15ad:07a0 (rev: 01) 0x8000b000 00:16.0 Class 0604: Device 15ad:07a0 (rev: 01)
用lspci命令结果如下:
1 2 3 4 5 6 7 8 9 10 11 # lspci 00:00.0 Host bridge: Intel Corporation 440BX/ZX/DX - 82443BX/ZX/DX Host bridge (rev 01) 00:01.0 PCI bridge: Intel Corporation 440BX/ZX/DX - 82443BX/ZX/DX AGP bridge (rev 01) 00:07.0 ISA bridge: Intel Corporation 82371AB/EB/MB PIIX4 ISA (rev 08) 00:07.1 IDE interface: Intel Corporation 82371AB/EB/MB PIIX4 IDE (rev 01) 00:07.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 08) 00:07.7 System peripheral: VMware Virtual Machine Communication Interface (rev 10) 00:0f.0 VGA compatible controller: VMware SVGA II Adapter 00:10.0 SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01) 00:11.0 PCI bridge: VMware PCI bridge (rev 02) 00:15.0 PCI bridge: VMware PCI Express Root Port (rev 01)
对比可以发现,程序还没有做到获取PCI设备厂商描述信息。 本文不涉及到PCI电气特性,不涉及到linux内核深奥的框架,仅作为一个入门者的笔记。 参考资源: PCI文章:http://blog.chinaunix.net/uid-618506-id-204331.html http://blog.csdn.net/linuxdrivers/article/details/5849698 芯片手册(从搜索引擎可以搜索到的):http://www.intel.com/content/www/us/en/intelligent-systems/bay-trail/atom-e3800-family-datasheet.html
李迟,发表于2015年2月9日,中午