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