@@ -346,3 +346,171 @@ enum es_result __sev_set_pages_state_msr_proto(unsigned long vaddr, int npages,
return ES_OK;
}
+
+static void pvalidate_pages(struct snp_psc_desc *desc, unsigned long *vaddr_arr)
+{
+ struct psc_entry *entry;
+ int ret, i;
+ unsigned long vaddr;
+ bool validate;
+
+ for (i = 0; i <= desc->hdr.end_entry; i++) {
+ vaddr = vaddr_arr[i];
+ entry = &desc->entries[i];
+ validate = entry->operation == SNP_PAGE_STATE_PRIVATE ? true : false;
+
+ ret = pvalidate(vaddr, entry->pagesize, validate);
+ if (ret == PVALIDATE_FAIL_SIZEMISMATCH) {
+ assert(entry->pagesize == RMP_PG_SIZE_2M);
+ unsigned long vaddr_end = vaddr + LARGE_PAGE_SIZE;
+
+ for (; vaddr < vaddr_end; vaddr += PAGE_SIZE) {
+ ret = pvalidate(vaddr, RMP_PG_SIZE_4K, validate);
+ if (ret)
+ break;
+ }
+ }
+ assert(!ret);
+ }
+}
+
+static int verify_exception(struct ghcb *ghcb)
+{
+ return ghcb->save.sw_exit_info_1 & GENMASK_ULL(31, 0);
+}
+
+static int sev_ghcb_hv_call(struct ghcb *ghcb, u64 exit_code,
+ u64 exit_info_1, u64 exit_info_2)
+{
+ ghcb->version = GHCB_PROTOCOL_MAX;
+ 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);
+
+ VMGEXIT();
+
+ return verify_exception(ghcb);
+}
+
+static int vmgexit_psc(struct snp_psc_desc *desc, struct ghcb *ghcb)
+{
+ int cur_entry, end_entry, ret = 0;
+ struct snp_psc_desc *data;
+
+ /* Ensure end_entry is within bounds */
+ assert(desc->hdr.end_entry < VMGEXIT_PSC_MAX_ENTRY);
+
+ vc_ghcb_invalidate(ghcb);
+
+ data = (struct snp_psc_desc *)ghcb->shared_buffer;
+ memcpy(ghcb->shared_buffer, desc, GHCB_SHARED_BUF_SIZE);
+
+ cur_entry = data->hdr.cur_entry;
+ end_entry = data->hdr.end_entry;
+
+ while (data->hdr.cur_entry <= data->hdr.end_entry) {
+ ghcb_set_sw_scratch(ghcb, (u64)__pa(data));
+
+ ret = sev_ghcb_hv_call(ghcb, SVM_VMGEXIT_PSC, 0, 0);
+
+ if (ret) {
+ report_info("SNP: PSC failed with ret: %d\n", ret);
+ ret = 1;
+ break;
+ }
+
+ if (cur_entry > data->hdr.cur_entry) {
+ report_info("SNP: PSC processing going backward, cur_entry %d (got %d)\n",
+ cur_entry, data->hdr.cur_entry);
+ ret = 1;
+ break;
+ }
+
+ if (data->hdr.end_entry != end_entry) {
+ report_info("End entry mismatch: end_entry %d (got %d)\n",
+ end_entry, data->hdr.end_entry);
+ ret = 1;
+ break;
+ }
+
+ if (data->hdr.reserved) {
+ report_info("Reserved bit is set in the PSC header\n");
+ ret = 1;
+ break;
+ }
+ }
+
+ /* Copy the output in shared buffer back to desc */
+ memcpy(desc, ghcb->shared_buffer, GHCB_SHARED_BUF_SIZE);
+
+ return ret;
+}
+
+static void add_psc_entry(struct snp_psc_desc *desc, u8 idx, u8 op, unsigned long vaddr,
+ bool large_entry, u16 cur_page_offset)
+{
+ struct psc_hdr *hdr = &desc->hdr;
+ struct psc_entry *entry = &desc->entries[idx];
+
+ assert_msg(!large_entry || IS_ALIGNED(vaddr, LARGE_PAGE_SIZE),
+ "Must use 2M-aligned addresses for large PSC entries");
+
+ entry->gfn = pgtable_va_to_pa(vaddr) >> PAGE_SHIFT;
+ entry->operation = op;
+ entry->pagesize = large_entry;
+ entry->cur_page = cur_page_offset;
+ hdr->end_entry = idx;
+}
+
+unsigned long __sev_set_pages_state(struct snp_psc_desc *desc, unsigned long vaddr,
+ unsigned long vaddr_end, int op,
+ struct ghcb *ghcb, bool large_entry)
+{
+ unsigned long vaddr_arr[VMGEXIT_PSC_MAX_ENTRY];
+ int ret, iter = 0, iter2 = 0;
+ u8 page_size;
+
+ memset(desc, 0, sizeof(*desc));
+
+ report_info("%s: address start %lx end %lx op %d large %d",
+ __func__, vaddr, vaddr_end, op, large_entry);
+
+ while (vaddr < vaddr_end && iter < ARRAY_SIZE(desc->entries)) {
+ vaddr_arr[iter] = vaddr;
+
+ if (large_entry && IS_ALIGNED(vaddr, LARGE_PAGE_SIZE) &&
+ (vaddr_end - vaddr) >= LARGE_PAGE_SIZE) {
+ add_psc_entry(desc, iter, op, vaddr, true, 0);
+ vaddr += LARGE_PAGE_SIZE;
+ } else {
+ add_psc_entry(desc, iter, op, vaddr, false, 0);
+ vaddr += PAGE_SIZE;
+ }
+
+ iter++;
+ }
+
+ if (op == SNP_PAGE_STATE_SHARED)
+ pvalidate_pages(desc, vaddr_arr);
+
+ ret = vmgexit_psc(desc, ghcb);
+ assert_msg(!ret, "VMGEXIT failed with ret value: %d", ret);
+
+ if (op == SNP_PAGE_STATE_PRIVATE)
+ pvalidate_pages(desc, vaddr_arr);
+
+ for (iter2 = 0; iter2 < iter; iter2++) {
+ page_size = desc->entries[iter2].pagesize;
+
+ if (page_size == RMP_PG_SIZE_2M)
+ assert_msg(desc->entries[iter2].cur_page == 512,
+ "Failed to process sub-entries within 2M range");
+ else if (page_size == RMP_PG_SIZE_4K)
+ assert_msg(desc->entries[iter2].cur_page == 1,
+ "Failed to process 4K entry");
+ }
+
+ return vaddr;
+}
@@ -208,6 +208,29 @@ enum psc_op {
/* GHCBData[63:32] */ \
(((u64)(val) & GENMASK_ULL(63, 32)) >> 32)
+struct psc_hdr {
+ u16 cur_entry;
+ u16 end_entry;
+ u32 reserved;
+};
+
+struct psc_entry {
+ u64 cur_page : 12,
+ gfn : 40,
+ operation : 4,
+ pagesize : 1,
+ reserved : 7;
+};
+
+#define VMGEXIT_PSC_MAX_ENTRY \
+ ((GHCB_SHARED_BUF_SIZE - sizeof(struct psc_hdr)) / \
+ sizeof(struct psc_entry))
+
+struct snp_psc_desc {
+ struct psc_hdr hdr;
+ struct psc_entry entries[VMGEXIT_PSC_MAX_ENTRY];
+};
+
bool amd_sev_es_enabled(void);
efi_status_t setup_vc_handler(void);
bool amd_sev_snp_enabled(void);
@@ -219,6 +242,10 @@ void set_pte_encrypted(unsigned long vaddr, int npages);
bool is_validated_private_page(unsigned long vaddr, bool rmp_size);
enum es_result __sev_set_pages_state_msr_proto(unsigned long vaddr,
int npages, int operation);
+unsigned long __sev_set_pages_state(struct snp_psc_desc *desc, unsigned long vaddr,
+ unsigned long vaddr_end, int op,
+ struct ghcb *ghcb, bool large_entry);
+void vc_ghcb_invalidate(struct ghcb *ghcb);
unsigned long long get_amd_sev_c_bit_mask(void);
unsigned long long get_amd_sev_addr_upperbound(void);
@@ -11,7 +11,7 @@
extern phys_addr_t ghcb_addr;
-static void vc_ghcb_invalidate(struct ghcb *ghcb)
+void vc_ghcb_invalidate(struct ghcb *ghcb)
{
ghcb->save.sw_exit_code = 0;
memset(ghcb->save.valid_bitmap, 0, sizeof(ghcb->save.valid_bitmap));
@@ -372,6 +372,7 @@ struct __attribute__ ((__packed__)) vmcb {
#define SVM_EXIT_NPF 0x400
#define SVM_EXIT_ERR -1
+#define SVM_VMGEXIT_PSC 0x80000010
#define SVM_CR0_SELECTIVE_MASK (X86_CR0_TS | X86_CR0_MP)
@@ -370,3 +370,29 @@ unsigned long pgtable_va_to_pa(unsigned long va)
__builtin_unreachable();
}
+
+void *vmalloc_pages(int num_pages, int order, bool large_page)
+{
+ unsigned long length = num_pages * PAGE_SIZE;
+ pgd_t *cr3 = (pgd_t *)read_cr3();
+ void *vaddr, *paddr;
+
+ /* Allocate physical pages */
+ paddr = alloc_pages(order);
+ assert(paddr);
+
+ /* Allocate virtual pages */
+ vaddr = alloc_vpages_aligned(num_pages, large_page ? ORDER_2M : ORDER_4K);
+ assert(vaddr);
+
+ /*
+ * Create pagetable entries that map the newly assigned virtual
+ * pages to physical pages
+ */
+ if (!large_page)
+ install_pages(cr3, __pa(paddr), length, vaddr);
+ else
+ install_large_pages(cr3, __pa(paddr), length, vaddr);
+
+ return vaddr;
+}
@@ -61,6 +61,7 @@ static inline void *current_page_table(void)
void split_large_page(unsigned long *ptep, int level);
void force_4k_page(void *addr);
unsigned long pgtable_va_to_pa(unsigned long vaddr);
+void *vmalloc_pages(int num_pages, int order, bool large_page);
struct vm_vcpu_info {
u64 cr3;
@@ -173,6 +173,37 @@ static int test_write(unsigned long vaddr, int npages)
return 0;
}
+static void sev_set_pages_state(unsigned long vaddr, int npages, int op,
+ struct ghcb *ghcb)
+{
+ struct snp_psc_desc desc;
+ unsigned long vaddr_end;
+ bool large_entry;
+
+ vaddr &= PAGE_MASK;
+ vaddr_end = vaddr + (npages << PAGE_SHIFT);
+
+ if (IS_ALIGNED(vaddr, LARGE_PAGE_SIZE))
+ large_entry = true;
+
+ while (vaddr < vaddr_end) {
+ vaddr = __sev_set_pages_state(&desc, vaddr, vaddr_end,
+ op, ghcb, large_entry);
+ }
+}
+
+static void snp_free_pages(int order, int npages, unsigned long vaddr,
+ struct ghcb *ghcb)
+{
+ set_pte_encrypted(vaddr, SEV_ALLOC_PAGE_COUNT);
+
+ /* Convert pages back to default guest-owned state */
+ sev_set_pages_state(vaddr, npages, SNP_PAGE_STATE_PRIVATE, ghcb);
+
+ /* Free all the associated physical pages */
+ free_pages_by_order((void *)pgtable_va_to_pa(vaddr), order);
+}
+
static void test_sev_psc_ghcb_msr(void)
{
void *vaddr;
@@ -210,6 +241,44 @@ static void test_sev_psc_ghcb_msr(void)
free_pages_by_order(vaddr, SEV_ALLOC_ORDER);
}
+static void init_vpages(void)
+{
+ /*
+ * alloc_vpages_aligned() allocates contiguous virtual
+ * pages that grow downward from vfree_top, 0, and this is
+ * problematic for SNP related PSC tests because
+ * vaddr < vaddr_end using unsigned values causes an issue
+ * (vaddr_end is 0x0). To avoid this, allocate a dummy virtual
+ * page.
+ */
+ alloc_vpages_aligned(1, 0);
+}
+
+static void test_sev_psc_ghcb_nae(void)
+{
+ unsigned long vaddr;
+ struct ghcb *ghcb = (struct ghcb *)rdmsr(SEV_ES_GHCB_MSR_INDEX);
+
+ report_info("TEST: GHCB Protocol based page state change test");
+
+ vaddr = (unsigned long)vmalloc_pages(SEV_ALLOC_PAGE_COUNT,
+ SEV_ALLOC_ORDER, RMP_PG_SIZE_2M);
+
+ report(is_validated_private_page(vaddr, RMP_PG_SIZE_2M),
+ "Expected page state: Private");
+
+ sev_set_pages_state(vaddr, SEV_ALLOC_PAGE_COUNT, SNP_PAGE_STATE_SHARED,
+ ghcb);
+
+ set_pte_decrypted(vaddr, SEV_ALLOC_PAGE_COUNT);
+
+ report(!test_write((unsigned long)vaddr, SEV_ALLOC_PAGE_COUNT),
+ "Write to %d unencrypted 2M pages after private->shared conversion",
+ (SEV_ALLOC_PAGE_COUNT) / (1 << ORDER_2M));
+
+ snp_free_pages(SEV_ALLOC_ORDER, SEV_ALLOC_PAGE_COUNT, vaddr, ghcb);
+}
+
int main(void)
{
int rtn;
@@ -221,8 +290,15 @@ int main(void)
/* Setup a new page table via setup_vm() */
setup_vm();
- if (amd_sev_snp_enabled())
+ if (amd_sev_snp_enabled()) {
+ /*
+ * call init_vpages() before running any of SEV-SNP
+ * related PSC tests.
+ */
+ init_vpages();
test_sev_psc_ghcb_msr();
+ test_sev_psc_ghcb_nae();
+ }
return report_summary();
}
As mentioned in the GHCB spec (Section 4 GHCB protocol, Table-5 SNP page state change), perform page state change conversions on a user inputted number of pages from private to hypervisor-owned. The page state change NAE event allows for SEV-SNP guest to request page state changes to hypervisor using GHCB protocol. The test handles both 4K pages and 2M large pages, depending on the order specified and whether the address of the page is 2M aligned or not. If 2M range is backed by a 4K page, 'pvalidate' fails with FAIL_SIZEMISMATCH error. In such a case, the guest tries to pvalidate all the 4K entries in this 2M range. Conduct a test to re-validate the private page before conversion to ensure PVALIDATE_FAIL_NOUPDATE is met, indicating that expected page state is met. Then, perform the page state conversions, unset the C-bits on these pages and write data to the shared guest pages post page state conversions with C-bits unset appropriately to ensure pages are in expected shared state. Signed-off-by: Pavan Kumar Paluri <papaluri@amd.com> --- lib/x86/amd_sev.c | 168 +++++++++++++++++++++++++++++++++++++++++++ lib/x86/amd_sev.h | 27 +++++++ lib/x86/amd_sev_vc.c | 2 +- lib/x86/svm.h | 1 + lib/x86/vm.c | 26 +++++++ lib/x86/vm.h | 1 + x86/amd_sev.c | 78 +++++++++++++++++++- 7 files changed, 301 insertions(+), 2 deletions(-)