kern_hook.c revision 1.13 1 /* $NetBSD: kern_hook.c,v 1.13 2022/05/31 08:43:16 andvar Exp $ */
2
3 /*-
4 * Copyright (c) 1997, 1998, 1999, 2002, 2007, 2008 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
9 * NASA Ames Research Center, and by Luke Mewburn.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: kern_hook.c,v 1.13 2022/05/31 08:43:16 andvar Exp $");
35
36 #include <sys/param.h>
37 #include <sys/condvar.h>
38 #include <sys/cpu.h>
39 #include <sys/device.h>
40 #include <sys/hook.h>
41 #include <sys/kmem.h>
42 #include <sys/malloc.h>
43 #include <sys/rwlock.h>
44 #include <sys/systm.h>
45
46 /*
47 * A generic linear hook.
48 */
49 struct hook_desc {
50 LIST_ENTRY(hook_desc) hk_list;
51 void (*hk_fn)(void *);
52 void *hk_arg;
53 };
54 typedef LIST_HEAD(, hook_desc) hook_list_t;
55
56 enum hook_list_st {
57 HKLIST_IDLE,
58 HKLIST_INUSE,
59 };
60
61 struct khook_list {
62 hook_list_t hl_list;
63 kmutex_t hl_lock;
64 kmutex_t *hl_cvlock;
65 struct lwp *hl_lwp;
66 kcondvar_t hl_cv;
67 enum hook_list_st
68 hl_state;
69 khook_t *hl_active_hk;
70 char hl_namebuf[HOOKNAMSIZ];
71 };
72
73 int powerhook_debug = 0;
74
75 static void *
76 hook_establish(hook_list_t *list, void (*fn)(void *), void *arg)
77 {
78 struct hook_desc *hd;
79
80 hd = malloc(sizeof(*hd), M_DEVBUF, M_NOWAIT);
81 if (hd == NULL)
82 return (NULL);
83
84 hd->hk_fn = fn;
85 hd->hk_arg = arg;
86 LIST_INSERT_HEAD(list, hd, hk_list);
87
88 return (hd);
89 }
90
91 static void
92 hook_disestablish(hook_list_t *list, void *vhook)
93 {
94 #ifdef DIAGNOSTIC
95 struct hook_desc *hd;
96
97 LIST_FOREACH(hd, list, hk_list) {
98 if (hd == vhook)
99 break;
100 }
101
102 if (hd == NULL)
103 panic("hook_disestablish: hook %p not established", vhook);
104 #endif
105 LIST_REMOVE((struct hook_desc *)vhook, hk_list);
106 free(vhook, M_DEVBUF);
107 }
108
109 static void
110 hook_destroy(hook_list_t *list)
111 {
112 struct hook_desc *hd;
113
114 while ((hd = LIST_FIRST(list)) != NULL) {
115 LIST_REMOVE(hd, hk_list);
116 free(hd, M_DEVBUF);
117 }
118 }
119
120 static void
121 hook_proc_run(hook_list_t *list, struct proc *p)
122 {
123 struct hook_desc *hd;
124
125 LIST_FOREACH(hd, list, hk_list) {
126 __FPTRCAST(void (*)(struct proc *, void *), *hd->hk_fn)(p,
127 hd->hk_arg);
128 }
129 }
130
131 /*
132 * "Shutdown hook" types, functions, and variables.
133 *
134 * Should be invoked immediately before the
135 * system is halted or rebooted, i.e. after file systems unmounted,
136 * after crash dump done, etc.
137 *
138 * Each shutdown hook is removed from the list before it's run, so that
139 * it won't be run again.
140 */
141
142 static hook_list_t shutdownhook_list = LIST_HEAD_INITIALIZER(shutdownhook_list);
143
144 void *
145 shutdownhook_establish(void (*fn)(void *), void *arg)
146 {
147 return hook_establish(&shutdownhook_list, fn, arg);
148 }
149
150 void
151 shutdownhook_disestablish(void *vhook)
152 {
153 hook_disestablish(&shutdownhook_list, vhook);
154 }
155
156 /*
157 * Run shutdown hooks. Should be invoked immediately before the
158 * system is halted or rebooted, i.e. after file systems unmounted,
159 * after crash dump done, etc.
160 *
161 * Each shutdown hook is removed from the list before it's run, so that
162 * it won't be run again.
163 */
164 void
165 doshutdownhooks(void)
166 {
167 struct hook_desc *dp;
168
169 while ((dp = LIST_FIRST(&shutdownhook_list)) != NULL) {
170 LIST_REMOVE(dp, hk_list);
171 (*dp->hk_fn)(dp->hk_arg);
172 #if 0
173 /*
174 * Don't bother freeing the hook structure,, since we may
175 * be rebooting because of a memory corruption problem,
176 * and this might only make things worse. It doesn't
177 * matter, anyway, since the system is just about to
178 * reboot.
179 */
180 free(dp, M_DEVBUF);
181 #endif
182 }
183 }
184
185 /*
186 * "Mountroot hook" types, functions, and variables.
187 */
188
189 static hook_list_t mountroothook_list=LIST_HEAD_INITIALIZER(mountroothook_list);
190
191 void *
192 mountroothook_establish(void (*fn)(device_t), device_t dev)
193 {
194 return hook_establish(&mountroothook_list, __FPTRCAST(void (*), fn),
195 dev);
196 }
197
198 void
199 mountroothook_disestablish(void *vhook)
200 {
201 hook_disestablish(&mountroothook_list, vhook);
202 }
203
204 void
205 mountroothook_destroy(void)
206 {
207 hook_destroy(&mountroothook_list);
208 }
209
210 void
211 domountroothook(device_t therootdev)
212 {
213 struct hook_desc *hd;
214
215 LIST_FOREACH(hd, &mountroothook_list, hk_list) {
216 if (hd->hk_arg == therootdev) {
217 (*hd->hk_fn)(hd->hk_arg);
218 return;
219 }
220 }
221 }
222
223 static hook_list_t exechook_list = LIST_HEAD_INITIALIZER(exechook_list);
224
225 void *
226 exechook_establish(void (*fn)(struct proc *, void *), void *arg)
227 {
228 return hook_establish(&exechook_list, __FPTRCAST(void (*)(void *), fn),
229 arg);
230 }
231
232 void
233 exechook_disestablish(void *vhook)
234 {
235 hook_disestablish(&exechook_list, vhook);
236 }
237
238 /*
239 * Run exec hooks.
240 */
241 void
242 doexechooks(struct proc *p)
243 {
244 hook_proc_run(&exechook_list, p);
245 }
246
247 static hook_list_t exithook_list = LIST_HEAD_INITIALIZER(exithook_list);
248 extern krwlock_t exec_lock;
249
250 void *
251 exithook_establish(void (*fn)(struct proc *, void *), void *arg)
252 {
253 void *rv;
254
255 rw_enter(&exec_lock, RW_WRITER);
256 rv = hook_establish(&exithook_list, __FPTRCAST(void (*)(void *), fn),
257 arg);
258 rw_exit(&exec_lock);
259 return rv;
260 }
261
262 void
263 exithook_disestablish(void *vhook)
264 {
265
266 rw_enter(&exec_lock, RW_WRITER);
267 hook_disestablish(&exithook_list, vhook);
268 rw_exit(&exec_lock);
269 }
270
271 /*
272 * Run exit hooks.
273 */
274 void
275 doexithooks(struct proc *p)
276 {
277 hook_proc_run(&exithook_list, p);
278 }
279
280 static hook_list_t forkhook_list = LIST_HEAD_INITIALIZER(forkhook_list);
281
282 void *
283 forkhook_establish(void (*fn)(struct proc *, struct proc *))
284 {
285 return hook_establish(&forkhook_list, __FPTRCAST(void (*)(void *), fn),
286 NULL);
287 }
288
289 void
290 forkhook_disestablish(void *vhook)
291 {
292 hook_disestablish(&forkhook_list, vhook);
293 }
294
295 /*
296 * Run fork hooks.
297 */
298 void
299 doforkhooks(struct proc *p2, struct proc *p1)
300 {
301 struct hook_desc *hd;
302
303 LIST_FOREACH(hd, &forkhook_list, hk_list) {
304 __FPTRCAST(void (*)(struct proc *, struct proc *), *hd->hk_fn)
305 (p2, p1);
306 }
307 }
308
309 static hook_list_t critpollhook_list = LIST_HEAD_INITIALIZER(critpollhook_list);
310
311 void *
312 critpollhook_establish(void (*fn)(void *), void *arg)
313 {
314 return hook_establish(&critpollhook_list, fn, arg);
315 }
316
317 void
318 critpollhook_disestablish(void *vhook)
319 {
320 hook_disestablish(&critpollhook_list, vhook);
321 }
322
323 /*
324 * Run critical polling hooks.
325 */
326 void
327 docritpollhooks(void)
328 {
329 struct hook_desc *hd;
330
331 LIST_FOREACH(hd, &critpollhook_list, hk_list) {
332 (*hd->hk_fn)(hd->hk_arg);
333 }
334 }
335
336 /*
337 * "Power hook" types, functions, and variables.
338 * The list of power hooks is kept ordered with the last registered hook
339 * first.
340 * When running the hooks on power down the hooks are called in reverse
341 * registration order, when powering up in registration order.
342 */
343 struct powerhook_desc {
344 TAILQ_ENTRY(powerhook_desc) sfd_list;
345 void (*sfd_fn)(int, void *);
346 void *sfd_arg;
347 char sfd_name[16];
348 };
349
350 static TAILQ_HEAD(powerhook_head, powerhook_desc) powerhook_list =
351 TAILQ_HEAD_INITIALIZER(powerhook_list);
352
353 void *
354 powerhook_establish(const char *name, void (*fn)(int, void *), void *arg)
355 {
356 struct powerhook_desc *ndp;
357
358 ndp = (struct powerhook_desc *)
359 malloc(sizeof(*ndp), M_DEVBUF, M_NOWAIT);
360 if (ndp == NULL)
361 return (NULL);
362
363 ndp->sfd_fn = fn;
364 ndp->sfd_arg = arg;
365 strlcpy(ndp->sfd_name, name, sizeof(ndp->sfd_name));
366 TAILQ_INSERT_HEAD(&powerhook_list, ndp, sfd_list);
367
368 aprint_error("%s: WARNING: powerhook_establish is deprecated\n", name);
369 return (ndp);
370 }
371
372 void
373 powerhook_disestablish(void *vhook)
374 {
375 #ifdef DIAGNOSTIC
376 struct powerhook_desc *dp;
377
378 TAILQ_FOREACH(dp, &powerhook_list, sfd_list)
379 if (dp == vhook)
380 goto found;
381 panic("powerhook_disestablish: hook %p not established", vhook);
382 found:
383 #endif
384
385 TAILQ_REMOVE(&powerhook_list, (struct powerhook_desc *)vhook,
386 sfd_list);
387 free(vhook, M_DEVBUF);
388 }
389
390 /*
391 * Run power hooks.
392 */
393 void
394 dopowerhooks(int why)
395 {
396 struct powerhook_desc *dp;
397 const char *why_name;
398 static const char * pwr_names[] = {PWR_NAMES};
399 why_name = why < __arraycount(pwr_names) ? pwr_names[why] : "???";
400
401 if (why == PWR_RESUME || why == PWR_SOFTRESUME) {
402 TAILQ_FOREACH_REVERSE(dp, &powerhook_list, powerhook_head,
403 sfd_list)
404 {
405 if (powerhook_debug)
406 printf("dopowerhooks %s: %s (%p)\n",
407 why_name, dp->sfd_name, dp);
408 (*dp->sfd_fn)(why, dp->sfd_arg);
409 }
410 } else {
411 TAILQ_FOREACH(dp, &powerhook_list, sfd_list) {
412 if (powerhook_debug)
413 printf("dopowerhooks %s: %s (%p)\n",
414 why_name, dp->sfd_name, dp);
415 (*dp->sfd_fn)(why, dp->sfd_arg);
416 }
417 }
418
419 if (powerhook_debug)
420 printf("dopowerhooks: %s done\n", why_name);
421 }
422
423 /*
424 * A simple linear hook.
425 */
426
427 khook_list_t *
428 simplehook_create(int ipl, const char *wmsg)
429 {
430 khook_list_t *l;
431
432 l = kmem_zalloc(sizeof(*l), KM_SLEEP);
433
434 mutex_init(&l->hl_lock, MUTEX_DEFAULT, ipl);
435 strlcpy(l->hl_namebuf, wmsg, sizeof(l->hl_namebuf));
436 cv_init(&l->hl_cv, l->hl_namebuf);
437 LIST_INIT(&l->hl_list);
438 l->hl_state = HKLIST_IDLE;
439
440 return l;
441 }
442
443 void
444 simplehook_destroy(khook_list_t *l)
445 {
446 struct hook_desc *hd;
447
448 KASSERT(l->hl_state == HKLIST_IDLE);
449
450 while ((hd = LIST_FIRST(&l->hl_list)) != NULL) {
451 LIST_REMOVE(hd, hk_list);
452 kmem_free(hd, sizeof(*hd));
453 }
454
455 cv_destroy(&l->hl_cv);
456 mutex_destroy(&l->hl_lock);
457 kmem_free(l, sizeof(*l));
458 }
459
460 int
461 simplehook_dohooks(khook_list_t *l)
462 {
463 struct hook_desc *hd, *nexthd;
464 kmutex_t *cv_lock;
465 void (*fn)(void *);
466 void *arg;
467
468 mutex_enter(&l->hl_lock);
469 if (l->hl_state != HKLIST_IDLE) {
470 mutex_exit(&l->hl_lock);
471 return EBUSY;
472 }
473
474 /* stop removing hooks */
475 l->hl_state = HKLIST_INUSE;
476 l->hl_lwp = curlwp;
477
478 LIST_FOREACH(hd, &l->hl_list, hk_list) {
479 if (hd->hk_fn == NULL)
480 continue;
481
482 fn = hd->hk_fn;
483 arg = hd->hk_arg;
484 l->hl_active_hk = hd;
485 l->hl_cvlock = NULL;
486
487 mutex_exit(&l->hl_lock);
488
489 /* do callback without l->hl_lock */
490 (*fn)(arg);
491
492 mutex_enter(&l->hl_lock);
493 l->hl_active_hk = NULL;
494 cv_lock = l->hl_cvlock;
495
496 if (hd->hk_fn == NULL) {
497 if (cv_lock != NULL) {
498 mutex_exit(&l->hl_lock);
499 mutex_enter(cv_lock);
500 }
501
502 cv_broadcast(&l->hl_cv);
503
504 if (cv_lock != NULL) {
505 mutex_exit(cv_lock);
506 mutex_enter(&l->hl_lock);
507 }
508 }
509 }
510
511 /* remove marked node while running hooks */
512 LIST_FOREACH_SAFE(hd, &l->hl_list, hk_list, nexthd) {
513 if (hd->hk_fn == NULL) {
514 LIST_REMOVE(hd, hk_list);
515 kmem_free(hd, sizeof(*hd));
516 }
517 }
518
519 l->hl_lwp = NULL;
520 l->hl_state = HKLIST_IDLE;
521 mutex_exit(&l->hl_lock);
522
523 return 0;
524 }
525
526 khook_t *
527 simplehook_establish(khook_list_t *l, void (*fn)(void *), void *arg)
528 {
529 struct hook_desc *hd;
530
531 hd = kmem_zalloc(sizeof(*hd), KM_SLEEP);
532 hd->hk_fn = fn;
533 hd->hk_arg = arg;
534
535 mutex_enter(&l->hl_lock);
536 LIST_INSERT_HEAD(&l->hl_list, hd, hk_list);
537 mutex_exit(&l->hl_lock);
538
539 return hd;
540 }
541
542 void
543 simplehook_disestablish(khook_list_t *l, khook_t *hd, kmutex_t *lock)
544 {
545 struct hook_desc *hd0 __diagused;
546 kmutex_t *cv_lock;
547
548 KASSERT(lock == NULL || mutex_owned(lock));
549 mutex_enter(&l->hl_lock);
550
551 #ifdef DIAGNOSTIC
552 LIST_FOREACH(hd0, &l->hl_list, hk_list) {
553 if (hd == hd0)
554 break;
555 }
556
557 if (hd0 == NULL)
558 panic("hook_disestablish: hook %p not established", hd);
559 #endif
560
561 /* The hook is not referred, remove immediately */
562 if (l->hl_state == HKLIST_IDLE) {
563 LIST_REMOVE(hd, hk_list);
564 kmem_free(hd, sizeof(*hd));
565 mutex_exit(&l->hl_lock);
566 return;
567 }
568
569 /* remove callback. hd will be removed in dohooks */
570 hd->hk_fn = NULL;
571 hd->hk_arg = NULL;
572
573 /* If the hook is running, wait for the completion */
574 if (l->hl_active_hk == hd &&
575 l->hl_lwp != curlwp) {
576 if (lock != NULL) {
577 cv_lock = lock;
578 KASSERT(l->hl_cvlock == NULL);
579 l->hl_cvlock = lock;
580 mutex_exit(&l->hl_lock);
581 } else {
582 cv_lock = &l->hl_lock;
583 }
584
585 cv_wait(&l->hl_cv, cv_lock);
586
587 if (lock == NULL)
588 mutex_exit(&l->hl_lock);
589 } else {
590 mutex_exit(&l->hl_lock);
591 }
592 }
593
594 bool
595 simplehook_has_hooks(khook_list_t *l)
596 {
597 bool empty;
598
599 mutex_enter(&l->hl_lock);
600 empty = LIST_EMPTY(&l->hl_list);
601 mutex_exit(&l->hl_lock);
602
603 return !empty;
604 }
605