firmware - Import FreeBSD's firmware API.
authorJoe Talbott <josepht@neptune.xenno.com>
Wed, 31 Mar 2010 23:39:44 +0000 (19:39 -0400)
committerRui Paulo <rpaulo@FreeBSD.org>
Thu, 8 Apr 2010 12:50:12 +0000 (13:50 +0100)
This should be considered experimental.  When porting code from
FreeBSD instances of #include <sys/firmware.h> should be converted
to #include <sys/fbsd_firmware.h>.  I have also taken out some of
the firmware file locking code.

sys/Makefile
sys/conf/kmod.mk
sys/firmware/Makefile [new file with mode: 0644]
sys/kern/kern_linker.c
sys/kern/subr_firmware.c [new file with mode: 0644]
sys/sys/fbsd_firmware.h [new file with mode: 0644]
sys/sys/linker.h
sys/tools/fw_stub.awk [new file with mode: 0644]

index 06dca54..ad77163 100644 (file)
@@ -12,7 +12,7 @@ SUBDIR=       boot
 .if defined(MODULES_OVERRIDE)
 SUBDIR+=${MODULES_OVERRIDE}
 .else
-SUBDIR+=bus crypto emulation dev kern net netbt netgraph netproto vfs
+SUBDIR+=bus crypto emulation firmware dev kern net netbt netgraph netproto vfs
 SUBDIR+=libiconv
 .endif
 .endif
index 86d97c3..3467321 100644 (file)
@@ -7,7 +7,6 @@
 #
 #
 # +++ variables +++
-#
 # CLEANFILES   Additional files to remove for the clean and cleandir targets.
 #
 # KMOD          The name of the kernel module to build.
@@ -37,6 +36,8 @@
 # MFILES       Optionally a list of interfaces used by the module.
 #              This file contains a default list of interfaces.
 #
+# FIRMWS       Firmware module in the form filename:shortname:version
+#
 # +++ targets +++
 #
 #      install:
@@ -134,6 +135,36 @@ CFLAGS+=   -fno-omit-frame-pointer
 
 .include <bsd.patch.mk>
 
