OpenSolaris_b135/uts/sun4/io/efcode/fc_subr.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Kernel framework functions for the fcode interpreter
 */

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/esunddi.h>
#include <sys/ksynch.h>
#include <sys/modctl.h>
#include <sys/errno.h>
#include <sys/fcode.h>

#ifdef	DEBUG
int fcode_debug = 0;
#else
int fcode_debug = 0;
#endif

static kmutex_t fc_request_lock;
static kmutex_t fc_resource_lock;
static kmutex_t fc_hash_lock;
static kmutex_t fc_device_tree_lock;
static kmutex_t fc_phandle_lock;
static kcondvar_t fc_request_cv;
static struct fc_request *fc_request_head;
static int fc_initialized;

static void fcode_timer(void *);

int fcode_timeout = 300;	/* seconds */

int fcodem_unloadable;

extern int hz;

/*
 * Initialize the fcode interpreter framework ... must be called
 * prior to activating any of the fcode interpreter framework including
 * the driver.
 */
static void
fcode_init(void)
{
	if (fc_initialized)
		return;

	mutex_init(&fc_request_lock, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&fc_resource_lock, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&fc_hash_lock, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&fc_device_tree_lock, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&fc_phandle_lock, NULL, MUTEX_DRIVER, NULL);
	cv_init(&fc_request_cv, NULL, CV_DRIVER, NULL);
	++fc_initialized;
}

static void
fcode_fini(void)
{
	mutex_destroy(&fc_request_lock);
	mutex_destroy(&fc_resource_lock);
	mutex_destroy(&fc_hash_lock);
	cv_destroy(&fc_request_cv);
	fc_initialized = 0;
}

/*
 * Module linkage information for the kernel.
 */
static struct modlmisc modlmisc = {
	&mod_miscops, "FCode framework 1.13"
};

static struct modlinkage modlinkage = {
	MODREV_1, (void *)&modlmisc, NULL
};

int
_init(void)
{
	int error;

	fcode_init();
	if ((error = mod_install(&modlinkage)) != 0)
		fcode_fini();
	return (error);
}

