Intel MKTME 在Linux Kernel中的初始化和Key编辑API

Intel MKTME 在Linux Kernel中的初始化和Key编辑API

PCONFIG指令枚举 & MKTME Target枚举

#define X86_FEATURE_PCONFIG   (18*32+18) /* Intel PCONFIG */

检查cpu是否包含某feature,用的是boot_cpu_has([feature_encode])

#define boot_cpu_has(bit)  cpu_has(&boot_cpu_data, bit)
#define cpu_has(c, bit)             \
	(__builtin_constant_p(bit) && REQUIRED_MASK_BIT_SET(bit) ? 1 :  \
	test_cpu_cap(c, bit))

__builtin_constant_p 是编译器gcc内置函数,用于判断一个值是否为编译时常量,如果是常数,函数返回1 ,否则返回0。由于编译时,cpu_has传入的bit一定是常量,类似 X86_FEATURE_PCONFIG代表的18*32+18,因此这里的__builtin_constant_p返回true。

#define REQUIRED_MASK_BIT_SET(feature_bit)    \
   ( CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK,  0, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 1, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 2, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 3, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 4, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 5, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 6, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 7, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 8, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 9, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 10, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 11, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 12, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 13, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 14, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 15, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 16, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 17, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 18, feature_bit) ||   \
    CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 19, feature_bit) ||   \
    REQUIRED_MASK_CHECK           ||   \
    BUILD_BUG_ON_ZERO(NCAPINTS != 20))
    
