crypto: ccp: Handle the legacy TMR allocation when SNP is enabled
authorBrijesh Singh <brijesh.singh@amd.com>
Fri, 26 Jan 2024 04:11:16 +0000 (22:11 -0600)
committerBorislav Petkov (AMD) <bp@alien8.de>
Mon, 29 Jan 2024 19:34:18 +0000 (20:34 +0100)
The behavior and requirement for the SEV-legacy command is altered when
the SNP firmware is in the INIT state. See SEV-SNP firmware ABI
specification for more details.

Allocate the Trusted Memory Region (TMR) as a 2MB-sized/aligned region
when SNP is enabled to satisfy new requirements for SNP. Continue
allocating a 1MB-sized region for !SNP configuration.

  [ bp: Carve out TMR allocation into a helper. ]

Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
Co-developed-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
Signed-off-by: Michael Roth <michael.roth@amd.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Link: https://lore.kernel.org/r/20240126041126.1927228-17-michael.roth@amd.com
drivers/crypto/ccp/sev-dev.c
include/linux/psp-sev.h

index abee1a68d60946e147efab4464572b608243a228..70aabd1d3d5febc4c072ae78c610f8e5db71280e 100644 (file)
@@ -30,6 +30,7 @@
 #include <asm/smp.h>
 #include <asm/cacheflush.h>
 #include <asm/e820/types.h>
+#include <asm/sev.h>
 
 #include "psp-dev.h"
 #include "sev-dev.h"
@@ -73,9 +74,14 @@ static int psp_timeout;
  *   The TMR is a 1MB area that must be 1MB aligned.  Use the page allocator
  *   to allocate the memory, which will return aligned memory for the specified
  *   allocation order.
+ *
+ * When SEV-SNP is enabled the TMR needs to be 2MB aligned and 2MB sized.
  */
-#define SEV_ES_TMR_SIZE                (1024 * 1024)
+#define SEV_TMR_SIZE           (1024 * 1024)
+#define SNP_TMR_SIZE           (2 * 1024 * 1024)
+
 static void *sev_es_tmr;
