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

From: Peter Gonda
Date: Mon Nov 01 2021 - 15:18:47 EST


On Mon, Nov 1, 2021 at 12:41 PM Tom Lendacky <thomas.lendacky@xxxxxxx> wrote:
>
> On 11/1/21 12:21 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. If the file at
> > 'init_ex_path' does not exist the PSP will not be initialized. The user
> > must create the file prior to use with 32Kb of 0xFFs per spec.
> >
> > Signed-off-by: David Rientjes <rientjes@xxxxxxxxxx>
> > Co-developed-by: Peter Gonda <pgonda@xxxxxxxxxx>
> > Signed-off-by: Peter Gonda <pgonda@xxxxxxxxxx>
> > Reviewed-by: Marc Orr <marcorr@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: linux-crypto@xxxxxxxxxxxxxxx
> > Cc: linux-kernel@xxxxxxxxxxxxxxx
> > ---
> > .../virt/kvm/amd-memory-encryption.rst | 4 +
> > drivers/crypto/ccp/sev-dev.c | 185 ++++++++++++++++--
> > include/linux/psp-sev.h | 21 ++
> > 3 files changed, 196 insertions(+), 14 deletions(-)
> >
> > diff --git a/Documentation/virt/kvm/amd-memory-encryption.rst b/Documentation/virt/kvm/amd-memory-encryption.rst
> > index 5c081c8c7164..6d906a47e568 100644
> > --- a/Documentation/virt/kvm/amd-memory-encryption.rst
> > +++ b/Documentation/virt/kvm/amd-memory-encryption.rst
> > @@ -84,6 +84,10 @@ guests, such as launching, running, snapshotting, migrating and decommissioning.
> >
> > The KVM_SEV_INIT command is used by the hypervisor to initialize the SEV platform
> > context. In a typical workflow, this command should be the first command issued.
>
> Add a blank line here.
>
> > +The AMD-SP can be initialized either by using its own non-volatile storage or
>
> Just to be consistent within the document, s/AMD-SP/firmware/
>
> > +the system can manage the NV storage for the AMD-SP using the module parameter
>
> s/system/OS/
> s/AMD-SP/firmware/
>
> > +``init_ex_path``. This file must exist, to create a new NV storage file allocate
>
> s/This file must exist/The file specified by ``init_ex_path`` must exist./
> s/, to create/ To create/
>
> > +a the file with 32Kb of 0xFF as required by the SEV FW spec.
>
> s/a the/the/
> s/32Kb/32KB bytes/
>
> Just to be consistent within the document, s/SEV FW spec/SEV spec/

Updated documentation.

>
> >
> > Returns: 0 on success, -negative on error
> >
> > diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
> > index 00ca74dd7b3c..1bbb9c3dd1ce 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 * 1024)
> > +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;
> > @@ -107,6 +120,7 @@ static int sev_cmd_buffer_len(int cmd)
> > {
> > switch (cmd) {
> > case SEV_CMD_INIT: return sizeof(struct sev_data_init);
> > + case SEV_CMD_INIT_EX: return sizeof(struct sev_data_init_ex);
> > case SEV_CMD_PLATFORM_STATUS: return sizeof(struct sev_user_data_status);
> > case SEV_CMD_PEK_CSR: return sizeof(struct sev_data_pek_csr);
> > case SEV_CMD_PEK_CERT_IMPORT: return sizeof(struct sev_data_pek_cert_import);
> > @@ -152,6 +166,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)) {
> > + const int ret = PTR_ERR(fp);
>
> I don't think you need the "const" here.

Sounds good, removed. I normally default to consting a variable if I
don't expect/want it to change. What guidance should I be following
here?

>
> > +
> > + dev_err(psp_master->dev,
> > + "sev could not open file for read, error %d\n",
> > + ret);
>
> Maybe "SEV: could not open %s for read, ret=%d\n"
>
> > + return ret;
> > + }
> > +
> > + nread = kernel_read(fp, sev_init_ex_nv_address, NV_LENGTH, NULL);
>
> kernel_read can return an error, you should check nread before continuing.
>
> > + dev_dbg(psp_master->dev, "sev NV read %d bytes\n", nread);
>
> "SEV: read %ld bytes from NV file\n"
>
> > + filp_close(fp, NULL);
> > +
> > + return 0;
> > +}
> > +
> > +static int sev_write_nv_memory(void)
>
> The return code is never checked by the caller. Is it expected that any
> error is not supposed to stop SEV for the current boot? Should you make
> this void, then?

