MTRR是什么

MTRR的全称是Memory Type Range Registers。

它们是一组组的MSR寄存器对(目前最多有96组),用来指定特定的系统内存段的类型。

这里说的类型,是指内存的CACHE类型,有如下的值:

对应的C代码中的实现:

//
// Memory cache types
//
typedef enum {
  CacheUncacheable    = 0,
  CacheWriteCombining = 1,
  CacheWriteThrough   = 4,
  CacheWriteProtected = 5,
  CacheWriteBack      = 6,
  CacheInvalid        = 7
} MTRR_MEMORY_CACHE_TYPE;

通过MTRR,系统可以优化RAM/ROM/MMIO等不同类型的内存的访问速度。

下面是一个设置MTRR的例子:

如何判断MTRR的支持情况

因为MTRR是MSR寄存器,因此它是跟CPU有关的,并不是所有的CPU都支持。

不过现在的x86平台CPU都支持该功能,事实上从P6型号开始就只是MTRR了。

要判断是否支持MTRR,需要使用CPUID命令,对应的是CPUID.EAX=01H,返回的结果在EDX:

当确定是否支持之后,还需要确定MTRR的支持情况,这对应于一个MSR寄存器(IA32_MTRRCAP):

它是一个只读的寄存器,具体的BIT位说明如下:

SMRR:确定是否支持System-Management Range Register;

FIX:确定是否支持Fixed Range MTRR(两种类型的MTRR,可变和固定);

WC:Write Combining的CACHE类型是否支持;

VCNT:可变MTRR的支持个数。

这里没有说固定MTRR的个数,是否意味着它的个数是固定的?(目前来看是固定的11个,后面再介绍)

对应判断MTRR是否支持的代码如下:

/**
  Checks if MTRR is supported.

  @retval TRUE  MTRR is supported.
  @retval FALSE MTRR is not supported.

**/
BOOLEAN
EFIAPI
IsMtrrSupported (
  VOID
  )
{
  CPUID_VERSION_INFO_EDX    Edx;
  MSR_IA32_MTRRCAP_REGISTER MtrrCap;

  //
  // Check CPUID(1).EDX[12] for MTRR capability
  //
  AsmCpuid (CPUID_VERSION_INFO, NULL, NULL, NULL, &Edx.Uint32);
  if (Edx.Bits.MTRR == 0) {
    return FALSE;
  }

  //
  // Check number of variable MTRRs and fixed MTRRs existence.
  // If number of variable MTRRs is zero, or fixed MTRRs do not
  // exist, return false.
  //
  MtrrCap.Uint64 = AsmReadMsr64 (MSR_IA32_MTRRCAP);
  if ((MtrrCap.Bits.VCNT == 0) || (MtrrCap.Bits.FIX == 0)) {
    return FALSE;
  }
  return TRUE;
}

MTRR的设置

硬件复位之后,CPU会Disable掉所有的MTRR,此时所有的系统内存都是UNCACHEABLE的。

需要BIOS来完成MTRR,且在多处理器的系统中,各个处理器的设置必须是一致的。

MTRR的设置对应到3类寄存器,首先是一个全局的MSR(IA32_MTRR_DEF_TYPE):

E:MTRR的开关;

FE:固定MTRR的开关;

Type:系统内存的默认CACHE类型,对于没有被MTRR覆盖到的内存段,就使用默认的CACHE类型;

第二类是固定MTRR,因为它们对应的内存段是固定的,所以也比较好理解,下面是所有的固定MTRR:

每一个寄存器又将内存段分为8个小段,可以分别设置。

第三类是可变MTRR,它通过一组MSR寄存器来设置一段内存的属性,这组MSR如下所示:

第一个寄存器设定了内存的基址和类型,而第二个寄存器设置了它的大小以及使能与否。

这里还有一个概念,就是MAXPHYADDR,它表示的是最大支持的系统内存。

虽然我们说现在x86是64位的系统,但是CPU真正只是的系统内存却不是2^64,而是2^MAXPHYADDR,而这个MAXPHYADDR的大小不同系统有不同的值。关于这个值,可以通过CPUID.80000008H获取,下面是具体的代码:

/**
  Initializes the valid bits mask and valid address mask for MTRRs.

  This function initializes the valid bits mask and valid address mask for MTRRs.

  @param[out]  MtrrValidBitsMask     The mask for the valid bit of the MTRR
  @param[out]  MtrrValidAddressMask  The valid address mask for the MTRR

**/
VOID
MtrrLibInitializeMtrrMask (
  OUT UINT64 *MtrrValidBitsMask,
  OUT UINT64 *MtrrValidAddressMask
  )
{
  UINT32                          MaxExtendedFunction;
  CPUID_VIR_PHY_ADDRESS_SIZE_EAX  VirPhyAddressSize;


  AsmCpuid (CPUID_EXTENDED_FUNCTION, &MaxExtendedFunction, NULL, NULL, NULL);

  if (MaxExtendedFunction >= CPUID_VIR_PHY_ADDRESS_SIZE) {
    AsmCpuid (CPUID_VIR_PHY_ADDRESS_SIZE, &VirPhyAddressSize.Uint32, NULL, NULL, NULL);
  } else {
    VirPhyAddressSize.Bits.PhysicalAddressBits = 36;
  }

  *MtrrValidBitsMask = LShiftU64 (1, VirPhyAddressSize.Bits.PhysicalAddressBits) - 1;
  *MtrrValidAddressMask = *MtrrValidBitsMask & 0xfffffffffffff000ULL;
}

当MAXPHYADDR确定之后,基址的设置没有什么特别好介绍的,不过大小需要说明下。

一个可变MTRR可以存放的内存大小通过如下的方式计算:

Address_Within_Range AND PhysMask = PhysBase AND PhysMask

其中Address_Within_Range就是可用的内存地址。

对应到代码如下:

Mask = ((~(Length - 1)) & MtrrValidAddressMask) | BIT11;

其中的MtrrValidAddressMask是可用的地址的掩码(即MAXPHYADDR个1,前面的MtrrLibInitializeMtrrMask()有计算方式)。

MTRR查看

Linux下查看MTRR的方式:

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