1、基本概念
cpu地址空间和pci地址空间是两个常用的比较容易混淆的概念,特别是其中不同系列的cpu的实现还各不相同:x86系列cpu地址空间和pci地址空间是重合的,即为同一空间;而非x86 cpu的cpu地址空间和pci地址空间为两个独立的空间。
也许是因为pci总线是intel发明的,所以x86内部总线和pci总线实现是一致的,但非x86系列的cpu实现pci总线需要用到总线转换。
1.1、非x86系列的CPU空间和PCI空间互访(以PowerPC为例):
因为PCI地址空间和CPU地址空间是独立的,所以PCI设备和CPU之间的互访需要用到地址转换。涉及到两个概念,inbound和outbound。
Inbound窗口:即将CPU的一段地址空间映射到PCI地址空间上,供PCI设备访问CPU空间所用,即inbound窗口。因为对cpu来说pci访问就是inbound访问。
Outbound窗口:即将PCI的一段地址空间映射到CPU地址空间上,供CPU访问PCI空间所用,即Outbound窗口。因为对cpu来说访问pci空间就是outbound访问。
1.2、x86系列的CPU空间和PCI空间互访:
对x86 cpu来说,就没有了cpu地址空间和pci地址空间之分,这两个空间是重合的。所以pci外设和cpu之间的相互访问就不需要设置地址转换窗口:
Inbound访问:pci设备访问cpu空间,这个不需要设置inbound窗口,对外设来说所有的地址空间都是可以访问的。
Outbound访问:cpu访问pci外设,这个也还需要保留一段cpu地址给外设,让外设的窗口能映射到这段空间来,即MMIO空间。有点类似于Outbound窗口,但是不需要地址转换。
1.3、x86系列的IO空间和CFG空间:
由于x86 cpu对pci标准的完美支持,所以x86 cpu还支持pci的io空间访问和pci的配置空间访问:
io空间:pci标准规定的io地址空间最大可以有4G,但是x86只实现了64k的大小。io空间用来实现早期兼容的外设寄存器的访问(IO端口),和用来映射pci外设的io空间。
配置空间:用来访问pci总线设备的配置空间,而x86也把内部的寄存器组织成虚拟的pci设备,使用访问pci配置寄存器的方式来访问内部寄存器。
访问配置空间的方法有两种:一是通过CF8/CFC io端口的间接访问来访问配置空间;二是通过mmcfg方式,把配置空间映射到memory空间来访问。对每个配置空间来说,CF8/CFC方式只能访问传统的pci配置空间256字节,而mmcfg访问方式,能访问pcie的整个配置空间4k。
2、x86 CPU地址空间分配
本节以系统的桥片为例,来说明x86系列cpu的地址空间分配。x86 其他系列cpu的地址空间分配类似。
x86 xeon系列cpu 在32位系统下面,通过PAE(Physical address Extension)机制可以访问到36位的地址,即最大64G的空间。
2.1、0-1M 兼容空间:
0-FFFFF | 0-640k常规内存(MS-DOS Area) | 这一段区域就是ram。 其中有功能划分的区域是:起始位置的1 KB被用做BIOS中断向量表,随后的1 KB被用做BIOS数据区 | |
A0000-BFFFF | 640 – 768 kB Video Buffer Area | 1、这一段区域是显卡的显示RAM区域,老式的VGA显示模式直接往这段显存写数据,就可以显示。现在估计只有bios阶段使用这种显示方式,系统起来后会开启更高级的显卡显示模式。 2、被显存地址覆盖的这一块128K大小的内存,可以被利用起来当做SMM内存。SMM是CPU一种等级最高的管理模式,所以它的内存在常规下不可以被访问。 | 1、PCI在支持VGA显示时,有个VGAEN功能,比较特殊,值得关注。VGA显卡设备不需要配置常规的pci bar寄存器地址,而只需要使能显卡所挂在PCI-PCI桥设备的配置寄存器0x3E bit 3(VGA Enable),显卡就会响应专为VGA保留的固定pci memory地址(A0000-BFFFF)和pci io地址(03c0-03df)。 2、什么是SMM模式? SMM是System Management Mode系统管理模式的缩写。从Intel 386SL开始,此后的x86架构微处理器中都开始支持这个模式。在这个模式中,所有正常执行的软件,包括操作系统都已经暂停运行。只有特别的单独软件,具备高特权模式的软件才能运行。通常这些软件都是一些固件程序或者是硬件辅助调试器。 x86 处理器的模式Mode模式 起始支持的处理器 Real mode Intel 8086 Protected mode Intel 80286 Virtual 8086 mode Intel 80386 Unreal mode Intel 80386 System Management Mode Intel 386SL Long mode AMD Opteron |
C0000-CFFFF | 768 - 832 kB VGA Video BIOS ROM IDE Hard Disk BIOS ROM Optional Adapter ROM BIOS or RAM UMBs | 1、这一段区域存放显卡的Option Rom还有其他设备的OptionRom(如硬盘、网卡..)。 | 这一段区域,是OptionRom和BIOS区域覆盖了原RAM区域。由于RAM的访问速度远远快于这些固件的访问速度,所以通常的做法是把固件中的内容拷贝到相同地址的RAM中,然后再使能RAM而屏蔽原有的固件映射。 访问BIOS和OptionRom内容和地址都没有改变,但是速度却加快了。这种做法就叫ROM Shadowing |
D0000-DFFFF | 832 - 896 kB Optional Adapter ROM BIOS or RAM UMBs | 这一段区域也是来存放设备的OptionRom。如果没有OptionRom覆盖,那就是常规内存 | |
E0000-EFFFF | 896 - 960 kB System BIOS Plug and Play Extended Information | 扩展BIOS区域。 | |
F0000-FFFFF | 960 kB–1 MB System BIOS ROM | 常规BIOS区域,映射到BIOS芯片。CPU的第一句指令0xFFFF0就跳到该区域 |
2.2、1M以上的memory地址空间:
1M-TOLM | 低于4G的常规内存 | 这一段的内存就是低于4G的可用的内存,其中有两段区域比较特殊。 1、15 MB - 16 MB Window (ISA Hole)传统的ISA黑洞,现在基本不支持。 2、Extended SMRAM Space (TSEG)扩展SMM内存。前面已经有VGA RAM覆盖的128k内存可做SMM内存使用,系统还允许分配更多的SMM内存。 | “Coherency Protocol”Intel 桥片通过同步协议保证所有对内存访问的一致性。 |
HECBASE-(HECBASE+256M) | MMCFG(Memory Mapped Configuration) 映射到memory空间的pci配置寄存器 | 256M的计算方法: 256bus x 32device x 8function x 4k bytes register = 256M bytes | mmcfg、mmio这两段区域覆盖的相同地址的常规内存比较大,怎么样能使用到这段被覆盖的内存? 芯片组和内存控制其有一种叫做Main Memory Reclaim AddressRange。通过这个技术可将重叠得部分的内存地址印射到4g以上的地址上去,这个功能是由硬件决定的。 |
(HECBASE+256M) - (4G-32M) | MMIO(Memory Mapped I/O) 可分配给外设使用的pci memory空间 | 为什么要在4G以下设置Low MMIO区域,造成内存被分割成两块,为什么不能把MMIO都放在4G以上?主要还是为了照顾pci 32/64bit的兼容,32bit的pci地址需要放在4G以下的空间。 | |
(4G-32M) - 4G | CPU-spec | 4G以下的32M区域是CPU的一些特殊区域,主要由以下几部分组成: 1、16M fireware地址; 2、一些直接访问的cpu寄存器; 3、Interrupt、I/O APIC区域; | |
4G - ram_size | 高于4G的常规内存 | Coherency Protocol”Intel 桥片通过同步协议保证所有对内存访问的一致性。 | |
ram_size - | high MMIO | 在高端内存之上一直到CPU支持的最大空间,都可以用来映射64bit的pci memory外设地址 |
2.3、x86 io地址空间:
x86只实现64k大小的io空间,其中低4k是兼容的io空间用做专门用途,4k以上的io地址空间可以分配给外部设备使用。
2.4、命令操作:
在linux下通过以下命令查看cpu的地址空间分配:
- cat /proc/iomem 查看cpu的pci memory空间分配
- cat /proc/ioports 查看cpu的pci io空间分配
3、x86内部寄存器的访问
除了一些是需要直接在memory空间访问的寄存器。x86把大部分内部的寄存器组织成虚拟的pci设备,使用访问pci配置寄存器的方式来访问内部寄存器。
访问配置空间的方法有两种:
- 一是通过CF8/CFC io端口的间接访问来访问配置空间;
- 二是通过mmcfg方式,把配置空间映射到memory空间来访问。对每个配置空间来说,CF8/CFC方式只能访问传统的pci配置空间256字节,而mmcfg访问方式,能访问pcie的整个配置空间4k。
linux内核态实现pci配置空间访问的函数有以下:
static inline int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val)
{
return pci_bus_read_config_byte (dev->bus, dev->devfn, where, val);
}
static inline int pci_read_config_word(struct pci_dev *dev, int where, u16 *val)
{
return pci_bus_read_config_word (dev->bus, dev->devfn, where, val);
}
static inline int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val)
{
return pci_bus_read_config_dword (dev->bus, dev->devfn, where, val);
}
static inline int pci_write_config_byte(struct pci_dev *dev, int where, u8 val)
{
return pci_bus_write_config_byte (dev->bus, dev->devfn, where, val);
}
static inline int pci_write_config_word(struct pci_dev *dev, int where, u16 val)
{
return pci_bus_write_config_word (dev->bus, dev->devfn, where, val);
}
static inline int pci_write_config_dword(struct pci_dev *dev, int where, u32 val)
{
return pci_bus_write_config_dword (dev->bus, dev->devfn, where, val);
}
这些函数的内部实现,会判断cpu是否进行了mmcfg映射。如果已经进行了mmcfg映射,则使用mmcfg模式访问;如果没有实现mmcfg,则使用CF8/CFC方式访问。
具体的实现过程如下:
- 1、这些函数的定义在driver/pci/access.c,可以看到其中的关键是bus->ops指针:
#define PCI_OP_READ(size,type,len) \
int pci_bus_read_config_##size \
(struct pci_bus *bus, unsigned int devfn, int pos, type *value) \
{ \
int res; \
unsigned long flags; \
u32 data = 0; \
if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \
spin_lock_irqsave(&pci_lock, flags); \
res = bus->ops->read(bus, devfn, pos, len, &data); \
*value = (type)data; \
spin_unlock_irqrestore(&pci_lock, flags); \
return res; \
}
#define PCI_OP_WRITE(size,type,len) \
int pci_bus_write_config_##size \
(struct pci_bus *bus, unsigned int devfn, int pos, type value) \
{ \
int res; \
unsigned long flags; \
if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \
spin_lock_irqsave(&pci_lock, flags); \
res = bus->ops->write(bus, devfn, pos, len, value); \
spin_unlock_irqrestore(&pci_lock, flags); \
return res; \
}
PCI_OP_READ(byte, u8, 1)
PCI_OP_READ(word, u16, 2)
PCI_OP_READ(dword, u32, 4)
PCI_OP_WRITE(byte, u8, 1)
PCI_OP_WRITE(word, u16, 2)
PCI_OP_WRITE(dword, u32, 4)
- 2、bus->ops的实现由arch/i386/pci/common.c中的pci_root_ops结构提供,pci_root_ops结构实际调用的是raw_pci_ops结构:
static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value)
{
return raw_pci_ops->read(0, bus->number, devfn, where, size, value);
}
static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value)
{
return raw_pci_ops->write(0, bus->number, devfn, where, size, value);
}
struct pci_ops pci_root_ops = {
.read = pci_read,
.write = pci_write,
};
- 3、raw_pci_ops的mmcfg模式实现在arch/i386/pci/mmconfig.c中定义:
static int pci_mmcfg_read(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 *value)
{
unsigned long flags;
u32 base;
if (!value || (bus > 255) || (devfn > 255) || (reg > 4095))
return -EINVAL;
base = get_base_addr(seg, bus, devfn);
if (!base)
return pci_conf1_read(seg,bus,devfn,reg,len,value);
spin_lock_irqsave(&pci_config_lock, flags);
pci_exp_set_dev_base(base, bus, devfn);
switch (len) {
case 1:
*value = readb(mmcfg_virt_addr + reg);
break;
case 2:
*value = readw(mmcfg_virt_addr + reg);
break;
case 4:
*value = readl(mmcfg_virt_addr + reg);
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return 0;
}
static int pci_mmcfg_write(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 value)
{
unsigned long flags;
u32 base;
if ((bus > 255) || (devfn > 255) || (reg > 4095))
return -EINVAL;
base = get_base_addr(seg, bus, devfn);
if (!base)
return pci_conf1_write(seg,bus,devfn,reg,len,value);
spin_lock_irqsave(&pci_config_lock, flags);
pci_exp_set_dev_base(base, bus, devfn);
switch (len) {
case 1:
writeb(value, mmcfg_virt_addr + reg);
break;
case 2:
writew(value, mmcfg_virt_addr + reg);
break;
case 4:
writel(value, mmcfg_virt_addr + reg);
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return 0;
}
- 4、raw_pci_ops的CF8/CFC方式访问模式实现在arch/i386/pci/mmconfig.c中定义:
int pci_conf1_read(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 *value)
{
unsigned long flags;
if (!value || (bus > 255) || (devfn > 255) || (reg > 255))
return -EINVAL;
spin_lock_irqsave(&pci_config_lock, flags);
outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);
switch (len) {
case 1:
*value = inb(0xCFC + (reg & 3));
break;
case 2:
*value = inw(0xCFC + (reg & 2));
break;
case 4:
*value = inl(0xCFC);
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return 0;
}
int pci_conf1_write(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 value)
{
unsigned long flags;
if ((bus > 255) || (devfn > 255) || (reg > 255))
return -EINVAL;
spin_lock_irqsave(&pci_config_lock, flags);
outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);
switch (len) {
case 1:
outb((u8)value, 0xCFC + (reg & 3));
break;
case 2:
outw((u16)value, 0xCFC + (reg & 2));
break;
case 4:
outl((u32)value, 0xCFC);
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return 0;
}