lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAAH4kHYqQAkUO8phdQaE=R0qHZjKBB1uXsKR3Nq5yJxeZS-o=A@mail.gmail.com>
Date: Mon, 4 Nov 2024 11:30:30 -0800
From: Dionna Amalie Glaze <dionnaglaze@...gle.com>
To: Tom Lendacky <thomas.lendacky@....com>
Cc: linux-kernel@...r.kernel.org, x86@...nel.org, 
	John Allen <john.allen@....com>, Herbert Xu <herbert@...dor.apana.org.au>, 
	"David S. Miller" <davem@...emloft.net>, Ashish Kalra <ashish.kalra@....com>, 
	Sean Christopherson <seanjc@...gle.com>, Paolo Bonzini <pbonzini@...hat.com>, 
	Thomas Gleixner <tglx@...utronix.de>, Ingo Molnar <mingo@...hat.com>, Borislav Petkov <bp@...en8.de>, 
	Dave Hansen <dave.hansen@...ux.intel.com>, Michael Roth <michael.roth@....com>, 
	Luis Chamberlain <mcgrof@...nel.org>, Russ Weight <russ.weight@...ux.dev>, 
	Danilo Krummrich <dakr@...hat.com>, Greg Kroah-Hartman <gregkh@...uxfoundation.org>, 
	"Rafael J. Wysocki" <rafael@...nel.org>, Tianfei zhang <tianfei.zhang@...el.com>, 
	Alexey Kardashevskiy <aik@....com>, linux-crypto@...r.kernel.org
Subject: Re: [PATCH v3 3/4] crypto: ccp: Add SNP firmware hotload support

