Skip to contentSkip to conversation

Implement SNP live firmware update support

PATCH Superseded Pratik R. Sampat 3 participants 19 messages Oct 5 – Oct 7

Subsystems (18)

v3 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

v3 cover Implement SNP live firmware update support

Introducing support for live update on AMD SEV-SNP platforms via DOWNLOAD_FIRMWARE_EX.

This patchset is an extension of the RFC patchset from Tycho Andersen.

This series also fronts firmware_loader patches from Dan Williams[1] which majorly cleans up refcount issues so that registering the upload interface no longer pins the module. Without which having live firmware update will cause failure to reload the ccp module as well as break kexec.

Patches based on cryptodev-2.6

v3:
* Print psp return codes along with driver returns for easier parsing - Shantanu
* Clean up error codes to map the right firmware/driver error condition to the firmware error codes. - Shantanu
* Report -ETIMEOUT case by checking for psp_dead after DLFW_EX command is called instead of reporting unknown error - Shantanu
* __sev_do_snp_platform_status() call can race and incorrect cached platform states may be supplied. Guard the call under a mutex - Sashiko
* firmware unregister can race when called from sev_dev_destroy() or sev_pci_exit(). Guard the call under a mutex - Sashiko

v2: https://lore.kernel.org/lkml/[email protected]/
* In snp_get_platform_data() use snp_[alloc|free]_firmware_page() to transition pages to and from firmware-owned state, instead of open-coding rmp_mark_pages_firmware()/snp_reclaim_pages() around the SNP_FEATURE_INFO command - Tom, Sashiko
* Claim sev->fwl atomically with xchg() in unregister_sev_fw_uploader() so a concurrent module removal and sysfs unbind cannot both reach firmware_upload_unregister() - Sashiko

Responding to other Sashiko comments:
* __sev_release_firmware_buffers() called with panic=true can cause deadlock as another stopped CPU might currently hold zone->lock or other page allocator locks.
- I think having a panic param within __snp_free_firmware_pages which checks and returns before __free_pages() should resolve this issue. Since this is a pre-existing issue not relevant to this patchset, plan to send this as a seperate patch
* The sev_download_firmware_ex() function is called with the lock held and performs a GFP_KERNEL allocation. Since GFP_KERNEL can trigger direct memory reclaim, could this invoke SEV page reclaim operations that attempt to acquire the already-held sev_cmd_mutex, resulting in a deadlocked state?
- GFP_KERNEL may perform direct MM reclaim, but SNP page reclamation is not registered as an MM shrinker or normal page-reclaim callback. snp_reclaim_pages() acquires sev_cmd_mutex only when explicitly called with locked=false
* "If snp_reclaim_cmd_buf() fails and returns early, the code skips resetting sev->cmd_buf_active and sev->cmd_buf_backup_active. Will this permanently leak the bounce buffer slots and result in future -EBUSY failures?"
- Since the command buffer may still be firmware owned, I think declaring psp_dead = true is the way to ensure we do not make it available for reuse after a failed reclaim. Pre-existing issue, plan to send this as a seperate patch

v1: https://lore.kernel.org/linux-crypto/[email protected]/
* firmware-loader patches are rebased as-is and only incorporates fixes for minor build issues
* Dropped crypto/ccp: Hoist kernel part of SNP_PLATFORM_STATUS as that patch has been merged since
* Dropped crypto/ccp: Reclaim command buffer when the PSP dies and subsequent handling since firmware quirk is now resolved and need not be handled in the OS
* Use guard(mutex) for the locked region, which the split makes possible since sev_get_api_version() acquires sev_cmd_mutex - Maxwell
* Added a patch factoring out the TMR and INIT_EX teardown, so the update path and __sev_firmware_shutdown() share it - Shantanu
* Skip the re-init when the PSP is dead, not just when a rollback is outstanding - Shantanu
* Re-initialize the platform before refreshing the cached status - Shantanu
* Split the monolithic .write into a locked update helper, an error translation helper, and a thin .write that refreshes the cached status once the lock is dropped and clean up various allocations
* Convert rollback required and reinit required globals to reside in sev_dev struct to avoid carrying over states if manually reloaded

RFC: https://lore.kernel.org/all/[email protected]/

[1]: https://lore.kernel.org/lkml/[email protected]/

Dan Williams (3):
firmware_loader: Stop pinning modules on registration
firmware_loader: Stop pinning parent device per workqueue invocation
treewide: firmware_loader: Drop the unused @module argument

Pratik R. Sampat (4):
crypto: ccp - Factor out the release of the SEV firmware buffers
crypto: ccp - Allow SNP platform data to be queried after SNP INIT crypto/ccp: Register with fw_uploader and always fail crypto/ccp: Implement SNP Download Firmware EX

 .../driver-api/firmware/fw_upload.rst         |   2 +-
 drivers/base/firmware_loader/sysfs_upload.c   |  50 +-
 drivers/base/firmware_loader/sysfs_upload.h   |   1 -
 drivers/crypto/ccp/sev-dev.c                  | 444 ++++++++++++++++--
 drivers/crypto/ccp/sev-dev.h                  |   4 +
 drivers/cxl/core/memdev.c                     |   4 +-
 drivers/firmware/microchip/mpfs-auto-update.c |   2 +-
 drivers/fpga/intel-m10-bmc-sec-update.c       |   4 +-
 drivers/greybus/gb-beagleplay.c               |   2 +-
 drivers/media/i2c/thp7312.c                   |   2 +-
 drivers/net/pse-pd/pd692x0.c                  |   4 +-
 drivers/virt/coco/tdx-host/tdx-host.c         |   4 +-
 include/linux/firmware.h                      |  15 +-
 include/linux/psp-sev.h                       |  19 +
 lib/test_firmware.c                           |   3 +-
 15 files changed, 467 insertions(+), 93 deletions(-)
Signature · 1 line
2.43.0
#1 lore raw

PATCH v3 1/7 firmware_loader: Stop pinning modules on registration

From: Dan Williams <[email protected]>

The module reference counting can result in callers pinning themselves in a circular loop. The module reference counting is unnecessary. firmware_upload_unregister() must be able to guarantee that all ops are idle at return.

All ops are either called from sysfs or the workqueue, so unregister sysfs to stop submissions, cancel any started transfers, flush cancelled transfers, and then release the device.

This also solves a theoretical race of new submissions starting between flush_work() and device_unregister(). The module reference was not protecting against that race.

Cc Luis Chamberlain <[email protected]>
Cc Russ Weight <[email protected]>
Cc Danilo Krummrich <[email protected]>
Cc Greg Kroah-Hartman <[email protected]>
Cc "Rafael J. Wysocki" <[email protected]>
Fixes 97730bbb242c ("firmware_loader: Add firmware-upload support")
---
 drivers/base/firmware_loader/sysfs_upload.c | 31 ++++++++++-----------
 1 file changed, 14 insertions(+), 17 deletions(-)
drivers/base/firmware_loader/sysfs_upload.c +14 -17
index efc33294212f..6f2123538682 100644
@@ -312,14 +312,9 @@ firmware_upload_register(struct module *module, struct device *parent,
 		return ERR_PTR(-EINVAL);
 	}
 
-	if (!try_module_get(module))
-		return ERR_PTR(-EFAULT);
-
 	fw_upload = kzalloc_obj(*fw_upload);
