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: releng/11.0/sys/kern/kern_khelp.c 251778 2013-06-15 06:45:17Z lstewart $"); 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 54static struct rwlock khelp_list_lock; 55RW_SYSINIT(khelplistlock, &khelp_list_lock, "helper list lock"); 56 57static TAILQ_HEAD(helper_head, helper) helpers = TAILQ_HEAD_INITIALIZER(helpers); 58 59/* Private function prototypes. */ 60static inline void khelp_remove_osd(struct helper *h, struct osd *hosd); 61void 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 69int 70khelp_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 118int 119khelp_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 148int 149khelp_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 185int 186khelp_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 209static inline void 210khelp_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 229void * 230khelp_get_osd(struct osd *hosd, int32_t id) 231{ 232 233 return (osd_get(OSD_KHELP, hosd, id)); 234} 235 236int32_t 237khelp_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 256int 257khelp_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 270int 271khelp_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 */ 288void 289khelp_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 314int 315khelp_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