Commit 24512afa authored by Brijesh Singh's avatar Brijesh Singh Committed by Borislav Petkov (AMD)
Browse files

crypto: ccp: Handle the legacy TMR allocation when SNP is enabled



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: default avatarBrijesh Singh <brijesh.singh@amd.com>
Co-developed-by: default avatarAshish Kalra <ashish.kalra@amd.com>
Signed-off-by: default avatarAshish Kalra <ashish.kalra@amd.com>
Signed-off-by: default avatarMichael Roth <michael.roth@amd.com>
Signed-off-by: default avatarBorislav Petkov (AMD) <bp@alien8.de>
Link: https://lore.kernel.org/r/20240126041126.1927228-17-michael.roth@amd.com
parent 8dac6429
Loading
Loading
Loading
Loading
+167 −26
Original line number Diff line number Diff line
@@ -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;
	}

+9 −0
Original line number Diff line number Diff line
@@ -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__ */