[PATCH v1 17/26] crypto: ccp: Handle non-volatile INIT_EX data when SNP is enabled

Michael Roth
2023-12-30T16:24:36Z

From: Tom Lendacky <thomas.lendacky@amd.com>

For SEV/SEV-ES, a buffer can be used to access non-volatile data so it
can be initialized from a specified by the init_ex_path CCP module
parameter instead of relying on the SPI bus for NV storage, and
afterward the buffer can be read from to sync new data back to the file.

When SNP is enabled, the pages comprising this buffer need to be set to
firmware-owned in the RMP table before they can be accessed by firmware
for subsequent updates to the initial contents.

Implement that handling here.

Setting these pages to firmware-owned will also result in them being
removed from the kernel direct map, since generally the hypervisor does
not access firmware-owned pages. However, in this exceptional case,
the hypervisor does need to read the buffer to transfer updated contents
back to the file at init_ex_path.

Support this by using vmap() to create temporary mappings to the
firmware-owned buffer whenever accesses are made, and rework the
existing code to track the struct page corresponding to the start of the
buffer rather than the virtual address that was previously provided via
the direct map.

Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Co-developed-by: Michael Roth <michael.roth@amd.com>
Signed-off-by: Michael Roth <michael.roth@amd.com>
---
drivers/crypto/ccp/sev-dev.c | 104 ++++++++++++++++++++++++++---------
1 file changed, 79 insertions(+), 25 deletions(-)

diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
index 307eb3e7c354..dfe7f7afc411 100644
--- a/drivers/crypto/ccp/sev-dev.c
+++ b/drivers/crypto/ccp/sev-dev.c
@@ -88,8 +88,9 @@ static size_t sev_es_tmr_size = SEV_TMR_SIZE;
* allocator to allocate the memory, which will return aligned memory for the
* specified allocation order.
*/
-#define NV_LENGTH (32 * 1024)
-static void *sev_init_ex_buffer;
+#define NV_LENGTH (32 * 1024)
+#define NV_PAGES (NV_LENGTH >> PAGE_SHIFT)
+static struct page *sev_init_ex_page;

/*
* SEV_DATA_RANGE_LIST:
@@ -231,7 +232,7 @@ static int sev_read_init_ex_file(void)

lockdep_assert_held(&sev_cmd_mutex);

- if (!sev_init_ex_buffer)
+ if (!sev_init_ex_page)
return -EOPNOTSUPP;

fp = open_file_as_root(init_ex_path, O_RDONLY, 0);
@@ -251,7 +252,7 @@ static int sev_read_init_ex_file(void)
return ret;
}

- nread = kernel_read(fp, sev_init_ex_buffer, NV_LENGTH, NULL);
+ nread = kernel_read(fp, page_to_virt(sev_init_ex_page), NV_LENGTH, NULL);
if (nread != NV_LENGTH) {
dev_info(sev->dev,
"SEV: could not read %u bytes to non volatile memory area, ret %ld\n",
@@ -264,16 +265,44 @@ static int sev_read_init_ex_file(void)
return 0;
}

+/*
+ * When SNP is enabled, the pages comprising the buffer used to populate
+ * the file specified by the init_ex_path module parameter needs to be set
+ * to firmware-owned, which removes the mapping from the kernel direct
+ * mapping since generally the hypervisor does not access firmware-owned
+ * pages. However, in this case the hypervisor does need to read the
+ * buffer to transfer the contents to the file at init_ex_path, so this
+ * function is used to create a temporary virtual mapping to be used for
+ * this purpose.
+ */
+static void *vmap_sev_init_ex_buffer(void)
+{
+ struct page *pages[NV_PAGES];
+ unsigned long base_pfn;
+ int i;
+
+ if (WARN_ON_ONCE(!sev_init_ex_page))
+ return NULL;
+
+ base_pfn = page_to_pfn(sev_init_ex_page);
+
+ for (i = 0; i < NV_PAGES; i++)
+ pages[i] = pfn_to_page(base_pfn + i);
+
+ return vmap(pages, NV_PAGES, VM_MAP, PAGE_KERNEL_RO);
+}
+
static int sev_write_init_ex_file(void)
{
struct sev_device *sev = psp_master->sev_data;
+ void *sev_init_ex_buffer;
struct file *fp;
loff_t offset = 0;
ssize_t nwrite;

lockdep_assert_held(&sev_cmd_mutex);

- if (!sev_init_ex_buffer)
+ if (!sev_init_ex_page)
return 0;

fp = open_file_as_root(init_ex_path, O_CREAT | O_WRONLY, 0600);
@@ -286,6 +315,12 @@ static int sev_write_init_ex_file(void)
return ret;
}

