subr_firmware.c revision 159589
1/*- 2 * Copyright (c) 2005, Sam Leffler <sam@errno.com> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice unmodified, this list of conditions, and the following 10 * disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: head/sys/kern/subr_firmware.c 159589 2006-06-13 21:34:12Z jhb $"); 29 30#include <sys/param.h> 31#include <sys/kernel.h> 32#include <sys/malloc.h> 33#include <sys/queue.h> 34#include <sys/taskqueue.h> 35#include <sys/systm.h> 36#include <sys/lock.h> 37#include <sys/mutex.h> 38#include <sys/errno.h> 39#include <sys/linker.h> 40#include <sys/firmware.h> 41#include <sys/proc.h> 42#include <sys/module.h> 43 44#define FIRMWARE_MAX 30 45static struct firmware firmware_table[FIRMWARE_MAX]; 46struct task firmware_task; 47struct mtx firmware_mtx; 48MTX_SYSINIT(firmware, &firmware_mtx, "firmware table", MTX_DEF); 49 50/* 51 * Register a firmware image with the specified name. The 52 * image name must not already be registered. If this is a 53 * subimage then parent refers to a previously registered 54 * image that this should be associated with. 55 */ 56struct firmware * 57firmware_register(const char *imagename, const void *data, size_t datasize, 58 unsigned int version, struct firmware *parent) 59{ 60 struct firmware *frp = NULL; 61 int i; 62 63 mtx_lock(&firmware_mtx); 64 for (i = 0; i < FIRMWARE_MAX; i++) { 65 struct firmware *fp = &firmware_table[i]; 66 67 if (fp->name == NULL) { 68 if (frp == NULL) 69 frp = fp; 70 continue; 71 } 72 if (strcasecmp(imagename, fp->name) == 0) { 73 mtx_unlock(&firmware_mtx); 74 printf("%s: image %s already registered!\n", 75 __func__, imagename); 76 return NULL; 77 } 78 } 79 if (frp == NULL) { 80 mtx_unlock(&firmware_mtx); 81 printf("%s: cannot register image %s, firmware table full!\n", 82 __func__, imagename); 83 return NULL; 84 } 85 frp->name = imagename; 86 frp->data = data; 87 frp->datasize = datasize; 88 frp->version = version; 89 frp->refcnt = 0; 90 frp->flags = 0; 91 if (parent != NULL) 92 parent->refcnt++; 93 frp->parent = parent; 94 frp->file = NULL; 95 mtx_unlock(&firmware_mtx); 96 return frp; 97} 98 99static void 100clearentry(struct firmware *fp) 101{ 102 KASSERT(fp->refcnt == 0, ("image %s refcnt %u", fp->name, fp->refcnt)); 103 fp->name = NULL; 104 fp->file = NULL; 105 fp->data = NULL; 106 fp->datasize = 0; 107 fp->version = 0; 108 fp->flags = 0; 109 if (fp->parent != NULL) { /* release parent reference */ 110 fp->parent->refcnt--; 111 fp->parent = NULL; 112 } 113} 114 115static struct firmware * 116lookup(const char *name) 117{ 118 struct firmware *fp; 119 int i; 120 121 for (i = 0; i < FIRMWARE_MAX; i++) { 122 fp = &firmware_table[i]; 123 if (fp->name != NULL && strcasecmp(name, fp->name) == 0) 124 return fp; 125 } 126 return NULL; 127} 128 129/* 130 * Unregister/remove a firmware image. If there are outstanding 131 * references an error is returned and the image is not removed 132 * from the registry. 133 */ 134int 135firmware_unregister(const char *imagename) 136{ 137 struct firmware *fp; 138 int refcnt = 0; 139 140 mtx_lock(&firmware_mtx); 141 /* 142 * NB: it is ok for the lookup to fail; this can happen 143 * when a module is unloaded on last reference and the 144 * module unload handler unregister's each of it's 145 * firmware images. 146 */ 147 fp = lookup(imagename); 148 if (fp != NULL) { 149 refcnt = fp->refcnt; 150 if (refcnt == 0) 151 clearentry(fp); 152 } 153 mtx_unlock(&firmware_mtx); 154 return (refcnt != 0 ? EBUSY : 0); 155} 156 157/* 158 * Lookup and potentially load the specified firmware image. 159 * If the firmware is not found in the registry attempt to 160 * load a kernel module with the image name. If the firmware 161 * is located a reference is returned. The caller must release 162 * this reference for the image to be eligible for removal/unload. 163 */ 164struct firmware * 165firmware_get(const char *imagename) 166{ 167 struct thread *td; 168 struct firmware *fp; 169 linker_file_t result; 170 int requested_load = 0; 171 172again: 173 mtx_lock(&firmware_mtx); 174 fp = lookup(imagename); 175 if (fp != NULL) { 176 if (requested_load) 177 fp->file = result; 178 fp->refcnt++; 179 mtx_unlock(&firmware_mtx); 180 return fp; 181 } 182 /* 183 * Image not present, try to load the module holding it 184 * or if we already tried give up. 185 */ 186 mtx_unlock(&firmware_mtx); 187 if (requested_load) { 188 printf("%s: failed to load firmware image %s\n", 189 __func__, imagename); 190 return NULL; 191 } 192 td = curthread; 193 if (suser(td) != 0 || securelevel_gt(td->td_ucred, 0) != 0) { 194 printf("%s: insufficient privileges to " 195 "load firmware image %s\n", __func__, imagename); 196 return NULL; 197 } 198 mtx_lock(&Giant); /* XXX */ 199 (void) linker_reference_module(imagename, NULL, &result); 200 mtx_unlock(&Giant); /* XXX */ 201 requested_load = 1; 202 goto again; /* sort of an Algol-style for loop */ 203} 204 205static void 206unloadentry(void *unused1, int unused2) 207{ 208 struct firmware *fp; 209 linker_file_t file; 210 int i; 211 212 mtx_lock(&firmware_mtx); 213 for (;;) { 214 /* Look for an unwanted entry that we explicitly loaded. */ 215 for (i = 0; i < FIRMWARE_MAX; i++) { 216 fp = &firmware_table[i]; 217 if (fp->name != NULL && fp->file != NULL && 218 fp->refcnt == 0 && 219 (fp->flags & FIRMWAREFLAG_KEEPKLDREF) == 0) 220 break; 221 fp = NULL; 222 } 223 if (fp == NULL) 224 break; 225 file = fp->file; 226 /* No longer explicitly loaded. */ 227 fp->file = NULL; 228 mtx_unlock(&firmware_mtx); 229 230 linker_file_unload(file, LINKER_UNLOAD_NORMAL); 231 232 mtx_lock(&firmware_mtx); 233 } 234 mtx_unlock(&firmware_mtx); 235} 236 237/* 238 * Release a reference to a firmware image returned by 239 * firmware_get. The reference is released and if this is 240 * the last reference to the firmware image the associated 241 * module may be released/unloaded. 242 */ 243void 244firmware_put(struct firmware *fp, int flags) 245{ 246 mtx_lock(&firmware_mtx); 247 fp->refcnt--; 248 if (fp->refcnt == 0) { 249 if ((flags & FIRMWARE_UNLOAD) == 0) 250 fp->flags |= FIRMWAREFLAG_KEEPKLDREF; 251 } 252 if (fp->file) 253 taskqueue_enqueue(taskqueue_thread, &firmware_task); 254 mtx_unlock(&firmware_mtx); 255} 256 257/* 258 * Module glue. 259 */ 260static int 261firmware_modevent(module_t mod, int type, void *unused) 262{ 263 struct firmware *fp; 264 int i; 265 266 switch (type) { 267 case MOD_LOAD: 268 TASK_INIT(&firmware_task, 0, unloadentry, NULL); 269 return 0; 270 case MOD_UNLOAD: 271 for (i = 0; i < FIRMWARE_MAX; i++) { 272 fp = &firmware_table[i]; 273 fp->flags &= ~FIRMWAREFLAG_KEEPKLDREF; 274 } 275 taskqueue_enqueue(taskqueue_thread, &firmware_task); 276 taskqueue_drain(taskqueue_thread, &firmware_task); 277 return 0; 278 } 279 return EINVAL; 280} 281 282static moduledata_t firmware_mod = { 283 "firmware", 284 firmware_modevent, 285 0 286}; 287DECLARE_MODULE(firmware, firmware_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST); 288MODULE_VERSION(firmware, 1); 289