subr_firmware.c revision 159589
1/*-
2 * Copyright (c) 2005, Sam Leffler <sam@errno.com>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice unmodified, this list of conditions, and the following
10 *    disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/kern/subr_firmware.c 159589 2006-06-13 21:34:12Z jhb $");
29
30#include <sys/param.h>
31#include <sys/kernel.h>
32#include <sys/malloc.h>
33#include <sys/queue.h>
34#include <sys/taskqueue.h>
35#include <sys/systm.h>
36#include <sys/lock.h>
37#include <sys/mutex.h>
38#include <sys/errno.h>
39#include <sys/linker.h>
40#include <sys/firmware.h>
41#include <sys/proc.h>
42#include <sys/module.h>
43
44#define	FIRMWARE_MAX	30
45static struct firmware firmware_table[FIRMWARE_MAX];
46struct task firmware_task;
47struct mtx firmware_mtx;
48MTX_SYSINIT(firmware, &firmware_mtx, "firmware table", MTX_DEF);
49
50/*
51 * Register a firmware image with the specified name.  The
52 * image name must not already be registered.  If this is a
53 * subimage then parent refers to a previously registered
54 * image that this should be associated with.
55 */
56struct firmware *
57firmware_register(const char *imagename, const void *data, size_t datasize,
58    unsigned int version, struct firmware *parent)
59{
60	struct firmware *frp = NULL;
61	int i;
62
63	mtx_lock(&firmware_mtx);
64	for (i = 0; i < FIRMWARE_MAX; i++) {
65		struct firmware *fp = &firmware_table[i];
66
67		if (fp->name == NULL) {
68			if (frp == NULL)
69				frp = fp;
70			continue;
71		}
72		if (strcasecmp(imagename, fp->name) == 0) {
73			mtx_unlock(&firmware_mtx);
74			printf("%s: image %s already registered!\n",
75				__func__, imagename);
76			return NULL;
77		}
78	}
79	if (frp == NULL) {
80		mtx_unlock(&firmware_mtx);
81		printf("%s: cannot register image %s, firmware table full!\n",
82		    __func__, imagename);
83		return NULL;
84	}
85	frp->name = imagename;
86	frp->data = data;
87	frp->datasize = datasize;
88	frp->version = version;
89	frp->refcnt = 0;
90	frp->flags = 0;
91	if (parent != NULL)
92		parent->refcnt++;
93	frp->parent = parent;
94	frp->file = NULL;
95	mtx_unlock(&firmware_mtx);
96	return frp;
97}
98
99static void
100clearentry(struct firmware *fp)
101{
102	KASSERT(fp->refcnt == 0, ("image %s refcnt %u", fp->name, fp->refcnt));
103	fp->name = NULL;
104	fp->file = NULL;
105	fp->data = NULL;
106	fp->datasize = 0;
107	fp->version = 0;
108	fp->flags = 0;
109	if (fp->parent != NULL) {	/* release parent reference */
110		fp->parent->refcnt--;
111		fp->parent = NULL;
112	}
113}
114
115static struct firmware *
116lookup(const char *name)
117{
118	struct firmware *fp;
119	int i;
120
121	for (i = 0; i < FIRMWARE_MAX; i++) {
122		fp = &firmware_table[i];
123		if (fp->name != NULL && strcasecmp(name, fp->name) == 0)
124			return fp;
125	}
126	return NULL;
127}
128
129/*
130 * Unregister/remove a firmware image.  If there are outstanding
131 * references an error is returned and the image is not removed
132 * from the registry.
133 */
134int
135firmware_unregister(const char *imagename)
136{
137	struct firmware *fp;
138	int refcnt = 0;
139
140	mtx_lock(&firmware_mtx);
141	/*
142	 * NB: it is ok for the lookup to fail; this can happen
143	 * when a module is unloaded on last reference and the
144	 * module unload handler unregister's each of it's
145	 * firmware images.
146	 */
147	fp = lookup(imagename);
148	if (fp != NULL) {
149		refcnt = fp->refcnt;
150		if (refcnt == 0)
151			clearentry(fp);
152	}
153	mtx_unlock(&firmware_mtx);
154	return (refcnt != 0 ? EBUSY : 0);
155}
156
157/*
158 * Lookup and potentially load the specified firmware image.
159 * If the firmware is not found in the registry attempt to
160 * load a kernel module with the image name.  If the firmware
161 * is located a reference is returned.  The caller must release
162 * this reference for the image to be eligible for removal/unload.
163 */
164struct firmware *
165firmware_get(const char *imagename)
166{
167	struct thread *td;
168	struct firmware *fp;
169	linker_file_t result;
170	int requested_load = 0;
171
172again:
173	mtx_lock(&firmware_mtx);
174	fp = lookup(imagename);
175	if (fp != NULL) {
176		if (requested_load)
177			fp->file = result;
178		fp->refcnt++;
179		mtx_unlock(&firmware_mtx);
180		return fp;
181	}
182	/*
183	 * Image not present, try to load the module holding it
184	 * or if we already tried give up.
185	 */
186	mtx_unlock(&firmware_mtx);
187	if (requested_load) {
188		printf("%s: failed to load firmware image %s\n",
189		    __func__, imagename);
190		return NULL;
191	}
192	td = curthread;
193	if (suser(td) != 0 || securelevel_gt(td->td_ucred, 0) != 0) {
194		printf("%s: insufficient privileges to "
195		    "load firmware image %s\n", __func__, imagename);
196		return NULL;
197	}
198	mtx_lock(&Giant);		/* XXX */
199	(void) linker_reference_module(imagename, NULL, &result);
200	mtx_unlock(&Giant);		/* XXX */
201	requested_load = 1;
202	goto again;		/* sort of an Algol-style for loop */
203}
204
205static void
206unloadentry(void *unused1, int unused2)
207{
208	struct firmware *fp;
209	linker_file_t file;
210	int i;
211
212	mtx_lock(&firmware_mtx);
213	for (;;) {
214		/* Look for an unwanted entry that we explicitly loaded. */
215		for (i = 0; i < FIRMWARE_MAX; i++) {
216			fp = &firmware_table[i];
217			if (fp->name != NULL && fp->file != NULL &&
218			    fp->refcnt == 0 &&
219			    (fp->flags & FIRMWAREFLAG_KEEPKLDREF) == 0)
220				break;
221			fp = NULL;
222		}
223		if (fp == NULL)
224			break;
225		file = fp->file;
226		/* No longer explicitly loaded. */
227		fp->file = NULL;
228		mtx_unlock(&firmware_mtx);
229
230		linker_file_unload(file, LINKER_UNLOAD_NORMAL);
231
232		mtx_lock(&firmware_mtx);
233	}
234	mtx_unlock(&firmware_mtx);
235}
236
237/*
238 * Release a reference to a firmware image returned by
239 * firmware_get.  The reference is released and if this is
240 * the last reference to the firmware image the associated
241 * module may be released/unloaded.
242 */
243void
244firmware_put(struct firmware *fp, int flags)
245{
246	mtx_lock(&firmware_mtx);
247	fp->refcnt--;
248	if (fp->refcnt == 0) {
249		if ((flags & FIRMWARE_UNLOAD) == 0)
250			fp->flags |= FIRMWAREFLAG_KEEPKLDREF;
251	}
252	if (fp->file)
253		taskqueue_enqueue(taskqueue_thread, &firmware_task);
254	mtx_unlock(&firmware_mtx);
255}
256
257/*
258 * Module glue.
259 */
260static int
261firmware_modevent(module_t mod, int type, void *unused)
262{
263	struct firmware *fp;
264	int i;
265
266	switch (type) {
267	case MOD_LOAD:
268		TASK_INIT(&firmware_task, 0, unloadentry, NULL);
269		return 0;
270	case MOD_UNLOAD:
271		for (i = 0; i < FIRMWARE_MAX; i++) {
272			fp = &firmware_table[i];
273			fp->flags &= ~FIRMWAREFLAG_KEEPKLDREF;
274		}
275		taskqueue_enqueue(taskqueue_thread, &firmware_task);
276		taskqueue_drain(taskqueue_thread, &firmware_task);
277		return 0;
278	}
279	return EINVAL;
280}
281
282static moduledata_t firmware_mod = {
283	"firmware",
284	firmware_modevent,
285	0
286};
287DECLARE_MODULE(firmware, firmware_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);
288MODULE_VERSION(firmware, 1);
289