lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20230331071823.956087-7-conor.dooley@microchip.com>
Date:   Fri, 31 Mar 2023 08:18:22 +0100
From:   Conor Dooley <conor.dooley@...rochip.com>
To:     <linux-fpga@...r.kernel.org>
CC:     <conor@...nel.org>, <conor.dooley@...rochip.com>,
        Daire McNamara <daire.mcnamara@...rochip.com>,
        Rob Herring <robh+dt@...nel.org>,
        "Krzysztof Kozlowski" <krzysztof.kozlowski+dt@...aro.org>,
        Moritz Fischer <mdf@...nel.org>, Wu Hao <hao.wu@...el.com>,
        Xu Yilun <yilun.xu@...el.com>, Tom Rix <trix@...hat.com>,
        <linux-riscv@...ts.infradead.org>, <devicetree@...r.kernel.org>,
        <linux-kernel@...r.kernel.org>
Subject: [PATCH v2 6/7] fpga: add PolarFire SoC Auto Update support

Add support for Auto Update reprogramming of the FPGA fabric on
PolarFire SoC, using the fw_upload mechanism a la the
intel-m10-bmc-sec-update driver.

This driver only writes the image to the spi flash & performs
validation on it, as the entire FPGA becomes unusable during the actual
reprogramming of a bitstream. To initiate the reprogramming itself, a
device reset is required.
The SBI SRST extension's "cold reboot" can trigger such a device reset,
by means of a "Tamper Macro" in the FPGA fabric, provided corresponding
support has been enabled in the HSS (Hart Software Services).

Signed-off-by: Conor Dooley <conor.dooley@...rochip.com>
---
 drivers/fpga/Kconfig                 |  11 +
 drivers/fpga/Makefile                |   3 +-
 drivers/fpga/microchip-auto-update.c | 494 +++++++++++++++++++++++++++
 3 files changed, 507 insertions(+), 1 deletion(-)
 create mode 100644 drivers/fpga/microchip-auto-update.c

diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index 0a00763b9f28..2838ce7ddafe 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -257,6 +257,17 @@ config FPGA_M10_BMC_SEC_UPDATE
 	  (BMC) and provides support for secure updates for the BMC image,
 	  the FPGA image, the Root Entry Hashes, etc.
 
+config FPGA_MICROCHIP_AUTO_UPDATE
+	tristate "Microchip PolarFire SoC AUTO UPDATE"
+	depends on POLARFIRE_SOC_SYS_CTRL
+	select FW_LOADER
+	select FW_UPLOAD
+	help
+	  FPGA manager driver support for reprogramming PolarFire SoC from
+	  within Linux, using the Auto Upgrade feature of the system controller.
+
+	  If built as a module, it will be called microchip-auto-update.
+
 config FPGA_MGR_MICROCHIP_SPI
 	tristate "Microchip Polarfire SPI FPGA manager"
 	depends on SPI
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index 72e554b4d2f7..0b1ba6db860d 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -25,8 +25,9 @@ obj-$(CONFIG_FPGA_MGR_LATTICE_SYSCONFIG_SPI)	+= lattice-sysconfig-spi.o
 obj-$(CONFIG_ALTERA_PR_IP_CORE)		+= altera-pr-ip-core.o
 obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT)	+= altera-pr-ip-core-plat.o
 
-# FPGA Secure Update Drivers
+# FPGA Firmware Upload Drivers
 obj-$(CONFIG_FPGA_M10_BMC_SEC_UPDATE)	+= intel-m10-bmc-sec-update.o
+obj-$(CONFIG_FPGA_MICROCHIP_AUTO_UPDATE)	+= microchip-auto-update.o
 
 # FPGA Bridge Drivers
 obj-$(CONFIG_FPGA_BRIDGE)		+= fpga-bridge.o
