xref: /freebsd/sys/kern/subr_firmware.c (revision acd3428b)
16aec1278SMax Laier /*-
26aec1278SMax Laier  * Copyright (c) 2005, Sam Leffler <sam@errno.com>
36aec1278SMax Laier  * All rights reserved.
46aec1278SMax Laier  *
56aec1278SMax Laier  * Redistribution and use in source and binary forms, with or without
66aec1278SMax Laier  * modification, are permitted provided that the following conditions
76aec1278SMax Laier  * are met:
86aec1278SMax Laier  * 1. Redistributions of source code must retain the above copyright
96aec1278SMax Laier  *    notice unmodified, this list of conditions, and the following
106aec1278SMax Laier  *    disclaimer.
116aec1278SMax Laier  * 2. Redistributions in binary form must reproduce the above copyright
126aec1278SMax Laier  *    notice, this list of conditions and the following disclaimer in the
136aec1278SMax Laier  *    documentation and/or other materials provided with the distribution.
146aec1278SMax Laier  *
156aec1278SMax Laier  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
166aec1278SMax Laier  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
176aec1278SMax Laier  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
186aec1278SMax Laier  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
196aec1278SMax Laier  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
206aec1278SMax Laier  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
216aec1278SMax Laier  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
226aec1278SMax Laier  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
236aec1278SMax Laier  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
246aec1278SMax Laier  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
256aec1278SMax Laier  */
266aec1278SMax Laier 
276aec1278SMax Laier #include <sys/cdefs.h>
286aec1278SMax Laier __FBSDID("$FreeBSD$");
296aec1278SMax Laier 
306aec1278SMax Laier #include <sys/param.h>
316aec1278SMax Laier #include <sys/kernel.h>
326aec1278SMax Laier #include <sys/malloc.h>
336aec1278SMax Laier #include <sys/queue.h>
346aec1278SMax Laier #include <sys/taskqueue.h>
356aec1278SMax Laier #include <sys/systm.h>
366aec1278SMax Laier #include <sys/lock.h>
376aec1278SMax Laier #include <sys/mutex.h>
386aec1278SMax Laier #include <sys/errno.h>
396aec1278SMax Laier #include <sys/linker.h>
406aec1278SMax Laier #include <sys/firmware.h>
41acd3428bSRobert Watson #include <sys/priv.h>
426aec1278SMax Laier #include <sys/proc.h>
436aec1278SMax Laier #include <sys/module.h>
446aec1278SMax Laier 
456aec1278SMax Laier #define	FIRMWARE_MAX	30
466aec1278SMax Laier static struct firmware firmware_table[FIRMWARE_MAX];
476aec1278SMax Laier struct task firmware_task;
486aec1278SMax Laier struct mtx firmware_mtx;
496aec1278SMax Laier MTX_SYSINIT(firmware, &firmware_mtx, "firmware table", MTX_DEF);
506aec1278SMax Laier 
516aec1278SMax Laier /*
526aec1278SMax Laier  * Register a firmware image with the specified name.  The
536aec1278SMax Laier  * image name must not already be registered.  If this is a
546aec1278SMax Laier  * subimage then parent refers to a previously registered
556aec1278SMax Laier  * image that this should be associated with.
566aec1278SMax Laier  */
576aec1278SMax Laier struct firmware *
586aec1278SMax Laier firmware_register(const char *imagename, const void *data, size_t datasize,
596aec1278SMax Laier     unsigned int version, struct firmware *parent)
606aec1278SMax Laier {
616aec1278SMax Laier 	struct firmware *frp = NULL;
626aec1278SMax Laier 	int i;
636aec1278SMax Laier 
646aec1278SMax Laier 	mtx_lock(&firmware_mtx);
656aec1278SMax Laier 	for (i = 0; i < FIRMWARE_MAX; i++) {
666aec1278SMax Laier 		struct firmware *fp = &firmware_table[i];
676aec1278SMax Laier 
686aec1278SMax Laier 		if (fp->name == NULL) {
696aec1278SMax Laier 			if (frp == NULL)
706aec1278SMax Laier 				frp = fp;
716aec1278SMax Laier 			continue;
726aec1278SMax Laier 		}
736aec1278SMax Laier 		if (strcasecmp(imagename, fp->name) == 0) {
746aec1278SMax Laier 			mtx_unlock(&firmware_mtx);
756aec1278SMax Laier 			printf("%s: image %s already registered!\n",
766aec1278SMax Laier 				__func__, imagename);
776aec1278SMax Laier 			return NULL;
786aec1278SMax Laier 		}
796aec1278SMax Laier 	}
806aec1278SMax Laier 	if (frp == NULL) {
816aec1278SMax Laier 		mtx_unlock(&firmware_mtx);
826aec1278SMax Laier 		printf("%s: cannot register image %s, firmware table full!\n",
836aec1278SMax Laier 		    __func__, imagename);
846aec1278SMax Laier 		return NULL;
856aec1278SMax Laier 	}
866aec1278SMax Laier 	frp->name = imagename;
876aec1278SMax Laier 	frp->data = data;
886aec1278SMax Laier 	frp->datasize = datasize;
896aec1278SMax Laier 	frp->version = version;
906aec1278SMax Laier 	frp->refcnt = 0;
91eb1030c4SIan Dowse 	frp->flags = 0;
926aec1278SMax Laier 	if (parent != NULL)
936aec1278SMax Laier 		parent->refcnt++;
946aec1278SMax Laier 	frp->parent = parent;
956aec1278SMax Laier 	frp->file = NULL;
966aec1278SMax Laier 	mtx_unlock(&firmware_mtx);
976aec1278SMax Laier 	return frp;
986aec1278SMax Laier }
996aec1278SMax Laier 
1006aec1278SMax Laier static void
101eb1030c4SIan Dowse clearentry(struct firmware *fp)
1026aec1278SMax Laier {
1036aec1278SMax Laier 	KASSERT(fp->refcnt == 0, ("image %s refcnt %u", fp->name, fp->refcnt));
1046aec1278SMax Laier 	fp->name = NULL;
1056aec1278SMax Laier 	fp->file = NULL;
1066aec1278SMax Laier 	fp->data = NULL;
1076aec1278SMax Laier 	fp->datasize = 0;
1086aec1278SMax Laier 	fp->version = 0;
109eb1030c4SIan Dowse 	fp->flags = 0;
1106aec1278SMax Laier 	if (fp->parent != NULL) {	/* release parent reference */
1116aec1278SMax Laier 		fp->parent->refcnt--;
1126aec1278SMax Laier 		fp->parent = NULL;
1136aec1278SMax Laier 	}
1146aec1278SMax Laier }
1156aec1278SMax Laier 
1166aec1278SMax Laier static struct firmware *
1176aec1278SMax Laier lookup(const char *name)
1186aec1278SMax Laier {
119b21c9288SJohn Baldwin 	struct firmware *fp;
1206aec1278SMax Laier 	int i;
1216aec1278SMax Laier 
1226aec1278SMax Laier 	for (i = 0; i < FIRMWARE_MAX; i++) {
123b21c9288SJohn Baldwin 		fp = &firmware_table[i];
1246aec1278SMax Laier 		if (fp->name != NULL && strcasecmp(name, fp->name) == 0)
1256aec1278SMax Laier 			return fp;
1266aec1278SMax Laier 	}
1276aec1278SMax Laier 	return NULL;
1286aec1278SMax Laier }
1296aec1278SMax Laier 
1306aec1278SMax Laier /*
1316aec1278SMax Laier  * Unregister/remove a firmware image.  If there are outstanding
1326aec1278SMax Laier  * references an error is returned and the image is not removed
1336aec1278SMax Laier  * from the registry.
1346aec1278SMax Laier  */
1356aec1278SMax Laier int
1366aec1278SMax Laier firmware_unregister(const char *imagename)
1376aec1278SMax Laier {
1386aec1278SMax Laier 	struct firmware *fp;
1396aec1278SMax Laier 	int refcnt = 0;
1406aec1278SMax Laier 
1416aec1278SMax Laier 	mtx_lock(&firmware_mtx);
1426aec1278SMax Laier 	/*
1436aec1278SMax Laier 	 * NB: it is ok for the lookup to fail; this can happen
1446aec1278SMax Laier 	 * when a module is unloaded on last reference and the
1456aec1278SMax Laier 	 * module unload handler unregister's each of it's
1466aec1278SMax Laier 	 * firmware images.
1476aec1278SMax Laier 	 */
1486aec1278SMax Laier 	fp = lookup(imagename);
1496aec1278SMax Laier 	if (fp != NULL) {
1506aec1278SMax Laier 		refcnt = fp->refcnt;
1516aec1278SMax Laier 		if (refcnt == 0)
152eb1030c4SIan Dowse 			clearentry(fp);
1536aec1278SMax Laier 	}
1546aec1278SMax Laier 	mtx_unlock(&firmware_mtx);
1556aec1278SMax Laier 	return (refcnt != 0 ? EBUSY : 0);
1566aec1278SMax Laier }
1576aec1278SMax Laier 
1586aec1278SMax Laier /*
1596aec1278SMax Laier  * Lookup and potentially load the specified firmware image.
1606aec1278SMax Laier  * If the firmware is not found in the registry attempt to
1616aec1278SMax Laier  * load a kernel module with the image name.  If the firmware
1626aec1278SMax Laier  * is located a reference is returned.  The caller must release
1636aec1278SMax Laier  * this reference for the image to be eligible for removal/unload.
1646aec1278SMax Laier  */
1656aec1278SMax Laier struct firmware *
1666aec1278SMax Laier firmware_get(const char *imagename)
1676aec1278SMax Laier {
1686aec1278SMax Laier 	struct thread *td;
1696aec1278SMax Laier 	struct firmware *fp;
1706aec1278SMax Laier 	linker_file_t result;
1716aec1278SMax Laier 	int requested_load = 0;
1726aec1278SMax Laier 
1736aec1278SMax Laier again:
1746aec1278SMax Laier 	mtx_lock(&firmware_mtx);
1756aec1278SMax Laier 	fp = lookup(imagename);
1766aec1278SMax Laier 	if (fp != NULL) {
1776aec1278SMax Laier 		if (requested_load)
1786aec1278SMax Laier 			fp->file = result;
1796aec1278SMax Laier 		fp->refcnt++;
1806aec1278SMax Laier 		mtx_unlock(&firmware_mtx);
1816aec1278SMax Laier 		return fp;
1826aec1278SMax Laier 	}
1836aec1278SMax Laier 	/*
1846aec1278SMax Laier 	 * Image not present, try to load the module holding it
1856aec1278SMax Laier 	 * or if we already tried give up.
1866aec1278SMax Laier 	 */
1876aec1278SMax Laier 	mtx_unlock(&firmware_mtx);
1886aec1278SMax Laier 	if (requested_load) {
1896aec1278SMax Laier 		printf("%s: failed to load firmware image %s\n",
1906aec1278SMax Laier 		    __func__, imagename);
1916aec1278SMax Laier 		return NULL;
1926aec1278SMax Laier 	}
1936aec1278SMax Laier 	td = curthread;
194acd3428bSRobert Watson 	if (priv_check(td, PRIV_FIRMWARE_LOAD) != 0 ||
195acd3428bSRobert Watson 	    securelevel_gt(td->td_ucred, 0) != 0) {
1966aec1278SMax Laier 		printf("%s: insufficient privileges to "
1976aec1278SMax Laier 		    "load firmware image %s\n", __func__, imagename);
1986aec1278SMax Laier 		return NULL;
1996aec1278SMax Laier 	}
2006aec1278SMax Laier 	(void) linker_reference_module(imagename, NULL, &result);
2016aec1278SMax Laier 	requested_load = 1;
2026aec1278SMax Laier 	goto again;		/* sort of an Algol-style for loop */
2036aec1278SMax Laier }
2046aec1278SMax Laier 
2056aec1278SMax Laier static void
2066aec1278SMax Laier unloadentry(void *unused1, int unused2)
2076aec1278SMax Laier {
2086aec1278SMax Laier 	struct firmware *fp;
209eb1030c4SIan Dowse 	linker_file_t file;
210450ec4edSIan Dowse 	int i, err;
2116aec1278SMax Laier 
2126aec1278SMax Laier 	mtx_lock(&firmware_mtx);
213eb1030c4SIan Dowse 	for (;;) {
214eb1030c4SIan Dowse 		/* Look for an unwanted entry that we explicitly loaded. */
215eb1030c4SIan Dowse 		for (i = 0; i < FIRMWARE_MAX; i++) {
216eb1030c4SIan Dowse 			fp = &firmware_table[i];
217eb1030c4SIan Dowse 			if (fp->name != NULL && fp->file != NULL &&
218eb1030c4SIan Dowse 			    fp->refcnt == 0 &&
219eb1030c4SIan Dowse 			    (fp->flags & FIRMWAREFLAG_KEEPKLDREF) == 0)
220eb1030c4SIan Dowse 				break;
221eb1030c4SIan Dowse 			fp = NULL;
222eb1030c4SIan Dowse 		}
223eb1030c4SIan Dowse 		if (fp == NULL)
224eb1030c4SIan Dowse 			break;
225eb1030c4SIan Dowse 		file = fp->file;
226eb1030c4SIan Dowse 		/* No longer explicitly loaded. */
227eb1030c4SIan Dowse 		fp->file = NULL;
2286aec1278SMax Laier 		mtx_unlock(&firmware_mtx);
2296aec1278SMax Laier 
230450ec4edSIan Dowse 		err = linker_release_module(NULL, NULL, file);
2316aec1278SMax Laier 
2326aec1278SMax Laier 		mtx_lock(&firmware_mtx);
233450ec4edSIan Dowse 		if (err) {
234450ec4edSIan Dowse 			/*
235450ec4edSIan Dowse 			 * If linker_release_module() failed then we still
236450ec4edSIan Dowse 			 * hold a reference on the module so it should not be
237450ec4edSIan Dowse 			 * possible for it to go away or be re-registered.
238450ec4edSIan Dowse 			 */
239450ec4edSIan Dowse 			KASSERT(fp->file == NULL,
240450ec4edSIan Dowse 			    ("firmware entry reused while referenced!"));
241450ec4edSIan Dowse 			fp->file = file;
242450ec4edSIan Dowse 		}
2436aec1278SMax Laier 	}
2446aec1278SMax Laier 	mtx_unlock(&firmware_mtx);
2456aec1278SMax Laier }
2466aec1278SMax Laier 
2476aec1278SMax Laier /*
2486aec1278SMax Laier  * Release a reference to a firmware image returned by
2496aec1278SMax Laier  * firmware_get.  The reference is released and if this is
2506aec1278SMax Laier  * the last reference to the firmware image the associated
2516aec1278SMax Laier  * module may be released/unloaded.
2526aec1278SMax Laier  */
2536aec1278SMax Laier void
2546aec1278SMax Laier firmware_put(struct firmware *fp, int flags)
2556aec1278SMax Laier {
2566aec1278SMax Laier 	mtx_lock(&firmware_mtx);
2576aec1278SMax Laier 	fp->refcnt--;
258eb1030c4SIan Dowse 	if (fp->refcnt == 0) {
259eb1030c4SIan Dowse 		if ((flags & FIRMWARE_UNLOAD) == 0)
260eb1030c4SIan Dowse 			fp->flags |= FIRMWAREFLAG_KEEPKLDREF;
261eb1030c4SIan Dowse 	}
2626aec1278SMax Laier 	if (fp->file)
2636aec1278SMax Laier 		taskqueue_enqueue(taskqueue_thread, &firmware_task);
2646aec1278SMax Laier 	mtx_unlock(&firmware_mtx);
2656aec1278SMax Laier }
2666aec1278SMax Laier 
2676aec1278SMax Laier /*
2686aec1278SMax Laier  * Module glue.
2696aec1278SMax Laier  */
2706aec1278SMax Laier static int
2716aec1278SMax Laier firmware_modevent(module_t mod, int type, void *unused)
2726aec1278SMax Laier {
273b21c9288SJohn Baldwin 	struct firmware *fp;
274eb1030c4SIan Dowse 	int i;
275eb1030c4SIan Dowse 
2766aec1278SMax Laier 	switch (type) {
2776aec1278SMax Laier 	case MOD_LOAD:
2786aec1278SMax Laier 		TASK_INIT(&firmware_task, 0, unloadentry, NULL);
2796aec1278SMax Laier 		return 0;
2806aec1278SMax Laier 	case MOD_UNLOAD:
281eb1030c4SIan Dowse 		for (i = 0; i < FIRMWARE_MAX; i++) {
282b21c9288SJohn Baldwin 			fp = &firmware_table[i];
283eb1030c4SIan Dowse 			fp->flags &= ~FIRMWAREFLAG_KEEPKLDREF;
284eb1030c4SIan Dowse 		}
285eb1030c4SIan Dowse 		taskqueue_enqueue(taskqueue_thread, &firmware_task);
2866aec1278SMax Laier 		taskqueue_drain(taskqueue_thread, &firmware_task);
2876aec1278SMax Laier 		return 0;
2886aec1278SMax Laier 	}
2896aec1278SMax Laier 	return EINVAL;
2906aec1278SMax Laier }
2916aec1278SMax Laier 
2926aec1278SMax Laier static moduledata_t firmware_mod = {
2936aec1278SMax Laier 	"firmware",
2946aec1278SMax Laier 	firmware_modevent,
2956aec1278SMax Laier 	0
2966aec1278SMax Laier };
2976aec1278SMax Laier DECLARE_MODULE(firmware, firmware_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);
2986aec1278SMax Laier MODULE_VERSION(firmware, 1);
299