/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * devctl - device control utility * * to compile: * cc -o devctl -ldevice -ldevinfo devctl.c * * usage: devctl [-v] command [device/bus pathname] * * Commands: * list - list all controllers exporting the devctl interface * online - online a device * offline - offline a device * remove - remove a device from the device tree * quiesce - quiesce the bus * unquiesce - resume bus activity * configure - configure a bus's child devices * unconfigure - unconfigure a bus's child devices * bus-reset - reset a bus * dev-reset - reset a device * bus-getstate - return the current state of the bus * dev-getstate - return the current state of the device * bus-devcreate - create a new device, bus specific * dev-raisepower - power up a device via pm_raise_power() (pm) * dev-idlecomp - idle a device's component 0 (pm) * dev-busycomp - busy a device's component 0 (pm) * dev-testbusy - test a device's component 0's busy state (pm) * dev-changepowerhigh - power up a device via pm_power_has_changed() * (pm) * dev-changepowerlow - power off a device via pm_power_has_changed() * (pm) * dev-failsuspend - fail DDI_SUSPEND (pm) * dev-changeonresume - issue pm_power_has_changed() vs, * pm_raise_power() on device resume (pm) * dev-nolowerpower - don't call pm_lower_power() on detach (pm) * dev-promprintf - issue a prom_printf() call (pm) * bus-raisepower - power up a bus via pm_raise_power() (pm) * bus-idlecomp - idle a bus' component (pm) * bus-busycomp - busy a bus' component (pm) * bus-testbusy - test a bus' component busy state (pm) * bus-changepowerhigh - power up a bus via pm_power_has_changed() (pm) * bus-changepowerlow - power off a bus via pm_power_has_changed() * (pm) * bus-failsuspend - fail DDI_SUSPEND (pm) * bus-teststrict - test is bus driver is strict or involved (pm) * bus-noinvol - mark idle twice when child detaches * * * Returns: * - Success * - Operation not supported by device * - No Permission * - No Such Device * * Examples: * devctl list - list all controllers exporting a :devctl node * devctl offline /dev/dsk/c0t3d0s0 - offline disk * devctl dev-getstate /devices/sbus@1f,0/espdma@e,8400000/esp@e,8800000\ * sd@3,0 * */ #include #include #include #include #include #include #include #include #include #include #include typedef struct cmds { char *cmdname; int (*cmdf)(devctl_hdl_t); } cmds_t; extern int errno; static void setpname(char *name); static void print_bus_state(char *devname, uint_t state); static void print_dev_state(char *devname, uint_t state); static int dev_getstate(devctl_hdl_t); static int bus_getstate(devctl_hdl_t); static int bus_devcreate(devctl_hdl_t); static void run_list_ctlrs(void); static struct cmds *dc_cmd(); static int nexif(di_node_t din, di_minor_t dim, void *arg); static void *s_malloc(size_t); static void *s_realloc(void *, size_t); static char *s_strdup(char *); static int dev_pm_testbusy(devctl_hdl_t); static int bus_pm_teststrict(devctl_hdl_t); static char *devctl_device; static char *orig_path; static char *devctl_cmdname; static char *progname; static int verbose; static int debug; static char *dev_name; static char **dev_props; static const char *usage = "%s [-v] list | online | offline | remove |\n" "\tquiesce | unquiesce | configure | unconfigure |\n" "\t{bus,dev}-reset {bus,dev}-getstate | {bus,dev}-raisepower |\n" "\t{bus,dev}-idlecomp | {bus,dev}-busycomp |\n" "\t{bus,dev}-changepowerhigh | {bus,dev}-changepowerlow |\n" "\t{bus,dev}-testbusy | {bus,dev}-failsuspend | dev-changeonresume |\n" "\tdev-promprintf | dev-nolowerpower | bus-teststrict |\n" "\tbus-noinvol [/dev/... | /devices/...]\n"; static struct cmds device_cmds[] = { {"online", devctl_device_online}, {"offline", devctl_device_offline}, {"remove", devctl_device_remove}, {"dev-reset", devctl_device_reset}, {"dev-getstate", dev_getstate}, {"dev-raisepower", devctl_pm_raisepower}, {"dev-busycomp", devctl_pm_busycomponent}, {"dev-idlecomp", devctl_pm_idlecomponent}, {"dev-testbusy", dev_pm_testbusy}, {"dev-changepowerlow", devctl_pm_changepowerlow}, {"dev-changepowerhigh", devctl_pm_changepowerhigh}, {"dev-failsuspend", devctl_pm_failsuspend}, {"dev-changeonresume", devctl_pm_device_changeonresume}, {"dev-promprintf", devctl_pm_device_promprintf}, {"dev-nolowerpower", devctl_pm_device_no_lower_power}, {NULL, NULL}, }; static struct cmds bus_cmds[] = { {"quiesce", devctl_bus_quiesce}, {"unquiesce", devctl_bus_unquiesce}, {"bus-reset", devctl_bus_reset}, {"configure", devctl_bus_configure}, {"unconfigure", devctl_bus_unconfigure}, {"bus-getstate", bus_getstate}, {"bus-devcreate", bus_devcreate}, {"bus-raisepower", devctl_pm_raisepower}, {"bus-busycomp", devctl_pm_busycomponent}, {"bus-idlecomp", devctl_pm_idlecomponent}, {"bus-changepowerlow", devctl_pm_changepowerlow}, {"bus-changepowerhigh", devctl_pm_changepowerhigh}, {"bus-testbusy", dev_pm_testbusy}, {"bus-failsuspend", devctl_pm_failsuspend}, {"bus-teststrict", bus_pm_teststrict}, {"bus-noinvol", devctl_pm_bus_no_invol}, {NULL, NULL}, }; int main(int argc, char *argv[]) { int c; int rv; int pathlen; struct cmds *dcmd; devctl_hdl_t dcp; struct stat stat_buf; setpname(argv[0]); while ((c = getopt(argc, argv, "vd")) != -1) { switch (c) { case 'v': ++verbose; break; case 'd': ++debug; (void) putenv("LIBDEVICE_DEBUG"); break; default: (void) fprintf(stderr, usage, progname); exit(1); /*NOTREACHED*/ } } if (optind == argc) { (void) fprintf(stderr, usage, progname); exit(-1); } devctl_cmdname = argv[optind++]; if (strcmp(devctl_cmdname, "list") == 0) { run_list_ctlrs(); exit(0); } /* * any command other than "list" requires a device path */ if (((optind + 1) > argc)) { (void) fprintf(stderr, usage, progname); exit(-1); } orig_path = s_strdup(argv[optind]); devctl_device = s_malloc(MAXPATHLEN); (void) strcpy(devctl_device, orig_path); /* * Additional properties follow for bus-devcreate */ if ((optind + 1 < argc) && strcmp(devctl_cmdname, "bus-devcreate") == 0) { int i; optind++; dev_name = s_strdup(argv[optind]); i = argc - optind; dev_props = s_malloc(i * sizeof (char *)); while (--i) { dev_props[i - 1] = s_strdup(argv[optind + i]); } } /* * if the device is a logical name, get the physical name */ if (lstat(orig_path, &stat_buf) == 0) { if (S_ISLNK(stat_buf.st_mode)) { if ((pathlen = readlink(orig_path, devctl_device, MAXPATHLEN)) == -1) { (void) fprintf(stderr, "devctl: readlink(%s) - %s\n", orig_path, strerror(errno)); exit(-1); } devctl_device[pathlen] = '\0'; } } if ((dcmd = dc_cmd(device_cmds, devctl_cmdname)) == NULL) { dcmd = dc_cmd(bus_cmds, devctl_cmdname); if (dcmd == NULL) { (void) fprintf(stderr, "unrecognized command (%s)\n", devctl_cmdname); (void) fprintf(stderr, usage, progname); exit(1); } else if (strcmp(devctl_cmdname, "bus-raisepower") == 0 || strcmp(devctl_cmdname, "bus-changepowerlow") == 0 || strcmp(devctl_cmdname, "bus-changepowerhigh") == 0 || strcmp(devctl_cmdname, "bus-idlecomp") == 0 || strcmp(devctl_cmdname, "bus-busycomp") == 0 || strcmp(devctl_cmdname, "bus-testbusy") == 0 || strcmp(devctl_cmdname, "bus-failsuspend") == 0 || strcmp(devctl_cmdname, "bus-teststrict") == 0 || strcmp(devctl_cmdname, "bus-noinvol") == 0) { dcp = devctl_pm_bus_acquire(devctl_device, 0); if (dcp == NULL) { (void) fprintf(stderr, "devctl: device_pm_bus_acquire %s - %s\n", devctl_device, strerror(errno)); exit(-1); } } else { dcp = devctl_bus_acquire(devctl_device, 0); if (dcp == NULL) { (void) fprintf(stderr, "devctl: bus_acquire " "%s - %s\n", devctl_device, strerror(errno)); exit(-1); } } } else if (strcmp(devctl_cmdname, "dev-raisepower") == 0 || strcmp(devctl_cmdname, "dev-changepowerlow") == 0 || strcmp(devctl_cmdname, "dev-changepowerhigh") == 0 || strcmp(devctl_cmdname, "dev-idlecomp") == 0 || strcmp(devctl_cmdname, "dev-busycomp") == 0 || strcmp(devctl_cmdname, "dev-testbusy") == 0 || strcmp(devctl_cmdname, "dev-failsuspend") == 0 || strcmp(devctl_cmdname, "dev-changeonresume") == 0 || strcmp(devctl_cmdname, "dev-promprintf") == 0 || strcmp(devctl_cmdname, "dev-nolowerpower") == 0) { dcp = devctl_pm_dev_acquire(devctl_device, 0); if (dcp == NULL) { (void) fprintf(stderr, "devctl: device_pm_dev_acquire %s - %s\n", devctl_device, strerror(errno)); exit(-1); } } else { dcp = devctl_device_acquire(devctl_device, 0); if (dcp == NULL) { (void) fprintf(stderr, "devctl: device_acquire %s - %s\n", devctl_device, strerror(errno)); exit(-1); } } if (verbose) (void) printf("devctl: cmd (%s) device (%s)\n", devctl_cmdname, orig_path); (void) fflush(NULL); /* get output out of the way */ rv = (dcmd->cmdf)(dcp); if (rv == -1) { perror("devctl"); exit(-1); } return (0); } /* main */ static int dev_pm_testbusy(devctl_hdl_t dcp) { int rv; uint_t *busyp; busyp = s_malloc(sizeof (uint_t)); rv = devctl_pm_testbusy(dcp, busyp); if (rv != -1) (void) printf("%s: busy state %d\n", orig_path, *busyp); return (0); } static int bus_pm_teststrict(devctl_hdl_t dcp) { int rv; uint_t *strict; strict = s_malloc(sizeof (uint_t)); rv = devctl_pm_bus_teststrict(dcp, strict); if (rv != -1) (void) printf("%s: strict %d\n", orig_path, *strict); return (0); } static int dev_getstate(devctl_hdl_t dcp) { int rv; uint_t state; rv = devctl_device_getstate(dcp, &state); if (rv != -1) print_dev_state(orig_path, state); return (0); } static int bus_getstate(devctl_hdl_t dcp) { int rv; uint_t state; rv = devctl_bus_getstate(dcp, &state); if (rv != -1) print_bus_state(orig_path, state); return (0); } /* * Only string property is supported now. * Will add more later. */ static void add_prop(devctl_ddef_t ddef_hdl, char *prop_str) { char *pname, *pval, *tmp; char **strs = NULL; int nstr; tmp = strchr(prop_str, '='); if (tmp == NULL) { (void) fprintf(stderr, "invalid property %s", prop_str); exit(-1); } (void) printf("prop string: %s\n", prop_str); pname = prop_str; *tmp++ = '\0'; if (*tmp != '"') { (void) devctl_ddef_string(ddef_hdl, pname, tmp); return; } nstr = 0; while (*tmp != '\0') { pval = tmp + 1; tmp = strchr(pval, '"'); if (tmp == NULL) { (void) fprintf(stderr, "missing quote in %s", tmp); exit(-1); } nstr++; strs = (char **)s_realloc(strs, nstr * sizeof (char *)); strs[nstr - 1] = pval; *tmp++ = '\0'; (void) printf("string[%d] = %s\n", nstr - 1, pval); if (*tmp) tmp = strchr(tmp, '"'); if (tmp == NULL) { (void) fprintf(stderr, "string not ending with quote"); exit(-1); } } (void) devctl_ddef_string_array(ddef_hdl, pname, nstr, strs); free(strs); } static int bus_devcreate(devctl_hdl_t bus_dcp) { int rv; char **propp = dev_props; devctl_ddef_t ddef_hdl = NULL; devctl_hdl_t dev_hdl = NULL; ddef_hdl = devctl_ddef_alloc(dev_name, 0); if (dev_props == NULL) { (void) fprintf(stderr, "dev-create: missing device props\n"); return (-1); } while (*propp) { add_prop(ddef_hdl, *propp); propp++; } if (devctl_bus_dev_create(bus_dcp, ddef_hdl, 0, &dev_hdl)) { (void) fprintf(stderr, "bus-devcreate: failed to create device node\n"); rv = -1; } else if (devctl_get_pathname(dev_hdl, devctl_device, MAXPATHLEN) == NULL) { (void) fprintf(stderr, "bus-devcreate: failed to get device path\n"); rv = -1; } else { (void) printf("created device %s\n", devctl_device); rv = 0; } devctl_ddef_free(ddef_hdl); if (dev_hdl) devctl_release(dev_hdl); return (rv); } static void print_bus_state(char *devname, uint_t state) { (void) printf("\t%s: ", devname); if (state == BUS_QUIESCED) (void) printf("Quiesced"); else if (state == BUS_ACTIVE) (void) printf("Active"); else if (state == BUS_SHUTDOWN) (void) printf("Shutdown"); (void) printf("\n"); } static void print_dev_state(char *devname, uint_t state) { (void) printf("\t%s: ", devname); if (state & DEVICE_ONLINE) { (void) printf("Online"); if (state & DEVICE_BUSY) (void) printf(" Busy"); if (state & DEVICE_DOWN) (void) printf(" Down"); } else { if (state & DEVICE_OFFLINE) { (void) printf("Offline"); if (state & DEVICE_DOWN) (void) printf(" Down"); } } (void) printf("\n"); } static void setpname(char *name) { register char *p; if (p = strrchr(name, '/')) progname = p + 1; else progname = name; } static struct cmds * dc_cmd(struct cmds *cmd_tbl, char *devctl_cmdname) { int i; for (i = 0; cmd_tbl[i].cmdname != NULL; i++) { if (strcasecmp(cmd_tbl[i].cmdname, devctl_cmdname) == 0) return (&cmd_tbl[i]); } return (NULL); } /* * list all nexus drivers exporting the :devctl minor device */ static void run_list_ctlrs(void) { di_node_t dinode; if ((dinode = di_init("/", DINFOSUBTREE|DINFOMINOR)) == NULL) { (void) fprintf(stderr, "%s: di_init() failed\n", progname); exit(-1); } (void) di_walk_minor(dinode, DDI_NT_NEXUS, 0, NULL, &nexif); di_fini(dinode); exit(0); } /*ARGSUSED*/ static int nexif(di_node_t din, di_minor_t dim, void *arg) { char *devname; if ((devname = di_devfs_path(din)) != NULL) { (void) printf("%s%d: /devices%s\n", di_driver_name(din), di_instance(din), devname); di_devfs_path_free(devname); } return (DI_WALK_CONTINUE); } void * s_malloc(size_t len) { void *buf = malloc(len); if (buf == NULL) { perror("s_malloc failed"); exit(-1); } return (buf); } void * s_realloc(void *ptr, size_t len) { void *buf = realloc(ptr, len); if (buf == NULL) { perror("s_realloc failed"); exit(-1); } return (buf); } char * s_strdup(char *str) { char *buf = strdup(str); if (buf == NULL) { perror("s_malloc failed"); exit(-1); } return (buf); }