#include <linux/types.h>
#include <asm/sev.h>
#include "internal.h"
struct svsm_ca boot_svsm_ca_page __aligned(PAGE_SIZE);
SYM_PIC_ALIAS(boot_svsm_ca_page);
u64 boot_svsm_caa_pa __ro_after_init;
SYM_PIC_ALIAS(boot_svsm_caa_pa);
DEFINE_PER_CPU(struct svsm_ca *, svsm_caa);
DEFINE_PER_CPU(u64, svsm_caa_pa);
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);
}
int svsm_perform_call_protocol(struct svsm_call *call)
{
struct ghcb_state state;
unsigned long flags;
struct ghcb *ghcb;
int ret;
flags = native_local_irq_save();
if (sev_cfg.ghcbs_initialized)
ghcb = __sev_get_ghcb(&state);
else if (boot_ghcb)
ghcb = boot_ghcb;
else
ghcb = NULL;
do {
ret = ghcb ? svsm_perform_ghcb_protocol(ghcb, call)
: __pi_svsm_perform_msr_protocol(call);
} while (ret == -EAGAIN);
if (sev_cfg.ghcbs_initialized)
__sev_put_ghcb(&state);
native_local_irq_restore(flags);
return ret;
}
static u64 svsm_build_ca_from_pfn_range(u64 pfn, u64 pfn_end, bool action,
struct svsm_pvalidate_call *pc)
{
struct svsm_pvalidate_entry *pe;
pc->num_entries = 0;
pc->cur_index = 0;
pe = &pc->entry[0];
while (pfn < pfn_end) {
pe->page_size = RMP_PG_SIZE_4K;
pe->action = action;
pe->ignore_cf = 0;
pe->rsvd = 0;
pe->pfn = pfn;
pe++;
pfn++;
pc->num_entries++;
if (pc->num_entries == SVSM_PVALIDATE_MAX_COUNT)
break;
}
return pfn;
}
static int svsm_build_ca_from_psc_desc(struct snp_psc_desc *desc, unsigned int desc_entry,
struct svsm_pvalidate_call *pc)
{
struct svsm_pvalidate_entry *pe;
struct psc_entry *e;
pc->num_entries = 0;
pc->cur_index = 0;
pe = &pc->entry[0];
e = &desc->entries[desc_entry];
while (desc_entry <= desc->hdr.end_entry) {
pe->page_size = e->pagesize ? RMP_PG_SIZE_2M : RMP_PG_SIZE_4K;
pe->action = e->operation == SNP_PAGE_STATE_PRIVATE;
pe->ignore_cf = 0;
pe->rsvd = 0;
pe->pfn = e->gfn;
pe++;
e++;
desc_entry++;
pc->num_entries++;
if (pc->num_entries == SVSM_PVALIDATE_MAX_COUNT)
break;
}
return desc_entry;
}
static void svsm_pval_terminate(struct svsm_pvalidate_call *pc, int ret, u64 svsm_ret)
{
unsigned int page_size;
bool action;
u64 pfn;
pfn = pc->entry[pc->cur_index].pfn;
action = pc->entry[pc->cur_index].action;
page_size = pc->entry[pc->cur_index].page_size;
__pval_terminate(pfn, action, page_size, ret, svsm_ret);
}
void svsm_pval_pages(struct snp_psc_desc *desc)
{
struct svsm_pvalidate_entry pv_4k[VMGEXIT_PSC_MAX_ENTRY];
unsigned int i, pv_4k_count = 0;
struct svsm_pvalidate_call *pc;
struct svsm_call call = {};
unsigned long flags;
bool action;
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);
call.rax = SVSM_CORE_CALL(SVSM_CORE_PVALIDATE);
call.rcx = pc_pa;
for (i = 0; i <= desc->hdr.end_entry;) {
i = svsm_build_ca_from_psc_desc(desc, i, pc);
do {
ret = svsm_perform_call_protocol(&call);
if (!ret)
continue;
if (call.rax_out == SVSM_PVALIDATE_FAIL_SIZEMISMATCH &&
pc->entry[pc->cur_index].page_size == RMP_PG_SIZE_2M) {
pv_4k[pv_4k_count++] = pc->entry[pc->cur_index];
pc->cur_index++;
if (pc->cur_index < pc->num_entries)
ret = -EAGAIN;
else
ret = 0;
}
} while (ret == -EAGAIN);
if (ret)
svsm_pval_terminate(pc, ret, call.rax_out);
}
for (i = 0; i < pv_4k_count; i++) {
u64 pfn, pfn_end;
action = pv_4k[i].action;
pfn = pv_4k[i].pfn;
pfn_end = pfn + 512;
while (pfn < pfn_end) {
pfn = svsm_build_ca_from_pfn_range(pfn, pfn_end, action, pc);
ret = svsm_perform_call_protocol(&call);
if (ret)
svsm_pval_terminate(pc, ret, call.rax_out);
}
}
native_local_irq_restore(flags);
}
static void update_attest_input(struct svsm_call *call, struct svsm_attest_call *input)
{
if (call->rcx_out != call->rcx)
input->manifest_buf.len = call->rcx_out;
if (call->rdx_out != call->rdx)
input->certificates_buf.len = call->rdx_out;
if (call->r8_out != call->r8)
input->report_buf.len = call->r8_out;
}
int snp_issue_svsm_attest_req(u64 call_id, struct svsm_call *call,
struct svsm_attest_call *input)
{
struct svsm_attest_call *ac;
unsigned long flags;
u64 attest_call_pa;
int ret;
if (!snp_vmpl)
return -EINVAL;
local_irq_save(flags);
call->caa = svsm_get_caa();
ac = (struct svsm_attest_call *)call->caa->svsm_buffer;
attest_call_pa = svsm_get_caa_pa() + offsetof(struct svsm_ca, svsm_buffer);
*ac = *input;
call->rax = call_id;
call->rcx = attest_call_pa;
call->rdx = -1;
call->r8 = -1;
ret = svsm_perform_call_protocol(call);
update_attest_input(call, input);
local_irq_restore(flags);
return ret;
}
EXPORT_SYMBOL_GPL(snp_issue_svsm_attest_req);
int snp_svsm_vtpm_send_command(u8 *buffer)
{
struct svsm_call call = {};
call.caa = svsm_get_caa();
call.rax = SVSM_VTPM_CALL(SVSM_VTPM_CMD);
call.rcx = __pa(buffer);
return svsm_perform_call_protocol(&call);
}
EXPORT_SYMBOL_GPL(snp_svsm_vtpm_send_command);
bool snp_svsm_vtpm_probe(void)
{
struct svsm_call call = {};
if (!snp_vmpl)
return false;
call.caa = svsm_get_caa();
call.rax = SVSM_VTPM_CALL(SVSM_VTPM_QUERY);
if (svsm_perform_call_protocol(&call))
return false;
return call.rcx_out & BIT_ULL(8);
}