xref: /illumos-gate/usr/src/cmd/fm/fmd/common/fmd_eventq.c (revision 904e51f67bfac9f3ec88d9254757474c448808eb)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 #include <fmd_alloc.h>
27 #include <fmd_eventq.h>
28 #include <fmd_module.h>
29 #include <fmd_dispq.h>
30 #include <fmd_subr.h>
31 
32 #include <fmd.h>
33 
34 fmd_eventq_t *
35 fmd_eventq_create(fmd_module_t *mp, fmd_eventqstat_t *stats,
36     pthread_mutex_t *stats_lock, uint_t limit)
37 {
38 	fmd_eventq_t *eq = fmd_zalloc(sizeof (fmd_eventq_t), FMD_SLEEP);
39 
40 	(void) pthread_mutex_init(&eq->eq_lock, NULL);
41 	(void) pthread_cond_init(&eq->eq_cv, NULL);
42 
43 	eq->eq_mod = mp;
44 	eq->eq_stats = stats;
45 	eq->eq_stats_lock = stats_lock;
46 	eq->eq_limit = limit;
47 	eq->eq_sgid = fmd_dispq_getgid(fmd.d_disp, eq);
48 
49 	return (eq);
50 }
51 
52 void
53 fmd_eventq_destroy(fmd_eventq_t *eq)
54 {
55 	fmd_eventqelem_t *eqe;
56 
57 	while ((eqe = fmd_list_next(&eq->eq_list)) != NULL) {
58 		fmd_list_delete(&eq->eq_list, eqe);
59 		fmd_event_rele(eqe->eqe_event);
60 		fmd_free(eqe, sizeof (fmd_eventqelem_t));
61 	}
62 
63 	fmd_dispq_delgid(fmd.d_disp, eq->eq_sgid);
64 	fmd_free(eq, sizeof (fmd_eventq_t));
65 }
66 
67 static void
68 fmd_eventq_drop(fmd_eventq_t *eq, fmd_eventqelem_t *eqe)
69 {
70 	(void) pthread_mutex_lock(eq->eq_stats_lock);
71 	eq->eq_stats->eqs_dropped.fmds_value.ui64++;
72 	(void) pthread_mutex_unlock(eq->eq_stats_lock);
73 
74 	fmd_event_rele(eqe->eqe_event);
75 	fmd_free(eqe, sizeof (fmd_eventqelem_t));
76 }
77 
78 void
79 fmd_eventq_drop_topo(fmd_eventq_t *eq)
80 {
81 	fmd_eventqelem_t *eqe, *tmp;
82 	boolean_t got_fm_events = B_FALSE;
83 
84 	/*
85 	 * Here we iterate through the per-module event queue in order to remove
86 	 * redundant FMD_EVT_TOPO events.  The trick is to not drop a given
87 	 * topo event if there are any FM protocol events in the queue after
88 	 * it, as those events need to be processed with the correct topology.
89 	 */
90 	(void) pthread_mutex_lock(&eq->eq_lock);
91 	eqe = fmd_list_prev(&eq->eq_list);
92 	while (eqe) {
93 		if (FMD_EVENT_TYPE(eqe->eqe_event) == FMD_EVT_TOPO) {
94 			if (!got_fm_events) {
95 				tmp = eqe;
96 				eqe = fmd_list_prev(eqe);
97 				fmd_list_delete(&eq->eq_list, tmp);
98 				eq->eq_size--;
99 				fmd_eventq_drop(eq, tmp);
100 			} else {
101 				got_fm_events = B_FALSE;
102 				eqe = fmd_list_prev(eqe);
103 			}
104 		} else if (FMD_EVENT_TYPE(eqe->eqe_event) == FMD_EVT_PROTOCOL) {
105 			got_fm_events = B_TRUE;
106 			eqe = fmd_list_prev(eqe);
107 		} else
108 			eqe = fmd_list_prev(eqe);
109 	}
110 	(void) pthread_mutex_unlock(&eq->eq_lock);
111 }
112 
113 /*
114  * Update statistics when an event is dispatched and placed on a module's event
115  * queue.  This is essentially the same code as kstat_waitq_enter(9F).
116  */
117 static void
118 fmd_eventqstat_dispatch(fmd_eventq_t *eq)
119 {
120 	fmd_eventqstat_t *eqs = eq->eq_stats;
121 	hrtime_t new, delta;
122 	uint32_t wcnt;
123 
124 	(void) pthread_mutex_lock(eq->eq_stats_lock);
125 
126 	new = gethrtime();
127 	delta = new - eqs->eqs_wlastupdate.fmds_value.ui64;
128 	eqs->eqs_wlastupdate.fmds_value.ui64 = new;
129 	wcnt = eqs->eqs_wcnt.fmds_value.ui32++;
130 
131 	if (wcnt != 0) {
132 		eqs->eqs_wlentime.fmds_value.ui64 += delta * wcnt;
133 		eqs->eqs_wtime.fmds_value.ui64 += delta;
134 	}
135 
136 	eqs->eqs_dispatched.fmds_value.ui64++;
137 	(void) pthread_mutex_unlock(eq->eq_stats_lock);
138 }
139 
140 void
141 fmd_eventq_insert_at_head(fmd_eventq_t *eq, fmd_event_t *ep)
142 {
143 	uint_t evt = FMD_EVENT_TYPE(ep);
144 	fmd_eventqelem_t *eqe;
145 	int ok;
146 
147 	/*
148 	 * If this event queue is acting as /dev/null, bounce the reference
149 	 * count to free an unreferenced event and just return immediately.
150 	 */
151 	if (eq->eq_limit == 0) {
152 		fmd_event_hold(ep);
153 		fmd_event_rele(ep);
154 		return;
155 	}
156 
157 	eqe = fmd_alloc(sizeof (fmd_eventqelem_t), FMD_SLEEP);
158 	fmd_event_hold(ep);
159 	eqe->eqe_event = ep;
160 
161 	(void) pthread_mutex_lock(&eq->eq_lock);
162 
163 	if ((ok = eq->eq_size < eq->eq_limit || evt != FMD_EVT_PROTOCOL) != 0) {
164 		if (evt != FMD_EVT_CTL)
165 			fmd_eventqstat_dispatch(eq);
166 
167 		fmd_list_prepend(&eq->eq_list, eqe);
168 		eq->eq_size++;
169 	}
170 
171 	(void) pthread_cond_broadcast(&eq->eq_cv);
172 	(void) pthread_mutex_unlock(&eq->eq_lock);
173 
174 	if (!ok)
175 		fmd_eventq_drop(eq, eqe);
176 }
177 
178 void
179 fmd_eventq_insert_at_time(fmd_eventq_t *eq, fmd_event_t *ep)
180 {
181 	uint_t evt = FMD_EVENT_TYPE(ep);
182 	hrtime_t hrt = fmd_event_hrtime(ep);
183 	fmd_eventqelem_t *eqe, *oqe;
184 	int ok;
185 
186 	/*
187 	 * If this event queue is acting as /dev/null, bounce the reference
188 	 * count to free an unreferenced event and just return immediately.
189 	 */
190 	if (eq->eq_limit == 0) {
191 		fmd_event_hold(ep);
192 		fmd_event_rele(ep);
193 		return;
194 	}
195 
196 	eqe = fmd_alloc(sizeof (fmd_eventqelem_t), FMD_SLEEP);
197 	fmd_event_hold(ep);
198 	eqe->eqe_event = ep;
199 
200 	(void) pthread_mutex_lock(&eq->eq_lock);
201 
202 	/*
203 	 * fmd makes no guarantees that events will be delivered in time order
204 	 * because its transport can make no such guarantees.  Instead we make
205 	 * a looser guarantee that an enqueued event will be dequeued before
206 	 * any newer *pending* events according to event time.  This permits us
207 	 * to state, for example, that a timer expiry event will be delivered
208 	 * prior to any enqueued event whose time is after the timer expired.
209 	 * We use a simple insertion sort for this task, as queue lengths are
210 	 * typically short and events do *tend* to be received chronologically.
211 	 */
212 	for (oqe = fmd_list_prev(&eq->eq_list); oqe; oqe = fmd_list_prev(oqe)) {
213 		if (hrt >= fmd_event_hrtime(oqe->eqe_event))
214 			break; /* 'ep' is newer than the event in 'oqe' */
215 	}
216 
217 	if ((ok = eq->eq_size < eq->eq_limit || evt != FMD_EVT_PROTOCOL) != 0) {
218 		if (evt != FMD_EVT_CTL)
219 			fmd_eventqstat_dispatch(eq);
220 
221 		if (oqe == NULL)
222 			fmd_list_prepend(&eq->eq_list, eqe);
223 		else
224 			fmd_list_insert_after(&eq->eq_list, oqe, eqe);
225 		eq->eq_size++;
226 	}
227 
228 	(void) pthread_cond_broadcast(&eq->eq_cv);
229 	(void) pthread_mutex_unlock(&eq->eq_lock);
230 
231 	if (!ok)
232 		fmd_eventq_drop(eq, eqe);
233 }
234 
235 fmd_event_t *
236 fmd_eventq_delete(fmd_eventq_t *eq)
237 {
238 	fmd_eventqstat_t *eqs = eq->eq_stats;
239 	hrtime_t new, delta;
240 	uint32_t wcnt;
241 
242 	fmd_eventqelem_t *eqe;
243 	fmd_event_t *ep;
244 top:
245 	(void) pthread_mutex_lock(&eq->eq_lock);
246 
247 	while (!(eq->eq_flags & FMD_EVENTQ_ABORT) &&
248 	    (eq->eq_size == 0 || (eq->eq_flags & FMD_EVENTQ_SUSPEND)))
249 		(void) pthread_cond_wait(&eq->eq_cv, &eq->eq_lock);
250 
251 	if (eq->eq_flags & FMD_EVENTQ_ABORT) {
252 		(void) pthread_mutex_unlock(&eq->eq_lock);
253 		return (NULL);
254 	}
255 
256 	eqe = fmd_list_next(&eq->eq_list);
257 	fmd_list_delete(&eq->eq_list, eqe);
258 	eq->eq_size--;
259 
260 	(void) pthread_mutex_unlock(&eq->eq_lock);
261 
262 	ep = eqe->eqe_event;
263 	fmd_free(eqe, sizeof (fmd_eventqelem_t));
264 
265 	/*
266 	 * If we dequeued a control event, release it and go back to sleep.
267 	 * fmd_event_rele() on the event will block as described in fmd_ctl.c.
268 	 * This effectively renders control events invisible to our callers
269 	 * as well as to statistics and observability tools (e.g. fmstat(1M)).
270 	 */
271 	if (FMD_EVENT_TYPE(ep) == FMD_EVT_CTL) {
272 		fmd_event_rele(ep);
273 		goto top;
274 	}
275 
276 	/*
277 	 * Before returning, update our statistics.  This code is essentially
278 	 * kstat_waitq_to_runq(9F), except simplified because our queues are
279 	 * always consumed by a single thread (i.e. runq len == 1).
280 	 */
281 	(void) pthread_mutex_lock(eq->eq_stats_lock);
282 
283 	new = gethrtime();
284 	delta = new - eqs->eqs_wlastupdate.fmds_value.ui64;
285 
286 	eqs->eqs_wlastupdate.fmds_value.ui64 = new;
287 	eqs->eqs_dlastupdate.fmds_value.ui64 = new;
288 
289 	ASSERT(eqs->eqs_wcnt.fmds_value.ui32 != 0);
290 	wcnt = eqs->eqs_wcnt.fmds_value.ui32--;
291 
292 	eqs->eqs_wlentime.fmds_value.ui64 += delta * wcnt;
293 	eqs->eqs_wtime.fmds_value.ui64 += delta;
294 
295 	if (FMD_EVENT_TYPE(ep) == FMD_EVT_PROTOCOL)
296 		eqs->eqs_prdequeued.fmds_value.ui64++;
297 
298 	eqs->eqs_dequeued.fmds_value.ui64++;
299 	(void) pthread_mutex_unlock(eq->eq_stats_lock);
300 
301 	return (ep);
302 }
303 
304 /*
305  * Update statistics when an event is done being processed by the eventq's
306  * consumer thread.  This is essentially kstat_runq_exit(9F) simplified for
307  * our principle that a single thread consumes the queue (i.e. runq len == 1).
308  */
309 void
310 fmd_eventq_done(fmd_eventq_t *eq)
311 {
312 	fmd_eventqstat_t *eqs = eq->eq_stats;
313 	hrtime_t new, delta;
314 
315 	(void) pthread_mutex_lock(eq->eq_stats_lock);
316 
317 	new = gethrtime();
318 	delta = new - eqs->eqs_dlastupdate.fmds_value.ui64;
319 
320 	eqs->eqs_dlastupdate.fmds_value.ui64 = new;
321 	eqs->eqs_dtime.fmds_value.ui64 += delta;
322 
323 	(void) pthread_mutex_unlock(eq->eq_stats_lock);
324 }
325 
326 void
327 fmd_eventq_cancel(fmd_eventq_t *eq, uint_t type, void *data)
328 {
329 	fmd_eventqelem_t *eqe, *nqe;
330 
331 	(void) pthread_mutex_lock(&eq->eq_lock);
332 
333 	for (eqe = fmd_list_next(&eq->eq_list); eqe != NULL; eqe = nqe) {
334 		nqe = fmd_list_next(eqe);
335 
336 		if (fmd_event_match(eqe->eqe_event, type, data)) {
337 			fmd_list_delete(&eq->eq_list, eqe);
338 			eq->eq_size--;
339 			fmd_event_rele(eqe->eqe_event);
340 			fmd_free(eqe, sizeof (fmd_eventqelem_t));
341 		}
342 	}
343 
344 	(void) pthread_mutex_unlock(&eq->eq_lock);
345 }
346 
347 void
348 fmd_eventq_suspend(fmd_eventq_t *eq)
349 {
350 	(void) pthread_mutex_lock(&eq->eq_lock);
351 	eq->eq_flags |= FMD_EVENTQ_SUSPEND;
352 	(void) pthread_mutex_unlock(&eq->eq_lock);
353 }
354 
355 void
356 fmd_eventq_resume(fmd_eventq_t *eq)
357 {
358 	(void) pthread_mutex_lock(&eq->eq_lock);
359 	eq->eq_flags &= ~FMD_EVENTQ_SUSPEND;
360 	(void) pthread_cond_broadcast(&eq->eq_cv);
361 	(void) pthread_mutex_unlock(&eq->eq_lock);
362 }
363 
364 void
365 fmd_eventq_abort(fmd_eventq_t *eq)
366 {
367 	fmd_eventqelem_t *eqe;
368 
369 	(void) pthread_mutex_lock(&eq->eq_lock);
370 
371 	while ((eqe = fmd_list_next(&eq->eq_list)) != NULL) {
372 		fmd_list_delete(&eq->eq_list, eqe);
373 		fmd_event_rele(eqe->eqe_event);
374 		fmd_free(eqe, sizeof (fmd_eventqelem_t));
375 	}
376 
377 	eq->eq_flags |= FMD_EVENTQ_ABORT;
378 	(void) pthread_cond_broadcast(&eq->eq_cv);
379 	(void) pthread_mutex_unlock(&eq->eq_lock);
380 }
381