diff --git a/drivers/fpga/microchip-auto-update.c b/drivers/fpga/microchip-auto-update.c
new file mode 100644
index 000000000000..5fa920b659c6
--- /dev/null
+++ b/drivers/fpga/microchip-auto-update.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Microchip Polarfire SoC "Auto Update" FPGA reprogramming.
+ *
+ * Documentation of this functionality is available in the "PolarFire® FPGA and
+ * PolarFire SoC FPGA Programming" User Guide.
+ *
+ * Copyright (c) 2022-2023 Microchip Corporation. All rights reserved.
+ *
+ * Author: Conor Dooley <conor.dooley@...rochip.com>
+ */
+#include <linux/debugfs.h>
+#include <linux/firmware.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/of_device.h>
+#include <linux/sizes.h>
+
+#include <soc/microchip/mpfs.h>
+
+#define AUTO_UPDATE_DEFAULT_MBOX_OFFSET		0u
+#define AUTO_UPDATE_DEFAULT_RESP_OFFSET		0u
+
+#define AUTO_UPDATE_FEATURE_CMD_OPCODE		0x05u
+#define AUTO_UPDATE_FEATURE_CMD_DATA_SIZE	0u
+#define AUTO_UPDATE_FEATURE_RESP_SIZE		33u
+#define AUTO_UPDATE_FEATURE_CMD_DATA		NULL
+#define AUTO_UPDATE_FEATURE_ENABLED		BIT(5)
+
+#define AUTO_UPDATE_AUTHENTICATE_CMD_OPCODE	0x22u
+#define AUTO_UPDATE_AUTHENTICATE_CMD_DATA_SIZE	0u
+#define AUTO_UPDATE_AUTHENTICATE_RESP_SIZE	1u
+#define AUTO_UPDATE_AUTHENTICATE_CMD_DATA	NULL
+
+#define AUTO_UPDATE_PROGRAM_CMD_OPCODE		0x46u
+#define AUTO_UPDATE_PROGRAM_CMD_DATA_SIZE	0u
+#define AUTO_UPDATE_PROGRAM_RESP_SIZE		1u
+#define AUTO_UPDATE_PROGRAM_CMD_DATA		NULL
+
+/*
+ * SPI Flash layout example:
+ * |------------------------------| 0x0000000
+ * | 1 KiB                        |
+ * | SPI "directories"            |
+ * |------------------------------| 0x0000400
+ * | 1 MiB                        |
+ * | Reserved area                |
+ * | Used for bitstream info      |
+ * |------------------------------| 0x0100400
+ * | 20 MiB                       |
+ * | Golden Image                 |
+ * |------------------------------| 0x1500400
+ * | 20 MiB                       |
+ * | Auto Upgrade Image           |
+ * |------------------------------| 0x2900400
+ * | 20 MiB                       |
+ * | Reserved for multi-image IAP |
+ * | Unused for Auto Upgrade      |
+ * |------------------------------| 0x3D00400
+ * | ? B                          |
+ * | Unused                       |
+ * |------------------------------| 0x?
+ */
+#define AUTO_UPDATE_DIRECTORY_BASE	0u
+#define AUTO_UPDATE_DIRECTORY_WIDTH	4u
+#define AUTO_UPDATE_GOLDEN_INDEX	0u
+#define AUTO_UPDATE_UPGRADE_INDEX	1u
+#define AUTO_UPDATE_BLANK_INDEX		2u
+#define AUTO_UPDATE_GOLDEN_DIRECTORY	(AUTO_UPDATE_DIRECTORY_WIDTH * AUTO_UPDATE_GOLDEN_INDEX)
+#define AUTO_UPDATE_UPGRADE_DIRECTORY	(AUTO_UPDATE_DIRECTORY_WIDTH * AUTO_UPDATE_UPGRADE_INDEX)
+#define AUTO_UPDATE_BLANK_DIRECTORY	(AUTO_UPDATE_DIRECTORY_WIDTH * AUTO_UPDATE_BLANK_INDEX)
+#define AUTO_UPDATE_DIRECTORY_SIZE	SZ_1K
+#define AUTO_UPDATE_RESERVED_SIZE	SZ_1M
+#define AUTO_UPDATE_BITSTREAM_BASE	(AUTO_UPDATE_DIRECTORY_SIZE + AUTO_UPDATE_RESERVED_SIZE)
+
+#define AUTO_UPDATE_TIMEOUT_MS		60000
+
+struct mpfs_auto_update_priv {
+	struct mpfs_sys_controller *sys_controller;
+	struct device *dev;
+	struct mtd_info *flash;
+	struct fw_upload *fw_uploader;
+	struct completion programming_complete;
+	size_t size_per_bitstream;
+	bool cancel_request;
+};
+
+static enum fw_upload_err mpfs_auto_update_prepare(struct fw_upload *fw_uploader, const u8 *data,
+						   u32 size)
+{
+	struct mpfs_auto_update_priv *priv = fw_uploader->dd_handle;
+	size_t erase_size = AUTO_UPDATE_DIRECTORY_SIZE;
+
+	/*
+	 * Verifying the Golden Image is idealistic. It will be evaluated
+	 * against the currently programmed image and thus may fail - due to
+	 * either rollback protection (if its an older version than that in use)
+	 * or if the version is the same as that of the in-use image.
+	 * Extracting the information as to why a failure occurred is not
+	 * currently possible due to limitations of the system controller
+	 * driver. If those are fixed, verification of the Golden Image should
+	 * be added here.
+	 */
+
+	priv->flash = mpfs_sys_controller_get_flash(priv->sys_controller);
+	if (!priv->flash)
+		return FW_UPLOAD_ERR_HW_ERROR;
+
+	erase_size = round_up(erase_size, (u64)priv->flash->erasesize);
+
+	/*
+	 * We need to calculate if we have enough space in the flash for the
+	 * new image.
+	 * First, chop off the first 1 KiB as it's reserved for the directory.
+	 * The 1 MiB reserved for design info needs to be ignored also.
+	 * All that remains is carved into 3 & rounded down to the erasesize.
+	 * If this is smaller than the image size, we abort.
+	 * There's also no need to consume more than 20 MiB per image.
+	 */
+	priv->size_per_bitstream = priv->flash->size - SZ_1K - SZ_1M;
+	priv->size_per_bitstream = round_down(priv->size_per_bitstream / 3, erase_size);
+	if (priv->size_per_bitstream > 20 * SZ_1M)
+		priv->size_per_bitstream = 20 * SZ_1M;
+
+	if (priv->size_per_bitstream < size) {
+		dev_err(priv->dev,
+			"flash device has insufficient capacity to store this bitstream\n");
+		return FW_UPLOAD_ERR_INVALID_SIZE;
+	}
+
+	priv->cancel_request = false;
+
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static void mpfs_auto_update_cancel(struct fw_upload *fw_uploader)
+{
+	struct mpfs_auto_update_priv *priv = fw_uploader->dd_handle;
+
+	priv->cancel_request = true;
+}
+
+static enum fw_upload_err mpfs_auto_update_poll_complete(struct fw_upload *fw_uploader)
+{
+	struct mpfs_auto_update_priv *priv = fw_uploader->dd_handle;
+	int ret;
+
+	/*
+	 * There is no meaningful way to get the status of the programming while
+	 * it is in progress, so attempting anything other than waiting for it
+	 * to complete would be misplaced.
+	 */
+	ret = wait_for_completion_timeout(&priv->programming_complete,
+					  msecs_to_jiffies(AUTO_UPDATE_TIMEOUT_MS));
+	if (ret)
+		return FW_UPLOAD_ERR_TIMEOUT;
+
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static int mpfs_auto_update_verify_image(struct fw_upload *fw_uploader)
+{
+	struct mpfs_auto_update_priv *priv = fw_uploader->dd_handle;
+	struct mpfs_mss_response *response;
+	struct mpfs_mss_msg *message;
+	u32 *response_msg;
+	int ret;
+
+	response_msg = devm_kzalloc(priv->dev, AUTO_UPDATE_FEATURE_RESP_SIZE * sizeof(response_msg),
+				    GFP_KERNEL);
+	if (!response_msg)
+		return -ENOMEM;
+
+	response = devm_kzalloc(priv->dev, sizeof(struct mpfs_mss_response), GFP_KERNEL);
+	if (!response) {
+		ret = -ENOMEM;
+		goto free_response_msg;
+	}
+
+	message = devm_kzalloc(priv->dev, sizeof(struct mpfs_mss_msg), GFP_KERNEL);
+	if (!message) {
+		ret = -ENOMEM;
+		goto free_response;
+	}
+
+	/*
+	 * The system controller can verify that an image in the flash is valid.
+	 * Rather than duplicate the check in this driver, call the relevant
+	 * service from the system controller instead.
+	 * This service has no command data and no response data. It overloads
+	 * mbox_offset with the image index in the flash's SPI directory where
+	 * the bitstream is located.
+	 */
+	response->resp_msg = response_msg;
+	response->resp_size = AUTO_UPDATE_AUTHENTICATE_RESP_SIZE;
+	message->cmd_opcode = AUTO_UPDATE_AUTHENTICATE_CMD_OPCODE;
+	message->cmd_data_size = AUTO_UPDATE_AUTHENTICATE_CMD_DATA_SIZE;
+	message->response = response;
+	message->cmd_data = AUTO_UPDATE_AUTHENTICATE_CMD_DATA;
+	message->mbox_offset = AUTO_UPDATE_UPGRADE_INDEX;
+	message->resp_offset = AUTO_UPDATE_DEFAULT_RESP_OFFSET;
+
+	dev_info(priv->dev, "Running verification of Upgrade Image\n");
+	ret = mpfs_blocking_transaction(priv->sys_controller, message);
+	if (ret | response->resp_status) {
+		dev_warn(priv->dev, "Verification of Upgrade Image failed!\n");
+		ret = ret ? ret : -EBADMSG;
+	}
+
+	dev_info(priv->dev, "Verification of Upgrade Image passed!\n");
+
+	devm_kfree(priv->dev, message);
+free_response:
+	devm_kfree(priv->dev, response);
+free_response_msg:
+	devm_kfree(priv->dev, response_msg);
+
+	return ret;
+}
+
+static int mpfs_auto_update_set_image_address(struct mpfs_auto_update_priv *priv, char *buffer,
+					      u32 image_address, loff_t directory_address)
+{
+	struct erase_info erase;
+	size_t erase_size = AUTO_UPDATE_DIRECTORY_SIZE;
+	size_t bytes_written = 0, bytes_read = 0;
+	int ret;
+
+	erase_size = round_up(erase_size, (u64)priv->flash->erasesize);
+
+	erase.addr = AUTO_UPDATE_DIRECTORY_BASE;
+	erase.len = erase_size;
+
+	/*
+	 * We need to write the "SPI DIRECTORY" to the first 1 KiB, telling
+	 * the system controller where to find the actual bitstream. Since
+	 * this is spi-nor, we have to read the first eraseblock, erase that
+	 * portion of the flash, modify the data and then write it back.
+	 * There's no need to do this though if things are already the way they
+	 * should be, so check and save the write in that case.
+	 */
+	ret = mtd_read(priv->flash, AUTO_UPDATE_DIRECTORY_BASE, erase_size, &bytes_read,
+		       (u_char *)buffer);
+	if (ret)
+		return ret;
+
+	if (bytes_read != erase_size)
+		return -EIO;
+
+	if ((*(u32 *)(buffer + AUTO_UPDATE_UPGRADE_DIRECTORY) == image_address) &&
+	    !(*(u32 *)(buffer + AUTO_UPDATE_BLANK_DIRECTORY)))
+		return 0;
+
+	ret = mtd_erase(priv->flash, &erase);
+	if (ret)
+		return ret;
+
+	/*
+	 * Populate the image address and then zero out the next directory so
+	 * that the system controller doesn't complain if in "Single Image"
+	 * mode.
+	 */
+	memcpy(buffer + AUTO_UPDATE_UPGRADE_DIRECTORY, &image_address,
+	       AUTO_UPDATE_DIRECTORY_WIDTH);
+	memset(buffer + AUTO_UPDATE_BLANK_DIRECTORY, 0x0, AUTO_UPDATE_DIRECTORY_WIDTH);
+
+	dev_info(priv->dev, "Writing the image address (%x) to the flash directory (%llx)\n",
+		 image_address, directory_address);
+
+	ret = mtd_write(priv->flash, 0x0, erase_size, &bytes_written, (u_char *)buffer);
+	if (ret)
+		return ret;
+
+	if (bytes_written != erase_size)
+		return ret;
+
+	return 0;
+}
+
+static int mpfs_auto_update_write_bitstream(struct fw_upload *fw_uploader, const u8 *data,
+					    u32 offset, u32 size, u32 *written)
+{
+	struct mpfs_auto_update_priv *priv = fw_uploader->dd_handle;
+	struct erase_info erase;
+	char *buffer;
+	loff_t directory_address = AUTO_UPDATE_UPGRADE_DIRECTORY;
+	size_t erase_size = AUTO_UPDATE_DIRECTORY_SIZE;
+	size_t bytes_written = 0;
+	u32 image_address;
+	int ret;
+
+	erase_size = round_up(erase_size, (u64)priv->flash->erasesize);
+
+	image_address = AUTO_UPDATE_BITSTREAM_BASE +
+		AUTO_UPDATE_UPGRADE_INDEX * priv->size_per_bitstream;
+
+	buffer = devm_kzalloc(priv->dev, erase_size, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	ret = mpfs_auto_update_set_image_address(priv, buffer, image_address, directory_address);
+	if (ret) {
+		dev_err(priv->dev, "failed to set image address in the SPI directory: %d\n", ret);
+		goto out;
+	}
+
+	/*
+	 * Now the .spi image itself can be written to the flash. Preservation
+	 * of contents here is not important here, unlike the spi "directory"
+	 * which must be RMWed.
+	 */
+	erase.len = round_up(size, (size_t)priv->flash->erasesize);
+	erase.addr = image_address;
+
+	dev_info(priv->dev, "Erasing the flash at address (%x)\n", image_address);
+	ret = mtd_erase(priv->flash, &erase);
+	if (ret)
+		goto out;
+
+	/*
+	 * No parsing etc of the bitstream is required. The system controller
+	 * will do all of that itself - including verifying that the bitstream
+	 * is valid.
+	 */
+	dev_info(priv->dev, "Writing the image to the flash at address (%x)\n", image_address);
+	ret = mtd_write(priv->flash, (loff_t)image_address, size, &bytes_written, data);
+	if (ret)
+		goto out;
+
+	if (bytes_written != size) {
+		ret = -EIO;
+		goto out;
+	}
+
+	*written = bytes_written;
+
+out:
+	devm_kfree(priv->dev, buffer);
+	return ret;
+}
+
+static enum fw_upload_err mpfs_auto_update_write(struct fw_upload *fw_uploader, const u8 *data,
+						 u32 offset, u32 size, u32 *written)
+{
+	struct mpfs_auto_update_priv *priv = fw_uploader->dd_handle;
+	enum fw_upload_err err = FW_UPLOAD_ERR_NONE;
+	int ret;
+
+	reinit_completion(&priv->programming_complete);
+
+	ret = mpfs_auto_update_write_bitstream(fw_uploader, data, offset, size, written);
+	if (ret) {
+		err = FW_UPLOAD_ERR_RW_ERROR;
+		goto out;
+	}
+
+	if (priv->cancel_request) {
+		err = FW_UPLOAD_ERR_CANCELED;
+		goto out;
+	}
+
+	ret = mpfs_auto_update_verify_image(fw_uploader);
+	if (ret)
+		err = FW_UPLOAD_ERR_HW_ERROR;
+
+out:
+	complete(&priv->programming_complete);
+
+	return err;
+}
+
+static const struct fw_upload_ops mpfs_auto_update_ops = {
+	.prepare = mpfs_auto_update_prepare,
+	.write = mpfs_auto_update_write,
+	.poll_complete = mpfs_auto_update_poll_complete,
+	.cancel = mpfs_auto_update_cancel,
+};
+
+static int mpfs_auto_update_available(struct mpfs_auto_update_priv *priv)
+{
+	struct mpfs_mss_response *response;
+	struct mpfs_mss_msg *message;
+	u32 *response_msg;
+	int ret;
+
+	response_msg = devm_kzalloc(priv->dev, AUTO_UPDATE_FEATURE_RESP_SIZE * sizeof(response_msg),
+				    GFP_KERNEL);
+	if (!response_msg)
+		return -ENOMEM;
+
+	response = devm_kzalloc(priv->dev, sizeof(struct mpfs_mss_response), GFP_KERNEL);
+	if (!response)
+		return -ENOMEM;
+
+	message = devm_kzalloc(priv->dev, sizeof(struct mpfs_mss_msg), GFP_KERNEL);
+	if (!message)
+		return -ENOMEM;
+
+	/*
+	 * To verify that Auto Update is possible, the "Query Security Service
+	 * Request" is performed.
+	 * This service has no command data & does not overload mbox_offset.
+	 */
+	response->resp_msg = response_msg;
+	response->resp_size = AUTO_UPDATE_FEATURE_RESP_SIZE;
+	message->cmd_opcode = AUTO_UPDATE_FEATURE_CMD_OPCODE;
+	message->cmd_data_size = AUTO_UPDATE_FEATURE_CMD_DATA_SIZE;
+	message->response = response;
+	message->cmd_data = AUTO_UPDATE_FEATURE_CMD_DATA;
+	message->mbox_offset = AUTO_UPDATE_DEFAULT_MBOX_OFFSET;
+	message->resp_offset = AUTO_UPDATE_DEFAULT_RESP_OFFSET;
+
+	ret = mpfs_blocking_transaction(priv->sys_controller, message);
+	if (ret)
+		return ret;
+
+	/*
+	 * Currently, the system controller's firmware does not generate any
+	 * interrupts for failed services, so mpfs_blocking_transaction() should
+	 * time out & therefore return an error.
+	 * Hitting this check is highly unlikely at present, but if the system
+	 * controller's behaviour changes so that it does generate interrupts
+	 * for failed services, it will be required.
+	 */
+	if (response->resp_status)
+		return -EIO;
+
+	/*
+	 * Bit 5 of byte 1 is "UL_Auto Update" & if it is set, Auto Update is
+	 * not possible.
+	 */
+	if (response_msg[1] & AUTO_UPDATE_FEATURE_ENABLED)
+		return -EPERM;
+
+	return 0;
+}
+
+static int mpfs_auto_update_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mpfs_auto_update_priv *priv;
+	struct fw_upload *fw_uploader;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->sys_controller = mpfs_sys_controller_get(dev);
+	if (IS_ERR(priv->sys_controller))
+		return dev_err_probe(dev, PTR_ERR(priv->sys_controller),
+				     "Could not register as a sub device of the system controller\n");
+
+	priv->dev = dev;
+	platform_set_drvdata(pdev, priv);
+
+	ret = mpfs_auto_update_available(priv);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "The current bitstream does not support auto-update\n");
+
+	init_completion(&priv->programming_complete);
+
+	fw_uploader = firmware_upload_register(THIS_MODULE, dev, "mpfs_bitstream",
+					       &mpfs_auto_update_ops, priv);
+	if (IS_ERR(fw_uploader))
+		return dev_err_probe(dev, PTR_ERR(fw_uploader),
+				     "Failed to register the bitstream uploader\n");
+
+	priv->fw_uploader = fw_uploader;
+
+	return 0;
+}
+
+static void mpfs_auto_update_remove(struct platform_device *pdev)
+{
+	struct mpfs_auto_update_priv *priv = platform_get_drvdata(pdev);
+
+	firmware_upload_unregister(priv->fw_uploader);
+}
+
+static struct platform_driver mpfs_auto_update_driver = {
+	.driver = {
+		.name = "mpfs-auto-update",
+	},
+	.probe = mpfs_auto_update_probe,
+	.remove_new = mpfs_auto_update_remove,
+};
+module_platform_driver(mpfs_auto_update_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Conor Dooley <conor.dooley@...rochip.com>");
+MODULE_DESCRIPTION("PolarFire SoC Auto Update FPGA reprogramming");
-- 
2.39.2

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