Re: [PATCH 4/4] crypto: ccp - Add SEV_INIT_EX support

From: Peter Gonda
Date: Fri Oct 29 2021 - 13:26:27 EST


On Fri, Oct 29, 2021 at 8:45 AM Tom Lendacky <thomas.lendacky@xxxxxxx> wrote:
>
> On 10/28/21 12:57 PM, Peter Gonda wrote:
> > From: David Rientjes <rientjes@xxxxxxxxxx>
> >
> > Add new module parameter to allow users to use SEV_INIT_EX instead of
> > SEV_INIT. This helps users who lock their SPI bus to use the PSP for SEV
> > functionality. The 'init_ex_path' parameter defaults to NULL which means
> > the kernel will use SEV_INIT, if a path is specified SEV_INIT_EX will be
> > used with the data found at the path. On certain PSP commands this
> > file is written to as the PSP updates the NV memory region. Depending on
> > file system initialization this file open may fail during module init
> > but the CCP driver for SEV already has sufficient retries for platform
> > initialization. During normal operation of PSP system and SEV commands
> > if the PSP has not been initialized it is at run time.
>
> IIUC, it looks as though the file has to exist before the very first use,
> otherwise the initialization will fail. Did you consider checking for the
> presence of the file first and, if not there, just using a memory area of
> all f's (as documented in the SEV API)? Then on successful INIT, the
> memory would be written and the file created.

Thats a great idea. I'll add that functionality.

>
> Either way, probably worth adding to the commit message. And if you stay
> with having to pre-allocate the file, it's worth adding to the SEV
> documentation what is required to be done to initialize the file.
>
> Although, INIT_EX is probably worth adding to the SEV documentation in
> general.