On Mon, Nov 4, 2024 at 9:06 AM Tom Lendacky <thomas.lendacky@....com> wrote:
>
> On 11/1/24 19:08, Dionna Glaze wrote:
> > uapi changes: The status values added are defined in AMD's SEV-SNP API
> > documentation as new error modes for the DOWNLOAD_FIRMWARE_EX command.
> >
> > On init, the ccp device will make /sys/class/firmware/amd/loading etc
> > firmware upload API attributes available to late-load a SEV-SNP firmware
> > binary.
> >
> > The firmware upload api errors reported are actionable in the following
> > ways:
> > * FW_UPLOAD_ERR_HW_ERROR: the machine is in an unstable state and must
> >   be reset.
> > * FW_UPLOAD_ERR_RW_ERROR: the firmware update went bad but can be
> >   recovered by hotloading the previous firmware version.
> >   Also used in the case that the kernel used the API wrong (bug).
> > * FW_UPLOAD_ERR_FW_INVALID: user error with the data provided, but no
> >   instability is expected and no recovery actions are needed.
> > * FW_UPLOAD_ERR_BUSY: upload attempted at a bad time either due to
> >   overload or the machine is in the wrong platform state.
> >
> > snp_asid_to_gctx_page_map:
> > Compliance with SEV-SNP API section 3.3 Firmware Updates and 4.1.1
> > Live Update: before a firmware is committed, all active GCTX pages
> > should be updated with SNP_GUEST_STATUS to ensure their data structure
> > remains consistent for the new firmware version.
> > There can only be cpuid_edx(0x8000001f)-1 many SEV-SNP asids in use at
> > one time, so this map associates asid to gctx in order to track which
> > addresses are active gctx pages that need updating. When an asid and
> > gctx page are decommissioned, the page is removed from tracking for
> > update-purposes.
> > Given that GCTX page creation and binding through the SNP_ACTIVATE
> > command are separate, the creation operation also tracks pages that are
> > yet to be bound to an asid.
> >
> > synthetic_restore_required:
> > Instead of tracking the status of whether an individual GCTX is safe for
> > use in a firmware command, force all following commands to fail with an
> > error that is indicative of needing a firmware rollback.
> >
> > According to SEV-SNP API section 3.3 Firmware Updates, the
> > DOWNLOAD_FIRMWARE_EX command is usable when the SNP firmware is in
> > either UNINIT or INIT states, but SEV-legacy firmware must be in the
> > UNINIT state. A sev_platform_init call when only SEV-SNP VMs are
> > expected should set supports_download_firmware_ex to delay legacy
> > initialization actions.
> > The firmware can only be hotloaded for SEV VMs when legacy
> > initialization has not happened.
> >
> > To test:
> > 1. Build the kernel enabling SEV-SNP as normal and add CONFIG_FW_UPLOAD=y.
> > 2. Add the following to your kernel_cmdline: ccp.psp_init_on_probe=0.
> > 3.Get an AMD SEV-SNP firmware sbin appropriate to your Epyc chip model at
> > https://www.amd.com/en/developer/sev.html and extract to get a .sbin
> > file.
> > 4. Run the following with your sbinfile in FW:
> >
> > echo 1 > /sys/class/firmware/snp_dlfw_ex/loading
> > cat "${FW?}" > /sys/class/firmware/snp_dlfw_ex/data
> > echo 0 > /sys/class/firmware/snp_dlfw_ex/loading
> >
> > 5. Verify the firmware update message in dmesg.
> >
> > CC: Sean Christopherson <seanjc@...gle.com>
> > CC: Paolo Bonzini <pbonzini@...hat.com>
> > CC: Thomas Gleixner <tglx@...utronix.de>
> > CC: Ingo Molnar <mingo@...hat.com>
> > CC: Borislav Petkov <bp@...en8.de>
> > CC: Dave Hansen <dave.hansen@...ux.intel.com>
> > CC: Ashish Kalra <ashish.kalra@....com>
> > CC: Tom Lendacky <thomas.lendacky@....com>
> > CC: John Allen <john.allen@....com>
> > CC: Herbert Xu <herbert@...dor.apana.org.au>
> > CC: "David S. Miller" <davem@...emloft.net>
> > CC: Michael Roth <michael.roth@....com>
> > CC: Luis Chamberlain <mcgrof@...nel.org>
> > CC: Russ Weight <russ.weight@...ux.dev>
> > CC: Danilo Krummrich <dakr@...hat.com>
> > CC: Greg Kroah-Hartman <gregkh@...uxfoundation.org>
> > CC: "Rafael J. Wysocki" <rafael@...nel.org>
> > CC: Tianfei zhang <tianfei.zhang@...el.com>
> > CC: Alexey Kardashevskiy <aik@....com>
> >
> > Co-developed-by: Ashish Kalra <ashish.kalra@....com>
> > Signed-off-by: Ashish Kalra <ashish.kalra@....com>
> > Tested-by: Ashish Kalra <ashish.kalra@....com>
> > Signed-off-by: Dionna Glaze <dionnaglaze@...gle.com>
> > ---
> >  drivers/crypto/ccp/Kconfig   |   2 +
> >  drivers/crypto/ccp/sev-dev.c | 399 ++++++++++++++++++++++++++++++++++-
> >  drivers/crypto/ccp/sev-dev.h |  14 ++
> >  include/linux/psp-sev.h      |  29 +++
> >  include/uapi/linux/psp-sev.h |   5 +
> >  5 files changed, 441 insertions(+), 8 deletions(-)
>
> This is a big patch. Can you split this up into smaller patches? At a
> minimum, one patch that adds the context tracking and one that adds the
> firmware support.
>

Can do. Would it be appropriate to also add a sysfs attribute group
for sev that makes the firmware version more easily readable? Or
should I put that in a follow-up chain?
The dmesg log about the firmware version isn't the best for writing
automation for managing firmware hotloading at scale, and we're better
set up for working with sysfs generally.

