@@ -92,6 +92,7 @@ struct ghcb {
/* RMP page size */
#define RMP_PG_SIZE_4K 0
+#define RMP_PG_SIZE_2M 1
enum es_result {
ES_OK, /* All good */
@@ -165,6 +166,7 @@ efi_status_t setup_amd_sev(void);
* - Section "GHCB"
*/
#define SEV_ES_GHCB_MSR_INDEX 0xc0010130
+#define VMGEXIT_PSC_MAX_ENTRY 253
#define GHCB_DATA_LOW 12
#define GHCB_MSR_INFO_MASK (BIT_ULL(GHCB_DATA_LOW) - 1)
@@ -196,11 +198,31 @@ 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;
+ u64 gfn : 40;
+ u64 operation : 4;
+ u64 pagesize : 1;
+ u64 reserved : 7;
+};
+
+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);
void setup_ghcb_pte(pgd_t *page_table);
void handle_sev_es_vc(struct ex_regs *regs);
+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);
@@ -12,7 +12,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));
@@ -362,6 +362,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)
@@ -115,6 +115,121 @@ static inline int pvalidate(u64 vaddr, bool rmp_size,
return result;
}
+static void pvalidate_pages(struct snp_psc_desc *desc)
+{
+ struct psc_entry *entry;
+ unsigned long vaddr;
+ int pvalidate_result, i;
+ bool validate;
+
+ for (i = 0; i <= desc->hdr.end_entry; i++) {
+ entry = &desc->entries[i];
+
+ vaddr = (unsigned long)__pa(entry->gfn << PAGE_SHIFT);
+ validate = entry->operation == SNP_PAGE_STATE_PRIVATE;
+
+ pvalidate_result = pvalidate(vaddr, entry->pagesize, validate);
+ if (pvalidate_result == PVALIDATE_FAIL_SIZEMISMATCH &&
+ entry->pagesize == RMP_PG_SIZE_2M) {
+ unsigned long vaddr_end = vaddr + LARGE_PAGE_SIZE;
+
+ for (; vaddr < vaddr_end; vaddr += PAGE_SIZE) {
+ pvalidate_result = pvalidate(vaddr, RMP_PG_SIZE_4K,
+ validate);
+ if (pvalidate_result)
+ break;
+ }
+ }
+
+ if (pvalidate_result) {
+ assert_msg(!pvalidate_result, "Failed to validate address: 0x%lx, ret: %d\n",
+ vaddr, pvalidate_result);
+ }
+ }
+}
+
+static int verify_exception(struct ghcb *ghcb)
+{
+ return ghcb->save.sw_exit_info_1 & GENMASK_ULL(31, 0);
+}
+
+static inline 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;
+
+ /*
+ * If ever sizeof(*desc) becomes larger than GHCB_SHARED_BUF_SIZE,
+ * adjust the end_entry here to point to the last entry that will
+ * be copied to GHCB shared buffer in vmgexit_psc().
+ */
+ if (sizeof(*desc) > GHCB_SHARED_BUF_SIZE)
+ desc->hdr.end_entry = VMGEXIT_PSC_MAX_ENTRY - 1;
+
+ vc_ghcb_invalidate(ghcb);
+
+ /* Copy the input desc into GHCB shared buffer */
+ 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);
+
+ /*
+ * Page state change VMGEXIT passes error code to
+ * exit_info_2.
+ */
+ if (ret || ghcb->save.sw_exit_info_2) {
+ printf("SNP: PSC failed ret=%d exit_info_2=%lx\n",
+ ret, ghcb->save.sw_exit_info_2);
+ ret = 1;
+ break;
+ }
+
+ if (cur_entry > data->hdr.cur_entry) {
+ printf("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) {
+ printf("End entry mismatch: end_entry %d (got %d)\n",
+ end_entry, data->hdr.end_entry);
+ ret = 1;
+ break;
+ }
+
+ if (data->hdr.reserved) {
+ printf("Reserved bit is set in the PSC header\n");
+ ret = 1;
+ break;
+ }
+ }
+
+ return ret;
+}
+
static efi_status_t __sev_set_pages_state_msr_proto(unsigned long vaddr, int npages,
int operation)
{
@@ -261,6 +376,66 @@ static efi_status_t sev_set_pages_state_msr_proto(unsigned long vaddr,
return ES_OK;
}
+static 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)
+{
+ struct psc_hdr *hdr;
+ struct psc_entry *entry;
+ unsigned long pfn;
+ int iter, ret;
+
+ hdr = &desc->hdr;
+ entry = desc->entries;
+
+ memset(desc, 0, sizeof(*desc));
+ iter = 0;
+
+ while (vaddr < vaddr_end && iter < ARRAY_SIZE(desc->entries)) {
+ hdr->end_entry = iter;
+ pfn = __pa(vaddr) >> PAGE_SHIFT;
+ entry->gfn = pfn;
+ entry->operation = op;
+
+ if (large_entry && IS_ALIGNED(vaddr, LARGE_PAGE_SIZE) &&
+ (vaddr_end - vaddr) >= LARGE_PAGE_SIZE) {
+ entry->pagesize = RMP_PG_SIZE_2M;
+ vaddr += LARGE_PAGE_SIZE;
+ } else {
+ entry->pagesize = RMP_PG_SIZE_4K;
+ vaddr += PAGE_SIZE;
+ }
+
+ entry++;
+ iter++;
+ }
+
+ if (op == SNP_PAGE_STATE_SHARED)
+ pvalidate_pages(desc);
+
+ ret = vmgexit_psc(desc, ghcb);
+ assert_msg(!ret, "VMGEXIT failed with return value: %d", ret);
+
+ if (op == SNP_PAGE_STATE_PRIVATE)
+ pvalidate_pages(desc);
+
+ return vaddr;
+}
+
+static void sev_set_pages_state(unsigned long vaddr, unsigned long npages,
+ int op, struct ghcb *ghcb, bool large_entry)
+{
+ struct snp_psc_desc desc;
+ unsigned long vaddr_end;
+
+ vaddr = vaddr & PAGE_MASK;
+ vaddr_end = vaddr + (npages << PAGE_SHIFT);
+
+ while (vaddr < vaddr_end)
+ vaddr = __sev_set_pages_state(&desc, vaddr, vaddr_end, op,
+ ghcb, large_entry);
+}
+
static void test_sev_snp_activation(void)
{
efi_status_t status;
@@ -407,6 +582,43 @@ static void test_sev_psc_ghcb_msr(void)
free_pages_by_order(vaddr, SNP_PSC_ALLOC_ORDER);
}
+static void test_sev_psc_ghcb_nae(void)
+{
+ pteval_t *pte;
+ bool large_page = false;
+ unsigned long *vm_pages;
+ struct ghcb *ghcb = (struct ghcb *)(rdmsr(SEV_ES_GHCB_MSR_INDEX));
+
+ vm_pages = alloc_pages(SNP_PSC_ALLOC_ORDER);
+ assert_msg(vm_pages, "Page allocation failure");
+
+ pte = get_pte_level((pgd_t *)read_cr3(), (void *)vm_pages, 1);
+ if (!pte && IS_ALIGNED((unsigned long)vm_pages, LARGE_PAGE_SIZE)) {
+ report_info("Installing a large 2M page");
+ /* Install 2M large page */
+ install_large_page((pgd_t *)read_cr3(),
+ (phys_addr_t)vm_pages, (void *)(ulong)vm_pages);
+ large_page = true;
+ }
+
+ report(is_validated_private_page((unsigned long)vm_pages, large_page, true),
+ "Expected page state: Private");
+
+ report_info("Private->Shared conversion test using GHCB NAE");
+ /* Private->Shared operations */
+ sev_set_pages_state((unsigned long)vm_pages, 1 << SNP_PSC_ALLOC_ORDER,
+ SNP_PAGE_STATE_SHARED, ghcb, large_page);
+
+ set_pte_decrypted((unsigned long)vm_pages, 1 << SNP_PSC_ALLOC_ORDER);
+
+ report(!test_write((unsigned long)vm_pages, 1 << SNP_PSC_ALLOC_ORDER),
+ "Write to %d un-encrypted pages after private->shared conversion",
+ 1 << SNP_PSC_ALLOC_ORDER);
+
+ /* Cleanup */
+ free_pages_by_order(vm_pages, SNP_PSC_ALLOC_ORDER);
+}
+
int main(void)
{
int rtn;
@@ -417,8 +629,10 @@ int main(void)
test_stringio();
setup_vm();
- if (amd_sev_snp_enabled())
+ if (amd_sev_snp_enabled()) {
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 introduces support for both 4K pages as well as 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_SIZE_MISMATCH 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 un-encrypted pages after private->shared conversions to confirm we can write data to the shared guest pages post page state conversions with C-bits unset appropriately. Signed-off-by: Pavan Kumar Paluri <papaluri@amd.com> --- lib/x86/amd_sev.h | 22 +++++ lib/x86/amd_sev_vc.c | 2 +- lib/x86/svm.h | 1 + x86/amd_sev.c | 216 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 239 insertions(+), 2 deletions(-)