Is this (https://www.kernel.org/doc/Documentation/virt/kvm/amd-memory-encryption.rst)
the documentation you were referring too? If so I can add another
patch before this one to document INIT, then add the INIT_EX
documentation here. Or were you thinking something else?

>
> >
> > Signed-off-by: David Rientjes <rientjes@xxxxxxxxxx>
> > Co-developed-by: Peter Gonda <pgonda@xxxxxxxxxx>
> > Signed-off-by: Peter Gonda <pgonda@xxxxxxxxxx>
> > Cc: Tom Lendacky <thomas.lendacky@xxxxxxx>
> > Cc: Brijesh Singh <brijesh.singh@xxxxxxx>
> > Cc: Marc Orr <marcorr@xxxxxxxxxx>
> > Cc: Joerg Roedel <jroedel@xxxxxxx>
> > Cc: Herbert Xu <herbert@xxxxxxxxxxxxxxxxxxx>
> > Cc: David Rientjes <rientjes@xxxxxxxxxx>
> > Cc: John Allen <john.allen@xxxxxxx>
> > Cc: "David S. Miller" <davem@xxxxxxxxxxxxx>
> > Cc: Paolo Bonzini <pbonzini@xxxxxxxxxx> (
> > Cc: linux-crypto@xxxxxxxxxxxxxxx
> > Cc: linux-kernel@xxxxxxxxxxxxxxx
> > ---
> > drivers/crypto/ccp/sev-dev.c | 186 ++++++++++++++++++++++++++++++++---
> > include/linux/psp-sev.h | 21 ++++
> > 2 files changed, 192 insertions(+), 15 deletions(-)
> >
> > diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
> > index b568ae734857..c8718b4cbc93 100644
> > --- a/drivers/crypto/ccp/sev-dev.c
> > +++ b/drivers/crypto/ccp/sev-dev.c
> > @@ -22,6 +22,7 @@
> > #include <linux/firmware.h>
> > #include <linux/gfp.h>
> > #include <linux/cpufeature.h>
> > +#include <linux/fs.h>
> >
> > #include <asm/smp.h>
> >
> > @@ -43,6 +44,10 @@ static int psp_probe_timeout = 5;
> > module_param(psp_probe_timeout, int, 0644);
> > MODULE_PARM_DESC(psp_probe_timeout, " default timeout value, in seconds, during PSP device probe");
> >
> > +static char *init_ex_path;
> > +module_param(init_ex_path, charp, 0660);
> > +MODULE_PARM_DESC(init_ex_path, " Path for INIT_EX data; if set try INIT_EX");
> > +
> > MODULE_FIRMWARE("amd/amd_sev_fam17h_model0xh.sbin"); /* 1st gen EPYC */
> > MODULE_FIRMWARE("amd/amd_sev_fam17h_model3xh.sbin"); /* 2nd gen EPYC */
> > MODULE_FIRMWARE("amd/amd_sev_fam19h_model0xh.sbin"); /* 3rd gen EPYC */
> > @@ -58,6 +63,14 @@ static int psp_timeout;
> > #define SEV_ES_TMR_SIZE (1024 * 1024)
> > static void *sev_es_tmr;
> >
> > +/* INIT_EX NV Storage:
> > + * The NV Storage is a 32Kb area and must be 4Kb page aligned. Use the page
> > + * allocator to allocate the memory, which will return aligned memory for the
> > + * specified allocation order.
> > + */
> > +#define NV_LENGTH (32 << 10)
>
> Just me, but I think '32 * 1024' would be a bit clearer.

SGTM and more consistent with SEV_ES_TMR_SIZE.

>
> > +static void *sev_init_ex_nv_address;
> > +
> > static inline bool sev_version_greater_or_equal(u8 maj, u8 min)
> > {
> > struct sev_device *sev = psp_master->sev_data;
> > @@ -135,6 +148,7 @@ static int sev_cmd_buffer_len(int cmd)
> > case SEV_CMD_GET_ID: return sizeof(struct sev_data_get_id);
> > case SEV_CMD_ATTESTATION_REPORT: return sizeof(struct sev_data_attestation_report);
> > case SEV_CMD_SEND_CANCEL: return sizeof(struct sev_data_send_cancel);
> > + case SEV_CMD_INIT_EX: return sizeof(struct sev_data_init_ex);
>
> Maybe move this to just under the SEV_CMD_INIT: case statement?
>
> > default: return 0;
> > }
> >
> > @@ -156,6 +170,89 @@ static void *sev_fw_alloc(unsigned long len)
> > return page_address(page);
> > }
> >
> > +static int sev_read_nv_memory(void)
> > +{
> > + struct file *fp;
> > + ssize_t nread;
> > +
> > + if (!sev_init_ex_nv_address)
> > + return -EOPNOTSUPP;
> > +
> > + fp = filp_open(init_ex_path, O_RDONLY, 0);
> > + if (IS_ERR(fp)) {
> > + dev_err(psp_master->dev, "sev could not open file for read\n");
> > + return PTR_ERR(fp);
> > + }
> > +
> > + nread = kernel_read(fp, sev_init_ex_nv_address, NV_LENGTH, 0);
> > + dev_dbg(psp_master->dev, "sev NV read %d bytes\n", nread);
> > + filp_close(fp, NULL);
>
> Add a blank line here.
>
> > + return 0;
> > +}
> > +
> > +static int sev_write_nv_memory(void)
> > +{
> > + struct sev_device *sev = psp_master->sev_data;
> > + struct file *fp;
> > + loff_t offset = 0;
> > + int ret;
> > +
> > + if (!sev_init_ex_nv_address)
> > + return -EOPNOTSUPP;
> > +
> > + fp = filp_open(init_ex_path, O_CREAT | O_WRONLY, 0600);
> > + if (IS_ERR(fp)) {
> > + dev_err(sev->dev, "sev NV data could not be created\n");
> > + return PTR_ERR(fp);
> > + }
>
> Add a blank line here.
>
> > + ret = kernel_write(fp, sev_init_ex_nv_address, NV_LENGTH, &offset);
> > + vfs_fsync(fp, 0);
> > + filp_close(fp, NULL);
> > +
> > + if (ret != NV_LENGTH) {
> > + dev_err(sev->dev,
> > + "failed to write %d bytes to non volatile memory area, ret=%lu\n",
> > + NV_LENGTH, ret);
> > + if (ret >= 0)
> > + return -EIO;
> > + return ret;
> > + }
> > +
> > + dev_dbg(sev->dev, "wrote to non volatile memory area\n");
>
> Add a blank line here.

Added all these blank lines.

>
> > + return 0;
> > +}
> > +
> > +static void sev_write_nv_memory_if_required(int cmd_id)
> > +{
> > + struct sev_device *sev = psp_master->sev_data;
> > + int ret;
> > +
> > + if (!sev_init_ex_nv_address)
> > + return;
> > +
> > + /*
> > + * Only a few platform commands modify the SPI/NV area,
> > + * but none of the non-platform commands do. Only INIT,
>
> maybe say INIT(_EX)?

Done.

>
> > + * PLATFORM_RESET, PEK_GEN, PEK_CERT_IMPORT, and
> > + * PDH_GEN do.
>
> Does SHUTDOWN modify the SPI/NV area? Otherwise a separate comment about
> why it is included below.

Double checked the FW doc looks like it does not. Richard can you
confirm? If it doesn't I'll remove this case.

>
> > + */
> > + switch (cmd_id) {
> > + case SEV_CMD_FACTORY_RESET:
> > + case SEV_CMD_INIT_EX:
> > + case SEV_CMD_PDH_GEN:
> > + case SEV_CMD_PEK_CERT_IMPORT:
> > + case SEV_CMD_PEK_GEN:
> > + case SEV_CMD_SHUTDOWN:
> > + break;
> > + default:
> > + return;
> > + };
> > +
> > + ret = sev_write_nv_memory();
> > + if (ret)
> > + dev_err(sev->dev, "sev NV write failed %d\n", ret);
>
> You already have error messages in the sev_write_nv_memory() function,
> this one probably isn't needed.

Removed.

>
> > +}
> > +
> > static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret)
> > {
> > struct psp_device *psp = psp_master;
> > @@ -225,6 +322,8 @@ static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret)
> > dev_dbg(sev->dev, "sev command %#x failed (%#010x)\n",
> > cmd, reg & PSP_CMDRESP_ERR_MASK);
> > ret = -EIO;
> > + } else {
> > + sev_write_nv_memory_if_required(cmd);
> > }
> >
> > print_hex_dump_debug("(out): ", DUMP_PREFIX_OFFSET, 16, 2, data,
> > @@ -251,22 +350,42 @@ static int sev_do_cmd(int cmd, void *data, int *psp_ret)
> > return rc;
> > }
> >
> > -static int __sev_platform_init_locked(int *error)
> > +static int __sev_init_locked(int *error)
> > {
> > - struct psp_device *psp = psp_master;
> > struct sev_data_init data;
> > - struct sev_device *sev;
> > - int rc = 0;
> >
> > - if (!psp || !psp->sev_data)
> > - return -ENODEV;
> > + memset(&data, 0, sizeof(data));
> > + if (sev_es_tmr) {
> > + u64 tmr_pa;
> >
> > - sev = psp->sev_data;
> > + /*
> > + * Do not include the encryption mask on the physical
> > + * address of the TMR (firmware should clear it anyway).
> > + */
> > + tmr_pa = __pa(sev_es_tmr);
> >
> > - if (sev->state == SEV_STATE_INIT)
> > - return 0;
> > + data.flags |= SEV_INIT_FLAGS_SEV_ES;
> > + data.tmr_address = tmr_pa;
> > + data.tmr_len = SEV_ES_TMR_SIZE;
> > + }
> > +
> > + return __sev_do_cmd_locked(SEV_CMD_INIT, &data, error);
> > +}
> > +
> > +static int __sev_init_ex_locked(int *error)
> > +{
> > + struct sev_data_init_ex data;
> > + int ret;
> >
> > memset(&data, 0, sizeof(data));
> > + data.length = sizeof(data);
> > + data.nv_address = __psp_pa(sev_init_ex_nv_address);
> > + data.nv_len = NV_LENGTH;
> > +
> > + ret = sev_read_nv_memory();
> > + if (ret)
> > + return ret;
> > +
> > if (sev_es_tmr) {
> > u64 tmr_pa;
> >
> > @@ -276,12 +395,30 @@ static int __sev_platform_init_locked(int *error)
> > */
> > tmr_pa = __pa(sev_es_tmr);
> >
> > - data.flags |= SEV_INIT_FLAGS_SEV_ES;
>
> Inadvertant deletion?

Oops only testing with SEV guests. Will test with ES too. Fixed.

>
> > data.tmr_address = tmr_pa;
> > data.tmr_len = SEV_ES_TMR_SIZE;
> > }
>
> Add a blank line here.
>
> > + return __sev_do_cmd_locked(SEV_CMD_INIT_EX, &data, error);
> > +}
> > +
> > +static int __sev_platform_init_locked(int *error)
> > +{
> > + struct psp_device *psp = psp_master;
> > + struct sev_device *sev;
> > + int rc;
> > + int (*init_function)(int *error) = sev_init_ex_nv_address ?
> > + __sev_init_ex_locked :
> > + __sev_init_locked;
>
> This seems a bit much in the declaration. How about moving the assignment
> down to just before the first call?

Done.

>
> > +
> > + if (!psp || !psp->sev_data)
> > + return -ENODEV;
> > +
> > + sev = psp->sev_data;
> >
> > - rc = __sev_do_cmd_locked(SEV_CMD_INIT, &data, error);
> > + if (sev->state == SEV_STATE_INIT)
> > + return 0;
> > +
> > + rc = init_function(error);
> > if (rc && *error == SEV_RET_SECURE_DATA_INVALID) {
> > /*
> > * INIT command returned an integrity check failure
> > @@ -290,8 +427,8 @@ static int __sev_platform_init_locked(int *error)
> > * failed and persistent state has been erased.
> > * Retrying INIT command here should succeed.
> > */
> > - dev_dbg(sev->dev, "SEV: retrying INIT command");
> > - rc = __sev_do_cmd_locked(SEV_CMD_INIT, &data, error);
> > + dev_err(sev->dev, "SEV: retrying INIT command");
>
> Maybe dev_notice() instead of dev_err()?

Done.,

>
> > + rc = init_function(error);
> > }
> >
> > if (rc)
> > @@ -307,7 +444,7 @@ static int __sev_platform_init_locked(int *error)
> >
> > dev_dbg(sev->dev, "SEV firmware initialized\n");
> >
> > - return rc;
> > + return 0;
> > }
> >
> > int sev_platform_init(int *error)
> > @@ -987,7 +1124,7 @@ static int sev_misc_init(struct sev_device *sev)
> >
> > init_waitqueue_head(&sev->int_queue);
> > sev->misc = misc_dev;
> > - dev_dbg(dev, "registered SEV device\n");
> > + dev_err(dev, "registered SEV device\n");
>
> Not sure this is a necessary change... but, if you don't want this as a
> dev_dbg() then it should be a dev_info(), because it is not an error.

