1 /*	$NetBSD: firmload.c,v 1.22 2016/05/30 02:33:49 dholland 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.22 2016/05/30 02:33:49 dholland 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 nameidata nd;
208 	struct vattr va;
209 	char *pnbuf, *path, *prefix;
210 	firmware_handle_t fh;
211 	struct vnode *vp;
212 	int error;
213 	extern struct cwdinfo cwdi0;
214 
215 	if (drvname == NULL || imgname == NULL)
216 		return (EINVAL);
217 
218 	if (cwdi0.cwdi_cdir == NULL) {
219 		printf("firmware_open(%s/%s) called too early.\n",
220 			drvname, imgname);
221 		return ENOENT;
222 	}
223 
224 	pnbuf = PNBUF_GET();
225 	KASSERT(pnbuf != NULL);
226 
227 	fh = firmware_handle_alloc();
228 	KASSERT(fh != NULL);
229 
230 	error = 0;
231 	for (path = firmware_path_first(drvname, imgname, pnbuf, &prefix);
232 	     path != NULL;
233 	     path = firmware_path_next(drvname, imgname, pnbuf, &prefix)) {
234 		pb = pathbuf_create(path);
235 		if (pb == NULL) {
236 			error = ENOMEM;
237 			break;
238 		}
239 		NDINIT(&nd, LOOKUP, FOLLOW | NOCHROOT, pb);
240 		error = vn_open(&nd, FREAD, 0);
241 		pathbuf_destroy(pb);
242 		if (error == ENOENT) {
243 			continue;
244 		}
245 		break;
246 	}
247 
248 	PNBUF_PUT(pnbuf);
249 	if (error) {
250 		firmware_handle_free(fh);
251 		return (error);
252 	}
253 
254 	vp = nd.ni_vp;
255 
256 	error = VOP_GETATTR(vp, &va, kauth_cred_get());
257 	if (error) {
258 		VOP_UNLOCK(vp);
259 		(void)vn_close(vp, FREAD, kauth_cred_get());
260 		firmware_handle_free(fh);
261 		return (error);
262 	}
263 
264 	if (va.va_type != VREG) {
265 		VOP_UNLOCK(vp);
266 		(void)vn_close(vp, FREAD, kauth_cred_get());
267 		firmware_handle_free(fh);
268 		return (EINVAL);
269 	}
270 
271 	/* XXX Mark as busy text file. */
272 
273 	fh->fh_vp = vp;
274 	fh->fh_size = va.va_size;
275 
276 	VOP_UNLOCK(vp);
277 
278 	*fhp = fh;
279 	return (0);
280 }
281 
282 /*
283  * firmware_close:
284  *
285  *	Close a firmware image.
286  */
287 int
firmware_close(firmware_handle_t fh)288 firmware_close(firmware_handle_t fh)
289 {
290 	int error;
291 
292 	error = vn_close(fh->fh_vp, FREAD, kauth_cred_get());
293 	firmware_handle_free(fh);
294 	return (error);
295 }
296 
297 /*
298  * firmware_get_size:
299  *
300  *	Return the total size of a firmware image.
301  */
302 off_t
firmware_get_size(firmware_handle_t fh)303 firmware_get_size(firmware_handle_t fh)
304 {
305 
306 	return (fh->fh_size);
307 }
308 
309 /*
310  * firmware_read:
311  *
312  *	Read data from a firmware image at the specified offset into
313  *	the provided buffer.
314  */
315 int
firmware_read(firmware_handle_t fh,off_t offset,void * buf,size_t len)316 firmware_read(firmware_handle_t fh, off_t offset, void *buf, size_t len)
317 {
318 
319 	return (vn_rdwr(UIO_READ, fh->fh_vp, buf, len, offset,
320 			UIO_SYSSPACE, 0, kauth_cred_get(), NULL, curlwp));
321 }
322 
323 /*
324  * firmware_malloc:
325  *
326  *	Allocate a firmware buffer of the specified size.
327  *
328  *	NOTE: This routine may block.
329  */
330 void *
firmware_malloc(size_t size)331 firmware_malloc(size_t size)
332 {
333 
334 	return (kmem_alloc(size, KM_SLEEP));
335 }
336 
337 /*
338  * firmware_free:
339  *
340  *	Free a previously allocated firmware buffer.
341  */
342 /*ARGSUSED*/
343 void
firmware_free(void * v,size_t size)344 firmware_free(void *v, size_t size)
345 {
346 
347 	kmem_free(v, size);
348 }
349