+ sev_init_ex_buffer = vmap_sev_init_ex_buffer();
+ if (!sev_init_ex_buffer) {
+ dev_err(sev->dev, "SEV: failed to map non-volative memory area\n");
+ return -EIO;
+ }
+
nwrite = kernel_write(fp, sev_init_ex_buffer, NV_LENGTH, &offset);
vfs_fsync(fp, 0);
filp_close(fp, NULL);
@@ -294,10 +329,12 @@ static int sev_write_init_ex_file(void)
dev_err(sev->dev,
"SEV: failed to write %u bytes to non volatile memory area, ret %ld\n",
NV_LENGTH, nwrite);
+ vunmap(sev_init_ex_buffer);
return -EIO;
}

dev_dbg(sev->dev, "SEV: write successful to NV file\n");
+ vunmap(sev_init_ex_buffer);

return 0;
}
@@ -306,7 +343,7 @@ static int sev_write_init_ex_file_if_required(int cmd_id)
{
lockdep_assert_held(&sev_cmd_mutex);

- if (!sev_init_ex_buffer)
+ if (!sev_init_ex_page)
return 0;

/*
@@ -599,7 +636,7 @@ static int __sev_init_ex_locked(int *error)

memset(&data, 0, sizeof(data));
data.length = sizeof(data);
- data.nv_address = __psp_pa(sev_init_ex_buffer);
+ data.nv_address = sme_me_mask | PFN_PHYS(page_to_pfn(sev_init_ex_page));
data.nv_len = NV_LENGTH;

if (sev_es_tmr) {
@@ -618,7 +655,7 @@ static int __sev_init_ex_locked(int *error)

static inline int __sev_do_init_locked(int *psp_ret)
{
- if (sev_init_ex_buffer)
+ if (sev_init_ex_page)
return __sev_init_ex_locked(psp_ret);
else
return __sev_init_locked(psp_ret);
@@ -787,10 +824,38 @@ static int __sev_platform_init_locked(int *error)
}
}

- if (sev_init_ex_buffer) {
+ /*
+ * If an init_ex_path is provided allocate a buffer for the file and
+ * read in the contents. Additionally, if SNP is initialized, convert
+ * the buffer pages to firmware pages.
+ */
+ if (init_ex_path && !sev_init_ex_page) {
+ struct page *page;
+
+ page = alloc_pages(GFP_KERNEL, get_order(NV_LENGTH));
+ if (!page) {
+ dev_err(sev->dev, "SEV: INIT_EX NV memory allocation failed\n");
+ return -ENOMEM;
+ }
+
+ sev_init_ex_page = page;
+
rc = sev_read_init_ex_file();
if (rc)
return rc;
+
+ /* If SEV-SNP is initialized, transition to firmware page. */
+ if (sev->snp_initialized) {
+ unsigned long npages;
+
+ npages = 1UL << get_order(NV_LENGTH);
+ if (rmp_mark_pages_firmware(PFN_PHYS(page_to_pfn(sev_init_ex_page)),
+ npages, false)) {
+ dev_err(sev->dev,
+ "SEV: INIT_EX NV memory page state change failed.\n");
+ return -ENOMEM;
+ }
+ }
}

rc = __sev_do_init_locked(&psp_ret);
@@ -1689,10 +1754,11 @@ static void sev_firmware_shutdown(struct sev_device *sev)
sev_es_tmr = NULL;
}

- if (sev_init_ex_buffer) {
- free_pages((unsigned long)sev_init_ex_buffer,
- get_order(NV_LENGTH));
- sev_init_ex_buffer = NULL;
+ if (sev_init_ex_page) {
+ __snp_free_firmware_pages(sev_init_ex_page,
+ get_order(NV_LENGTH),
+ true);
+ sev_init_ex_page = NULL;
}

if (snp_range_list) {
@@ -1745,18 +1811,6 @@ void sev_pci_init(void)
if (sev_update_firmware(sev->dev) == 0)
sev_get_api_version();

- /* If an init_ex_path is provided rely on INIT_EX for PSP initialization
- * instead of INIT.
- */
- if (init_ex_path) {
- sev_init_ex_buffer = sev_fw_alloc(NV_LENGTH);
- if (!sev_init_ex_buffer) {
- dev_err(sev->dev,
- "SEV: INIT_EX NV memory allocation failed\n");
- goto err;
- }
- }
-
/* Initialize the platform */
args.probe = true;
rc = sev_platform_init(&args);
--
2.25.1