From nobody Mon Mar 23 19:50:52 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E2ABC3B27E1; Mon, 23 Mar 2026 14:39:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774276787; cv=none; b=K1/KSFykEGI6Z0GsuYPsZIXWg/bEyHHgNCaY/JYtbT4GU7Hn8rJcklA7kShfj5lx2wfhHMhe2eec+Pk31j/ftzMvYcMUHLgb+osq0n3/I+qQwz9gCNI9FFvjUi72vntKiwidArpRFvanYGiNa0/v6q3tfOQTqGGw7hOtZqepNBM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774276787; c=relaxed/simple; bh=qTh5iHJUrUvA/mCf9ROhVLF/ed+RNX39tTVd2f/sH+Y=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=uymTyK8xpRq0GxjSHYU1peDObV8l2y00oAZkjRoPk4X49f+wUS2EoGxykBu3O6llqM4g7+EywKVD0nz0Jv+YXwtCAYlwAUJsaSxr6Q7zbtcHjpl3fkQIueBJ6HhCXA/m4c/6iWuoCK17Z4EqC0ppUiVGmXNzrjNZaFMfZ8w+ZCg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=mNBNS/wc; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="mNBNS/wc" Received: by smtp.kernel.org (Postfix) with ESMTPSA id D0109C2BCB4; Mon, 23 Mar 2026 14:39:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774276786; bh=qTh5iHJUrUvA/mCf9ROhVLF/ed+RNX39tTVd2f/sH+Y=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=mNBNS/wcJG6Qc7cZmAJvYxS3Vq+irU67wpfthe+JnANVAajrhhhs5b8hAv0fA9E1x eo1g/PPZ459XS9/QjfjGC2hDArZtLoqz49I9pE0RlHOE1nE493901slOOLOwBA58xQ DSOt6+dJNUIif5XEkU7wvMcZGhLIuUOABAtTtMZcULAztlcKL3mOoLSYQnhu1PpJx/ YobzpzOZQn3hKyW82SQ2jPtaa9a+Ny2skk2yXYaTW8K652QUdjcy1YVXndWm3jdPVB fv4LSkItO71igFbTbwO72KOy44sxEs2lOmADX5cg2se76qsEhaSBPock0WKM1Qe8U8 nqfDKK1VF0y2A== From: Jeff Layton Date: Mon, 23 Mar 2026 10:39:30 -0400 Subject: [PATCH v5 1/2] firmware_loader: add search= module option for multi-path firmware lookup Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260323-fw-path-v5-1-e88b2fe145f3@kernel.org> References: <20260323-fw-path-v5-0-e88b2fe145f3@kernel.org> In-Reply-To: <20260323-fw-path-v5-0-e88b2fe145f3@kernel.org> To: Luis Chamberlain , Russ Weight , Danilo Krummrich , Greg Kroah-Hartman , "Rafael J. Wysocki" , Shuah Khan Cc: Michal Grzedzicki , driver-core@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=9798; i=jlayton@kernel.org; h=from:subject:message-id; bh=qTh5iHJUrUvA/mCf9ROhVLF/ed+RNX39tTVd2f/sH+Y=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBpwVCwZRHSXBGu29v8v/rUqbAbe7L/GRj3SI4sl hcL4v2lrOKJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCacFQsAAKCRAADmhBGVaC FUp8D/9mb6NSzRNhsnfeFbp+zAzmUydMkgBn5sOQNSXDOMSWjcEYWJK/wqN5HjwsBG9S7WnT6JW AzmldFgl7K2tXE9urjXTwWKUZOJQCVXFKLD+KZvMD4R3R0Jc837foMF8Jb+V+R8cCtAcljIxzmC r5TSCn5UJQQXS+AVlzElhSPHTz4P9LRQXTQzVMefqb/dW5g/693NwmpV2UZPPgwgUvWC27gtkLj BL9POJ2HZvZrBMM3GGTPQOyIKD8I4SCVnfk6kwdH4dwSkh8NTmRaVL5agvsRIjrTMPXPuZJiv9B QVgdlzTulwd7Ssrq8OWfHuBu/sMw+ottl6kirlZJdhbK0+pOLayYIYLLWOG0bT213612MWkv5Um gnCg4/qjy53lFCloHqt0h9+3q0i4Oj6z7q1g2o8Lxbt3tHe8Fz2t/9g92pC1KS/r6TcxyVVYsx1 KL6fgTeZToOkfcrR3TDA45WldBYxwmUULobpytUWggCZa8vK0YGhRg4889AqgjTV/USz6NYsL5V XRl3WaiYBAgGi3HtCxKUZDyUDNqsmZZcTSZ/Fjlv+wnamIhYWpbuelrMRRzsvSd9h8p2wcECVSj xHywtSefjXZAp/EZTJhmXEDSUULp0NiCsvr7k8010SzeW4BdZ5GusU1uSz4+0gKZX7mRVXgnt5b plGX05joz2NpMkw== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 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=3D 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=3D (single legacy path) 2. firmware_class.search=3D (colon-separated paths) 3. Built-in default paths (/lib/firmware/updates/..., /lib/firmware) Example: firmware_class.search=3D/custom/path1:/custom/path2 Suggested-by: Michal Grzedzicki Signed-off-by: Jeff Layton --- 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_lo= ader/main.c index a11b30dda23be563bd55f25474ceff2153ddd667..c86f86977aa436ce6ccc19506cb= 0a774e06d019c 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, =20 /* 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[] =3D { - 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[] =3D { module_param_string(path, fw_path_para, sizeof(fw_path_para), 0644); MODULE_PARM_DESC(path, "customized firmware image search path with a highe= r priority than default path"); =20 +/* + * 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 =3D 0; + + if (!val) { + fw_search_para[0] =3D '\0'; + fw_search_len =3D 0; + return 0; + } + + for (p =3D val; *p; p++) { + if (p[0] =3D=3D '\\' && (p[1] =3D=3D ':' || p[1] =3D=3D '\\')) { + p++; + if (len < sizeof(fw_search_para) - 2) + fw_search_para[len++] =3D *p; + } else if (*p =3D=3D ':') { + /* strip trailing newline before the separator */ + if (len > 0 && fw_search_para[len - 1] =3D=3D '\n') + len--; + if (len > 0 && fw_search_para[len - 1] !=3D '\0') { + if (len < sizeof(fw_search_para) - 2) + fw_search_para[len++] =3D '\0'; + } + } else { + if (len < sizeof(fw_search_para) - 2) + fw_search_para[len++] =3D *p; + } + } + + /* strip trailing newline from last component */ + if (len > 0 && fw_search_para[len - 1] =3D=3D '\n') + len--; + + /* ensure double-NUL termination */ + fw_search_para[len] =3D '\0'; + fw_search_len =3D 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 =3D 0; + + p =3D fw_search_para; + while (p < fw_search_para + fw_search_len) { + int slen =3D strlen(p); + + if (!slen) + break; + if (pos > 0) + buffer[pos++] =3D ':'; + memcpy(buffer + pos, p, slen); + pos +=3D slen; + p +=3D slen + 1; + } + buffer[pos] =3D '\0'; + return pos; +} + +static const struct kernel_param_ops fw_search_ops =3D { + .set =3D fw_search_set, + .get =3D fw_search_get, +}; +module_param_cb(search, &fw_search_ops, NULL, 0644); +MODULE_PARM_DESC(search, "colon-separated list of firmware search paths, t= ried after path=3D (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 =3D 0; + size_t *file_size_ptr =3D NULL; + size_t size; + int len, rc; + + len =3D snprintf(path, PATH_MAX, "%.*s/%s%s", + dirlen, dir, fw_priv->fw_name, suffix); + if (len >=3D PATH_MAX) + return -ENAMETOOLONG; + + fw_priv->size =3D 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 =3D &file_size; + + /* load firmware files from the mount namespace of init */ + rc =3D 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 !=3D -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 =3D 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 =3D decompress(device, fw_priv, size, *bufp); + /* discard the superfluous original content */ + vfree(*bufp); + *bufp =3D 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 =3D *bufp; + fw_priv->size =3D 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, stru= ct fw_priv *fw_priv, size_t in_size, const void *in_buffer)) { - size_t size; - int i, len, maxlen =3D 0; + int i; int rc =3D -ENOENT; - char *path, *nt =3D NULL; + char *path; size_t msize =3D INT_MAX; void *buffer =3D NULL; =20 @@ -511,83 +664,51 @@ fw_get_filesystem_firmware(struct device *device, str= uct fw_priv *fw_priv, return -ENOMEM; =20 wait_for_initramfs(); - for (i =3D 0; i < ARRAY_SIZE(fw_path); i++) { - size_t file_size =3D 0; - size_t *file_size_ptr =3D NULL; - - /* skip the unset customized path */ - if (!fw_path[i][0]) - continue; - - /* strip off \n from customized path */ - maxlen =3D strlen(fw_path[i]); - if (i =3D=3D 0) { - nt =3D strchr(fw_path[i], '\n'); - if (nt) - maxlen =3D nt - fw_path[i]; - } =20 - len =3D snprintf(path, PATH_MAX, "%.*s/%s%s", - maxlen, fw_path[i], - fw_priv->fw_name, suffix); - if (len >=3D PATH_MAX) { - rc =3D -ENAMETOOLONG; - break; - } + /* Try the customized path first */ + if (fw_path_para[0]) { + int dirlen =3D strlen(fw_path_para); =20 - fw_priv->size =3D 0; + /* strip trailing newline */ + if (fw_path_para[dirlen - 1] =3D=3D '\n') + dirlen--; =20 - /* - * 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 =3D &file_size; - - /* load firmware files from the mount namespace of init */ - rc =3D 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 !=3D -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 =3D rc; - rc =3D 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 =3D decompress(device, fw_priv, size, buffer); - /* discard the superfluous original content */ - vfree(buffer); - buffer =3D 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 =3D buffer; - fw_priv->size =3D size; + rc =3D 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 =3D fw_search_para; + + while (p < fw_search_para + fw_search_len) { + int dirlen =3D strlen(p); + + if (!dirlen) + break; + rc =3D fw_try_firmware_path(device, fw_priv, + suffix, decompress, + p, dirlen, + path, &buffer, msize); + if (!rc) + goto done; + p +=3D dirlen + 1; } - fw_state_done(fw_priv); - break; } + + /* Try default firmware paths */ + for (i =3D 0; i < ARRAY_SIZE(fw_path); i++) { + rc =3D 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); =20 return rc; --=20 2.53.0 From nobody Mon Mar 23 19:50:52 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6C5073B2FD2; Mon, 23 Mar 2026 14:39:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774276788; cv=none; b=NpfnnAACSOLtjf/Pl5HcAdBoqvHXC5SUcEngZO8LhH5OdMzWXEebDPkapYMmPKJgMAq2ZYw5vcg7RDN21RP742ptbYtIBW18QCTbDxR44Q8BgVtIv61Hi4BvSWlMSwty/y4VkIdC+yanvdqdBK0L/B7jWjrA/jexE0gIQxjd98A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774276788; c=relaxed/simple; bh=n5PwD28reFcmNKZbHa+WcDYNkJJFYigMLc9GUuNvQoo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=LDOSzbRgnmA2O7KyF1/vnsINQadHOx7EJqjt1n7WC7gaGhhFPix2sO58DZV7zQkJJMnwKkSdbj9xEKyT1y4LpgSEU4KSTql1g+aLUHpkEgF98ArFeNogz3HvTH3gngWJxlT3VeabLmULFOq5ETkIN44tnEgyK61ESXMjjDKrIH0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=KHdi+yDP; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="KHdi+yDP" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 11551C2BCB3; Mon, 23 Mar 2026 14:39:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774276788; bh=n5PwD28reFcmNKZbHa+WcDYNkJJFYigMLc9GUuNvQoo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=KHdi+yDPZPtjtdi0FXUi5t8TmmndezAl94iseM5S9KbjW+5zaxuX4iLc/yHhkYe3c he4h7LoOCv+2R1spJTagYTzgitExfSTFTrereoeWQudGbHiMW5WhRnYIh4qP5frc5T QP/aUmEZ0dCbbhhZFvhXR6/n1ZxW2/G8NDzrQ/4s6p+cwx5kJGlPXfAkkft350BfJd A8lKftExnUJzUX18h+D6TWjLC6/DHvGApiM9UTbEAcGxP6VBCtOMze6IAWFM13g03W InXocLrRDfHxQkiKTKxy6Y7tK617gAgAifIN9j+9LhPRh/ETSNSOJR7vfaOlU46FIn H16IRNlDnE8MA== From: Jeff Layton Date: Mon, 23 Mar 2026 10:39:31 -0400 Subject: [PATCH v5 2/2] selftests/firmware: add search path test for firmware_class.search= Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260323-fw-path-v5-2-e88b2fe145f3@kernel.org> References: <20260323-fw-path-v5-0-e88b2fe145f3@kernel.org> In-Reply-To: <20260323-fw-path-v5-0-e88b2fe145f3@kernel.org> To: Luis Chamberlain , Russ Weight , Danilo Krummrich , Greg Kroah-Hartman , "Rafael J. Wysocki" , Shuah Khan Cc: Michal Grzedzicki , driver-core@lists.linux.dev, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=8673; i=jlayton@kernel.org; h=from:subject:message-id; bh=n5PwD28reFcmNKZbHa+WcDYNkJJFYigMLc9GUuNvQoo=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBpwVCw0z/5NmzSwuIaro5cjgT/gp3IrKZovUXg2 KuWBdfCRX+JAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCacFQsAAKCRAADmhBGVaC FbzqD/wIzKybdMPw/v1PTnI5rKcO+zEn60Hu5dJW2WpTGvULkVh3UVjPlW5PCDjxLlCRgjSNuWb F4rQ9NsVJIOOJ0HkxY8apCJnrctDhaOG1VVUqbxCc5u/JSYRZ/gBY8QjOQhf13wQAnLLRVCwl2Y gFxZvO6ZDxx3sv0HYaa4skQs+gd0+mRUoX8J8/ADvN11U23D7zszdJVD5O3vOBG5KG1Led7qX3k zgOwHD6eWRnadGp7KmQyVImZnRelLLc5M5bWVAMDvrN30xeq02UYEcM+phiVkc6yhZkqZRNpode KfZAsL6DOzCbAlmt5cyiV7cSqcKM4+3eB7n2LcK6i46C7uv3bL0vpS9pEm1KCnXtxjbvzpao6Se SedM5oNmmQzk2MNaTpNa5+6TeOnHlai68Hh1JFgRHI94Ui/qZWC22hvGwFMBUmOyH7AWS3UgMGZ SqEfjgVEIXD1ergrZVJ2qEWGwzAheVH0ZnYfYo/JnauQD76mMrqEfjYM1NK+/cnExP4B+CBICKM IvhaJ3LqDwFIb8W+RlieMG9GPc1JlmPhuFkJsPMSoZVCbBMbJM+cRQUgHv3frd2S93MeM4oOuVC cCg0CBAO/evHd7q8FQWjUDEGT+b+L1+x6wbhdMFcqVvp4HeYmmwElNiWWYOWOy5CJ6bV+OszhLh pfpBLu7AJ8l1vGQ== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 Add fw_search.sh, a new selftest that validates the firmware_class.search= =3D module parameter using the existing test_firmware module's sysfs trigger interface. The test covers: - Firmware found in first/second/third search directory - Firmware not found in any search path - path=3D takes priority over search=3D - Empty search=3D does not break firmware loading - Sysfs readback matches what was written - Escaped colon (\:) in directory name - Escaped backslash (\\) in directory name - Escaped colon combined with multiple search paths Signed-off-by: Jeff Layton --- tools/testing/selftests/firmware/Makefile | 2 +- tools/testing/selftests/firmware/fw_search.sh | 222 ++++++++++++++++++++++= ++++ 2 files changed, 223 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/firmware/Makefile b/tools/testing/self= tests/firmware/Makefile index 7992969deaa2737ff2a033ffe60136b84ea2f3f0..42e5bd72886b3cd8f62ae42d53c= 1554e8a1b331c 100644 --- a/tools/testing/selftests/firmware/Makefile +++ b/tools/testing/selftests/firmware/Makefile @@ -3,7 +3,7 @@ CFLAGS =3D -Wall \ -O2 =20 -TEST_PROGS :=3D fw_run_tests.sh +TEST_PROGS :=3D fw_run_tests.sh fw_search.sh TEST_FILES :=3D fw_fallback.sh fw_filesystem.sh fw_upload.sh fw_lib.sh TEST_GEN_FILES :=3D fw_namespace =20 diff --git a/tools/testing/selftests/firmware/fw_search.sh b/tools/testing/= selftests/firmware/fw_search.sh new file mode 100755 index 0000000000000000000000000000000000000000..ec6873e54a4cc381771311be112= 03083b2531bf6 --- /dev/null +++ b/tools/testing/selftests/firmware/fw_search.sh @@ -0,0 +1,222 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Test the firmware_class.search=3D module parameter, which allows +# specifying multiple colon-separated firmware search directories. + +set -e + +TEST_REQS_FW_SYSFS_FALLBACK=3D"no" +TEST_REQS_FW_SET_CUSTOM_PATH=3D"no" +TEST_DIR=3D$(dirname $0) +source $TEST_DIR/fw_lib.sh + +# Kselftest framework requirement - SKIP code is 4. +ksft_skip=3D4 + +SEARCH_SYSFS=3D"/sys/module/firmware_class/parameters/search" +PATH_SYSFS=3D"/sys/module/firmware_class/parameters/path" + +check_mods +check_setup +verify_reqs + +if [ ! -f "$SEARCH_SYSFS" ]; then + echo "$0: search=3D parameter not available, skipping" + exit $ksft_skip +fi + +# Save original values +OLD_SEARCH=3D"$(cat $SEARCH_SYSFS)" +OLD_PATH=3D"$(cat $PATH_SYSFS)" + +# Create temp directories for firmware +FWDIR1=3D$(mktemp -d) +FWDIR2=3D$(mktemp -d) +FWDIR3=3D$(mktemp -d) + +FW_NAME=3D"test-search-fw.bin" +FW_CONTENT1=3D"SEARCH_PATH_1" +FW_CONTENT2=3D"SEARCH_PATH_2" +FW_CONTENT3=3D"SEARCH_PATH_3" + +DIR=3D/sys/devices/virtual/misc/test_firmware + +cleanup() +{ + # Restore original values + if [ "$OLD_PATH" =3D "" ]; then + printf '\000' >$PATH_SYSFS + else + echo -n "$OLD_PATH" >$PATH_SYSFS + fi + if [ "$OLD_SEARCH" =3D "" ]; then + printf '\000' >$SEARCH_SYSFS + else + echo -n "$OLD_SEARCH" >$SEARCH_SYSFS + fi + rm -rf "$FWDIR1" "$FWDIR2" "$FWDIR3" +} +trap cleanup EXIT + +# Clear path=3D so search=3D is consulted +printf '\000' >$PATH_SYSFS + +# Test 1: firmware found in first search path +echo -n "$FW_CONTENT1" >"$FWDIR1/$FW_NAME" +echo -n "$FWDIR1:$FWDIR2" >$SEARCH_SYSFS + +if ! echo -n "$FW_NAME" >$DIR/trigger_request; then + echo "$0: FAIL - could not trigger request for test 1" >&2 + exit 1 +fi +if ! diff -q "$FWDIR1/$FW_NAME" /dev/test_firmware >/dev/null; then + echo "$0: FAIL - firmware content mismatch in test 1" >&2 + exit 1 +fi +echo "$0: search path - first directory: OK" + +# Test 2: firmware found in second search path (not in first) +rm -f "$FWDIR1/$FW_NAME" +echo -n "$FW_CONTENT2" >"$FWDIR2/$FW_NAME" +echo -n "$FWDIR1:$FWDIR2" >$SEARCH_SYSFS + +if ! echo -n "$FW_NAME" >$DIR/trigger_request; then + echo "$0: FAIL - could not trigger request for test 2" >&2 + exit 1 +fi +if ! diff -q "$FWDIR2/$FW_NAME" /dev/test_firmware >/dev/null; then + echo "$0: FAIL - firmware content mismatch in test 2" >&2 + exit 1 +fi +echo "$0: search path - second directory: OK" + +# Test 3: firmware not found in any search path +rm -f "$FWDIR2/$FW_NAME" +echo -n "$FWDIR1:$FWDIR2" >$SEARCH_SYSFS + +if echo -n "nonexistent-$FW_NAME" >$DIR/trigger_request 2>/dev/null; then + echo "$0: FAIL - firmware should not have been found in test 3" >&2 + exit 1 +fi +echo "$0: search path - not found: OK" + +# Test 4: path=3D takes priority over search=3D +echo -n "$FW_CONTENT1" >"$FWDIR1/$FW_NAME" +echo -n "$FW_CONTENT2" >"$FWDIR2/$FW_NAME" +echo -n "$FWDIR1" >$PATH_SYSFS +echo -n "$FWDIR2" >$SEARCH_SYSFS + +if ! echo -n "$FW_NAME" >$DIR/trigger_request; then + echo "$0: FAIL - could not trigger request for test 4" >&2 + exit 1 +fi +if ! diff -q "$FWDIR1/$FW_NAME" /dev/test_firmware >/dev/null; then + echo "$0: FAIL - path=3D should take priority over search=3D in test 4" >= &2 + exit 1 +fi +echo "$0: search path - path=3D priority over search=3D: OK" + +# Clear path=3D again for remaining tests +printf '\000' >$PATH_SYSFS + +# Test 5: three search paths, firmware in third +rm -f "$FWDIR1/$FW_NAME" "$FWDIR2/$FW_NAME" +echo -n "$FW_CONTENT3" >"$FWDIR3/$FW_NAME" +echo -n "$FWDIR1:$FWDIR2:$FWDIR3" >$SEARCH_SYSFS + +if ! echo -n "$FW_NAME" >$DIR/trigger_request; then + echo "$0: FAIL - could not trigger request for test 5" >&2 + exit 1 +fi +if ! diff -q "$FWDIR3/$FW_NAME" /dev/test_firmware >/dev/null; then + echo "$0: FAIL - firmware content mismatch in test 5" >&2 + exit 1 +fi +echo "$0: search path - third directory: OK" + +# Test 6: empty search=3D should not break anything +rm -f "$FWDIR3/$FW_NAME" +printf '\000' >$SEARCH_SYSFS + +if echo -n "nonexistent-$FW_NAME" >$DIR/trigger_request 2>/dev/null; then + echo "$0: FAIL - empty search=3D should not find firmware" >&2 + exit 1 +fi +echo "$0: search path - empty search=3D: OK" + +# Test 7: verify sysfs readback matches what was written +echo -n "$FWDIR1:$FWDIR2:$FWDIR3" >$SEARCH_SYSFS +READBACK=3D"$(cat $SEARCH_SYSFS)" +EXPECTED=3D"$FWDIR1:$FWDIR2:$FWDIR3" +if [ "$READBACK" !=3D "$EXPECTED" ]; then + echo "$0: FAIL - sysfs readback mismatch: '$READBACK' !=3D '$EXPECTED'" >= &2 + exit 1 +fi +echo "$0: search path - sysfs readback: OK" + +# Test 8: escaped colon in directory name (\: -> literal ':') +FWDIR_COLON=3D$(mktemp -d)/fw:dir +mkdir -p "$FWDIR_COLON" +echo -n "$FW_CONTENT1" >"$FWDIR_COLON/$FW_NAME" +# Write the path with the colon escaped as \: +ESCAPED_COLON=3D"${FWDIR_COLON//:/\\:}" +echo -n "$ESCAPED_COLON" >$SEARCH_SYSFS + +if ! echo -n "$FW_NAME" >$DIR/trigger_request; then + echo "$0: FAIL - could not trigger request for test 8" >&2 + rm -rf "$(dirname "$FWDIR_COLON")" + exit 1 +fi +if ! diff -q "$FWDIR_COLON/$FW_NAME" /dev/test_firmware >/dev/null; then + echo "$0: FAIL - firmware content mismatch in test 8" >&2 + rm -rf "$(dirname "$FWDIR_COLON")" + exit 1 +fi +rm -rf "$(dirname "$FWDIR_COLON")" +echo "$0: search path - escaped colon in directory: OK" + +# Test 9: escaped backslash in directory name (\\ -> literal '\') +FWDIR_BS=3D$(mktemp -d)/fw\\dir +mkdir -p "$FWDIR_BS" +echo -n "$FW_CONTENT2" >"$FWDIR_BS/$FW_NAME" +# Write the path with backslashes escaped as \\ +ESCAPED_BS=3D"${FWDIR_BS//\\/\\\\}" +echo -n "$ESCAPED_BS" >$SEARCH_SYSFS + +if ! echo -n "$FW_NAME" >$DIR/trigger_request; then + echo "$0: FAIL - could not trigger request for test 9" >&2 + rm -rf "$(dirname "$FWDIR_BS")" + exit 1 +fi +if ! diff -q "$FWDIR_BS/$FW_NAME" /dev/test_firmware >/dev/null; then + echo "$0: FAIL - firmware content mismatch in test 9" >&2 + rm -rf "$(dirname "$FWDIR_BS")" + exit 1 +fi +rm -rf "$(dirname "$FWDIR_BS")" +echo "$0: search path - escaped backslash in directory: OK" + +# Test 10: escaped colon with multiple search paths +FWDIR_COLON2=3D$(mktemp -d)/has:colon +mkdir -p "$FWDIR_COLON2" +echo -n "$FW_CONTENT3" >"$FWDIR_COLON2/$FW_NAME" +ESCAPED_COLON2=3D"${FWDIR_COLON2//:/\\:}" +# First path is normal (no firmware), second has escaped colon (has firmwa= re) +echo -n "$FWDIR1:$ESCAPED_COLON2" >$SEARCH_SYSFS + +if ! echo -n "$FW_NAME" >$DIR/trigger_request; then + echo "$0: FAIL - could not trigger request for test 10" >&2 + rm -rf "$(dirname "$FWDIR_COLON2")" + exit 1 +fi +if ! diff -q "$FWDIR_COLON2/$FW_NAME" /dev/test_firmware >/dev/null; then + echo "$0: FAIL - firmware content mismatch in test 10" >&2 + rm -rf "$(dirname "$FWDIR_COLON2")" + exit 1 +fi +rm -rf "$(dirname "$FWDIR_COLON2")" +echo "$0: search path - escaped colon with multiple paths: OK" + +echo "$0: all search path tests passed" +exit 0 --=20 2.53.0