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 |
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 != '/') { /* 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 * |
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 |
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 |
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 |
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 |
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 * |
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 |
344 | firmware_free(void *v, size_t size) |
345 | { |
346 | |
347 | kmem_free(v, size); |
348 | } |
349 | |