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
99typedef struct cmds {
100	char *cmdname;
101	int (*cmdf)(devctl_hdl_t);
102} cmds_t;
103
104extern int errno;
105
106static void setpname(char *name);
107static void print_bus_state(char *devname, uint_t state);
108static void print_dev_state(char *devname, uint_t state);
109static int dev_getstate(devctl_hdl_t);
110static int bus_getstate(devctl_hdl_t);
111static int bus_devcreate(devctl_hdl_t);
112static void run_list_ctlrs(void);
113static struct cmds *dc_cmd();
114static int nexif(di_node_t din, di_minor_t dim, void *arg);
115static void *s_malloc(size_t);
116static void *s_realloc(void *, size_t);
117static char *s_strdup(char *);
118static int dev_pm_testbusy(devctl_hdl_t);
119static int bus_pm_teststrict(devctl_hdl_t);
120
121static char *devctl_device;
122static char *orig_path;
123static char *devctl_cmdname;
124static char *progname;
125static int  verbose;
126static int  debug;
127static char *dev_name;
128static char **dev_props;
129
130static 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
139static 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
158static 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
180int
181main(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
336static int
337dev_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
350static int
351bus_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
365static int
366dev_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
378static int
379bus_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 */
395static void
396add_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
441static int
442bus_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
481static void
482print_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
494static void
495print_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
514static void
515setpname(char *name)
516{
517	register char *p;
518
519	if (p = strrchr(name, '/'))
520		progname = p + 1;
521	else
522		progname = name;
523}
524
525static struct cmds *
526dc_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 */
541static void
542run_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, NULL, 0, &nexif);
552	di_fini(dinode);
553	exit(0);
554}
555
556/*ARGSUSED*/
557static int
558nexif(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
571void *
572s_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
583void *
584s_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
595char *
596s_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