aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/kern/subr_firmware.c54
-rw-r--r--sys/sys/firmware.h4
2 files changed, 39 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;
}
diff --git a/sys/sys/firmware.h b/sys/sys/firmware.h
index 4d84942b024f..30790b742f3e 100644
--- a/sys/sys/firmware.h
+++ b/sys/sys/firmware.h
@@ -48,10 +48,14 @@ struct firmware {
size_t datasize; /* size of image in bytes */
unsigned int version; /* version of the image */
int refcnt; /* held references */
+ int flags; /* FIRMWAREFLAG_ flags */
struct firmware *parent; /* not null if a subimage */
linker_file_t file; /* loadable module */
};
+/* "flags" field definitions */
+#define FIRMWAREFLAG_KEEPKLDREF 0x0001 /* don't release KLD reference */
+
struct firmware *firmware_register(const char *, const void *, size_t,
unsigned int, struct firmware *);
int firmware_unregister(const char *);