Oops this was my debugging. Reverted.

>
> >
> > return 0;
> > }
> > @@ -1061,6 +1198,12 @@ static void sev_firmware_shutdown(struct sev_device *sev)
> > get_order(SEV_ES_TMR_SIZE));
> > sev_es_tmr = NULL;
> > }
> > +
> > + if (sev_init_ex_nv_address) {
> > + free_pages((unsigned long)sev_init_ex_nv_address,
> > + get_order(NV_LENGTH));
> > + sev_init_ex_nv_address = NULL;
> > + }
> > }
> >
> > void sev_dev_destroy(struct psp_device *psp)
> > @@ -1105,6 +1248,19 @@ void sev_pci_init(void)
> > 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_nv_address = sev_fw_alloc(NV_LENGTH);
> > + if (!sev_init_ex_nv_address) {
> > + dev_warn(
>
> Shouldn't this be a dev_err(), since you are erroring out?
>
> > + sev->dev,
>
> Move this up to the previous line, i.e.: dev_err(sev->dev,
>
> > + "SEV: INIT_EX NV storage allocation failed, INIT-EX support unavailable\n");
>
> Since you're erroring out, probably enough to just have the first part of
> that message. But if not:
>
> s/INIT-EX/INIT_EX/

Fixed this log line.

>
> Thanks,
> Tom
>
> > + goto err;
> > + }
> > + }
> > +
> > /* Obtain the TMR memory area for SEV-ES use */
> > sev_es_tmr = sev_fw_alloc(SEV_ES_TMR_SIZE);
> > if (!sev_es_tmr)
> > diff --git a/include/linux/psp-sev.h b/include/linux/psp-sev.h
> > index d48a7192e881..1595088c428b 100644
> > --- a/include/linux/psp-sev.h
> > +++ b/include/linux/psp-sev.h
> > @@ -52,6 +52,7 @@ enum sev_cmd {
> > SEV_CMD_DF_FLUSH = 0x00A,
> > SEV_CMD_DOWNLOAD_FIRMWARE = 0x00B,
> > SEV_CMD_GET_ID = 0x00C,
> > + SEV_CMD_INIT_EX = 0x00D,
> >
> > /* Guest commands */
> > SEV_CMD_DECOMMISSION = 0x020,
> > @@ -102,6 +103,26 @@ struct sev_data_init {
> > u32 tmr_len; /* In */
> > } __packed;
> >
> > +/**
> > + * struct sev_data_init_ex - INIT_EX command parameters
> > + *
> > + * @length: len of the command buffer read by the PSP
> > + * @flags: processing flags
> > + * @tmr_address: system physical address used for SEV-ES
> > + * @tmr_len: len of tmr_address
> > + * @nv_address: system physical address used for PSP NV storage
> > + * @nv_len: len of nv_address
> > + */
> > +struct sev_data_init_ex {
> > + u32 length; /* In */
> > + u32 flags; /* In */
> > + u64 tmr_address; /* In */
> > + u32 tmr_len; /* In */
> > + u32 reserved; /* In */
> > + u64 nv_address; /* In/Out */
> > + u32 nv_len; /* In */
> > +} __packed;
> > +
> > #define SEV_INIT_FLAGS_SEV_ES 0x01
> >
> > /**
> >