/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ttymux_rcm_impl.h" #include "rcm_module.h" #define TTYMUX_OFFLINE_ERR gettext("Resource is in use by") #define TTYMUX_UNKNOWN_ERR gettext("Unknown Operation attempted") #define TTYMUX_ONLINE_ERR gettext("Failed to connect under multiplexer") #define TTYMUX_INVALID_ERR gettext("Invalid Operation on this resource") #define TTYMUX_OFFLINE_FAIL gettext("Failed to disconnect from multiplexer") #define TTYMUX_MEMORY_ERR gettext("TTYMUX: strdup failure\n") static int msglvl = 6; /* print messages less than this level */ #define TEST(cond, stmt) { if (cond) stmt; } #define _msg(lvl, args) TEST(msglvl > (lvl), trace args) static int oflags = O_EXCL|O_RDWR|O_NONBLOCK|O_NOCTTY; static dev_t cn_dev = NODEV; static rsrc_t *cn_rsrc = NULL; static rsrc_t cache_head; static rsrc_t cache_tail; static mutex_t cache_lock; static char muxctl[PATH_MAX] = {0}; static char muxcon[PATH_MAX] = {0}; static int muxfd; static boolean_t register_rsrcs; /* module interface routines */ static int tty_register(rcm_handle_t *); static int tty_unregister(rcm_handle_t *); static int tty_getinfo(rcm_handle_t *, char *, id_t, uint_t, char **, char **, nvlist_t *, rcm_info_t **); static int tty_suspend(rcm_handle_t *, char *, id_t, timespec_t *, uint_t, char **, rcm_info_t **); static int tty_resume(rcm_handle_t *, char *, id_t, uint_t, char **, rcm_info_t **); static int tty_offline(rcm_handle_t *, char *, id_t, uint_t, char **, rcm_info_t **); static int tty_online(rcm_handle_t *, char *, id_t, uint_t, char **, rcm_info_t **); static int tty_remove(rcm_handle_t *, char *, id_t, uint_t, char **, rcm_info_t **); static int get_devpath(char *, char **, dev_t *); /* * Module-Private data */ static struct rcm_mod_ops tty_ops = { RCM_MOD_OPS_VERSION, tty_register, tty_unregister, tty_getinfo, tty_suspend, tty_resume, tty_offline, tty_online, tty_remove, NULL, NULL }; /*PRINTFLIKE1*/ static void trace(char *fmt, ...) { va_list args; char buf[256]; int sz; va_start(args, fmt); sz = vsnprintf(buf, sizeof (buf), fmt, args); va_end(args); if (sz < 0) rcm_log_message(RCM_TRACE1, _("TTYMUX: vsnprintf parse error\n")); else if (sz > sizeof (buf)) { char *b = malloc(sz + 1); if (b != NULL) { va_start(args, fmt); sz = vsnprintf(b, sz + 1, fmt, args); va_end(args); if (sz > 0) rcm_log_message(RCM_TRACE1, _("%s"), b); free(b); } } else { rcm_log_message(RCM_TRACE1, _("%s"), buf); } } /* * CACHE MANAGEMENT * Resources managed by this module are stored in a list of rsrc_t * structures. */ /* * cache_lookup() * * Get a cache node for a resource. Call with cache lock held. */ static rsrc_t * cache_lookup(const char *resource) { rsrc_t *rsrc; rsrc = cache_head.next; while (rsrc != &cache_tail) { if (rsrc->id && strcmp(resource, rsrc->id) == 0) { return (rsrc); } rsrc = rsrc->next; } return (NULL); } /* * Get a cache node for a minor node. Call with cache lock held. */ static rsrc_t * cache_lookup_bydevt(dev_t devt) { rsrc_t *rsrc; rsrc = cache_head.next; while (rsrc != &cache_tail) { if (rsrc->dev == devt) return (rsrc); rsrc = rsrc->next; } return (NULL); } /* * free_node() * * Free a node. Make sure it isn't in the list! */ static void free_node(rsrc_t *node) { if (node) { if (node->id) { free(node->id); } free(node); } } /* * cache_insert() * * Call with the cache_lock held. */ static void cache_insert(rsrc_t *node) { /* insert at the head for best performance */ node->next = cache_head.next; node->prev = &cache_head; node->next->prev = node; node->prev->next = node; } /* * cache_create() * * Call with the cache_lock held. */ static rsrc_t * cache_create(const char *resource, dev_t dev) { rsrc_t *rsrc = malloc(sizeof (rsrc_t)); if (rsrc != NULL) { if ((rsrc->id = strdup(resource)) != NULL) { rsrc->dev = dev; rsrc->flags = 0; rsrc->dependencies = NULL; cache_insert(rsrc); } else { free(rsrc); rsrc = NULL; } } else { _msg(0, ("TTYMUX: malloc failure for resource %s.\n", resource)); } return (rsrc); } /* * cache_get() * * Call with the cache_lock held. */ static rsrc_t * cache_get(const char *resource) { rsrc_t *rsrc = cache_lookup(resource); if (rsrc == NULL) { dev_t dev; (void) get_devpath((char *)resource, NULL, &dev); rsrc = cache_create(resource, dev); } return (rsrc); } /* * cache_remove() * * Call with the cache_lock held. */ static void cache_remove(rsrc_t *node) { node->next->prev = node->prev; node->prev->next = node->next; node->next = NULL; node->prev = NULL; } /* * Open a file identified by fname with the given open flags. * If the request is to open a file with exclusive access and the open * fails then backoff exponentially and then retry the open. * Do not wait for longer than about a second (since this may be an rcm * framework thread). */ static int open_file(char *fname, int flags) { int fd, cnt; struct timespec ts; if ((flags & O_EXCL) == 0) return (open(fname, flags)); ts.tv_sec = 0; ts.tv_nsec = 16000000; /* 16 milliseconds */ for (cnt = 0; cnt < 5 && (fd = open(fname, flags)) == -1; cnt++) { (void) nanosleep(&ts, NULL); ts.tv_nsec *= 2; } return (fd); } /* * No-op for creating an association between a pair of resources. */ /*ARGSUSED*/ static int nullconnect(link_t *link) { return (0); } /* * No-op for destroying an association between a pair of resources. */ /*ARGSUSED*/ static int nulldisconnect(link_t *link) { return (0); } /* * Record an actual or desired association between two resources * identified by their rsrc_t structures. */ static link_t * add_dependency(rsrc_t *user, rsrc_t *used) { link_t *linkhead; link_t *link; if (user == NULL || used == NULL) return (NULL); if (user->id && used->id && strcmp(user->id, used->id) == 0) { _msg(2, ("TTYMUX: attempt to connect devices created by " "the same driver\n")); return (NULL); } /* * Search for all resources that this resource user is depending * upon. */ linkhead = user->dependencies; for (link = linkhead; link != NULL; link = link->next) { /* * Does the using resource already depends on the used * resource */ if (link->used == used) return (link); } link = malloc(sizeof (link_t)); if (link == NULL) { rcm_log_message(RCM_ERROR, _("TTYMUX: Out of memory\n")); return (NULL); } _msg(6, ("TTYMUX: New link user %s used %s\n", user->id, used->id)); link->user = user; link->used = used; link->linkid = 0; link->state = UNKNOWN; link->flags = 0; link->connect = nullconnect; link->disconnect = nulldisconnect; link->next = linkhead; user->dependencies = link; return (link); } /* * Send an I_STR stream ioctl to a device */ static int istrioctl(int fd, int cmd, void *data, int datalen, int *bytes) { struct strioctl ios; int rval; ios.ic_timout = 0; /* use the default */ ios.ic_cmd = cmd; ios.ic_dp = (char *)data; ios.ic_len = datalen; rval = ioctl(fd, I_STR, (char *)&ios); if (bytes) *bytes = ios.ic_len; return (rval); } /* * Streams link the driver identified by fd underneath a mux * identified by ctrl_fd. */ static int plink(int ctrl_fd, int fd) { int linkid; /* * pop any modules off the lower stream. */ while (ioctl(fd, I_POP, 0) == 0) ; if ((linkid = ioctl(ctrl_fd, I_PLINK, fd)) < 0) rcm_log_message(RCM_ERROR, _("TTYMUX: I_PLINK error %d.\n"), errno); return (linkid); } /* * Streams unlink the STREAM identified by linkid from a mux * identified by ctrl_fd. */ static int punlink(int ctrl_fd, int linkid) { if (ioctl(ctrl_fd, I_PUNLINK, linkid) < 0) return (errno); else return (0); } /* * Connect a pair of resources by establishing the dependency association. * Only works for devices that support the TTYMUX ioctls. */ static int mux_connect(link_t *link) { int lfd; int rv; ttymux_assoc_t as; uint8_t ioflags; _msg(6, ("TTYMUX: mux_connect (%ld:%ld<->%ld:%ld %s <-> %s\n", major(link->user->dev), minor(link->user->dev), major(link->used->dev), minor(link->used->dev), link->user->id, link->used->id)); _msg(12, ("TTYMUX: struct size = %d (plen %d)\n", sizeof (as), PATH_MAX)); if (link->user->dev == NODEV || link->used->dev == NODEV) { /* * One of the resources in the association is not * present (wait for the online notification before * attempting to establish the dependency association. */ return (EAGAIN); } if (major(link->user->dev) == major(link->used->dev)) { _msg(2, ("TTYMUX: attempt to link devices created by " "the same driver\n")); return (EINVAL); } /* * Explicitly check for attempts to plumb the system console - * required becuase not all serial devices support the * O_EXCL open flag. */ if (link->used->dev == cn_dev) { rcm_log_message(RCM_WARNING, _("TTYMUX: Request to link the " " system console under another device not allowed!\n")); return (EPERM); } /* * Ensure that the input/output mode of the dependent is reasonable */ if ((ioflags = link->flags & FORIO) == 0) ioflags = FORIO; /* * Open each resource participating in the association. */ lfd = open(link->used->id, O_EXCL|O_RDWR|O_NONBLOCK|O_NOCTTY); if (lfd == -1) { if (errno == EBUSY) { rcm_log_message(RCM_WARNING, _("TTYMUX: device %s is " " busy - " " cannot connect to %s\n"), link->used->id, link->user->id); } else { rcm_log_message(RCM_WARNING, _("TTYMUX: open error %d for device %s\n"), errno, link->used->id); } return (errno); } /* * Note: Issuing the I_PLINK and TTYMUX_ASSOC request on the 'using' * resource is more generic: * muxfd = open(link->user->id, oflags); * However using the ctl (MUXCTLLINK) node means that any current opens * on the 'using' resource are uneffected. */ /* * Figure out if the 'used' resource is already associated with * some resource - if so tell the caller to try again later. * More generally if any user or kernel thread has the resource * open then the association should not be made. * The ttymux driver makes this check (but it should be done here). */ as.ttymux_linkid = 0; as.ttymux_ldev = link->used->dev; if (istrioctl(muxfd, TTYMUX_GETLINK, (void *)&as, sizeof (as), NULL) == 0) { _msg(7, ("TTYMUX: %ld:%ld (%d) (udev %ld:%ld) already linked\n", major(as.ttymux_ldev), minor(as.ttymux_ldev), as.ttymux_linkid, major(as.ttymux_udev), minor(as.ttymux_udev))); link->linkid = as.ttymux_linkid; if (as.ttymux_udev != NODEV) { (void) close(lfd); return (EAGAIN); } } /* * Now link and associate the used resource under the using resource. */ as.ttymux_udev = link->user->dev; as.ttymux_ldev = link->used->dev; as.ttymux_tag = 0ul; as.ttymux_ioflag = ioflags; _msg(6, ("TTYMUX: connecting %ld:%ld to %ld:%ld\n", major(as.ttymux_ldev), minor(as.ttymux_ldev), major(as.ttymux_udev), minor(as.ttymux_udev))); if (as.ttymux_udev == cn_dev) { struct termios tc; if (ioctl(lfd, TCGETS, &tc) != -1) { tc.c_cflag |= CREAD; if (ioctl(lfd, TCSETSW, &tc) == -1) { rcm_log_message(RCM_WARNING, _("TTYMUX: error %d whilst enabling the " "receiver on device %d:%d\n"), errno, major(as.ttymux_ldev), minor(as.ttymux_ldev)); } } } if (as.ttymux_linkid <= 0 && (as.ttymux_linkid = plink(muxfd, lfd)) <= 0) { rcm_log_message(RCM_WARNING, _("TTYMUX: Link error %d for device %s\n"), errno, link->used->id); rv = errno; goto out; } link->linkid = as.ttymux_linkid; _msg(6, ("TTYMUX: associating\n")); if (istrioctl(muxfd, TTYMUX_ASSOC, (void *)&as, sizeof (as), 0) != 0) { rv = errno; goto out; } _msg(6, ("TTYMUX: Succesfully connected %ld:%ld to %ld:%ld\n", major(as.ttymux_ldev), minor(as.ttymux_ldev), major(as.ttymux_udev), minor(as.ttymux_udev))); link->state = CONNECTED; (void) close(lfd); return (0); out: rcm_log_message(RCM_WARNING, _("TTYMUX: Error [%d] connecting %d:%d to %d:%d\n"), rv, major(as.ttymux_ldev), minor(as.ttymux_ldev), major(as.ttymux_udev), minor(as.ttymux_udev)); (void) close(lfd); if (as.ttymux_linkid > 0) { /* * There was an error so unwind the I_PLINK step */ if (punlink(muxfd, as.ttymux_linkid) != 0) rcm_log_message(RCM_WARNING, _("TTYMUX: Unlink error %d (%s).\n"), errno, link->used->id); } return (rv); } /* * Disconnect a pair of resources by destroying the dependency association. * Only works for devices that support the TTYMUX ioctls. */ static int mux_disconnect(link_t *link) { int rv; ttymux_assoc_t as; _msg(6, ("TTYMUX: mux_disconnect %s<->%s (%ld:%ld<->%ld:%ld)\n", link->user->id, link->used->id, major(link->user->dev), minor(link->user->dev), major(link->used->dev), minor(link->used->dev))); as.ttymux_ldev = link->used->dev; if (istrioctl(muxfd, TTYMUX_GETLINK, (void *)&as, sizeof (as), NULL) != 0) { _msg(1, ("TTYMUX: %ld:%ld not linked [err %d]\n", major(link->used->dev), minor(link->used->dev), errno)); return (0); /* * Do not disassociate console resources - simply * unlink them so that they remain persistent. */ } else if (as.ttymux_udev != cn_dev && istrioctl(muxfd, TTYMUX_DISASSOC, (void *)&as, sizeof (as), 0) == -1) { rv = errno; rcm_log_message(RCM_WARNING, _("TTYMUX: Dissassociate error %d for %s\n"), rv, link->used->id); } else if (punlink(muxfd, as.ttymux_linkid) != 0) { rv = errno; rcm_log_message(RCM_WARNING, _("TTYMUX: Error %d unlinking %d:%d\n"), errno, major(link->used->dev), minor(link->used->dev)); } else { _msg(6, ("TTYMUX: %s<->%s disconnected.\n", link->user->id, link->used->id)); link->state = DISCONNECTED; link->linkid = 0; rv = 0; } return (rv); } /* PESISTENCY */ /* * Given a special device file system path return the /devices path * and/or the device number (dev_t) of the device. */ static int get_devpath(char *dev, char **cname, dev_t *devt) { struct stat sb; if (cname != NULL) *cname = NULL; if (devt != NULL) *devt = NODEV; if (lstat(dev, &sb) < 0) { return (errno); } else if ((sb.st_mode & S_IFMT) == S_IFLNK) { int lsz; char linkbuf[PATH_MAX+1]; if (stat(dev, &sb) < 0) return (errno); lsz = readlink(dev, linkbuf, PATH_MAX); if (lsz <= 0) return (ENODEV); linkbuf[lsz] = '\0'; dev = strstr(linkbuf, "/devices"); if (dev == NULL) return (ENODEV); } if (cname != NULL) *cname = strdup(dev); if (devt != NULL) *devt = sb.st_rdev; return (0); } /* * See routine locate_node */ static int locate_dev(di_node_t node, di_minor_t minor, void *arg) { char *devfspath; char resource[PATH_MAX]; rsrc_t *rsrc; if (di_minor_devt(minor) != (dev_t)arg) return (DI_WALK_CONTINUE); if ((devfspath = di_devfs_path(node)) == NULL) return (DI_WALK_TERMINATE); if (snprintf(resource, sizeof (resource), "/devices%s:%s", devfspath, di_minor_name(minor)) > sizeof (resource)) { di_devfs_path_free(devfspath); return (DI_WALK_TERMINATE); } di_devfs_path_free(devfspath); rsrc = cache_lookup(resource); if (rsrc == NULL && (rsrc = cache_create(resource, di_minor_devt(minor))) == NULL) return (DI_WALK_TERMINATE); rsrc->dev = di_minor_devt(minor); rsrc->flags |= PRESENT; rsrc->flags &= ~UNKNOWN; return (DI_WALK_TERMINATE); } /* * Find a devinfo node that matches the device argument (dev). * This is an expensive search of the whole device tree! */ static rsrc_t * locate_node(dev_t dev, di_node_t *root) { rsrc_t *rsrc; assert(root != NULL); if ((rsrc = cache_lookup_bydevt(dev)) != NULL) return (rsrc); (void) di_walk_minor(*root, NULL, 0, (void*)dev, locate_dev); return (cache_lookup_bydevt(dev)); } /* * Search for any existing dependency relationships managed by this * RCM module. */ static int probe_dependencies() { ttymux_assocs_t links; ttymux_assoc_t *asp; int cnt, n; rsrc_t *ruser; rsrc_t *used; link_t *link; di_node_t root; cnt = istrioctl(muxfd, TTYMUX_LIST, (void *)0, 0, 0); _msg(8, ("TTYMUX: Probed %d links [%d]\n", cnt, errno)); if (cnt <= 0) return (0); if ((links.ttymux_assocs = calloc(cnt, sizeof (ttymux_assoc_t))) == 0) return (EAGAIN); links.ttymux_nlinks = cnt; n = istrioctl(muxfd, TTYMUX_LIST, (void *)&links, sizeof (links), 0); if (n == -1) { _msg(2, ("TTYMUX: Probe error %s\n", strerror(errno))); free(links.ttymux_assocs); return (0); } asp = (ttymux_assoc_t *)links.ttymux_assocs; if ((root = di_init("/", DINFOSUBTREE|DINFOMINOR)) == DI_NODE_NIL) return (errno); (void) mutex_lock(&cache_lock); for (; cnt--; asp++) { _msg(7, ("TTYMUX: Probed: %ld %ld %ld:%ld <-> %ld:%ld\n", asp->ttymux_udev, asp->ttymux_ldev, major(asp->ttymux_udev), minor(asp->ttymux_udev), major(asp->ttymux_ldev), minor(asp->ttymux_ldev))); /* * The TTYMUX_LIST ioctl can return links relating * to potential devices. Such devices are identified * in the path field. */ if (asp->ttymux_ldev == NODEV) { char buf[PATH_MAX]; if (asp->ttymux_path == NULL || *asp->ttymux_path != '/') continue; if (snprintf(buf, sizeof (buf), "/devices%s", asp->ttymux_path) > sizeof (buf)) continue; used = cache_get(buf); } else { used = locate_node(asp->ttymux_ldev, &root); } if ((ruser = locate_node(asp->ttymux_udev, &root)) == NULL) { _msg(7, ("TTYMUX: Probe: %ld:%ld not present\n", major(asp->ttymux_udev), minor(asp->ttymux_udev))); continue; } if (used == NULL) { _msg(7, ("TTYMUX: Probe: %ld:%ld not present\n", major(asp->ttymux_ldev), minor(asp->ttymux_ldev))); continue; } _msg(6, ("TTYMUX: Probe: Restore %s <-> %s (id %d)\n", ruser->id, used->id, asp->ttymux_linkid)); link = add_dependency(ruser, used); if (link != NULL) { link->flags = (uint_t)asp->ttymux_ioflag; link->linkid = asp->ttymux_linkid; link->state = CONNECTED; link->connect = mux_connect; link->disconnect = mux_disconnect; } } di_fini(root); (void) mutex_unlock(&cache_lock); free(links.ttymux_assocs); return (0); } /* * A resource has become available. Re-establish any associations involving * the resource. */ static int rsrc_available(rsrc_t *rsrc) { link_t *link; rsrc_t *rs; if (rsrc->dev == NODEV) { /* * Now that the resource is present obtain its device number. * For this to work the node must be present in the /devices * tree (see devfsadm(1M) or drvconfig(1M)). * We do not use libdevinfo because the node must be present * under /devices for the connect step below to work * (the node needs to be opened). */ (void) get_devpath(rsrc->id, NULL, &rsrc->dev); if (rsrc->dev == NODEV) { _msg(4, ("Device node %s does not exist\n", rsrc->id)); /* * What does RCM do with failed online notifications. */ return (RCM_FAILURE); } } for (rs = cache_head.next; rs != &cache_tail; rs = rs->next) { for (link = rs->dependencies; link != NULL; link = link->next) { if (link->user == rsrc || link->used == rsrc) { _msg(6, ("TTYMUX: re-connect\n")); (void) link->connect(link); } } } return (RCM_SUCCESS); } /* * A resource is going away. Tear down any associations involving * the resource. */ static int rsrc_unavailable(rsrc_t *rsrc) { link_t *link; rsrc_t *rs; for (rs = cache_head.next; rs != &cache_tail; rs = rs->next) { for (link = rs->dependencies; link != NULL; link = link->next) { if (link->user == rsrc || link->used == rsrc) { _msg(6, ("TTYMUX: unavailable %s %s\n", link->user->id, link->used->id)); (void) link->disconnect(link); } } } return (RCM_SUCCESS); } /* * Find any resources that are using a given resource (identified by * the rsrc argument). The search begins after the resource identified * by the next argument. If next is NULL start at the first resource * in this RCM modules resource list. If the redundancy argument is * greater than zero then a resource which uses rsrc will only be * returned if it is associated with >= redundancy dependents. * * Thus, provided that the caller keeps the list locked he can iterate * through all the resources in the cache that depend upon rsrc. */ static rsrc_t * get_next_user(rsrc_t *next, rsrc_t *rsrc, int redundancy) { rsrc_t *src; link_t *link; int cnt = 0; boolean_t inuse; src = (next != NULL) ? next->next : cache_head.next; while (src != &cache_tail) { inuse = B_FALSE; for (link = src->dependencies, cnt = 0; link != NULL; link = link->next) { if (link->state == CONNECTED) cnt++; if (link->used == rsrc) inuse = B_TRUE; } if (inuse == B_TRUE && (redundancy <= 0 || cnt == redundancy)) { return (src); } src = src->next; } _msg(8, ("TTYMUX: count_users(%s) res %d.\n", rsrc->id, cnt)); return (NULL); } /* * Common handler for RCM notifications. */ /*ARGSUSED*/ static int rsrc_change_common(rcm_handle_t *hd, int op, const char *rsrcid, uint_t flag, char **reason, rcm_info_t **dependent_reason, void *arg) { rsrc_t *rsrc, *user; int rv, len; char *tmp = NULL; (void) mutex_lock(&cache_lock); rsrc = cache_lookup(rsrcid); if (rsrc == NULL) { /* shouldn't happen because rsrc has been registered */ (void) mutex_unlock(&cache_lock); return (RCM_SUCCESS); } if ((muxfd = open_file(muxctl, oflags)) == -1) { rcm_log_message(RCM_ERROR, _("TTYMUX: %s unavailable: %s\n"), muxctl, strerror(errno)); (void) mutex_unlock(&cache_lock); return (RCM_SUCCESS); } switch (op) { case TTYMUX_SUSPEND: rv = RCM_FAILURE; _msg(4, ("TTYMUX: SUSPEND %s operation refused.\n", rsrc->id)); if ((*reason = strdup(TTYMUX_INVALID_ERR)) == NULL) { rcm_log_message(RCM_ERROR, TTYMUX_MEMORY_ERR); } break; case TTYMUX_REMOVE: rsrc->flags |= UNKNOWN; rsrc->flags &= ~(PRESENT | REGISTERED); rv = RCM_SUCCESS; break; case TTYMUX_OFFLINE: user = get_next_user(NULL, rsrc, 1); if (flag & RCM_QUERY) { rv = ((flag & RCM_FORCE) || (user == NULL)) ? RCM_SUCCESS : RCM_FAILURE; if (rv == RCM_FAILURE) { tmp = TTYMUX_OFFLINE_ERR; assert(tmp != NULL); len = strlen(tmp) + strlen(user->id) + 2; if ((*reason = (char *)malloc(len)) != NULL) { (void) snprintf(*reason, len, "%s %s", tmp, user->id); } else { rcm_log_message(RCM_ERROR, TTYMUX_MEMORY_ERR); } } } else if (flag & RCM_FORCE) { rv = rsrc_unavailable(rsrc); if (rv == RCM_FAILURE) { if ((*reason = strdup(TTYMUX_OFFLINE_FAIL)) == NULL) { rcm_log_message(RCM_ERROR, TTYMUX_MEMORY_ERR); } } } else if (user != NULL) { rv = RCM_FAILURE; tmp = TTYMUX_OFFLINE_ERR; assert(tmp != NULL); len = strlen(tmp) + strlen(user->id) + 2; if ((*reason = (char *)malloc(len)) != NULL) { (void) snprintf(*reason, len, "%s %s", tmp, user->id); } else { rcm_log_message(RCM_ERROR, TTYMUX_MEMORY_ERR); } } else { rv = rsrc_unavailable(rsrc); if (rv == RCM_FAILURE) { if ((*reason = strdup(TTYMUX_OFFLINE_FAIL)) == NULL) { rcm_log_message(RCM_ERROR, TTYMUX_MEMORY_ERR); } } } if (rv == RCM_FAILURE) { _msg(4, ("TTYMUX: OFFLINE %s operation refused.\n", rsrc->id)); } else { _msg(4, ("TTYMUX: OFFLINE %s res %d.\n", rsrc->id, rv)); } break; case TTYMUX_RESUME: rv = RCM_FAILURE; _msg(4, ("TTYMUX: RESUME %s operation refused.\n", rsrc->id)); if ((*reason = strdup(TTYMUX_INVALID_ERR)) == NULL) { rcm_log_message(RCM_ERROR, TTYMUX_MEMORY_ERR); } break; case TTYMUX_ONLINE: _msg(4, ("TTYMUX: ONLINE %s res %d.\n", rsrc->id, rv)); rv = rsrc_available(rsrc); if (rv == RCM_FAILURE) { if ((*reason = strdup(TTYMUX_ONLINE_ERR)) == NULL) { rcm_log_message(RCM_ERROR, TTYMUX_MEMORY_ERR); } } break; default: rv = RCM_FAILURE; if ((*reason = strdup(TTYMUX_UNKNOWN_ERR)) == NULL) { rcm_log_message(RCM_ERROR, TTYMUX_MEMORY_ERR); } } (void) close(muxfd); (void) mutex_unlock(&cache_lock); return (rv); } static boolean_t find_mux_nodes(char *drv) { di_node_t root, node; di_minor_t dim; char *devfspath; char muxctlname[] = "ctl"; char muxconname[] = "con"; int nminors = 0; (void) strcpy(muxctl, MUXCTLLINK); (void) strcpy(muxcon, MUXCONLINK); cn_rsrc = NULL; if ((root = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) { rcm_log_message(RCM_WARNING, _("di_init error\n")); return (B_FALSE); } node = di_drv_first_node(drv, root); if (node == DI_NODE_NIL) { _msg(4, ("no node for %s\n", drv)); di_fini(root); return (B_FALSE); } /* * If the device is not a prom node do not continue. */ if (di_nodeid(node) != DI_PROM_NODEID) { di_fini(root); return (B_FALSE); } if ((devfspath = di_devfs_path(node)) == NULL) { di_fini(root); return (B_FALSE); } /* * Loop through all the minor nodes the driver (drv) looking * for the ctl node (this is the device on which * to issue ioctls). */ dim = DI_MINOR_NIL; while ((dim = di_minor_next(node, dim)) != DI_MINOR_NIL) { _msg(7, ("MUXNODES: minor %s\n", di_minor_name(dim))); if (strcmp(di_minor_name(dim), muxctlname) == 0) { if (snprintf(muxctl, sizeof (muxctl), "/devices%s:%s", devfspath, di_minor_name(dim)) > sizeof (muxctl)) { _msg(1, ("muxctl:snprintf error\n")); } if (++nminors == 2) break; } else if (strcmp(di_minor_name(dim), muxconname) == 0) { if (snprintf(muxcon, sizeof (muxcon), "/devices%s:%s", devfspath, di_minor_name(dim)) > sizeof (muxcon)) { _msg(1, ("muxcon:snprintf error\n")); } if (++nminors == 2) break; } } di_devfs_path_free(devfspath); di_fini(root); if ((muxfd = open_file(muxctl, oflags)) != -1) { if (istrioctl(muxfd, TTYMUX_CONSDEV, (void *)&cn_dev, sizeof (cn_dev), 0) != 0) { cn_dev = NODEV; } else { _msg(8, ("MUXNODES: found sys console: %ld:%ld\n", major(cn_dev), minor(cn_dev))); cn_rsrc = cache_create(muxcon, cn_dev); if (cn_rsrc != NULL) { cn_rsrc->flags |= PRESENT; cn_rsrc->flags &= ~UNKNOWN; } } (void) close(muxfd); if (cn_dev != NODEV) return (B_TRUE); } else { _msg(1, ("TTYMUX: %s unavailable: %s\n", muxctl, strerror(errno))); } return (B_FALSE); } /* * Update registrations, and return the ops structure. */ struct rcm_mod_ops * rcm_mod_init() { _msg(4, ("TTYMUX: mod_init:\n")); cache_head.next = &cache_tail; cache_head.prev = NULL; cache_tail.prev = &cache_head; cache_tail.next = NULL; (void) mutex_init(&cache_lock, NULL, NULL); /* * Find the multiplexer ctl and con nodes */ register_rsrcs = find_mux_nodes(TTYMUX_DRVNAME); return (&tty_ops); } /* * Save state and release resources. */ int rcm_mod_fini() { rsrc_t *rsrc; link_t *link, *nlink; _msg(7, ("TTYMUX: freeing cache.\n")); (void) mutex_lock(&cache_lock); rsrc = cache_head.next; while (rsrc != &cache_tail) { cache_remove(rsrc); for (link = rsrc->dependencies; link != NULL; ) { nlink = link->next; free(link); link = nlink; } free_node(rsrc); rsrc = cache_head.next; } (void) mutex_unlock(&cache_lock); (void) mutex_destroy(&cache_lock); return (RCM_SUCCESS); } /* * Return a string describing this module. */ const char * rcm_mod_info() { return ("Serial mux device module 1.1"); } /* * RCM Notification Handlers */ static int tty_register(rcm_handle_t *hd) { rsrc_t *rsrc; link_t *link; int rv; if (register_rsrcs == B_FALSE) return (RCM_SUCCESS); if ((muxfd = open_file(muxctl, oflags)) == -1) { rcm_log_message(RCM_ERROR, _("TTYMUX: %s unavailable: %s\n"), muxctl, strerror(errno)); return (RCM_SUCCESS); } /* * Search for any new dependencies since the last notification or * since module was initialisated. */ (void) probe_dependencies(); /* * Search the whole cache looking for any unregistered used resources * and register them. Note that the 'using resource' (a ttymux device * node) is not subject to DR operations so there is no need to * register them with the RCM framework. */ (void) mutex_lock(&cache_lock); for (rsrc = cache_head.next; rsrc != &cache_tail; rsrc = rsrc->next) { _msg(6, ("TTYMUX: REGISTER rsrc %s flags %d\n", rsrc->id, rsrc->flags)); if (rsrc->dependencies != NULL && (rsrc->flags & REGISTERED) == 0) { _msg(6, ("TTYMUX: Registering rsrc %s\n", rsrc->id)); rv = rcm_register_interest(hd, rsrc->id, 0, NULL); if (rv == RCM_SUCCESS) rsrc->flags |= REGISTERED; } for (link = rsrc->dependencies; link != NULL; link = link->next) { if ((link->used->flags & REGISTERED) != 0) continue; _msg(6, ("TTYMUX: Registering rsrc %s\n", link->used->id)); rv = rcm_register_interest(hd, link->used->id, 0, NULL); if (rv != RCM_SUCCESS) rcm_log_message(RCM_WARNING, _("TTYMUX: err %d registering %s\n"), rv, link->used->id); else link->used->flags |= REGISTERED; } } (void) mutex_unlock(&cache_lock); (void) close(muxfd); return (RCM_SUCCESS); } /* * Unregister all registrations. */ static int tty_unregister(rcm_handle_t *hd) { rsrc_t *rsrc; (void) mutex_lock(&cache_lock); /* * Search every resource in the cache and if it has been registered * then unregister it from the RCM framework. */ for (rsrc = cache_head.next; rsrc != &cache_tail; rsrc = rsrc->next) { if ((rsrc->flags & REGISTERED) == 0) continue; if (rcm_unregister_interest(hd, rsrc->id, 0) != RCM_SUCCESS) rcm_log_message(RCM_WARNING, _("TTYMUX: Failed to unregister %s\n"), rsrc->id); else rsrc->flags &= ~REGISTERED; } (void) mutex_unlock(&cache_lock); return (RCM_SUCCESS); } /* * Report resource usage information. */ /*ARGSUSED*/ static int tty_getinfo(rcm_handle_t *hd, char *rsrcid, id_t id, uint_t flag, char **info, char **errstr, nvlist_t *proplist, rcm_info_t **depend_info) { rsrc_t *rsrc, *user; char *ru; size_t sz; (void) mutex_lock(&cache_lock); rsrc = cache_lookup(rsrcid); if (rsrc == NULL) { (void) mutex_unlock(&cache_lock); *errstr = strdup(gettext("Unmanaged resource")); return (RCM_FAILURE); } ru = strdup(gettext("Resource Users")); user = NULL; while ((user = get_next_user(user, rsrc, -1)) != NULL) { *info = ru; sz = strlen(*info) + strlen(user->id) + 2; ru = malloc(sz); if (ru == NULL) { free(*info); *info = NULL; break; } if (snprintf(ru, sz, ": %s%s", *info, user->id) > sz) { _msg(4, ("tty_getinfo: snprintf error.\n")); } free(*info); } *info = ru; if (*info == NULL) { (void) mutex_unlock(&cache_lock); *errstr = strdup(gettext("Short of memory resources")); return (RCM_FAILURE); } (void) mutex_unlock(&cache_lock); return (RCM_SUCCESS); } /*ARGSUSED*/ static int tty_offline(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags, char **reason, rcm_info_t **dependent_reason) { return (rsrc_change_common(hd, TTYMUX_OFFLINE, rsrc, flags, reason, dependent_reason, NULL)); } /*ARGSUSED*/ static int tty_remove(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags, char **reason, rcm_info_t **dependent_reason) { return (rsrc_change_common(hd, TTYMUX_REMOVE, rsrc, flags, reason, dependent_reason, NULL)); } /*ARGSUSED*/ static int tty_suspend(rcm_handle_t *hd, char *rsrc, id_t id, timespec_t *interval, uint_t flag, char **reason, rcm_info_t **dependent_reason) { return (rsrc_change_common(hd, TTYMUX_SUSPEND, rsrc, flag, reason, dependent_reason, (void *)interval)); } /*ARGSUSED*/ static int tty_online(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags, char **reason, rcm_info_t **dependent_reason) { return (rsrc_change_common(hd, TTYMUX_ONLINE, rsrc, flags, reason, dependent_reason, NULL)); } /*ARGSUSED*/ static int tty_resume(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags, char **reason, rcm_info_t **dependent_reason) { return (rsrc_change_common(hd, TTYMUX_RESUME, rsrc, flags, reason, dependent_reason, NULL)); }