diff options
-rw-r--r-- | sys/kern/subr_firmware.c | 54 | ||||
-rw-r--r-- | sys/sys/firmware.h | 4 |
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 *); |