+ if (ret != FPGA_ASYNC_ERR_NONE) {
+ fpga_async_dev_error(mgr, ret);
+ goto done;
+ }
+
+ update->remaining_size = size;
+ offset += blk_size;
+ }
+
+ update->progress = FPGA_ASYNC_PROG_PROGRAMMING;
+ ret = mgr->mops->async_write_complete(mgr);
+ if (ret != FPGA_ASYNC_ERR_NONE)
+ fpga_async_dev_error(mgr, ret);
+
+done:
+ if (mgr->mops->async_cleanup)
+ mgr->mops->async_cleanup(mgr);
+
+modput_exit:
+ module_put(mgr->dev.parent->driver->owner);
+
+release_fw_exit:
+ update->data = NULL;
+ release_firmware(fw);
+
+idle_exit:
+ /*
+ * Note: update->remaining_size is left unmodified here to
+ * provide additional information on errors. It will be
+ * reinitialized when the next async update begins.
+ */
+ kfree(update->filename);
+ update->filename = NULL;
+ put_device(&mgr->dev);
+ progress_complete(mgr);
+}
+
+static ssize_t filename_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count) {
+ struct fpga_manager *mgr = to_fpga_manager(dev);
+ struct fpga_async_update *update = &mgr->async_update;
+ int ret = count;
+
+ if (count == 0 || count >= PATH_MAX)
+ return -EINVAL;
+
+ mutex_lock(&update->lock);
+ if (update->driver_unload || update->progress != FPGA_ASYNC_PROG_IDLE) {
+ ret = -EBUSY;
+ goto unlock_exit;
+ }
+
+ update->filename = kmemdup_nul(buf, count, GFP_KERNEL);
+ if (!update->filename) {
+ ret = -ENOMEM;
+ goto unlock_exit;
+ }
+
+ update->err_code = FPGA_ASYNC_ERR_NONE;
+ update->progress = FPGA_ASYNC_PROG_READING;
+ reinit_completion(&update->done);
+ schedule_work(&update->work);
+
+unlock_exit:
+ mutex_unlock(&update->lock);
+ return ret;
+}
+static DEVICE_ATTR_WO(filename);
+
+static umode_t
+fpga_async_update_visible(struct kobject *kobj, struct attribute
+*attr, int n) {
+ struct fpga_manager *mgr = to_fpga_manager(kobj_to_dev(kobj));
+
+ if (!mgr->mops->async_write_init)
+ return 0;
+
+ return attr->mode;
+}
+
+static struct attribute *fpga_mgr_async_attrs[] = {
+ &dev_attr_filename.attr,
+ NULL,
+};
+
+static struct attribute_group fpga_mgr_async_group = {
+ .name = "async_update",
+ .attrs = fpga_mgr_async_attrs,
+ .is_visible = fpga_async_update_visible, };
+
+static const struct attribute_group *fpga_mgr_groups[] = {
+ &fpga_mgr_group,
+ &fpga_mgr_async_group,
+ NULL,
+};
static struct fpga_manager *__fpga_mgr_get(struct device *dev) { @@
-575,6 +735,15 @@ struct fpga_manager *fpga_mgr_create(struct device *dev, const char *name,
return NULL;
}
+ if (mops->async_write_init || mops->async_write_blk ||
+ mops->async_write_complete || mops->async_cancel ||
+ mops->async_cleanup) {
+ if (!mops->async_write_init || !mops->async_write_blk ||
+ !mops->async_write_complete || !mops->async_cancel)
+ dev_err(dev, "Attempt to register incomplete async ops\n");
+ return NULL;
+ }
+
if (!name || !strlen(name)) {
dev_err(dev, "Attempt to register with no name!\n");
return NULL;
@@ -594,6 +763,12 @@ struct fpga_manager *fpga_mgr_create(struct device *dev, const char *name,
mgr->mops = mops;
mgr->priv = priv;
+ mgr->async_update.driver_unload = false;
+ mgr->async_update.progress = FPGA_ASYNC_PROG_IDLE;
+ mutex_init(&mgr->async_update.lock);
+ init_completion(&mgr->async_update.done);
+ INIT_WORK(&mgr->async_update.work, fpga_mgr_async_update);
+
device_initialize(&mgr->dev);
mgr->dev.class = fpga_mgr_class;
mgr->dev.groups = mops->groups;
@@ -710,11 +885,33 @@ EXPORT_SYMBOL_GPL(fpga_mgr_register);
* @mgr: fpga manager struct
*
* This function is intended for use in a FPGA manager driver's remove function.
+ *
+ * For some devices, once an aysynchronous update has begun the
+ authentication
+ * phase, the hardware cannot be signaled to stop, and the driver
+ will not exit
+ * until the hardware signals completion. This could be 30+ minutes of waiting.
+ * The driver_unload flag enables a force-unload of the driver (e.g.
+ modprobe -r)
+ * by signaling the parent driver to exit even if the hardware update is incomplete.
+ * The driver_unload flag also prevents new updates from starting
+ once the
+ * unregister process has begun.
*/
void fpga_mgr_unregister(struct fpga_manager *mgr) {
+ struct fpga_async_update *update = &mgr->async_update;
+
dev_info(&mgr->dev, "%s %s\n", __func__, mgr->name);
+ mutex_lock(&update->lock);
+ update->driver_unload = true;
+ if (update->progress == FPGA_ASYNC_PROG_IDLE) {
+ mutex_unlock(&update->lock);
+ goto unregister;
+ }
+
+ mutex_unlock(&update->lock);
+ wait_for_completion(&update->done);
+
+unregister:
+
/*
* If the low level driver provides a method for putting fpga into
* a desired state upon unregister, do it.
diff --git a/include/linux/fpga/fpga-mgr.h
b/include/linux/fpga/fpga-mgr.h index 2bc3030a69e5..9ff6c55f7a43
100644
--- a/include/linux/fpga/fpga-mgr.h
+++ b/include/linux/fpga/fpga-mgr.h
@@ -9,6 +9,7 @@
#define _LINUX_FPGA_MGR_H
#include <linux/mutex.h>
+#include <linux/completion.h>
#include <linux/platform_device.h>
struct fpga_manager;
@@ -74,6 +75,30 @@ enum fpga_mgr_states {
#define FPGA_MGR_BITSTREAM_LSB_FIRST BIT(3)
#define FPGA_MGR_COMPRESSED_BITSTREAM BIT(4)
+/* Asynchronous update error codes */ enum fpga_async_err {
+ FPGA_ASYNC_ERR_NONE,
+ FPGA_ASYNC_ERR_HW_ERROR,
+ FPGA_ASYNC_ERR_TIMEOUT,
+ FPGA_ASYNC_ERR_CANCELED,
+ FPGA_ASYNC_ERR_BUSY,
+ FPGA_ASYNC_ERR_INVALID_SIZE,
+ FPGA_ASYNC_ERR_RW_ERROR,
+ FPGA_ASYNC_ERR_WEAROUT,
+ FPGA_ASYNC_ERR_FILE_READ,
+ FPGA_ASYNC_ERR_MAX
+};
+
+/* Asynchronous update progress codes */ enum fpga_async_prog {
+ FPGA_ASYNC_PROG_IDLE,
+ FPGA_ASYNC_PROG_READING,
+ FPGA_ASYNC_PROG_PREPARING,
+ FPGA_ASYNC_PROG_WRITING,
+ FPGA_ASYNC_PROG_PROGRAMMING,
+ FPGA_ASYNC_PROG_MAX
+};
+
/**
* struct fpga_image_info - information specific to a FPGA image
* @flags: boolean flags as defined above @@ -133,6 +158,16 @@ struct
fpga_manager_ops {
int (*write_complete)(struct fpga_manager *mgr,
struct fpga_image_info *info);
void (*fpga_remove)(struct fpga_manager *mgr);
+
+ /* async update ops */
+ enum fpga_async_err (*async_write_init)(struct fpga_manager *mgr,
+ size_t count);
+ enum fpga_async_err (*async_write_blk)(struct fpga_manager *mgr,
+ const char *buf, u32 offset, size_t count);
+ enum fpga_async_err (*async_write_complete)(struct fpga_manager *mgr);
+ enum fpga_async_err (*async_cancel)(struct fpga_manager *mgr);
+ void (*async_cleanup)(struct fpga_manager *mgr);
+
const struct attribute_group **groups; };
@@ -154,6 +189,22 @@ struct fpga_compat_id {
u64 id_l;
};
+/**
+ * struct fpga_async_update - asynchronous image update structure
+ * @name: name of low level fpga manager */ struct fpga_async_update
+{
+ char *filename;
+ const u8 *data; /* pointer to update data */
+ u32 remaining_size; /* size remaining to transfer */
+ struct work_struct work;
+ struct completion done;
+ enum fpga_async_prog progress;
+ enum fpga_async_err err_code; /* async update error code */
+ bool driver_unload;
+ struct mutex lock; /* protect data structure contents */
+};
+
/**
* struct fpga_manager - fpga manager structure
* @name: name of low level fpga manager @@ -171,6 +222,7 @@ struct
fpga_manager {
enum fpga_mgr_states state;
struct fpga_compat_id *compat_id;
const struct fpga_manager_ops *mops;
+ struct fpga_async_update async_update;
void *priv;
};