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,
®s.eax, ®s.ebx, ®s.ecx, ®s.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.