+static size_t sev_es_tmr_size = SEV_TMR_SIZE;
 
 /* INIT_EX NV Storage:
  *   The NV Storage is a 32Kb area and must be 4Kb page aligned.  Use the page
@@ -192,17 +198,6 @@ static int sev_cmd_buffer_len(int cmd)
        return 0;
 }
 
-static void *sev_fw_alloc(unsigned long len)
-{
-       struct page *page;
-
-       page = alloc_pages(GFP_KERNEL, get_order(len));
-       if (!page)
-               return NULL;
-
-       return page_address(page);
-}
-
 static struct file *open_file_as_root(const char *filename, int flags, umode_t mode)
 {
        struct file *fp;
@@ -333,6 +328,142 @@ static int sev_write_init_ex_file_if_required(int cmd_id)
        return sev_write_init_ex_file();
 }
 
+/*
+ * snp_reclaim_pages() needs __sev_do_cmd_locked(), and __sev_do_cmd_locked()
+ * needs snp_reclaim_pages(), so a forward declaration is needed.
+ */
+static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret);
+
+static int snp_reclaim_pages(unsigned long paddr, unsigned int npages, bool locked)
+{
+       int ret, err, i;
+
+       paddr = __sme_clr(ALIGN_DOWN(paddr, PAGE_SIZE));
+
+       for (i = 0; i < npages; i++, paddr += PAGE_SIZE) {
+               struct sev_data_snp_page_reclaim data = {0};
+
+               data.paddr = paddr;
+
+               if (locked)
+                       ret = __sev_do_cmd_locked(SEV_CMD_SNP_PAGE_RECLAIM, &data, &err);
+               else
+                       ret = sev_do_cmd(SEV_CMD_SNP_PAGE_RECLAIM, &data, &err);
+
+               if (ret)
+                       goto cleanup;
+
+               ret = rmp_make_shared(__phys_to_pfn(paddr), PG_LEVEL_4K);
+               if (ret)
+                       goto cleanup;
+       }
+
+       return 0;
+
+cleanup:
+       /*
+        * If there was a failure reclaiming the page then it is no longer safe
+        * to release it back to the system; leak it instead.
+        */
+       snp_leak_pages(__phys_to_pfn(paddr), npages - i);
+       return ret;
+}
+
+static int rmp_mark_pages_firmware(unsigned long paddr, unsigned int npages, bool locked)
+{
+       unsigned long pfn = __sme_clr(paddr) >> PAGE_SHIFT;
+       int rc, i;
+
+       for (i = 0; i < npages; i++, pfn++) {
+               rc = rmp_make_private(pfn, 0, PG_LEVEL_4K, 0, true);
+               if (rc)
+                       goto cleanup;
+       }
+
+       return 0;
+
+cleanup:
+       /*
+        * Try unrolling the firmware state changes by
+        * reclaiming the pages which were already changed to the
+        * firmware state.
+        */
+       snp_reclaim_pages(paddr, i, locked);
+
+       return rc;
+}
+
+static struct page *__snp_alloc_firmware_pages(gfp_t gfp_mask, int order)
+{
+       unsigned long npages = 1ul << order, paddr;
+       struct sev_device *sev;
+       struct page *page;
+
+       if (!psp_master || !psp_master->sev_data)
+               return NULL;
+
+       page = alloc_pages(gfp_mask, order);
+       if (!page)
+               return NULL;
+
+       /* If SEV-SNP is initialized then add the page in RMP table. */
+       sev = psp_master->sev_data;
+       if (!sev->snp_initialized)
+               return page;
+
+       paddr = __pa((unsigned long)page_address(page));
+       if (rmp_mark_pages_firmware(paddr, npages, false))
+               return NULL;
+
+       return page;
+}
+
+void *snp_alloc_firmware_page(gfp_t gfp_mask)
+{
+       struct page *page;
+
+       page = __snp_alloc_firmware_pages(gfp_mask, 0);
+
+       return page ? page_address(page) : NULL;
+}
+EXPORT_SYMBOL_GPL(snp_alloc_firmware_page);
+
+static void __snp_free_firmware_pages(struct page *page, int order, bool locked)
+{
+       struct sev_device *sev = psp_master->sev_data;
+       unsigned long paddr, npages = 1ul << order;
+
+       if (!page)
+               return;
+
+       paddr = __pa((unsigned long)page_address(page));
+       if (sev->snp_initialized &&
+           snp_reclaim_pages(paddr, npages, locked))
+               return;
+
+       __free_pages(page, order);
+}
+
+void snp_free_firmware_page(void *addr)
+{
+       if (!addr)
+               return;
+
+       __snp_free_firmware_pages(virt_to_page(addr), 0, false);
+}
+EXPORT_SYMBOL_GPL(snp_free_firmware_page);
+
+static void *sev_fw_alloc(unsigned long len)
+{
+       struct page *page;
+
+       page = __snp_alloc_firmware_pages(GFP_KERNEL, get_order(len));
+       if (!page)
+               return NULL;
+
+       return page_address(page);
+}
+
 static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret)
 {
        struct psp_device *psp = psp_master;
@@ -456,7 +587,7 @@ static int __sev_init_locked(int *error)
                data.tmr_address = __pa(sev_es_tmr);
 
                data.flags |= SEV_INIT_FLAGS_SEV_ES;
-               data.tmr_len = SEV_ES_TMR_SIZE;
+               data.tmr_len = sev_es_tmr_size;
        }
 
        return __sev_do_cmd_locked(SEV_CMD_INIT, &data, error);
@@ -479,7 +610,7 @@ static int __sev_init_ex_locked(int *error)
                data.tmr_address = __pa(sev_es_tmr);
 
                data.flags |= SEV_INIT_FLAGS_SEV_ES;
