Skip to content

Commit

Permalink
KVM: TDX: initialize VM with TDX specific parameters
Browse files Browse the repository at this point in the history
TDX requires additional parameters for VM for guest protection.

Add new subcommand, KVM_TDX_INIT_VM, to pass parameters for TDX guest.  Do
sanitary check of parameters, determine configurable values (EPT page
level, 4 or 5.  GPA shared bit, 47 bit or 51 bit, etc.), and initialize
control structures corresponding to TDX VM for the TDX module.

Signed-off-by: Isaku Yamahata <isaku.yamahata@intel.com>
  • Loading branch information
yamahata committed Dec 15, 2021
1 parent 90b6cc6 commit aa43996
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 0 deletions.
2 changes: 2 additions & 0 deletions arch/x86/include/asm/kvm_host.h
Expand Up @@ -1226,6 +1226,8 @@ struct kvm_arch {
hpa_t hv_root_tdp;
spinlock_t hv_root_tdp_lock;
#endif

gfn_t gfn_shared_mask;
};

struct kvm_vm_stat {
Expand Down
12 changes: 12 additions & 0 deletions arch/x86/include/uapi/asm/kvm.h
Expand Up @@ -514,6 +514,7 @@ struct kvm_pmu_event_filter {
/* Trust Domain eXtension sub-ioctl() commands. */
enum kvm_tdx_cmd_id {
KVM_TDX_CAPABILITIES = 0,
KVM_TDX_INIT_VM,

KVM_TDX_CMD_NR_MAX,
};
Expand Down Expand Up @@ -544,4 +545,15 @@ struct kvm_tdx_capabilities {
struct kvm_tdx_cpuid_config cpuid_configs[0];
};

struct kvm_tdx_init_vm {
__u32 max_vcpus;
__u32 tsc_khz;
__u64 attributes;
__u64 cpuid;
__u64 mrconfigid[6]; /* sha384 digest */
__u64 mrowner[6]; /* sha384 digest */
__u64 mrownerconfig[6]; /* sha348 digest */
__u64 reserved[43]; /* must be zero for future extensibility */
};

#endif /* _ASM_X86_KVM_H */
9 changes: 9 additions & 0 deletions arch/x86/kvm/vmx/main.c
Expand Up @@ -85,6 +85,14 @@ static int vt_mem_enc_op_dev(void __user *argp)
return tdx_dev_ioctl(argp);
}

static int vt_mem_enc_op(struct kvm *kvm, void __user *argp)
{
if (!is_td(kvm))
return -ENOTTY;

return tdx_vm_ioctl(kvm, argp);
}

struct kvm_x86_ops vt_x86_ops __initdata = {
.name = "kvm_intel",

Expand Down Expand Up @@ -226,6 +234,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
.vcpu_deliver_sipi_vector = kvm_vcpu_deliver_sipi_vector,

.mem_enc_op_dev = vt_mem_enc_op_dev,
.mem_enc_op = vt_mem_enc_op,
};

static struct kvm_x86_init_ops vt_init_ops __initdata = {
Expand Down
266 changes: 266 additions & 0 deletions arch/x86/kvm/vmx/tdx.c
Expand Up @@ -268,6 +268,9 @@ int tdx_vm_init(struct kvm *kvm)
int ret, i;
u64 err;

/* vCPUs can't be created until after KVM_TDX_INIT_VM. */
kvm->max_vcpus = 0;

kvm_tdx->hkid = tdx_keyid_alloc();
if (kvm_tdx->hkid < 0)
return -EBUSY;
Expand Down Expand Up @@ -393,6 +396,269 @@ int tdx_dev_ioctl(void __user *argp)
return 0;
}

/*
* TDX-SEAM definitions for fixed{0,1} are inverted relative to VMX. The TDX
* definitions are sane, the VMX definitions are backwards.
*
* if fixed0[i] == 0: val[i] must be 0
* if fixed1[i] == 1: val[i] must be 1
*/
static inline bool tdx_fixed_bits_valid(u64 val, u64 fixed0, u64 fixed1)
{
return ((val & fixed0) | fixed1) == val;
}

static struct kvm_cpuid_entry2 *tdx_find_cpuid_entry(struct kvm_tdx *kvm_tdx,
u32 function, u32 index)
{
struct kvm_cpuid_entry2 *e;
int i;

for (i = 0; i < kvm_tdx->cpuid_nent; i++) {
e = &kvm_tdx->cpuid_entries[i];

if (e->function == function && (e->index == index ||
!(e->flags & KVM_CPUID_FLAG_SIGNIFCANT_INDEX)))
return e;
}
return NULL;
}

static int setup_tdparams(struct kvm *kvm, struct td_params *td_params,
struct kvm_tdx_init_vm *init_vm)
{
struct kvm_tdx *kvm_tdx = to_kvm_tdx(kvm);
struct tdx_cpuid_config *config;
struct kvm_cpuid_entry2 *entry;
struct tdx_cpuid_value *value;
u64 guest_supported_xcr0;
u64 guest_supported_xss;
u32 guest_tsc_khz;
int max_pa;
int i;

/* init_vm->reserved must be zero */
if (find_first_bit((unsigned long *)init_vm->reserved,
sizeof(init_vm->reserved) * 8) !=
sizeof(init_vm->reserved) * 8)
return -EINVAL;

td_params->attributes = init_vm->attributes;
td_params->max_vcpus = init_vm->max_vcpus;

/* TODO: Enforce consistent CPUID features for all vCPUs. */
for (i = 0; i < tdx_caps.nr_cpuid_configs; i++) {
config = &tdx_caps.cpuid_configs[i];

entry = tdx_find_cpuid_entry(kvm_tdx, config->leaf,
config->sub_leaf);
if (!entry)
continue;

/*
* Non-configurable bits must be '0', even if they are fixed to
* '1' by TDX-SEAM, i.e. mask off non-configurable bits.
*/
value = &td_params->cpuid_values[i];
value->eax = entry->eax & config->eax;
value->ebx = entry->ebx & config->ebx;
value->ecx = entry->ecx & config->ecx;
value->edx = entry->edx & config->edx;
}

entry = tdx_find_cpuid_entry(kvm_tdx, 0xd, 0);
if (entry)
guest_supported_xcr0 = (entry->eax | ((u64)entry->edx << 32));
else
guest_supported_xcr0 = 0;
guest_supported_xcr0 &= supported_xcr0;

entry = tdx_find_cpuid_entry(kvm_tdx, 0xd, 1);
if (entry)
guest_supported_xss = (entry->ecx | ((u64)entry->edx << 32));
else
guest_supported_xss = 0;
/* PT can be exposed to TD guest regardless of KVM's XSS support */
guest_supported_xss &= (supported_xss | XFEATURE_MASK_PT);

max_pa = 36;
entry = tdx_find_cpuid_entry(kvm_tdx, 0x80000008, 0);
if (entry)
max_pa = entry->eax & 0xff;

td_params->eptp_controls = VMX_EPTP_MT_WB;

if (cpu_has_vmx_ept_5levels() && max_pa > 48) {
td_params->eptp_controls |= VMX_EPTP_PWL_5;
td_params->exec_controls |= TDX_EXEC_CONTROL_MAX_GPAW;
} else {
td_params->eptp_controls |= VMX_EPTP_PWL_4;
}

if (!tdx_fixed_bits_valid(td_params->attributes,
tdx_caps.attrs_fixed0,
tdx_caps.attrs_fixed1))
return -EINVAL;

if (td_params->attributes & TDX_TD_ATTRIBUTE_PERFMON) {
pr_warn("TD doesn't support perfmon. KVM needs to save/restore "
"host perf registers properly.\n");
return -EOPNOTSUPP;
}

/* Setup td_params.xfam */
td_params->xfam = guest_supported_xcr0 | guest_supported_xss;
if (!tdx_fixed_bits_valid(td_params->xfam,
tdx_caps.xfam_fixed0,
tdx_caps.xfam_fixed1))
return -EINVAL;

if (td_params->xfam & TDX_TD_XFAM_LBR) {
pr_warn("TD doesn't support LBR. KVM needs to save/restore "
"IA32_LBR_DEPTH properly.\n");
return -EOPNOTSUPP;
}

if (td_params->xfam & TDX_TD_XFAM_AMX) {
pr_warn("TD doesn't support AMX. KVM needs to save/restore "
"IA32_XFD, IA32_XFD_ERR properly.\n");
return -EOPNOTSUPP;
}

if (init_vm->tsc_khz)
guest_tsc_khz = init_vm->tsc_khz;
else
guest_tsc_khz = kvm->arch.initial_tsc_khz;

if (guest_tsc_khz < TDX_MIN_TSC_FREQUENCY_KHZ ||
guest_tsc_khz > TDX_MAX_TSC_FREQUENCY_KHZ) {
pr_warn_ratelimited("Illegal TD TSC %d Khz, it must be between [%d, %d] Khz\n",
guest_tsc_khz, TDX_MIN_TSC_FREQUENCY_KHZ, TDX_MAX_TSC_FREQUENCY_KHZ);
return -EINVAL;
}

td_params->tsc_frequency = TDX_TSC_KHZ_TO_25MHZ(guest_tsc_khz);
if (TDX_TSC_25MHZ_TO_KHZ(td_params->tsc_frequency) != guest_tsc_khz) {
pr_warn_ratelimited("TD TSC %d Khz not a multiple of 25Mhz\n", guest_tsc_khz);
if (init_vm->tsc_khz)
return -EINVAL;
}

BUILD_BUG_ON(sizeof(td_params->mrconfigid) !=
sizeof(init_vm->mrconfigid));
memcpy(td_params->mrconfigid, init_vm->mrconfigid,
sizeof(td_params->mrconfigid));
BUILD_BUG_ON(sizeof(td_params->mrowner) !=
sizeof(init_vm->mrowner));
memcpy(td_params->mrowner, init_vm->mrowner, sizeof(td_params->mrowner));
BUILD_BUG_ON(sizeof(td_params->mrownerconfig) !=
sizeof(init_vm->mrownerconfig));
memcpy(td_params->mrownerconfig, init_vm->mrownerconfig,
sizeof(td_params->mrownerconfig));

return 0;
}

static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd)
{
struct kvm_tdx *kvm_tdx = to_kvm_tdx(kvm);
struct kvm_cpuid2 __user *user_cpuid;
struct kvm_tdx_init_vm init_vm;
struct td_params *td_params;
struct tdx_ex_ret ex_ret;
struct kvm_cpuid2 cpuid;
int ret;
u64 err;

BUILD_BUG_ON(sizeof(init_vm) != 512);

if (is_td_initialized(kvm))
return -EINVAL;

if (cmd->metadata)
return -EINVAL;

if (copy_from_user(&init_vm, (void __user *)cmd->data, sizeof(init_vm)))
return -EFAULT;

if (init_vm.max_vcpus > KVM_MAX_VCPUS)
return -EINVAL;

user_cpuid = (void *)init_vm.cpuid;
if (copy_from_user(&cpuid, user_cpuid, sizeof(cpuid)))
return -EFAULT;

if (cpuid.nent > KVM_MAX_CPUID_ENTRIES)
return -E2BIG;

if (copy_from_user(&kvm_tdx->cpuid_entries, user_cpuid->entries,
cpuid.nent * sizeof(struct kvm_cpuid_entry2)))
return -EFAULT;

BUILD_BUG_ON(sizeof(struct td_params) != 1024);

td_params = kzalloc(sizeof(struct td_params), GFP_KERNEL_ACCOUNT);
if (!td_params)
return -ENOMEM;

kvm_tdx->cpuid_nent = cpuid.nent;

ret = setup_tdparams(kvm, td_params, &init_vm);
if (ret)
goto free_tdparams;

err = tdh_mng_init(kvm_tdx->tdr.pa, __pa(td_params), &ex_ret);
if (WARN_ON_ONCE(err)) {
pr_tdx_error(TDH_MNG_INIT, err, &ex_ret);
ret = -EIO;
goto free_tdparams;
}

kvm_tdx->tsc_offset = td_tdcs_exec_read64(kvm_tdx, TD_TDCS_EXEC_TSC_OFFSET);
kvm_tdx->attributes = td_params->attributes;
kvm_tdx->xfam = td_params->xfam;
kvm->max_vcpus = td_params->max_vcpus;
kvm->arch.initial_tsc_khz = TDX_TSC_25MHZ_TO_KHZ(td_params->tsc_frequency);

if (td_params->exec_controls & TDX_EXEC_CONTROL_MAX_GPAW)
kvm->arch.gfn_shared_mask = BIT_ULL(51) >> PAGE_SHIFT;
else
kvm->arch.gfn_shared_mask = BIT_ULL(47) >> PAGE_SHIFT;

free_tdparams:
kfree(td_params);
if (ret)
kvm_tdx->cpuid_nent = 0;
return ret;
}

