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