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 */
46static pthread_mutex_t	hotplug_lock = PTHREAD_MUTEX_INITIALIZER;
47
48/*
49 * Local functions.
50 */
51static boolean_t	check_rcm_required(hp_node_t, int);
52static int		pack_properties(const char *, ddi_hp_property_t *);
53static void		unpack_properties(ddi_hp_property_t *, char **);
54static 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 */
63int
64changestate(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 */
164int
165private_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 */
231static boolean_t
232check_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 */
261static int
262pack_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 */
341static void
342unpack_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 */
427static void
428free_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