Changed to 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");
>
> You should output a message similar to what is in sev_read_nv_memory().
>
> > + return PTR_ERR(fp);
> > + }
> > +
> > + ret = kernel_write(fp, sev_init_ex_nv_address, NV_LENGTH, &offset);
>
> kernel_write returns a ssize_t, but ret is an int. and maybe use nwrite
> similar to nread in sev_read_nv_memory()?

Done.
>
> > + 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",
>
> "SEV: failed to write %u bytes to NV file, ret=%ld\n"
>
> > + NV_LENGTH, ret); > + if (ret >= 0)
> > + return -EIO;
> > + return ret;
> > + }
> > +
> > + dev_dbg(sev->dev, "wrote to non volatile memory area\n");
>
> "SEV: write successful to NV file\n"

Updated all messages. Should have noted the "SEV: .." format.

>
> > +
> > + return 0;
> > +}
> > +
> > +static void sev_write_nv_memory_if_required(int cmd_id)
> > +{
> > + 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(_EX), PLATFORM_RESET, PEK_GEN,
> > + * PEK_CERT_IMPORT, and PDH_GEN do.
> > + */
> > + 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:
> > + break;
> > + default:
> > + return;
> > + };
> > +
> > + sev_write_nv_memory();
> > +}
> > +
> > static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret)
> > {
> > struct psp_device *psp = psp_master;
> > @@ -221,6 +318,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,
> > @@ -247,22 +346,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;
> >
> > @@ -277,7 +396,27 @@ static int __sev_platform_init_locked(int *error)
> > data.tmr_len = SEV_ES_TMR_SIZE;
> > }
> >
> > - rc = __sev_do_cmd_locked(SEV_CMD_INIT, &data, error);
> > + 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);
> > +
> > + if (!psp || !psp->sev_data)
> > + return -ENODEV;
> > +
> > + sev = psp->sev_data;
> > +
> > + if (sev->state == SEV_STATE_INIT)
> > + return 0;
> > +
> > + init_function = sev_init_ex_nv_address ? __sev_init_ex_locked :
> > + __sev_init_locked;
> > + rc = init_function(error);
> > if (rc && *error == SEV_RET_SECURE_DATA_INVALID) {
> > /*
> > * INIT command returned an integrity check failure
> > @@ -286,8 +425,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_notice(sev->dev, "SEV: retrying INIT command");
> > + rc = init_function(error);
> > }
> >
> > if (rc)
> > @@ -303,7 +442,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)
> > @@ -1057,6 +1196,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)
> > @@ -1101,11 +1246,23 @@ 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_err(sev->dev,
> > + "SEV: INIT_EX NV storage allocation failed, INIT-EX support unavailable\n");
>
> Since this is a file, maybe s/storage/memory/ ?

Done.

>
> At this point, SEV will fail to initialize, it won't fall back to the INIT
> support. So I think the ", INIT-EX support unavailable" can be removed
> from the message.
>
> > + 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)
> > dev_warn(sev->dev,
> > - "SEV: TMR allocation failed, SEV-ES support unavailable\n");
> > + "SEV: TMR allocation failed\n");
>
> This message shouldn't be changed.

Oops I deleted the wrong "... support unavailable". Fixed.
>
>
> I should have made some of these comments on the first version, sorry
> about that.

No worries! I appreciate the reviews.

>
> Thanks,
> Tom
>
> >
> > /* Initialize the platform */
> > rc = sev_platform_init(&error);
> > 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
> >
> > /**
> >