xref: /netbsd-src/sys/dev/firmload.c (revision 63aea4bd5b445e491ff0389fe27ec78b3099dba3)
1 /*	$NetBSD: firmload.c,v 1.21 2015/01/04 19:25:32 pooka 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.21 2015/01/04 19:25:32 pooka 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
59 firmware_handle_alloc(void)
60 {
61 
62 	return (kmem_alloc(sizeof(struct firmware_handle), KM_SLEEP));
63 }
64 
65 static void
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
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 *
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 == '\0'		/* no more left */
146 	    || *prefix != '/') {	/* not absolute */
147 		*prefixp = NULL;
148 	    	return (NULL);
149 	}
150 
151 	/*
152 	 * Compute the max path prefix based on the length of the provided
153 	 * names.
154 	 */
155 	maxprefix = MAXPATHLEN -
156 		(1 /* / */
157 		 + strlen(drvname)
158 		 + 1 /* / */
159 		 + strlen(imgname)
160 		 + 1 /* terminating NUL */);
161 
162 	/* Check for underflow (size_t is unsigned). */
163 	if (maxprefix > MAXPATHLEN) {
164 		*prefixp = NULL;
165 		return (NULL);
166 	}
167 
168 	for (i = 0; i < maxprefix; i++) {
169 		if (*prefix == ':' || *prefix == '\0')
170 			break;
171 		pnbuf[i] = *prefix++;
172 	}
173 
174 	if (*prefix != ':' && *prefix != '\0') {
175 		/* Path prefix was too long. */
176 		*prefixp = NULL;
177 		return (NULL);
178 	}
179 
180 	if (*prefix != '\0')
181 		prefix++;
182 	*prefixp = prefix;
183 
184 	KASSERT(MAXPATHLEN >= i);
185 	snprintf(pnbuf + i, MAXPATHLEN - i, "/%s/%s", drvname, imgname);
186 
187 	return (pnbuf);
188 }
189 
190 static char *
191 firmware_path_first(const char *drvname, const char *imgname, char *pnbuf,
192     char **prefixp)
193 {
194 
195 	*prefixp = firmware_paths;
196 	return (firmware_path_next(drvname, imgname, pnbuf, prefixp));
197 }
198 
199 /*
200  * firmware_open:
201  *
202  *	Open a firmware image and return its handle.
203  */
204 int
205 firmware_open(const char *drvname, const char *imgname, firmware_handle_t *fhp)
206 {
207 	struct pathbuf *pb;
208 	struct nameidata nd;
209 	struct vattr va;
210 	char *pnbuf, *path, *prefix;
211 	firmware_handle_t fh;
212 	struct vnode *vp;
213 	int error;
214 	extern struct cwdinfo cwdi0;
215 
216 	if (drvname == NULL || imgname == NULL)
217 		return (EINVAL);
218 
219 	if (cwdi0.cwdi_cdir == NULL) {
220 		printf("firmware_open(%s/%s) called too early.\n",
221 			drvname, imgname);
222 		return ENOENT;
223 	}
224 
225 	pnbuf = PNBUF_GET();
226 	KASSERT(pnbuf != NULL);
227 
228 	fh = firmware_handle_alloc();
229 	KASSERT(fh != NULL);
230 
231 	error = 0;
232 	for (path = firmware_path_first(drvname, imgname, pnbuf, &prefix);
233 	     path != NULL;
234 	     path = firmware_path_next(drvname, imgname, pnbuf, &prefix)) {
235 		pb = pathbuf_create(path);
236 		if (pb == NULL) {
237 			error = ENOMEM;
238 			break;
239 		}
240 		NDINIT(&nd, LOOKUP, FOLLOW | NOCHROOT, pb);
241 		error = vn_open(&nd, FREAD, 0);
242 		pathbuf_destroy(pb);
243 		if (error == ENOENT) {
244 			continue;
245 		}
246 		break;
247 	}
248 
249 	PNBUF_PUT(pnbuf);
250 	if (error) {
251 		firmware_handle_free(fh);
252 		return (error);
253 	}
254 
255 	vp = nd.ni_vp;
256 
257 	error = VOP_GETATTR(vp, &va, kauth_cred_get());
258 	if (error) {
259 		VOP_UNLOCK(vp);
260 		(void)vn_close(vp, FREAD, kauth_cred_get());
261 		firmware_handle_free(fh);
262 		return (error);
263 	}
264 
265 	if (va.va_type != VREG) {
266 		VOP_UNLOCK(vp);
267 		(void)vn_close(vp, FREAD, kauth_cred_get());
268 		firmware_handle_free(fh);
269 		return (EINVAL);
270 	}
271 
272 	/* XXX Mark as busy text file. */
273 
274 	fh->fh_vp = vp;
275 	fh->fh_size = va.va_size;
276 
277 	VOP_UNLOCK(vp);
278 
279 	*fhp = fh;
280 	return (0);
281 }
282 
283 /*
284  * firmware_close:
285  *
286  *	Close a firmware image.
287  */
288 int
289 firmware_close(firmware_handle_t fh)
290 {
291 	int error;
292 
293 	error = vn_close(fh->fh_vp, FREAD, kauth_cred_get());
294 	firmware_handle_free(fh);
295 	return (error);
296 }
297 
298 /*
299  * firmware_get_size:
300  *
301  *	Return the total size of a firmware image.
302  */
303 off_t
304 firmware_get_size(firmware_handle_t fh)
305 {
306 
307 	return (fh->fh_size);
308 }
309 
310 /*
311  * firmware_read:
312  *
313  *	Read data from a firmware image at the specified offset into
314  *	the provided buffer.
315  */
316 int
317 firmware_read(firmware_handle_t fh, off_t offset, void *buf, size_t len)
318 {
319 
320 	return (vn_rdwr(UIO_READ, fh->fh_vp, buf, len, offset,
321 			UIO_SYSSPACE, 0, kauth_cred_get(), NULL, curlwp));
322 }
323 
324 /*
325  * firmware_malloc:
326  *
327  *	Allocate a firmware buffer of the specified size.
328  *
329  *	NOTE: This routine may block.
330  */
331 void *
332 firmware_malloc(size_t size)
333 {
334 
335 	return (kmem_alloc(size, KM_SLEEP));
336 }
337 
338 /*
339  * firmware_free:
340  *
341  *	Free a previously allocated firmware buffer.
342  */
343 /*ARGSUSED*/
344 void
345 firmware_free(void *v, size_t size)
346 {
347 
348 	kmem_free(v, size);
349 }
350