linux驱动---用I/O命令访问PCI总线设备配置空间
PCI总线推出以来,以其独有的特性受到众多厂商的青睐,已经成为计算机扩展总线的主流。目前,国内的许多技术人员已经具备开发PCI总线接口设备的能 力。但是PCI总线的编程技术,也就是对PCI总线设备的操作技术,一直是一件让技术人员感到头疼的事情。PCI总线编程的核心技术是对相应板卡配置空间 的理解和访问。一般软件编程人员基于对硬件设备原理的生疏,很难理解并操作配置空间,希望硬件开发人员直接告诉他们怎样操作;而PCI总线硬件开发人员虽 深刻地理解了其意义,在没有太多编程经验地前提下,也难于轻易地操作PCI板卡。结果大多是硬件技术人员花费大量时间和精力去学习DDK、 WINDRVER等驱动程序开发软件。
作者在开发PCI总线接口设备时,经过对PCI总线协议的深入研究,从协议本身的角度出发,找到一种方面而快捷的PCI配置空间操作方法,只使用简单的 I/O命令即可找到特定的PCI总线设备并对其所有的配置空间进行读写操作。一旦读得其配置空间的内容,即可中得到担任系统对该PCI总线设备的资源分 配。
1 PCI总线配置空间及配置机制
为避免各PCI设备在资源的占用上发生冲突,PCI总线采用即插即用协议。即在系统建立时由操作系统按照各设备的要求统一分配资源,资源分配的信息由系统 写入各PCI设备的配置空间寄存器,并在操作系统内部备份。各PCI设备有其独自的配置空间,设计者通过对积压设备(或插槽)的ISDEL引脚的驱动区分 不同设备的配置空间。配置空间的前64个字节称为配置空间的预定自区,它对每个设备都具有相同的定义且必须被支持;共后的空间称为设备关联区,由设备制造 商根据需要定义。与编程有关的配置空间信息主要有:
(1)设备号(Device ID)及销售商号(Vendor ID),配置空间偏移量为00h,用于对各PCI设备的区分和查找。为了保证其唯一性,Vendor ID应当向PCI特别兴趣小组(PCI SIG)申请而得到。
(2)PCI基地址(PCI Base Address),配置空间偏移量为10~24h,设备通过设定可读写的高位数值来向操作系统指示所需资源空间的大小。比如,某设备需要64K字节的内存 空间,可以将配置空间的某基地址寄存器的高16位设成可读写的,而将低16位置为0(只可读)。操作系统在建立时,先向所有位写1,实际上只有高16位被 接收而被置成了1,低16位仍为0.这样操作系统读取该寄存器时,返回值为FFFF0000h,据此操作系统可以断定其需要的空间大小是64K字节,然后 分配一段空闲的内存空间并向该寄存器的高16位填写其地址。
其它可能与编程有关的配置空间的定义及地址请参阅参考文献[1]。
由于PC-AT兼容系统CPU只有内存和I/O两种空间,没有专用的配置空间,PCI协议规定利用特定的I/O空间操作驱动PCI桥路转换成配置空间的操 作。目前存在两种转换机制,即配置机制1#和配置机制2#。配置机制2#在新的设计中将不再被采用,新的设计应使用配置机制1#来产生配置空间的物理操 作。这种机制使用了两个特定的32位I/O空间,即CF8h和CFCh。这两个空间对应于PCI桥路的两个寄存器,当桥路看到CPU在局部总线对这两个 I/O空间进行双字操作时,就将该I/O操作转变为PCI总线的配置操作。寄存器CF8h用于产生配置空间的地址(CONFIG-ADDRESS),寄存 器CFCh用于保存配置空间的读写数据(CONFIG-DATA)。
配置空间地址寄存器的格式如图1。
CF8H(局部总线):
当CPU发出对I/O空间CFCh的操作时,PCI桥路将检查配置空间地址寄存器CF8h的31位。如果为1,就在PCI总线上产生一个相应的配置空间读或写操作,其地址由PCI桥路根据配置空间地址寄存器的内容作如图2所示的转换。
CFCh (局部总线):
设备号被PCI桥路译码产生PCI总线地址的高位地址,它们被设计者用作IDSEL信号来区分相应的PCI设备。6位寄存器号用于寻址该PCI设备配置空 间62个双字的配置寄存器(256字节)。功能号用于区分多功能设备的某特定功能的配置空间,对常用的单功能设备为000。某中PCI插槽的总线号随系统 (主板)的不同稍有区别,大多数PC机为1,工控机可能为2或3。为了找到某设备,应在系统的各个总线号上查找,直到定位。如果在0~5号总线上不能发现 该设备,即可认为该设备不存在。
理解了上述PCI协议里的配置机制后,就可以直接对CF8h和CFCh两个双字的I/O空间进行操作,查找某个PCI设备并访问其配置空间,从而得到操作系统对该PCI设备的资源分配。
2 用I/O命令访问PCI总线配置空间
要访问PCI总线设备的配置空间,必须先查找该设备。查找的基本根据是各PCI设备的配置空间里都存有特定的设备号(Device ID)及销售商号(Vendor ID),它们占用配置空间的00h地址。而查找的目的是获得该设备的总线号和设备号。查找的基本过程如下:用I/O命令写配置空间的地址寄存器CF8h, 使其最高位为1,总线号及设备为0,功能号及寄存器号为0,即往I/O端口CF8h80000000h;然后用I/O命令读取配置空间的数据寄存器 CFCh。如果该寄存器值与该PCI设备的Device ID及Vendor ID不相符,则依次递增设备号/总线号,重复上述操作直到找到该设备为止。如果查完所有的设备号/总线号(1~5)仍不能找到该设备,则应当考虑硬件上的 问题。对于多功能设备,只要设备配置寄存器相应的功能号值,其余步骤与单功能设备一样。
如查找设备号为9054h,销售商号为10b5的单功能PCI设备,编写的程序如下:
unsigned int ioa0,iod;
int scan( )
{
bus=0;device=0;
for(char i=0;i<5;i++) {
for(char j=0;j<32;j++) {
bus=i; device=j;
ioa0=0x80000000+bus*0x10000
+(device*8)*0x100;
_outpd(0xcf8,ioa0);
iod=_inpd(0xcfc);
if (iod0= =0x905410b5) return 0;
}
}
retrn -1
}
调用子程序scan( ),如果返回值为-1,则没有找到该PCI设备。如果返回值为0,则找到了该PCI设备。该设备的总线号和设备号分别在全局变量bus和device中, 利用这两个变量即可轻易对该设备的配置空间进行访问,从而得到分配的资源信息。假设该PCI设备占用了4个资源空间,分别对应于配置空间10h~1ch, 其中前两个为I/O空间,后两个为内存空间,若定义其基地址分别为ioaddr1,ioaddr2,memaddr1,memaddr2,相应的程序如 下:
unsigned int memaddr1,memaddr2;
unsigned int iobase,ioa;
void getbaseaddr(char bus,char device);
{
iobase=0x80000000+bus*0x10000+(device*8)*0x100;
ioa=iobase+0x10;/*寻址基地址寄存器0*/
_outpd(0xcf8,ioa);
ioaddr1=(unsigned short)_inpd(0xcfc)&0xfffc;
/*屏蔽低两位和高16位*/
ioa=iobase+0x14; /*寻址基地址寄存器1*/
_outpd(0xcf8,ioa);
ioaddr2=(unsigned short)_inpd(0xcfc)&0xfffc;
ioa=iobase+0x18;/*寻址基地寄存器2*/
_outpd(0xcf8,ioa);
memaddr1=_inpd(0xcfc) & 0xfffffff0;
/*屏蔽低4位*/
ioa=iobase+0x1c; /*寻址基地址寄存器3*/
_outpd(0xcf8,ioa);
memaddr2=_inpd(0xcfc) & 0xfffffff0;
}
对于I/O基地址,最低两位D0、D1固定为01,对地址本身无效,应当被屏蔽。对PC-AT兼容机,I/O有效地址为16位,因此高位也应被屏蔽。对于 内存地址,最低位D0固定为0,而D1~D3用于指示该地址的一些物理特性[1],因此其低4位地址应当被屏蔽。需要指出的是该内存地址是系统的物理地 址,在WINDOWS运行于保护模式时,需要经过转换得到相应的线性地址才能对该内存空间进行直接读写。介绍该转换方法的相关文章较为常见,此处不再赘 述。
上述程序给出了读取配置空间里的基地址的方法。另有相当多PCI设备通过配置空间的设备关联区来设置该设备的工作状态,可轻易地用I/O命令进行相应的设置,无须编写繁杂的驱动程序。在开发PCI视频图像采集卡的过程中,该方法得到了实际应用。
#define PCI_CFG_DATA 0xcfc
#define PCI_CFG_CTRL 0xcf8
void pci_read_config_byte(unsigned char bus, unsigned char dev, unsigned char offset, unsigned char *val)
{
unsigned char fun = 0;
outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset & ~0x3)), PCI_CFG_CTRL);
*val = inl(PCI_CFG_DATA) >> ((offset & 3) * 8);
}
void pci_read_config_word(unsigned char bus, unsigned char dev, unsigned char offset, unsigned short *val)
{
unsigned char fun = 0;
outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset & ~0x3)), PCI_CFG_CTRL);
*val = inl(PCI_CFG_DATA) >> ((offset & 3) * 8);
}
void pci_read_config_dword(unsigned char bus, unsigned char dev, unsigned char offset, unsigned int *val)
{
unsigned char fun = 0;
outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset)), PCI_CFG_CTRL);
*val = inl(PCI_CFG_DATA);
}
很明显就是先向控制寄存器写入综合地址,格式前面已经提到,对比一下是完全一样的。然后从数据寄存器读数据即可,由于数据寄存器是32位的,如果不是读取双字,需要做移位操作。
另外一定需要注意大小端问题,如需要就要进行大小端转换,下面写程序也一样。
5. 写程序
void pci_write_config_dword(unsigned char bus, unsigned char dev, unsigned char offset, unsigned int val)
{
unsigned char fun = 0;
outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset)), PCI_CFG_CTRL);
outl(val, PCI_CFG_DATA);
}
void pci_write_config_word(unsigned char bus, unsigned char dev, unsigned char offset, unsigned short val)
{
unsigned long tmp;
unsigned char fun = 0;
outl((0x80000000 | ((bus)<<16) |((dev)<<11) | ((fun)<<8) | (offset & ~0x3)), PCI_CFG_CTRL);
tmp = inl(PCI_CFG_DATA);
tmp &= ~(0xffff << ((offset & 0x3) * 8));
tmp |= (val << ((offset & 0x3) * 8));
outl(tmp, PCI_CFG_DATA);
}
void pci_write_config_byte(unsigned char bus, unsigned char dev, unsigned char offset, unsigned short val)
{
unsigned long tmp;
unsigned char fun = 0;
outl((0x80000000 | ((bus)<<16) |((dev)<<11) |((fun)<<8) | (offset & ~0x3)), PCI_CFG_CTRL);
tmp = inl(PCI_CFG_DATA);
tmp &= ~(0xff << ((offset & 0x3) * 8));
tmp |= (val << ((offset & 0x3) * 8));
outl(tmp, PCI_CFG_DATA);
}
写程序同读程序一样,先向控制寄存器写入综合地址,然后向数据寄存器写入数据。
6. 问题
上面的程序都是参考linux内核对pci空间的读写程序写的。但是在应用程序中读写pci空间和在内核中读写pci空间是完全不同的。在linux源代码中可以看到,在进行pci空间的读写操作都是在关闭中断的情况下进行的,而在用户程序空间就没有这个手段了。所以,读写可能会出错。
经过本人试验,读基本上没有出错过,而写有一定出错的概率,慎用!
有兴趣的,可以随便写个应用程序试试看。
7. 源代码
附上一份源代码,可以直接编译运行。
#include <stdio.h>
#include <stdlib.h>
#include <sys/io.h>
static unsigned int read_pci_config_32(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset)
{
unsigned int v;
outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
v = inl(0xcfc);
return v;
}
unsigned char read_pci_config_8(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset)
{
unsigned char v;
outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
v = inb(0xcfc + (offset&3));
return v;
}
unsigned short read_pci_config_16(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset)
{
unsigned short v;
outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
v = inw(0xcfc + (offset&2));
return v;
}
void write_pci_config_32(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset, unsigned int val)
{
outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
outl(val, 0xcfc);
}
void write_pci_config_8(unsigned char bus,unsigned char slot, unsigned char func, unsigned char offset, unsigned char val)
{
outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
outb(val, 0xcfc + (offset&3));
}
void write_pci_config_16(unsigned char bus,unsigned char slot, unsigned char func, unsigned char offset, unsigned char val)
{
outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
outw(val, 0xcfc + (offset&2));
}
int main(void)
{
iopl(3);
printf("0 0 0 0 = %x\n", read_pci_config_16(0, 0 , 0, 0));
printf("0 0 0 2 = %x\n", read_pci_config_16(0, 0 , 0, 2));
printf("0 1 0 0 = %x\n", read_pci_config_16(0, 1 , 0, 0));
printf("0 1 0 2 = %x\n", read_pci_config_16(0, 1 , 0, 2));
printf("0 7 1 0 = %x\n", read_pci_config_16(0, 7 , 1, 0));
printf("0 7 1 2 = %x\n", read_pci_config_16(0, 7 , 1, 2));
return 0;
}
//
/////////////////////////////////////////////////////////
#include <sys/io.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
unsigned char bus;
unsigned char device,i,j;
unsigned int pcicfgaddr=0;
static unsigned int read_pci_config_32(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset)
{
unsigned int v;
outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
v = inl(0xcfc);
return v;
}
void write_pci_config_32(unsigned char bus, unsigned char slot, unsigned char func, unsigned char offset, unsigned int val)
{
outl(0x80000000 | (bus<<16) | (slot<<11) | (func<<8) | offset, 0xcf8);
outl(val, 0xcfc);
}
int scan()
{//list pci device
bus=0;device=0;
for( i=0;i<5;i++)
{
for( j=0;j<32;j++)
{
bus=i; device=j;
pcicfgaddr=read_pci_config_32(bus,device,0,0);
// printf("%d %d 0x%x\n",bus,device, pcicfgaddr);
if (pcicfgaddr==0x27201103)//deviceID&vendorID
{
pcicfgaddr=read_pci_config_32(bus,device,0,0x18)&0xfffffff0;
printf("%d %d 0x%x\n",bus,device,pcicfgaddr );
return pcicfgaddr;
}
}
}
return 0 ;
}
int main(int argc, char *argv[])
{
int i;
unsigned char * base2addr;
int fd;
iopl(3);
if(scan()==0)
{
printf("no card\n");
return 0;
}
if ( (argc < 2) || (1 != strlen(argv[1])))
{
printf("1: set raid to sata1.0(1.5Gbps)\n");
printf("2: set raid to sata2.0(3Gbps)\n");
printf("3: set raid to sata3.0(6Gbps)\n");
return -1;
}
fd = open ("/dev/mem", O_RDWR);
if (fd < 0)
{
printf("cannot open /dev/mem.\n");
return -1;
}
//map raid card bar2 physical addr
base2addr = (unsigned char *)mmap(0, 0x40000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, pcicfgaddr);
if(base2addr < 0)
{
printf("base2addr mmap failed.\n");
return -1;
}
switch (*argv[1])
{
case '1':
//sata1.0(1.5Gbps)
for(i=0; i<4; i++)
{
*(unsigned int *)(base2addr+0x20000+0x250+i*8) = 0x08;
*(unsigned int *)(base2addr+0x20000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x20000+0x254+i*8))&0xffffff8f)|0x00000011;
*(unsigned int *)(base2addr+0x24000+0x250+i*8) = 0x08;
*(unsigned int *)(base2addr+0x24000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x24000+0x254+i*8))&0xffffff8f)|0x00000011;
}
printf("1: set raid to sata1.0(1.5Gbps)\n");
break;
case '2':
//sata2.0(3Gbps)
for(i=0; i<4; i++)
{
*(unsigned int *)(base2addr+0x20000+0x250+i*8) = 0x08;
*(unsigned int *)(base2addr+0x20000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x20000+0x254+i*8))&0xffffff8f)|0x00000031;
*(unsigned int *)(base2addr+0x24000+0x250+i*8) = 0x08;
*(unsigned int *)(base2addr+0x24000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x24000+0x254+i*8))&0xffffff8f)|0x00000031;
}
printf("2: set raid to sata2.0(3Gbps)\n");
break;
case '3':
//sata3.0(6Gbps)
for(i=0; i<4; i++)
{
*(unsigned int *)(base2addr+0x20000+0x250+i*8) = 0x08;
*(unsigned int *)(base2addr+0x20000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x20000+0x254+i*8))&0xffffff8f)|0x00000071;
*(unsigned int *)(base2addr+0x24000+0x250+i*8) = 0x08;
*(unsigned int *)(base2addr+0x24000+0x254+i*8) = ((*(unsigned int *)(base2addr+0x24000+0x254+i*8))&0xffffff8f)|0x00000071;
}
printf("3: set raid to sata3.0(6Gbps)\n");
break;
}
/*
((*(unsigned int *)(base2addr+0x20258))) = 0xc;
sleep(1);
printf("base2addr mmap 0x%x.\n",((*(unsigned int *)(base2addr+0x2025c))));
sleep(1);
((*(unsigned int *)(base2addr+0x24258))) = 0xc;
sleep(1);
printf("base2addr mmap 0x%x.\n",((*(unsigned int *)(base2addr+0x2425c))));
*/
munmap(base2addr, 0x40000); //destroy map memory
close(fd); //close
return 0;
}
linux驱动---用I/O命令访问PCI总线设备配置空间的更多相关文章
- Linux PCI/PCI-E设备配置空间读取与修改
Linux PCI/PCI-E设备配置空间读取与修改 1 前言 PCI和PCI Express,是计算机常使用的一种高速总线.操作系统中的PCI/PCI-E设备驱动以及操作系统内核,都需要访问PCI及 ...
- Linux驱动设计——内存与IO访问
名词解释 内存空间与IO空间 内存空间是计算机系统里面非系统内存区域的地址空间,现在的通用X86体系提供32位地址,寻址4G字节的内存空间,但一般的计算机只安装256M字节或者更少的内存,剩下的高位内 ...
- 007 PCI总线的桥与配置(二)
一.PCI桥与PCI设备的配置空间 PCI设备都有独立的配置空间,HOST主桥通过配置读写总线事务访问这段空间.PCI总线规定了三种类型的PCI配置空间,分别是PCI Agent设备使用的配置空间,P ...
- 3.1 PCI设备BAR空间的初始化
在PCI Agent设备进行数据传送之前,系统软件需要初始化PCI Agent设备的BAR0~5寄存器和PCI桥的Base.Limit寄存器.系统软件使用DFS算法对PCI总线进行遍历时,完成这些寄存 ...
- 2.1 存储器域与PCI总线域
HOST主桥的实现因处理器系统而异.PowerPC处理器和x86处理器的HOST主桥除了集成方式不同之外,其实现机制也有较大差异.但是这些HOST主桥所完成的最基本功能依然是分离存储器域与PCI总线域 ...
- PCI总线学习
PCI总线概述: 因为不实际操作了,所以就写一些方法论上的东西,纪念一下. PCI总线有三个非常显著的优点: 1. 在计算机和外设传输数据时具有更好的性能. 2. 能够尽量独立于具体的平台. 3. 可 ...
- 006 PCI总线的桥与配置(一)
在PCI体系结构中,含有两类桥片,一个是HOST主桥,另一个是PCI桥.在每一个PCI设备中(包括PCI桥)都含有一个配置空间.这个配置空间由HOST主桥管理,而PCI桥可以转发来自HOST主桥的配置 ...
- 1.1 PCI总线的组成结构
如上文所述,PCI总线作为处理器系统的局部总线,是处理器系统的一个组成部件,讲述PCI总线的组成结构不能离开处理器系统这个大环境.在一个处理器系统中,与PCI总线相关的模块如图1?1所示. 如图1?1 ...
- 2.4 PCI总线的配置
PCI总线定义了两类配置请求,一个是Type 00h配置请求,另一个是Type 01h配置请求.PCI总线使用这些配置请求访问PCI总线树上的设备配置空间,包括PCI桥和PCI Agent设备的配置空 ...
随机推荐
- DELL服务器硬件信息采集SHELL脚本
DELL服务器硬件信息采集SHELL脚本最近需要做资产列表,要采集DELL服务器的硬件信息,包括如下信息:1.操作系统信息(类型,版本,内核,平台,主机名)2.主板信息(厂商,机型,序列号)3.CPU ...
- oracle学习(一)
作为一个入门选手,怕忘记,所以所有东西都尽量写下来.(省略oracle11g的安装过程) 一.sqlpuls用sys账户登录 (sqlplus是客户端连上服务器的一个工具) 1.使用cmd控制台登录 ...
- 实现兼容document.querySelector的方法
var querySelector = function(selector) { //TODO 先简单兼容,后续继续扩展: var element = null; if(document.queryS ...
- IE7、IE8不兼容js trim函数的解决方法
IE兼容,有时候让人头疼,但又不得不去解决. 先看看一下代码: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xh ...
- 单元测试系列:Mock工具Jmockit使用介绍
更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 原文链接:http://www.cnblogs.com/zishi/p/6760272.html Mock工具Jm ...
- centos 下安装 Jre 及 selenium
下载软件包 下载链接: jre-7u55-linux-i586.tar.gz : http://pan.baidu.com/s/14cjds selenium-server-standalone-2. ...
- yii 缓存之apc
首先yii CApcCache 实现了一个针对APC的缓存应用组件,常见的缓存操作方法get,set,add,delete,flush... 下面说说配置: 1. 在config/main.php c ...
- 时间函数DateTime()的用法
//2008年4月24日 System.DateTime.Now.ToString("D"); //2008-4-24 System.DateTime.Now.ToString(& ...
- 使用jvisualvm来远程观察Java程序及jvisualvm插件[转]
jvisualvm是JDK自带的监控JVM运行状况的工具,利用jvisualvm可以查看JVM的运行.GC情况:线程的运行情况:内存中类及实例的情况等. 使用jvisualvm来远程观察Java程序, ...
- 济南清北学堂游记 Day 4.
不知不觉,行程已经过半了啊. 基本上已经熟悉了这里的环境,这其实也意味着我应该很快就要走了. 明天和后天还有最后四场模拟赛,虽然以我的实力拿不到奖,但我也会尽力做好我自己. 我大概反思了一下这几天,其 ...