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