int tdx_vm_ioctl(struct kvm *kvm, void __user *argp)
{
struct kvm_tdx_cmd tdx_cmd;
int r;

if (copy_from_user(&tdx_cmd, argp, sizeof(struct kvm_tdx_cmd)))
return -EFAULT;

mutex_lock(&kvm->lock);

switch (tdx_cmd.id) {
case KVM_TDX_INIT_VM:
r = tdx_td_init(kvm, &tdx_cmd);
break;
default:
r = -EINVAL;
goto out;
}

if (copy_to_user(argp, &tdx_cmd, sizeof(struct kvm_tdx_cmd)))
r = -EFAULT;

out:
mutex_unlock(&kvm->lock);
return r;
}

int __init tdx_hardware_setup(struct kvm_x86_ops *x86_ops)
{
int i, max_pkgs;
Expand Down
25 changes: 25 additions & 0 deletions arch/x86/kvm/vmx/tdx.h
Expand Up @@ -23,7 +23,14 @@ struct kvm_tdx {
struct tdx_td_page tdr;
struct tdx_td_page *tdcs;

u64 attributes;
u64 xfam;
int hkid;

int cpuid_nent;
struct kvm_cpuid_entry2 cpuid_entries[KVM_MAX_CPUID_ENTRIES];

u64 tsc_offset;
};

struct vcpu_tdx {
Expand Down Expand Up @@ -76,6 +83,11 @@ static inline struct vcpu_tdx *to_tdx(struct kvm_vcpu *vcpu)
return container_of(vcpu, struct vcpu_tdx, vcpu);
}

static inline bool is_td_initialized(struct kvm *kvm)
{
return !!kvm->max_vcpus;
}

static __always_inline void tdvps_vmcs_check(u32 field, u8 bits)
{
BUILD_BUG_ON_MSG(__builtin_constant_p(field) && (field) & 0x1,
Expand Down Expand Up @@ -161,6 +173,19 @@ TDX_BUILD_TDVPS_ACCESSORS(64, VMCS, vmcs);
TDX_BUILD_TDVPS_ACCESSORS(64, STATE_NON_ARCH, state_non_arch);
TDX_BUILD_TDVPS_ACCESSORS(8, MANAGEMENT, management);

static __always_inline u64 td_tdcs_exec_read64(struct kvm_tdx *kvm_tdx, u32 field)
{
struct tdx_ex_ret ex_ret;
u64 err;

err = tdh_mng_rd(kvm_tdx->tdr.pa, TDCS_EXEC(field), &ex_ret);
if (unlikely(err)) {
pr_err("TDH_MNG_RD[EXEC.0x%x] failed: 0x%llx\n", field, err);
return 0;
}
return ex_ret.r8;
}

#else
struct kvm_tdx;
struct vcpu_tdx;
Expand Down
1 change: 1 addition & 0 deletions arch/x86/kvm/vmx/tdx_stubs.c
Expand Up @@ -8,3 +8,4 @@ void tdx_hardware_enable(void) {}
void tdx_hardware_disable(void) {}

int tdx_dev_ioctl(void __user *argp) { return -EOPNOTSUPP; }
int tdx_vm_ioctl(struct kvm *kvm, void __user *argp) { return -EOPNOTSUPP; }
1 change: 1 addition & 0 deletions arch/x86/kvm/vmx/x86_ops.h
Expand Up @@ -134,5 +134,6 @@ void tdx_vm_teardown(struct kvm *kvm);
void tdx_vm_free(struct kvm *kvm);

int tdx_dev_ioctl(void __user *argp);
int tdx_vm_ioctl(struct kvm *kvm, void __user *argp);

#endif /* __KVM_X86_VMX_X86_OPS_H */

0 comments on commit aa43996

Please sign in to comment.