应用程序中pci配置空间读写
~~~~~~~~~~~~~~~~~~~~~~~
1. 概述
主要讲述在linux操作系统中应用程序如何读写pci配置空间。
2. 基本原理
对于pci设备,由cpu通过一个统一的入口地址向pci总桥发出命令,再由相应的pci桥间接地完成具体的读写操作。对于X86结构,pci总线的设计者在I/O地址空间保留了8个字节用于这个操作:0xcf8 - 0xcff。这8个字节分成两个32位的寄存器,0xcf8是地址寄存器,0xcfc是数据寄存器。cpu先往地址寄存器中写入目标地址,然后通过数据寄存器读写数据,写入地址寄存器中的目标地址是一种包括总线号、设备号、功能号以及配置寄存器地址的综合地址。具体如下:
bit31 1
bit30-bit24 保留
bit23-bit16 总线号
bit15-bit11 设备号
bit10-bit8 功能号
bit7-bit0 寄存器地址,最低两位为0
设备的这些信息都可以通过lspci命令获取。
3. ioport读写权限
由于要通过操作ioport,所以先要获取ioport的读写权限。
能获取读写权限的函数有两个ioperm()和iopl()。
由于pci的口地址比较高,要用iopl(3)获取权限。
4. 读程序
#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. 写程序
来源:(http://blog.sina.com.cn/s/blog_559f6ffc0100i95s.html) - 应用程序中pci配置空间读写_云月_新浪博客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
#include
#include
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;
}