#include <asm/setup_data.h>
#ifndef __BOOT_COMPRESSED
#define error(v) pr_err(v)
#define has_cpuflag(f) boot_cpu_has(f)
#else
#undef WARN
#define WARN(condition, format...) (!!(condition))
#undef vc_forward_exception
#define vc_forward_exception(c) panic("SNP: Hypervisor requested exception\n")
#endif
struct svsm_ca *boot_svsm_caa __ro_after_init;
u64 boot_svsm_caa_pa __ro_after_init;
static u16 ghcb_version __ro_after_init;
static struct snp_cpuid_table cpuid_table_copy __ro_after_init;
static u32 cpuid_std_range_max __ro_after_init;
static u32 cpuid_hyp_range_max __ro_after_init;
static u32 cpuid_ext_range_max __ro_after_init;
bool __init sev_es_check_cpu_features(void)
{
if (!has_cpuflag(X86_FEATURE_RDRAND)) {
error("RDRAND instruction not supported - no trusted source of randomness available\n");
return false;
}
return true;
}
void __head __noreturn
sev_es_terminate(unsigned int set, unsigned int reason)
{
u64 val = GHCB_MSR_TERM_REQ;
val |= GHCB_SEV_TERM_REASON(set, reason);
sev_es_wr_ghcb_msr(val);
VMGEXIT();
while (true)
asm volatile("hlt\n" : : : "memory");
}
u64 get_hv_features(void)
{
u64 val;
if (ghcb_version < 2)
return 0;
sev_es_wr_ghcb_msr(GHCB_MSR_HV_FT_REQ);
VMGEXIT();
val = sev_es_rd_ghcb_msr();
if (GHCB_RESP_CODE(val) != GHCB_MSR_HV_FT_RESP)
return 0;
return GHCB_MSR_HV_FT_RESP_VAL(val);
}
void snp_register_ghcb_early(unsigned long paddr)
{
unsigned long pfn = paddr >> PAGE_SHIFT;
u64 val;
sev_es_wr_ghcb_msr(GHCB_MSR_REG_GPA_REQ_VAL(pfn));
VMGEXIT();
val = sev_es_rd_ghcb_msr();
if ((GHCB_RESP_CODE(val) != GHCB_MSR_REG_GPA_RESP) ||
(GHCB_MSR_REG_GPA_RESP_VAL(val) != pfn))
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_REGISTER);
}
bool sev_es_negotiate_protocol(void)
{
u64 val;
sev_es_wr_ghcb_msr(GHCB_MSR_SEV_INFO_REQ);
VMGEXIT();
val = sev_es_rd_ghcb_msr();
if (GHCB_MSR_INFO(val) != GHCB_MSR_SEV_INFO_RESP)
return false;
if (GHCB_MSR_PROTO_MAX(val) < GHCB_PROTOCOL_MIN ||
GHCB_MSR_PROTO_MIN(val) > GHCB_PROTOCOL_MAX)
return false;
ghcb_version = min_t(size_t, GHCB_MSR_PROTO_MAX(val), GHCB_PROTOCOL_MAX);
return true;
}
static enum es_result verify_exception_info(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
{
u32 ret;
ret = ghcb->save.sw_exit_info_1 & GENMASK_ULL(31, 0);
if (!ret)
return ES_OK;
if (ret == 1) {
u64 info = ghcb->save.sw_exit_info_2;
unsigned long v = info & SVM_EVTINJ_VEC_MASK;
if ((info & SVM_EVTINJ_VALID) &&
((v == X86_TRAP_GP) || (v == X86_TRAP_UD)) &&
((info & SVM_EVTINJ_TYPE_MASK) == SVM_EVTINJ_TYPE_EXEPT)) {
ctxt->fi.vector = v;
if (info & SVM_EVTINJ_VALID_ERR)
ctxt->fi.error_code = info >> 32;
return ES_EXCEPTION;
}
}
return ES_VMM_ERROR;
}
static inline int svsm_process_result_codes(struct svsm_call *call)
{
switch (call->rax_out) {
case SVSM_SUCCESS:
return 0;
case SVSM_ERR_INCOMPLETE:
case SVSM_ERR_BUSY:
return -EAGAIN;
default:
return -EINVAL;
}
}
static __always_inline void svsm_issue_call(struct svsm_call *call, u8 *pending)
{
register unsigned long rax asm("rax") = call->rax;
register unsigned long rcx asm("rcx") = call->rcx;
register unsigned long rdx asm("rdx") = call->rdx;
register unsigned long r8 asm("r8") = call->r8;
register unsigned long r9 asm("r9") = call->r9;
call->caa->call_pending = 1;
asm volatile("rep; vmmcall\n\t"
: "+r" (rax), "+r" (rcx), "+r" (rdx), "+r" (r8), "+r" (r9)
: : "memory");
*pending = xchg(&call->caa->call_pending, *pending);
call->rax_out = rax;
call->rcx_out = rcx;
call->rdx_out = rdx;
call->r8_out = r8;
call->r9_out = r9;
}
static int svsm_perform_msr_protocol(struct svsm_call *call)
{
u8 pending = 0;
u64 val, resp;
val = sev_es_rd_ghcb_msr();
sev_es_wr_ghcb_msr(GHCB_MSR_VMPL_REQ_LEVEL(0));
svsm_issue_call(call, &pending);
resp = sev_es_rd_ghcb_msr();
sev_es_wr_ghcb_msr(val);
if (pending)
return -EINVAL;
if (GHCB_RESP_CODE(resp) != GHCB_MSR_VMPL_RESP)
return -EINVAL;
if (GHCB_MSR_VMPL_RESP_VAL(resp))
return -EINVAL;
return svsm_process_result_codes(call);
}
static int svsm_perform_ghcb_protocol(struct ghcb *ghcb, struct svsm_call *call)
{
struct es_em_ctxt ctxt;
u8 pending = 0;
vc_ghcb_invalidate(ghcb);
ghcb->protocol_version = ghcb_version;
ghcb->ghcb_usage = GHCB_DEFAULT_USAGE;
ghcb_set_sw_exit_code(ghcb, SVM_VMGEXIT_SNP_RUN_VMPL);
ghcb_set_sw_exit_info_1(ghcb, 0);
ghcb_set_sw_exit_info_2(ghcb, 0);
sev_es_wr_ghcb_msr(__pa(ghcb));
svsm_issue_call(call, &pending);
if (pending)
return -EINVAL;
switch (verify_exception_info(ghcb, &ctxt)) {
case ES_OK:
break;
case ES_EXCEPTION:
vc_forward_exception(&ctxt);
fallthrough;
default:
return -EINVAL;
}
return svsm_process_result_codes(call);
}
enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb,
struct es_em_ctxt *ctxt,
u64 exit_code, u64 exit_info_1,
u64 exit_info_2)
{
ghcb->protocol_version = ghcb_version;
ghcb->ghcb_usage = GHCB_DEFAULT_USAGE;
ghcb_set_sw_exit_code(ghcb, exit_code);
ghcb_set_sw_exit_info_1(ghcb, exit_info_1);
ghcb_set_sw_exit_info_2(ghcb, exit_info_2);
sev_es_wr_ghcb_msr(__pa(ghcb));
VMGEXIT();
return verify_exception_info(ghcb, ctxt);
}
static int __sev_cpuid_hv(u32 fn, int reg_idx, u32 *reg)
{
u64 val;
sev_es_wr_ghcb_msr(GHCB_CPUID_REQ(fn, reg_idx));
VMGEXIT();
val = sev_es_rd_ghcb_msr();
if (GHCB_RESP_CODE(val) != GHCB_MSR_CPUID_RESP)
return -EIO;
*reg = (val >> 32);
return 0;
}
static int __sev_cpuid_hv_msr(struct cpuid_leaf *leaf)
{
int ret;
if (cpuid_function_is_indexed(leaf->fn) && leaf->subfn)
return -EINVAL;
ret = __sev_cpuid_hv(leaf->fn, GHCB_CPUID_REQ_EAX, &leaf->eax);
ret = ret ? : __sev_cpuid_hv(leaf->fn, GHCB_CPUID_REQ_EBX, &leaf->ebx);
ret = ret ? : __sev_cpuid_hv(leaf->fn, GHCB_CPUID_REQ_ECX, &leaf->ecx);
ret = ret ? : __sev_cpuid_hv(leaf->fn, GHCB_CPUID_REQ_EDX, &leaf->edx);
return ret;
}
static int __sev_cpuid_hv_ghcb(struct ghcb *ghcb, struct es_em_ctxt *ctxt, struct cpuid_leaf *leaf)
{
u32 cr4 = native_read_cr4();
int ret;
ghcb_set_rax(ghcb, leaf->fn);
ghcb_set_rcx(ghcb, leaf->subfn);
if (cr4 & X86_CR4_OSXSAVE)
ghcb_set_xcr0(ghcb, xgetbv(XCR_XFEATURE_ENABLED_MASK));
else
ghcb_set_xcr0(ghcb, 1);
ret = sev_es_ghcb_hv_call(ghcb, ctxt, SVM_EXIT_CPUID, 0, 0);
if (ret != ES_OK)
return ret;
if (!(ghcb_rax_is_valid(ghcb) &&
ghcb_rbx_is_valid(ghcb) &&
ghcb_rcx_is_valid(ghcb) &&
ghcb_rdx_is_valid(ghcb)))
return ES_VMM_ERROR;
leaf->eax = ghcb->save.rax;
leaf->ebx = ghcb->save.rbx;
leaf->ecx = ghcb->save.rcx;
leaf->edx = ghcb->save.rdx;
return ES_OK;
}
static int sev_cpuid_hv(struct ghcb *ghcb, struct es_em_ctxt *ctxt, struct cpuid_leaf *leaf)
{
return ghcb ? __sev_cpuid_hv_ghcb(ghcb, ctxt, leaf)
: __sev_cpuid_hv_msr(leaf);
}
const struct snp_cpuid_table *snp_cpuid_get_table(void)
{
return rip_rel_ptr(&cpuid_table_copy);
}
static u32 __head snp_cpuid_calc_xsave_size(u64 xfeatures_en, bool compacted)
{
const struct snp_cpuid_table *cpuid_table = snp_cpuid_get_table();
u64 xfeatures_found = 0;
u32 xsave_size = 0x240;
int i;
for (i = 0; i < cpuid_table->count; i++) {
const struct snp_cpuid_fn *e = &cpuid_table->fn[i];
if (!(e->eax_in == 0xD && e->ecx_in > 1 && e->ecx_in < 64))
continue;
if (!(xfeatures_en & (BIT_ULL(e->ecx_in))))
continue;
if (xfeatures_found & (BIT_ULL(e->ecx_in)))
continue;
xfeatures_found |= (BIT_ULL(e->ecx_in));
if (compacted)
xsave_size += e->eax;
else
xsave_size = max(xsave_size, e->eax + e->ebx);
}
if (xfeatures_found != (xfeatures_en & GENMASK_ULL(63, 2)))
return 0;
return xsave_size;
}
static bool __head
snp_cpuid_get_validated_func(struct cpuid_leaf *leaf)
{
const struct snp_cpuid_table *cpuid_table = snp_cpuid_get_table();
int i;
for (i = 0; i < cpuid_table->count; i++) {
const struct snp_cpuid_fn *e = &cpuid_table->fn[i];
if (e->eax_in != leaf->fn)
continue;
if (cpuid_function_is_indexed(leaf->fn) && e->ecx_in != leaf->subfn)
continue;
if (e->eax_in == 0xD && (e->ecx_in == 0 || e->ecx_in == 1))
if (!(e->xcr0_in == 1 || e->xcr0_in == 3) || e->xss_in)
continue;
leaf->eax = e->eax;
leaf->ebx = e->ebx;
leaf->ecx = e->ecx;
leaf->edx = e->edx;
return true;
}
return false;
}
static void snp_cpuid_hv(struct ghcb *ghcb, struct es_em_ctxt *ctxt, struct cpuid_leaf *leaf)
{
if (sev_cpuid_hv(ghcb, ctxt, leaf))
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_CPUID_HV);
}
static int __head
snp_cpuid_postprocess(struct ghcb *ghcb, struct es_em_ctxt *ctxt,
struct cpuid_leaf *leaf)
{
struct cpuid_leaf leaf_hv = *leaf;
switch (leaf->fn) {
case 0x1:
snp_cpuid_hv(ghcb, ctxt, &leaf_hv);
leaf->ebx = (leaf_hv.ebx & GENMASK(31, 24)) | (leaf->ebx & GENMASK(23, 0));
leaf->edx = (leaf_hv.edx & BIT(9)) | (leaf->edx & ~BIT(9));
if (native_read_cr4() & X86_CR4_OSXSAVE)
leaf->ecx |= BIT(27);
break;
case 0x7:
leaf->ecx &= ~BIT(4);
if (native_read_cr4() & X86_CR4_PKE)
leaf->ecx |= BIT(4);
break;
case 0xB:
leaf_hv.subfn = 0;
snp_cpuid_hv(ghcb, ctxt, &leaf_hv);
leaf->edx = leaf_hv.edx;
break;
case 0xD: {
bool compacted = false;
u64 xcr0 = 1, xss = 0;
u32 xsave_size;
if (leaf->subfn != 0 && leaf->subfn != 1)
return 0;
if (native_read_cr4() & X86_CR4_OSXSAVE)
xcr0 = xgetbv(XCR_XFEATURE_ENABLED_MASK);
if (leaf->subfn == 1) {
if (leaf->eax & BIT(3)) {
unsigned long lo, hi;
asm volatile("rdmsr" : "=a" (lo), "=d" (hi)
: "c" (MSR_IA32_XSS));
xss = (hi << 32) | lo;
}
if (!(leaf->eax & (BIT(1) | BIT(3))))
return -EINVAL;
compacted = true;
}
xsave_size = snp_cpuid_calc_xsave_size(xcr0 | xss, compacted);
if (!xsave_size)
return -EINVAL;
leaf->ebx = xsave_size;
}
break;
case 0x8000001E:
snp_cpuid_hv(ghcb, ctxt, &leaf_hv);
leaf->eax = leaf_hv.eax;
leaf->ebx = (leaf->ebx & GENMASK(31, 8)) | (leaf_hv.ebx & GENMASK(7, 0));
leaf->ecx = (leaf->ecx & GENMASK(31, 8)) | (leaf_hv.ecx & GENMASK(7, 0));
break;
default:
break;
}
return 0;
}
int __head
snp_cpuid(struct ghcb *ghcb, struct es_em_ctxt *ctxt, struct cpuid_leaf *leaf)
{
const struct snp_cpuid_table *cpuid_table = snp_cpuid_get_table();
if (!cpuid_table->count)
return -EOPNOTSUPP;
if (!snp_cpuid_get_validated_func(leaf)) {
leaf->eax = leaf->ebx = leaf->ecx = leaf->edx = 0;
if (!(leaf->fn <= cpuid_std_range_max ||
(leaf->fn >= 0x40000000 && leaf->fn <= cpuid_hyp_range_max) ||
(leaf->fn >= 0x80000000 && leaf->fn <= cpuid_ext_range_max)))
return 0;
}
return snp_cpuid_postprocess(ghcb, ctxt, leaf);
}
void __head do_vc_no_ghcb(struct pt_regs *regs, unsigned long exit_code)
{
unsigned int subfn = lower_bits(regs->cx, 32);
unsigned int fn = lower_bits(regs->ax, 32);
u16 opcode = *(unsigned short *)regs->ip;
struct cpuid_leaf leaf;
int ret;
if (exit_code != SVM_EXIT_CPUID)
goto fail;
if (opcode != 0xa20f)
goto fail;
leaf.fn = fn;
leaf.subfn = subfn;
ret = snp_cpuid(NULL, NULL, &leaf);
if (!ret)
goto cpuid_done;
if (ret != -EOPNOTSUPP)
goto fail;
if (__sev_cpuid_hv_msr(&leaf))
goto fail;
cpuid_done:
regs->ax = leaf.eax;
regs->bx = leaf.ebx;
regs->cx = leaf.ecx;
regs->dx = leaf.edx;
if (fn == 0x80000000 && (regs->ax < 0x8000001f))
goto fail;
else if ((fn == 0x8000001f && !(regs->ax & BIT(1))))
goto fail;
regs->ip += 2;
return;
fail:
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SEV_ES_GEN_REQ);
}
struct cc_setup_data {
struct setup_data header;
u32 cc_blob_address;
};
static __head
struct cc_blob_sev_info *find_cc_blob_setup_data(struct boot_params *bp)
{
struct cc_setup_data *sd = NULL;
struct setup_data *hdr;
hdr = (struct setup_data *)bp->hdr.setup_data;
while (hdr) {
if (hdr->type == SETUP_CC_BLOB) {
sd = (struct cc_setup_data *)hdr;
return (struct cc_blob_sev_info *)(unsigned long)sd->cc_blob_address;
}
hdr = (struct setup_data *)hdr->next;
}
return NULL;
}
static void __head setup_cpuid_table(const struct cc_blob_sev_info *cc_info)
{
const struct snp_cpuid_table *cpuid_table_fw, *cpuid_table;
int i;
if (!cc_info || !cc_info->cpuid_phys || cc_info->cpuid_len < PAGE_SIZE)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_CPUID);
cpuid_table_fw = (const struct snp_cpuid_table *)cc_info->cpuid_phys;
if (!cpuid_table_fw->count || cpuid_table_fw->count > SNP_CPUID_COUNT_MAX)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_CPUID);
cpuid_table = snp_cpuid_get_table();
memcpy((void *)cpuid_table, cpuid_table_fw, sizeof(*cpuid_table));
for (i = 0; i < cpuid_table->count; i++) {
const struct snp_cpuid_fn *fn = &cpuid_table->fn[i];
if (fn->eax_in == 0x0)
cpuid_std_range_max = fn->eax;
else if (fn->eax_in == 0x40000000)
cpuid_hyp_range_max = fn->eax;
else if (fn->eax_in == 0x80000000)
cpuid_ext_range_max = fn->eax;
}
}
static void __head svsm_pval_4k_page(unsigned long paddr, bool validate)
{
struct svsm_pvalidate_call *pc;
struct svsm_call call = {};
unsigned long flags;
u64 pc_pa;
int ret;
flags = native_local_irq_save();
call.caa = svsm_get_caa();
pc = (struct svsm_pvalidate_call *)call.caa->svsm_buffer;
pc_pa = svsm_get_caa_pa() + offsetof(struct svsm_ca, svsm_buffer);
pc->num_entries = 1;
pc->cur_index = 0;
pc->entry[0].page_size = RMP_PG_SIZE_4K;
pc->entry[0].action = validate;
pc->entry[0].ignore_cf = 0;
pc->entry[0].rsvd = 0;
pc->entry[0].pfn = paddr >> PAGE_SHIFT;
call.rax = SVSM_CORE_CALL(SVSM_CORE_PVALIDATE);
call.rcx = pc_pa;
ret = svsm_perform_call_protocol(&call);
if (ret)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PVALIDATE);
native_local_irq_restore(flags);
}
static void __head pvalidate_4k_page(unsigned long vaddr, unsigned long paddr,
bool validate)
{
int ret;
if (snp_vmpl) {
svsm_pval_4k_page(paddr, validate);
} else {
ret = pvalidate(vaddr, RMP_PG_SIZE_4K, validate);
if (ret)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PVALIDATE);
}
if (validate && !has_cpuflag(X86_FEATURE_COHERENCY_SFW_NO))
sev_evict_cache((void *)vaddr, 1);
}
static bool __head svsm_setup_ca(const struct cc_blob_sev_info *cc_info)
{
struct snp_secrets_page *secrets_page;
struct snp_cpuid_table *cpuid_table;
unsigned int i;
u64 caa;
BUILD_BUG_ON(sizeof(*secrets_page) != PAGE_SIZE);
if (!rmpadjust((unsigned long)rip_rel_ptr(&boot_ghcb_page), RMP_PG_SIZE_4K, 1))
return false;
if (!cc_info || !cc_info->secrets_phys || cc_info->secrets_len != PAGE_SIZE)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SECRETS_PAGE);
secrets_page = (struct snp_secrets_page *)cc_info->secrets_phys;
if (!secrets_page->svsm_size)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_NO_SVSM);
if (!secrets_page->svsm_guest_vmpl)
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SVSM_VMPL0);
snp_vmpl = secrets_page->svsm_guest_vmpl;
caa = secrets_page->svsm_caa;
if (caa & (PAGE_SIZE - 1))
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_SVSM_CAA);
boot_svsm_caa = (struct svsm_ca *)caa;
boot_svsm_caa_pa = caa;
cpuid_table = (struct snp_cpuid_table *)snp_cpuid_get_table();
for (i = 0; i < cpuid_table->count; i++) {
struct snp_cpuid_fn *fn = &cpuid_table->fn[i];
if (fn->eax_in == 0x8000001f)
fn->eax |= BIT(28);
}
return true;
}