[PATCH] firmware_class: Add firmware filename overrides

From: Charlie Mooney
Date: Wed Mar 04 2015 - 18:26:49 EST


This patch adds an additional feature to the firmware_class.c module.
To allow a unified method of specifying new firmware locations when
drivers request a firmware binary to update their devices with, we
have added the concept of a "fw override"

A fw override is a rule that matches firmware requests based on the
name of the device requesting the fw and what the filename for the
fw they are requesting is, and overrides their requests with a new
value.

Overrides are set up by piping a description of the override into
an attribute set up at /sys/class/firmware/fw_override. The string
should be a null-deliminited list of the firmware name you want to
over ride, the new name to replace it with, and the device name that
you want the override to apply to. For example you could set up
an override for a device called "my_device" so that any time it
requests a firmware called "my_fw.bin" it instead gets "new_fw.bin"
with the following command:

echo -e 'my_fw.bin\0new_fw.bin\0my_device\0' >
/sys/class/firmware/fw_override

The device name is an optional field, and if no string is there it
will apply to ANY device requesting a firmware of that name. For
example if you want all devices looking for my_fw.bin to get
new_fw.bin instead you might use this command:

echo -e 'my_fw.bin\0new_fw.bin\0\0' > /sys/class/firmware/fw_override

Existing overrides can be viewed by cat-ing that attribute and can
be overwritten with new rules at will.

To delete an override simply put an empty string in the new firmware
field and the matching rule will be removed. eg: to remove the rule
set up in the first above example, one might run this command.

echo -e 'my_fw.bin\0\0my_device\0' > /sys/class/firmware/fw_override

Note: To make this feature usable even when CONFIG_FW_LOADER_USER_HELPER
is not set, the firmware class is brought back. However, "timeout"
and the other functionality remain disabled.

Signed-off-by: Charlie Mooney <charliemooney@xxxxxxxxxxxx>
---
drivers/base/firmware_class.c | 196 ++++++++++++++++++++++++++++++++++++++----
1 file changed, 180 insertions(+), 16 deletions(-)

diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index 6c5c9ed..863ced5 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -440,6 +440,163 @@ static int fw_add_devm_name(struct device *dev, const char *name)
}
#endif

+struct fw_override {
+ struct list_head node;
+ const char *old_name;
+ const char *new_name;
+ const char *device_name;
+};
+static LIST_HEAD(fw_override_list);
+
+/*
+ * find_fw_override -- Find the override for the fw name and device specified
+ *
+ * Given a fw filename and the devices's name, this function finds the fw
+ * override assigned to it (if one exists).
+ *
+ * Returns a pointer to the fw_override struct in question or NULL if it
+ * does not exist.
+ */
+static struct fw_override *find_fw_override(const char *fw_name,
+ const char *device_name)
+{
+ struct fw_override *override;
+
+ if (!fw_name)
+ return NULL;
+
+ list_for_each_entry(override, &fw_override_list, node) {
+ if (!strcmp(override->old_name, fw_name) &&
+ (!device_name ||
+ !strcmp(device_name, override->device_name))) {
+ return override;
+ }
+ }
+ return NULL;
+}
+
+/* Determine what is the actual filename that should be loaded for a given
+ * firmware and device. If there is no overrides in place for this device
+ * and firmware, then the name will be returned unchanged. Otherwise, the
+ * overridden firmware name is returned.
+ *
+ * This is called from _request_firmware() to make sure that overrides are
+ * applied to every firmware request regardless of how the request is made.
+ */
+static const char *firmware_overridden_name(const char *name,
+ struct device *device)
+{
+ struct fw_override *override;
+
+ /* Check first for device-specific overrides, before checking for more
+ * generic overrides that might apply to this device.
+ */
+ override = find_fw_override(name, dev_name(device));
+ if (!override)
+ override = find_fw_override(name, NULL);
+
+ if (override)
+ dev_info(device, "firmware: '%s' overridden to '%s'.\n",
+ name, override->new_name);
+
+ return override ? override->new_name : name;
+}
+
+
+/*
+ * delete_fw_override -- Remove an override for the fw
+ *
+ * Given a firmware filename and the device name, remove any overrides that may
+ * have been set for it. If the device name is NULL, then this looks only for
+ * global overrides.
+ */
+static void delete_fw_override(const char *fw_name, const char *device_name)
+{
+ struct fw_override *to_delete;
+
+ to_delete = find_fw_override(fw_name, device_name);
+ if (to_delete) {
+ list_del(&to_delete->node);
+ kfree(to_delete);
+ }
+}
+
+/*
+ * fw_override_show -- Display the current table of fw overrides
+ *
+ * When reading from this attribute, this function returns a table of
+ * fw filenames and their overridden values.
+ */
+static ssize_t fw_override_show(struct class *class,
+ struct class_attribute *attr,
+ char *buf)
+{
+ struct fw_override *override;
+ int len = 0;
+
+ list_for_each_entry(override, &fw_override_list, node) {
+ len += scnprintf(&buf[len],
+ PAGE_SIZE - len,
+ "%s\t%s\t(%s)\n",
+ override->old_name, override->new_name,
+ (strlen(override->device_name) > 0 ?
+ override->device_name : "Any"));
+ }
+
+ return len;
+}
+
+/*
+ * fw_override_store -- Add a new fw override
+ *
+ * This function takes a string as input of the form
+ * "old_fw_name\0new_fw_name\0device_name\0"
+ * and adds it to the list of existing fw overrides.
+ *
+ * When any device requests a FW with the "old_fw_name" it will instead
+ * be given the FW at new_fw_name.
+ *
+ * Note: The device_name is optional, but if it's included this new rule
+ * will only apply to that specific device as opposed to ALL devices.
+ */
+static ssize_t fw_override_store(struct class *class,
+ struct class_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fw_override *new_override;
+ int i, num_strings = 0;
+
+ /* First check that there are exactly 3 null terminated strings and
+ * that the first one is not the empty string.
+ */
+ for (i = 0; i < count; i++)
+ num_strings += (buf[i] == '\0') ? 1 : 0;
+ if (num_strings != 3 || strlen(buf) == 0)
+ return -EINVAL;
+
+ /* Fill a new fw_override struct with the supplied information */
+ new_override = kmalloc(sizeof(struct fw_override) + count, GFP_KERNEL);
+ if (!new_override)
+ return -ENOMEM;
+ memcpy((char *)new_override + sizeof(struct fw_override), buf, count);
+ new_override->old_name = (char *)new_override +
+ sizeof(struct fw_override);
+ new_override->new_name = new_override->old_name +
+ strlen(new_override->old_name) + 1;
+ new_override->device_name = new_override->new_name +
+ strlen(new_override->new_name) + 1;
+
+ /* Delete any existing overrides with this old_name and device_name */
+ delete_fw_override(new_override->old_name, new_override->device_name);
+
+ /* If there was a new fw name specified, store this override */
+ if (strlen(new_override->new_name) > 0)
+ list_add_tail(&new_override->node, &fw_override_list);
+ else
+ kfree(new_override);
+
+ return count;
+}

