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 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26
27#include "cfga_fp.h"
28
29static fpcfga_ret_t fp_rcm_init(char *, cfga_flags_t, char **, uint_t *,
30	char **rsrc_fixed);
31static int fp_rcm_process_node(di_node_t, void *);
32static fpcfga_ret_t fp_rcm_info_table(rcm_info_t *, char **);
33static char *chop_minor(char *);
34
35#define	MAX_FORMAT	80
36#define	DEVICES		"/devices"
37
38typedef struct {
39	char *bus_path;
40	char *filter;
41	char **errstring;
42	fpcfga_ret_t ret;
43	cfga_flags_t flags;
44	fpcfga_ret_t (*func)(char *, char *, char **, cfga_flags_t);
45} walkargs_t;
46
47static fpcfga_ret_t fp_rcm_info_table(rcm_info_t *, char **);
48static int fp_rcm_process_node(di_node_t, void *);
49static fpcfga_ret_t fp_rcm_init(char *, cfga_flags_t, char **, uint_t *,
50    char **);
51static char *chop_minor(char *);
52
53static rcm_handle_t *rcm_handle = NULL;
54static mutex_t rcm_handle_lock;
55
56/*
57 * fp_rcm_offline()
58 *
59 *	Offline FP resource consumers.
60 */
61fpcfga_ret_t
62fp_rcm_offline(char *rsrc, char **errstring, cfga_flags_t flags)
63{
64	int rret;
65	uint_t rflags = 0;
66	char *rsrc_fixed;
67	rcm_info_t *rinfo = NULL;
68	fpcfga_ret_t ret = FPCFGA_OK;
69
70	if ((ret = fp_rcm_init(rsrc, flags, errstring, &rflags, &rsrc_fixed))
71	    != FPCFGA_OK)
72		return (ret);
73
74	if ((rret = rcm_request_offline(rcm_handle, rsrc_fixed, rflags, &rinfo))
75	    != RCM_SUCCESS) {
76		cfga_err(errstring, 0, ERRARG_RCM_OFFLINE, rsrc_fixed, 0);
77		if (rinfo) {
78			(void) fp_rcm_info_table(rinfo, errstring);
79			rcm_free_info(rinfo);
80		}
81		if (rret == RCM_FAILURE)
82			(void) fp_rcm_online(rsrc, errstring, flags);
83		ret = FPCFGA_BUSY;
84	}
85
86	S_FREE(rsrc_fixed);
87
88	return (ret);
89}
90
91/*
92 * fp_rcm_online()
93 *
94 *	Online FP resource consumers that were previously offlined.
95 */
96fpcfga_ret_t
97fp_rcm_online(char *rsrc, char **errstring, cfga_flags_t flags)
98{
99	char *rsrc_fixed;
100	rcm_info_t *rinfo = NULL;
101	fpcfga_ret_t ret = FPCFGA_OK;
102
103	if ((ret = fp_rcm_init(rsrc, flags, errstring, NULL, &rsrc_fixed))
104	    != FPCFGA_OK)
105		return (ret);
106
107	if (rcm_notify_online(rcm_handle, rsrc_fixed, 0, &rinfo)
108	    != RCM_SUCCESS && rinfo != NULL) {
109		cfga_err(errstring, 0, ERRARG_RCM_ONLINE, rsrc_fixed, 0);
110		(void) fp_rcm_info_table(rinfo, errstring);
111		rcm_free_info(rinfo);
112		ret = FPCFGA_ERR;
113	}
114
115	S_FREE(rsrc_fixed);
116
117	return (ret);
118}
119
120/*
121 * fp_rcm_remove()
122 *
123 *	Remove FP resource consumers after their kernel removal.
124 */
125fpcfga_ret_t
126fp_rcm_remove(char *rsrc, char **errstring, cfga_flags_t flags)
127{
128	char *rsrc_fixed;
129	rcm_info_t *rinfo = NULL;
130	fpcfga_ret_t ret = FPCFGA_OK;
131
132	if ((ret = fp_rcm_init(rsrc, flags, errstring, NULL, &rsrc_fixed))
133	    != FPCFGA_OK)
134		return (ret);
135
136	if (rcm_notify_remove(rcm_handle, rsrc_fixed, 0, &rinfo)
137	    != RCM_SUCCESS) {
138		cfga_err(errstring, 0, ERRARG_RCM_REMOVE, rsrc_fixed, 0);
139		if (rinfo) {
140			(void) fp_rcm_info_table(rinfo, errstring);
141			rcm_free_info(rinfo);
142		}
143		ret = FPCFGA_ERR;
144	}
145
146	S_FREE(rsrc_fixed);
147
148	return (ret);
149}
150
151/*
152 * fp_rcm_suspend()
153 *
154 *	Suspend FP resource consumers before a bus quiesce.
155 */
156fpcfga_ret_t
157fp_rcm_suspend(char *rsrc, char *filter, char **errstring, cfga_flags_t flags)
158{
159	int rret;
160	uint_t rflags = 0;
161	char *rsrc_fixed;
162	char *filter_fixed;
163	char *rsrc_devpath;
164	rcm_info_t *rinfo = NULL;
165	di_node_t node;
166	fpcfga_ret_t ret = FPCFGA_OK;
167	walkargs_t walkargs;
168	timespec_t zerotime = { 0, 0 };
169
170	if ((ret = fp_rcm_init(rsrc, flags, errstring, &rflags, &rsrc_fixed))
171	    != FPCFGA_OK)
172		return (ret);
173
174	/* If a filter is provided, ensure that it makes sense */
175	if (filter != NULL && strstr(filter, rsrc) != filter) {
176		S_FREE(rsrc_fixed);
177		cfga_err(errstring, 0, ERR_APID_INVAL, 0);
178		return (FPCFGA_ERR);
179	}
180
181	/*
182	 * If no filter is specified: attempt a suspension on the resource,
183	 * directly.
184	 */
185	if (filter == NULL) {
186		if ((rret = rcm_request_suspend(rcm_handle, rsrc_fixed, rflags,
187		    &zerotime, &rinfo)) != RCM_SUCCESS) {
188			cfga_err(errstring, 0, ERRARG_RCM_SUSPEND, rsrc_fixed,
189			    0);
190			if (rinfo) {
191				(void) fp_rcm_info_table(rinfo, errstring);
192				rcm_free_info(rinfo);
193			}
194			if (rret == RCM_FAILURE)
195				(void) fp_rcm_resume(rsrc, filter, errstring,
196				    (flags & (~CFGA_FLAG_FORCE)));
197			ret = FPCFGA_BUSY;
198		}
199		S_FREE(rsrc_fixed);
200		return (ret);
201	}
202
203	/*
204	 * If a filter is specified: open the resource with libdevinfo, walk
205	 * through its nodes, and attempt a suspension of each node that
206	 * mismatches the filter.
207	 */
208
209	/* Chop off the filter's minor name */
210	if ((filter_fixed = chop_minor(filter)) == NULL)
211		return (FPCFGA_ERR);
212
213	/* get a libdevinfo snapshot of the resource's subtree */
214	rsrc_devpath = rsrc_fixed;
215	if (strstr(rsrc_fixed, DEVICES) != NULL)
216		rsrc_devpath += strlen(DEVICES);
217	node = di_init(rsrc_devpath, DINFOSUBTREE | DINFOMINOR);
218	if (node == DI_NODE_NIL) {
219		cfga_err(errstring, 0, ERRARG_DEVINFO, rsrc_fixed, 0);
220		ret = FPCFGA_ERR;
221	}
222
223	/* apply the filter, and suspend all resources not filtered out */
224	if (ret == FPCFGA_OK) {
225
226		walkargs.bus_path = rsrc_fixed;
227		walkargs.filter = filter_fixed;
228		walkargs.errstring = errstring;
229		walkargs.ret = FPCFGA_OK;
230		walkargs.flags = rflags;
231		walkargs.func = fp_rcm_suspend;
232
233		if (di_walk_node(node, 0, &walkargs, fp_rcm_process_node) < 0)
234			cfga_err(errstring, 0, ERRARG_DEVINFO, rsrc_fixed, 0);
235
236		ret = walkargs.ret;
237	}
238
239	if (node != DI_NODE_NIL)
240		di_fini(node);
241
242	S_FREE(rsrc_fixed);
243	S_FREE(filter_fixed);
244
245	if (ret != FPCFGA_OK)
246		(void) fp_rcm_resume(rsrc, filter, errstring,
247		    (flags & (~CFGA_FLAG_FORCE)));
248
249	return (ret);
250}
251
252/*
253 * fp_rcm_resume()
254 *
255 *	Resume FP resource consumers after a bus has been unquiesced.
256 */
257fpcfga_ret_t
258fp_rcm_resume(char *rsrc, char *filter, char **errstring, cfga_flags_t flags)
259{
260	uint_t rflags = 0;
261	char *rsrc_fixed;
262	char *filter_fixed;
263	char *rsrc_devpath;
264	rcm_info_t *rinfo = NULL;
265	di_node_t node;
266	fpcfga_ret_t ret = FPCFGA_OK;
267	walkargs_t walkargs;
268
269	if ((ret = fp_rcm_init(rsrc, flags, errstring, &rflags, &rsrc_fixed))
270	    != FPCFGA_OK)
271		return (ret);
272
273	/* If a filter is provided, ensure that it makes sense */
274	if (filter != NULL && strstr(filter, rsrc) != filter) {
275		S_FREE(rsrc_fixed);
276		cfga_err(errstring, 0, ERR_APID_INVAL, 0);
277		return (FPCFGA_ERR);
278	}
279
280	/*
281	 * If no filter is specified: resume the resource directly.
282	 */
283	if (filter == NULL) {
284		if (rcm_notify_resume(rcm_handle, rsrc_fixed, rflags, &rinfo)
285		    != RCM_SUCCESS && rinfo != NULL) {
286			cfga_err(errstring, 0, ERRARG_RCM_RESUME, rsrc_fixed,
287			    0);
288			(void) fp_rcm_info_table(rinfo, errstring);
289			rcm_free_info(rinfo);
290			ret = FPCFGA_BUSY;
291		}
292		S_FREE(rsrc_fixed);
293		return (ret);
294	}
295
296	/*
297	 * If a filter is specified: open the resource with libdevinfo, walk
298	 * through its nodes, and resume each of its nodes that mismatches
299	 * the filter.
300	 */
301
302	/* Chop off the filter's minor name */
303	if ((filter_fixed = chop_minor(filter)) == NULL)
304		return (FPCFGA_ERR);
305
306	/* get a libdevinfo snapshot of the resource's subtree */
307	rsrc_devpath = rsrc_fixed;
308	if (strstr(rsrc_fixed, DEVICES) != NULL)
309		rsrc_devpath += strlen(DEVICES);
310	node = di_init(rsrc_devpath, DINFOSUBTREE | DINFOMINOR);
311	if (node == DI_NODE_NIL) {
312		cfga_err(errstring, 0, ERRARG_DEVINFO, rsrc_fixed, 0);
313		ret = FPCFGA_ERR;
314	}
315
316	/* apply the filter, and resume all resources not filtered out */
317	if (ret == FPCFGA_OK) {
318
319		walkargs.bus_path = rsrc_fixed;
320		walkargs.filter = filter_fixed;
321		walkargs.errstring = errstring;
322		walkargs.ret = FPCFGA_OK;
323		walkargs.flags = rflags;
324		walkargs.func = fp_rcm_resume;
325
326		if (di_walk_node(node, 0, &walkargs, fp_rcm_process_node) < 0)
327			cfga_err(errstring, 0, ERRARG_DEVINFO, rsrc_fixed, 0);
328
329		ret = walkargs.ret;
330	}
331
332	if (node != DI_NODE_NIL)
333		di_fini(node);
334
335	S_FREE(rsrc_fixed);
336	S_FREE(filter_fixed);
337
338	return (ret);
339}
340
341/*
342 * fp_rcm_info
343 *
344 *	Queries RCM information for resources, and formats it into a table.
345 * The table is appended to the info argument.  If the info argument is a
346 * null pointer, then a new string is malloc'ed.  If the info argument is
347 * not a null pointer, then it is realloc'ed to the required size.
348 */
349fpcfga_ret_t
350fp_rcm_info(char *rsrc, char **errstring, char **info)
351{
352	char *rsrc_fixed;
353	rcm_info_t *rinfo = NULL;
354	fpcfga_ret_t ret = FPCFGA_OK;
355
356	if ((ret = fp_rcm_init(rsrc, 0, errstring, NULL, &rsrc_fixed))
357	    != FPCFGA_OK)
358		return (ret);
359
360	if (info == NULL) {
361		S_FREE(rsrc_fixed);
362		return (FPCFGA_ERR);
363	}
364
365	if (rcm_get_info(rcm_handle, rsrc_fixed, 0, &rinfo)
366	    != RCM_SUCCESS) {
367		cfga_err(errstring, 0, ERRARG_RCM_INFO, rsrc_fixed, 0);
368		ret = FPCFGA_ERR;
369	} else if (rinfo == NULL)
370		ret = FPCFGA_OK;
371
372	if (rinfo) {
373		if ((ret = fp_rcm_info_table(rinfo, info)) != FPCFGA_OK)
374			cfga_err(errstring, 0, ERRARG_RCM_INFO, rsrc_fixed, 0);
375		rcm_free_info(rinfo);
376	}
377
378	S_FREE(rsrc_fixed);
379
380	return (ret);
381}
382
383/*
384 * fp_rcm_init()
385 *
386 *	Contains common initialization code for entering a fp_rcm_xx()
387 * routine.
388 */
389static fpcfga_ret_t
390fp_rcm_init(char *rsrc, cfga_flags_t flags, char **errstring, uint_t *rflags,
391	char **rsrc_fixed)
392{
393	/* Validate the rsrc argument */
394	if (rsrc == NULL) {
395		cfga_err(errstring, 0, ERR_APID_INVAL, 0);
396		return (FPCFGA_ERR);
397	}
398
399	/* Translate the cfgadm flags to RCM flags */
400	if (rflags && (flags & CFGA_FLAG_FORCE))
401		*rflags |= RCM_FORCE;
402
403	/* Get a handle for the RCM operations */
404	(void) mutex_lock(&rcm_handle_lock);
405	if (rcm_handle == NULL) {
406		if (rcm_alloc_handle(NULL, RCM_NOPID, NULL, &rcm_handle) !=
407		    RCM_SUCCESS) {
408			cfga_err(errstring, 0, ERR_RCM_HANDLE, 0);
409			(void) mutex_unlock(&rcm_handle_lock);
410			return (FPCFGA_LIB_ERR);
411		}
412	}
413	(void) mutex_unlock(&rcm_handle_lock);
414
415	/* Chop off the rsrc's minor, if it has one */
416	if ((*rsrc_fixed = chop_minor(rsrc)) == NULL)
417		return (FPCFGA_ERR);
418
419	return (FPCFGA_OK);
420}
421
422/*
423 * fp_rcm_process_node
424 *
425 *	Helper routine for fp_rcm_{suspend,resume}.  This is a di_walk_node()
426 * callback that will apply a filter to every node it sees, and either suspend
427 * or resume it if it doesn't match the filter.
428 */
429static int
430fp_rcm_process_node(di_node_t node, void *argp)
431{
432	char *devfs_path;
433	walkargs_t *walkargs;
434	fpcfga_ret_t ret = FPCFGA_OK;
435	char disk_path[MAXPATHLEN];
436
437	/* Guard against bad arguments */
438	if ((walkargs = (walkargs_t *)argp) == NULL)
439		return (DI_WALK_TERMINATE);
440	if (walkargs->filter == NULL || walkargs->errstring == NULL) {
441		walkargs->ret = FPCFGA_ERR;
442		return (DI_WALK_TERMINATE);
443	}
444
445	/* If the node has no minors, then skip it */
446	if (di_minor_next(node, DI_MINOR_NIL) == DI_MINOR_NIL)
447		return (DI_WALK_CONTINUE);
448
449	/* Construct the devices path */
450	if ((devfs_path = di_devfs_path(node)) == NULL)
451		return (DI_WALK_CONTINUE);
452	(void) snprintf(disk_path, MAXPATHLEN, "%s%s", DEVICES, devfs_path);
453	di_devfs_path_free(devfs_path);
454
455	/*
456	 * If the node does not correspond to the targeted FP bus or the
457	 * disk being filtered out, then use the appropriate suspend/resume
458	 * function.
459	 */
460	if (strcmp(disk_path, walkargs->bus_path) != 0 &&
461	    strcmp(disk_path, walkargs->filter) != 0)
462		ret = (*walkargs->func)(disk_path, NULL, walkargs->errstring,
463		    walkargs->flags);
464
465	/* Stop the walk early if the above operation failed */
466	if (ret != FPCFGA_OK) {
467		walkargs->ret = ret;
468		return (DI_WALK_TERMINATE);
469	}
470
471	return (DI_WALK_CONTINUE);
472}
473
474/*
475 * fp_rcm_info_table
476 *
477 *	Takes an opaque rcm_info_t pointer and a character pointer, and appends
478 * the rcm_info_t data in the form of a table to the given character pointer.
479 */
480static fpcfga_ret_t
481fp_rcm_info_table(rcm_info_t *rinfo, char **table)
482{
483	int i;
484	size_t w;
485	size_t width = 0;
486	size_t w_rsrc = 0;
487	size_t w_info = 0;
488	size_t table_size = 0;
489	uint_t tuples = 0;
490	rcm_info_tuple_t *tuple = NULL;
491	char *rsrc;
492	char *info;
493	char *newtable;
494	static char format[MAX_FORMAT];
495	const char *info_info_str, *info_rsrc_str;
496
497	/* Protect against invalid arguments */
498	if (rinfo == NULL || table == NULL)
499		return (FPCFGA_ERR);
500
501	/* Set localized table header strings */
502	rsrc = gettext("Resource");
503	info = gettext("Information");
504
505	/* A first pass, to size up the RCM information */
506	while (tuple = rcm_info_next(rinfo, tuple)) {
507		info_info_str = rcm_info_info(tuple);
508		info_rsrc_str = rcm_info_rsrc(tuple);
509		if ((info_info_str != NULL) && (info_rsrc_str != NULL)) {
510			tuples++;
511			if ((w = strlen(info_rsrc_str)) > w_rsrc)
512				w_rsrc = w;
513			if ((w = strlen(info_info_str)) > w_info)
514				w_info = w;
515		}
516	}
517
518	/* If nothing was sized up above, stop early */
519	if (tuples == 0)
520		return (FPCFGA_OK);
521
522	/* Adjust column widths for column headings */
523	if ((w = strlen(rsrc)) > w_rsrc)
524		w_rsrc = w;
525	else if ((w_rsrc - w) % 2)
526		w_rsrc++;
527	if ((w = strlen(info)) > w_info)
528		w_info = w;
529	else if ((w_info - w) % 2)
530		w_info++;
531
532	/*
533	 * Compute the total line width of each line,
534	 * accounting for intercolumn spacing.
535	 */
536	width = w_info + w_rsrc + 4;
537
538	/* Allocate space for the table */
539	table_size = (2 + tuples) * (width + 1) + 2;
540	if (*table == NULL)
541		*table = malloc(table_size);
542	else {
543		newtable = realloc(*table, strlen(*table) + table_size);
544		if (newtable != NULL)
545			*table = newtable;
546	}
547	if (*table == NULL)
548		return (FPCFGA_ERR);
549
550	/* Place a table header into the string */
551
552	/* The resource header */
553	(void) strcat(*table, "\n");
554	w = strlen(rsrc);
555	for (i = 0; i < ((w_rsrc - w) / 2); i++)
556		(void) strcat(*table, " ");
557	(void) strcat(*table, rsrc);
558	for (i = 0; i < ((w_rsrc - w) / 2); i++)
559		(void) strcat(*table, " ");
560
561	/* The information header */
562	(void) strcat(*table, "  ");
563	w = strlen(info);
564	for (i = 0; i < ((w_info - w) / 2); i++)
565		(void) strcat(*table, " ");
566	(void) strcat(*table, info);
567	for (i = 0; i < ((w_info - w) / 2); i++)
568		(void) strcat(*table, " ");
569
570	/* Underline the headers */
571	(void) strcat(*table, "\n");
572	for (i = 0; i < w_rsrc; i++)
573		(void) strcat(*table, "-");
574	(void) strcat(*table, "  ");
575	for (i = 0; i < w_info; i++)
576		(void) strcat(*table, "-");
577
578	/* Construct the format string */
579	(void) snprintf(format, MAX_FORMAT, "%%-%ds  %%-%ds", w_rsrc, w_info);
580
581	/* Add the tuples to the table string */
582	tuple = NULL;
583	while ((tuple = rcm_info_next(rinfo, tuple)) != NULL) {
584		info_info_str = rcm_info_info(tuple);
585		info_rsrc_str = rcm_info_rsrc(tuple);
586		if ((info_info_str != NULL) && (info_rsrc_str != NULL)) {
587			(void) strcat(*table, "\n");
588			(void) sprintf(&((*table)[strlen(*table)]),
589			    format, info_rsrc_str, info_info_str);
590		}
591	}
592
593	return (FPCFGA_OK);
594}
595
596/*
597 * chop_minor()
598 *
599 *	Chops off the minor name portion of a resource.  Allocates storage for
600 * the returned string.  Caller must free the storage if return is non-NULL.
601 */
602static char *
603chop_minor(char *rsrc)
604{
605	char *rsrc_fixed;
606	char *cp;
607
608	if ((rsrc_fixed = strdup(rsrc)) == NULL)
609		return (NULL);
610	if ((cp = strrchr(rsrc_fixed, ':')) != NULL)
611		*cp = '\0';
612	return (rsrc_fixed);
613}
614