-	if (!fw_upload) {
-		ret = -ENOMEM;
-		goto exit_module_put;
-	}
+	if (!fw_upload)
+		return ERR_PTR(-ENOMEM);
 
 	fw_upload_priv = kzalloc_obj(*fw_upload_priv);
 	if (!fw_upload_priv) {
@@ -361,7 +356,7 @@ firmware_upload_register(struct module *module, struct device *parent,
 	if (ret) {
 		dev_err(fw_dev, "%s: device_register failed\n", __func__);
 		put_device(fw_dev);
-		goto exit_module_put;
+		return ERR_PTR(ret);
 	}
 
 	return fw_upload;
@@ -372,9 +367,6 @@ firmware_upload_register(struct module *module, struct device *parent,
 free_fw_upload:
 	kfree(fw_upload);
 
-exit_module_put:
-	module_put(module);
-
 	return ERR_PTR(ret);
 }
 EXPORT_SYMBOL_GPL(firmware_upload_register);
@@ -386,23 +378,28 @@ EXPORT_SYMBOL_GPL(firmware_upload_register);
 void firmware_upload_unregister(struct fw_upload *fw_upload)
 {
 	struct fw_sysfs *fw_sysfs = fw_upload->priv;
+	struct device *parent = fw_sysfs->dev.parent;
 	struct fw_upload_priv *fw_upload_priv = fw_sysfs->fw_upload_priv;
-	struct module *module = fw_upload_priv->module;
+
+	/* hold a parent reference while child is unregistered */
+	get_device(parent);
+
+	/* shutdown the sysfs interface to block new requests */
+	device_del(&fw_sysfs->dev);
 
 	mutex_lock(&fw_upload_priv->lock);
 	if (fw_upload_priv->progress == FW_UPLOAD_PROG_IDLE) {
 		mutex_unlock(&fw_upload_priv->lock);
-		goto unregister;
+		goto release;
 	}
 
 	fw_upload_priv->ops->cancel(fw_upload);
 	mutex_unlock(&fw_upload_priv->lock);
 
+release:
 	/* Ensure lower-level device-driver is finished */
 	flush_work(&fw_upload_priv->work);
-
-unregister:
-	device_unregister(&fw_sysfs->dev);
-	module_put(module);
+	put_device(&fw_sysfs->dev);
+	put_device(parent);
 }
 EXPORT_SYMBOL_GPL(firmware_upload_unregister);
Signature · 1 line
2.43.0
#2 lore raw

PATCH v3 2/7 firmware_loader: Stop pinning parent device per workqueue invocation

In reply to Pratik R. Sampat

From: Dan Williams <[email protected]>

The device core pins parent devices while children are registered. As long as all usage of the parent device by the firmware_loader ends at firmware_upload_unregister(), no per queue_work() reference is needed.

Now that firmware_upload_unregister() holds its own parent device reference over the child device_del() and flush_work() events, the per queue_work() reference can be deleted.

Cc Luis Chamberlain <[email protected]>
Cc Russ Weight <[email protected]>
Cc Danilo Krummrich <[email protected]>
Cc Greg Kroah-Hartman <[email protected]>
Cc "Rafael J. Wysocki" <[email protected]>
Cc Chao Gao <[email protected]>
[[email protected]: drop unused fw_dev in fw_upload_start()]
---
 drivers/base/firmware_loader/sysfs_upload.c | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)
drivers/base/firmware_loader/sysfs_upload.c +2 -7
index 6f2123538682..890a242a7c20 100644
@@ -176,7 +176,7 @@ static void fw_upload_main(struct work_struct *work)
 	ret = fwlp->ops->prepare(fwl, fwlp->data, fwlp->remaining_size);
 	if (ret != FW_UPLOAD_ERR_NONE) {
 		fw_upload_set_error(fwlp, ret);
-		goto putdev_exit;
+		goto out;
 	}
 
 	fw_upload_update_progress(fwlp, FW_UPLOAD_PROG_TRANSFERRING);
@@ -205,9 +205,7 @@ static void fw_upload_main(struct work_struct *work)
 	if (fwlp->ops->cleanup)
 		fwlp->ops->cleanup(fwl);
 
-putdev_exit:
-	put_device(fw_dev->parent);
-
+out:
 	/*
 	 * Note: fwlp->remaining_size is left unmodified here to provide
 	 * additional information on errors. It will be reinitialized when
@@ -228,7 +226,6 @@ static void fw_upload_main(struct work_struct *work)
 int fw_upload_start(struct fw_sysfs *fw_sysfs)
 {
 	struct fw_priv *fw_priv = fw_sysfs->fw_priv;
-	struct device *fw_dev = &fw_sysfs->dev;
 	struct fw_upload_priv *fwlp;
 
 	if (!fw_sysfs->fw_upload_priv)
@@ -249,8 +246,6 @@ int fw_upload_start(struct fw_sysfs *fw_sysfs)
 		return -EBUSY;
 	}
 
-	get_device(fw_dev->parent); /* released in fw_upload_main */
-
 	fwlp->progress = FW_UPLOAD_PROG_RECEIVING;
 	fwlp->err_code = 0;
 	fwlp->remaining_size = fw_priv->size;
Signature · 1 line
2.43.0
#3 lore raw

PATCH v3 3/7 treewide: firmware_loader: Drop the unused @module argument

In reply to Pratik R. Sampat

From: Dan Williams <[email protected]>

Now that the firmware loader properly ceases all operations at firmware_upload_unregister() and no longer takes module references, clean up the unused parameter.

Cc Chao Gao <[email protected]>
Cc Luis Chamberlain <[email protected]>
Cc Russ Weight <[email protected]>
Cc Danilo Krummrich <[email protected]>
Cc Greg Kroah-Hartman <[email protected]>
Cc "Rafael J. Wysocki" <[email protected]>

[[email protected]: drop leftover fw_upload_priv->module assignment
and the unused @module field from struct fw_upload_priv]
[[email protected]: convert the tdx-host caller added since the original
posting]

---
 Documentation/driver-api/firmware/fw_upload.rst |  2 +-
 drivers/base/firmware_loader/sysfs_upload.c     | 10 ++++------
 drivers/base/firmware_loader/sysfs_upload.h     |  1 -
 drivers/cxl/core/memdev.c                       |  4 ++--
 drivers/firmware/microchip/mpfs-auto-update.c   |  2 +-
 drivers/fpga/intel-m10-bmc-sec-update.c         |  4 ++--
 drivers/greybus/gb-beagleplay.c                 |  2 +-
 drivers/media/i2c/thp7312.c                     |  2 +-
 drivers/net/pse-pd/pd692x0.c                    |  4 ++--
 drivers/virt/coco/tdx-host/tdx-host.c           |  4 ++--
 include/linux/firmware.h                        | 15 +++++++--------
 lib/test_firmware.c                             |  3 +--
 12 files changed, 24 insertions(+), 29 deletions(-)
Documentation/driver-api/firmware/fw_upload.rst +1 -1
index edf1d0c5e7c3..702b1ed77551 100644
@@ -57,7 +57,7 @@ function calls firmware_upload_unregister() such as::
 		len = (truncate) ? truncate - fw_name : strlen(fw_name);
 		sec->fw_name = kmemdup_nul(fw_name, len, GFP_KERNEL);
 
-		fwl = firmware_upload_register(THIS_MODULE, sec->dev, sec->fw_name,
+		fwl = firmware_upload_register(sec->dev, sec->fw_name,
 					       &m10bmc_ops, sec);
 		if (IS_ERR(fwl)) {
 			dev_err(sec->dev, "Firmware Upload driver failed to start\n");
drivers/base/firmware_loader/sysfs_upload.c +4 -6
index 890a242a7c20..920c4b640f96 100644
@@ -273,7 +273,6 @@ void fw_upload_free(struct fw_sysfs *fw_sysfs)
 
 /**
  * firmware_upload_register() - register for the firmware upload sysfs API
- * @module: kernel module of this device
  * @parent: parent device instantiating firmware upload
  * @name: firmware name to be associated with this device
  * @ops: pointer to structure of firmware upload ops
@@ -285,10 +284,10 @@ void fw_upload_free(struct fw_sysfs *fw_sysfs)
  *	Return: struct fw_upload pointer or ERR_PTR()
  *
  **/
-struct fw_upload *
-firmware_upload_register(struct module *module, struct device *parent,
-			 const char *name, const struct fw_upload_ops *ops,
-			 void *dd_handle)
+struct fw_upload *firmware_upload_register(struct device *parent,
+					   const char *name,
+					   const struct fw_upload_ops *ops,
+					   void *dd_handle)
 {
 	u32 opt_flags = FW_OPT_NOCACHE;
 	struct fw_upload *fw_upload;
@@ -320,7 +319,6 @@ firmware_upload_register(struct module *module, struct device *parent,
 	fw_upload_priv->fw_upload = fw_upload;
 	fw_upload_priv->ops = ops;
 	mutex_init(&fw_upload_priv->lock);
-	fw_upload_priv->module = module;
 	fw_upload_priv->name = name;
 	fw_upload_priv->err_code = 0;
 	fw_upload_priv->progress = FW_UPLOAD_PROG_IDLE;
drivers/base/firmware_loader/sysfs_upload.h +0 -1
index 31931ff7808a..dc7ccdceb96f 100644
@@ -26,7 +26,6 @@ enum fw_upload_prog {
 
 struct fw_upload_priv {
 	struct fw_upload *fw_upload;
-	struct module *module;
 	const char *name;
 	const struct fw_upload_ops *ops;
 	struct mutex lock;		  /* protect data structure contents */
drivers/cxl/core/memdev.c +2 -2
index b3419df586b9..ee7a97b8b2c8 100644
@@ -1151,8 +1151,8 @@ int devm_cxl_setup_fw_upload(struct device *host, struct cxl_memdev_state *mds)
 	if (!test_bit(CXL_MEM_COMMAND_ID_GET_FW_INFO, cxl_mbox->enabled_cmds))
 		return 0;
 
-	fwl = firmware_upload_register(THIS_MODULE, dev, dev_name(dev),
-				       &cxl_memdev_fw_ops, mds);
+	fwl = firmware_upload_register(dev, dev_name(dev), &cxl_memdev_fw_ops,
+				       mds);
 	if (IS_ERR(fwl))
 		return PTR_ERR(fwl);
 	return devm_add_action_or_reset(host, cxl_remove_fw_upload, fwl);
drivers/firmware/microchip/mpfs-auto-update.c +1 -1
index 1211fd8d0463..992088606204 100644
@@ -437,7 +437,7 @@ static int mpfs_auto_update_probe(struct platform_device *pdev)
 		return dev_err_probe(dev, ret,
 				     "The current bitstream does not support auto-update\n");
 
-	fw_uploader = firmware_upload_register(THIS_MODULE, dev, "mpfs-auto-update",
+	fw_uploader = firmware_upload_register(dev, "mpfs-auto-update",
 					       &mpfs_auto_update_ops, priv);
 	if (IS_ERR(fw_uploader))
 		return dev_err_probe(dev, PTR_ERR(fw_uploader),
drivers/fpga/intel-m10-bmc-sec-update.c +2 -2
index 7d23d914df3f..3d318570faba 100644
@@ -712,8 +712,8 @@ static int m10bmc_sec_probe(struct platform_device *pdev)
 		goto fw_name_fail;
 	}
 
-	fwl = firmware_upload_register(THIS_MODULE, sec->dev, sec->fw_name,
-				       &m10bmc_ops, sec);
+	fwl = firmware_upload_register(sec->dev, sec->fw_name, &m10bmc_ops,
+				       sec);
 	if (IS_ERR(fwl)) {
 		dev_err(sec->dev, "Firmware Upload driver failed to start\n");
 		ret = PTR_ERR(fwl);
drivers/greybus/gb-beagleplay.c +1 -1
index 244966d56c9b..a097a3b10552 100644
@@ -1172,7 +1172,7 @@ static int gb_fw_init(struct gb_beagleplay *bg)
 		return PTR_ERR(desc);
 	bg->rst_gpio = desc;
 
-	fwl = firmware_upload_register(THIS_MODULE, &bg->sd->dev, "cc1352p7",
+	fwl = firmware_upload_register(&bg->sd->dev, "cc1352p7",
 				       &cc1352_bootloader_ops, bg);
 	if (IS_ERR(fwl))
 		return PTR_ERR(fwl);
drivers/media/i2c/thp7312.c +1 -1
index 775cfba188d8..ebbe14393c9f 100644
@@ -1909,7 +1909,7 @@ static int thp7312_register_flash_mode(struct thp7312_device *thp7312)
 		goto error;
 	}
 
-	fwl = firmware_upload_register(THIS_MODULE, dev, "thp7312-firmware",
+	fwl = firmware_upload_register(dev, "thp7312-firmware",
 				       &thp7312_fw_upload_ops, thp7312);
 	if (IS_ERR(fwl)) {
 		ret = PTR_ERR(fwl);
drivers/net/pse-pd/pd692x0.c +2 -2
index 209de9cec849..5bb91a0f4438 100644
@@ -1841,8 +1841,8 @@ static int pd692x0_i2c_probe(struct i2c_client *client)
 		return dev_err_probe(dev, ret,
 				     "failed to register PSE controller\n");
 
-	fwl = firmware_upload_register(THIS_MODULE, dev, dev_name(dev),
-				       &pd692x0_fw_ops, priv);
+	fwl = firmware_upload_register(dev, dev_name(dev), &pd692x0_fw_ops,
+				       priv);
 	if (IS_ERR(fwl))
 		return dev_err_probe(dev, PTR_ERR(fwl),
 				     "failed to register to the Firmware Upload API\n");
drivers/virt/coco/tdx-host/tdx-host.c +2 -2
index e8ed8dfa526e..f11a545d40cf 100644
@@ -194,8 +194,8 @@ static int seamldr_init(struct device *dev)
 	if (!supports_runtime_update())
 		return 0;
 
-	tdx_fwl = firmware_upload_register(THIS_MODULE, dev, "tdx_module",
-					   &tdx_fw_ops, NULL);
+	tdx_fwl = firmware_upload_register(dev, "tdx_module", &tdx_fw_ops,
+					   NULL);
 	if (IS_ERR(tdx_fwl))
 		return PTR_ERR(tdx_fwl);
 
include/linux/firmware.h +7 -8
index 0fa3b027f02f..aa0333095c10 100644
@@ -197,20 +197,19 @@ static inline int request_partial_firmware_into_buf
 
 #ifdef CONFIG_FW_UPLOAD
 
-struct fw_upload *
-firmware_upload_register(struct module *module, struct device *parent,
-			 const char *name, const struct fw_upload_ops *ops,
-			 void *dd_handle);
+struct fw_upload *firmware_upload_register(struct device *parent,
+					   const char *name,
+					   const struct fw_upload_ops *ops,
+					   void *dd_handle);
 void firmware_upload_unregister(struct fw_upload *fw_upload);
 
 #else
 
 static inline struct fw_upload *
-firmware_upload_register(struct module *module, struct device *parent,
-			 const char *name, const struct fw_upload_ops *ops,
-			 void *dd_handle)
+firmware_upload_register(struct device *parent, const char *name,
+			 const struct fw_upload_ops *ops, void *dd_handle)
 {
-		return ERR_PTR(-EINVAL);
+	return ERR_PTR(-EINVAL);
 }
 
 static inline void firmware_upload_unregister(struct fw_upload *fw_upload)
lib/test_firmware.c +1 -2
index 7459bba65444..a77a99404fd3 100644
@@ -1322,8 +1322,7 @@ static ssize_t upload_register_store(struct device *dev,
 		goto free_tst;
 	}
 
-	fwl = firmware_upload_register(THIS_MODULE, dev, tst->name,
-				       &upload_test_ops, tst);
+	fwl = firmware_upload_register(dev, tst->name, &upload_test_ops, tst);
 	if (IS_ERR(fwl)) {
 		ret = PTR_ERR(fwl);
 		goto free_buf;
Signature · 1 line
2.43.0
#4 lore raw

PATCH v3 4/7 crypto: ccp - Factor out the release of the SEV firmware buffers

In reply to Pratik R. Sampat

__sev_firmware_shutdown() reclaims and frees the TMR and the INIT_EX NV area once the platform has been taken down. Move that into a helper so that other paths which de-initialize the platform can reuse the same path.

No functional change intended.

---
 drivers/crypto/ccp/sev-dev.c | 55 ++++++++++++++++++++----------------
 1 file changed, 30 insertions(+), 25 deletions(-)
drivers/crypto/ccp/sev-dev.c +30 -25
index f833cb7e4da3..1ed9e61a95cc 100644
@@ -1881,6 +1881,35 @@ static int __sev_platform_shutdown_locked(int *error)
 	return ret;
 }
 
+static void __sev_release_firmware_buffers(bool panic)
+{
+	if (sev_es_tmr) {
+		/*
+		 * The TMR area was encrypted, flush it from the cache.
+		 *
+		 * If invoked during panic handling, local interrupts are
+		 * disabled and all CPUs are stopped, so wbinvd_on_all_cpus()
+		 * can't be used. In that case, wbinvd() is done on remote CPUs
+		 * via the NMI callback, and done for this CPU later during
+		 * SNP shutdown, so wbinvd_on_all_cpus() can be skipped.
+		 */
+		if (!panic)
+			wbinvd_on_all_cpus();
+
+		__snp_free_firmware_pages(virt_to_page(sev_es_tmr),
+					  get_order(sev_es_tmr_size),
+					  true);
+		sev_es_tmr = NULL;
+	}
+
+	if (sev_init_ex_buffer) {
+		__snp_free_firmware_pages(virt_to_page(sev_init_ex_buffer),
+					  get_order(NV_LENGTH),
+					  true);
+		sev_init_ex_buffer = NULL;
+	}
+}
+
 static int sev_get_platform_state(int *state, int *error)
 {
 	struct sev_user_data_status data;
@@ -2906,31 +2935,7 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic)
 
 	__sev_platform_shutdown_locked(&error);
 
-	if (sev_es_tmr) {
-		/*
-		 * The TMR area was encrypted, flush it from the cache.
-		 *
-		 * If invoked during panic handling, local interrupts are
-		 * disabled and all CPUs are stopped, so wbinvd_on_all_cpus()
-		 * can't be used. In that case, wbinvd() is done on remote CPUs
-		 * via the NMI callback, and done for this CPU later during
-		 * SNP shutdown, so wbinvd_on_all_cpus() can be skipped.
-		 */
-		if (!panic)
-			wbinvd_on_all_cpus();
-
-		__snp_free_firmware_pages(virt_to_page(sev_es_tmr),
-					  get_order(sev_es_tmr_size),
-					  true);
-		sev_es_tmr = NULL;
-	}
-
-	if (sev_init_ex_buffer) {
-		__snp_free_firmware_pages(virt_to_page(sev_init_ex_buffer),
-					  get_order(NV_LENGTH),
-					  true);
-		sev_init_ex_buffer = NULL;
-	}
+	__sev_release_firmware_buffers(panic);
 
 	__sev_snp_shutdown_locked(&error, panic);
 }
Signature · 1 line
2.43.0
#5 lore raw

PATCH v3 5/7 crypto: ccp - Allow SNP platform data to be queried after SNP INIT

In reply to Pratik R. Sampat

In preparation for refreshing the cached SNP platform status and feature information after a successful firmware live update from DOWNLOAD_FIRMWARE_EX, allow snp_get_platform_data() to be called while the SNP firmware is in the INIT state.

Once SNP is initialized the firmware requires the output page of both commands to be firmware-owned. sev->snp_plat_status cannot satisfy that as it is embedded in struct sev_device, so use __sev_do_snp_platform_status(), which stages the output through a dedicated page. Allocate the SNP_FEATURE_INFO output page with __snp_alloc_firmware_pages(), which transitions the page to firmware-owned when SNP is initialized, and release it with __snp_free_firmware_pages(), which reclaims it before freeing.

Typically this was only ever exercised during initialization. However DOWNLOAD_FIRMWARE_EX can change that. Live firmware update can race with IOCTLs such as SEV_FACTORY_RESET which could cause operations performed on incorrect cached platform states. Avoid this race by wrapping the function under the sev_cmd_mutex.

---
 drivers/crypto/ccp/sev-dev.c | 27 ++++++++++++---------------
 1 file changed, 12 insertions(+), 15 deletions(-)
drivers/crypto/ccp/sev-dev.c +12 -15
index 1ed9e61a95cc..c40401c861d3 100644
@@ -131,6 +131,8 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic);
 
 static int snp_shutdown_on_panic(struct notifier_block *nb,
 				 unsigned long reason, void *arg);
+static int __sev_do_snp_platform_status(struct sev_user_data_snp_status *status,
+					int *error);
 
 static struct notifier_block snp_panic_notifier = {
 	.notifier_call = snp_shutdown_on_panic,
@@ -1261,19 +1263,12 @@ static int snp_get_platform_data(struct sev_device *sev, int *error)
 {
 	struct sev_data_snp_feature_info snp_feat_info;
 	struct snp_feature_info *feat_info;
-	struct sev_data_snp_addr buf;
 	struct page *page;
 	int rc;
 
-	/*
-	 * This function is expected to be called before SNP is
-	 * initialized.
-	 */
-	if (sev->snp_initialized)
-		return -EINVAL;
+	lockdep_assert_held(&sev_cmd_mutex);
 
-	buf.address = __psp_pa(&sev->snp_plat_status);
-	rc = sev_do_cmd(SEV_CMD_SNP_PLATFORM_STATUS, &buf, error);
+	rc = __sev_do_snp_platform_status(&sev->snp_plat_status, error);
 	if (rc) {
 		dev_err(sev->dev, "SNP PLATFORM_STATUS command failed, ret = %d, error = %#x\n",
 			rc, *error);
@@ -1297,23 +1292,23 @@ static int snp_get_platform_data(struct sev_device *sev, int *error)
 	 * command to ensure structure is 8-byte aligned, and does not
 	 * cross a page boundary.
 	 */
-	page = alloc_page(GFP_KERNEL);
-	if (!page)
+	page = __snp_alloc_firmware_pages(GFP_KERNEL, 0, true);
+	feat_info =  page ? page_address(page) : NULL;
+	if (!feat_info)
 		return -ENOMEM;
 
-	feat_info = page_address(page);
 	snp_feat_info.length = sizeof(snp_feat_info);
 	snp_feat_info.ecx_in = 0;
 	snp_feat_info.feature_info_paddr = __psp_pa(feat_info);
 
-	rc = sev_do_cmd(SEV_CMD_SNP_FEATURE_INFO, &snp_feat_info, error);
+	rc = __sev_do_cmd_locked(SEV_CMD_SNP_FEATURE_INFO, &snp_feat_info, error);
 	if (!rc)
 		sev->snp_feat_info_0 = *feat_info;
 	else
 		dev_err(sev->dev, "SNP FEATURE_INFO command failed, ret = %d, error = %#x\n",
 			rc, *error);
 
-	__free_page(page);
+	__snp_free_firmware_pages(page, 0, true);
 
 	return rc;
 }
@@ -2097,6 +2092,8 @@ static int sev_get_api_version(void)
 	struct sev_user_data_status status;
 	int error = 0, ret;
 
+	guard(mutex)(&sev_cmd_mutex);
+
 	/*
 	 * Cache SNP platform status and SNP feature information
 	 * if SNP is available.
@@ -2107,7 +2104,7 @@ static int sev_get_api_version(void)
 			return 1;
 	}
 
-	ret = sev_platform_status(&status, &error);
+	ret = __sev_do_cmd_locked(SEV_CMD_PLATFORM_STATUS, &status, &error);
 	if (ret) {
 		dev_err(sev->dev,
 			"SEV: failed to get status. Error: %#x\n", error);
Signature · 1 line
2.43.0
maintainer Reviewed-by on v3 5/7
#6 lore raw

Re: [Patch v3 5/7] crypto: ccp - Allow SNP platform data to be queried after SNP INIT

Quoted 20 lines · Pratik R. Sampat

In preparation for refreshing the cached SNP platform status and feature information after a successful firmware live update from DOWNLOAD_FIRMWARE_EX, allow snp_get_platform_data() to be called while the SNP firmware is in the INIT state.

Once SNP is initialized the firmware requires the output page of both commands to be firmware-owned. sev->snp_plat_status cannot satisfy that as it is embedded in struct sev_device, so use __sev_do_snp_platform_status(), which stages the output through a dedicated page. Allocate the SNP_FEATURE_INFO output page with __snp_alloc_firmware_pages(), which transitions the page to firmware-owned when SNP is initialized, and release it with __snp_free_firmware_pages(), which reclaims it before freeing.

Typically this was only ever exercised during initialization. However DOWNLOAD_FIRMWARE_EX can change that. Live firmware update can race with IOCTLs such as SEV_FACTORY_RESET which could cause operations performed on incorrect cached platform states. Avoid this race by wrapping the function under the sev_cmd_mutex.

Quoted 3 lines

Just a minor comment below.

Quoted 41 lines
---
 drivers/crypto/ccp/sev-dev.c | 27 ++++++++++++---------------
 1 file changed, 12 insertions(+), 15 deletions(-)
drivers/crypto/ccp/sev-dev.c +4 -9
index 1ed9e61a95cc..c40401c861d3 100644
@@ -131,6 +131,8 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic);
 
 static int snp_shutdown_on_panic(struct notifier_block *nb,
 				 unsigned long reason, void *arg);
+static int __sev_do_snp_platform_status(struct sev_user_data_snp_status *status,
+					int *error);
 
 static struct notifier_block snp_panic_notifier = {
 	.notifier_call = snp_shutdown_on_panic,
@@ -1261,19 +1263,12 @@ static int snp_get_platform_data(struct sev_device *sev, int *error)
 {
 	struct sev_data_snp_feature_info snp_feat_info;
 	struct snp_feature_info *feat_info;
-	struct sev_data_snp_addr buf;
 	struct page *page;
 	int rc;
 
-	/*
-	 * This function is expected to be called before SNP is
-	 * initialized.
-	 */
-	if (sev->snp_initialized)
-		return -EINVAL;
+	lockdep_assert_held(&sev_cmd_mutex);
 
-	buf.address = __psp_pa(&sev->snp_plat_status);
-	rc = sev_do_cmd(SEV_CMD_SNP_PLATFORM_STATUS, &buf, error);
+	rc = __sev_do_snp_platform_status(&sev->snp_plat_status, error);
 	if (rc) {
 		dev_err(sev->dev, "SNP PLATFORM_STATUS command failed, ret = %d, error = %#x\n",
 			rc, *error);
@@ -1297,23 +1292,23 @@ static int snp_get_platform_data(struct sev_device *sev, int *error)
 	 * command to ensure structure is 8-byte aligned, and does not

drivers/crypto/ccp/sev-dev.c static int snp_get_platform_data(struct sev_device *sev, int *error) · line 1304

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +3 -3
 	 * cross a page boundary.
 	 */
-	page = alloc_page(GFP_KERNEL);
-	if (!page)
+	page = __snp_alloc_firmware_pages(GFP_KERNEL, 0, true);
+	feat_info =  page ? page_address(page) : NULL;
+	if (!feat_info)
 		return -ENOMEM;
 
-	feat_info = page_address(page);

This is a bit of churn when all you have to do is replace alloc_page() with __snp_alloc_firmware_pages(), right?

Thanks,
Tom

Quoted 35 lines
drivers/crypto/ccp/sev-dev.c +5 -3
 	snp_feat_info.length = sizeof(snp_feat_info);
 	snp_feat_info.ecx_in = 0;
 	snp_feat_info.feature_info_paddr = __psp_pa(feat_info);
 
-	rc = sev_do_cmd(SEV_CMD_SNP_FEATURE_INFO, &snp_feat_info, error);
+	rc = __sev_do_cmd_locked(SEV_CMD_SNP_FEATURE_INFO, &snp_feat_info, error);
 	if (!rc)
 		sev->snp_feat_info_0 = *feat_info;
 	else
 		dev_err(sev->dev, "SNP FEATURE_INFO command failed, ret = %d, error = %#x\n",
 			rc, *error);
 
-	__free_page(page);
+	__snp_free_firmware_pages(page, 0, true);
 
 	return rc;
 }
@@ -2097,6 +2092,8 @@ static int sev_get_api_version(void)
 	struct sev_user_data_status status;
 	int error = 0, ret;
 
+	guard(mutex)(&sev_cmd_mutex);
+
 	/*
 	 * Cache SNP platform status and SNP feature information
 	 * if SNP is available.
@@ -2107,7 +2104,7 @@ static int sev_get_api_version(void)
 			return 1;
 	}
 
-	ret = sev_platform_status(&status, &error);
+	ret = __sev_do_cmd_locked(SEV_CMD_PLATFORM_STATUS, &status, &error);
 	if (ret) {
 		dev_err(sev->dev,
 			"SEV: failed to get status. Error: %#x\n", error);
#7 lore raw

Re: [Patch v3 5/7] crypto: ccp - Allow SNP platform data to be queried after SNP INIT

Quoted 79 lines · Tom Lendacky
Quoted 20 lines · Pratik R. Sampat

In preparation for refreshing the cached SNP platform status and feature information after a successful firmware live update from DOWNLOAD_FIRMWARE_EX, allow snp_get_platform_data() to be called while the SNP firmware is in the INIT state.

Once SNP is initialized the firmware requires the output page of both commands to be firmware-owned. sev->snp_plat_status cannot satisfy that as it is embedded in struct sev_device, so use __sev_do_snp_platform_status(), which stages the output through a dedicated page. Allocate the SNP_FEATURE_INFO output page with __snp_alloc_firmware_pages(), which transitions the page to firmware-owned when SNP is initialized, and release it with __snp_free_firmware_pages(), which reclaims it before freeing.

Typically this was only ever exercised during initialization. However DOWNLOAD_FIRMWARE_EX can change that. Live firmware update can race with IOCTLs such as SEV_FACTORY_RESET which could cause operations performed on incorrect cached platform states. Avoid this race by wrapping the function under the sev_cmd_mutex.

Quoted 3 lines

Just a minor comment below.

Quoted 50 lines
---
 drivers/crypto/ccp/sev-dev.c | 27 ++++++++++++---------------
 1 file changed, 12 insertions(+), 15 deletions(-)
drivers/crypto/ccp/sev-dev.c +7 -11
index 1ed9e61a95cc..c40401c861d3 100644
@@ -131,6 +131,8 @@ static void __sev_firmware_shutdown(struct sev_device *sev, bool panic);
 
 static int snp_shutdown_on_panic(struct notifier_block *nb,
 				 unsigned long reason, void *arg);
+static int __sev_do_snp_platform_status(struct sev_user_data_snp_status *status,
+					int *error);
 
 static struct notifier_block snp_panic_notifier = {
 	.notifier_call = snp_shutdown_on_panic,
@@ -1261,19 +1263,12 @@ static int snp_get_platform_data(struct sev_device *sev, int *error)
 {
 	struct sev_data_snp_feature_info snp_feat_info;
 	struct snp_feature_info *feat_info;
-	struct sev_data_snp_addr buf;
 	struct page *page;
 	int rc;
 
-	/*
-	 * This function is expected to be called before SNP is
-	 * initialized.
-	 */
-	if (sev->snp_initialized)
-		return -EINVAL;
+	lockdep_assert_held(&sev_cmd_mutex);
 
-	buf.address = __psp_pa(&sev->snp_plat_status);
-	rc = sev_do_cmd(SEV_CMD_SNP_PLATFORM_STATUS, &buf, error);
+	rc = __sev_do_snp_platform_status(&sev->snp_plat_status, error);
 	if (rc) {
 		dev_err(sev->dev, "SNP PLATFORM_STATUS command failed, ret = %d, error = %#x\n",
 			rc, *error);
@@ -1297,23 +1292,23 @@ static int snp_get_platform_data(struct sev_device *sev, int *error)
 	 * command to ensure structure is 8-byte aligned, and does not
 	 * cross a page boundary.
 	 */
-	page = alloc_page(GFP_KERNEL);
-	if (!page)
+	page = __snp_alloc_firmware_pages(GFP_KERNEL, 0, true);
+	feat_info =  page ? page_address(page) : NULL;
+	if (!feat_info)
 		return -ENOMEM;
 
Quoted 5 lines

drivers/crypto/ccp/sev-dev.c static int snp_get_platform_data(struct sev_device *sev, int *error) · line 1304

Quoted 1 line
drivers/crypto/ccp/sev-dev.c +0 -1
-	feat_info = page_address(page);

This is a bit of churn when all you have to do is replace alloc_page() with __snp_alloc_firmware_pages(), right?

I'll clean that right up!

Thanks,
--Pratik

Quoted 39 lines

Thanks,
Tom

Quoted 35 lines
drivers/crypto/ccp/sev-dev.c +5 -3
 	snp_feat_info.length = sizeof(snp_feat_info);
 	snp_feat_info.ecx_in = 0;
 	snp_feat_info.feature_info_paddr = __psp_pa(feat_info);
 
-	rc = sev_do_cmd(SEV_CMD_SNP_FEATURE_INFO, &snp_feat_info, error);
+	rc = __sev_do_cmd_locked(SEV_CMD_SNP_FEATURE_INFO, &snp_feat_info, error);
 	if (!rc)
 		sev->snp_feat_info_0 = *feat_info;
 	else
 		dev_err(sev->dev, "SNP FEATURE_INFO command failed, ret = %d, error = %#x\n",
 			rc, *error);
 
-	__free_page(page);
+	__snp_free_firmware_pages(page, 0, true);
 
 	return rc;
 }
@@ -2097,6 +2092,8 @@ static int sev_get_api_version(void)
 	struct sev_user_data_status status;
 	int error = 0, ret;
 
+	guard(mutex)(&sev_cmd_mutex);
+
 	/*
 	 * Cache SNP platform status and SNP feature information
 	 * if SNP is available.
@@ -2107,7 +2104,7 @@ static int sev_get_api_version(void)
 			return 1;
 	}
 
-	ret = sev_platform_status(&status, &error);
+	ret = __sev_do_cmd_locked(SEV_CMD_PLATFORM_STATUS, &status, &error);
 	if (ret) {
 		dev_err(sev->dev,
 			"SEV: failed to get status. Error: %#x\n", error);
#8 lore raw

PATCH v3 6/7 crypto/ccp: Register with fw_uploader and always fail

In reply to Pratik R. Sampat

In preparation for SEV-SNP DOWNLOAD_FIRMWARE_EX live firmware update support, add an 'sev' firmware loader that always fails with EBUSY.

---
 drivers/crypto/ccp/sev-dev.c | 80 ++++++++++++++++++++++++++++++++++++
 drivers/crypto/ccp/sev-dev.h |  2 +
 2 files changed, 82 insertions(+)
drivers/crypto/ccp/sev-dev.c +80 -0
index c40401c861d3..88cf60a9640e 100644
@@ -2212,6 +2212,80 @@ static int sev_update_firmware(struct device *dev)
 	return ret;
 }
 
+#ifdef CONFIG_FW_UPLOAD
+static enum fw_upload_err sev_fw_upload_prepare(struct fw_upload *fw_upload,
+						const u8 *data, u32 size)
+{
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err sev_fw_upload_write(struct fw_upload *fw_upload,
+					      const u8 *data, u32 offset,
+					      u32 size, u32 *written)
+{
+	return FW_UPLOAD_ERR_BUSY;
+}
+
+static enum fw_upload_err sev_fw_upload_poll_complete(struct fw_upload *fw_upload)
+{
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static void sev_fw_upload_cancel(struct fw_upload *fw_upload)
+{
+	/* intentional no-op */
+}
+
+static const struct fw_upload_ops sev_fw_upload_ops = {
+	.prepare = sev_fw_upload_prepare,
+	.write = sev_fw_upload_write,
+	.poll_complete = sev_fw_upload_poll_complete,
+	.cancel = sev_fw_upload_cancel,
+};
+
+/* Serializes registration and unregistration of sev->fwl */
+static DEFINE_MUTEX(sev_fwl_lock);
+
+static void register_sev_fw_uploader(struct sev_device *sev)
+{
+	struct fw_upload *fwl;
+
+	guard(mutex)(&sev_fwl_lock);
+
+	/*
+	 * The interface only drives DOWNLOAD_FIRMWARE_EX, which is an SNP
+	 * command, so do not advertise it where SNP is unavailable or the
+	 * interface already exists.
+	 */
+	if (!cc_platform_has(CC_ATTR_HOST_SEV_SNP) || sev->fwl)
+		return;
+
+	fwl = firmware_upload_register(sev->dev, "sev", &sev_fw_upload_ops,
+				       sev);
+	if (IS_ERR(fwl)) {
+		dev_err(sev->dev, "SEV firmware upload registration failure: %ld\n",
+			PTR_ERR(fwl));
+		return;
+	}
+
+	sev->fwl = fwl;
+}
+
+static void unregister_sev_fw_uploader(struct sev_device *sev)
+{
+	guard(mutex)(&sev_fwl_lock);
+
+	if (!sev->fwl)
+		return;
+
+	firmware_upload_unregister(sev->fwl);
+	sev->fwl = NULL;
+}
+#else	/* CONFIG_FW_UPLOAD */
+static void register_sev_fw_uploader(struct sev_device *sev) { }
+static void unregister_sev_fw_uploader(struct sev_device *sev) { }
+#endif	/* CONFIG_FW_UPLOAD */
+
 static int __sev_snp_shutdown_locked(int *error, bool panic)
 {
 	struct psp_device *psp = psp_master;
@@ -3018,6 +3092,8 @@ void sev_dev_destroy(struct psp_device *psp)
 	if (!sev)
 		return;
 
+	unregister_sev_fw_uploader(sev);
+
 	sev_firmware_shutdown(sev);
 
 	if (sev->misc)
@@ -3081,6 +3157,8 @@ void sev_pci_init(void)
 			 api_major, api_minor, build,
 			 sev->api_major, sev->api_minor, sev->build);
 
+	register_sev_fw_uploader(sev);
+
 	return;
 
 err:
@@ -3096,6 +3174,8 @@ void sev_pci_exit(void)
 	if (!sev)
 		return;
 
+	unregister_sev_fw_uploader(sev);
+
 	sev_firmware_shutdown(sev);
 }
 
drivers/crypto/ccp/sev-dev.h +2 -0
index d5e596606def..7ec692e2147e 100644
@@ -69,6 +69,8 @@ struct sev_device {
 
 	struct tsm_dev *tsmdev;
 	struct sev_tio_status *tio_status;
+
+	struct fw_upload *fwl;
 };
 
 int sev_dev_init(struct psp_device *psp);
Signature · 1 line
2.43.0
#9 lore raw

Re: [Patch v3 6/7] crypto/ccp: Register with fw_uploader and always fail

Quoted 1 line · Pratik R. Sampat

+#ifdef CONFIG_FW_UPLOAD

[...]

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2287

Quoted 4 lines
drivers/crypto/ccp/sev-dev.c +4 -0
+#else	/* CONFIG_FW_UPLOAD */
+static void register_sev_fw_uploader(struct sev_device *sev) { }
+static void unregister_sev_fw_uploader(struct sev_device *sev) { }
+#endif	/* CONFIG_FW_UPLOAD */

There's no Kconfig change in this patch. Should CRYPTO_DEV_SP_PSP select FW_UPLOAD? It defaults to N, and the other fw_upload users select it.

Thanks,
Shantanu

#10 lore raw

Re: [Patch v3 6/7] crypto/ccp: Register with fw_uploader and always fail

Quoted 11 lines · Shantanu Sinha
Quoted 1 line · Pratik R. Sampat

+#ifdef CONFIG_FW_UPLOAD

[...]

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2287

Quoted 4 lines
drivers/crypto/ccp/sev-dev.c +4 -0
+#else	/* CONFIG_FW_UPLOAD */
+static void register_sev_fw_uploader(struct sev_device *sev) { }
+static void unregister_sev_fw_uploader(struct sev_device *sev) { }
+#endif	/* CONFIG_FW_UPLOAD */

There's no Kconfig change in this patch. Should CRYPTO_DEV_SP_PSP select FW_UPLOAD? It defaults to N, and the other fw_upload users select it.

Sure, that makes sense. I can select both FW_UPLOAD and FW_LOADER when this config option is flipped.

Thanks!
Pratik

maintainer Reviewed-by on v3 6/7
#11 lore raw

Re: [Patch v3 6/7] crypto/ccp: Register with fw_uploader and always fail

In reply to Pratik R. Sampat
Quoted 6 lines · Pratik R. Sampat

In preparation for SEV-SNP DOWNLOAD_FIRMWARE_EX live firmware update support, add an 'sev' firmware loader that always fails with EBUSY.

Just some minor comments below.

Quoted 53 lines
---
 drivers/crypto/ccp/sev-dev.c | 80 ++++++++++++++++++++++++++++++++++++
 drivers/crypto/ccp/sev-dev.h |  2 +
 2 files changed, 82 insertions(+)
drivers/crypto/ccp/sev-dev.c +40 -0
index c40401c861d3..88cf60a9640e 100644
@@ -2212,6 +2212,80 @@ static int sev_update_firmware(struct device *dev)
 	return ret;
 }
 
+#ifdef CONFIG_FW_UPLOAD
+static enum fw_upload_err sev_fw_upload_prepare(struct fw_upload *fw_upload,
+						const u8 *data, u32 size)
+{
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err sev_fw_upload_write(struct fw_upload *fw_upload,
+					      const u8 *data, u32 offset,
+					      u32 size, u32 *written)
+{
+	return FW_UPLOAD_ERR_BUSY;
+}
+
+static enum fw_upload_err sev_fw_upload_poll_complete(struct fw_upload *fw_upload)
+{
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static void sev_fw_upload_cancel(struct fw_upload *fw_upload)
+{
+	/* intentional no-op */
+}
+
+static const struct fw_upload_ops sev_fw_upload_ops = {
+	.prepare = sev_fw_upload_prepare,
+	.write = sev_fw_upload_write,
+	.poll_complete = sev_fw_upload_poll_complete,
+	.cancel = sev_fw_upload_cancel,
+};
+
+/* Serializes registration and unregistration of sev->fwl */
+static DEFINE_MUTEX(sev_fwl_lock);
+
+static void register_sev_fw_uploader(struct sev_device *sev)
+{
+	struct fw_upload *fwl;
+
+	guard(mutex)(&sev_fwl_lock);
+

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2264

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+	/*
+	 * The interface only drives DOWNLOAD_FIRMWARE_EX, which is an SNP
+	 * command, so do not advertise it where SNP is unavailable or the
+	 * interface already exists.
+	 */
+	if (!cc_platform_has(CC_ATTR_HOST_SEV_SNP) || sev->fwl)
+		return;
+
+	fwl = firmware_upload_register(sev->dev, "sev", &sev_fw_upload_ops,
+				       sev);

Make this all one line.

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2267

Quoted 3 lines
drivers/crypto/ccp/sev-dev.c +3 -0
+	if (IS_ERR(fwl)) {
+		dev_err(sev->dev, "SEV firmware upload registration failure: %ld\n",
+			PTR_ERR(fwl));

and this one.

Thanks,
Tom

Quoted 64 lines
drivers/crypto/ccp/sev-dev.c +27 -0
+		return;
+	}
+
+	sev->fwl = fwl;
+}
+
+static void unregister_sev_fw_uploader(struct sev_device *sev)
+{
+	guard(mutex)(&sev_fwl_lock);
+
+	if (!sev->fwl)
+		return;
+
+	firmware_upload_unregister(sev->fwl);
+	sev->fwl = NULL;
+}
+#else	/* CONFIG_FW_UPLOAD */
+static void register_sev_fw_uploader(struct sev_device *sev) { }
+static void unregister_sev_fw_uploader(struct sev_device *sev) { }
+#endif	/* CONFIG_FW_UPLOAD */
+
 static int __sev_snp_shutdown_locked(int *error, bool panic)
 {
 	struct psp_device *psp = psp_master;
@@ -3018,6 +3092,8 @@ void sev_dev_destroy(struct psp_device *psp)
 	if (!sev)
 		return;
 
+	unregister_sev_fw_uploader(sev);
+
 	sev_firmware_shutdown(sev);
 
 	if (sev->misc)
@@ -3081,6 +3157,8 @@ void sev_pci_init(void)
 			 api_major, api_minor, build,
 			 sev->api_major, sev->api_minor, sev->build);
 
+	register_sev_fw_uploader(sev);
+
 	return;
 
 err:
@@ -3096,6 +3174,8 @@ void sev_pci_exit(void)
 	if (!sev)
 		return;
 
+	unregister_sev_fw_uploader(sev);
+
 	sev_firmware_shutdown(sev);
 }
 
drivers/crypto/ccp/sev-dev.h +2 -0
index d5e596606def..7ec692e2147e 100644
@@ -69,6 +69,8 @@ struct sev_device {
 
 	struct tsm_dev *tsmdev;
 	struct sev_tio_status *tio_status;
+
+	struct fw_upload *fwl;
 };
 
 int sev_dev_init(struct psp_device *psp);
#12 lore raw

PATCH v3 7/7 crypto/ccp: Implement SNP Download Firmware EX

In reply to Pratik R. Sampat

Implement SNP live firmware update using the DOWNLOAD_FIRMWARE_EX command.

DOWNLOAD_FIRMWARE_EX requires the legacy SEV platform to be UNINIT. If it is WORKING then legacy guests are running and the update is refused as busy. If it is INIT, shut it down, release the buffers the firmware owns across that shutdown, run the update, and bring the platform back up afterwards. SNP is never taken down, so SNP guests are unaffected.

To test run the following with your sbin file in FW:

echo 1 > /sys/class/firmware/sev/loading cat <firmware.sbin> > /sys/class/firmware/sev/data echo 0 > /sys/class/firmware/sev/loading

The COMMIT bit is left clear, so the image is only loaded provisionally and the admin decides when to make it permanent with ioctl(/dev/sev, SNP_COMMIT). To roll back, do not commit and upload the previous image the same way.

---
 drivers/crypto/ccp/sev-dev.c | 284 ++++++++++++++++++++++++++++++++++-
 drivers/crypto/ccp/sev-dev.h |   2 +
 include/linux/psp-sev.h      |  19 +++
 3 files changed, 304 insertions(+), 1 deletion(-)
drivers/crypto/ccp/sev-dev.c +283 -1
index 88cf60a9640e..e54f23ba1b9b 100644
@@ -29,6 +29,7 @@
 #include <linux/psp.h>
 #include <linux/amd-iommu.h>
 #include <linux/crash_dump.h>
+#include <linux/sizes.h>
 
 #include <asm/smp.h>
 #include <asm/cacheflush.h>
@@ -252,6 +253,7 @@ static int sev_cmd_buffer_len(int cmd)
 	case SEV_CMD_SNP_PLATFORM_STATUS:	return sizeof(struct sev_data_snp_addr);
 	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_DOWNLOAD_FIRMWARE_EX:	return sizeof(struct sev_data_download_firmware_ex);
 	case SEV_CMD_SNP_COMMIT:		return sizeof(struct sev_data_snp_commit);
 	case SEV_CMD_SNP_FEATURE_INFO:		return sizeof(struct sev_data_snp_feature_info);
 	case SEV_CMD_SNP_VLEK_LOAD:		return sizeof(struct sev_user_data_snp_vlek_load);
@@ -2213,17 +2215,297 @@ static int sev_update_firmware(struct device *dev)
 }
 
 #ifdef CONFIG_FW_UPLOAD
+/* Largest image the firmware accepts, anything above is rejected */
+#define SEV_FW_IMAGE_MAX_SIZE	SZ_512K
+
 static enum fw_upload_err sev_fw_upload_prepare(struct fw_upload *fw_upload,
 						const u8 *data, u32 size)
 {
+	struct sev_device *sev = fw_upload->dd_handle;
+
+	if (size > SEV_FW_IMAGE_MAX_SIZE) {
+		dev_err(sev->dev, "DLFW_EX: image of %u bytes exceeds the %u byte maximum\n",
+			size, SEV_FW_IMAGE_MAX_SIZE);
+		return FW_UPLOAD_ERR_INVALID_SIZE;
+	}
+
 	return FW_UPLOAD_ERR_NONE;
 }
 
+static int sev_download_firmware_ex(const u8 *data, u32 size, int *psp_ret)
+{
+	struct sev_data_download_firmware_ex sev_data = {0};
+	int ret, order;
+	struct page *p;
+	void *fw_blob;
+
+	order = get_order(size);
+	p = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
+	if (!p)
+		return -ENOMEM;
+
+	fw_blob = page_address(p);
+	memcpy(fw_blob, data, size);
+
+	sev_data.len = sizeof(sev_data);
+	sev_data.fw_paddr = __psp_pa(fw_blob);
+	sev_data.fw_len = size;
+	/*
+	 * Don't commit to the new firmware immediately, perform an explicit
+	 * SNP_COMMIT after
+	 */
+	sev_data.commit = 0;
+
+	ret = __sev_do_cmd_locked(SEV_CMD_SNP_DOWNLOAD_FIRMWARE_EX, &sev_data,
+				  psp_ret);
+
+	__free_pages(p, order);
+
+	return ret;
+}
+
+static enum fw_upload_err sev_fw_upload_handle_err(struct sev_device *sev,
+						   int rc, int psp_ret)
+{
+	enum fw_upload_err ret = FW_UPLOAD_ERR_FW_INVALID;
+	const char *msg;
+
+	if (!rc)
+		return FW_UPLOAD_ERR_NONE;
+
+	/*
+	 * The command timed out: psp_ret was cleared and the PSP was declared
+	 * dead, so there is no firmware status to decode.
+	 */
+	if (psp_dead) {
+		dev_err(sev->dev, "DLFW_EX: PSP not responding (failed %d, error %#x)\n",
+			rc, psp_ret);
+		sev->fwl_rollback_required = false;
+
+		return FW_UPLOAD_ERR_TIMEOUT;
+	}
+
+	switch (psp_ret) {
+	case SEV_RET_INVALID_PARAM:
+		msg = "Provided image is not well formed";
+		break;
+	case SEV_RET_INVALID_LEN:
+		ret = FW_UPLOAD_ERR_INVALID_SIZE;
+		msg = "Provided image has an unusable length";
+		break;
+	case SEV_RET_SHUTDOWN_REQUIRED:
+		msg = "Provided image cannot be live-updated, shutdown required";
+		break;
+	case SEV_RET_BAD_VERSION:
+		msg = "Provided image < committed version";
+		break;
+	case SEV_RET_INVALID_PLATFORM_STATE:
+		msg = "Platform not in UNINIT state";
+		break;
+	case SEV_RET_INVALID_ADDRESS:
+		msg = "Unaligned address provided";
+		break;
+	case SEV_RET_UNSUPPORTED:
+		msg = "Feature not supported";
+		break;
+	case SEV_RET_INVALID_CONFIG:
+		msg = "Image rejected, unsupported configuration";
+		break;
+	case SEV_RET_BAD_SVN:
+		msg = "Image rejected, SVN < committed SVN";
+		break;
+	case SEV_RET_BAD_SIGNATURE:
+		msg = "Bad firmware signature";
+		break;
+	case SEV_RET_UPDATE_FAILED:
+		/* The previous firmware is still running, a retry is safe. */
+		ret = FW_UPLOAD_ERR_BUSY;
+		msg = "Upgrade failed, automatically reverted";
+		break;
+	case SEV_RET_RESTORE_REQUIRED:
+		/*
+		 * Firmware requested a roll-back. Declare the PSP dead so
+		 * nothing else tries to use it, and let the next upload through
+		 * so the admin can restore the previous image.
+		 */
+		sev->fwl_rollback_required = true;
+		psp_dead = true;
+		ret = FW_UPLOAD_ERR_RW_ERROR;
+		msg = "Live upgrade failed, please roll back";
+		break;
+	case SEV_RET_HWSEV_RET_UNSAFE:
+		/*
+		 * Following a return of HARDWARE_UNSAFE, operation of the SEV
+		 * firmware is indeterminate and the recommendation is to reboot
+		 * the platform. Declare the PSP dead so the driver stops
+		 * issuing commands to it while the reboot is pending.
+		 */
+		sev->fwl_rollback_required = false;
+		psp_dead = true;
+		ret = FW_UPLOAD_ERR_HW_ERROR;
+		msg = "SEV firmware no longer safe. Reboot recommended";
+		break;
+	case SEV_RET_NO_FW_CALL:
+		/* The command never reached the firmware. */
+		ret = FW_UPLOAD_ERR_BUSY;
+		msg = "Driver error";
+		break;
+	default:
+		ret = FW_UPLOAD_ERR_HW_ERROR;
+		msg = "Unknown SEV firmware error";
+		break;
+	}
+
+	dev_err(sev->dev, "DLFW_EX: %s (failed %d, error %#x)\n", msg, rc, psp_ret);
+
+	return ret;
+}
+
+static int sev_fw_upload_shutdown_platform(struct sev_device *sev)
+{
+	int rc, error = SEV_RET_NO_FW_CALL, sev_plat_state;
+
+	lockdep_assert_held(&sev_cmd_mutex);
+
+	rc = sev_get_platform_state(&sev_plat_state, &error);
+	if (rc) {
+		dev_err(sev->dev, "SEV get platform state failed %d, error %#x\n",
+			rc, error);
+		return rc;
+	}
+
+	switch (sev_plat_state) {
+	case SEV_STATE_UNINIT:
+		return 0;
+	case SEV_STATE_WORKING:
+		/* Legacy guests are running, the update cannot proceed. */
+		return -EBUSY;
+	case SEV_STATE_INIT:
+		break;
+	default:
+		dev_err(sev->dev, "Unknown SEV firmware state %d\n", sev_plat_state);
+		return -EINVAL;
+	}
+
+	rc = __sev_platform_shutdown_locked(&error);
+	if (rc) {
+		dev_err(sev->dev, "SEV platform shutdown failed %d, error %#x\n",
+			rc, error);
+		return rc;
+	}
+
+	__sev_release_firmware_buffers(false);
+
+	sev->fwl_reinit_required = true;
+
+	return 0;
+}
+
+static void sev_fw_upload_reinit_platform(struct sev_device *sev)
+{
+	int rc, error = SEV_RET_NO_FW_CALL;
+
+	lockdep_assert_held(&sev_cmd_mutex);
+
+	if (!sev->fwl_reinit_required)
+		return;
+
+	rc = __sev_platform_init_locked(&error);
+	if (rc) {
+		dev_err(sev->dev, "SEV platform re-init failed %d, error %#x\n",
+			rc, error);
+		return;
+	}
+
+	sev->fwl_reinit_required = false;
+}
+
+static enum fw_upload_err sev_fw_upload_update(struct sev_device *sev,
+					       const u8 *data, u32 size,
+					       u32 *written)
+{
+	int rc, error = SEV_RET_NO_FW_CALL;
+	enum fw_upload_err ret;
+
+	guard(mutex)(&sev_cmd_mutex);
+
+	/*
+	 * A PSP declared dead only executes DOWNLOAD_FIRMWARE_EX if it was the
+	 * firmware update that killed it and asked for a rollback. Declared
+	 * dead for any other reason it will not answer until the platform is
+	 * rebooted.
+	 */
+	if (psp_dead && !sev->fwl_rollback_required) {
+		dev_err(sev->dev, "DLFW_EX: PSP is not responding\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	/*
+	 * If the last firmware update returned RESTORE_REQUIRED, retry DLFW_EX.
+	 * We being in this state means that the legacy firmware has previously
+	 * been shut down, so no need to do it again.
+	 */
+	if (sev->fwl_rollback_required) {
+		psp_dead = false;
+	} else {
+		rc = sev_fw_upload_shutdown_platform(sev);
+		if (rc) {
+			return psp_dead ? FW_UPLOAD_ERR_HW_ERROR
+					: FW_UPLOAD_ERR_BUSY;
+		}
+	}
+
+	rc = sev_download_firmware_ex(data, size, &error);
+	ret = sev_fw_upload_handle_err(sev, rc, error);
+	if (ret == FW_UPLOAD_ERR_NONE) {
+		*written = size;
+		sev->fwl_rollback_required = false;
+	}
+
+	/* A rollback retry failed. PSP now stays dead */
+	if (sev->fwl_rollback_required) {
+		psp_dead = true;
+		if (ret != FW_UPLOAD_ERR_HW_ERROR)
+			ret = FW_UPLOAD_ERR_RW_ERROR;
+	}
+
+	if (!sev->fwl_rollback_required && !psp_dead)
+		sev_fw_upload_reinit_platform(sev);
+
+	return ret;
+}
+
 static enum fw_upload_err sev_fw_upload_write(struct fw_upload *fw_upload,
 					      const u8 *data, u32 offset,
 					      u32 size, u32 *written)
 {
-	return FW_UPLOAD_ERR_BUSY;
+	struct sev_device *sev = fw_upload->dd_handle;
+	u8 old_major, old_minor, old_build;
+	enum fw_upload_err ret;
+
+	old_major = sev->api_major;
+	old_minor = sev->api_minor;
+	old_build = sev->build;
+
+	ret = sev_fw_upload_update(sev, data, size, written);
+	if (ret != FW_UPLOAD_ERR_NONE)
+		return ret;
+
+	if (sev_get_api_version()) {
+		dev_err(sev->dev, "SNP platform data refresh after firmware update failed\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	if (sev->api_major != old_major || sev->api_minor != old_minor ||
+	    sev->build != old_build) {
+		dev_info(sev->dev, "SEV firmware updated to %d.%d build %d\n",
+			 sev->api_major, sev->api_minor, sev->build);
+	} else {
+		dev_info(sev->dev, "SEV firmware version unchanged: %d.%d build %d\n",
+			 sev->api_major, sev->api_minor, sev->build);
+	}
+
+	return ret;
 }
 
 static enum fw_upload_err sev_fw_upload_poll_complete(struct fw_upload *fw_upload)
drivers/crypto/ccp/sev-dev.h +2 -0
index 7ec692e2147e..1e45a08c41da 100644
@@ -71,6 +71,8 @@ struct sev_device {
 	struct sev_tio_status *tio_status;
 
 	struct fw_upload *fwl;
+	bool fwl_rollback_required;
+	bool fwl_reinit_required;
 };
 
 int sev_dev_init(struct psp_device *psp);
include/linux/psp-sev.h +19 -0
index fab62228f981..b71154e9ae4b 100644
@@ -890,6 +890,25 @@ struct sev_platform_init_args {
 	unsigned int max_snp_asid;
 };
 
+/**
+ * struct sev_data_download_firmware_ex - SNP_DOWNLOAD_FIRMWARE_EX structure
+ *
+ * @len: length of the command buffer read by the PSP
+ * @rsvd0: reserved
+ * @fw_paddr: system physical address of the start of the firmware blob
+ * @fw_len: length of the firmware blob
+ * @commit: whether to immediately commit the firmware update
+ * @rsvd1: reserved
+ */
+struct sev_data_download_firmware_ex {
+	u32 len;			/* In */
+	u32 rsvd0;
+	u64 fw_paddr;			/* In */
+	u32 fw_len;			/* In */
+	u32 commit:1;			/* In */
+	u32 rsvd1:31;
+} __packed;
+
 /**
  * struct sev_data_snp_commit - SNP_COMMIT structure
  *
Signature · 1 line
2.43.0
maintainer
#13 lore raw

Re: [Patch v3 7/7] crypto/ccp: Implement SNP Download Firmware EX

Quoted 50 lines · Pratik R. Sampat

Implement SNP live firmware update using the DOWNLOAD_FIRMWARE_EX command.

DOWNLOAD_FIRMWARE_EX requires the legacy SEV platform to be UNINIT. If it is WORKING then legacy guests are running and the update is refused as busy. If it is INIT, shut it down, release the buffers the firmware owns across that shutdown, run the update, and bring the platform back up afterwards. SNP is never taken down, so SNP guests are unaffected.

To test run the following with your sbin file in FW:

echo 1 > /sys/class/firmware/sev/loading cat <firmware.sbin> > /sys/class/firmware/sev/data echo 0 > /sys/class/firmware/sev/loading

The COMMIT bit is left clear, so the image is only loaded provisionally and the admin decides when to make it permanent with ioctl(/dev/sev, SNP_COMMIT). To roll back, do not commit and upload the previous image the same way.

---
 drivers/crypto/ccp/sev-dev.c | 284 ++++++++++++++++++++++++++++++++++-
 drivers/crypto/ccp/sev-dev.h |   2 +
 include/linux/psp-sev.h      |  19 +++
 3 files changed, 304 insertions(+), 1 deletion(-)
drivers/crypto/ccp/sev-dev.c +2 -0
index 88cf60a9640e..e54f23ba1b9b 100644
@@ -29,6 +29,7 @@
 #include <linux/psp.h>
 #include <linux/amd-iommu.h>
 #include <linux/crash_dump.h>
+#include <linux/sizes.h>
 
 #include <asm/smp.h>
 #include <asm/cacheflush.h>
@@ -252,6 +253,7 @@ static int sev_cmd_buffer_len(int cmd)
 	case SEV_CMD_SNP_PLATFORM_STATUS:	return sizeof(struct sev_data_snp_addr);
 	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_DOWNLOAD_FIRMWARE_EX:	return sizeof(struct sev_data_download_firmware_ex);
 	case SEV_CMD_SNP_COMMIT:		return sizeof(struct sev_data_snp_commit);
 	case SEV_CMD_SNP_FEATURE_INFO:		return sizeof(struct sev_data_snp_feature_info);
 	case SEV_CMD_SNP_VLEK_LOAD:		return sizeof(struct sev_user_data_snp_vlek_load);

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2218

Quoted 4 lines
drivers/crypto/ccp/sev-dev.c +1 -0
@@ -2213,17 +2215,297 @@ static int sev_update_firmware(struct device *dev)
 }
 
 #ifdef CONFIG_FW_UPLOAD
+/* Largest image the firmware accepts, anything above is rejected */

I may have missed it, but I don't see anything in the SNP ABI spec that says the limit is 512K. If that doesn't have a limit how did we arrive at 512K?

Quoted 27 lines
drivers/crypto/ccp/sev-dev.c +21 -0
+#define SEV_FW_IMAGE_MAX_SIZE	SZ_512K
+
 static enum fw_upload_err sev_fw_upload_prepare(struct fw_upload *fw_upload,
 						const u8 *data, u32 size)
 {
+	struct sev_device *sev = fw_upload->dd_handle;
+
+	if (size > SEV_FW_IMAGE_MAX_SIZE) {
+		dev_err(sev->dev, "DLFW_EX: image of %u bytes exceeds the %u byte maximum\n",
+			size, SEV_FW_IMAGE_MAX_SIZE);
+		return FW_UPLOAD_ERR_INVALID_SIZE;
+	}
+
 	return FW_UPLOAD_ERR_NONE;
 }
 
+static int sev_download_firmware_ex(const u8 *data, u32 size, int *psp_ret)
+{
+	struct sev_data_download_firmware_ex sev_data = {0};
+	int ret, order;
+	struct page *p;
+	void *fw_blob;
+
+	order = get_order(size);
+	p = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
+	if (!p)
+		return -ENOMEM;

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2255

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+
+	fw_blob = page_address(p);
+	memcpy(fw_blob, data, size);
+
+	sev_data.len = sizeof(sev_data);
+	sev_data.fw_paddr = __psp_pa(fw_blob);
+	sev_data.fw_len = size;
+	/*
+	 * Don't commit to the new firmware immediately, perform an explicit
+	 * SNP_COMMIT after

Don't commit the new firmware, an explict SNP_COMMIT is required after update.

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2268

Quoted 13 lines
drivers/crypto/ccp/sev-dev.c +13 -0
+	 */
+	sev_data.commit = 0;
+
+	ret = __sev_do_cmd_locked(SEV_CMD_SNP_DOWNLOAD_FIRMWARE_EX, &sev_data,
+				  psp_ret);
+
+	__free_pages(p, order);
+
+	return ret;
+}
+
+static enum fw_upload_err sev_fw_upload_handle_err(struct sev_device *sev,
+						   int rc, int psp_ret)

Name rc something more specific, like cmd_ret, to better distinguish what you're checking.

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2279

Quoted 11 lines
drivers/crypto/ccp/sev-dev.c +11 -0
+{
+	enum fw_upload_err ret = FW_UPLOAD_ERR_FW_INVALID;
+	const char *msg;
+
+	if (!rc)
+		return FW_UPLOAD_ERR_NONE;
+
+	/*
+	 * The command timed out: psp_ret was cleared and the PSP was declared
+	 * dead, so there is no firmware status to decode.
+	 */

Move this comment into the if block as it is explaining what happened if psp_dead is set and simplify it:

"The SEV command timed out and marked the ASP dead, there is no status to decode."

Quoted 70 lines
drivers/crypto/ccp/sev-dev.c +70 -0
+	if (psp_dead) {
+		dev_err(sev->dev, "DLFW_EX: PSP not responding (failed %d, error %#x)\n",
+			rc, psp_ret);
+		sev->fwl_rollback_required = false;
+
+		return FW_UPLOAD_ERR_TIMEOUT;
+	}
+
+	switch (psp_ret) {
+	case SEV_RET_INVALID_PARAM:
+		msg = "Provided image is not well formed";
+		break;
+	case SEV_RET_INVALID_LEN:
+		ret = FW_UPLOAD_ERR_INVALID_SIZE;
+		msg = "Provided image has an unusable length";
+		break;
+	case SEV_RET_SHUTDOWN_REQUIRED:
+		msg = "Provided image cannot be live-updated, shutdown required";
+		break;
+	case SEV_RET_BAD_VERSION:
+		msg = "Provided image < committed version";
+		break;
+	case SEV_RET_INVALID_PLATFORM_STATE:
+		msg = "Platform not in UNINIT state";
+		break;
+	case SEV_RET_INVALID_ADDRESS:
+		msg = "Unaligned address provided";
+		break;
+	case SEV_RET_UNSUPPORTED:
+		msg = "Feature not supported";
+		break;
+	case SEV_RET_INVALID_CONFIG:
+		msg = "Image rejected, unsupported configuration";
+		break;
+	case SEV_RET_BAD_SVN:
+		msg = "Image rejected, SVN < committed SVN";
+		break;
+	case SEV_RET_BAD_SIGNATURE:
+		msg = "Bad firmware signature";
+		break;
+	case SEV_RET_UPDATE_FAILED:
+		/* The previous firmware is still running, a retry is safe. */
+		ret = FW_UPLOAD_ERR_BUSY;
+		msg = "Upgrade failed, automatically reverted";
+		break;
+	case SEV_RET_RESTORE_REQUIRED:
+		/*
+		 * Firmware requested a roll-back. Declare the PSP dead so
+		 * nothing else tries to use it, and let the next upload through
+		 * so the admin can restore the previous image.
+		 */
+		sev->fwl_rollback_required = true;
+		psp_dead = true;
+		ret = FW_UPLOAD_ERR_RW_ERROR;
+		msg = "Live upgrade failed, please roll back";
+		break;
+	case SEV_RET_HWSEV_RET_UNSAFE:
+		/*
+		 * Following a return of HARDWARE_UNSAFE, operation of the SEV
+		 * firmware is indeterminate and the recommendation is to reboot
+		 * the platform. Declare the PSP dead so the driver stops
+		 * issuing commands to it while the reboot is pending.
+		 */
+		sev->fwl_rollback_required = false;
+		psp_dead = true;
+		ret = FW_UPLOAD_ERR_HW_ERROR;
+		msg = "SEV firmware no longer safe. Reboot recommended";
+		break;
+	case SEV_RET_NO_FW_CALL:
+		/* The command never reached the firmware. */

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2359

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+		ret = FW_UPLOAD_ERR_BUSY;
+		msg = "Driver error";
+		break;
+	default:
+		ret = FW_UPLOAD_ERR_HW_ERROR;
+		msg = "Unknown SEV firmware error";
+		break;
+	}
+
+	dev_err(sev->dev, "DLFW_EX: %s (failed %d, error %#x)\n", msg, rc, psp_ret);

This is coming from userspace interaction, so probably should use ratelimited variant (here and any place you issue a message).

Quoted 28 lines
drivers/crypto/ccp/sev-dev.c +28 -0
+
+	return ret;
+}
+
+static int sev_fw_upload_shutdown_platform(struct sev_device *sev)
+{
+	int rc, error = SEV_RET_NO_FW_CALL, sev_plat_state;
+
+	lockdep_assert_held(&sev_cmd_mutex);
+
+	rc = sev_get_platform_state(&sev_plat_state, &error);
+	if (rc) {
+		dev_err(sev->dev, "SEV get platform state failed %d, error %#x\n",
+			rc, error);
+		return rc;
+	}
+
+	switch (sev_plat_state) {
+	case SEV_STATE_UNINIT:
+		return 0;
+	case SEV_STATE_WORKING:
+		/* Legacy guests are running, the update cannot proceed. */
+		return -EBUSY;
+	case SEV_STATE_INIT:
+		break;
+	default:
+		dev_err(sev->dev, "Unknown SEV firmware state %d\n", sev_plat_state);
+		return -EINVAL;

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2397

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+	}
+
+	rc = __sev_platform_shutdown_locked(&error);
+	if (rc) {
+		dev_err(sev->dev, "SEV platform shutdown failed %d, error %#x\n",
+			rc, error);
+		return rc;
+	}
+
+	__sev_release_firmware_buffers(false);

Do the buffers have to be released? If so, why? I think you can keep the allocations. During platform initialization the buffers will be detected. Is there a shutdown path where they might not get freed?

Quoted 9 lines
drivers/crypto/ccp/sev-dev.c +9 -0
+
+	sev->fwl_reinit_required = true;
+
+	return 0;
+}
+
+static void sev_fw_upload_reinit_platform(struct sev_device *sev)
+{
+	int rc, error = SEV_RET_NO_FW_CALL;

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2416

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+
+	lockdep_assert_held(&sev_cmd_mutex);
+
+	if (!sev->fwl_reinit_required)
+		return;
+
+	rc = __sev_platform_init_locked(&error);
+	if (rc) {
+		dev_err(sev->dev, "SEV platform re-init failed %d, error %#x\n",
+			rc, error);

Single line.

Quoted 11 lines
drivers/crypto/ccp/sev-dev.c +11 -0
+		return;
+	}
+
+	sev->fwl_reinit_required = false;
+}
+
+static enum fw_upload_err sev_fw_upload_update(struct sev_device *sev,
+					       const u8 *data, u32 size,
+					       u32 *written)
+{
+	int rc, error = SEV_RET_NO_FW_CALL;

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2437

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+	enum fw_upload_err ret;
+
+	guard(mutex)(&sev_cmd_mutex);
+
+	/*
+	 * A PSP declared dead only executes DOWNLOAD_FIRMWARE_EX if it was the
+	 * firmware update that killed it and asked for a rollback. Declared
+	 * dead for any other reason it will not answer until the platform is
+	 * rebooted.
+	 */

"SEV firmware will only successfully execute the DOWNLOAD_FIRMWARE_EX command if a firmware rollback is required. Other commands may be processed, but may not execute properly. Use the psp_dead boolean to restrict execution to this path."

Say something similar where psp_dead is being set to true in sev_fw_upload_handle_err().

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2444

Quoted 7 lines
drivers/crypto/ccp/sev-dev.c +7 -0
+	if (psp_dead && !sev->fwl_rollback_required) {
+		dev_err(sev->dev, "DLFW_EX: PSP is not responding\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	/*
+	 * If the last firmware update returned RESTORE_REQUIRED, retry DLFW_EX.

I see a mix of DOWNLOAD_FIRMWARE_EX and DLFW_EX, change these to all be the same name of your choice.

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2445

Quoted 1 line
drivers/crypto/ccp/sev-dev.c +1 -0
+	 * We being in this state means that the legacy firmware has previously

s/We being/Being/

Quoted 4 lines
drivers/crypto/ccp/sev-dev.c +4 -0
+	 * been shut down, so no need to do it again.
+	 */
+	if (sev->fwl_rollback_required) {
+		psp_dead = false;

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2459

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+	} else {
+		rc = sev_fw_upload_shutdown_platform(sev);
+		if (rc) {
+			return psp_dead ? FW_UPLOAD_ERR_HW_ERROR
+					: FW_UPLOAD_ERR_BUSY;
+		}
+	}
+
+	rc = sev_download_firmware_ex(data, size, &error);
+	ret = sev_fw_upload_handle_err(sev, rc, error);

Maybe it's just me, but using generic rc and ret can make this possibly confusing in the future. How about:

s/rc/cmd_ret/
s/ret/fwl_ret/
s/error/psp_ret/

Quoted 32 lines
drivers/crypto/ccp/sev-dev.c +27 -1
+	if (ret == FW_UPLOAD_ERR_NONE) {
+		*written = size;
+		sev->fwl_rollback_required = false;
+	}
+
+	/* A rollback retry failed. PSP now stays dead */
+	if (sev->fwl_rollback_required) {
+		psp_dead = true;
+		if (ret != FW_UPLOAD_ERR_HW_ERROR)
+			ret = FW_UPLOAD_ERR_RW_ERROR;
+	}
+
+	if (!sev->fwl_rollback_required && !psp_dead)
+		sev_fw_upload_reinit_platform(sev);
+
+	return ret;
+}
+
 static enum fw_upload_err sev_fw_upload_write(struct fw_upload *fw_upload,
 					      const u8 *data, u32 offset,
 					      u32 size, u32 *written)
 {
-	return FW_UPLOAD_ERR_BUSY;
+	struct sev_device *sev = fw_upload->dd_handle;
+	u8 old_major, old_minor, old_build;
+	enum fw_upload_err ret;
+
+	old_major = sev->api_major;
+	old_minor = sev->api_minor;
+	old_build = sev->build;
+
+	ret = sev_fw_upload_update(sev, data, size, written);

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2500

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+	if (ret != FW_UPLOAD_ERR_NONE)
+		return ret;
+
+	if (sev_get_api_version()) {
+		dev_err(sev->dev, "SNP platform data refresh after firmware update failed\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	if (sev->api_major != old_major || sev->api_minor != old_minor ||
+	    sev->build != old_build) {

One line.

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2501

Quoted 1 line
drivers/crypto/ccp/sev-dev.c +1 -0
+		dev_info(sev->dev, "SEV firmware updated to %d.%d build %d\n",

Should be the same as the sev_pci_init() issued message.

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2504

Quoted 3 lines
drivers/crypto/ccp/sev-dev.c +3 -0
+			 sev->api_major, sev->api_minor, sev->build);
+	} else {
+		dev_info(sev->dev, "SEV firmware version unchanged: %d.%d build %d\n",

s/ build /./

Thanks,
Tom

Quoted 50 lines
drivers/crypto/ccp/sev-dev.c +4 -0
+			 sev->api_major, sev->api_minor, sev->build);
+	}
+
+	return ret;
 }
 
 static enum fw_upload_err sev_fw_upload_poll_complete(struct fw_upload *fw_upload)
drivers/crypto/ccp/sev-dev.h +2 -0
index 7ec692e2147e..1e45a08c41da 100644
@@ -71,6 +71,8 @@ struct sev_device {
 	struct sev_tio_status *tio_status;
 
 	struct fw_upload *fwl;
+	bool fwl_rollback_required;
+	bool fwl_reinit_required;
 };
 
 int sev_dev_init(struct psp_device *psp);
include/linux/psp-sev.h +19 -0
index fab62228f981..b71154e9ae4b 100644
@@ -890,6 +890,25 @@ struct sev_platform_init_args {
 	unsigned int max_snp_asid;
 };
 
+/**
+ * struct sev_data_download_firmware_ex - SNP_DOWNLOAD_FIRMWARE_EX structure
+ *
+ * @len: length of the command buffer read by the PSP
+ * @rsvd0: reserved
+ * @fw_paddr: system physical address of the start of the firmware blob
+ * @fw_len: length of the firmware blob
+ * @commit: whether to immediately commit the firmware update
+ * @rsvd1: reserved
+ */
+struct sev_data_download_firmware_ex {
+	u32 len;			/* In */
+	u32 rsvd0;
+	u64 fw_paddr;			/* In */
+	u32 fw_len;			/* In */
+	u32 commit:1;			/* In */
+	u32 rsvd1:31;
+} __packed;
+
 /**
  * struct sev_data_snp_commit - SNP_COMMIT structure
  *
#14 lore raw

Re: [Patch v3 7/7] crypto/ccp: Implement SNP Download Firmware EX

Hi Tom,

Thanks for the review!

Quoted 53 lines · Tom Lendacky
Quoted 52 lines · Pratik R. Sampat

Implement SNP live firmware update using the DOWNLOAD_FIRMWARE_EX command.

DOWNLOAD_FIRMWARE_EX requires the legacy SEV platform to be UNINIT. If it is WORKING then legacy guests are running and the update is refused as busy. If it is INIT, shut it down, release the buffers the firmware owns across that shutdown, run the update, and bring the platform back up afterwards. SNP is never taken down, so SNP guests are unaffected.

To test run the following with your sbin file in FW:

echo 1 > /sys/class/firmware/sev/loading cat <firmware.sbin> > /sys/class/firmware/sev/data echo 0 > /sys/class/firmware/sev/loading

The COMMIT bit is left clear, so the image is only loaded provisionally and the admin decides when to make it permanent with ioctl(/dev/sev, SNP_COMMIT). To roll back, do not commit and upload the previous image the same way.

---
 drivers/crypto/ccp/sev-dev.c | 284 ++++++++++++++++++++++++++++++++++-
 drivers/crypto/ccp/sev-dev.h |   2 +
 include/linux/psp-sev.h      |  19 +++
 3 files changed, 304 insertions(+), 1 deletion(-)
drivers/crypto/ccp/sev-dev.c +2 -0
index 88cf60a9640e..e54f23ba1b9b 100644
@@ -29,6 +29,7 @@
 #include <linux/psp.h>
 #include <linux/amd-iommu.h>
 #include <linux/crash_dump.h>
+#include <linux/sizes.h>
 
 #include <asm/smp.h>
 #include <asm/cacheflush.h>
@@ -252,6 +253,7 @@ static int sev_cmd_buffer_len(int cmd)
 	case SEV_CMD_SNP_PLATFORM_STATUS:	return sizeof(struct sev_data_snp_addr);
 	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_DOWNLOAD_FIRMWARE_EX:	return sizeof(struct sev_data_download_firmware_ex);
 	case SEV_CMD_SNP_COMMIT:		return sizeof(struct sev_data_snp_commit);
 	case SEV_CMD_SNP_FEATURE_INFO:		return sizeof(struct sev_data_snp_feature_info);
 	case SEV_CMD_SNP_VLEK_LOAD:		return sizeof(struct sev_user_data_snp_vlek_load);
@@ -2213,17 +2215,297 @@ static int sev_update_firmware(struct device *dev)
 }
 
Quoted 7 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2218

Quoted 2 lines
drivers/crypto/ccp/sev-dev.c +1 -0
 #ifdef CONFIG_FW_UPLOAD
+/* Largest image the firmware accepts, anything above is rejected */

I may have missed it, but I don't see anything in the SNP ABI spec that says the limit is 512K. If that doesn't have a limit how did we arrive at 512K?

The ABI spec doesn't mention it, but the firmware had this limit hard-coded. This can potentially change without notice to the ABI. However, I did want a sanity check in the OS and that's why had it in.

I can drop it and let the firmware fail if the image is too large.

Quoted 32 lines
Quoted 32 lines
drivers/crypto/ccp/sev-dev.c +26 -0
+#define SEV_FW_IMAGE_MAX_SIZE	SZ_512K
+
 static enum fw_upload_err sev_fw_upload_prepare(struct fw_upload *fw_upload,
 						const u8 *data, u32 size)
 {
+	struct sev_device *sev = fw_upload->dd_handle;
+
+	if (size > SEV_FW_IMAGE_MAX_SIZE) {
+		dev_err(sev->dev, "DLFW_EX: image of %u bytes exceeds the %u byte maximum\n",
+			size, SEV_FW_IMAGE_MAX_SIZE);
+		return FW_UPLOAD_ERR_INVALID_SIZE;
+	}
+
 	return FW_UPLOAD_ERR_NONE;
 }
 
+static int sev_download_firmware_ex(const u8 *data, u32 size, int *psp_ret)
+{
+	struct sev_data_download_firmware_ex sev_data = {0};
+	int ret, order;
+	struct page *p;
+	void *fw_blob;
+
+	order = get_order(size);
+	p = alloc_pages(GFP_KERNEL | __GFP_ZERO, order);
+	if (!p)
+		return -ENOMEM;
+
+	fw_blob = page_address(p);
+	memcpy(fw_blob, data, size);
+
+	sev_data.len = sizeof(sev_data);
Quoted 8 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2255

Quoted 5 lines
drivers/crypto/ccp/sev-dev.c +5 -0
+	sev_data.fw_paddr = __psp_pa(fw_blob);
+	sev_data.fw_len = size;
+	/*
+	 * Don't commit to the new firmware immediately, perform an explicit
+	 * SNP_COMMIT after

Don't commit the new firmware, an explict SNP_COMMIT is required after update.

Ack.

Quoted 17 lines
Quoted 4 lines
drivers/crypto/ccp/sev-dev.c +3 -0
+	 */
+	sev_data.commit = 0;
+

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2268

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+	ret = __sev_do_cmd_locked(SEV_CMD_SNP_DOWNLOAD_FIRMWARE_EX, &sev_data,
+				  psp_ret);
+
+	__free_pages(p, order);
+
+	return ret;
+}
+
+static enum fw_upload_err sev_fw_upload_handle_err(struct sev_device *sev,
+						   int rc, int psp_ret)

Name rc something more specific, like cmd_ret, to better distinguish what you're checking.

Sure.

Quoted 16 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2279

Quoted 12 lines
drivers/crypto/ccp/sev-dev.c +11 -0
+{
+	enum fw_upload_err ret = FW_UPLOAD_ERR_FW_INVALID;
+	const char *msg;
+
+	if (!rc)
+		return FW_UPLOAD_ERR_NONE;
+
+	/*
+	 * The command timed out: psp_ret was cleared and the PSP was declared
+	 * dead, so there is no firmware status to decode.
+	 */

Move this comment into the if block as it is explaining what happened if psp_dead is set and simplify it:

Quoted 2 lines

"The SEV command timed out and marked the ASP dead, there is no status to decode."

Ack, will do.

Quoted 76 lines
Quoted 76 lines
drivers/crypto/ccp/sev-dev.c +75 -0
+	if (psp_dead) {
+		dev_err(sev->dev, "DLFW_EX: PSP not responding (failed %d, error %#x)\n",
+			rc, psp_ret);
+		sev->fwl_rollback_required = false;
+
+		return FW_UPLOAD_ERR_TIMEOUT;
+	}
+
+	switch (psp_ret) {
+	case SEV_RET_INVALID_PARAM:
+		msg = "Provided image is not well formed";
+		break;
+	case SEV_RET_INVALID_LEN:
+		ret = FW_UPLOAD_ERR_INVALID_SIZE;
+		msg = "Provided image has an unusable length";
+		break;
+	case SEV_RET_SHUTDOWN_REQUIRED:
+		msg = "Provided image cannot be live-updated, shutdown required";
+		break;
+	case SEV_RET_BAD_VERSION:
+		msg = "Provided image < committed version";
+		break;
+	case SEV_RET_INVALID_PLATFORM_STATE:
+		msg = "Platform not in UNINIT state";
+		break;
+	case SEV_RET_INVALID_ADDRESS:
+		msg = "Unaligned address provided";
+		break;
+	case SEV_RET_UNSUPPORTED:
+		msg = "Feature not supported";
+		break;
+	case SEV_RET_INVALID_CONFIG:
+		msg = "Image rejected, unsupported configuration";
+		break;
+	case SEV_RET_BAD_SVN:
+		msg = "Image rejected, SVN < committed SVN";
+		break;
+	case SEV_RET_BAD_SIGNATURE:
+		msg = "Bad firmware signature";
+		break;
+	case SEV_RET_UPDATE_FAILED:
+		/* The previous firmware is still running, a retry is safe. */
+		ret = FW_UPLOAD_ERR_BUSY;
+		msg = "Upgrade failed, automatically reverted";
+		break;
+	case SEV_RET_RESTORE_REQUIRED:
+		/*
+		 * Firmware requested a roll-back. Declare the PSP dead so
+		 * nothing else tries to use it, and let the next upload through
+		 * so the admin can restore the previous image.
+		 */
+		sev->fwl_rollback_required = true;
+		psp_dead = true;
+		ret = FW_UPLOAD_ERR_RW_ERROR;
+		msg = "Live upgrade failed, please roll back";
+		break;
+	case SEV_RET_HWSEV_RET_UNSAFE:
+		/*
+		 * Following a return of HARDWARE_UNSAFE, operation of the SEV
+		 * firmware is indeterminate and the recommendation is to reboot
+		 * the platform. Declare the PSP dead so the driver stops
+		 * issuing commands to it while the reboot is pending.
+		 */
+		sev->fwl_rollback_required = false;
+		psp_dead = true;
+		ret = FW_UPLOAD_ERR_HW_ERROR;
+		msg = "SEV firmware no longer safe. Reboot recommended";
+		break;
+	case SEV_RET_NO_FW_CALL:
+		/* The command never reached the firmware. */
+		ret = FW_UPLOAD_ERR_BUSY;
+		msg = "Driver error";
+		break;
+	default:
+		ret = FW_UPLOAD_ERR_HW_ERROR;
Quoted 9 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2359

Quoted 5 lines
drivers/crypto/ccp/sev-dev.c +5 -0
+		msg = "Unknown SEV firmware error";
+		break;
+	}
+
+	dev_err(sev->dev, "DLFW_EX: %s (failed %d, error %#x)\n", msg, rc, psp_ret);

This is coming from userspace interaction, so probably should use ratelimited variant (here and any place you issue a message).

Ack, will do.

Quoted 34 lines
Quoted 34 lines
drivers/crypto/ccp/sev-dev.c +34 -0
+
+	return ret;
+}
+
+static int sev_fw_upload_shutdown_platform(struct sev_device *sev)
+{
+	int rc, error = SEV_RET_NO_FW_CALL, sev_plat_state;
+
+	lockdep_assert_held(&sev_cmd_mutex);
+
+	rc = sev_get_platform_state(&sev_plat_state, &error);
+	if (rc) {
+		dev_err(sev->dev, "SEV get platform state failed %d, error %#x\n",
+			rc, error);
+		return rc;
+	}
+
+	switch (sev_plat_state) {
+	case SEV_STATE_UNINIT:
+		return 0;
+	case SEV_STATE_WORKING:
+		/* Legacy guests are running, the update cannot proceed. */
+		return -EBUSY;
+	case SEV_STATE_INIT:
+		break;
+	default:
+		dev_err(sev->dev, "Unknown SEV firmware state %d\n", sev_plat_state);
+		return -EINVAL;
+	}
+
+	rc = __sev_platform_shutdown_locked(&error);
+	if (rc) {
+		dev_err(sev->dev, "SEV platform shutdown failed %d, error %#x\n",
+			rc, error);
Quoted 9 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2397

Quoted 4 lines
drivers/crypto/ccp/sev-dev.c +4 -0
+		return rc;
+	}
+
+	__sev_release_firmware_buffers(false);

Do the buffers have to be released? If so, why? I think you can keep the allocations. During platform initialization the buffers will be detected. Is there a shutdown path where they might not get freed?

Shantanu hit this on Milan with an earlier version of the series that kept the buffers across the update [1]. After DOWNLOAD_FIRMWARE_EX the new firmware rejected INIT_EX with SEV_RET_INVALID_PAGE_STATE (0x1A), because sev_es_tmr and sev_init_ex_buffer were still in the firmware state left over from the previous image's INIT. Freeing them, and letting re-init allocate fresh ones, fixed it, which is where this call came from.

However, a fresh allocation with SNP initialized just goes through rmp_mark_pages_firmware(), so my understanding of what the new firmware needs is the reclaim -> make shared -> mark firmware cycle, not necessarily new memory.

Freeing seemed like the easiest option, but if you prefer, just cycling them back should be able to achieve the same effect, I believe.

[1] https://lore.kernel.org/all/[email protected]/

Quoted 13 lines
Quoted 13 lines
drivers/crypto/ccp/sev-dev.c +13 -0
+
+	sev->fwl_reinit_required = true;
+
+	return 0;
+}
+
+static void sev_fw_upload_reinit_platform(struct sev_device *sev)
+{
+	int rc, error = SEV_RET_NO_FW_CALL;
+
+	lockdep_assert_held(&sev_cmd_mutex);
+
+	if (!sev->fwl_reinit_required)
Quoted 8 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2416

Quoted 6 lines
drivers/crypto/ccp/sev-dev.c +6 -0
+		return;
+
+	rc = __sev_platform_init_locked(&error);
+	if (rc) {
+		dev_err(sev->dev, "SEV platform re-init failed %d, error %#x\n",
+			rc, error);

Single line.

Ack.

Quoted 28 lines
Quoted 12 lines
drivers/crypto/ccp/sev-dev.c +11 -0
+		return;
+	}
+
+	sev->fwl_reinit_required = false;
+}
+
+static enum fw_upload_err sev_fw_upload_update(struct sev_device *sev,
+					       const u8 *data, u32 size,
+					       u32 *written)
+{
+	int rc, error = SEV_RET_NO_FW_CALL;

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2437

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+	enum fw_upload_err ret;
+
+	guard(mutex)(&sev_cmd_mutex);
+
+	/*
+	 * A PSP declared dead only executes DOWNLOAD_FIRMWARE_EX if it was the
+	 * firmware update that killed it and asked for a rollback. Declared
+	 * dead for any other reason it will not answer until the platform is
+	 * rebooted.
+	 */

"SEV firmware will only successfully execute the DOWNLOAD_FIRMWARE_EX command if a firmware rollback is required. Other commands may be processed, but may not execute properly. Use the psp_dead boolean to restrict execution to this path."

Quoted 2 lines

Say something similar where psp_dead is being set to true in sev_fw_upload_handle_err().

Sure.

Quoted 11 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2444

Quoted 8 lines
drivers/crypto/ccp/sev-dev.c +7 -0
+	if (psp_dead && !sev->fwl_rollback_required) {
+		dev_err(sev->dev, "DLFW_EX: PSP is not responding\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	/*
+	 * If the last firmware update returned RESTORE_REQUIRED, retry DLFW_EX.

I see a mix of DOWNLOAD_FIRMWARE_EX and DLFW_EX, change these to all be the same name of your choice.

Sure.

Quoted 4 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2445

Quoted 2 lines
drivers/crypto/ccp/sev-dev.c +1 -0
+	 * We being in this state means that the legacy firmware has previously

s/We being/Being/

Ack.

Quoted 19 lines
Quoted 5 lines
drivers/crypto/ccp/sev-dev.c +4 -0
+	 * been shut down, so no need to do it again.
+	 */
+	if (sev->fwl_rollback_required) {
+		psp_dead = false;

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2459

Quoted 10 lines
drivers/crypto/ccp/sev-dev.c +10 -0
+	} else {
+		rc = sev_fw_upload_shutdown_platform(sev);
+		if (rc) {
+			return psp_dead ? FW_UPLOAD_ERR_HW_ERROR
+					: FW_UPLOAD_ERR_BUSY;
+		}
+	}
+
+	rc = sev_download_firmware_ex(data, size, &error);
+	ret = sev_fw_upload_handle_err(sev, rc, error);

Maybe it's just me, but using generic rc and ret can make this possibly confusing in the future. How about:

Quoted 4 lines

s/rc/cmd_ret/
s/ret/fwl_ret/
s/error/psp_ret/

Nah, I can see that. Your options are so much better!

Quoted 36 lines
Quoted 36 lines
drivers/crypto/ccp/sev-dev.c +31 -1
+	if (ret == FW_UPLOAD_ERR_NONE) {
+		*written = size;
+		sev->fwl_rollback_required = false;
+	}
+
+	/* A rollback retry failed. PSP now stays dead */
+	if (sev->fwl_rollback_required) {
+		psp_dead = true;
+		if (ret != FW_UPLOAD_ERR_HW_ERROR)
+			ret = FW_UPLOAD_ERR_RW_ERROR;
+	}
+
+	if (!sev->fwl_rollback_required && !psp_dead)
+		sev_fw_upload_reinit_platform(sev);
+
+	return ret;
+}
+
 static enum fw_upload_err sev_fw_upload_write(struct fw_upload *fw_upload,
 					      const u8 *data, u32 offset,
 					      u32 size, u32 *written)
 {
-	return FW_UPLOAD_ERR_BUSY;
+	struct sev_device *sev = fw_upload->dd_handle;
+	u8 old_major, old_minor, old_build;
+	enum fw_upload_err ret;
+
+	old_major = sev->api_major;
+	old_minor = sev->api_minor;
+	old_build = sev->build;
+
+	ret = sev_fw_upload_update(sev, data, size, written);
+	if (ret != FW_UPLOAD_ERR_NONE)
+		return ret;
+
+	if (sev_get_api_version()) {
Quoted 8 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2500

Quoted 6 lines
drivers/crypto/ccp/sev-dev.c +6 -0
+		dev_err(sev->dev, "SNP platform data refresh after firmware update failed\n");
+		return FW_UPLOAD_ERR_HW_ERROR;
+	}
+
+	if (sev->api_major != old_major || sev->api_minor != old_minor ||
+	    sev->build != old_build) {

One line.

Ack.

Quoted 11 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2501

Quoted 2 lines
drivers/crypto/ccp/sev-dev.c +1 -0
+		dev_info(sev->dev, "SEV firmware updated to %d.%d build %d\n",

Should be the same as the sev_pci_init() issued message.

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev) · line 2504

Quoted 3 lines
drivers/crypto/ccp/sev-dev.c +3 -0
+			 sev->api_major, sev->api_minor, sev->build);
+	} else {
+		dev_info(sev->dev, "SEV firmware version unchanged: %d.%d build %d\n",

s/ build /./

Thanks!
--Pratik

Quoted 53 lines

Thanks,
Tom

Quoted 50 lines
drivers/crypto/ccp/sev-dev.c +4 -0
+			 sev->api_major, sev->api_minor, sev->build);
+	}
+
+	return ret;
 }
 
 static enum fw_upload_err sev_fw_upload_poll_complete(struct fw_upload *fw_upload)
drivers/crypto/ccp/sev-dev.h +2 -0
index 7ec692e2147e..1e45a08c41da 100644
@@ -71,6 +71,8 @@ struct sev_device {
 	struct sev_tio_status *tio_status;
 
 	struct fw_upload *fwl;
+	bool fwl_rollback_required;
+	bool fwl_reinit_required;
 };
 
 int sev_dev_init(struct psp_device *psp);
include/linux/psp-sev.h +19 -0
index fab62228f981..b71154e9ae4b 100644
@@ -890,6 +890,25 @@ struct sev_platform_init_args {
 	unsigned int max_snp_asid;
 };
 
+/**
+ * struct sev_data_download_firmware_ex - SNP_DOWNLOAD_FIRMWARE_EX structure
+ *
+ * @len: length of the command buffer read by the PSP
+ * @rsvd0: reserved
+ * @fw_paddr: system physical address of the start of the firmware blob
+ * @fw_len: length of the firmware blob
+ * @commit: whether to immediately commit the firmware update
+ * @rsvd1: reserved
+ */
+struct sev_data_download_firmware_ex {
+	u32 len;			/* In */
+	u32 rsvd0;
+	u64 fw_paddr;			/* In */
+	u32 fw_len;			/* In */
+	u32 commit:1;			/* In */
+	u32 rsvd1:31;
+} __packed;
+
 /**
  * struct sev_data_snp_commit - SNP_COMMIT structure
  *
maintainer
#15 lore raw

Re: [Patch v3 7/7] crypto/ccp: Implement SNP Download Firmware EX

Quoted 38 lines · Pratik R. Sampat

Hi Tom,

Thanks for the review!

Quoted 25 lines · Tom Lendacky
Quoted 23 lines · Pratik R. Sampat

Implement SNP live firmware update using the DOWNLOAD_FIRMWARE_EX command.

DOWNLOAD_FIRMWARE_EX requires the legacy SEV platform to be UNINIT. If it is WORKING then legacy guests are running and the update is refused as busy. If it is INIT, shut it down, release the buffers the firmware owns across that shutdown, run the update, and bring the platform back up afterwards. SNP is never taken down, so SNP guests are unaffected.

To test run the following with your sbin file in FW:

echo 1 > /sys/class/firmware/sev/loading cat <firmware.sbin> > /sys/class/firmware/sev/data echo 0 > /sys/class/firmware/sev/loading

The COMMIT bit is left clear, so the image is only loaded provisionally and the admin decides when to make it permanent with ioctl(/dev/sev, SNP_COMMIT). To roll back, do not commit and upload the previous image the same way.

Quoted 7 lines
Quoted 2 lines

#ifdef CONFIG_FW_UPLOAD
+/* Largest image the firmware accepts, anything above is rejected */

I may have missed it, but I don't see anything in the SNP ABI spec that says the limit is 512K. If that doesn't have a limit how did we arrive at 512K?

Quoted 5 lines

The ABI spec doesn't mention it, but the firmware had this limit hard-coded. This can potentially change without notice to the ABI. However, I did want a sanity check in the OS and that's why had it in.

I can drop it and let the firmware fail if the image is too large.

The memory holding the firmware on the call to the ASP has to be contiguous, so you're likely to fail on the alloc_pages() if the image is too large. Up to you if you want to keep it.

Quoted 24 lines
Quoted 11 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev)

Quoted 6 lines
drivers/crypto/ccp/sev-dev.c +4 -0
+#define SEV_FW_IMAGE_MAX_SIZE	SZ_512K
+
 
+
+	__sev_release_firmware_buffers(false);

Do the buffers have to be released? If so, why? I think you can keep the allocations. During platform initialization the buffers will be detected. Is there a shutdown path where they might not get freed?

Shantanu hit this on Milan with an earlier version of the series that kept the buffers across the update [1]. After DOWNLOAD_FIRMWARE_EX the new firmware rejected INIT_EX with SEV_RET_INVALID_PAGE_STATE (0x1A), because sev_es_tmr and sev_init_ex_buffer were still in the firmware state left over from the previous image's INIT. Freeing them, and letting re-init allocate fresh ones, fixed it, which is where this call came from.

However, a fresh allocation with SNP initialized just goes through rmp_mark_pages_firmware(), so my understanding of what the new firmware needs is the reclaim -> make shared -> mark firmware cycle, not necessarily new memory.

Quoted 4 lines

Freeing seemed like the easiest option, but if you prefer, just cycling them back should be able to achieve the same effect, I believe.

[1] https://lore.kernel.org/all/[email protected]/

Sounds like some good info to have as a comment above the call then.

Thanks,
Tom

#16 lore raw

Re: [Patch v3 7/7] crypto/ccp: Implement SNP Download Firmware EX

Quoted 43 lines · Tom Lendacky
Quoted 42 lines · Pratik R. Sampat

Hi Tom,

Thanks for the review!

Quoted 25 lines · Tom Lendacky
Quoted 23 lines · Pratik R. Sampat

Implement SNP live firmware update using the DOWNLOAD_FIRMWARE_EX command.

DOWNLOAD_FIRMWARE_EX requires the legacy SEV platform to be UNINIT. If it is WORKING then legacy guests are running and the update is refused as busy. If it is INIT, shut it down, release the buffers the firmware owns across that shutdown, run the update, and bring the platform back up afterwards. SNP is never taken down, so SNP guests are unaffected.

To test run the following with your sbin file in FW:

echo 1 > /sys/class/firmware/sev/loading cat <firmware.sbin> > /sys/class/firmware/sev/data echo 0 > /sys/class/firmware/sev/loading

The COMMIT bit is left clear, so the image is only loaded provisionally and the admin decides when to make it permanent with ioctl(/dev/sev, SNP_COMMIT). To roll back, do not commit and upload the previous image the same way.

Quoted 7 lines
Quoted 2 lines

#ifdef CONFIG_FW_UPLOAD
+/* Largest image the firmware accepts, anything above is rejected */

I may have missed it, but I don't see anything in the SNP ABI spec that says the limit is 512K. If that doesn't have a limit how did we arrive at 512K?

The ABI spec doesn't mention it, but the firmware had this limit hard-coded. This can potentially change without notice to the ABI. However, I did want a sanity check in the OS and that's why had it in.

Quoted 5 lines
Quoted 1 line

I can drop it and let the firmware fail if the image is too large.

The memory holding the firmware on the call to the ASP has to be contiguous, so you're likely to fail on the alloc_pages() if the image is too large. Up to you if you want to keep it.

Sure.

Quoted 24 lines
Quoted 24 lines
Quoted 11 lines

drivers/crypto/ccp/sev-dev.c static int sev_update_firmware(struct device *dev)

Quoted 6 lines
drivers/crypto/ccp/sev-dev.c +4 -0
+#define SEV_FW_IMAGE_MAX_SIZE	SZ_512K
+
 
+
+	__sev_release_firmware_buffers(false);

Do the buffers have to be released? If so, why? I think you can keep the allocations. During platform initialization the buffers will be detected. Is there a shutdown path where they might not get freed?

Shantanu hit this on Milan with an earlier version of the series that kept the buffers across the update [1]. After DOWNLOAD_FIRMWARE_EX the new firmware rejected INIT_EX with SEV_RET_INVALID_PAGE_STATE (0x1A), because sev_es_tmr and sev_init_ex_buffer were still in the firmware state left over from the previous image's INIT. Freeing them, and letting re-init allocate fresh ones, fixed it, which is where this call came from.

However, a fresh allocation with SNP initialized just goes through rmp_mark_pages_firmware(), so my understanding of what the new firmware needs is the reclaim -> make shared -> mark firmware cycle, not necessarily new memory.

Quoted 7 lines
Quoted 4 lines

Freeing seemed like the easiest option, but if you prefer, just cycling them back should be able to achieve the same effect, I believe.

[1] https://lore.kernel.org/all/[email protected]/

Sounds like some good info to have as a comment above the call then.

Will do!

Thanks,
--Pratik

Quoted 3 lines

Thanks,
Tom

#17 lore raw

Re: [Patch v3 7/7] crypto/ccp: Implement SNP Download Firmware EX

Quoted 8 lines · Pratik R. Sampat
Quoted 7 lines · Tom Lendacky
Quoted 4 lines · Pratik R. Sampat

However, a fresh allocation with SNP initialized just goes through rmp_mark_pages_firmware(), so my understanding of what the new firmware needs is the reclaim -> make shared -> mark firmware cycle, not necessarily new memory.

Sounds like some good info to have as a comment above the call then.

I had looked at cycling briefly, but it gets tricky. If we reclaim the pages in sev_fw_upload_shutdown_platform() and re-init gets skipped because of RESTORE_REQUIRED or a dead PSP, the buffers are still allocated but aren't marked as firmware pages, so all subsequent paths need to be aware of that.

Reclaiming after the update avoids that, but relies on the new image accepting a reclaim of pages the old image's INIT left behind, which I'm not sure works, though I haven't tested it.

In either case, failure handling also seemed tricky, and the fallback would just be free and re-allocate anyway.

The only issue I see with freeing is that the TMR is a 2M buffer that needs to be contiguous. If the new allocation fails, INIT carries on without a TMR and without any error (it's only logged in dmesg), even though SEV-ES is now disabled. It should be very rare (since we did just free the TMR and can probably get the same block back), but it seems worth mentioning in a comment.

Quoted 3 lines
Quoted 3 lines

The memory holding the firmware on the call to the ASP has to be contiguous, so you're likely to fail on the alloc_pages() if the image is too large. Up to you if you want to keep it.

I think it's better to keep the explicit check and error message. If the size check falls through to the alloc_pages() failure, userspace sees device-busy, which doesn't communicate the right intent.

One other small thing. If the platform data refresh in sev_fw_upload_write() fails after DLFW_EX has succeeded, it returns hw-error even when the PSP is still alive and the new image is running. I may be missing a reason for that, but would something like this make sense?

if (sev_get_api_version()) {
- dev_err(sev->dev, "SNP platform data refresh after firmware update failed\n");
- return FW_UPLOAD_ERR_HW_ERROR;
+ dev_warn(sev->dev, "SNP platform data refresh after firmware update failed\n");
+ return psp_dead ? FW_UPLOAD_ERR_HW_ERROR : FW_UPLOAD_ERR_NONE;
}

Apart from these, everything else I tested in v3 works on Milan.

Thanks,
Shantanu

#18 lore raw

Re: [Patch v3 7/7] crypto/ccp: Implement SNP Download Firmware EX

Quoted 16 lines · Shantanu Sinha
Quoted 8 lines · Pratik R. Sampat
Quoted 7 lines · Tom Lendacky
Quoted 4 lines · Pratik R. Sampat

However, a fresh allocation with SNP initialized just goes through rmp_mark_pages_firmware(), so my understanding of what the new firmware needs is the reclaim -> make shared -> mark firmware cycle, not necessarily new memory.

Sounds like some good info to have as a comment above the call then.

I had looked at cycling briefly, but it gets tricky. If we reclaim the pages in sev_fw_upload_shutdown_platform() and re-init gets skipped because of RESTORE_REQUIRED or a dead PSP, the buffers are still allocated but aren't marked as firmware pages, so all subsequent paths need to be aware of that.

Quoted 6 lines

Reclaiming after the update avoids that, but relies on the new image accepting a reclaim of pages the old image's INIT left behind, which I'm not sure works, though I haven't tested it.

In either case, failure handling also seemed tricky, and the fallback would just be free and re-allocate anyway.

Agreed.

Quoted 7 lines

The only issue I see with freeing is that the TMR is a 2M buffer that needs to be contiguous. If the new allocation fails, INIT carries on without a TMR and without any error (it's only logged in dmesg), even though SEV-ES is now disabled. It should be very rare (since we did just free the TMR and can probably get the same block back), but it seems worth mentioning in a comment.

Sure, I can add this to the comment too.

Quoted 9 lines
Quoted 4 lines
Quoted 4 lines

The memory holding the firmware on the call to the ASP has to be contiguous, so you're likely to fail on the alloc_pages() if the image is too large. Up to you if you want to keep it.

I think it's better to keep the explicit check and error message. If the size check falls through to the alloc_pages() failure, userspace sees device-busy, which doesn't communicate the right intent.

Ack.

Review

Quoted 13 lines

One other small thing. If the platform data refresh in sev_fw_upload_write() fails after DLFW_EX has succeeded, it returns hw-error even when the PSP is still alive and the new image is running. I may be missing a reason for that, but would something like this make sense?

(quoted diff) +2 -2
 	if (sev_get_api_version()) {
-		dev_err(sev->dev, "SNP platform data refresh after firmware update failed\n");
-		return FW_UPLOAD_ERR_HW_ERROR;
+		dev_warn(sev->dev, "SNP platform data refresh after firmware update failed\n");
+		return psp_dead ? FW_UPLOAD_ERR_HW_ERROR : FW_UPLOAD_ERR_NONE;
 	}

I haven't typically seen this fail for any other reason apart from potentially failing the PSP call but I do see a few paths where this could potentially happen. So, only returning an error when PSP is dead makes sense to me.

Quoted 1 line

Apart from these, everything else I tested in v3 works on Milan.

Thanks, for testing this out!

--Pratik

Quoted 3 lines

Thanks,
Shantanu