int
_fini(void)
{
	int error = EBUSY;

	if (fcodem_unloadable)
		if ((error = mod_remove(&modlinkage)) == 0)
			fcode_fini();

	return (error);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

/*
 * Framework function to invoke the interpreter. Wait and return when the
 * interpreter is done. See fcode.h for details.
 */
int
fcode_interpreter(dev_info_t *ap, fc_ops_t *ops, fco_handle_t handle)
{
	struct fc_request *fp, *qp;
	int error;

	ASSERT(fc_initialized);
	ASSERT(ap);
	ASSERT(ops);
	ASSERT(handle);

	/*
	 * Create a request structure
	 */
	fp = kmem_zalloc(sizeof (struct fc_request), KM_SLEEP);

	fp->next = NULL;
	fp->busy = FC_R_INIT;
	fp->error = FC_SUCCESS;
	fp->ap_dip = ap;
	fp->ap_ops = ops;
	fp->handle = handle;

	/*
	 * Add the request to the end of the request list.
	 */
	mutex_enter(&fc_request_lock);

	if (fc_request_head == NULL)
		fc_request_head = fp;
	else {
		for (qp = fc_request_head; qp->next != NULL; qp = qp->next)
			/* empty */;
		qp->next = fp;
	}
	mutex_exit(&fc_request_lock);

	/*
	 * log a message (ie: i_ddi_log_event) indicating that a request
	 * has been queued to start the userland fcode interpreter.
	 * This call is the glue to the eventd and automates the process.
	 */

	/*
	 * Signal the driver if it's waiting for a request to be queued.
	 */
	cv_broadcast(&fc_request_cv);

	/*
	 * Wait for the request to be serviced
	 */
	mutex_enter(&fc_request_lock);
	fp->timeout = timeout(fcode_timer, fp, hz * fcode_timeout);
	while (fp->busy != FC_R_DONE)
		cv_wait(&fc_request_cv, &fc_request_lock);

	if (fp->timeout) {
		(void) untimeout(fp->timeout);
		fp->timeout = NULL;
	}

	/*
	 * Remove the request from the queue (while still holding the lock)
	 */
	if (fc_request_head == fp)
		fc_request_head = fp->next;
	else {
		for (qp = fc_request_head; qp->next != fp; qp = qp->next)
			/* empty */;
		qp->next = fp->next;
	}
	mutex_exit(&fc_request_lock);

	FC_DEBUG1(2, CE_CONT, "fcode_interpreter: request finished, fp %p\n",
	    fp);

	/*
	 * Free the request structure and return any errors.
	 */
	error = fp->error;
	kmem_free(fp, sizeof (struct fc_request));
	return (error);
}

/*
 * Timeout requests thet don't get picked up by the interpreter.  This
 * would happen if the daemon is not running.  If the timer goes off
 * and it's state is not FC_R_INIT, then the interpreter has picked up the
 * request.
 */
static void
fcode_timer(void *arg)
{
	struct fc_request *fp = arg;

	mutex_enter(&fc_request_lock);
	fp->timeout = 0;
	if (fp->busy == FC_R_INIT) {
		cmn_err(CE_WARN, "fcode_timer: Timeout waiting for "
		    "interpreter - Interpreter did not pick up request\n");
		fp->busy = FC_R_DONE;
		fp->error = FC_TIMEOUT;
		mutex_exit(&fc_request_lock);
		cv_broadcast(&fc_request_cv);
		return;
	} else if (fp->error != FC_SUCCESS) {
		/*
		 * An error was detected, but didn't close the driver.
		 * This will allow the process to error out, returning
		 * the interpreter error code instead of FC_TIMEOUT.
		 */
		fp->busy = FC_R_DONE;
		cv_broadcast(&fc_request_cv);
		mutex_exit(&fc_request_lock);
		return;
	} else {
		cmn_err(CE_WARN, "fcode_timer: Timeout waiting for "
		    "interpreter - Interpreter is executing request\n");
	}
	mutex_exit(&fc_request_lock);
}

/*
 * This is the function the driver calls to wait for and get
 * a request.  The call should be interruptable since it's done
 * at read(2) time, so allow for signals to interrupt us.
 *
 * Return NULL if the wait was interrupted, else return a pointer
 * to the fc_request structure (marked as busy).
 *
 * Note that we have to check for a request first, before waiting,
 * in case the request is already queued. In this case, the signal
 * may have already been delivered.
 */
struct fc_request *
fc_get_request(void)
{
	struct fc_request *fp;

	ASSERT(fc_initialized);

	mutex_enter(&fc_request_lock);

	/*CONSTANTCONDITION*/
	while (1) {
		for (fp = fc_request_head; fp != NULL; fp = fp->next) {
			if (fp->busy == FC_R_INIT) {
				fp->busy = FC_R_BUSY;
				mutex_exit(&fc_request_lock);
				return (fp);
			}
		}
		if (cv_wait_sig(&fc_request_cv, &fc_request_lock) == 0) {
			mutex_exit(&fc_request_lock);
			return (NULL);
		}
	}
	/*NOTREACHED*/
}

/*
 * This is the function the driver calls when it's finished with
 * a request.  Mark the request as done and signal the thread that
 * enqueued the request.
 */
void
fc_finish_request(struct fc_request *fp)
{
	ASSERT(fc_initialized);
	ASSERT(fp);
	ASSERT(fp->busy == FC_R_BUSY);

	mutex_enter(&fc_request_lock);
	fp->busy = FC_R_DONE;
	mutex_exit(&fc_request_lock);

	cv_broadcast(&fc_request_cv);
}

/*
 * Generic resource list management subroutines
 */
void
fc_add_resource(fco_handle_t rp, struct fc_resource *ip)
{
	ASSERT(rp);
	ASSERT(ip);

	mutex_enter(&fc_resource_lock);
	ip->next = NULL;
	if (rp->head != NULL)
		ip->next = rp->head;
	rp->head = ip;
	mutex_exit(&fc_resource_lock);
}

void
fc_rem_resource(fco_handle_t rp, struct fc_resource *ip)
{
	struct fc_resource *fp;

	ASSERT(rp);
	ASSERT(ip);

	if (rp->head == NULL)  {
		cmn_err(CE_CONT, "fc_rem_resource: NULL list head!\n");
		return;
	}

	mutex_enter(&fc_resource_lock);
	if (rp->head == ip) {
		rp->head = ip->next;
		mutex_exit(&fc_resource_lock);
		return;
	}

	for (fp = rp->head; fp && (fp->next != ip); fp = fp->next)
		/* empty */;

	if (fp == NULL)  {
		mutex_exit(&fc_resource_lock);
		cmn_err(CE_CONT, "fc_rem_resource: Item not on list!\n");
		return;
	}

	fp->next = ip->next;
	mutex_exit(&fc_resource_lock);
}

/*ARGSUSED*/
void
fc_lock_resource_list(fco_handle_t rp)
{
	mutex_enter(&fc_resource_lock);
}

/*ARGSUSED*/
void
fc_unlock_resource_list(fco_handle_t rp)
{
	mutex_exit(&fc_resource_lock);
}

/*
 * Common helper ops and subroutines
 */
/*ARGSUSED*/
int
fc_syntax_error(fc_ci_t *cp, char *msg)
{
	cp->error = fc_int2cell(-1);
	cp->nresults = fc_int2cell(0);
	return (0);
}

/*ARGSUSED*/
int
fc_priv_error(fc_ci_t *cp, char *msg)
{
	cp->priv_error = fc_int2cell(-1);
	cp->error = fc_int2cell(0);
	cp->nresults = fc_int2cell(0);
	return (0);
}

/*ARGSUSED*/
int
fc_success_op(dev_info_t *ap, fco_handle_t handle, fc_ci_t *cp)
{
	cp->priv_error = cp->error = fc_int2cell(0);
	return (0);
}

/*
 * fc_fail_op: This 'handles' a request by specifically failing it,
 * as opposed to not handling it and returning '-1' to indicate
 * 'service unknown' and allowing somebody else in the chain to
 * handle it.
 */
/*ARGSUSED*/
int
fc_fail_op(dev_info_t *ap, fco_handle_t handle, fc_ci_t *cp)
{
	cmn_err(CE_CONT, "fcode ops: fail service name <%s>\n",
	    (char *)fc_cell2ptr(cp->svc_name));

	cp->nresults = fc_int2cell(0);
	cp->error = fc_int2cell(-1);
	return (0);
}

/*
 * Functions to manage the set of handles we give to the interpreter.
 * The handles are opaque and internally represent dev_info_t pointers.
 */
struct fc_phandle_entry **
fc_handle_to_phandle_head(fco_handle_t rp)
{
	while (rp->next_handle)
		rp = rp->next_handle;

	return (&rp->ptable);
}

/*ARGSUSED*/
void
fc_phandle_table_alloc(struct fc_phandle_entry **head)
{
}

void
fc_phandle_table_free(struct fc_phandle_entry **head)
{
	struct fc_phandle_entry *ip, *np;

	/*
	 * Free each entry in the table.
	 */
	for (ip = *head; ip; ip = np) {
		np = ip->next;
		kmem_free(ip, sizeof (struct fc_phandle_entry));
	}
	*head = NULL;
}

dev_info_t *
fc_phandle_to_dip(struct fc_phandle_entry **head, fc_phandle_t handle)
{
	struct fc_phandle_entry *ip;

	mutex_enter(&fc_hash_lock);

	for (ip = *head; ip; ip = ip->next)
		if (ip->h == handle)
			break;

	mutex_exit(&fc_hash_lock);

	return (ip ? ip->dip : NULL);
}

fc_phandle_t
fc_dip_to_phandle(struct fc_phandle_entry **head, dev_info_t *dip)
{
	struct fc_phandle_entry *hp, *np;
	fc_phandle_t h;

	ASSERT(dip);
	h = (fc_phandle_t)ddi_get_nodeid(dip);

	/*
	 * Just in case, allocate a new entry ...
	 */
	np = kmem_zalloc(sizeof (struct fc_phandle_entry), KM_SLEEP);

	mutex_enter(&fc_hash_lock);

	/*
	 * If we already have this dip in the table, just return the handle
	 */
	for (hp = *head; hp; hp = hp->next) {
		if (hp->dip == dip) {
			mutex_exit(&fc_hash_lock);
			kmem_free(np, sizeof (struct fc_phandle_entry));
			return (h);
		}
	}

	/*
	 * Insert this entry to the list of known entries
	 */
	np->next = *head;
	np->dip = dip;
	np->h = h;
	*head = np;
	mutex_exit(&fc_hash_lock);
	return (h);
}

/*
 * We won't need this function once the ddi is modified to handle
 * unique non-prom nodeids.  For now, this allows us to add a given
 * nodeid to the device tree without dereferencing the value in the
 * devinfo node, so we have a parallel mechanism.
 */
void
fc_add_dip_to_phandle(struct fc_phandle_entry **head, dev_info_t *dip,
    fc_phandle_t h)
{
	struct fc_phandle_entry *hp, *np;

	ASSERT(dip);

	/*
	 * Just in case, allocate a new entry ...
	 */
	np = kmem_zalloc(sizeof (struct fc_phandle_entry), KM_SLEEP);

	mutex_enter(&fc_hash_lock);

	/*
	 * If we already have this dip in the table, just return the handle
	 */
	for (hp = *head; hp; hp = hp->next) {
		if (hp->dip == dip) {
			mutex_exit(&fc_hash_lock);
			kmem_free(np, sizeof (struct fc_phandle_entry));
			return;
		}
	}

	/*
	 * Insert this entry to the list of known entries
	 */
	np->next = *head;
	np->dip = dip;
	np->h = h;
	*head = np;
	mutex_exit(&fc_hash_lock);
}

/*
 * Functions to manage our copy of our subtree.
 *
 * The head of the device tree is always stored in the last 'handle'
 * in the handle chain.
 */
struct fc_device_tree **
fc_handle_to_dtree_head(fco_handle_t rp)
{
	while (rp->next_handle)
		rp = rp->next_handle;

	return (&rp->dtree);
}

struct fc_device_tree *
fc_handle_to_dtree(fco_handle_t rp)
{
	struct fc_device_tree **head = fc_handle_to_dtree_head(rp);

	return (*head);
}

/*
 * The root of the subtree is the attachment point ...
 * Thus, there is never an empty device tree.
 */
void
fc_create_device_tree(dev_info_t *ap, struct fc_device_tree **head)
{
	struct fc_device_tree *dp;

	dp = kmem_zalloc(sizeof (struct fc_device_tree), KM_SLEEP);
	dp->dip = ap;
	*head = dp;
}

#ifdef	notdef
static void
fc_remove_subtree(struct fc_device_tree *dp)
{
	struct fc_device_tree *np;

	if (dp->child) {
		fc_remove_subtree(dp->child);
		dp->child = NULL;
	}

	/*
	 * Remove each peer node, working our way backwards from the
	 * last peer node to the first peer node.
	 */
	if (dp->peer != NULL) {
		for (np = dp->peer; np->peer; np = dp->peer) {
			for (/* empty */; np->peer; np = np->peer)
				/* empty */;
			fc_remove_subtree(np->peer);
			np->peer = NULL;
		}
		fc_remove_subtree(dp->peer)
		dp->peer = NULL;
	}

	ASSERT((dp->child == NULL) && (dp->peer == NULL));
	kmem_free(dp, sizeof (struct fc_device_tree));
}

void
fc_remove_device_tree(struct fc_device_tree **head)
{
	ASSERT(head && (*head != NULL));

	fc_remove_subtree(*head);
	*head = NULL;
}
#endif	/* notdef */

void
fc_remove_device_tree(struct fc_device_tree **head)
{
	struct fc_device_tree *dp;

	ASSERT(head && (*head != NULL));

	dp = *head;

	if (dp->child)
		fc_remove_device_tree(&dp->child);

	if (dp->peer)
		fc_remove_device_tree(&dp->peer);

	ASSERT((dp->child == NULL) && (dp->peer == NULL));

	kmem_free(dp, sizeof (struct fc_device_tree));
	*head = NULL;
}

struct fc_device_tree *
fc_find_node(dev_info_t *dip, struct fc_device_tree *hp)
{
	struct fc_device_tree *p;

	while (hp) {
		if (hp->dip == dip)
			return (hp);

		if (hp->child)
			if ((p = fc_find_node(dip, hp->child)) != NULL)
				return (p);

		hp = hp->peer;
	}
	return (NULL);
}

void
fc_add_child(dev_info_t *child, dev_info_t *parent, struct fc_device_tree *hp)
{
	struct fc_device_tree *p, *q;

	q = kmem_zalloc(sizeof (struct fc_device_tree), KM_SLEEP);
	q->dip = child;

	mutex_enter(&fc_device_tree_lock);

#ifdef	DEBUG
	/* XXX: Revisit ASSERT vs PANIC */
	p = fc_find_node(child, hp);
	ASSERT(p == NULL);
#endif

	p = fc_find_node(parent, hp);
	ASSERT(p != NULL);

	q->peer = p->child;
	p->child = q;

	mutex_exit(&fc_device_tree_lock);
}

void
fc_remove_child(dev_info_t *child, struct fc_device_tree *head)
{
	struct fc_device_tree *p, *c, *n;
	dev_info_t *parent = ddi_get_parent(child);

	mutex_enter(&fc_device_tree_lock);

	p = fc_find_node(parent, head);
	ASSERT(p != NULL);

	/*
	 * Find the child within the parent's subtree ...
	 */
	c = fc_find_node(child, p);
	ASSERT(c != NULL);
	ASSERT(c->child == NULL);

	/*
	 * If it's the first child, remove it, otherwise
	 * remove it from the child's peer list.
	 */
	if (p->child == c) {
		p->child = c->peer;
	} else {
		int found = 0;
		for (n = p->child; n->peer; n = n->peer) {
			if (n->peer == c) {
				n->peer = c->peer;
				found = 1;
				break;
			}
		}
		if (!found)
			cmn_err(CE_PANIC, "fc_remove_child: not found\n");
	}
	mutex_exit(&fc_device_tree_lock);

	kmem_free(c, sizeof (struct fc_device_tree));
}

dev_info_t *
fc_child_node(dev_info_t *parent, struct fc_device_tree *hp)
{
	struct fc_device_tree *p;
	dev_info_t *dip = NULL;

	mutex_enter(&fc_device_tree_lock);
	p = fc_find_node(parent, hp);
	if (p && p->child)
		dip = p->child->dip;
	mutex_exit(&fc_device_tree_lock);

	return (dip);
}

dev_info_t *
fc_peer_node(dev_info_t *devi, struct fc_device_tree *hp)
{
	struct fc_device_tree *p;
	dev_info_t *dip = NULL;

	mutex_enter(&fc_device_tree_lock);
	p = fc_find_node(devi, hp);
	if (p && p->peer)
		dip = p->peer->dip;
	mutex_exit(&fc_device_tree_lock);

	return (dip);
}