1 /*- 2 * Copyright (c) 2010,2013 Lawrence Stewart <lstewart@freebsd.org> 3 * Copyright (c) 2010 The FreeBSD Foundation 4 * All rights reserved. 5 * 6 * This software was developed by Lawrence Stewart while studying at the Centre 7 * for Advanced Internet Architectures, Swinburne University of Technology, 8 * made possible in part by grants from the FreeBSD Foundation and Cisco 9 * University Research Program Fund at Community Foundation Silicon Valley. 10 * 11 * Portions of this software were developed at the Centre for Advanced 12 * Internet Architectures, Swinburne University of Technology, Melbourne, 13 * Australia by Lawrence Stewart under sponsorship from the FreeBSD Foundation. 14 * 15 * Redistribution and use in source and binary forms, with or without 16 * modification, are permitted provided that the following conditions 17 * are met: 18 * 1. Redistributions of source code must retain the above copyright 19 * notice, this list of conditions and the following disclaimer. 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in the 22 * documentation and/or other materials provided with the distribution. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #include <sys/cdefs.h> 38 __FBSDID("$FreeBSD$"); 39 40 #include <sys/param.h> 41 #include <sys/kernel.h> 42 #include <sys/hhook.h> 43 #include <sys/khelp.h> 44 #include <sys/lock.h> 45 #include <sys/malloc.h> 46 #include <sys/module.h> 47 #include <sys/module_khelp.h> 48 #include <sys/osd.h> 49 #include <sys/queue.h> 50 #include <sys/refcount.h> 51 #include <sys/rwlock.h> 52 #include <sys/systm.h> 53 54 static struct rwlock khelp_list_lock; 55 RW_SYSINIT(khelplistlock, &khelp_list_lock, "helper list lock"); 56 57 static TAILQ_HEAD(helper_head, helper) helpers = TAILQ_HEAD_INITIALIZER(helpers); 58 59 /* Private function prototypes. */ 60 static inline void khelp_remove_osd(struct helper *h, struct osd *hosd); 61 void khelp_new_hhook_registered(struct hhook_head *hhh, uint32_t flags); 62 63 #define KHELP_LIST_WLOCK() rw_wlock(&khelp_list_lock) 64 #define KHELP_LIST_WUNLOCK() rw_wunlock(&khelp_list_lock) 65 #define KHELP_LIST_RLOCK() rw_rlock(&khelp_list_lock) 66 #define KHELP_LIST_RUNLOCK() rw_runlock(&khelp_list_lock) 67 #define KHELP_LIST_LOCK_ASSERT() rw_assert(&khelp_list_lock, RA_LOCKED) 68 69 int 70 khelp_register_helper(struct helper *h) 71 { 72 struct helper *tmph; 73 int error, i, inserted; 74 75 error = inserted = 0; 76 refcount_init(&h->h_refcount, 0); 77 h->h_id = osd_register(OSD_KHELP, NULL, NULL); 78 79 /* It's only safe to add the hooks after osd_register(). */ 80 for (i = 0; i < h->h_nhooks && !error; i++) { 81 /* We don't require the module to assign hook_helper. */ 82 h->h_hooks[i].hook_helper = h; 83 error = hhook_add_hook_lookup(&h->h_hooks[i], HHOOK_WAITOK); 84 if (error) 85 printf("%s: \"%s\" khelp module unable to " 86 "hook type %d id %d due to error %d\n", __func__, 87 h->h_name, h->h_hooks[i].hook_type, 88 h->h_hooks[i].hook_id, error); 89 } 90 91 if (error) { 92 for (i--; i >= 0; i--) 93 hhook_remove_hook_lookup(&h->h_hooks[i]); 94 osd_deregister(OSD_KHELP, h->h_id); 95 } else { 96 KHELP_LIST_WLOCK(); 97 /* 98 * Keep list of helpers sorted in descending h_id order. Due to 99 * the way osd_set() works, a sorted list ensures 100 * khelp_init_osd() will operate with improved efficiency. 101 */ 102 TAILQ_FOREACH(tmph, &helpers, h_next) { 103 if (tmph->h_id < h->h_id) { 104 TAILQ_INSERT_BEFORE(tmph, h, h_next); 105 inserted = 1; 106 break; 107 } 108 } 109 110 if (!inserted) 111 TAILQ_INSERT_TAIL(&helpers, h, h_next); 112 KHELP_LIST_WUNLOCK(); 113 } 114 115 return (error); 116 } 117 118 int 119 khelp_deregister_helper(struct helper *h) 120 { 121 struct helper *tmph; 122 int error, i; 123 124 KHELP_LIST_WLOCK(); 125 if (h->h_refcount > 0) 126 error = EBUSY; 127 else { 128 error = ENOENT; 129 TAILQ_FOREACH(tmph, &helpers, h_next) { 130 if (tmph == h) { 131 TAILQ_REMOVE(&helpers, h, h_next); 132 error = 0; 133 break; 134 } 135 } 136 } 137 KHELP_LIST_WUNLOCK(); 138 139 if (!error) { 140 for (i = 0; i < h->h_nhooks; i++) 141 hhook_remove_hook_lookup(&h->h_hooks[i]); 142 osd_deregister(OSD_KHELP, h->h_id); 143 } 144 145 return (error); 146 } 147 148 int 149 khelp_init_osd(uint32_t classes, struct osd *hosd) 150 { 151 struct helper *h; 152 void *hdata; 153 int error; 154 155 KASSERT(hosd != NULL, ("struct osd not initialised!")); 156 157 error = 0; 158 159 KHELP_LIST_RLOCK(); 160 TAILQ_FOREACH(h, &helpers, h_next) { 161 /* If helper is correct class and needs to store OSD... */ 162 if (h->h_classes & classes && h->h_flags & HELPER_NEEDS_OSD) { 163 hdata = uma_zalloc(h->h_zone, M_NOWAIT); 164 if (hdata == NULL) { 165 error = ENOMEM; 166 break; 167 } 168 osd_set(OSD_KHELP, hosd, h->h_id, hdata); 169 refcount_acquire(&h->h_refcount); 170 } 171 } 172 173 if (error) { 174 /* Delete OSD that was assigned prior to the error. */ 175 TAILQ_FOREACH(h, &helpers, h_next) { 176 if (h->h_classes & classes) 177 khelp_remove_osd(h, hosd); 178 } 179 } 180 KHELP_LIST_RUNLOCK(); 181 182 return (error); 183 } 184 185 int 186 khelp_destroy_osd(struct osd *hosd) 187 { 188 struct helper *h; 189 int error; 190 191 KASSERT(hosd != NULL, ("struct osd not initialised!")); 192 193 error = 0; 194 195 KHELP_LIST_RLOCK(); 196 /* 197 * Clean up all khelp related OSD. 198 * 199 * XXXLAS: Would be nice to use something like osd_exit() here but it 200 * doesn't have the right semantics for this purpose. 201 */ 202 TAILQ_FOREACH(h, &helpers, h_next) 203 khelp_remove_osd(h, hosd); 204 KHELP_LIST_RUNLOCK(); 205 206 return (error); 207 } 208 209 static inline void 210 khelp_remove_osd(struct helper *h, struct osd *hosd) 211 { 212 void *hdata; 213 214 if (h->h_flags & HELPER_NEEDS_OSD) { 215 /* 216 * If the current helper uses OSD and calling osd_get() 217 * on the helper's h_id returns non-NULL, the helper has 218 * OSD attached to 'hosd' which needs to be cleaned up. 219 */ 220 hdata = osd_get(OSD_KHELP, hosd, h->h_id); 221 if (hdata != NULL) { 222 uma_zfree(h->h_zone, hdata); 223 osd_del(OSD_KHELP, hosd, h->h_id); 224 refcount_release(&h->h_refcount); 225 } 226 } 227 } 228 229 void * 230 khelp_get_osd(struct osd *hosd, int32_t id) 231 { 232 233 return (osd_get(OSD_KHELP, hosd, id)); 234 } 235 236 int32_t 237 khelp_get_id(char *hname) 238 { 239 struct helper *h; 240 int32_t id; 241 242 id = -1; 243 244 KHELP_LIST_RLOCK(); 245 TAILQ_FOREACH(h, &helpers, h_next) { 246 if (strncmp(h->h_name, hname, HELPER_NAME_MAXLEN) == 0) { 247 id = h->h_id; 248 break; 249 } 250 } 251 KHELP_LIST_RUNLOCK(); 252 253 return (id); 254 } 255 256 int 257 khelp_add_hhook(struct hookinfo *hki, uint32_t flags) 258 { 259 int error; 260 261 /* 262 * XXXLAS: Should probably include the functionality to update the 263 * helper's h_hooks struct member. 264 */ 265 error = hhook_add_hook_lookup(hki, flags); 266 267 return (error); 268 } 269 270 int 271 khelp_remove_hhook(struct hookinfo *hki) 272 { 273 int error; 274 275 /* 276 * XXXLAS: Should probably include the functionality to update the 277 * helper's h_hooks struct member. 278 */ 279 error = hhook_remove_hook_lookup(hki); 280 281 return (error); 282 } 283 284 /* 285 * Private KPI between hhook and khelp that allows khelp modules to insert hook 286 * functions into hhook points which register after the modules were loaded. 287 */ 288 void 289 khelp_new_hhook_registered(struct hhook_head *hhh, uint32_t flags) 290 { 291 struct helper *h; 292 int error, i; 293 294 KHELP_LIST_RLOCK(); 295 TAILQ_FOREACH(h, &helpers, h_next) { 296 for (i = 0; i < h->h_nhooks; i++) { 297 if (hhh->hhh_type != h->h_hooks[i].hook_type || 298 hhh->hhh_id != h->h_hooks[i].hook_id) 299 continue; 300 error = hhook_add_hook(hhh, &h->h_hooks[i], flags); 301 if (error) { 302 printf("%s: \"%s\" khelp module unable to " 303 "hook type %d id %d due to error %d\n", 304 __func__, h->h_name, 305 h->h_hooks[i].hook_type, 306 h->h_hooks[i].hook_id, error); 307 error = 0; 308 } 309 } 310 } 311 KHELP_LIST_RUNLOCK(); 312 } 313 314 int 315 khelp_modevent(module_t mod, int event_type, void *data) 316 { 317 struct khelp_modevent_data *kmd; 318 int error; 319 320 kmd = (struct khelp_modevent_data *)data; 321 error = 0; 322 323 switch(event_type) { 324 case MOD_LOAD: 325 if (kmd->helper->h_flags & HELPER_NEEDS_OSD) { 326 if (kmd->uma_zsize <= 0) { 327 printf("Use KHELP_DECLARE_MOD_UMA() instead!\n"); 328 error = EDOOFUS; 329 break; 330 } 331 kmd->helper->h_zone = uma_zcreate(kmd->name, 332 kmd->uma_zsize, kmd->umactor, kmd->umadtor, NULL, 333 NULL, 0, 0); 334 if (kmd->helper->h_zone == NULL) { 335 error = ENOMEM; 336 break; 337 } 338 } 339 strlcpy(kmd->helper->h_name, kmd->name, HELPER_NAME_MAXLEN); 340 kmd->helper->h_hooks = kmd->hooks; 341 kmd->helper->h_nhooks = kmd->nhooks; 342 if (kmd->helper->mod_init != NULL) 343 error = kmd->helper->mod_init(); 344 if (!error) 345 error = khelp_register_helper(kmd->helper); 346 break; 347 348 case MOD_QUIESCE: 349 case MOD_SHUTDOWN: 350 case MOD_UNLOAD: 351 error = khelp_deregister_helper(kmd->helper); 352 if (!error) { 353 if (kmd->helper->h_flags & HELPER_NEEDS_OSD) 354 uma_zdestroy(kmd->helper->h_zone); 355 if (kmd->helper->mod_destroy != NULL) 356 kmd->helper->mod_destroy(); 357 } else if (error == ENOENT) 358 /* Do nothing and allow unload if helper not in list. */ 359 error = 0; 360 else if (error == EBUSY) 361 printf("Khelp module \"%s\" can't unload until its " 362 "refcount drops from %d to 0.\n", kmd->name, 363 kmd->helper->h_refcount); 364 break; 365 366 default: 367 error = EINVAL; 368 break; 369 } 370 371 return (error); 372 } 373