firmware_loader: Add sysfs nodes to monitor fw_upload
authorRuss Weight <russell.h.weight@intel.com>
Thu, 21 Apr 2022 21:22:01 +0000 (14:22 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 26 Apr 2022 10:35:55 +0000 (12:35 +0200)
Add additional sysfs nodes to monitor the transfer of firmware upload data
to the target device:

cancel: Write 1 to cancel the data transfer
error: Display error status for a failed firmware upload
remaining_size: Display the remaining amount of data to be transferred
status: Display the progress of the firmware upload

Reviewed-by: Luis Chamberlain <mcgrof@kernel.org>
Reviewed-by: Tianfei zhang <tianfei.zhang@intel.com>
Tested-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
Signed-off-by: Russ Weight <russell.h.weight@intel.com>
Link: https://lore.kernel.org/r/20220421212204.36052-6-russell.h.weight@intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/ABI/testing/sysfs-class-firmware
Documentation/driver-api/firmware/fw_upload.rst
drivers/base/firmware_loader/sysfs.c
drivers/base/firmware_loader/sysfs_upload.c
drivers/base/firmware_loader/sysfs_upload.h

index 18336c2..978d3d5 100644 (file)
@@ -10,6 +10,30 @@ Description: The data sysfs file is used for firmware-fallback and for
                signal the lower-level driver that the firmware data is
                available.
 
+What:          /sys/class/firmware/.../cancel
+Date:          July 2022
+KernelVersion: 5.19
+Contact:       Russ Weight <russell.h.weight@intel.com>
+Description:   Write-only. For firmware uploads, write a "1" to this file to
+               request that the transfer of firmware data to the lower-level
+               device be canceled. This request will be rejected (EBUSY) if
+               the update cannot be canceled (e.g. a FLASH write is in
+               progress) or (ENODEV) if there is no firmware update in progress.
+
+What:          /sys/class/firmware/.../error
+Date:          July 2022
+KernelVersion: 5.19
+Contact:       Russ Weight <russell.h.weight@intel.com>
+Description:   Read-only. Returns a string describing a failed firmware
+               upload. This string will be in the form of <STATUS>:<ERROR>,
+               where <STATUS> will be one of the status strings described
+               for the status sysfs file and <ERROR> will be one of the
+               following: "hw-error", "timeout", "user-abort", "device-busy",
+               "invalid-file-size", "read-write-error", "flash-wearout". The
+               error sysfs file is only meaningful when the current firmware
+               upload status is "idle". If this file is read while a firmware
+               transfer is in progress, then the read will fail with EBUSY.
+
 What:          /sys/class/firmware/.../loading
 Date:          July 2022
 KernelVersion: 5.19
@@ -22,6 +46,27 @@ Description: The loading sysfs file is used for both firmware-fallback and
                uploads, the zero value also triggers the transfer of the
                firmware data to the lower-level device driver.
 
+What:          /sys/class/firmware/.../remaining_size
+Date:          July 2022
+KernelVersion: 5.19
+Contact:       Russ Weight <russell.h.weight@intel.com>
+Description:   Read-only. For firmware upload, this file contains the size
+               of the firmware data that remains to be transferred to the
+               lower-level device driver. The size value is initialized to
+               the full size of the firmware image that was previously
+               written to the data sysfs file. This value is periodically
+               updated during the "transferring" phase of the firmware
+               upload.
+               Format: "%u".
+
+What:          /sys/class/firmware/.../status
+Date:          July 2022
+KernelVersion: 5.19
+Contact:       Russ Weight <russell.h.weight@intel.com>
+Description:   Read-only. Returns a string describing the current status of
+               a firmware upload. The string will be one of the following:
+               idle, "receiving", "preparing", "transferring", "programming".
+
 What:          /sys/class/firmware/.../timeout
 Date:          July 2022
 KernelVersion: 5.19
index afbd8ba..7692259 100644 (file)
@@ -9,7 +9,8 @@ persistent sysfs nodes to enable users to initiate firmware updates for
 that device.  It is the responsibility of the device driver and/or the
 device itself to perform any validation on the data received. Firmware
 upload uses the same *loading* and *data* sysfs files described in the
-documentation for firmware fallback.
+documentation for firmware fallback. It also adds additional sysfs files
+to provide status on the transfer of the firmware image to the device.
 
 Register for firmware upload
 ============================
@@ -93,7 +94,9 @@ Firmware Upload Ops
 
 Firmware Upload Progress Codes
 ------------------------------
-The following progress codes are used internally by the firmware loader:
+The following progress codes are used internally by the firmware loader.
+Corresponding strings are reported through the status sysfs node that
+is described below and are documented in the ABI documentation.
 
 .. kernel-doc:: drivers/base/firmware_loader/sysfs_upload.h
    :identifiers: fw_upload_prog
@@ -105,3 +108,19 @@ failure:
 
 .. kernel-doc:: include/linux/firmware.h
    :identifiers: fw_upload_err
+
+Sysfs Attributes
+================
+
+In addition to the *loading* and *data* sysfs files, there are additional
+sysfs files to monitor the status of the data transfer to the target
+device and to determine the final pass/fail status of the transfer.
+Depending on the device and the size of the firmware image, a firmware
+update could take milliseconds or minutes.
+
+The additional sysfs files are:
+
+* status - provides an indication of the progress of a firmware update
+* error - provides error information for a failed firmware update
+* remaining_size - tracks the data transfer portion of an update
+* cancel - echo 1 to this file to cancel the update
index 4a956cc..c09fceb 100644 (file)
@@ -371,6 +371,12 @@ static struct bin_attribute firmware_attr_data = {
 
 static struct attribute *fw_dev_attrs[] = {
        &dev_attr_loading.attr,
+#ifdef CONFIG_FW_UPLOAD
+       &dev_attr_cancel.attr,
+       &dev_attr_status.attr,
+       &dev_attr_error.attr,
+       &dev_attr_remaining_size.attr,
+#endif
        NULL
 };
 
@@ -382,6 +388,9 @@ static struct bin_attribute *fw_dev_bin_attrs[] = {
 static const struct attribute_group fw_dev_attr_group = {
        .attrs = fw_dev_attrs,
        .bin_attrs = fw_dev_bin_attrs,
+#ifdef CONFIG_FW_UPLOAD
+       .is_visible = fw_upload_is_visible,
+#endif
 };
 
 static const struct attribute_group *fw_dev_attr_groups[] = {
index 0a6450d..c504dae 100644 (file)
  * Support for user-space to initiate a firmware upload to a device.
  */
 
+static const char * const fw_upload_prog_str[] = {
+       [FW_UPLOAD_PROG_IDLE]         = "idle",
+       [FW_UPLOAD_PROG_RECEIVING]    = "receiving",
+       [FW_UPLOAD_PROG_PREPARING]    = "preparing",
+       [FW_UPLOAD_PROG_TRANSFERRING] = "transferring",
+       [FW_UPLOAD_PROG_PROGRAMMING]  = "programming"
+};
+
+static const char * const fw_upload_err_str[] = {
+       [FW_UPLOAD_ERR_NONE]         = "none",
+       [FW_UPLOAD_ERR_HW_ERROR]     = "hw-error",
+       [FW_UPLOAD_ERR_TIMEOUT]      = "timeout",
+       [FW_UPLOAD_ERR_CANCELED]     = "user-abort",
+       [FW_UPLOAD_ERR_BUSY]         = "device-busy",
+       [FW_UPLOAD_ERR_INVALID_SIZE] = "invalid-file-size",
+       [FW_UPLOAD_ERR_RW_ERROR]     = "read-write-error",
+       [FW_UPLOAD_ERR_WEAROUT]      = "flash-wearout",
+};
+
+static const char *fw_upload_progress(struct device *dev,
+                                     enum fw_upload_prog prog)
+{
+       const char *status = "unknown-status";
+
+       if (prog < FW_UPLOAD_PROG_MAX)
+               status = fw_upload_prog_str[prog];
+       else
+               dev_err(dev, "Invalid status during secure update: %d\n", prog);
+
+       return status;
+}
+
+static const char *fw_upload_error(struct device *dev,
+                                  enum fw_upload_err err_code)
+{
+       const char *error = "unknown-error";
+
+       if (err_code < FW_UPLOAD_ERR_MAX)
+               error = fw_upload_err_str[err_code];
+       else
+               dev_err(dev, "Invalid error code during secure update: %d\n",
+                       err_code);
+
+       return error;
+}
+
+static ssize_t
+status_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv;
+
+       return sysfs_emit(buf, "%s\n", fw_upload_progress(dev, fwlp->progress));
+}
+DEVICE_ATTR_RO(status);
+
+static ssize_t
+error_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv;
+       int ret;
+
+       mutex_lock(&fwlp->lock);
+
+       if (fwlp->progress != FW_UPLOAD_PROG_IDLE)
+               ret = -EBUSY;
+       else if (!fwlp->err_code)
+               ret = 0;
+       else
+               ret = sysfs_emit(buf, "%s:%s\n",
+                                fw_upload_progress(dev, fwlp->err_progress),
+                                fw_upload_error(dev, fwlp->err_code));
+
+       mutex_unlock(&fwlp->lock);
+
+       return ret;
+}
+DEVICE_ATTR_RO(error);
+
+static ssize_t cancel_store(struct device *dev, struct device_attribute *attr,
+                           const char *buf, size_t count)
+{
+       struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv;
+       int ret = count;
+       bool cancel;
+
+       if (kstrtobool(buf, &cancel) || !cancel)
+               return -EINVAL;
+
+       mutex_lock(&fwlp->lock);
+       if (fwlp->progress == FW_UPLOAD_PROG_IDLE)
+               ret = -ENODEV;
+
+       fwlp->ops->cancel(fwlp->fw_upload);
+       mutex_unlock(&fwlp->lock);
+
+       return ret;
+}
+DEVICE_ATTR_WO(cancel);
+
+static ssize_t remaining_size_show(struct device *dev,
+                                  struct device_attribute *attr, char *buf)
+{
+       struct fw_upload_priv *fwlp = to_fw_sysfs(dev)->fw_upload_priv;
+
+       return sysfs_emit(buf, "%u\n", fwlp->remaining_size);
+}
+DEVICE_ATTR_RO(remaining_size);
+
+umode_t
+fw_upload_is_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+       static struct fw_sysfs *fw_sysfs;
+
+       fw_sysfs = to_fw_sysfs(kobj_to_dev(kobj));
+
+       if (fw_sysfs->fw_upload_priv || attr == &dev_attr_loading.attr)
+               return attr->mode;
+
+       return 0;
+}
+
 static void fw_upload_update_progress(struct fw_upload_priv *fwlp,
                                      enum fw_upload_prog new_progress)
 {
index 18bd4d9..9edd47d 100644 (file)
@@ -37,6 +37,11 @@ struct fw_upload_priv {
 };
 
 #ifdef CONFIG_FW_UPLOAD
+extern struct device_attribute dev_attr_status;
+extern struct device_attribute dev_attr_error;
+extern struct device_attribute dev_attr_cancel;
+extern struct device_attribute dev_attr_remaining_size;
+
 int fw_upload_start(struct fw_sysfs *fw_sysfs);
 umode_t fw_upload_is_visible(struct kobject *kobj, struct attribute *attr, int n);
 #else