aboutsummaryrefslogtreecommitdiff
path: root/sys/kern/subr_firmware.c
diff options
context:
space:
mode:
authorIan Dowse <iedowse@FreeBSD.org>2006-06-10 17:04:07 +0000
committerIan Dowse <iedowse@FreeBSD.org>2006-06-10 17:04:07 +0000
commiteb1030c4fd5dbda71009b4345e83c7324a6b8875 (patch)
treeb25106c52f7980c62f687d776341cc8b42829cd4 /sys/kern/subr_firmware.c
parent11cff99d3ee6c24730e843bf7f1de430cfba7042 (diff)
downloadsrc-eb1030c4fd5dbda71009b4345e83c7324a6b8875.tar.gz
src-eb1030c4fd5dbda71009b4345e83c7324a6b8875.zip
Keep firmware images on the list until they have been unregistered
with firmware_unregister(). Previously when the last driver reference had been dropped we would clear the list entry under the assumption that the firmware module was about to be unloaded, but this was not true if the firmware image had been loaded manually with kldload. This makes it possible to manually kldload firmware images as a workaround for drivers such as ipw that attempt to load firmware while resuming after a suspend. Reviewed by: mlaier (an earlier version of the patch)
Notes
Notes: svn path=/head/; revision=159486
Diffstat (limited to 'sys/kern/subr_firmware.c')
-rw-r--r--sys/kern/subr_firmware.c54
1 files changed, 35 insertions, 19 deletions
diff --git a/sys/kern/subr_firmware.c b/sys/kern/subr_firmware.c
index 43ed1aee479a..717f665f2f54 100644
--- a/sys/kern/subr_firmware.c
+++ b/sys/kern/subr_firmware.c
@@ -42,7 +42,6 @@ __FBSDID("$FreeBSD$");
#include <sys/module.h>
#define FIRMWARE_MAX 30
-static char *name_unload = "UNLOADING";
static struct firmware firmware_table[FIRMWARE_MAX];
struct task firmware_task;
struct mtx firmware_mtx;
@@ -88,6 +87,7 @@ firmware_register(const char *imagename, const void *data, size_t datasize,
frp->datasize = datasize;
frp->version = version;
frp->refcnt = 0;
+ frp->flags = 0;
if (parent != NULL)
parent->refcnt++;
frp->parent = parent;
@@ -97,18 +97,15 @@ firmware_register(const char *imagename, const void *data, size_t datasize,
}
static void
-clearentry(struct firmware *fp, int keep_file)
+clearentry(struct firmware *fp)
{
KASSERT(fp->refcnt == 0, ("image %s refcnt %u", fp->name, fp->refcnt));
- if (keep_file && (fp->file != NULL))
- fp->name = name_unload;
- else {
- fp->name = NULL;
- fp->file = NULL;
- }
+ fp->name = NULL;
+ fp->file = NULL;
fp->data = NULL;
fp->datasize = 0;
fp->version = 0;
+ fp->flags = 0;
if (fp->parent != NULL) { /* release parent reference */
fp->parent->refcnt--;
fp->parent = NULL;
@@ -150,7 +147,7 @@ firmware_unregister(const char *imagename)
if (fp != NULL) {
refcnt = fp->refcnt;
if (refcnt == 0)
- clearentry(fp, 0);
+ clearentry(fp);
}
mtx_unlock(&firmware_mtx);
return (refcnt != 0 ? EBUSY : 0);
@@ -208,20 +205,30 @@ static void
unloadentry(void *unused1, int unused2)
{
struct firmware *fp;
+ linker_file_t file;
+ int i;
mtx_lock(&firmware_mtx);
- while ((fp = lookup(name_unload))) {
- /*
- * XXX: ugly, we should be able to lookup unlocked here if
- * we properly lock around clearentry below to avoid double
- * unload. Play it safe for now.
- */
+ for (;;) {
+ /* Look for an unwanted entry that we explicitly loaded. */
+ for (i = 0; i < FIRMWARE_MAX; i++) {
+ fp = &firmware_table[i];
+ if (fp->name != NULL && fp->file != NULL &&
+ fp->refcnt == 0 &&
+ (fp->flags & FIRMWAREFLAG_KEEPKLDREF) == 0)
+ break;
+ fp = NULL;
+ }
+ if (fp == NULL)
+ break;
+ file = fp->file;
+ /* No longer explicitly loaded. */
+ fp->file = NULL;
mtx_unlock(&firmware_mtx);
- linker_file_unload(fp->file, LINKER_UNLOAD_NORMAL);
+ linker_file_unload(file, LINKER_UNLOAD_NORMAL);
mtx_lock(&firmware_mtx);
- clearentry(fp, 0);
}
mtx_unlock(&firmware_mtx);
}
@@ -237,8 +244,10 @@ firmware_put(struct firmware *fp, int flags)
{
mtx_lock(&firmware_mtx);
fp->refcnt--;
- if (fp->refcnt == 0 && (flags & FIRMWARE_UNLOAD))
- clearentry(fp, 1);
+ if (fp->refcnt == 0) {
+ if ((flags & FIRMWARE_UNLOAD) == 0)
+ fp->flags |= FIRMWAREFLAG_KEEPKLDREF;
+ }
if (fp->file)
taskqueue_enqueue(taskqueue_thread, &firmware_task);
mtx_unlock(&firmware_mtx);
@@ -250,11 +259,18 @@ firmware_put(struct firmware *fp, int flags)
static int
firmware_modevent(module_t mod, int type, void *unused)
{
+ int i;
+
switch (type) {
case MOD_LOAD:
TASK_INIT(&firmware_task, 0, unloadentry, NULL);
return 0;
case MOD_UNLOAD:
+ for (i = 0; i < FIRMWARE_MAX; i++) {
+ struct firmware *fp = &firmware_table[i];
+ fp->flags &= ~FIRMWAREFLAG_KEEPKLDREF;
+ }
+ taskqueue_enqueue(taskqueue_thread, &firmware_task);
taskqueue_drain(taskqueue_thread, &firmware_task);
return 0;
}