> Also, can the guest context page tracking and new firmware related
> functions be placed in a new file, sev-fw.c or similar, that is
> conditionally built based on CONFIG_FW_UPLOAD. Then a header file can be
> used to provide the different implementations of the new functions
> removing the #ifdefs from the code.
>
> Thanks,
> Tom
>
> >
> > diff --git a/drivers/crypto/ccp/Kconfig b/drivers/crypto/ccp/Kconfig
> > index f394e45e11ab4..cd9b667640e7c 100644
> > --- a/drivers/crypto/ccp/Kconfig
> > +++ b/drivers/crypto/ccp/Kconfig
> > @@ -39,6 +39,8 @@ config CRYPTO_DEV_SP_PSP
> >       bool "Platform Security Processor (PSP) device"
> >       default y
> >       depends on CRYPTO_DEV_CCP_DD && X86_64 && AMD_IOMMU
> > +     select FW_LOADER
> > +        select FW_UPLOAD
> >       help
> >        Provide support for the AMD Platform Security Processor (PSP).
> >        The PSP is a dedicated processor that provides support for key
> > diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c
> > index 9810edbb272d2..1da183e8a9ebc 100644
> > --- a/drivers/crypto/ccp/sev-dev.c
> > +++ b/drivers/crypto/ccp/sev-dev.c
> > @@ -223,6 +223,7 @@ static int sev_cmd_buffer_len(int cmd)
> >       case SEV_CMD_SNP_GUEST_REQUEST:         return sizeof(struct sev_data_snp_guest_request);
> >       case SEV_CMD_SNP_CONFIG:                return sizeof(struct sev_user_data_snp_config);
> >       case SEV_CMD_SNP_COMMIT:                return sizeof(struct sev_data_snp_commit);
> > +     case SEV_CMD_SNP_DOWNLOAD_FIRMWARE_EX:  return sizeof(struct sev_data_download_firmware_ex);
> >       default:                                return 0;
> >       }
> >
> > @@ -829,6 +830,7 @@ static int snp_reclaim_cmd_buf(int cmd, void *cmd_buf)
> >       return 0;
> >  }
> >
> > +static void snp_cmd_bookkeeping_locked(int cmd, struct sev_device *sev, void *data);
> >  static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret)
> >  {
> >       struct cmd_buf_desc desc_list[CMD_BUF_DESC_MAX] = {0};
> > @@ -852,6 +854,15 @@ static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret)
> >       if (WARN_ON_ONCE(!data != !buf_len))
> >               return -EINVAL;
> >
> > +     /* The firmware does not coordinate all GCTX page updates. Force an
> > +      * error to ensure that no new VM can be started and no active VM makes
> > +      * progress on guest requests until the firmware is rolled back.
> > +      */
> > +     if (sev->synthetic_restore_required) {
> > +             *psp_ret = SEV_RET_RESTORE_REQUIRED;
> > +             return -EBUSY;
> > +     }
> > +
> >       /*
> >        * Copy the incoming data to driver's scratch buffer as __pa() will not
> >        * work for some memory, e.g. vmalloc'd addresses, and @data may not be
> > @@ -982,6 +993,9 @@ static int __sev_do_cmd_locked(int cmd, void *data, int *psp_ret)
> >       print_hex_dump_debug("(out): ", DUMP_PREFIX_OFFSET, 16, 2, data,
> >                            buf_len, false);
> >
> > +     if (!ret)
> > +             snp_cmd_bookkeeping_locked(cmd, sev, data);
> > +
> >       return ret;
> >  }
> >
> > @@ -1082,6 +1096,7 @@ static int snp_filter_reserved_mem_regions(struct resource *rs, void *arg)
> >       return 0;
> >  }
> >
> > +static int sev_snp_platform_init_firmware_upload(struct sev_device *sev);
> >  static int __sev_snp_init_locked(int *error)
> >  {
> >       struct psp_device *psp = psp_master;
> > @@ -1179,6 +1194,8 @@ static int __sev_snp_init_locked(int *error)
> >
> >       sev_es_tmr_size = SNP_TMR_SIZE;
> >
> > +     rc = sev_snp_platform_init_firmware_upload(sev);
> > +
> >       return rc;
> >  }
> >
> > @@ -1326,6 +1343,13 @@ static int _sev_platform_init_locked(struct sev_platform_init_args *args)
> >       if (args->probe && !psp_init_on_probe)
> >               return 0;
> >
> > +     /*
> > +      * Defer legacy SEV/SEV-ES support if legacy VMs are not being launched
> > +      * and module psp_init_on_probe parameter is false.
> > +      */
> > +     if (args->supports_download_firmware_ex && !psp_init_on_probe)
> > +             return 0;
> > +
> >       return __sev_platform_init_locked(&args->error);
> >  }
> >
> > @@ -1592,14 +1616,8 @@ static int sev_update_firmware(struct device *dev)
> >               return -1;
> >       }
> >
> > -     /*
> > -      * SEV FW expects the physical address given to it to be 32
> > -      * byte aligned. Memory allocated has structure placed at the
> > -      * beginning followed by the firmware being passed to the SEV
> > -      * FW. Allocate enough memory for data structure + alignment
> > -      * padding + SEV FW.
> > -      */
> > -     data_size = ALIGN(sizeof(struct sev_data_download_firmware), 32);
> > +     data_size = ALIGN(sizeof(struct sev_data_download_firmware),
> > +                       SEV_FW_ALIGNMENT);
> >
> >       order = get_order(firmware->size + data_size);
> >       p = alloc_pages(GFP_KERNEL, order);
> > @@ -2224,6 +2242,367 @@ static int sev_misc_init(struct sev_device *sev)
> >       return 0;
> >  }
> >
> > +#ifdef CONFIG_FW_UPLOAD
> > +
> > +static enum fw_upload_err snp_dlfw_ex_prepare(struct fw_upload *fw_upload,
> > +                                           const u8 *data, u32 size)
> > +{
> > +     return FW_UPLOAD_ERR_NONE;
> > +}
> > +
> > +static enum fw_upload_err snp_dlfw_ex_poll_complete(struct fw_upload *fw_upload)
> > +{
> > +     return FW_UPLOAD_ERR_NONE;
> > +}
> > +
> > +/*
> > + * This may be called asynchronously with an on-going update.  All other
> > + * functions are called sequentially in a single thread. To avoid contention on
> > + * register accesses, only update the cancel_request flag. Other functions will
> > + * check this flag and handle the cancel request synchronously.
> > + */
> > +static void snp_dlfw_ex_cancel(struct fw_upload *fw_upload)
> > +{
> > +     struct sev_device *sev = psp_master->sev_data;
> > +
> > +     mutex_lock(&sev->fw_lock);
> > +     sev->fw_cancel = true;
> > +     mutex_unlock(&sev->fw_lock);
> > +}
> > +
> > +static enum fw_upload_err snp_dlfw_ex_err_translate(struct sev_device *sev, int psp_ret)
> > +{
> > +     dev_dbg(sev->dev, "Failed to update SEV firmware: %#x\n", psp_ret);
> > +     /*
> > +      * Operation error:
> > +      *   HW_ERROR: Critical error. Machine needs repairs now.
> > +      *   RW_ERROR: Severe error. Roll back to the prior version to recover.
> > +      * User error:
> > +      *   FW_INVALID: Bad input for this interface.
> > +      *   BUSY: Wrong machine state to run download_firmware_ex.
> > +      */
> > +     switch (psp_ret) {
> > +     case SEV_RET_RESTORE_REQUIRED:
> > +             dev_warn(sev->dev, "Firmware updated but unusable\n");
> > +             dev_warn(sev->dev, "Need to do manual firmware rollback!!!\n");
> > +             return FW_UPLOAD_ERR_RW_ERROR;
> > +     case SEV_RET_SHUTDOWN_REQUIRED:
> > +             /* No state changes made. Not a hardware error. */
> > +             dev_warn(sev->dev, "Firmware image cannot be live updated\n");
> > +             return FW_UPLOAD_ERR_FW_INVALID;
> > +     case SEV_RET_BAD_VERSION:
> > +             /* No state changes made. Not a hardware error. */
> > +             dev_warn(sev->dev, "Firmware image is not well formed\n");
> > +             return FW_UPLOAD_ERR_FW_INVALID;
> > +             /* SEV-specific errors that can still happen. */
> > +     case SEV_RET_BAD_SIGNATURE:
> > +             /* No state changes made. Not a hardware error. */
> > +             dev_warn(sev->dev, "Firmware image signature is bad\n");
> > +             return FW_UPLOAD_ERR_FW_INVALID;
> > +     case SEV_RET_INVALID_PLATFORM_STATE:
> > +             /* Calling at the wrong time. Not a hardware error. */
> > +             dev_warn(sev->dev, "Firmware not updated as SEV in INIT state\n");
> > +             return FW_UPLOAD_ERR_BUSY;
> > +     case SEV_RET_HWSEV_RET_UNSAFE:
> > +             dev_err(sev->dev, "Firmware is unstable. Reset your machine!!!\n");
> > +             return FW_UPLOAD_ERR_HW_ERROR;
> > +             /* Kernel bug cases. */
> > +     case SEV_RET_INVALID_PARAM:
> > +             dev_err(sev->dev, "Download-firmware-EX invalid parameter\n");
> > +             return FW_UPLOAD_ERR_RW_ERROR;
> > +     case SEV_RET_INVALID_ADDRESS:
> > +             dev_err(sev->dev, "Download-firmware-EX invalid address\n");
> > +             return FW_UPLOAD_ERR_RW_ERROR;
> > +     default:
> > +             dev_err(sev->dev, "Unhandled download_firmware_ex err %d\n", psp_ret);
> > +             return FW_UPLOAD_ERR_HW_ERROR;
> > +     }
> > +}
> > +
> > +static enum fw_upload_err snp_dlfw_ex_write(struct fw_upload *fwl, const u8 *data,
> > +                                         u32 offset, u32 size, u32 *written)
> > +{
> > +     struct sev_data_download_firmware_ex *data_ex;
> > +     struct sev_data_snp_guest_status status_data;
> > +     struct sev_device *sev = fwl->dd_handle;
> > +     void *snp_guest_status, *fw_dest;
> > +     u8 api_major, api_minor, build;
> > +     int ret, error, order;
> > +     struct page *p;
> > +     u64 data_size;
> > +     bool cancel;
> > +
> > +     if (!sev)
> > +             return FW_UPLOAD_ERR_HW_ERROR;
> > +
> > +     mutex_lock(&sev->fw_lock);
> > +     cancel = sev->fw_cancel;
> > +     mutex_unlock(&sev->fw_lock);
> > +
> > +     if (cancel)
> > +             return FW_UPLOAD_ERR_CANCELED;
> > +
> > +     /*
> > +      * SEV firmware update is a one-shot update operation, the write()
> > +      * callback to be invoked multiple times for the same update is
> > +      * unexpected.
> > +      */
> > +     if (offset)
> > +             return FW_UPLOAD_ERR_INVALID_SIZE;
> > +
> > +     if (sev_get_api_version())
> > +             return FW_UPLOAD_ERR_HW_ERROR;
> > +
> > +     api_major = sev->api_major;
> > +     api_minor = sev->api_minor;
> > +     build     = sev->build;
> > +
> > +     data_size = ALIGN(sizeof(struct sev_data_download_firmware_ex),
> > +                       SEV_FW_ALIGNMENT);
> > +
> > +     order = get_order(size + data_size);
> > +     p = alloc_pages(GFP_KERNEL, order);
> > +     if (!p) {
> > +             ret = FW_UPLOAD_ERR_INVALID_SIZE;
> > +             goto fw_err;
> > +     }
> > +
> > +     /*
> > +      * Copy firmware data to a kernel allocated contiguous
> > +      * memory region.
> > +      */
> > +     data_ex = page_address(p);
> > +     fw_dest = page_address(p) + data_size;
> > +     memset(data_ex, 0, data_size);
> > +     memcpy(fw_dest, data, size);
> > +
> > +     data_ex->address = __psp_pa(fw_dest);
> > +     data_ex->len = size;
> > +     data_ex->cmdlen = sizeof(struct sev_data_download_firmware_ex);
> > +
> > +     /*
> > +      * SNP_COMMIT should be issued explicitly to commit the updated
> > +      * firmware after guest context pages have been updated.
> > +      */
> > +
> > +     ret = sev_do_cmd(SEV_CMD_SNP_DOWNLOAD_FIRMWARE_EX, data_ex, &error);
> > +
> > +     if (ret) {
> > +             ret = snp_dlfw_ex_err_translate(sev, error);
> > +             goto free_err;
> > +     }
> > +
> > +     __free_pages(p, order);
> > +
> > +     /* Need to do a DF_FLUSH after live firmware update */
> > +     wbinvd_on_all_cpus();
> > +     ret = __sev_do_cmd_locked(SEV_CMD_SNP_DF_FLUSH, NULL, &error);
> > +     if (ret) {
> > +             dev_dbg(sev->dev, "DF_FLUSH error %d\n", error);
> > +             ret = FW_UPLOAD_ERR_HW_ERROR;
> > +             goto fw_err;
> > +     }
> > +
> > +     sev_get_api_version();
> > +
> > +     /*
> > +      * Force an update of guest context pages after SEV firmware
> > +      * live update by issuing SNP_GUEST_STATUS on all guest
> > +      * context pages.
> > +      */
> > +     snp_guest_status = sev_fw_alloc(PAGE_SIZE);
> > +     if (!snp_guest_status) {
> > +             ret = FW_UPLOAD_ERR_INVALID_SIZE;
> > +             goto fw_err;
> > +     }
> > +
> > +     /*
> > +      * After the last bound asid-to-gctx page is snp_unbound_gctx_end-many
> > +      * unbound gctx pages that also need updating.
> > +      */
> > +     for (int i = 1; i <= sev->last_snp_asid + sev->snp_unbound_gctx_end; i++) {
> > +             if (sev->snp_asid_to_gctx_pages_map[i]) {
> > +                     status_data.gctx_paddr = sev->snp_asid_to_gctx_pages_map[i];
> > +                     status_data.address = __psp_pa(snp_guest_status);
> > +                     ret = sev_do_cmd(SEV_CMD_SNP_GUEST_STATUS, &status_data, &error);
> > +                     if (ret) {
> > +                             /*
> > +                              * Handle race with SNP VM being destroyed/decommissoned,
> > +                              * if guest context page invalid error is returned,
> > +                              * assume guest has been destroyed.
> > +                              */
> > +                             if (error == SEV_RET_INVALID_GUEST)
> > +                                     continue;
> > +                             sev->synthetic_restore_required = true;
> > +                             dev_err(sev->dev, "SNP GCTX update error: %#x\n", error);
> > +                             dev_err(sev->dev, "Roll back SNP firmware!\n");
> > +                             snp_free_firmware_page(snp_guest_status);
> > +                             ret = FW_UPLOAD_ERR_RW_ERROR;
> > +                             goto fw_err;
> > +                     }
> > +             }
> > +     }
> > +     snp_free_firmware_page(snp_guest_status);
> > +
> > +     if (api_major != sev->api_major || api_minor != sev->api_minor ||
> > +         build != sev->build) {
> > +             dev_info(sev->dev, "SEV firmware updated from %d.%d.%d to %d.%d.%d\n",
> > +                      api_major, api_minor, build,
> > +                      sev->api_major, sev->api_minor, sev->build);
> > +     } else {
> > +             dev_info(sev->dev, "SEV firmware same as old %d.%d.%d\n",
> > +                      api_major, api_minor, build);
> > +     }
> > +
> > +     *written = size;
> > +     return FW_UPLOAD_ERR_NONE;
> > +
> > +free_err:
> > +     __free_pages(p, order);
> > +fw_err:
> > +     return ret;
> > +}
> > +
> > +static const struct fw_upload_ops snp_dlfw_ex_ops = {
> > +     .prepare = snp_dlfw_ex_prepare,
> > +     .write = snp_dlfw_ex_write,
> > +     .poll_complete = snp_dlfw_ex_poll_complete,
> > +     .cancel = snp_dlfw_ex_cancel,
> > +};
> > +
> > +static void sev_snp_dev_init_firmware_upload(struct sev_device *sev)
> > +{
> > +     struct fw_upload *fwl;
> > +
> > +     fwl = firmware_upload_register(THIS_MODULE, sev->dev, "snp_dlfw_ex", &snp_dlfw_ex_ops, sev);
> > +
> > +     if (IS_ERR(fwl))
> > +             dev_err(sev->dev, "SEV firmware upload initialization error %ld\n", PTR_ERR(fwl));
> > +
> > +     sev->fwl = fwl;
> > +     mutex_init(&sev->fw_lock);
> > +}
> > +
> > +static void sev_snp_destroy_firmware_upload(struct sev_device *sev)
> > +{
> > +     firmware_upload_unregister(sev->fwl);
> > +}
> > +
> > +/*
> > + * After a gctx is created, it is used by snp_launch_start before getting
> > + * bound to an asid. The launch protocol allocates an asid before creating a
> > + * matching gctx page, so there should never be more unbound gctx pages than
> > + * there are possible SNP asids.
> > + *
> > + * The unbound gctx pages must be updated after executing DOWNLOAD_FIRMWARE_EX
> > + * and before committing the firmware.
> > + */
> > +static void snp_gctx_create_track_locked(struct sev_device *sev, void *data)
> > +{
> > +     struct sev_data_snp_addr *gctx_create = data;
> > +
> > +     /* This condition should never happen, but is needed for memory safety. */
> > +     if (sev->snp_unbound_gctx_end >= sev->last_snp_asid) {
> > +             dev_warn(sev->dev, "Too many unbound SNP GCTX pages to track\n");
> > +             return;
> > +     }
> > +
> > +     sev->snp_unbound_gctx_pages[sev->snp_unbound_gctx_end] = gctx_create->address;
> > +     sev->snp_unbound_gctx_end++;
> > +}
> > +
> > +/*
> > + * PREREQUISITE: The snp_activate command was successful, meaning the asid
> > + * is in the acceptable range 1..sev->last_snp_asid.
> > + *
> > + * The gctx_paddr must be in the unbound gctx buffer.
> > + */
> > +static void snp_activate_track_locked(struct sev_device *sev, void *data)
> > +{
> > +     struct sev_data_snp_activate *data_activate = data;
> > +
> > +     sev->snp_asid_to_gctx_pages_map[data_activate->asid] = data_activate->gctx_paddr;
> > +
> > +     for (int i = 0; i < sev->snp_unbound_gctx_end; i++) {
> > +             if (sev->snp_unbound_gctx_pages[i] == data_activate->gctx_paddr) {
> > +                     /*
> > +                      * Swap the last unbound gctx page with the now-bound
> > +                      * gctx page to shrink the buffer.
> > +                      */
> > +                     sev->snp_unbound_gctx_end--;
> > +                     sev->snp_unbound_gctx_pages[i] =
> > +                             sev->snp_unbound_gctx_pages[sev->snp_unbound_gctx_end];
> > +                     sev->snp_unbound_gctx_pages[sev->snp_unbound_gctx_end] = 0;
> > +                     break;
> > +             }
> > +     }
> > +}
> > +
> > +static void snp_decommission_track_locked(struct sev_device *sev, void *data)
> > +{
> > +     struct sev_data_snp_addr *data_decommission = data;
> > +
> > +     for (int i = 1; i <= sev->last_snp_asid; i++) {
> > +             if (sev->snp_asid_to_gctx_pages_map[i] == data_decommission->address) {
> > +                     sev->snp_asid_to_gctx_pages_map[i] = 0;
> > +                     break;
> > +             }
> > +     }
> > +}
> > +
> > +static void snp_cmd_bookkeeping_locked(int cmd, struct sev_device *sev, void *data)
> > +{
> > +     if (!sev->snp_asid_to_gctx_pages_map)
> > +             return;
> > +
> > +     switch (cmd) {
> > +     case SEV_CMD_SNP_GCTX_CREATE:
> > +             snp_gctx_create_track_locked(sev, data);
> > +             break;
> > +     case SEV_CMD_SNP_ACTIVATE:
> > +             snp_activate_track_locked(sev, data);
> > +             break;
> > +     case SEV_CMD_SNP_DECOMMISSION:
> > +             snp_decommission_track_locked(sev, data);
> > +             break;
> > +     default:
> > +             break;
> > +     }
> > +}
> > +
> > +static int sev_snp_platform_init_firmware_upload(struct sev_device *sev)
> > +{
> > +     u32 max_snp_asid;
> > +
> > +     /*
> > +      * cpuid_edx(0x8000001f) is the minimum SEV ASID, hence the exclusive
> > +      * maximum SEV-SNP ASID. Save the inclusive maximum to avoid confusing
> > +      * logic elsewhere.
> > +      */
> > +     max_snp_asid = cpuid_edx(0x8000001f);
> > +     sev->last_snp_asid = max_snp_asid - 1;
> > +     if (sev->last_snp_asid) {
> > +             sev->snp_asid_to_gctx_pages_map = devm_kmalloc_array(
> > +                     sev->dev, max_snp_asid * 2, sizeof(u64), GFP_KERNEL | __GFP_ZERO);
> > +             sev->snp_unbound_gctx_pages = &sev->snp_asid_to_gctx_pages_map[max_snp_asid];
> > +             if (!sev->snp_asid_to_gctx_pages_map) {
> > +                     kfree(snp_range_list);
> > +                     dev_err(sev->dev,
> > +                             "SEV-SNP: snp_asid_to_gctx_pages_map memory allocation failed\n");
> > +                     return -ENOMEM;
> > +             }
> > +     }
> > +     return 0;
> > +}
> > +
> > +#else
> > +
> > +static int sev_snp_platform_init_firmware_upload(struct sev_device *sev) { return 0; }
> > +static void sev_snp_dev_init_firmware_upload(struct sev_device *sev) { }
> > +static void sev_snp_destroy_firmware_upload(struct sev_device *sev) { }
> > +static void snp_cmd_bookkeeping_locked(int cmd, struct sev_device *sev, void *data) { }
> > +#endif
> > +
> >  int sev_dev_init(struct psp_device *psp)
> >  {
> >       struct device *dev = psp->dev;
> > @@ -2265,6 +2644,8 @@ int sev_dev_init(struct psp_device *psp)
> >       if (ret)
> >               goto e_irq;
> >
> > +     sev_snp_dev_init_firmware_upload(sev);
> > +
> >       dev_notice(dev, "sev enabled\n");
> >
> >       return 0;
> > @@ -2343,6 +2724,8 @@ void sev_dev_destroy(struct psp_device *psp)
> >               kref_put(&misc_dev->refcount, sev_exit);
> >
> >       psp_clear_sev_irq_handler(psp);
> > +
> > +     sev_snp_destroy_firmware_upload(sev);
> >  }
> >
> >  static int snp_shutdown_on_panic(struct notifier_block *nb,
> > diff --git a/drivers/crypto/ccp/sev-dev.h b/drivers/crypto/ccp/sev-dev.h
> > index 3e4e5574e88a3..d6280688675d9 100644
> > --- a/drivers/crypto/ccp/sev-dev.h
> > +++ b/drivers/crypto/ccp/sev-dev.h
> > @@ -57,6 +57,20 @@ struct sev_device {
> >       bool cmd_buf_backup_active;
> >
> >       bool snp_initialized;
> > +
> > +#ifdef CONFIG_FW_UPLOAD
> > +     /* Lock to protect fw_cancel */
> > +     struct mutex fw_lock;
> > +     struct fw_upload *fwl;
> > +     bool fw_cancel;
> > +
> > +     /* Firmware update management. */
> > +     u32 last_snp_asid;
> > +     bool synthetic_restore_required;
> > +     u64 *snp_asid_to_gctx_pages_map;
> > +     u64 *snp_unbound_gctx_pages;
> > +     u32 snp_unbound_gctx_end;
> > +#endif /* CONFIG_FW_UPLOAD */
> >  };
> >
> >  int sev_dev_init(struct psp_device *psp);
> > diff --git a/include/linux/psp-sev.h b/include/linux/psp-sev.h
> > index 903ddfea85850..ff7f9e33f3335 100644
> > --- a/include/linux/psp-sev.h
> > +++ b/include/linux/psp-sev.h
> > @@ -16,6 +16,15 @@
> >
> >  #define SEV_FW_BLOB_MAX_SIZE 0x4000  /* 16KB */
> >
> > +/*
> > + * SEV FW expects the physical address given to it to be 32
> > + * byte aligned. Memory allocated has structure placed at the
> > + * beginning followed by the firmware being passed to the SEV
> > + * FW. Allocate enough memory for data structure + alignment
> > + * padding + SEV FW.
> > + */
> > +#define SEV_FW_ALIGNMENT     32
> > +
> >  /**
> >   * SEV platform state
> >   */
> > @@ -185,6 +194,23 @@ struct sev_data_download_firmware {
> >       u32 len;                                /* In */
> >  } __packed;
> >
> > +/**
> > + * struct sev_data_download_firmware_ex - DOWNLOAD_FIRMWARE_EX command parameters
> > + *
> > + * @length: length of this command buffer
> > + * @address: physical address of firmware image
> > + * @len: len of the firmware image
> > + * @commit: automatically commit the newly installed image
> > + */
> > +struct sev_data_download_firmware_ex {
> > +     u32 cmdlen;                             /* In */
> > +     u32 reserved;                           /* in */
> > +     u64 address;                            /* In */
> > +     u32 len;                                /* In */
> > +     u32 commit:1;                           /* In */
> > +     u32 reserved2:31;                       /* In */
> > +} __packed;
> > +
> >  /**
> >   * struct sev_data_get_id - GET_ID command parameters
> >   *
> > @@ -797,10 +823,13 @@ struct sev_data_snp_shutdown_ex {
> >   * @probe: True if this is being called as part of CCP module probe, which
> >   *  will defer SEV_INIT/SEV_INIT_EX firmware initialization until needed
> >   *  unless psp_init_on_probe module param is set
> > + * @supports_download_firmware_ex: True if legacy SEV/SEV-ES guests are not
> > + *  being launched via KVM
> >   */
> >  struct sev_platform_init_args {
> >       int error;
> >       bool probe;
> > +     bool supports_download_firmware_ex;
> >  };
> >
> >  /**
> > diff --git a/include/uapi/linux/psp-sev.h b/include/uapi/linux/psp-sev.h
> > index 832c15d9155bd..936464d4f282a 100644
> > --- a/include/uapi/linux/psp-sev.h
> > +++ b/include/uapi/linux/psp-sev.h
> > @@ -80,6 +80,11 @@ typedef enum {
> >       SEV_RET_INVALID_PAGE_OWNER,
> >       SEV_RET_INVALID_PAGE_AEAD_OFLOW,
> >       SEV_RET_RMP_INIT_REQUIRED,
> > +     SEV_RET_BAD_SVN,
> > +     SEV_RET_BAD_VERSION,
> > +     SEV_RET_SHUTDOWN_REQUIRED,
> > +     SEV_RET_UPDATE_FAILED,
> > +     SEV_RET_RESTORE_REQUIRED,
> >       SEV_RET_MAX,
> >  } sev_ret_code;
> >



--
-Dionna Glaze, PhD, CISSP, CCSP (she/her)

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