-               data.tmr_len = SEV_ES_TMR_SIZE;
+               data.tmr_len = sev_es_tmr_size;
        }
 
        return __sev_do_cmd_locked(SEV_CMD_INIT_EX, &data, error);
@@ -623,9 +754,27 @@ static int __sev_snp_init_locked(int *error)
        sev->snp_initialized = true;
        dev_dbg(sev->dev, "SEV-SNP firmware initialized\n");
 
+       sev_es_tmr_size = SNP_TMR_SIZE;
+
        return rc;
 }
 
+static void __sev_platform_init_handle_tmr(struct sev_device *sev)
+{
+       if (sev_es_tmr)
+               return;
+
+       /* Obtain the TMR memory area for SEV-ES use */
+       sev_es_tmr = sev_fw_alloc(sev_es_tmr_size);
+       if (sev_es_tmr) {
+               /* Must flush the cache before giving it to the firmware */
+               if (!sev->snp_initialized)
+                       clflush_cache_range(sev_es_tmr, sev_es_tmr_size);
+       } else {
+                       dev_warn(sev->dev, "SEV: TMR allocation failed, SEV-ES support unavailable\n");
+       }
+}
+
 static int __sev_platform_init_locked(int *error)
 {
        int rc, psp_ret = SEV_RET_NO_FW_CALL;
@@ -639,16 +788,7 @@ static int __sev_platform_init_locked(int *error)
        if (sev->state == SEV_STATE_INIT)
                return 0;
 
-       if (!sev_es_tmr) {
-               /* Obtain the TMR memory area for SEV-ES use */
-               sev_es_tmr = sev_fw_alloc(SEV_ES_TMR_SIZE);
-               if (sev_es_tmr)
-                       /* Must flush the cache before giving it to the firmware */
-                       clflush_cache_range(sev_es_tmr, SEV_ES_TMR_SIZE);
-               else
-                       dev_warn(sev->dev,
-                                "SEV: TMR allocation failed, SEV-ES support unavailable\n");
-               }
+       __sev_platform_init_handle_tmr(sev);
 
        if (sev_init_ex_buffer) {
                rc = sev_read_init_ex_file();
@@ -1546,8 +1686,9 @@ static void sev_firmware_shutdown(struct sev_device *sev)
                /* The TMR area was encrypted, flush it from the cache */
                wbinvd_on_all_cpus();
 
-               free_pages((unsigned long)sev_es_tmr,
-                          get_order(SEV_ES_TMR_SIZE));
+               __snp_free_firmware_pages(virt_to_page(sev_es_tmr),
+                                         get_order(sev_es_tmr_size),
+                                         false);
                sev_es_tmr = NULL;
        }
 
index bcf9ceda030fc5974e0d517ae55c4bb888c234f5..84eabbfbbc0871b450082a307c1d375952fc4706 100644 (file)
@@ -932,6 +932,8 @@ int sev_guest_decommission(struct sev_data_decommission *data, int *error);
 int sev_do_cmd(int cmd, void *data, int *psp_ret);
 
 void *psp_copy_user_blob(u64 uaddr, u32 len);
+void *snp_alloc_firmware_page(gfp_t mask);
+void snp_free_firmware_page(void *addr);
 
 #else  /* !CONFIG_CRYPTO_DEV_SP_PSP */
 
@@ -959,6 +961,13 @@ sev_issue_cmd_external_user(struct file *filep, unsigned int id, void *data, int
 
 static inline void *psp_copy_user_blob(u64 __user uaddr, u32 len) { return ERR_PTR(-EINVAL); }
 
+static inline void *snp_alloc_firmware_page(gfp_t mask)
+{
+       return NULL;
+}
+
+static inline void snp_free_firmware_page(void *addr) { }
+
 #endif /* CONFIG_CRYPTO_DEV_SP_PSP */
 
 #endif /* __PSP_SEV_H__ */