分享

x86 CPU地址空间分配和寄存器访问

 waston 2022-09-16 发布于上海

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;
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多