xref: /illumos-gate/usr/src/cmd/devctl/devctl.c (revision 8b35e52344673c75ba6a446ced1fb5c36b52a242)
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 (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 /*
27  * devctl - device control utility
28  *
29  * to compile:
30  * cc -o devctl -ldevice -ldevinfo devctl.c
31  *
32  * usage: devctl [-v] command [device/bus pathname]
33  *
34  * Commands:
35  *	list		- list all controllers exporting the devctl interface
36  *	online		- online a device
37  *	offline		- offline a device
38  *	remove		- remove a device from the device tree
39  *	quiesce		- quiesce the bus
40  *	unquiesce	- resume bus activity
41  *	configure	- configure a bus's child devices
42  *	unconfigure	- unconfigure a bus's child devices
43  *	bus-reset	- reset a bus
44  *	dev-reset	- reset a device
45  *	bus-getstate	- return the current state of the bus
46  *	dev-getstate	- return the current state of the device
47  *	bus-devcreate	- create a new device, bus specific
48  *	dev-raisepower		- power up a device via pm_raise_power() (pm)
49  *	dev-idlecomp		- idle a device's component 0 (pm)
50  *	dev-busycomp		- busy a device's component 0 (pm)
51  *	dev-testbusy		- test a device's component 0's busy state (pm)
52  *	dev-changepowerhigh	- power up a device via pm_power_has_changed()
53  *				  (pm)
54  *	dev-changepowerlow	- power off a device via pm_power_has_changed()
55  *				  (pm)
56  *	dev-failsuspend		- fail DDI_SUSPEND (pm)
57  *	dev-changeonresume	- issue pm_power_has_changed() vs,
58  *				  pm_raise_power() on device resume (pm)
59  *	dev-nolowerpower	- don't call pm_lower_power() on detach (pm)
60  *	dev-promprintf		- issue a prom_printf() call (pm)
61  *	bus-raisepower		- power up a bus via pm_raise_power() (pm)
62  *	bus-idlecomp		- idle a bus' component (pm)
63  *	bus-busycomp		- busy a bus' component (pm)
64  *	bus-testbusy		- test a bus' component busy state (pm)
65  *	bus-changepowerhigh	- power up a bus via pm_power_has_changed() (pm)
66  *	bus-changepowerlow	- power off a bus via pm_power_has_changed()
67  *				  (pm)
68  *	bus-failsuspend		- fail DDI_SUSPEND (pm)
69  *	bus-teststrict		- test is bus driver is  strict or involved (pm)
70  *	bus-noinvol		- mark idle twice when child detaches
71  *
72  *
73  * Returns:
74  *	- Success
75  *	- Operation not supported by device
76  *	- No Permission
77  *	- No Such Device
78  *
79  * Examples:
80  *	devctl list - list all controllers exporting a :devctl node
81  *	devctl offline /dev/dsk/c0t3d0s0  - offline disk
82  *	devctl dev-getstate  /devices/sbus@1f,0/espdma@e,8400000/esp@e,8800000\
83  * sd@3,0
84  *
85  */
86 
87 #include <stdio.h>
88 #include <string.h>
89 #include <unistd.h>
90 #include <stdlib.h>
91 #include <sys/types.h>
92 #include <sys/errno.h>
93 #include <sys/stat.h>
94 #include <sys/param.h>
95 #include <libdevice.h>
96 #include <libdevinfo.h>
97 #include <sys/sunddi.h>
98 
99 typedef struct cmds {
100 	char *cmdname;
101 	int (*cmdf)(devctl_hdl_t);
102 } cmds_t;
103 
104 extern int errno;
105 
106 static void setpname(char *name);
107 static void print_bus_state(char *devname, uint_t state);
108 static void print_dev_state(char *devname, uint_t state);
109 static int dev_getstate(devctl_hdl_t);
110 static int bus_getstate(devctl_hdl_t);
111 static int bus_devcreate(devctl_hdl_t);
112 static void run_list_ctlrs(void);
113 static struct cmds *dc_cmd();
114 static int nexif(di_node_t din, di_minor_t dim, void *arg);
115 static void *s_malloc(size_t);
116 static void *s_realloc(void *, size_t);
117 static char *s_strdup(char *);
118 static int dev_pm_testbusy(devctl_hdl_t);
119 static int bus_pm_teststrict(devctl_hdl_t);
120 
121 static char *devctl_device;
122 static char *orig_path;
123 static char *devctl_cmdname;
124 static char *progname;
125 static int  verbose;
126 static int  debug;
127 static char *dev_name;
128 static char **dev_props;
129 
130 static const char *usage = "%s [-v] list | online | offline | remove |\n"
131 	"\tquiesce | unquiesce | configure | unconfigure |\n"
132 	"\t{bus,dev}-reset {bus,dev}-getstate | {bus,dev}-raisepower |\n"
133 	"\t{bus,dev}-idlecomp | {bus,dev}-busycomp |\n"
134 	"\t{bus,dev}-changepowerhigh | {bus,dev}-changepowerlow |\n"
135 	"\t{bus,dev}-testbusy | {bus,dev}-failsuspend | dev-changeonresume |\n"
136 	"\tdev-promprintf | dev-nolowerpower | bus-teststrict |\n"
137 	"\tbus-noinvol [/dev/... | /devices/...]\n";
138 
139 static struct cmds device_cmds[] = {
140 	{"online", devctl_device_online},
141 	{"offline", devctl_device_offline},
142 	{"remove", devctl_device_remove},
143 	{"dev-reset", devctl_device_reset},
144 	{"dev-getstate", dev_getstate},
145 	{"dev-raisepower", devctl_pm_raisepower},
146 	{"dev-busycomp", devctl_pm_busycomponent},
147 	{"dev-idlecomp", devctl_pm_idlecomponent},
148 	{"dev-testbusy", dev_pm_testbusy},
149 	{"dev-changepowerlow", devctl_pm_changepowerlow},
150 	{"dev-changepowerhigh", devctl_pm_changepowerhigh},
151 	{"dev-failsuspend", devctl_pm_failsuspend},
152 	{"dev-changeonresume", devctl_pm_device_changeonresume},
153 	{"dev-promprintf", devctl_pm_device_promprintf},
154 	{"dev-nolowerpower", devctl_pm_device_no_lower_power},
155 	{NULL, NULL},
156 };
157 
158 static struct cmds bus_cmds[] = {
159 	{"quiesce", devctl_bus_quiesce},
160 	{"unquiesce", devctl_bus_unquiesce},
161 	{"bus-reset", devctl_bus_reset},
162 	{"configure", devctl_bus_configure},
163 	{"unconfigure", devctl_bus_unconfigure},
164 	{"bus-getstate", bus_getstate},
165 	{"bus-devcreate", bus_devcreate},
166 	{"bus-raisepower", devctl_pm_raisepower},
167 	{"bus-busycomp", devctl_pm_busycomponent},
168 	{"bus-idlecomp", devctl_pm_idlecomponent},
169 	{"bus-changepowerlow", devctl_pm_changepowerlow},
170 	{"bus-changepowerhigh", devctl_pm_changepowerhigh},
171 	{"bus-testbusy", dev_pm_testbusy},
172 	{"bus-failsuspend", devctl_pm_failsuspend},
173 	{"bus-teststrict", bus_pm_teststrict},
174 	{"bus-noinvol", devctl_pm_bus_no_invol},
175 	{NULL, NULL},
176 };
177 
178 
179 
180 int
181 main(int argc, char *argv[])
182 {
183 	int	c;
184 	int	rv;
185 	int	pathlen;
186 	struct cmds *dcmd;
187 	devctl_hdl_t dcp;
188 	struct stat stat_buf;
189 
190 	setpname(argv[0]);
191 	while ((c = getopt(argc, argv, "vd")) != -1)  {
192 		switch (c)  {
193 		case 'v':
194 			++verbose;
195 			break;
196 		case 'd':
197 			++debug;
198 			(void) putenv("LIBDEVICE_DEBUG");
199 			break;
200 		default:
201 			(void) fprintf(stderr, usage, progname);
202 			exit(1);
203 			/*NOTREACHED*/
204 		}
205 	}
206 
207 	if (optind == argc) {
208 		(void) fprintf(stderr, usage, progname);
209 		exit(-1);
210 	}
211 
212 	devctl_cmdname = argv[optind++];
213 
214 	if (strcmp(devctl_cmdname, "list") == 0) {
215 		run_list_ctlrs();
216 		exit(0);
217 	}
218 
219 	/*
220 	 * any command other than "list" requires a device path
221 	 */
222 	if (((optind + 1) > argc)) {
223 		(void) fprintf(stderr, usage, progname);
224 		exit(-1);
225 	}
226 
227 	orig_path = s_strdup(argv[optind]);
228 	devctl_device = s_malloc(MAXPATHLEN);
229 	(void) strcpy(devctl_device, orig_path);
230 
231 	/*
232 	 * Additional properties follow for bus-devcreate
233 	 */
234 	if ((optind + 1 < argc) &&
235 	    strcmp(devctl_cmdname, "bus-devcreate") == 0) {
236 		int i;
237 		optind++;
238 		dev_name = s_strdup(argv[optind]);
239 		i = argc - optind;
240 		dev_props = s_malloc(i * sizeof (char *));
241 		while (--i) {
242 			dev_props[i - 1] = s_strdup(argv[optind + i]);
243 		}
244 	}
245 
246 	/*
247 	 * if the device is a logical name, get the physical name
248 	 */
249 	if (lstat(orig_path, &stat_buf) == 0) {
250 		if (S_ISLNK(stat_buf.st_mode)) {
251 			if ((pathlen = readlink(orig_path, devctl_device,
252 			    MAXPATHLEN)) == -1)  {
253 				(void) fprintf(stderr,
254 				    "devctl: readlink(%s) - %s\n",
255 				    orig_path, strerror(errno));
256 				exit(-1);
257 			}
258 			devctl_device[pathlen] = '\0';
259 		}
260 	}
261 
262 	if ((dcmd = dc_cmd(device_cmds, devctl_cmdname)) == NULL) {
263 		dcmd = dc_cmd(bus_cmds, devctl_cmdname);
264 		if (dcmd == NULL) {
265 			(void) fprintf(stderr, "unrecognized command (%s)\n",
266 			    devctl_cmdname);
267 			(void) fprintf(stderr, usage, progname);
268 			exit(1);
269 		} else if (strcmp(devctl_cmdname, "bus-raisepower") == 0 ||
270 		    strcmp(devctl_cmdname, "bus-changepowerlow") == 0 ||
271 		    strcmp(devctl_cmdname, "bus-changepowerhigh") == 0 ||
272 		    strcmp(devctl_cmdname, "bus-idlecomp") == 0 ||
273 		    strcmp(devctl_cmdname, "bus-busycomp") == 0 ||
274 		    strcmp(devctl_cmdname, "bus-testbusy") == 0 ||
275 		    strcmp(devctl_cmdname, "bus-failsuspend") == 0 ||
276 		    strcmp(devctl_cmdname, "bus-teststrict") == 0 ||
277 		    strcmp(devctl_cmdname, "bus-noinvol") == 0) {
278 			dcp = devctl_pm_bus_acquire(devctl_device, 0);
279 			if (dcp == NULL) {
280 				(void) fprintf(stderr,
281 				    "devctl: device_pm_bus_acquire %s - %s\n",
282 				    devctl_device, strerror(errno));
283 				exit(-1);
284 			}
285 		} else {
286 			dcp = devctl_bus_acquire(devctl_device, 0);
287 			if (dcp == NULL) {
288 				(void) fprintf(stderr, "devctl: bus_acquire "
289 				    "%s - %s\n",
290 				    devctl_device, strerror(errno));
291 				exit(-1);
292 			}
293 		}
294 	} else if (strcmp(devctl_cmdname, "dev-raisepower") == 0 ||
295 	    strcmp(devctl_cmdname, "dev-changepowerlow") == 0 ||
296 	    strcmp(devctl_cmdname, "dev-changepowerhigh") == 0 ||
297 	    strcmp(devctl_cmdname, "dev-idlecomp") == 0 ||
298 	    strcmp(devctl_cmdname, "dev-busycomp") == 0 ||
299 	    strcmp(devctl_cmdname, "dev-testbusy") == 0 ||
300 	    strcmp(devctl_cmdname, "dev-failsuspend") == 0 ||
301 	    strcmp(devctl_cmdname, "dev-changeonresume") == 0 ||
302 	    strcmp(devctl_cmdname, "dev-promprintf") == 0 ||
303 	    strcmp(devctl_cmdname, "dev-nolowerpower") == 0) {
304 		dcp = devctl_pm_dev_acquire(devctl_device, 0);
305 		if (dcp == NULL) {
306 			(void) fprintf(stderr,
307 			    "devctl: device_pm_dev_acquire %s - %s\n",
308 			    devctl_device, strerror(errno));
309 			exit(-1);
310 		}
311 	} else {
312 		dcp = devctl_device_acquire(devctl_device, 0);
313 		if (dcp == NULL) {
314 			(void) fprintf(stderr,
315 			    "devctl: device_acquire %s - %s\n",
316 			    devctl_device, strerror(errno));
317 			exit(-1);
318 		}
319 	}
320 
321 	if (verbose)
322 		(void) printf("devctl: cmd (%s) device (%s)\n",
323 		    devctl_cmdname, orig_path);
324 
325 	(void) fflush(NULL);	/* get output out of the way */
326 
327 	rv = (dcmd->cmdf)(dcp);
328 
329 	if (rv == -1) {
330 		perror("devctl");
331 		exit(-1);
332 	}
333 	return (0);
334 } /* main */
335 
336 static int
337 dev_pm_testbusy(devctl_hdl_t dcp)
338 {
339 	int rv;
340 	uint_t *busyp;
341 
342 	busyp = s_malloc(sizeof (uint_t));
343 	rv = devctl_pm_testbusy(dcp, busyp);
344 	if (rv != -1)
345 		(void) printf("%s: busy state %d\n", orig_path, *busyp);
346 
347 	return (0);
348 }
349 
350 static int
351 bus_pm_teststrict(devctl_hdl_t dcp)
352 {
353 	int rv;
354 	uint_t *strict;
355 
356 	strict = s_malloc(sizeof (uint_t));
357 
358 	rv = devctl_pm_bus_teststrict(dcp, strict);
359 	if (rv != -1)
360 		(void) printf("%s: strict %d\n", orig_path, *strict);
361 
362 	return (0);
363 }
364 
365 static int
366 dev_getstate(devctl_hdl_t dcp)
367 {
368 	int rv;
369 	uint_t state;
370 
371 	rv = devctl_device_getstate(dcp, &state);
372 	if (rv != -1)
373 		print_dev_state(orig_path, state);
374 
375 	return (0);
376 }
377 
378 static int
379 bus_getstate(devctl_hdl_t dcp)
380 {
381 	int rv;
382 	uint_t state;
383 
384 	rv = devctl_bus_getstate(dcp, &state);
385 	if (rv != -1)
386 		print_bus_state(orig_path, state);
387 
388 	return (0);
389 }
390 
391 /*
392  * Only string property is supported now.
393  * Will add more later.
394  */
395 static void
396 add_prop(devctl_ddef_t ddef_hdl, char *prop_str)
397 {
398 	char *pname, *pval, *tmp;
399 	char **strs = NULL;
400 	int nstr;
401 
402 	tmp = strchr(prop_str, '=');
403 	if (tmp == NULL) {
404 		(void) fprintf(stderr, "invalid property %s", prop_str);
405 		exit(-1);
406 	}
407 
408 	(void) printf("prop string: %s\n", prop_str);
409 	pname = prop_str;
410 	*tmp++ = '\0';
411 	if (*tmp != '"') {
412 		(void) devctl_ddef_string(ddef_hdl, pname, tmp);
413 		return;
414 	}
415 
416 	nstr = 0;
417 	while (*tmp != '\0') {
418 		pval = tmp + 1;
419 		tmp = strchr(pval, '"');
420 		if (tmp == NULL) {
421 			(void) fprintf(stderr, "missing quote in %s", tmp);
422 			exit(-1);
423 		}
424 		nstr++;
425 		strs = (char **)s_realloc(strs, nstr * sizeof (char *));
426 		strs[nstr - 1] = pval;
427 		*tmp++ = '\0';
428 		(void) printf("string[%d] = %s\n", nstr - 1, pval);
429 		if (*tmp)
430 			tmp = strchr(tmp, '"');
431 		if (tmp == NULL) {
432 			(void) fprintf(stderr, "string not ending with quote");
433 			exit(-1);
434 		}
435 	}
436 
437 	(void) devctl_ddef_string_array(ddef_hdl, pname, nstr, strs);
438 	free(strs);
439 }
440 
441 static int
442 bus_devcreate(devctl_hdl_t bus_dcp)
443 {
444 	int rv;
445 	char **propp = dev_props;
446 	devctl_ddef_t ddef_hdl = NULL;
447 	devctl_hdl_t dev_hdl = NULL;
448 
449 	ddef_hdl = devctl_ddef_alloc(dev_name, 0);
450 	if (dev_props == NULL) {
451 		(void) fprintf(stderr, "dev-create: missing device props\n");
452 		return (-1);
453 	}
454 
455 	while (*propp) {
456 		add_prop(ddef_hdl, *propp);
457 		propp++;
458 	}
459 
460 	if (devctl_bus_dev_create(bus_dcp, ddef_hdl, 0, &dev_hdl)) {
461 		(void) fprintf(stderr,
462 		    "bus-devcreate: failed to create device node\n");
463 		rv = -1;
464 	} else if (devctl_get_pathname(dev_hdl, devctl_device, MAXPATHLEN)
465 	    == NULL) {
466 		(void) fprintf(stderr,
467 		    "bus-devcreate: failed to get device path\n");
468 		rv = -1;
469 	} else {
470 		(void) printf("created device %s\n", devctl_device);
471 		rv = 0;
472 	}
473 
474 	devctl_ddef_free(ddef_hdl);
475 	if (dev_hdl)
476 		devctl_release(dev_hdl);
477 
478 	return (rv);
479 }
480 
481 static void
482 print_bus_state(char *devname, uint_t state)
483 {
484 	(void) printf("\t%s: ", devname);
485 	if (state == BUS_QUIESCED)
486 		(void) printf("Quiesced");
487 	else if (state == BUS_ACTIVE)
488 		(void) printf("Active");
489 	else if (state == BUS_SHUTDOWN)
490 		(void) printf("Shutdown");
491 	(void) printf("\n");
492 }
493 
494 static void
495 print_dev_state(char *devname, uint_t state)
496 {
497 	(void) printf("\t%s: ", devname);
498 	if (state & DEVICE_ONLINE) {
499 		(void) printf("Online");
500 		if (state & DEVICE_BUSY)
501 			(void) printf(" Busy");
502 		if (state & DEVICE_DOWN)
503 			(void) printf(" Down");
504 	} else {
505 		if (state & DEVICE_OFFLINE) {
506 			(void) printf("Offline");
507 			if (state & DEVICE_DOWN)
508 				(void) printf(" Down");
509 		}
510 	}
511 	(void) printf("\n");
512 }
513 
514 static void
515 setpname(char *name)
516 {
517 	register char *p;
518 
519 	if (p = strrchr(name, '/'))
520 		progname = p + 1;
521 	else
522 		progname = name;
523 }
524 
525 static struct cmds *
526 dc_cmd(struct cmds *cmd_tbl, char *devctl_cmdname)
527 {
528 	int i;
529 
530 	for (i = 0; cmd_tbl[i].cmdname != NULL; i++) {
531 		if (strcasecmp(cmd_tbl[i].cmdname, devctl_cmdname) == 0)
532 			return (&cmd_tbl[i]);
533 	}
534 
535 	return (NULL);
536 }
537 
538 /*
539  * list all nexus drivers exporting the :devctl minor device
540  */
541 static void
542 run_list_ctlrs(void)
543 {
544 	di_node_t dinode;
545 
546 	if ((dinode = di_init("/", DINFOSUBTREE|DINFOMINOR)) == NULL) {
547 		(void) fprintf(stderr, "%s: di_init() failed\n",
548 		    progname);
549 		exit(-1);
550 	}
551 	(void) di_walk_minor(dinode, DDI_NT_NEXUS, 0, NULL, &nexif);
552 	di_fini(dinode);
553 	exit(0);
554 }
555 
556 /*ARGSUSED*/
557 static int
558 nexif(di_node_t din, di_minor_t dim, void *arg)
559 {
560 	char *devname;
561 
562 	if ((devname = di_devfs_path(din)) != NULL) {
563 		(void) printf("%s%d: /devices%s\n", di_driver_name(din),
564 		    di_instance(din), devname);
565 		di_devfs_path_free(devname);
566 	}
567 
568 	return (DI_WALK_CONTINUE);
569 }
570 
571 void *
572 s_malloc(size_t len)
573 {
574 	void *buf = malloc(len);
575 
576 	if (buf == NULL) {
577 		perror("s_malloc failed");
578 		exit(-1);
579 	}
580 	return (buf);
581 }
582 
583 void *
584 s_realloc(void *ptr, size_t len)
585 {
586 	void *buf = realloc(ptr, len);
587 
588 	if (buf == NULL) {
589 		perror("s_realloc failed");
590 		exit(-1);
591 	}
592 	return (buf);
593 }
594 
595 char *
596 s_strdup(char *str)
597 {
598 	char *buf = strdup(str);
599 
600 	if (buf == NULL) {
601 		perror("s_malloc failed");
602 		exit(-1);
603 	}
604 	return (buf);
605 }
606