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 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <pthread.h>
33 #include <alloca.h>
34 #include <libnvpair.h>
35 #include <libhotplug.h>
36 #include <libhotplug_impl.h>
37 #include <sys/types.h>
38 #include <sys/sunddi.h>
39 #include <sys/ddi_hp.h>
40 #include <sys/modctl.h>
41 #include "hotplugd_impl.h"
42 
43 /*
44  * All operations affecting kernel state are serialized.
45  */
46 static pthread_mutex_t	hotplug_lock = PTHREAD_MUTEX_INITIALIZER;
47 
48 /*
49  * Local functions.
50  */
51 static boolean_t	check_rcm_required(hp_node_t, int);
52 static int		pack_properties(const char *, ddi_hp_property_t *);
53 static void		unpack_properties(ddi_hp_property_t *, char **);
54 static void		free_properties(ddi_hp_property_t *);
55 
56 /*
57  * changestate()
58  *
59  *	Perform a state change operation.
60  *
61  *	NOTE: all operations are serialized, using a global lock.
62  */
63 int
changestate(const char * path,const char * connection,int state,uint_t flags,int * old_statep,hp_node_t * resultsp)64 changestate(const char *path, const char *connection, int state, uint_t flags,
65     int *old_statep, hp_node_t *resultsp)
66 {
67 	hp_node_t	root = NULL;
68 	char		**rsrcs = NULL;
69 	boolean_t	use_rcm = B_FALSE;
70 	int		rv;
71 
72 	dprintf("changestate(path=%s, connection=%s, state=0x%x, flags=0x%x)\n",
73 	    path, connection, state, flags);
74 
75 	/* Initialize results */
76 	*resultsp = NULL;
77 	*old_statep = -1;
78 
79 	(void) pthread_mutex_lock(&hotplug_lock);
80 
81 	/* Get an information snapshot, without usage details */
82 	if ((rv = getinfo(path, connection, 0, &root)) != 0) {
83 		(void) pthread_mutex_unlock(&hotplug_lock);
84 		dprintf("changestate: getinfo() failed (%s)\n", strerror(rv));
85 		return (rv);
86 	}
87 
88 	/* Record current state (used in hotplugd_door.c for auditing) */
89 	*old_statep = hp_state(root);
90 
91 	/* Check if RCM interactions are required */
92 	use_rcm = check_rcm_required(root, state);
93 
94 	/* If RCM is required, perform RCM offline */
95 	if (use_rcm) {
96 
97 		dprintf("changestate: RCM offline is required.\n");
98 
99 		/* Get RCM resources */
100 		if ((rv = rcm_resources(root, &rsrcs)) != 0) {
101 			dprintf("changestate: rcm_resources() failed.\n");
102 			(void) pthread_mutex_unlock(&hotplug_lock);
103 			hp_fini(root);
104 			return (rv);
105 		}
106 
107 		/* Request RCM offline */
108 		if ((rsrcs != NULL) &&
109 		    ((rv = rcm_offline(rsrcs, flags, root)) != 0)) {
110 			dprintf("changestate: rcm_offline() failed.\n");
111 			rcm_online(rsrcs);
112 			(void) pthread_mutex_unlock(&hotplug_lock);
113 			free_rcm_resources(rsrcs);
114 			*resultsp = root;
115 			return (rv);
116 		}
117 	}
118 
119 	/* The information snapshot is no longer needed */
120 	hp_fini(root);
121 
122 	/* Stop now if QUERY flag was specified */
123 	if (flags & HPQUERY) {
124 		dprintf("changestate: operation was QUERY only.\n");
125 		rcm_online(rsrcs);
126 		(void) pthread_mutex_unlock(&hotplug_lock);
127 		free_rcm_resources(rsrcs);
128 		return (0);
129 	}
130 
131 	/* Do state change in kernel */
132 	rv = 0;
133 	if (modctl(MODHPOPS, MODHPOPS_CHANGE_STATE, path, connection, state))
134 		rv = errno;
135 	dprintf("changestate: modctl(MODHPOPS_CHANGE_STATE) = %d.\n", rv);
136 
137 	/*
138 	 * If RCM is required, then perform an RCM online or RCM remove
139 	 * operation.  Which depends upon if modctl succeeded or failed.
140 	 */
141 	if (use_rcm && (rsrcs != NULL)) {
142 
143 		/* RCM online if failure, or RCM remove if successful */
144 		if (rv == 0)
145 			rcm_remove(rsrcs);
146 		else
147 			rcm_online(rsrcs);
148 
149 		/* RCM resources no longer required */
150 		free_rcm_resources(rsrcs);
151 	}
152 
153 	(void) pthread_mutex_unlock(&hotplug_lock);
154 
155 	*resultsp = NULL;
156 	return (rv);
157 }
158 
159 /*
160  * private_options()
161  *
162  *	Implement set/get of bus private options.
163  */
164 int
private_options(const char * path,const char * connection,hp_cmd_t cmd,const char * options,char ** resultsp)165 private_options(const char *path, const char *connection, hp_cmd_t cmd,
166     const char *options, char **resultsp)
167 {
168 	ddi_hp_property_t	prop;
169 	ddi_hp_property_t	results;
170 	char			*values = NULL;
171 	int			rv;
172 
173 	dprintf("private_options(path=%s, connection=%s, options='%s')\n",
174 	    path, connection, options);
175 
176 	/* Initialize property arguments */
177 	if ((rv = pack_properties(options, &prop)) != 0) {
178 		dprintf("private_options: failed to pack properties.\n");
179 		return (rv);
180 	}
181 
182 	/* Initialize results */
183 	(void) memset(&results, 0, sizeof (ddi_hp_property_t));
184 	results.buf_size = HP_PRIVATE_BUF_SZ;
185 	results.nvlist_buf = (char *)calloc(1, HP_PRIVATE_BUF_SZ);
186 	if (results.nvlist_buf == NULL) {
187 		dprintf("private_options: failed to allocate buffer.\n");
188 		free_properties(&prop);
189 		return (ENOMEM);
190 	}
191 
192 	/* Lock hotplug */
193 	(void) pthread_mutex_lock(&hotplug_lock);
194 
195 	/* Perform the command */
196 	rv = 0;
197 	if (cmd == HP_CMD_GETPRIVATE) {
198 		if (modctl(MODHPOPS, MODHPOPS_BUS_GET, path, connection,
199 		    &prop, &results))
200 			rv = errno;
201 		dprintf("private_options: modctl(MODHPOPS_BUS_GET) = %d\n", rv);
202 	} else {
203 		if (modctl(MODHPOPS, MODHPOPS_BUS_SET, path, connection,
204 		    &prop, &results))
205 			rv = errno;
206 		dprintf("private_options: modctl(MODHPOPS_BUS_SET) = %d\n", rv);
207 	}
208 
209 	/* Unlock hotplug */
210 	(void) pthread_mutex_unlock(&hotplug_lock);
211 
212 	/* Parse results */
213 	if (rv == 0) {
214 		unpack_properties(&results, &values);
215 		*resultsp = values;
216 	}
217 
218 	/* Cleanup */
219 	free_properties(&prop);
220 	free_properties(&results);
221 
222 	return (rv);
223 }
224 
225 /*
226  * check_rcm_required()
227  *
228  *	Given the root of a changestate operation and the target
229  *	state, determine if RCM interactions will be required.
230  */
231 static boolean_t
check_rcm_required(hp_node_t root,int target_state)232 check_rcm_required(hp_node_t root, int target_state)
233 {
234 	/*
235 	 * RCM is required when transitioning an ENABLED
236 	 * connector to a non-ENABLED state.
237 	 */
238 	if ((root->hp_type == HP_NODE_CONNECTOR) &&
239 	    HP_IS_ENABLED(root->hp_state) && !HP_IS_ENABLED(target_state))
240 		return (B_TRUE);
241 
242 	/*
243 	 * RCM is required when transitioning an OPERATIONAL
244 	 * port to a non-OPERATIONAL state.
245 	 */
246 	if ((root->hp_type == HP_NODE_PORT) &&
247 	    HP_IS_ONLINE(root->hp_state) && HP_IS_OFFLINE(target_state))
248 		return (B_TRUE);
249 
250 	/* RCM is not required in other cases */
251 	return (B_FALSE);
252 }
253 
254 /*
255  * pack_properties()
256  *
257  *	Given a specified set/get command and an options string,
258  *	construct the structure containing a packed nvlist that
259  *	contains the specified options.
260  */
261 static int
pack_properties(const char * options,ddi_hp_property_t * prop)262 pack_properties(const char *options, ddi_hp_property_t *prop)
263 {
264 	nvlist_t	*nvl;
265 	char		*buf, *tmp, *name, *value, *next;
266 	size_t		len;
267 
268 	/* Initialize results */
269 	(void) memset(prop, 0, sizeof (ddi_hp_property_t));
270 
271 	/* Do nothing if options string is empty */
272 	if ((len = strlen(options)) == 0) {
273 		dprintf("pack_properties: options string is empty.\n");
274 		return (ENOENT);
275 	}
276 
277 	/* Avoid modifying the input string by using a copy on the stack */
278 	if ((tmp = (char *)alloca(len + 1)) == NULL) {
279 		log_err("Failed to allocate buffer for private options.\n");
280 		return (ENOMEM);
281 	}
282 	(void) strlcpy(tmp, options, len + 1);
283 
284 	/* Allocate the nvlist */
285 	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) {
286 		log_err("Failed to allocate private options nvlist.\n");
287 		return (ENOMEM);
288 	}
289 
290 	/* Add each option from the string */
291 	for (name = tmp; name != NULL; name = next) {
292 
293 		/* Isolate current name/value, and locate the next */
294 		if ((next = strchr(name, ',')) != NULL) {
295 			*next = '\0';
296 			next++;
297 		}
298 
299 		/* Split current name/value pair */
300 		if ((value = strchr(name, '=')) != NULL) {
301 			*value = '\0';
302 			value++;
303 		} else {
304 			value = "";
305 		}
306 
307 		/* Add the option to the nvlist */
308 		if (nvlist_add_string(nvl, name, value) != 0) {
309 			log_err("Failed to add private option to nvlist.\n");
310 			nvlist_free(nvl);
311 			return (EFAULT);
312 		}
313 	}
314 
315 	/* Pack the nvlist */
316 	len = 0;
317 	buf = NULL;
318 	if (nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0) != 0) {
319 		log_err("Failed to pack private options nvlist.\n");
320 		nvlist_free(nvl);
321 		return (EFAULT);
322 	}
323 
324 	/* Save results */
325 	prop->nvlist_buf = buf;
326 	prop->buf_size = len;
327 
328 	/* The nvlist is no longer needed */
329 	nvlist_free(nvl);
330 
331 	return (0);
332 }
333 
334 /*
335  * unpack_properties()
336  *
337  *	Given a structure possibly containing a packed nvlist of
338  *	bus private options, unpack the nvlist and expand its
339  *	contents into an options string.
340  */
341 static void
unpack_properties(ddi_hp_property_t * prop,char ** optionsp)342 unpack_properties(ddi_hp_property_t *prop, char **optionsp)
343 {
344 	nvlist_t	*nvl = NULL;
345 	nvpair_t	*nvp;
346 	boolean_t	first_flag;
347 	char		*name, *value, *options;
348 	size_t		len;
349 
350 	/* Initialize results */
351 	*optionsp = NULL;
352 
353 	/* Do nothing if properties do not exist */
354 	if ((prop->nvlist_buf == NULL) || (prop->buf_size == 0)) {
355 		dprintf("unpack_properties: no properties exist.\n");
356 		return;
357 	}
358 
359 	/* Unpack the nvlist */
360 	if (nvlist_unpack(prop->nvlist_buf, prop->buf_size, &nvl, 0) != 0) {
361 		log_err("Failed to unpack private options nvlist.\n");
362 		return;
363 	}
364 
365 	/* Compute the size of the options string */
366 	for (len = 0, nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
367 
368 		name = nvpair_name(nvp);
369 
370 		/* Skip the command, and anything not a string */
371 		if ((strcmp(name, "cmd") == 0) ||
372 		    (nvpair_type(nvp) != DATA_TYPE_STRING))
373 			continue;
374 
375 		(void) nvpair_value_string(nvp, &value);
376 
377 		/* Account for '=' signs, commas, and terminating NULL */
378 		len += (strlen(name) + strlen(value) + 2);
379 	}
380 
381 	/* Allocate the resulting options string */
382 	if ((options = (char *)calloc(len, sizeof (char))) == NULL) {
383 		log_err("Failed to allocate private options string.\n");
384 		nvlist_free(nvl);
385 		return;
386 	}
387 
388 	/* Copy name/value pairs into the options string */
389 	first_flag = B_TRUE;
390 	for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
391 
392 		name = nvpair_name(nvp);
393 
394 		/* Skip the command, and anything not a string */
395 		if ((strcmp(name, "cmd") == 0) ||
396 		    (nvpair_type(nvp) != DATA_TYPE_STRING))
397 			continue;
398 
399 		if (!first_flag)
400 			(void) strlcat(options, ",", len);
401 
402 		(void) strlcat(options, name, len);
403 
404 		(void) nvpair_value_string(nvp, &value);
405 
406 		if (strlen(value) > 0) {
407 			(void) strlcat(options, "=", len);
408 			(void) strlcat(options, value, len);
409 		}
410 
411 		first_flag = B_FALSE;
412 	}
413 
414 	/* The unpacked nvlist is no longer needed */
415 	nvlist_free(nvl);
416 
417 	/* Save results */
418 	*optionsp = options;
419 }
420 
421 /*
422  * free_properties()
423  *
424  *	Destroy a structure containing a packed nvlist of bus
425  *	private properties.
426  */
427 static void
free_properties(ddi_hp_property_t * prop)428 free_properties(ddi_hp_property_t *prop)
429 {
430 	if (prop) {
431 		if (prop->nvlist_buf)
432 			free(prop->nvlist_buf);
433 		(void) memset(prop, 0, sizeof (ddi_hp_property_t));
434 	}
435 }
436