#define CHECK_BIT_IN_MASK_WORD(maskname, word, bit) \
  (((bit)>>5)==(word) && (1UL<<((bit)&31) & maskname##word ))

所以针对CHECK_BIT_IN_MASK_WORD(REQUIRED_MASK, 0, feature_bit),就可以转化为:

(((feature_bit)>>5)==(word) && (1UL<<((feature_bit)&31) & maskname##word ))

Feature_bit即上面的类似于X86_FEATURE_PCONFIG的数,例如1832+18,其中1832,是为了保证这个feature_bit的高于5个bit的位置为word数组中的index,即18 << 5,低5bit为这个feature在这个word中的bit_index,所以上面的这一堆CHECK_BIT_IN_MASK_WORD中,只会有一个宏的(feature_bit)>>5)==(word)为真,(feature_bit & 31)获得该feature bit的低5位,假设低5位为5'b 10010,即十进制18,那么1<<18,正好能与预设的REQUIRED_MASK18 做与运算,获取在该mask中,bit18是否设置为1的结果。

综上, REQUIRED_MASK_BIT_SET(X86_FEATURE_PCONFIG),就是在确认:

在REQUIRED_MASK18中,bit18是否为1,而在arch/x86/include/asm中,REQUIRED_MASK被设置为0,所以REQUIRED_MASK_BIT_SET(X86_FEATURE_PCONFIG)返回0.

kernel引入REQUIRED_MASK的目的,是为了设置优先级,即对于某些基础feature,如FPU,PSE,MSR,PAE等,会首先查询REQUIRED MASK,这样设计,即,REQUIRED_MASKn的优先级高于cpu->capabilities数组。kernel的注释中说明,我们必须构造一个最小feature集,以支持非常早期(early not old)的kernel功能。

回到我们的feature-X86_FEATURE_PCONFIG,这个feature会导致 REQUIRED_MASK_BIT_SET返回0,所以需要查看test_cpu_cap(c,bit).即test_cpu_cap(&boot_cpu_data,X86_FEATURE_PCONFIG).

#define test_cpu_cap(c, bit)             \
   test_bit(bit, (unsigned long *)((c)->x86_capability))

即,返回boot_cpu_data->x86_capability中,该bit是否为1。那么boot_cpu_data->x86_capability是何时赋值的。

#define NCAPINTS       20   /* N 32-bit words worth of info */

#define NBUGINTS       1    /* N 32-bit bug flags */



struct cpuinfo_x86 {
   ...
 union {
   __u32    x86_capability[NCAPINTS + NBUGINTS];
   unsigned long  x86_capability_alignment;
 };
   ...
}

首先看到x86_capability[21]是一个基础元素为32bit unsigned int类型,包含21个元素的数组。

enum cpuid_leafs {
  CPUID_1_EDX   = 0,
  CPUID_8000_0001_EDX,
  CPUID_8086_0001_EDX,
  CPUID_LNX_1,
  CPUID_1_ECX,
  CPUID_C000_0001_EDX,
  CPUID_8000_0001_ECX,
  CPUID_LNX_2,
  CPUID_LNX_3,
  CPUID_7_0_EBX,
  CPUID_D_1_EAX,
  CPUID_LNX_4,
  CPUID_7_1_EAX,
  CPUID_8000_0008_EBX,
  CPUID_6_EAX,
  CPUID_8000_000A_EDX,
  CPUID_7_ECX,
  CPUID_8000_0007_EBX,
  CPUID_7_EDX,
  CPUID_8000_001F_EAX,
};

kernel定义了一个枚举结构,其中一共19个元素,分别代表CPUID的不同leaf。

PCONFIG的枚举是在CPUID[EAX=7,ECX=0].EDX[bit18]枚举的,因此查询CPU是否支持PCONFIG指令,需要查询x86_capability[CPUID_7_EDX],即x86_capability[18]的bit18是否为1.

在get_cpu_cap()中,会根据cpuid的取值,对c->x86_capability[CPUID_7_EDX]进行赋值:

// arch/x86/kernel/cpu/common.c
void get_cpu_cap(struct cpuinfo_x86 *c)
{
    u32 eax, ebx, ecx, edx;
	...
        
  /* Additional Intel-defined flags: level 0x00000007 */
  if (c->cpuid_level >= 0x00000007) {
    cpuid_count(0x00000007, 0, &eax, &ebx, &ecx, &edx);
    c->x86_capability[CPUID_7_0_EBX] = ebx;
    c->x86_capability[CPUID_7_ECX] = ecx;
    c->x86_capability[CPUID_7_EDX] = edx;

  /* Check valid sub-leaf index before accessing it */
    if (eax >= 1) {
      cpuid_count(0x00000007, 1, &eax, &ebx, &ecx, &edx);
      c->x86_capability[CPUID_7_1_EAX] = eax;
    }
  }
    
	...
}

在intel_pconfig_init()时,会检查是否支持PCONFIG指令,如果不支持,直接返回0.

// arch/x86/kernel/cpu/intel_pconfig
static int __init intel_pconfig_init(void)
{
  if (!boot_cpu_has(X86_FEATURE_PCONFIG))
      return 0;
  ...
}

如果支持PCONFIG,那么就需要对PCONFIG可以操作的target进行枚举。

static int __init intel_pconfig_init(void)
{
  int subleaf;
  if (!boot_cpu_has(X86_FEATURE_PCONFIG))
      return 0;

  /*

   * Scan subleafs of PCONFIG CPUID leaf.

   * Subleafs of the same type need not to be consecutive.

   * Stop on the first invalid subleaf type. All subleafs after the first

   * invalid are invalid too.

   */

  for (subleaf = 0; subleaf < INT_MAX; subleaf++) {
    struct cpuid_regs regs;
    cpuid_count(PCONFIG_CPUID, subleaf,
        &regs.eax, &regs.ebx, &regs.ecx, &regs.edx);
    switch (regs.eax & PCONFIG_CPUID_SUBLEAF_TYPE_MASK) {
    case PCONFIG_CPUID_SUBLEAF_INVALID:
      /* Stop on the first invalid subleaf */
      goto out;
            
    case PCONFIG_CPUID_SUBLEAF_TYPE_TARGETID:
      /* Mark supported PCONFIG targets */
      if (regs.ebx < 64)
        targets_supported |= (1ULL << regs.ebx);
      if (regs.ecx < 64)
        targets_supported |= (1ULL << regs.ecx);
      if (regs.edx < 64)
        targets_supported |= (1ULL << regs.edx);
      break;

    default:
      /* Unknown CPUID.PCONFIG subleaf: ignore */
      break;
    }
  }
    
out:
  return 0;
}

CPUID[EAX=1BH,ECX=n].EAX会返回subleaf-type

0表示无效subleaf-type,并且EBX=ECX=EDX=0.

非0表示有效subleaf-type,该subleaf-type的其它相关信息会记录在EBX,ECX,EDX中。

现在只支持subleaf-type为1的subleaf,即TARGETID-subleaf-type, EBX,ECX,EDX中记录的是TARGETID数值。

即如果CPUID[EAX=1BH,ECX=n].EAX返回1,那么EBX ECX EDX中记录TARGETID数值。

任何TARGETID不为0均为有效TARGETID,目前只支持TARGETID=1,即MKMET TARGET。

而PCONFIG Leaf,也就是运行PCONFIG时,EAX输入的值与这个TARGET没有关系。

所以,针对上述代码,subleaf=0,CPUID[EAX=1BH,ECX=0].EAX会返回subleaf-type=1,即TARGETID-subleaf-type, EBX,ECX,EDX中记录的是TARGETID数值。而EBX,ECX,EDX中均会返回1,表示支持TARGETID=1.

另外spec明确规定要使用EAX=0来用PCONFIG编辑MKTME,因此之后编辑MKTME时,需要使用PCONFIG EAX=0.

至此,PCONFIG的枚举代码梳理完成。

MKTME key的编辑API

/* Hardware requires the structure to be 256 byte aligned. Otherwise #GP(0). */
struct mktme_key_program {
    u16 keyid;
    u32 keyid_ctrl;
    u8 __rsvd[58];
    u8 key_field_1[64];
    u8 key_field_2[64];
} __packed __aligned(256);


static inline int mktme_key_program(struct mktme_key_program *key_program)
{
    unsigned long rax = MKTME_KEY_PROGRAM;
    if (!pconfig_target_supported(MKTME_TARGET))
        return -ENXIO;
    asm volatile(PCONFIG
        : "=a" (rax), "=b" (key_program)
        : "0" (rax), "1" (key_program)
        : "memory", "cc");
    return rax;
}

kernel只是简单提供了一个向key table写入key entry的API: mktme_key_program.

posted @ 2022-06-03 22:34  EwanHai  阅读(318)  评论(0编辑  收藏  举报