1a6c2507dSBjoern A. Zeeb /*-
2a6c2507dSBjoern A. Zeeb  * SPDX-License-Identifier: BSD-2-Clause
3a6c2507dSBjoern A. Zeeb  *
4a6c2507dSBjoern A. Zeeb  * Copyright (c) 2020-2021 The FreeBSD Foundation
5a6c2507dSBjoern A. Zeeb  *
6a6c2507dSBjoern A. Zeeb  * This software was developed by Björn Zeeb under sponsorship from
7a6c2507dSBjoern A. Zeeb  * the FreeBSD Foundation.
8a6c2507dSBjoern A. Zeeb  *
9a6c2507dSBjoern A. Zeeb  * Redistribution and use in source and binary forms, with or without
10a6c2507dSBjoern A. Zeeb  * modification, are permitted provided that the following conditions
11a6c2507dSBjoern A. Zeeb  * are met:
12a6c2507dSBjoern A. Zeeb  * 1. Redistributions of source code must retain the above copyright
13a6c2507dSBjoern A. Zeeb  *    notice, this list of conditions and the following disclaimer.
14a6c2507dSBjoern A. Zeeb  * 2. Redistributions in binary form must reproduce the above copyright
15a6c2507dSBjoern A. Zeeb  *    notice, this list of conditions and the following disclaimer in the
16a6c2507dSBjoern A. Zeeb  *    documentation and/or other materials provided with the distribution.
17a6c2507dSBjoern A. Zeeb  *
18a6c2507dSBjoern A. Zeeb  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19a6c2507dSBjoern A. Zeeb  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20a6c2507dSBjoern A. Zeeb  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21a6c2507dSBjoern A. Zeeb  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22a6c2507dSBjoern A. Zeeb  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23a6c2507dSBjoern A. Zeeb  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24a6c2507dSBjoern A. Zeeb  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25a6c2507dSBjoern A. Zeeb  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26a6c2507dSBjoern A. Zeeb  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27a6c2507dSBjoern A. Zeeb  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28a6c2507dSBjoern A. Zeeb  * SUCH DAMAGE.
29a6c2507dSBjoern A. Zeeb  *
30a6c2507dSBjoern A. Zeeb  * $FreeBSD$
31a6c2507dSBjoern A. Zeeb  */
32a6c2507dSBjoern A. Zeeb 
33a6c2507dSBjoern A. Zeeb #include <sys/types.h>
34a6c2507dSBjoern A. Zeeb #include <sys/malloc.h>
35a6c2507dSBjoern A. Zeeb #include <sys/firmware.h>
36a6c2507dSBjoern A. Zeeb 
37a6c2507dSBjoern A. Zeeb #include <linux/types.h>
38a6c2507dSBjoern A. Zeeb #include <linux/device.h>
39a6c2507dSBjoern A. Zeeb 
40a6c2507dSBjoern A. Zeeb #include <linux/firmware.h>
41a6c2507dSBjoern A. Zeeb #undef firmware
42a6c2507dSBjoern A. Zeeb 
43a6c2507dSBjoern A. Zeeb MALLOC_DEFINE(M_LKPI_FW, "lkpifw", "LinuxKPI firmware");
44a6c2507dSBjoern A. Zeeb 
45a6c2507dSBjoern A. Zeeb static int
46a6c2507dSBjoern A. Zeeb _linuxkpi_request_firmware(const char *fw_name, const struct linuxkpi_firmware **fw,
47a6c2507dSBjoern A. Zeeb     struct device *dev, gfp_t gfp __unused, bool enoentok, bool warn)
48a6c2507dSBjoern A. Zeeb {
49a6c2507dSBjoern A. Zeeb 	const struct firmware *fbdfw;
50a6c2507dSBjoern A. Zeeb 	struct linuxkpi_firmware *lfw;
51a6c2507dSBjoern A. Zeeb 	const char *fwimg;
52a6c2507dSBjoern A. Zeeb 	char *p;
53a6c2507dSBjoern A. Zeeb 	uint32_t flags;
54a6c2507dSBjoern A. Zeeb 
55a6c2507dSBjoern A. Zeeb 	if (fw_name == NULL || fw == NULL || dev == NULL)
56a6c2507dSBjoern A. Zeeb 		return (-EINVAL);
57a6c2507dSBjoern A. Zeeb 
58a6c2507dSBjoern A. Zeeb 	/* Set independent on "warn". To debug, bootverbose is avail. */
59a6c2507dSBjoern A. Zeeb 	flags = FIRMWARE_GET_NOWARN;
60a6c2507dSBjoern A. Zeeb 
61a6c2507dSBjoern A. Zeeb 	KASSERT(gfp == GFP_KERNEL, ("%s: gfp %#x\n", __func__, gfp));
62a6c2507dSBjoern A. Zeeb 	lfw = malloc(sizeof(*lfw), M_LKPI_FW, M_WAITOK | M_ZERO);
63a6c2507dSBjoern A. Zeeb 
64a6c2507dSBjoern A. Zeeb 	/*
65a6c2507dSBjoern A. Zeeb 	 * Linux can have a path in the firmware which is hard to replicate
66a6c2507dSBjoern A. Zeeb 	 * for auto-firmware-module-loading.
67a6c2507dSBjoern A. Zeeb 	 * On FreeBSD, depending on what people do, the firmware will either
68a6c2507dSBjoern A. Zeeb 	 * be called "fw", or "dir_fw", or "modname_dir_fw".  The latter the
69a6c2507dSBjoern A. Zeeb 	 * driver author has to deal with herself (requesting the special name).
70a6c2507dSBjoern A. Zeeb 	 * We also optionally flatten '/'s and '.'s as some firmware modules do.
71a6c2507dSBjoern A. Zeeb 	 * We probe in the least-of-work order avoiding memory operations.
72a6c2507dSBjoern A. Zeeb 	 * It will be preferred to build the firmware .ko in a well matching
73a6c2507dSBjoern A. Zeeb 	 * way rather than adding more name-mangling-hacks here in the future
74a6c2507dSBjoern A. Zeeb 	 * (though we could if needed).
75a6c2507dSBjoern A. Zeeb 	 */
76a6c2507dSBjoern A. Zeeb 	/* (1) Try any name removed of path. */
77a6c2507dSBjoern A. Zeeb 	fwimg = strrchr(fw_name, '/');
78a6c2507dSBjoern A. Zeeb 	if (fwimg != NULL)
79a6c2507dSBjoern A. Zeeb 		fwimg++;
80a6c2507dSBjoern A. Zeeb 	if (fwimg == NULL || *fwimg == '\0')
81a6c2507dSBjoern A. Zeeb 		fwimg = fw_name;
82a6c2507dSBjoern A. Zeeb 	fbdfw = firmware_get_flags(fwimg, flags);
83a6c2507dSBjoern A. Zeeb 	/* (2) Try the original name if we have not yet. */
84a6c2507dSBjoern A. Zeeb 	if (fbdfw == NULL && fwimg != fw_name) {
85a6c2507dSBjoern A. Zeeb 		fwimg = fw_name;
86a6c2507dSBjoern A. Zeeb 		fbdfw = firmware_get_flags(fwimg, flags);
87a6c2507dSBjoern A. Zeeb 	}
88a6c2507dSBjoern A. Zeeb 	/* (3) Flatten '/' and then '.' to '_' and try with adjusted name. */
89a6c2507dSBjoern A. Zeeb 	if (fbdfw == NULL &&
90a6c2507dSBjoern A. Zeeb 	    (strchr(fw_name, '/') != NULL || strchr(fw_name, '.') != NULL)) {
91a6c2507dSBjoern A. Zeeb 		fwimg = strdup(fw_name, M_LKPI_FW);
92a6c2507dSBjoern A. Zeeb 		if (fwimg != NULL) {
93a6c2507dSBjoern A. Zeeb 			while ((p = strchr(fwimg, '/')) != NULL)
94a6c2507dSBjoern A. Zeeb 				*p = '_';
95a6c2507dSBjoern A. Zeeb 			fbdfw = firmware_get_flags(fwimg, flags);
96a6c2507dSBjoern A. Zeeb 			if (fbdfw == NULL) {
97a6c2507dSBjoern A. Zeeb 				while ((p = strchr(fwimg, '.')) != NULL)
98a6c2507dSBjoern A. Zeeb 					*p = '_';
99a6c2507dSBjoern A. Zeeb 				fbdfw = firmware_get_flags(fwimg, flags);
100a6c2507dSBjoern A. Zeeb 			}
101a6c2507dSBjoern A. Zeeb 			free(__DECONST(void *, fwimg), M_LKPI_FW);
102a6c2507dSBjoern A. Zeeb 		}
103a6c2507dSBjoern A. Zeeb 	}
104a6c2507dSBjoern A. Zeeb 	if (fbdfw == NULL) {
105a6c2507dSBjoern A. Zeeb 		if (enoentok)
106a6c2507dSBjoern A. Zeeb 			*fw = lfw;
107a6c2507dSBjoern A. Zeeb 		else {
108a6c2507dSBjoern A. Zeeb 			free(lfw, M_LKPI_FW);
109a6c2507dSBjoern A. Zeeb 			*fw = NULL;
110a6c2507dSBjoern A. Zeeb 		}
111a6c2507dSBjoern A. Zeeb 		if (warn)
112a6c2507dSBjoern A. Zeeb 			device_printf(dev->bsddev, "could not load firmware "
113a6c2507dSBjoern A. Zeeb 			    "image '%s'\n", fw_name);
114a6c2507dSBjoern A. Zeeb 		return (-ENOENT);
115a6c2507dSBjoern A. Zeeb 	}
116a6c2507dSBjoern A. Zeeb 
117a6c2507dSBjoern A. Zeeb 	device_printf(dev->bsddev,"successfully loaded firmware image '%s'\n",
118a6c2507dSBjoern A. Zeeb 	    fw_name);
119a6c2507dSBjoern A. Zeeb 	lfw->fbdfw = fbdfw;
120a6c2507dSBjoern A. Zeeb 	lfw->data = (const uint8_t *)fbdfw->data;
121a6c2507dSBjoern A. Zeeb 	lfw->size = fbdfw->datasize;
122a6c2507dSBjoern A. Zeeb 	*fw = lfw;
123a6c2507dSBjoern A. Zeeb 	return (0);
124a6c2507dSBjoern A. Zeeb }
125a6c2507dSBjoern A. Zeeb 
126a6c2507dSBjoern A. Zeeb int
127a6c2507dSBjoern A. Zeeb linuxkpi_request_firmware_nowait(struct module *mod __unused, bool _t __unused,
128a6c2507dSBjoern A. Zeeb     const char *fw_name, struct device *dev, gfp_t gfp, void *drv,
129a6c2507dSBjoern A. Zeeb     void(*cont)(const struct linuxkpi_firmware *, void *))
130a6c2507dSBjoern A. Zeeb {
131a6c2507dSBjoern A. Zeeb 	const struct linuxkpi_firmware *lfw;
132a6c2507dSBjoern A. Zeeb 	int error;
133a6c2507dSBjoern A. Zeeb 
134a6c2507dSBjoern A. Zeeb 	/*
135a6c2507dSBjoern A. Zeeb 	 * Linux seems to run the callback if it cannot find the firmware.
136a6c2507dSBjoern A. Zeeb 	 * The fact that this is "_nowait()" and has a callback seems to
137a6c2507dSBjoern A. Zeeb 	 * imply that this is run in a deferred conext which we currently
138a6c2507dSBjoern A. Zeeb 	 * do not do.  Should it become necessary (a driver actually requiring
139a6c2507dSBjoern A. Zeeb 	 * it) we would need to implement it here.
140a6c2507dSBjoern A. Zeeb 	 */
141a6c2507dSBjoern A. Zeeb 	error = _linuxkpi_request_firmware(fw_name, &lfw, dev, gfp, true, true);
142a6c2507dSBjoern A. Zeeb 	if (error == -ENOENT)
143a6c2507dSBjoern A. Zeeb 		error = 0;
144a6c2507dSBjoern A. Zeeb 	if (error == 0)
145a6c2507dSBjoern A. Zeeb 		cont(lfw, drv);
146a6c2507dSBjoern A. Zeeb 
147a6c2507dSBjoern A. Zeeb 	return (error);
148a6c2507dSBjoern A. Zeeb }
149a6c2507dSBjoern A. Zeeb 
150a6c2507dSBjoern A. Zeeb int
151a6c2507dSBjoern A. Zeeb linuxkpi_request_firmware(const struct linuxkpi_firmware **fw,
152a6c2507dSBjoern A. Zeeb     const char *fw_name, struct device *dev)
153a6c2507dSBjoern A. Zeeb {
154a6c2507dSBjoern A. Zeeb 
155a6c2507dSBjoern A. Zeeb 	return (_linuxkpi_request_firmware(fw_name, fw, dev, GFP_KERNEL, false,
156a6c2507dSBjoern A. Zeeb 	    true));
157a6c2507dSBjoern A. Zeeb }
158a6c2507dSBjoern A. Zeeb 
159a6c2507dSBjoern A. Zeeb int
160a6c2507dSBjoern A. Zeeb linuxkpi_firmware_request_nowarn(const struct linuxkpi_firmware **fw,
161a6c2507dSBjoern A. Zeeb     const char *fw_name, struct device *dev)
162a6c2507dSBjoern A. Zeeb {
163a6c2507dSBjoern A. Zeeb 
164a6c2507dSBjoern A. Zeeb 	return (_linuxkpi_request_firmware(fw_name, fw, dev, GFP_KERNEL, false,
165a6c2507dSBjoern A. Zeeb 	    false));
166a6c2507dSBjoern A. Zeeb }
167a6c2507dSBjoern A. Zeeb 
168a6c2507dSBjoern A. Zeeb void
169a6c2507dSBjoern A. Zeeb linuxkpi_release_firmware(const struct linuxkpi_firmware *fw)
170a6c2507dSBjoern A. Zeeb {
171a6c2507dSBjoern A. Zeeb 
172a6c2507dSBjoern A. Zeeb 	if (fw == NULL)
173a6c2507dSBjoern A. Zeeb 		return;
174a6c2507dSBjoern A. Zeeb 
175a6c2507dSBjoern A. Zeeb 	if (fw->fbdfw)
176a6c2507dSBjoern A. Zeeb 		firmware_put(fw->fbdfw, FIRMWARE_UNLOAD);
177a6c2507dSBjoern A. Zeeb 	free(__DECONST(void *, fw), M_LKPI_FW);
178a6c2507dSBjoern A. Zeeb }
179