Refactor fw_get_filesystem_firmware() by extracting the per-path firmware loading logic into a new fw_try_firmware_path() helper.
Add a new firmware_class.search= module option that accepts a ':'-separated list of firmware search directories. The input is preprocessed at set time (boot or sysfs write) into a NUL-separated sequence of paths, avoiding per-load parsing overhead. Backslash escapes are supported: '\:' for literal ':', '\\' for literal '\'. The firmware lookup order is: 1. firmware_class.path= (single legacy path) 2. firmware_class.search= (colon-separated paths) 3. Built-in default paths (/lib/firmware/updates/..., /lib/firmware) Example: firmware_class.search=/custom/path1:/custom/path2 Suggested-by: Michal Grzedzicki <[email protected]> Signed-off-by: Jeff Layton <[email protected]> --- drivers/base/firmware_loader/main.c | 273 ++++++++++++++++++++++++++---------- 1 file changed, 197 insertions(+), 76 deletions(-) diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c index a11b30dda23be563bd55f25474ceff2153ddd667..c86f86977aa436ce6ccc19506cb0a774e06d019c 100644 --- a/drivers/base/firmware_loader/main.c +++ b/drivers/base/firmware_loader/main.c @@ -469,8 +469,9 @@ static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv, /* direct firmware loading support */ static char fw_path_para[256]; +static char fw_search_para[4096]; +static int fw_search_len; static const char * const fw_path[] = { - fw_path_para, "/lib/firmware/updates/" UTS_RELEASE, "/lib/firmware/updates", "/lib/firmware/" UTS_RELEASE, @@ -485,6 +486,159 @@ static const char * const fw_path[] = { module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644); MODULE_PARM_DESC(path, "customized firmware image search path with a higher priority than default path"); +/* + * fw_search_set - preprocess a colon-separated search path string + * + * Converts the input into a NUL-separated sequence of paths stored in + * fw_search_para, with fw_search_len tracking the total used length. + * Backslash escapes '\:' (literal ':') and '\\' (literal '\'). + * Trailing newlines on each component are stripped. + */ +static int fw_search_set(const char *val, const struct kernel_param *kp) +{ + const char *p; + int len = 0; + + if (!val) { + fw_search_para[0] = '\0'; + fw_search_len = 0; + return 0; + } + + for (p = val; *p; p++) { + if (p[0] == '\\' && (p[1] == ':' || p[1] == '\\')) { + p++; + if (len < sizeof(fw_search_para) - 2) + fw_search_para[len++] = *p; + } else if (*p == ':') { + /* strip trailing newline before the separator */ + if (len > 0 && fw_search_para[len - 1] == '\n') + len--; + if (len > 0 && fw_search_para[len - 1] != '\0') { + if (len < sizeof(fw_search_para) - 2) + fw_search_para[len++] = '\0'; + } + } else { + if (len < sizeof(fw_search_para) - 2) + fw_search_para[len++] = *p; + } + } + + /* strip trailing newline from last component */ + if (len > 0 && fw_search_para[len - 1] == '\n') + len--; + + /* ensure double-NUL termination */ + fw_search_para[len] = '\0'; + fw_search_len = len; + + return 0; +} + +/* + * fw_search_get - reconstruct colon-separated string for sysfs reads + */ +static int fw_search_get(char *buffer, const struct kernel_param *kp) +{ + const char *p; + int pos = 0; + + p = fw_search_para; + while (p < fw_search_para + fw_search_len) { + int slen = strlen(p); + + if (!slen) + break; + if (pos > 0) + buffer[pos++] = ':'; + memcpy(buffer + pos, p, slen); + pos += slen; + p += slen + 1; + } + buffer[pos] = '\0'; + return pos; +} + +static const struct kernel_param_ops fw_search_ops = { + .set = fw_search_set, + .get = fw_search_get, +}; +module_param_cb(search, &fw_search_ops, NULL, 0644); +MODULE_PARM_DESC(search, "colon-separated list of firmware search paths, tried after path= (use '\\:' for literal ':', '\\\\' for literal '\\')"); + +static int +fw_try_firmware_path(struct device *device, struct fw_priv *fw_priv, + const char *suffix, + int (*decompress)(struct device *dev, + struct fw_priv *fw_priv, + size_t in_size, + const void *in_buffer), + const char *dir, int dirlen, + char *path, void **bufp, size_t msize) +{ + size_t file_size = 0; + size_t *file_size_ptr = NULL; + size_t size; + int len, rc; + + len = snprintf(path, PATH_MAX, "%.*s/%s%s", + dirlen, dir, fw_priv->fw_name, suffix); + if (len >= PATH_MAX) + return -ENAMETOOLONG; + + fw_priv->size = 0; + + /* + * The total file size is only examined when doing a partial + * read; the "full read" case needs to fail if the whole + * firmware was not completely loaded. + */ + if ((fw_priv->opt_flags & FW_OPT_PARTIAL) && *bufp) + file_size_ptr = &file_size; + + /* load firmware files from the mount namespace of init */ + rc = kernel_read_file_from_path_initns(path, fw_priv->offset, + bufp, msize, + file_size_ptr, + READING_FIRMWARE); + if (rc < 0) { + if (!(fw_priv->opt_flags & FW_OPT_NO_WARN)) { + if (rc != -ENOENT) + dev_warn(device, + "loading %s failed with error %d\n", + path, rc); + else + dev_dbg(device, + "loading %s failed for no such file or directory.\n", + path); + } + return rc; + } + size = rc; + + dev_dbg(device, "Loading firmware from %s\n", path); + if (decompress) { + dev_dbg(device, "f/w decompressing %s\n", + fw_priv->fw_name); + rc = decompress(device, fw_priv, size, *bufp); + /* discard the superfluous original content */ + vfree(*bufp); + *bufp = NULL; + if (rc) { + fw_free_paged_buf(fw_priv); + return rc; + } + } else { + dev_dbg(device, "direct-loading %s\n", + fw_priv->fw_name); + if (!fw_priv->data) + fw_priv->data = *bufp; + fw_priv->size = size; + } + fw_state_done(fw_priv); + return 0; +} + static int fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, const char *suffix, @@ -493,10 +647,9 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, size_t in_size, const void *in_buffer)) { - size_t size; - int i, len, maxlen = 0; + int i; int rc = -ENOENT; - char *path, *nt = NULL; + char *path; size_t msize = INT_MAX; void *buffer = NULL; @@ -511,83 +664,51 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv, return -ENOMEM; wait_for_initramfs(); - for (i = 0; i < ARRAY_SIZE(fw_path); i++) { - size_t file_size = 0; - size_t *file_size_ptr = NULL; - - /* skip the unset customized path */ - if (!fw_path[i][0]) - continue; - - /* strip off \n from customized path */ - maxlen = strlen(fw_path[i]); - if (i == 0) { - nt = strchr(fw_path[i], '\n'); - if (nt) - maxlen = nt - fw_path[i]; - } - len = snprintf(path, PATH_MAX, "%.*s/%s%s", - maxlen, fw_path[i], - fw_priv->fw_name, suffix); - if (len >= PATH_MAX) { - rc = -ENAMETOOLONG; - break; - } + /* Try the customized path first */ + if (fw_path_para[0]) { + int dirlen = strlen(fw_path_para); - fw_priv->size = 0; + /* strip trailing newline */ + if (fw_path_para[dirlen - 1] == '\n') + dirlen--; - /* - * The total file size is only examined when doing a partial - * read; the "full read" case needs to fail if the whole - * firmware was not completely loaded. - */ - if ((fw_priv->opt_flags & FW_OPT_PARTIAL) && buffer) - file_size_ptr = &file_size; - - /* load firmware files from the mount namespace of init */ - rc = kernel_read_file_from_path_initns(path, fw_priv->offset, - &buffer, msize, - file_size_ptr, - READING_FIRMWARE); - if (rc < 0) { - if (!(fw_priv->opt_flags & FW_OPT_NO_WARN)) { - if (rc != -ENOENT) - dev_warn(device, - "loading %s failed with error %d\n", - path, rc); - else - dev_dbg(device, - "loading %s failed for no such file or directory.\n", - path); - } - continue; - } - size = rc; - rc = 0; - - dev_dbg(device, "Loading firmware from %s\n", path); - if (decompress) { - dev_dbg(device, "f/w decompressing %s\n", - fw_priv->fw_name); - rc = decompress(device, fw_priv, size, buffer); - /* discard the superfluous original content */ - vfree(buffer); - buffer = NULL; - if (rc) { - fw_free_paged_buf(fw_priv); - continue; - } - } else { - dev_dbg(device, "direct-loading %s\n", - fw_priv->fw_name); - if (!fw_priv->data) - fw_priv->data = buffer; - fw_priv->size = size; + rc = fw_try_firmware_path(device, fw_priv, suffix, decompress, + fw_path_para, dirlen, + path, &buffer, msize); + if (!rc) + goto done; + } + + /* Try each preprocessed NUL-separated path in fw_search_para */ + if (fw_search_len > 0) { + const char *p = fw_search_para; + + while (p < fw_search_para + fw_search_len) { + int dirlen = strlen(p); + + if (!dirlen) + break; + rc = fw_try_firmware_path(device, fw_priv, + suffix, decompress, + p, dirlen, + path, &buffer, msize); + if (!rc) + goto done; + p += dirlen + 1; } - fw_state_done(fw_priv); - break; } + + /* Try default firmware paths */ + for (i = 0; i < ARRAY_SIZE(fw_path); i++) { + rc = fw_try_firmware_path(device, fw_priv, suffix, decompress, + fw_path[i], strlen(fw_path[i]), + path, &buffer, msize); + if (!rc) + break; + } + +done: __putname(path); return rc; -- 2.53.0