/*
* user-mode helper code
@@ -532,11 +689,6 @@ static ssize_t timeout_store(struct class *class, struct class_attribute *attr,
return count;
}

-static struct class_attribute firmware_class_attrs[] = {
- __ATTR_RW(timeout),
- __ATTR_NULL
-};
-
static void fw_dev_release(struct device *dev)
{
struct firmware_priv *fw_priv = to_firmware_priv(dev);
@@ -557,14 +709,26 @@ static int firmware_uevent(struct device *dev, struct kobj_uevent_env *env)

return 0;
}
+#endif /* CONFIG_FW_LOADER_USER_HELPER */
+
+static struct class_attribute firmware_class_attrs[] = {
+#ifdef CONFIG_FW_LOADER_USER_HELPER
+ __ATTR_RW(timeout),
+#endif
+ __ATTR_RW(fw_override),
+ __ATTR_NULL
+};

static struct class firmware_class = {
.name = "firmware",
.class_attrs = firmware_class_attrs,
+#ifdef CONFIG_FW_LOADER_USER_HELPER
.dev_uevent = firmware_uevent,
.dev_release = fw_dev_release,
+#endif
};

+#ifdef CONFIG_FW_LOADER_USER_HELPER
static ssize_t firmware_loading_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -978,7 +1142,6 @@ static inline void kill_requests_without_uevent(void) { }

#endif /* CONFIG_FW_LOADER_USER_HELPER */

-
/* wait until the shared firmware_buf becomes ready (or error) */
static int sync_cached_firmware_buf(struct firmware_buf *buf)
{
@@ -1087,6 +1250,7 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
{
struct firmware *fw;
long timeout;
+ const char *overridden_name;
int ret;

if (!firmware_p)
@@ -1095,7 +1259,8 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
if (!name || name[0] == '\0')
return -EINVAL;

- ret = _request_firmware_prepare(&fw, name, device);
+ overridden_name = firmware_overridden_name(name, device);
+ ret = _request_firmware_prepare(&fw, overridden_name, device);
if (ret <= 0) /* error or already assigned */
goto out;

@@ -1105,7 +1270,7 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
timeout = usermodehelper_read_lock_wait(timeout);
if (!timeout) {
dev_dbg(device, "firmware: %s loading timed out\n",
- name);
+ overridden_name);
ret = -EBUSY;
goto out;
}
@@ -1113,7 +1278,7 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
ret = usermodehelper_read_trylock();
if (WARN_ON(ret)) {
dev_err(device, "firmware: %s will not be loaded\n",
- name);
+ overridden_name);
goto out;
}
}
@@ -1123,11 +1288,12 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
if (!(opt_flags & FW_OPT_NO_WARN))
dev_warn(device,
"Direct firmware load for %s failed with error %d\n",
- name, ret);
+ overridden_name, ret);
if (opt_flags & FW_OPT_USERHELPER) {
dev_warn(device, "Falling back to user helper\n");
- ret = fw_load_from_user_helper(fw, name, device,
- opt_flags, timeout);
+ ret = fw_load_from_user_helper(fw, overridden_name,
+ device, opt_flags,
+ timeout);
}
}

@@ -1659,10 +1825,8 @@ static int __init firmware_class_init(void)
fw_cache_init();
#ifdef CONFIG_FW_LOADER_USER_HELPER
register_reboot_notifier(&fw_shutdown_nb);
- return class_register(&firmware_class);
-#else
- return 0;
#endif
+ return class_register(&firmware_class);
}

static void __exit firmware_class_exit(void)
@@ -1673,8 +1837,8 @@ static void __exit firmware_class_exit(void)
#endif
#ifdef CONFIG_FW_LOADER_USER_HELPER
unregister_reboot_notifier(&fw_shutdown_nb);
- class_unregister(&firmware_class);
#endif
+ class_unregister(&firmware_class);
}

fs_initcall(firmware_class_init);
--
2.1.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/