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
53struct firmware_handle {
54	struct vnode	*fh_vp;
55	off_t		 fh_size;
56};
57
58static firmware_handle_t
59firmware_handle_alloc(void)
60{
61
62	return (kmem_alloc(sizeof(struct firmware_handle), KM_SLEEP));
63}
64
65static void
66firmware_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
77static char firmware_paths[PATH_MAX+1] = FIRMWARE_PATHS;
78
79static int
80sysctl_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
118SYSCTL_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
137static char *
138firmware_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
189static char *
190firmware_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 */
203int
204firmware_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 */
282int
283firmware_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 */
297off_t
298firmware_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 */
310int
311firmware_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 */
325void *
326firmware_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*/
338void
339firmware_free(void *v, size_t size)
340{
341
342	kmem_free(v, size);
343}
344