+.if defined(FIRMWS)
+AWK=/usr/bin/awk
+.if !exists(@)
+${KMOD:S/$/.c/}: @
+.else
+${KMOD:S/$/.c/}: @/tools/fw_stub.awk
+.endif
+       ${AWK} -f @/tools/fw_stub.awk ${FIRMWS} -m${KMOD} -c${KMOD:S/$/.c/g} \
+           ${FIRMWARE_LICENSE:C/.+/-l/}${FIRMWARE_LICENSE}
+
+SRCS+= ${KMOD:S/$/.c/}
+CLEANFILES+=   ${KMOD:S/$/.c/}
+
+.for _firmw in ${FIRMWS}
+${_firmw:C/\:.*$/.fwo/}:       ${_firmw:C/\:.*$//}
+       @${ECHO} ${_firmw:C/\:.*$//} ${.ALLSRC:M*${_firmw:C/\:.*$//}}
+       @if [ -e ${_firmw:C/\:.*$//} ]; then                    \
+               ${LD} -b binary --no-warn-mismatch ${LDFLAGS}   \
+                   -r -d -o ${.TARGET} ${_firmw:C/\:.*$//};    \
+       else                                                    \
+               ln -s ${.ALLSRC:M*${_firmw:C/\:.*$//}} ${_firmw:C/\:.*$//}; \
+               ${LD} -b binary --no-warn-mismatch ${LDFLAGS}   \
+                   -r -d -o ${.TARGET} ${_firmw:C/\:.*$//};    \
+               rm ${_firmw:C/\:.*$//};                         \
+       fi
+
+OBJS+= ${_firmw:C/\:.*$/.fwo/}
+.endfor
+.endif
+
 OBJS+=  ${SRCS:N*.h:N*.patch:R:S/$/.o/g}
 
 .if !defined(PROG)
diff --git a/sys/firmware/Makefile b/sys/firmware/Makefile
new file mode 100644 (file)
index 0000000..41ae16a
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD: src/sys/modules/firmware/Makefile,v 1.2.2.1 2009/08/03 08:13:06 kensmith Exp $
+
+.PATH: ${.CURDIR}/../kern
+
+KMOD=  fbsd_firmware
+SRCS=  subr_firmware.c #vnode_if.h
+
+.include <bsd.kmod.mk>
index 02464c3..4c2ccc0 100644 (file)
@@ -368,6 +368,7 @@ out:
     return error;
 }
 
+
 linker_file_t
 linker_find_file_by_name(const char* filename)
 {
@@ -1075,6 +1076,51 @@ modlist_lookup2(const char *name, struct mod_depend *verinfo)
     return (bestmod);
 }
 
+int
+linker_reference_module(const char *modname, struct mod_depend *verinfo,
+    linker_file_t *result)
+{
+    modlist_t mod;
+    int error;
+
+    lockmgr(&lock, LK_SHARED);
+    if ((mod = modlist_lookup2(modname, verinfo)) != NULL) {
+        *result = mod->container;
+        (*result)->refs++;
+        lockmgr(&lock, LK_RELEASE);
+        return (0);
+    }
+
+    error = linker_load_module(NULL, modname, NULL, verinfo, result);
+    lockmgr(&lock, LK_RELEASE);
+    return (error);
+}
+
+int
+linker_release_module(const char *modname, struct mod_depend *verinfo,
+    linker_file_t lf)
+{
+    modlist_t mod;
+    int error;
+
+    lockmgr(&lock, LK_SHARED);
+    if (lf == NULL) {
+        KASSERT(modname != NULL,
+            ("linker_release_module: no file or name"));
+        mod = modlist_lookup2(modname, verinfo);
+        if (mod == NULL) {
+            lockmgr(&lock, LK_RELEASE);
+            return (ESRCH);
+        }
+        lf = mod->container;
+    } else
+        KASSERT(modname == NULL && verinfo == NULL,
+            ("linker_release_module: both file and name"));
+    error = linker_file_unload(lf);
+    lockmgr(&lock, LK_RELEASE);
+    return (error);
+}
+
 static modlist_t
 modlist_newmodule(const char *modname, int version, linker_file_t container)
 {
diff --git a/sys/kern/subr_firmware.c b/sys/kern/subr_firmware.c
new file mode 100644 (file)
index 0000000..eb6c911
--- /dev/null
@@ -0,0 +1,553 @@
+/*-
+ * Copyright (c) 2005-2008, Sam Leffler <sam@errno.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice unmodified, this list of conditions, and the following
+ *    disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+__FBSDID("$FreeBSD: src/sys/kern/subr_firmware.c,v 1.13.2.2 2010/02/11 18:34:06 mjacob Exp $");
+*/
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/queue.h>
+#include <sys/taskqueue.h>
+#include <sys/systm.h>
+#include <sys/lock.h>
+#include <sys/spinlock.h>
+#include <sys/spinlock2.h>
+#include <sys/errno.h>
+#include <sys/linker.h>
+#include <sys/fbsd_firmware.h>
+#include <sys/priv.h>
+#include <sys/proc.h>
+#include <sys/module.h>
+#include <sys/eventhandler.h>
+
+#include <sys/filedesc.h>
+#include <sys/vnode.h>
+
+/*
+ * Loadable firmware support. See sys/sys/firmware.h and firmware(9)
+ * form more details on the subsystem.
+ *
+ * 'struct firmware' is the user-visible part of the firmware table.
+ * Additional internal information is stored in a 'struct priv_fw'
+ * (currently a static array). A slot is in use if FW_INUSE is true:
+ */
+
+#define FW_INUSE(p)    ((p)->file != NULL || (p)->fw.name != NULL)
+
+/*
+ * fw.name != NULL when an image is registered; file != NULL for
+ * autoloaded images whose handling has not been completed.
+ *
+ * The state of a slot evolves as follows:
+ *     firmware_register       -->  fw.name = image_name
+ *     (autoloaded image)      -->  file = module reference
+ *     firmware_unregister     -->  fw.name = NULL
+ *     (unloadentry complete)  -->  file = NULL
+ *
+ * In order for the above to work, the 'file' field must remain
+ * unchanged in firmware_unregister().
+ *
+ * Images residing in the same module are linked to each other
+ * through the 'parent' argument of firmware_register().
+ * One image (typically, one with the same name as the module to let
+ * the autoloading mechanism work) is considered the parent image for
+ * all other images in the same module. Children affect the refcount
+ * on the parent image preventing improper unloading of the image itself.
+ */
+
+struct priv_fw {
+       int             refcnt;         /* reference count */
+
+       /*
+        * parent entry, see above. Set on firmware_register(),
+        * cleared on firmware_unregister().
+        */
+       struct priv_fw  *parent;
+
+       int             flags;  /* record FIRMWARE_UNLOAD requests */
+#define FW_UNLOAD      0x100
+
+       /*
+        * 'file' is private info managed by the autoload/unload code.
+        * Set at the end of firmware_get(), cleared only in the
+        * firmware_unload_task, so the latter can depend on its value even
+        * while the lock is not held.
+        */
+       linker_file_t   file;   /* module file, if autoloaded */
+
+       /*
+        * 'fw' is the externally visible image information.
+        * We do not make it the first field in priv_fw, to avoid the
+        * temptation of casting pointers to each other.
+        * Use PRIV_FW(fw) to get a pointer to the cointainer of fw.
+        * Beware, PRIV_FW does not work for a NULL pointer.
+        */
+       struct firmware fw;     /* externally visible information */
+};
+
+/*
+ * PRIV_FW returns the pointer to the container of struct firmware *x.
+ * Cast to intptr_t to override the 'const' attribute of x
+ */
+#define PRIV_FW(x)     ((struct priv_fw *)             \
+       ((intptr_t)(x) - offsetof(struct priv_fw, fw)) )
+
+/*
+ * At the moment we use a static array as backing store for the registry.
+ * Should we move to a dynamic structure, keep in mind that we cannot
+ * reallocate the array because pointers are held externally.
+ * A list may work, though.
+ */
+#define        FIRMWARE_MAX    30
+static struct priv_fw firmware_table[FIRMWARE_MAX];
+
+/*
+ * Firmware module operations are handled in a separate task as they
+ * might sleep and they require directory context to do i/o.
+ */
+static struct taskqueue *firmware_tq;
+static struct task firmware_unload_task;
+
+/*
+ * This mutex protects accesses to the firmware table.
+ */
+static struct lock firmware_lock;
+#if 0
+MTX_SYSINIT(firmware, &firmware_lock, "firmware table", MTX_DEF);
+#endif
+
+/*
+ * Helper function to lookup a name.
+ * As a side effect, it sets the pointer to a free slot, if any.
+ * This way we can concentrate most of the registry scanning in
+ * this function, which makes it easier to replace the registry
+ * with some other data structure.
+ */
+static struct priv_fw *
+lookup(const char *name, struct priv_fw **empty_slot)
+{
+       struct priv_fw *fp = NULL;
+       struct priv_fw *dummy;
+       int i;
+
+       if (empty_slot == NULL)
+               empty_slot = &dummy;
+       *empty_slot = NULL;
+       for (i = 0; i < FIRMWARE_MAX; i++) {
+               fp = &firmware_table[i];
+               if (fp->fw.name != NULL && strcasecmp(name, fp->fw.name) == 0)
+                       break;
+               else if (!FW_INUSE(fp))
+                       *empty_slot = fp;
+       }
+       return (i < FIRMWARE_MAX ) ? fp : NULL;
+}
+
+/*
+ * Register a firmware image with the specified name.  The
+ * image name must not already be registered.  If this is a
+ * subimage then parent refers to a previously registered
+ * image that this should be associated with.
+ */
+const struct firmware *
+firmware_register(const char *imagename, const void *data, size_t datasize,
+    unsigned int version, const struct firmware *parent)
+{
+       struct priv_fw *match, *frp;
+
+       lockmgr(&firmware_lock, LK_EXCLUSIVE);
+       /*
+        * Do a lookup to make sure the name is unique or find a free slot.
+        */
+       match = lookup(imagename, &frp);
+       if (match != NULL) {
+               lockmgr(&firmware_lock, LK_RELEASE);
+               kprintf("%s: image %s already registered!\n",
+                       __func__, imagename);
+               return NULL;
+       }
+       if (frp == NULL) {
+               lockmgr(&firmware_lock, LK_RELEASE);
+               kprintf("%s: cannot register image %s, firmware table full!\n",
+                   __func__, imagename);
+               return NULL;
+       }
+       bzero(frp, sizeof(frp));        /* start from a clean record */
+       frp->fw.name = imagename;
+       frp->fw.data = data;
+       frp->fw.datasize = datasize;
+       frp->fw.version = version;
+       if (parent != NULL) {
+               frp->parent = PRIV_FW(parent);
+               frp->parent->refcnt++;
+       }
+       lockmgr(&firmware_lock, LK_RELEASE);
+       if (bootverbose)
+               kprintf("firmware: '%s' version %u: %zu bytes loaded at %p\n",
+                   imagename, version, datasize, data);
+       return &frp->fw;
+}
+
+/*
+ * Unregister/remove a firmware image.  If there are outstanding
+ * references an error is returned and the image is not removed
+ * from the registry.
+ */
+int
+firmware_unregister(const char *imagename)
+{
+       struct priv_fw *fp;
+       int err;
+
+       lockmgr(&firmware_lock, LK_EXCLUSIVE);
+       fp = lookup(imagename, NULL);
+       if (fp == NULL) {
+               /*
+                * It is ok for the lookup to fail; this can happen
+                * when a module is unloaded on last reference and the
+                * module unload handler unregister's each of it's
+                * firmware images.
+                */
+               err = 0;
+       } else if (fp->refcnt != 0) {   /* cannot unregister */
+               err = EBUSY;
+       }  else {
+               linker_file_t x = fp->file;     /* save value */
+
+               if (fp->parent != NULL) /* release parent reference */
+                       fp->parent->refcnt--;
+               /*
+                * Clear the whole entry with bzero to make sure we
+                * do not forget anything. Then restore 'file' which is
+                * non-null for autoloaded images.
+                */
+               bzero(fp, sizeof(struct priv_fw));
+               fp->file = x;
+               err = 0;
+       }
+       lockmgr(&firmware_lock, LK_RELEASE);
+       return err;
+}
+
+static void
+loadimage(void *arg, int npending)
+{
+       struct thread *td = curthread;
+       char *imagename = arg;
+       struct priv_fw *fp;
+       linker_file_t result;
+       int error;
+
+       /* synchronize with the thread that dispatched us */
+       lockmgr(&firmware_lock, LK_EXCLUSIVE);
+       lockmgr(&firmware_lock, LK_RELEASE);
+
+/* JAT
+       if (td->td_proc->p_fd->fd_rdir == NULL) {
+               kprintf("%s: root not mounted yet, no way to load image\n",
+                   imagename);
+               goto done;
+       }
+*/
+       error = linker_reference_module(imagename, NULL, &result);
+       if (error != 0) {
+               kprintf("%s: could not load firmware image, error %d\n",
+                   imagename, error);
+               goto done;
+       }
+
+       lockmgr(&firmware_lock, LK_EXCLUSIVE);
+       fp = lookup(imagename, NULL);
+       if (fp == NULL || fp->file != NULL) {
+               lockmgr(&firmware_lock, LK_RELEASE);
+               if (fp == NULL)
+                       kprintf("%s: firmware image loaded, "
+                           "but did not register\n", imagename);
+               (void) linker_release_module(imagename, NULL, NULL);
+               goto done;
+       }
+       fp->file = result;      /* record the module identity */
+       lockmgr(&firmware_lock, LK_RELEASE);
+done:
+       wakeup_one(imagename);          /* we're done */
+}
+
+/*
+ * Lookup and potentially load the specified firmware image.
+ * If the firmware is not found in the registry, try to load a kernel
+ * module named as the image name.
+ * If the firmware is located, a reference is returned. The caller must
+ * release this reference for the image to be eligible for removal/unload.
+ */
+const struct firmware *
+firmware_get(const char *imagename)
+{
+       struct task fwload_task;
+       struct thread *td;
+       struct priv_fw *fp;
+
+       lockmgr(&firmware_lock, LK_EXCLUSIVE);
+       fp = lookup(imagename, NULL);
+       if (fp != NULL)
+               goto found;
+       /*
+        * Image not present, try to load the module holding it.
+        */
+       td = curthread;
+       if (priv_check(td, PRIV_FIRMWARE_LOAD) != 0 ||
+           priv_check_cred(td->td_ucred, 0, 0)) {
+               lockmgr(&firmware_lock, LK_RELEASE);
+               kprintf("%s: insufficient privileges to "
+                   "load firmware image %s\n", __func__, imagename);
+               return NULL;
+       }
+       /*
+        * Defer load to a thread with known context.  linker_reference_module
+        * may do filesystem i/o which requires root & current dirs, etc.
+        * Also we must not hold any lock's over this call which is problematic.
+        */
+       if (!cold) {
+               TASK_INIT(&fwload_task, 0, loadimage, __DECONST(void *,
+                   imagename));
+               taskqueue_enqueue(firmware_tq, &fwload_task);
+               lksleep(__DECONST(void *, imagename), &firmware_lock, 0,
+                   "fwload", 0);
+       }
+       /*
+        * After attempting to load the module, see if the image is registered.
+        */
+       fp = lookup(imagename, NULL);
+       if (fp == NULL) {
+               lockmgr(&firmware_lock, LK_RELEASE);
+               return NULL;
+       }
+found:                         /* common exit point on success */
+       fp->refcnt++;
+       lockmgr(&firmware_lock, LK_RELEASE);
+       return &fp->fw;
+}
+
+/*
+ * Release a reference to a firmware image returned by firmware_get.
+ * The caller may specify, with the FIRMWARE_UNLOAD flag, its desire
+ * to release the resource, but the flag is only advisory.
+ *
+ * If this is the last reference to the firmware image, and this is an
+ * autoloaded module, wake up the firmware_unload_task to figure out
+ * what to do with the associated module.
+ */
+void
+firmware_put(const struct firmware *p, int flags)
+{
+       struct priv_fw *fp = PRIV_FW(p);
+
+       lockmgr(&firmware_lock, LK_EXCLUSIVE);
+       fp->refcnt--;
+       if (fp->refcnt == 0) {
+               if (flags & FIRMWARE_UNLOAD)
+                       fp->flags |= FW_UNLOAD;
+               if (fp->file)
+                       taskqueue_enqueue(firmware_tq, &firmware_unload_task);
+       }
+       lockmgr(&firmware_lock, LK_RELEASE);
+}
+
+/*
+ * Setup directory state for the firmware_tq thread so we can do i/o.
+ */
+static void
+set_rootvnode(void *arg, int npending)
+{
+       struct thread *td = curthread;
+       struct proc *p = td->td_proc;
+
+
+       kprintf("JAT: arg = %p, p = %p\n", arg, p);
+#if 0
+       kprintf("JAT: arg = %p, p = %p, p_fd = %p, fd_spin = %p\n",
+               arg, p, p->p_fd, &p->p_fd->fd_spin);
+#endif
+
+#if 0
+       spin_lock_wr(&p->p_fd->fd_spin);
+       kprintf("JAT: after spin_lock_wr\n");
+       if (p->p_fd->fd_cdir == NULL) {
+               p->p_fd->fd_cdir = rootvnode;
+               vref(rootvnode);
+       }
+       if (p->p_fd->fd_rdir == NULL) {
+               p->p_fd->fd_rdir = rootvnode;
+               vref(rootvnode);
+       }
+       spin_unlock_wr(&p->p_fd->fd_spin);
+       kprintf("JAT: after spin_unlock_wr\n");
+
+       kfree(arg, M_TEMP);
+#endif
+}
+
+/*
+ * Event handler called on mounting of /; bounce a task
+ * into the task queue thread to setup it's directories.
+ */
+static void
+firmware_mountroot(void *arg)
+{
+       struct task *setroot_task;
+
+       setroot_task = kmalloc(sizeof(struct task), M_TEMP, M_NOWAIT);
+       if (setroot_task != NULL) {
+               TASK_INIT(setroot_task, 0, set_rootvnode, setroot_task);
+               taskqueue_enqueue(firmware_tq, setroot_task);
+       } else
+               kprintf("%s: no memory for task!\n", __func__);
+}
+#if 0
+EVENTHANDLER_DECLARE(mountroot, firmware_mountroot);
+#endif
+
+/*
+ * The body of the task in charge of unloading autoloaded modules
+ * that are not needed anymore.
+ * Images can be cross-linked so we may need to make multiple passes,
+ * but the time we spend in the loop is bounded because we clear entries
+ * as we touch them.
+ */
+static void
+unloadentry(void *unused1, int unused2)
+{
+       int limit = FIRMWARE_MAX;
+       int i;  /* current cycle */
+
+       lockmgr(&firmware_lock, LK_EXCLUSIVE);
+       /*
+        * Scan the table. limit is set to make sure we make another
+        * full sweep after matching an entry that requires unloading.
+        */
+       for (i = 0; i < limit; i++) {
+               struct priv_fw *fp;
+               int err;
+
+               fp = &firmware_table[i % FIRMWARE_MAX];
+               if (fp->fw.name == NULL || fp->file == NULL ||
+                   fp->refcnt != 0 || (fp->flags & FW_UNLOAD) == 0)
+                       continue;
+
+               /*
+                * Found an entry. Now:
+                * 1. bump up limit to make sure we make another full round;
+                * 2. clear FW_UNLOAD so we don't try this entry again.
+                * 3. release the lock while trying to unload the module.
+                * 'file' remains set so that the entry cannot be reused
+                * in the meantime (it also means that fp->file will
+                * not change while we release the lock).
+                */
+               limit = i + FIRMWARE_MAX;       /* make another full round */
+               fp->flags &= ~FW_UNLOAD;        /* do not try again */
+
+               lockmgr(&firmware_lock, LK_RELEASE);
+               err = linker_release_module(NULL, NULL, fp->file);
+               lockmgr(&firmware_lock, LK_EXCLUSIVE);
+
+               /*
+                * We rely on the module to call firmware_unregister()
+                * on unload to actually release the entry.
+                * If err = 0 we can drop our reference as the system
+                * accepted it. Otherwise unloading failed (e.g. the
+                * module itself gave an error) so our reference is
+                * still valid.
+                */
+               if (err == 0)
+                       fp->file = NULL;
+       }
+       lockmgr(&firmware_lock, LK_RELEASE);
+}
+
+/*
+ * Module glue.
+ */
+static int
+firmware_modevent(module_t mod, int type, void *unused)
+{
+       struct priv_fw *fp;
+       int i, err;
+
+       switch (type) {
+       case MOD_LOAD:
+               TASK_INIT(&firmware_unload_task, 0, unloadentry, NULL);
+               lockinit(&firmware_lock, "firmware table", 0, LK_CANRECURSE);
+               firmware_tq = taskqueue_create("taskqueue_firmware", M_WAITOK,
+                   taskqueue_thread_enqueue, &firmware_tq);
+               /* NB: use our own loop routine that sets up context */
+               (void) taskqueue_start_threads(&firmware_tq, 1, TDPRI_KERN_DAEMON,
+                   -1, "firmware taskq");
+               if (rootvnode != NULL) {
+                       /*
+                        * Root is already mounted so we won't get an event;
+                        * simulate one here.
+                        */
+#if 0
+                       firmware_mountroot(NULL);
+#endif
+               }
+               return 0;
+
+       case MOD_UNLOAD:
+               /* request all autoloaded modules to be released */
+               lockmgr(&firmware_lock, LK_EXCLUSIVE);
+               for (i = 0; i < FIRMWARE_MAX; i++) {
+                       fp = &firmware_table[i];
+                       fp->flags |= FW_UNLOAD;
+               }
+               lockmgr(&firmware_lock, LK_RELEASE);
+               taskqueue_enqueue(firmware_tq, &firmware_unload_task);
+               taskqueue_drain(firmware_tq, &firmware_unload_task);
+               err = 0;
+               for (i = 0; i < FIRMWARE_MAX; i++) {
+                       fp = &firmware_table[i];
+                       if (fp->fw.name != NULL) {
+                               kprintf("%s: image %p ref %d still active slot %d\n",
+                                       __func__, fp->fw.name,
+                                       fp->refcnt,  i);
+                               err = EINVAL;
+                       }
+               }
+               if (err == 0)
+                       taskqueue_free(firmware_tq);
+               return err;
+       }
+       return EINVAL;
+}
+
+static moduledata_t fbsd_firmware_mod = {
+       "fbsd_firmware",
+       firmware_modevent,
+       NULL
+};
+DECLARE_MODULE(fbsd_firmware, fbsd_firmware_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);
+MODULE_VERSION(fbsd_firmware, 1);
diff --git a/sys/sys/fbsd_firmware.h b/sys/sys/fbsd_firmware.h
new file mode 100644 (file)
index 0000000..47c84bb
--- /dev/null
@@ -0,0 +1,64 @@
+/*-
+ * Copyright (c) 2005, Sam Leffler <sam@errno.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice unmodified, this list of conditions, and the following
+ *    disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * $FreeBSD: src/sys/sys/firmware.h,v 1.4.10.1 2009/08/03 08:13:06 kensmith Exp $
+ */
+#ifndef _SYS_FIRMWARE_H_
+#define _SYS_FIRMWARE_H_
+/*
+ * Loadable firmware support.
+ *
+ * The firmware abstraction provides an interface for loading firmware
+ * images into the kernel and making them available to clients.
+ *
+ * Firmware images are usually embedded in kernel loadable modules that can
+ * be loaded on-demand or pre-loaded as desired.  Modules may contain
+ * one or more firmware images that are stored as opaque data arrays
+ * and registered with a unique string name. Clients request
+ * firmware by name, and are returned a struct firmware * below on success.
+ * The kernel keeps track of references to firmware images to allow/prevent
+ * module/data unload.
+ *
+ * When multiple images are stored in one module, the first image is
+ * treated as the master with the other images holding references
+ * to it.  This means that to unload the module each dependent/subimage
+ * must first have its references removed.
+ * In order for automatic loading to work, the master image must have
+ * the same name as the module it is embedded into.
+ */
+struct firmware {
+       const char      *name;          /* system-wide name */
+       const void      *data;          /* location of image */
+       size_t           datasize;      /* size of image in bytes */
+       unsigned int     version;       /* version of the image */
+};
+
+const struct firmware  *firmware_register(const char *,
+       const void *, size_t, unsigned int, const struct firmware *);
+int     firmware_unregister(const char *);
+const struct firmware *firmware_get(const char *);
+#define        FIRMWARE_UNLOAD         0x0001  /* unload if unreferenced */
+void            firmware_put(const struct firmware *, int);
+#endif /* _SYS_FIRMWARE_H_ */
index f2ffc9e..04ff770 100644 (file)
@@ -46,6 +46,8 @@
 #include <machine/elf.h>
 #endif
 
+#include <sys/module.h>
+
 #ifdef MALLOC_DECLARE
 MALLOC_DECLARE(M_LINKER);
 #endif
@@ -229,6 +231,11 @@ int linker_file_lookup_set(linker_file_t _file, const char *_name,
  */
 char *linker_search_path(const char *_filename);
 
+int linker_reference_module(const char *modname, struct mod_depend *verinfo,
+    linker_file_t *retult);
+int linker_release_module(const char *modname, struct mod_depend *verinfo,
+    linker_file_t lf);
+
 /*
  * DDB Helpers, tuned specifically for ddb/db_kld.c
  */
diff --git a/sys/tools/fw_stub.awk b/sys/tools/fw_stub.awk
new file mode 100644 (file)
index 0000000..a3ebdb4
--- /dev/null
@@ -0,0 +1,230 @@
+#!/usr/bin/awk -f
+
+#-
+# Copyright (c) 2006 Max Laier.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS `AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD: src/sys/tools/fw_stub.awk,v 1.6.10.2 2009/11/02 09:47:15 fjoe Exp $
+
+#
+# Script to generate module .c file from a list of firmware images
+#
+
+function usage ()
+{
+       print "usage: fw_stub <firmware:name>* [-l name] [-m modname] [-c outfile]";
+       exit 1;
+}
+
+#   These are just for convenience ...
+function printc(s)
+{
+       if (opt_c)
+               print s > ctmpfilename;
+       else
+               print s > "/dev/stdout";
+}
+
+BEGIN {
+
+#
+#   Process the command line.
+#
+
+num_files = 0;
+
+for (i = 1; i < ARGC; i++) {
+       if (ARGV[i] ~ /^-/) {
+               #
+               #   awk doesn't have getopt(), so we have to do it ourselves.
+               #   This is a bit clumsy, but it works.
+               #
+               for (j = 2; j <= length(ARGV[i]); j++) {
+                       o = substr(ARGV[i], j, 1);
+                       if (o == "c") {
+                               if (length(ARGV[i]) > j) {
+                                       opt_c = substr(ARGV[i], j + 1);
+                                       break;
+                               }
+                               else {
+                                       if (++i < ARGC)
+                                               opt_c = ARGV[i];
+                                       else
+                                               usage();
+                               }
+                       } else if (o == "m") {
+                               if (length(ARGV[i]) > j) {
+                                       opt_m = substr(ARGV[i], j + 1);
+                                       break;
+                               }
+                               else {
+                                       if (++i < ARGC)
+                                               opt_m = ARGV[i];
+                                       else
+                                               usage();
+                               }
+                       } else if (o == "l") {
+                               if (length(ARGV[i]) > j) {
+                                       opt_l = substr(ARGV[i], j + 1);
+                                       break;
+                               }
+                               else {
+                                       if (++i < ARGC)
+                                               opt_l = ARGV[i];
+                                       else
+                                               usage();
+                               }
+                       } else
+                               usage();
+               }
+       } else {
+               split(ARGV[i], curr, ":");
+               filenames[num_files] = curr[1];
+               if (length(curr[2]) > 0)
+                       shortnames[num_files] = curr[2];
+               else
+                       shortnames[num_files] = curr[1];
+               if (length(curr[3]) > 0)
+                       versions[num_files] = int(curr[3]);
+               else
+                       versions[num_files] = 0;
+               num_files++;
+       }
+}
+
+if (!num_files || !opt_m)
+       usage();
+
+cfilename = opt_c;
+ctmpfilename = cfilename ".tmp";
+modname = opt_m;
+gsub(/[-\.]/, "_", modname);
+
+printc("#include <sys/param.h>\
+#include <sys/errno.h>\
+#include <sys/kernel.h>\
+#include <sys/module.h>\
+#include <sys/linker.h>\
+#include <sys/fbsd_firmware.h>\
+#include <sys/systm.h>\n");
+
+if (opt_l) {
+       printc("static long " opt_l "_license_ack = 0;");
+}
+
+for (file_i = 0; file_i < num_files; file_i++) {
+       symb = filenames[file_i];
+       # '-', '.' and '/' are converted to '_' by ld/objcopy
+       gsub(/-|\.|\//, "_", symb);
+       printc("extern char _binary_" symb "_start[], _binary_" symb "_end[];");
+}
+
+printc("\nstatic int\n"\
+modname "_fw_modevent(module_t mod, int type, void *unused)\
+{\
+       const struct firmware *fp, *parent;\
+       int error;\
+       switch (type) {\
+       case MOD_LOAD:\n");
+
+if (opt_l) {
+               printc("\
+               TUNABLE_LONG_FETCH(\"legal." opt_l ".license_ack\", &" opt_l "_license_ack);\
+               if (!" opt_l "_license_ack) {\
+                       printf(\"" opt_m ": You need to read the LICENSE file in /usr/share/doc/legal/" opt_l "/.\\n\");\
+                       printf(\"" opt_m ": If you agree with the license, set legal." opt_l ".license_ack=1 in /boot/loader.conf.\\n\");\
+                       return(EPERM);\
+               }\n");
+}
+
+for (file_i = 0; file_i < num_files; file_i++) {
+       short = shortnames[file_i];
+       symb = filenames[file_i];
+       version = versions[file_i];
+       # '-', '.' and '/' are converted to '_' by ld/objcopy
+       gsub(/-|\.|\//, "_", symb);
+
+       reg = "\t\tfp = ";
+       reg = reg "firmware_register(\"" short "\", _binary_" symb "_start , ";
+       reg = reg "(size_t)(_binary_" symb "_end - _binary_" symb "_start), ";
+       reg = reg version ", ";
+
+       if (file_i == 0)
+               reg = reg "NULL);";
+       else
+               reg = reg "parent);";
+
+       printc(reg);
+
+       printc("\t\tif (fp == NULL)");
+       printc("\t\t\tgoto fail_" file_i ";");
+       if (file_i == 0)
+               printc("\t\tparent = fp;");
+}
+
+printc("\t\treturn (0);");
+
+for (file_i = num_files - 1; file_i > 0; file_i--) {
+       printc("fail_" file_i ":")
+       printc("\t\t(void)firmware_unregister(\"" shortnames[file_i - 1] "\");");
+}
+
+printc("\tfail_0:");
+printc("\t\treturn (ENXIO);");
+
+printc("\tcase MOD_UNLOAD:");
+
+for (file_i = 1; file_i < num_files; file_i++) {
+       printc("\t\terror = firmware_unregister(\"" shortnames[file_i] "\");");
+       printc("\t\tif (error)");
+       printc("\t\t\treturn (error);");
+}
+
+printc("\t\terror = firmware_unregister(\"" shortnames[0] "\");");
+
+printc("\t\treturn (error);\
+       }\
+       return (EINVAL);\
+}\
+\
+static moduledata_t " modname "_fw_mod = {\
+        \"" modname "_fw\",\
+        " modname "_fw_modevent,\
+        0\
+};\
+DECLARE_MODULE(" modname "_fw, " modname "_fw_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);\
+MODULE_VERSION(" modname "_fw, 1);\
+MODULE_DEPEND(" modname "_fw, firmware, 1, 1, 1);\
+");
+
+if (opt_c)
+       if ((rc = system("mv -f " ctmpfilename " " cfilename))) {
+               print "'mv -f " ctmpfilename " " cfilename "' failed: " rc \
+                   > "/dev/stderr";
+               exit 1;
+       }
+
+exit 0;
+
+}