xref: /illumos-gate/usr/src/cmd/picl/plugins/sun4v/pri/priplugin.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <pri.h>
30 #include "priplugin.h"
31 
32 #pragma init(priplugin_register)	/* place in .init section */
33 
34 static md_t *mdp;
35 
36 static mutex_t	rebuild_lock;
37 static cond_t	rebuild_cv;
38 
39 static thread_t pri_worker_thread_id, pri_reader_thread_id;
40 static boolean_t all_thr_exit = B_FALSE;
41 static boolean_t event_caught = B_FALSE;
42 
43 static void priplugin_init(void);
44 static void priplugin_fini(void);
45 static void
46 event_handler(const char *ename, const void *earg, size_t size, void *cookie);
47 static void *pri_worker_thread(void *arg);
48 static void *pri_reader_thread(void *arg);
49 static int remove_old_segments(picl_nodehdl_t node, void *args);
50 
51 
52 picld_plugin_reg_t priplugin_reg = {
53 	PICLD_PLUGIN_VERSION_1,
54 	PICLD_PLUGIN_CRITICAL,
55 	"pri_plugin",
56 	priplugin_init,
57 	priplugin_fini
58 };
59 
60 static void
61 set_prop_info(ptree_propinfo_t *propinfo, int size, char *name, int type)
62 {
63 	propinfo->version = PICLD_PLUGIN_VERSION_1;
64 	propinfo->read = NULL;
65 	propinfo->write = NULL;
66 	propinfo->piclinfo.type = type;
67 	propinfo->piclinfo.accessmode = PICL_READ;
68 	propinfo->piclinfo.size = size;
69 	(void) strlcpy(propinfo->piclinfo.name, name,
70 	    sizeof (propinfo->piclinfo.name));
71 }
72 
73 boolean_t
74 prop_exists(picl_nodehdl_t node, char *name)
75 {
76 	int status;
77 	picl_prophdl_t proph;
78 
79 	status = ptree_get_prop_by_name(node, name, &proph);
80 	if (status == PICL_SUCCESS)
81 		return (B_TRUE);
82 	else
83 		return (B_FALSE);
84 }
85 
86 void
87 add_md_prop(picl_nodehdl_t node, int size, char *name, void* value, int type)
88 {
89 	ptree_propinfo_t propinfo;
90 	picl_prophdl_t proph;
91 
92 	if (!prop_exists(node, name)) {
93 		set_prop_info(&propinfo, size, name, type);
94 
95 		(void) ptree_create_and_add_prop(node, &propinfo,
96 		    value, &proph);
97 	}
98 }
99 
100 /*ARGSUSED*/
101 static int
102 remove_old_segments(picl_nodehdl_t node, void *args)
103 {
104 	int status;
105 
106 	if ((status = ptree_delete_node(node)) == PICL_SUCCESS)
107 		ptree_destroy_node(node);
108 	else
109 		pri_debug(LOG_NOTICE, "remove_old_segments: can't delete "
110 		    "segment node: %s\n", picl_strerror(status));
111 
112 	return (PICL_WALK_CONTINUE);
113 }
114 
115 static void
116 priplugin_init(void)
117 {
118 	int status;
119 
120 	pri_debug(LOG_NOTICE, "priplugin: mem tree and io label thread "
121 	    "being created; callbacks being registered\n");
122 
123 	all_thr_exit = B_FALSE;
124 	event_caught = B_FALSE;
125 
126 	(void) mutex_init(&rebuild_lock, USYNC_THREAD, NULL);
127 	(void) cond_init(&rebuild_cv, USYNC_THREAD, NULL);
128 
129 	if ((status = thr_create(NULL, NULL, pri_worker_thread, NULL, THR_BOUND,
130 	    &pri_worker_thread_id)) < 0) {
131 		pri_debug(LOG_NOTICE, "priplugin: can't create worker thread: "
132 		    "%d\n", status);
133 		all_thr_exit = B_TRUE;
134 		(void) mutex_destroy(&rebuild_lock);
135 		(void) cond_destroy(&rebuild_cv);
136 	} else if ((status = thr_create(NULL, NULL, pri_reader_thread, NULL,
137 	    THR_BOUND, &pri_reader_thread_id)) < 0) {
138 		pri_debug(LOG_NOTICE, "priplugin: can't create reader thread: "
139 		    "%d\n", status);
140 		(void) mutex_lock(&rebuild_lock);
141 		all_thr_exit = B_TRUE;
142 		(void) cond_signal(&rebuild_cv);
143 		(void) mutex_unlock(&rebuild_lock);
144 		(void) thr_join(pri_worker_thread_id, NULL, NULL);
145 		(void) mutex_destroy(&rebuild_lock);
146 		(void) cond_destroy(&rebuild_cv);
147 	} else {
148 		pri_debug(LOG_NOTICE, "priplugin_init: worker and reader "
149 		    "threads created - registering event handlers\n");
150 		/*
151 		 * register event_handler for both "sysevent-device-added",
152 		 * "sysevent_device_removed", and for
153 		 * "sysevent-dr-app-state-change" PICL events
154 		 */
155 		(void) ptree_register_handler(PICLEVENT_SYSEVENT_DEVICE_ADDED,
156 		    event_handler, NULL);
157 		(void) ptree_register_handler(PICLEVENT_SYSEVENT_DEVICE_REMOVED,
158 		    event_handler, NULL);
159 		(void) ptree_register_handler(PICLEVENT_DR_AP_STATE_CHANGE,
160 		    event_handler, NULL);
161 	}
162 }
163 
164 /*
165  * This thread handles the main processing of PRI data.  It is woken
166  * up by either the event handler, to process a PICL event, or it is
167  * woken up by the PRI reader thread which has just fetched a new
168  * copy of the PRI.
169  */
170 /*ARGSUSED*/
171 static void *
172 pri_worker_thread(void *arg)
173 {
174 	int status;
175 	picl_nodehdl_t picl_root_node;
176 
177 	pri_debug(LOG_NOTICE, "pri_worker_thread: start\n");
178 
179 	(void) mutex_lock(&rebuild_lock);
180 	/*LINTED E_FUNC_RET_MAYBE_IGNORED2*/
181 	while (1) {
182 		(void) cond_wait(&rebuild_cv, &rebuild_lock);
183 
184 		if (all_thr_exit == B_TRUE) {
185 			(void) mutex_unlock(&rebuild_lock);
186 			pri_debug(LOG_NOTICE, "pri_worker_thread: time to "
187 			    "exit\n");
188 			break;
189 		}
190 
191 		/*
192 		 * We don't get events for changes to system memory,
193 		 * and we do not want to interfere with other plug-ins
194 		 * by making changes to the picl tree.  So if we were
195 		 * woken up by a thread then do not destroy and rebuild
196 		 * the memory info.  Just go fix the labels.
197 		 */
198 		if (event_caught == B_FALSE) {
199 			status = ptree_get_root(&picl_root_node);
200 			if (status != PICL_SUCCESS) {
201 				pri_debug(LOG_NOTICE, "pri_worker_thread: "
202 				    "can't get picl tree root node: %s\n",
203 				    picl_strerror(status));
204 				continue;
205 			}
206 
207 			pri_debug(LOG_NOTICE, "pri_worker_thread: have root "
208 			    "picl and PRI nodes\n");
209 
210 			status = ptree_walk_tree_by_class(picl_root_node,
211 			    "memory-segment", NULL, remove_old_segments);
212 			if (status != PICL_SUCCESS) {
213 				pri_debug(LOG_NOTICE, "pri_worker_thread: "
214 				    "can't remove old memory segments: \n",
215 				    picl_strerror(status));
216 			} else
217 				pri_debug(LOG_NOTICE, "pri_worker_thread: "
218 				    "old memory segments removed\n");
219 
220 			status = ptree_walk_tree_by_class(picl_root_node,
221 			    "memory", (void *) mdp, add_mem_prop);
222 			if (status != PICL_SUCCESS) {
223 				pri_debug(LOG_NOTICE, "pri_worker_thread: "
224 				    "memory segments walk failed: \n",
225 				    picl_strerror(status));
226 			} else
227 				pri_debug(LOG_NOTICE, "pri_worker_thread: "
228 				    "success walking memory node\n");
229 		} else
230 			event_caught = B_FALSE;
231 
232 		io_dev_addlabel(mdp);
233 	}
234 	pri_debug(LOG_NOTICE, "pri_worker_thread: exiting\n");
235 	return (NULL);
236 }
237 
238 /*
239  * This thread camps out in the PRI driver, waiting for it to return
240  * the contents of a new PRI.  When the PRI is changed this thread
241  * reads that data and prepares it for processing by the worker thread.
242  * It then signals the worker thread to process the new PRI data.
243  */
244 /*ARGSUSED*/
245 static void *
246 pri_reader_thread(void *arg)
247 {
248 	uint64_t tok;
249 	int status, count;
250 
251 	pri_debug(LOG_NOTICE, "pri_reader_thread: thread start\n");
252 
253 	if (pri_init() != 0) {
254 		pri_debug(LOG_NOTICE, "pri_reader_thread: pri_init failed\n");
255 		return (NULL);
256 	}
257 
258 	/*
259 	 * It's entirely possible that a new PRI may get pushed while
260 	 * the worker thread is processing the previous PRI.  We will
261 	 * wait until the worker is finished, then flush the old contents
262 	 * and wake up the worker again to process the new data.
263 	 */
264 	mdp = NULL;
265 	tok = 0;
266 	count = 0;
267 	/*LINTED E_FUNC_RET_MAYBE_IGNORED2*/
268 	while (1) {
269 		/*
270 		 * The _fini() function will close the PRI's fd, which will
271 		 * cause this function to break out of waiting in the PRI
272 		 * driver and return an error.
273 		 */
274 		status = pri_devinit(&tok);
275 
276 		(void) mutex_lock(&rebuild_lock);
277 		if (all_thr_exit == B_TRUE) {
278 			(void) mutex_unlock(&rebuild_lock);
279 			pri_debug(LOG_NOTICE, "pri_reader_thread: time to "
280 			    "exit\n");
281 			break;
282 		}
283 
284 		/*
285 		 * Wait until the worker is idle before swapping in the
286 		 * new PRI contents, then signal the worker to process
287 		 * that new data.
288 		 */
289 		if (status == 0) {
290 			pri_debug(LOG_NOTICE, "pri_reader_thread: got PRI\n");
291 
292 			/* old buffer will be freed by pri_bufinit() */
293 			mdp = pri_bufinit(mdp);
294 			if (mdp != NULL) {
295 				(void) cond_signal(&rebuild_cv);
296 				count = 0;
297 			} else {
298 				pri_debug(LOG_NOTICE, "pri_reader_thread: "
299 				    "NULL mdp!\n");
300 				status = -1;
301 			}
302 		}
303 
304 		/*
305 		 * Try to handle SP resets or other unexplained errors
306 		 * from ds by closing down and re-opening the PRI driver.
307 		 */
308 		if (status == -1) {
309 			if (errno != 0) {
310 				pri_debug(LOG_NOTICE, "pri_reader_thread: "
311 				    "can't get PRI contents: %s\n",
312 				    strerror(errno));
313 			}
314 			if (++count > 6) {
315 				pri_debug(LOG_NOTICE, "pci_reader_thread: "
316 				    "can't process PRI data\n");
317 				(void) mutex_unlock(&rebuild_lock);
318 				break;
319 			}
320 			/* old buffer will be freed by pri_fini() */
321 			pri_fini();
322 			tok = 0;
323 			sleep(10);
324 			if (pri_init() != 0) {
325 				pri_debug(LOG_NOTICE, "pci_reader_thread: "
326 				    "can't reinitialize PRI driver\n");
327 				(void) mutex_unlock(&rebuild_lock);
328 				break;
329 			}
330 		}
331 		(void) mutex_unlock(&rebuild_lock);
332 	}
333 
334 	pri_debug(LOG_NOTICE, "pri_reader_thread: thread exiting\n");
335 	return (NULL);
336 }
337 
338 static void
339 priplugin_fini(void)
340 {
341 	pri_debug(LOG_NOTICE, "priplugin_fini: called\n");
342 
343 	if (all_thr_exit == B_TRUE)
344 		return;
345 
346 	/* unregister the event handlers */
347 	(void) ptree_unregister_handler(PICLEVENT_SYSEVENT_DEVICE_ADDED,
348 	    event_handler, NULL);
349 	(void) ptree_unregister_handler(PICLEVENT_SYSEVENT_DEVICE_REMOVED,
350 	    event_handler, NULL);
351 	(void) ptree_unregister_handler(PICLEVENT_DR_AP_STATE_CHANGE,
352 	    event_handler, NULL);
353 
354 	/*
355 	 * Set the exit flag to tell the worker thread to quit and wake
356 	 * up that thread.  Once that thread is reaped then pull the rug
357 	 * out from the PRI reader thread by calling pri_fini(), which
358 	 * closes the PRI fd.  That wakes the PRI reader thread and it
359 	 * will then exit as well.
360 	 */
361 	(void) mutex_lock(&rebuild_lock);
362 	all_thr_exit = B_TRUE;
363 	(void) cond_signal(&rebuild_cv);
364 	(void) mutex_unlock(&rebuild_lock);
365 
366 	(void) thr_join(pri_worker_thread_id, NULL, NULL);
367 
368 	pri_devfini(mdp);
369 	mdp = NULL;
370 	pri_fini();
371 	(void) thr_join(pri_reader_thread_id, NULL, NULL);
372 
373 	(void) mutex_destroy(&rebuild_lock);
374 	(void) cond_destroy(&rebuild_cv);
375 }
376 
377 void
378 priplugin_register(void)
379 {
380 	picld_plugin_register(&priplugin_reg);
381 }
382 
383 /*
384  * Discovery event handler
385  * respond to the picl events:
386  *      PICLEVENT_SYSEVENT_DEVICE_ADDED
387  *      PICLEVENT_SYSEVENT_DEVICE_REMOVED
388  *      PICLEVENT_DR_AP_STATE_CHANGE
389  *
390  * We can't do much of anything fancy since the event data doesn't contain
391  * a nac for the device.  Nothing to do for remove - the devtree plug-in
392  * will have removed the node for us.  For add we have to go back and
393  * add labels again.
394  */
395 static void
396 event_handler(const char *ename, const void *earg, size_t size, void *cookie)
397 {
398 
399 	pri_debug(LOG_NOTICE, "pri: event_handler: caught event "
400 	    "%s\n", ename);
401 	if ((strcmp(ename, PICLEVENT_SYSEVENT_DEVICE_ADDED) == 0) ||
402 	    (strcmp(ename, PICLEVENT_SYSEVENT_DEVICE_REMOVED) == 0) ||
403 	    (strcmp(ename, PICLEVENT_DR_AP_STATE_CHANGE) == 0)) {
404 		pri_debug(LOG_NOTICE, "pri: event_handler: handle event "
405 		    "%s; waking worker thread\n", ename);
406 
407 		(void) mutex_lock(&rebuild_lock);
408 
409 		if (all_thr_exit == B_FALSE) {
410 			/*
411 			 * Tell the worker thread to only re-examine the
412 			 * IO device labels.
413 			 */
414 			event_caught = B_TRUE;
415 			(void) cond_signal(&rebuild_cv);
416 		}
417 
418 		(void) mutex_unlock(&rebuild_lock);
419 	}
420 }
421 
422 /*VARARGS2*/
423 void
424 pri_debug(int level, char *fmt, ...)
425 {
426 #if (PRI_DEBUG != 0)
427 	va_list	ap;
428 
429 	va_start(ap, fmt);
430 	vsyslog(level, fmt, ap);
431 	va_end(ap);
432 #endif
433 }
434