1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1992, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * This software was developed by the Computer Systems Engineering group
8 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
9 * contributed to Berkeley.
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 * 3. Neither the name of the University nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 *
35 */
36
37#include <sys/cdefs.h>
38#include "opt_ddb.h"
39
40#include <sys/param.h>
41#include <sys/kernel.h>
42#include <sys/linker.h>
43#include <sys/lock.h>
44#include <sys/malloc.h>
45#include <sys/mutex.h>
46#include <sys/systm.h>
47
48/*
49 * Autoconfiguration subroutines.
50 */
51
52/*
53 * "Interrupt driven config" functions.
54 */
55static STAILQ_HEAD(, intr_config_hook) intr_config_hook_list =
56	STAILQ_HEAD_INITIALIZER(intr_config_hook_list);
57static struct intr_config_hook *next_to_notify;
58static struct mtx intr_config_hook_lock;
59MTX_SYSINIT(intr_config_hook, &intr_config_hook_lock, "intr config", MTX_DEF);
60
61/* ARGSUSED */
62static void run_interrupt_driven_config_hooks(void);
63
64/*
65 * Private data and a shim function for implementing config_interhook_oneshot().
66 */
67struct oneshot_config_hook {
68	struct intr_config_hook
69			och_hook;		/* Must be first */
70	ich_func_t	och_func;
71	void		*och_arg;
72};
73
74static void
75config_intrhook_oneshot_func(void *arg)
76{
77	struct oneshot_config_hook *ohook;
78
79	ohook = arg;
80	ohook->och_func(ohook->och_arg);
81	config_intrhook_disestablish(&ohook->och_hook);
82	free(ohook, M_DEVBUF);
83}
84
85/*
86 * If we wait too long for an interrupt-driven config hook to return, print
87 * a diagnostic.
88 */
89#define	WARNING_INTERVAL_SECS	60
90static void
91run_interrupt_driven_config_hooks_warning(int warned)
92{
93	struct intr_config_hook *hook_entry;
94	char namebuf[64];
95	long offset;
96
97	if (warned < 6) {
98		printf("run_interrupt_driven_hooks: still waiting after %d "
99		    "seconds for", warned * WARNING_INTERVAL_SECS);
100		STAILQ_FOREACH(hook_entry, &intr_config_hook_list, ich_links) {
101			if (linker_search_symbol_name(
102			    (caddr_t)hook_entry->ich_func, namebuf,
103			    sizeof(namebuf), &offset) == 0)
104				printf(" %s", namebuf);
105			else
106				printf(" %p", hook_entry->ich_func);
107		}
108		printf("\n");
109	}
110	KASSERT(warned < 6,
111	    ("run_interrupt_driven_config_hooks: waited too long"));
112}
113
114static void
115run_interrupt_driven_config_hooks(void)
116{
117	static int running;
118	struct intr_config_hook *hook_entry;
119
120	TSENTER();
121	mtx_lock(&intr_config_hook_lock);
122
123	/*
124	 * If hook processing is already active, any newly
125	 * registered hooks will eventually be notified.
126	 * Let the currently running session issue these
127	 * notifications.
128	 */
129	if (running != 0) {
130		mtx_unlock(&intr_config_hook_lock);
131		return;
132	}
133	running = 1;
134
135	while (next_to_notify != NULL) {
136		hook_entry = next_to_notify;
137		next_to_notify = STAILQ_NEXT(hook_entry, ich_links);
138		hook_entry->ich_state = ICHS_RUNNING;
139		mtx_unlock(&intr_config_hook_lock);
140		(*hook_entry->ich_func)(hook_entry->ich_arg);
141		mtx_lock(&intr_config_hook_lock);
142	}
143
144	running = 0;
145	mtx_unlock(&intr_config_hook_lock);
146	TSEXIT();
147}
148
149static void
150boot_run_interrupt_driven_config_hooks(void *dummy)
151{
152	int warned;
153
154	run_interrupt_driven_config_hooks();
155
156	/* Block boot processing until all hooks are disestablished. */
157	TSWAIT("config hooks");
158	mtx_lock(&intr_config_hook_lock);
159	warned = 0;
160	while (!STAILQ_EMPTY(&intr_config_hook_list)) {
161		if (msleep(&intr_config_hook_list, &intr_config_hook_lock,
162		    0, "conifhk", WARNING_INTERVAL_SECS * hz) ==
163		    EWOULDBLOCK) {
164			mtx_unlock(&intr_config_hook_lock);
165			warned++;
166			run_interrupt_driven_config_hooks_warning(warned);
167			mtx_lock(&intr_config_hook_lock);
168		}
169	}
170	mtx_unlock(&intr_config_hook_lock);
171	TSUNWAIT("config hooks");
172}
173
174SYSINIT(intr_config_hooks, SI_SUB_INT_CONFIG_HOOKS, SI_ORDER_FIRST,
175	boot_run_interrupt_driven_config_hooks, NULL);
176
177/*
178 * Register a hook that will be called after "cold"
179 * autoconfiguration is complete and interrupts can
180 * be used to complete initialization.
181 */
182int
183config_intrhook_establish(struct intr_config_hook *hook)
184{
185	struct intr_config_hook *hook_entry;
186
187	TSHOLD("config hooks");
188	mtx_lock(&intr_config_hook_lock);
189	STAILQ_FOREACH(hook_entry, &intr_config_hook_list, ich_links)
190		if (hook_entry == hook)
191			break;
192	if (hook_entry != NULL) {
193		mtx_unlock(&intr_config_hook_lock);
194		printf("config_intrhook_establish: establishing an "
195		       "already established hook.\n");
196		return (1);
197	}
198	STAILQ_INSERT_TAIL(&intr_config_hook_list, hook, ich_links);
199	if (next_to_notify == NULL)
200		next_to_notify = hook;
201	hook->ich_state = ICHS_QUEUED;
202	mtx_unlock(&intr_config_hook_lock);
203	if (cold == 0)
204		/*
205		 * XXX Call from a task since not all drivers expect
206		 *     to be re-entered at the time a hook is established.
207		 */
208		/* XXX Sufficient for modules loaded after initial config??? */
209		run_interrupt_driven_config_hooks();
210	return (0);
211}
212
213/*
214 * Register a hook function that is automatically unregistered after it runs.
215 */
216void
217config_intrhook_oneshot(ich_func_t func, void *arg)
218{
219	struct oneshot_config_hook *ohook;
220
221	ohook = malloc(sizeof(*ohook), M_DEVBUF, M_WAITOK);
222	ohook->och_func = func;
223	ohook->och_arg  = arg;
224	ohook->och_hook.ich_func = config_intrhook_oneshot_func;
225	ohook->och_hook.ich_arg  = ohook;
226	config_intrhook_establish(&ohook->och_hook);
227}
228
229static void
230config_intrhook_disestablish_locked(struct intr_config_hook *hook)
231{
232	struct intr_config_hook *hook_entry;
233
234	STAILQ_FOREACH(hook_entry, &intr_config_hook_list, ich_links)
235		if (hook_entry == hook)
236			break;
237	if (hook_entry == NULL)
238		panic("config_intrhook_disestablish: disestablishing an "
239		      "unestablished hook");
240
241	if (next_to_notify == hook)
242		next_to_notify = STAILQ_NEXT(hook, ich_links);
243	STAILQ_REMOVE(&intr_config_hook_list, hook, intr_config_hook, ich_links);
244	TSRELEASE("config hooks");
245
246	/* Wakeup anyone watching the list */
247	hook->ich_state = ICHS_DONE;
248	wakeup(&intr_config_hook_list);
249}
250
251void
252config_intrhook_disestablish(struct intr_config_hook *hook)
253{
254	mtx_lock(&intr_config_hook_lock);
255	config_intrhook_disestablish_locked(hook);
256	mtx_unlock(&intr_config_hook_lock);
257}
258
259int
260config_intrhook_drain(struct intr_config_hook *hook)
261{
262	mtx_lock(&intr_config_hook_lock);
263
264	/*
265	 * The config hook has completed, so just return.
266	 */
267	if (hook->ich_state == ICHS_DONE) {
268		mtx_unlock(&intr_config_hook_lock);
269		return (ICHS_DONE);
270	}
271
272	/*
273	 * The config hook hasn't started running, just call disestablish.
274	 */
275	if (hook->ich_state == ICHS_QUEUED) {
276		config_intrhook_disestablish_locked(hook);
277		mtx_unlock(&intr_config_hook_lock);
278		return (ICHS_QUEUED);
279	}
280
281	/*
282	 * The config hook is running, so wait for it to complete and return.
283	 */
284	while (hook->ich_state != ICHS_DONE) {
285		if (msleep(&intr_config_hook_list, &intr_config_hook_lock,
286		    0, "confhd", hz) == EWOULDBLOCK) {
287			// XXX do I whine?
288		}
289	}
290	mtx_unlock(&intr_config_hook_lock);
291	return (ICHS_RUNNING);
292}
293
294#ifdef DDB
295#include <ddb/ddb.h>
296
297DB_SHOW_COMMAND_FLAGS(conifhk, db_show_conifhk, DB_CMD_MEMSAFE)
298{
299	struct intr_config_hook *hook_entry;
300	char namebuf[64];
301	long offset;
302
303	STAILQ_FOREACH(hook_entry, &intr_config_hook_list, ich_links) {
304		if (linker_ddb_search_symbol_name(
305		    (caddr_t)hook_entry->ich_func, namebuf, sizeof(namebuf),
306		    &offset) == 0) {
307			db_printf("hook: %p at %s+%#lx arg: %p\n",
308			    hook_entry->ich_func, namebuf, offset,
309			    hook_entry->ich_arg);
310		} else {
311			db_printf("hook: %p at ??+?? arg %p\n",
312			    hook_entry->ich_func, hook_entry->ich_arg);
313		}
314	}
315}
316#endif /* DDB */
317