xref: /netbsd-src/sys/dev/firmload.c (revision c0216ccf58cb9901d88c923a8ed2aa16477b70d2)
1 /*	$NetBSD: firmload.c,v 1.24 2022/10/26 23:39:10 riastradh Exp $	*/
2 
3 /*-
4  * Copyright (c) 2005, 2006 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason R. Thorpe.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: firmload.c,v 1.24 2022/10/26 23:39:10 riastradh Exp $");
34 
35 /*
36  * The firmload API provides an interface for device drivers to access
37  * firmware images that must be loaded onto their devices.
38  */
39 
40 #include <sys/param.h>
41 #include <sys/fcntl.h>
42 #include <sys/filedesc.h>
43 #include <sys/kmem.h>
44 #include <sys/namei.h>
45 #include <sys/systm.h>
46 #include <sys/sysctl.h>
47 #include <sys/vnode.h>
48 #include <sys/kauth.h>
49 #include <sys/lwp.h>
50 
51 #include <dev/firmload.h>
52 
53 struct firmware_handle {
54 	struct vnode	*fh_vp;
55 	off_t		 fh_size;
56 };
57 
58 static firmware_handle_t
firmware_handle_alloc(void)59 firmware_handle_alloc(void)
60 {
61 
62 	return (kmem_alloc(sizeof(struct firmware_handle), KM_SLEEP));
63 }
64 
65 static void
firmware_handle_free(firmware_handle_t fh)66 firmware_handle_free(firmware_handle_t fh)
67 {
68 
69 	kmem_free(fh, sizeof(*fh));
70 }
71 
72 #if !defined(FIRMWARE_PATHS)
73 #define	FIRMWARE_PATHS		\
74 	"/libdata/firmware:/usr/libdata/firmware:/usr/pkg/libdata/firmware:/usr/pkg/libdata"
75 #endif
76 
77 static char firmware_paths[PATH_MAX+1] = FIRMWARE_PATHS;
78 
79 static int
sysctl_hw_firmware_path(SYSCTLFN_ARGS)80 sysctl_hw_firmware_path(SYSCTLFN_ARGS)
81 {
82 	int error, i;
83 	char newpath[PATH_MAX+1];
84 	struct sysctlnode node;
85 	char expected_char;
86 
87 	node = *rnode;
88 	node.sysctl_data = &newpath[0];
89 	memcpy(node.sysctl_data, rnode->sysctl_data, PATH_MAX+1);
90 	error = sysctl_lookup(SYSCTLFN_CALL(&node));
91 	if (error || newp == NULL)
92 		return (error);
93 
94 	/*
95 	 * Make sure that all of the paths in the new path list are
96 	 * absolute.
97 	 *
98 	 * When sysctl_lookup() deals with a string, it's guaranteed
99 	 * to come back nul-terminated.
100 	 */
101 	expected_char = '/';
102 	for (i = 0; i < PATH_MAX+1; i++) {
103 		if (expected_char != 0 && newpath[i] != expected_char)
104 		    	return (EINVAL);
105 		if (newpath[i] == '\0')
106 			break;
107 		else if (newpath[i] == ':')
108 			expected_char = '/';
109 		else
110 			expected_char = 0;
111 	}
112 
113 	memcpy(rnode->sysctl_data, node.sysctl_data, PATH_MAX+1);
114 
115 	return (0);
116 }
117 
118 SYSCTL_SETUP(sysctl_hw_firmware_setup, "sysctl hw.firmware subtree setup")
119 {
120 	const struct sysctlnode *firmware_node;
121 
122 	if (sysctl_createv(clog, 0, NULL, &firmware_node,
123 	    CTLFLAG_PERMANENT,
124 	    CTLTYPE_NODE, "firmware", NULL,
125 	    NULL, 0, NULL, 0,
126 	    CTL_HW, CTL_CREATE, CTL_EOL) != 0)
127 	    	return;
128 
129 	sysctl_createv(clog, 0, NULL, NULL,
130 	    CTLFLAG_READWRITE,
131 	    CTLTYPE_STRING, "path",
132 	    SYSCTL_DESCR("Device firmware loading path list"),
133 	    sysctl_hw_firmware_path, 0, firmware_paths, PATH_MAX+1,
134 	    CTL_HW, firmware_node->sysctl_num, CTL_CREATE, CTL_EOL);
135 }
136 
137 static char *
firmware_path_next(const char * drvname,const char * imgname,char * pnbuf,char ** prefixp)138 firmware_path_next(const char *drvname, const char *imgname, char *pnbuf,
139     char **prefixp)
140 {
141 	char *prefix = *prefixp;
142 	size_t maxprefix, i;
143 
144 	if (prefix == NULL		/* terminated early */
145 	    || *prefix != '/') {	/* empty or not absolute */
146 		*prefixp = NULL;
147 	    	return (NULL);
148 	}
149 
150 	/*
151 	 * Compute the max path prefix based on the length of the provided
152 	 * names.
153 	 */
154 	maxprefix = MAXPATHLEN -
155 		(1 /* / */
156 		 + strlen(drvname)
157 		 + 1 /* / */
158 		 + strlen(imgname)
159 		 + 1 /* terminating NUL */);
160 
161 	/* Check for underflow (size_t is unsigned). */
162 	if (maxprefix > MAXPATHLEN) {
163 		*prefixp = NULL;
164 		return (NULL);
165 	}
166 
167 	for (i = 0; i < maxprefix; i++) {
168 		if (*prefix == ':' || *prefix == '\0')
169 			break;
170 		pnbuf[i] = *prefix++;
171 	}
172 
173 	if (*prefix != ':' && *prefix != '\0') {
174 		/* Path prefix was too long. */
175 		*prefixp = NULL;
176 		return (NULL);
177 	}
178 
179 	if (*prefix != '\0')
180 		prefix++;
181 	*prefixp = prefix;
182 
183 	KASSERT(MAXPATHLEN >= i);
184 	snprintf(pnbuf + i, MAXPATHLEN - i, "/%s/%s", drvname, imgname);
185 
186 	return (pnbuf);
187 }
188 
189 static char *
firmware_path_first(const char * drvname,const char * imgname,char * pnbuf,char ** prefixp)190 firmware_path_first(const char *drvname, const char *imgname, char *pnbuf,
191     char **prefixp)
192 {
193 
194 	*prefixp = firmware_paths;
195 	return (firmware_path_next(drvname, imgname, pnbuf, prefixp));
196 }
197 
198 /*
199  * firmware_open:
200  *
201  *	Open a firmware image and return its handle.
202  */
203 int
firmware_open(const char * drvname,const char * imgname,firmware_handle_t * fhp)204 firmware_open(const char *drvname, const char *imgname, firmware_handle_t *fhp)
205 {
206 	struct pathbuf *pb;
207 	struct vattr va;
208 	char *pnbuf, *path, *prefix;
209 	firmware_handle_t fh;
210 	struct vnode *vp;
211 	int error;
212 
213 	if (drvname == NULL || imgname == NULL)
214 		return (EINVAL);
215 
216 	if (cwdi0.cwdi_cdir == NULL) {
217 		printf("firmware_open(%s/%s) called too early.\n",
218 			drvname, imgname);
219 		return ENOENT;
220 	}
221 
222 	pnbuf = PNBUF_GET();
223 	KASSERT(pnbuf != NULL);
224 
225 	fh = firmware_handle_alloc();
226 	KASSERT(fh != NULL);
227 
228 	error = 0;
229 	for (path = firmware_path_first(drvname, imgname, pnbuf, &prefix);
230 	     path != NULL;
231 	     path = firmware_path_next(drvname, imgname, pnbuf, &prefix)) {
232 		pb = pathbuf_create(path);
233 		if (pb == NULL) {
234 			error = ENOMEM;
235 			break;
236 		}
237 		error = vn_open(NULL, pb, NOCHROOT, FREAD, 0, &vp, NULL, NULL);
238 		pathbuf_destroy(pb);
239 		if (error == ENOENT) {
240 			continue;
241 		}
242 		break;
243 	}
244 
245 	PNBUF_PUT(pnbuf);
246 	if (error) {
247 		firmware_handle_free(fh);
248 		return (error);
249 	}
250 
251 	error = VOP_GETATTR(vp, &va, kauth_cred_get());
252 	if (error) {
253 		VOP_UNLOCK(vp);
254 		(void)vn_close(vp, FREAD, kauth_cred_get());
255 		firmware_handle_free(fh);
256 		return (error);
257 	}
258 
259 	if (va.va_type != VREG) {
260 		VOP_UNLOCK(vp);
261 		(void)vn_close(vp, FREAD, kauth_cred_get());
262 		firmware_handle_free(fh);
263 		return (EINVAL);
264 	}
265 
266 	/* XXX Mark as busy text file. */
267 
268 	fh->fh_vp = vp;
269 	fh->fh_size = va.va_size;
270 
271 	VOP_UNLOCK(vp);
272 
273 	*fhp = fh;
274 	return (0);
275 }
276 
277 /*
278  * firmware_close:
279  *
280  *	Close a firmware image.
281  */
282 int
firmware_close(firmware_handle_t fh)283 firmware_close(firmware_handle_t fh)
284 {
285 	int error;
286 
287 	error = vn_close(fh->fh_vp, FREAD, kauth_cred_get());
288 	firmware_handle_free(fh);
289 	return (error);
290 }
291 
292 /*
293  * firmware_get_size:
294  *
295  *	Return the total size of a firmware image.
296  */
297 off_t
firmware_get_size(firmware_handle_t fh)298 firmware_get_size(firmware_handle_t fh)
299 {
300 
301 	return (fh->fh_size);
302 }
303 
304 /*
305  * firmware_read:
306  *
307  *	Read data from a firmware image at the specified offset into
308  *	the provided buffer.
309  */
310 int
firmware_read(firmware_handle_t fh,off_t offset,void * buf,size_t len)311 firmware_read(firmware_handle_t fh, off_t offset, void *buf, size_t len)
312 {
313 
314 	return (vn_rdwr(UIO_READ, fh->fh_vp, buf, len, offset,
315 			UIO_SYSSPACE, 0, kauth_cred_get(), NULL, curlwp));
316 }
317 
318 /*
319  * firmware_malloc:
320  *
321  *	Allocate a firmware buffer of the specified size.
322  *
323  *	NOTE: This routine may block.
324  */
325 void *
firmware_malloc(size_t size)326 firmware_malloc(size_t size)
327 {
328 
329 	return (kmem_alloc(size, KM_SLEEP));
330 }
331 
332 /*
333  * firmware_free:
334  *
335  *	Free a previously allocated firmware buffer.
336  */
337 /*ARGSUSED*/
338 void
firmware_free(void * v,size_t size)339 firmware_free(void *v, size_t size)
340 {
341 
342 	kmem_free(v, size);
343 }
344