/*
* 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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <limits.h>
#include <synch.h>
#include <libintl.h>
#include <errno.h>
#include <libdevinfo.h>
#include <sys/uio.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <stropts.h>
#include <sys/stream.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mkdev.h>
#include <sys/param.h>
#include <sys/openpromio.h>
#include <sys/ttymuxuser.h>
#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));
}