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

/*
 * provide the interface to the layered drivers (send request/receive
 * response to the RMC
 *
 */

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

/*
 *  Header files
 */
#include <sys/conf.h>
#include <sys/callb.h>
#include <sys/cyclic.h>
#include <sys/membar.h>
#include <sys/modctl.h>
#include <sys/strlog.h>
#include <sys/sunddi.h>
#include <sys/ddi.h>
#include <sys/types.h>
#include <sys/disp.h>
#include <sys/rmc_comm_dp.h>
#include <sys/rmc_comm_dp_boot.h>
#include <sys/rmc_comm_drvintf.h>
#include <sys/rmc_comm.h>

void dp_reset(struct rmc_comm_state *, uint8_t, boolean_t, boolean_t);
void dp_wake_up_waiter(struct rmc_comm_state *, uint8_t);

static int rmc_comm_send_req_resp(struct rmc_comm_state *rcs,
    rmc_comm_msg_t *request, rmc_comm_msg_t *response, uint32_t wait_time);
static int rmc_comm_wait_bp_reply(struct rmc_comm_state *,
    rmc_comm_dp_state_t *, dp_req_resp_t *, clock_t);
static void rmc_comm_wait_enable_to_send(struct rmc_comm_state *,
    rmc_comm_dp_state_t *);
static void rmc_comm_wake_up_next(struct rmc_comm_state *);
static void rmc_comm_send_pend_req(caddr_t arg);
static int rmc_comm_dreq_thread_start(struct rmc_comm_state *rcs);
static void rmc_comm_dreq_thread_kill(struct rmc_comm_state *rcs);

/*
 * leaf driver to use this function to send a request to the remote side (RMC)
 * and wait for a reply
 */
int
rmc_comm_request_response(rmc_comm_msg_t *request,
    rmc_comm_msg_t *response, uint32_t wait_time)
{
	struct rmc_comm_state	*rcs;
	int err;

	/*
	 * get the soft state struct (instance 0)
	 */
	if ((rcs = rmc_comm_getstate(NULL, 0,
				"rmc_comm_request_response")) == NULL)
		return (RCENOSOFTSTATE);

	do {
		err = rmc_comm_send_req_resp(rcs, request, response, wait_time);
	} while (err == RCEGENERIC);
	return (err);
}

/*
 * leaf driver to use this function to send a request to the remote side (RMC)
 * without waiting for a reply. If flag is RMC_COMM_DREQ_URGENT, the request
 * message is sent once-off (an eventual pending request is aborted). This
 * flag must only be used when try to send a request in critical condition
 * (while the system is shutting down for instance and the CPU signature
 * has to be sent). Otherwise, the request is stored in a temporary location
 * and delivered by a thread.
 */
int
rmc_comm_request_nowait(rmc_comm_msg_t *request, uint8_t flag)
{
	struct rmc_comm_state		*rcs;
	rmc_comm_dp_state_t		*dps;
	rmc_comm_drvintf_state_t	*dis;
	dp_message_t			req;
	int				err = RCNOERR;
	uint8_t				flags = 0;

	/*
	 * get the soft state struct (instance 0)
	 */
	if ((rcs = rmc_comm_getstate(NULL, 0,
				"rmc_comm_request_response")) == NULL)
		return (RCENOSOFTSTATE);

	/*
	 * just a sanity check...
	 */
	if (request == NULL) {
		DPRINTF(rcs, DAPI, (CE_CONT, "reqnowait, invalid args\n"));
		return (RCEINVARG);
	}

	if (!IS_NUMBERED_MSG(request->msg_type)) {
		DPRINTF(rcs, DAPI, (CE_CONT,
		    "reqnowait, ctrl msg not allowed! req type=%x\n",
		    request->msg_type));
		return (RCEINVARG);
	}

	if (flag == RMC_COMM_DREQ_URGENT) {
		/*
		 * Send this request with high priority i.e. abort eventual
		 * request/response pending sessions.
		 */

		dps = &rcs->dp_state;

		DPRINTF(rcs, DAPI, (CE_CONT, "going to send request=%x (URG)\n",
		    request->msg_type));

		/*
		 * Handle the case where we are called during panic
		 * processing.  If that occurs, then another thread in
		 * rmc_comm might have been idled by panic() while
		 * holding dp_mutex.  As a result, do not unconditionally
		 * grab dp_mutex.
		 */
		if (ddi_in_panic() != 0) {
			if (mutex_tryenter(dps->dp_mutex) == 0) {
				return (RCENODATALINK);
			}
		} else {
			mutex_enter(dps->dp_mutex);
		}

		/*
		 * send the request only if the protocol data link is up.
		 * it is pointless to send it in the other case.
		 */
		if (dps->data_link_ok) {

			/*
			 * clean up an eventual pending request/response session
			 * (save its current status)
			 */
			if (dps->pending_request) {
				flags = dps->req_resp.flags;
				rmc_comm_dp_mcleanup(rcs);
			}

			/*
			 * send the request message
			 */
			req.msg_type = request->msg_type;
			req.msg_buf = (uint8_t *)request->msg_buf;
			req.msg_msglen = (uint16_t)request->msg_len;

			DPRINTF(rcs, DAPI, (CE_CONT, "send request=%x (URG)\n",
			    request->msg_type));

			err = rmc_comm_dp_msend(rcs, &req);

			/*
			 * wait for fifos to drain
			 */
			rmc_comm_serdev_drain(rcs);

			/*
			 * clean up the current session
			 */
			rmc_comm_dp_mcleanup(rcs);

			/*
			 * abort an old session (if any)
			 */
			if (dps->pending_request) {
				dps->req_resp.flags = flags;
				dp_wake_up_waiter(rcs, MSG_ERROR);
			}
		}

		mutex_exit(dps->dp_mutex);

	} else {

		/*
		 * Get an 'independent' thread (rmc_comm_send_pend_req)
		 * to send this request (since the calling thread does not
		 * want to wait). Copy the request in the drvintf state
		 * structure and signal the thread.
		 */

		dis = &rcs->drvi_state;

		mutex_enter(dis->dreq_mutex);

		if (dis->dreq_state == RMC_COMM_DREQ_ST_WAIT) {

			DPRINTF(rcs, DAPI, (CE_CONT, "get to send request=%x\n",
			    request->msg_type));

			/*
			 * copy the request in a temporary location
			 * (drvinf_state structure) and signal the thread
			 * that a request message has to be delivered
			 */

			if (request->msg_len < DP_MAX_MSGLEN) {
				dis->dreq_request.msg_type = request->msg_type;
				dis->dreq_request.msg_len = request->msg_len;
				dis->dreq_request.msg_buf =
				    dis->dreq_request_buf;
				bcopy(request->msg_buf,
				    dis->dreq_request.msg_buf,
				    request->msg_len);

				dis->dreq_state = RMC_COMM_DREQ_ST_PROCESS;
				cv_signal(dis->dreq_sig_cv);

			} else {
				/*
				 * not enough space to hold the request
				 */
				err = RCEREPTOOBIG;
			}
		} else {

			DPRINTF(rcs, DAPI, (CE_CONT, "cannot get to send "
			    "request=%x (busy)\n", request->msg_type));

			/*
			 * only one request per time can be processed.
			 * the thread is either busy (RMC_COMM_DREQ_ST_PROCESS)
			 * or terminating (RMC_COMM_DREQ_ST_EXIT)
			 */
			err = RCEGENERIC;
		}

		mutex_exit(dis->dreq_mutex);
	}

	return (err);
}

/*
 * Function used to send a request and (eventually) wait for a response.
 * It can be called from a leaf driver (via rmc_comm_request_response) or
 * from the thread in charge of sending 'no-wait' requests
 * (rmc_comm_send_pend_req).
 */
static int
rmc_comm_send_req_resp(struct rmc_comm_state *rcs, rmc_comm_msg_t *request,
    rmc_comm_msg_t *response, uint32_t wait_time)
{
	rmc_comm_dp_state_t	*dps;
	dp_req_resp_t		*drr;
	dp_message_t		*exp_resp;
	dp_message_t		req;
	clock_t			resend_clockt;
	clock_t			stop_clockt;
	int			err;


	/*
	 * just a sanity check...
	 */
	if (request == NULL) {
		DPRINTF(rcs, DAPI, (CE_CONT, "reqresp, invalid args\n"));
		return (RCEINVARG);
	}

	/*
	 * drivers cannot send control messages at all. They are meant to
	 * be used at low level only.
	 */
	if (!IS_NUMBERED_MSG(request->msg_type)) {
		DPRINTF(rcs, DAPI, (CE_CONT,
		    "reqresp, ctrl msg not allowed! req type=%x\n",
		    request->msg_type));
		return (RCEINVARG);
	}

	dps = &rcs->dp_state;
	drr = &dps->req_resp;
	exp_resp = &drr->response;

	/*
	 * Handle the case where we are called during panic
	 * processing.  If that occurs, then another thread in
	 * rmc_comm might have been idled by panic() while
	 * holding dp_mutex.  As a result, do not unconditionally
	 * grab dp_mutex.
	 */
	if (ddi_in_panic() != 0) {
		if (mutex_tryenter(dps->dp_mutex) == 0) {
			return (RCENODATALINK);
		}
	} else {
		mutex_enter(dps->dp_mutex);
	}

	/*
	 * if the data link set up is suspended, just return.
	 * the only time that this can happen is during firmware download
	 * (see rmc_comm_request_response_bp). Basically, the data link is
	 * down and the timer for setting up the data link is not running.
	 */
	if (!dps->data_link_ok &&
	    dps->timer_link_setup == (timeout_id_t)0) {

		mutex_exit(dps->dp_mutex);
		return (RCENODATALINK);
	}

	DPRINTF(rcs, DAPI, (CE_CONT, "pending request=%d, req type=%x\n",
	    dps->pending_request, request->msg_type));

	rmc_comm_wait_enable_to_send(rcs, dps);

	/*
	 * We now have control of the RMC.
	 * Place a lower limit on the shortest amount of time to be
	 * waited before timing out while communicating with the RMC
	 */
	if (wait_time < DP_MIN_TIMEOUT)
		wait_time = DP_MIN_TIMEOUT;

	stop_clockt = ddi_get_lbolt() + drv_usectohz(wait_time * 1000);

	/*
	 * initialization of the request/response data structure
	 */
	drr->flags = 0;
	drr->error_status = 0;

	/*
	 * set the 'expected reply' buffer: get the buffer already allocated
	 * for the response (if a reply is expected!)
	 */
	if (response != NULL) {
		exp_resp->msg_type = response->msg_type;
		exp_resp->msg_buf = (uint8_t *)response->msg_buf;
		exp_resp->msg_msglen = (uint16_t)response->msg_bytes;
		exp_resp->msg_bufsiz = (uint16_t)response->msg_len;
	} else {
		exp_resp->msg_type = DP_NULL_MSG;
		exp_resp->msg_buf = (uint8_t)NULL;
		exp_resp->msg_bufsiz = (uint16_t)0;
		exp_resp->msg_msglen = (uint16_t)0;
	}

	/*
	 * send the request message
	 */
	req.msg_type = request->msg_type;
	req.msg_buf = (uint8_t *)request->msg_buf;
	req.msg_msglen = (uint16_t)request->msg_len;

	/*
	 * send the message and wait for the reply or ACKnowledgment
	 * re-send the message if reply/ACK is not received in the
	 * timeframe defined
	 */
	DPRINTF(rcs, DAPI, (CE_CONT, "send request=%x\n", request->msg_type));

	while ((err = rmc_comm_dp_msend(rcs, &req)) == RCNOERR) {

		resend_clockt = ddi_get_lbolt() +
		    drv_usectohz(TX_RETRY_TIME * 1000);

		/*
		 * wait for a reply or an acknowledgement
		 */
		(void) cv_timedwait(drr->cv_wait_reply, dps->dp_mutex,
		    resend_clockt);

		DPRINTF(rcs, DAPI, (CE_CONT,
		    "reqresp send status: flags=%02x req=%x resp=%x tick=%ld\n",
		    drr->flags, request->msg_type,
		    response ? response->msg_type : -1,
		    stop_clockt - resend_clockt));

		/*
		 * Check for error condition first
		 * Then, check if the command has been replied/ACKed
		 * Then, check if it has timeout and if there is any
		 * time left to resend the message.
		 */
		if ((drr->flags & MSG_ERROR) != 0) {
			if (drr->error_status == 0) {
				err = RCEGENERIC;
			} else {
				err = drr->error_status;
			}
			break;

		} else if (response != NULL &&
		    (drr->flags & MSG_REPLY_RXED) != 0) {
			/*
			 * yes! here is the reply
			 */

			/*
			 * get the actual length of the msg
			 * a negative value means that the reply message
			 * was too big for the receiver buffer
			 */
			response->msg_bytes = exp_resp->msg_msglen;
			if (response->msg_bytes < 0)
				err = RCEREPTOOBIG;
			else
				err = RCNOERR;
			break;

		} else if (response == NULL && (drr->flags & MSG_ACKED) != 0) {
			/*
			 * yes! message has been acknowledged
			 */

			err = RCNOERR;
			break;

		} else if ((stop_clockt - resend_clockt) <= 0) {
			/*
			 * no more time left. set the error code,
			 * exit the loop
			 */

			err = RCETIMEOUT;
			break;
		}
	}

	rmc_comm_dp_mcleanup(rcs);

	rmc_comm_wake_up_next(rcs);

	mutex_exit(dps->dp_mutex);

	DPRINTF(rcs, DAPI, (CE_CONT, "reqresp end: err=%d, request=%x\n",
	    err, request->msg_type));

	return (err);
}

/*
 * Function used to send a BP (Boot Prom) message and get the reply.
 * BP protocol is provided only to support firmware download.
 *
 * This function will look for the following key BP protocol commands:
 * BP_OBP_BOOTINIT: the data link is brought down so that request/response
 * sessions cannot be started. The reason why is that this command will cause
 * RMC fw to jump to the boot monitor (BOOTMON_FLASH) and data protocol is not
 * operational. In this context, RMC fw will only be using the BP protocol.
 * BP_OBP_RESET: data link setup timer is resumed. This command cause the RMC
 * to reboot and hence become operational.
 */
int
rmc_comm_request_response_bp(rmc_comm_msg_t *request_bp,
    rmc_comm_msg_t *response_bp, uint32_t wait_time)
{
	struct rmc_comm_state	*rcs;
	rmc_comm_dp_state_t	*dps;
	dp_req_resp_t		*drr;
	dp_message_t		*resp_bp;
	bp_msg_t		*bp_msg;
	clock_t			stop_clockt;
	int			err = RCNOERR;
	boolean_t		bootinit_sent = 0;

	/*
	 * get the soft state struct (instance 0)
	 */
	if ((rcs = rmc_comm_getstate(NULL, 0,
				"rmc_comm_request_response_bp")) == NULL)
		return (RCENOSOFTSTATE);

	/*
	 * sanity check: request_bp buffer must always be provided
	 */
	if (request_bp == NULL) {
		DPRINTF(rcs, DAPI, (CE_CONT, "reqresp_bp, invalid args\n"));
		return (RCEINVARG);
	}

	bp_msg = (bp_msg_t *)request_bp->msg_buf;

	DPRINTF(rcs, DAPI, (CE_CONT, "send request_bp=%x\n", bp_msg->cmd));

	/*
	 * only BP message can be sent
	 */
	if (!IS_BOOT_MSG(bp_msg->cmd)) {
		DPRINTF(rcs, DAPI, (CE_CONT,
		    "reqresp_bp, only BP msg are allowed! type=%x\n",
		    bp_msg->cmd));
		return (RCEINVARG);
	}

	dps = &rcs->dp_state;
	drr = &dps->req_resp;
	resp_bp = &drr->response;

	mutex_enter(dps->dp_mutex);

	rmc_comm_wait_enable_to_send(rcs, dps);

	/*
	 * Now, before sending the message, just check what it is being sent
	 * and take action accordingly.
	 *
	 * is it BP_OBP_BOOTINIT or BP_OBP_RESET command?
	 */
	if (bp_msg->cmd == BP_OBP_BOOTINIT) {

		/*
		 * bring down the protocol data link
		 * (must be done before aborting a request/response session)
		 */
		dps->data_link_ok = 0;
		dps->timer_link_setup = (timeout_id_t)0;

		bootinit_sent = 1;

	} else if (bp_msg->cmd == BP_OBP_RESET) {

		/*
		 * restart the data link set up timer. RMC is coming up...
		 */

		dp_reset(rcs, INITIAL_SEQID, 0, 1);
	}

	/*
	 * initialization of the request/response data structure
	 */
	drr->flags = 0;
	drr->error_status = 0;

	/*
	 * set the reply buffer: get the buffer already allocated
	 * for the response
	 */
	if (response_bp != NULL) {
		DPRINTF(rcs, DAPI, (CE_CONT, "expect BP reply. len=%d\n",
		    response_bp->msg_len));

		resp_bp->msg_buf = (uint8_t *)response_bp->msg_buf;
		resp_bp->msg_bufsiz = (uint16_t)response_bp->msg_len;
	}

	/*
	 * send the BP message and wait for the reply
	 */

	rmc_comm_bp_msend(rcs, bp_msg);

	if (response_bp != NULL) {

		/*
		 * place a lower limit on the shortest amount of time to be
		 * waited before timing out while communicating with the RMC
		 */
		if (wait_time < DP_MIN_TIMEOUT)
			wait_time = DP_MIN_TIMEOUT;

		stop_clockt = ddi_get_lbolt() + drv_usectohz(wait_time * 1000);

		if ((err = rmc_comm_wait_bp_reply(rcs, dps, drr,
		    stop_clockt)) == RCNOERR) {

			/*
			 * get the actual length of the msg
			 * a negative value means that the reply message
			 * was too big for the receiver buffer
			 */
			response_bp->msg_bytes = resp_bp->msg_msglen;
			if (response_bp->msg_bytes < 0) {
				err = RCEREPTOOBIG;

			} else if (bootinit_sent) {

				/*
				 * BOOTINIT cmd may fail. In this is the case,
				 * the RMC is still operational. Hence, we
				 * try (once) to set up the data link
				 * protocol.
				 */
				bp_msg = (bp_msg_t *)response_bp->msg_buf;

				if (bp_msg->cmd == BP_RSC_BOOTFAIL &&
				    bp_msg->dat1 == BP_DAT1_REJECTED) {
					(void) rmc_comm_dp_ctlsend(rcs,
					    DP_CTL_START);
				}
			}
		}
	}

	rmc_comm_dp_mcleanup(rcs);

	rmc_comm_wake_up_next(rcs);

	mutex_exit(dps->dp_mutex);

	return (err);
}


/*
 * to register for an asynchronous (via soft interrupt) notification
 * of a message from the remote side (RMC)
 */
int
rmc_comm_reg_intr(uint8_t msg_type, rmc_comm_intrfunc_t intr_handler,
    rmc_comm_msg_t *msgbuf, uint_t *state, kmutex_t *lock)
{
	struct rmc_comm_state 	*rcs;
	dp_msg_intr_t		*msgintr;
	int			 err = RCNOERR;

	if ((rcs = rmc_comm_getstate(NULL, 0, "rmc_comm_reg_intr")) == NULL)
		return (RCENOSOFTSTATE);

	mutex_enter(rcs->dp_state.dp_mutex);

	msgintr = &rcs->dp_state.msg_intr;

	/*
	 * lock is required. If it is not defined, the
	 * interrupt handler routine cannot be registered.
	 */
	if (lock == NULL) {
		mutex_exit(rcs->dp_state.dp_mutex);
		return (RCEINVARG);
	}

	/*
	 * only one interrupt handler can be registered.
	 */
	if (msgintr->intr_handler == NULL) {

		if (ddi_add_softintr(rcs->dip, DDI_SOFTINT_HIGH,
		    &msgintr->intr_id, NULL, NULL, intr_handler,
		    (caddr_t)msgbuf) == DDI_SUCCESS) {

			msgintr->intr_handler = intr_handler;
			msgintr->intr_lock = lock;
			msgintr->intr_state = state;
			msgintr->intr_msg_type = msg_type;
			msgintr->intr_arg = (caddr_t)msgbuf;
		} else {
			err = RCECANTREGINTR;
		}
	} else {
		err = RCEALREADYREG;
	}

	mutex_exit(rcs->dp_state.dp_mutex);

	return (err);
}

/*
 * To unregister for asynchronous notifications
 */
int
rmc_comm_unreg_intr(uint8_t msg_type, rmc_comm_intrfunc_t intr_handler)
{
	struct rmc_comm_state	*rcs;
	dp_msg_intr_t		*msgintr;
	int			 err = RCNOERR;

	if ((rcs = rmc_comm_getstate(NULL, 0, "rmc_comm_unreg_intr")) == NULL)
		return (RCENOSOFTSTATE);

	mutex_enter(rcs->dp_state.dp_mutex);

	msgintr = &rcs->dp_state.msg_intr;

	if (msgintr->intr_handler != NULL &&
		msgintr->intr_msg_type == msg_type &&
		msgintr->intr_handler == intr_handler) {

		ddi_remove_softintr(msgintr->intr_id);
		msgintr->intr_handler = NULL;
		msgintr->intr_id = 0;
		msgintr->intr_msg_type = 0;
		msgintr->intr_arg = NULL;
		msgintr->intr_lock = NULL;
		msgintr->intr_state = NULL;
	} else {
		err = RCEGENERIC;
	}

	mutex_exit(rcs->dp_state.dp_mutex);

	return (err);
}

/*
 * To send raw data (firmware s-records) down to the RMC.
 * It is provided only to support firmware download.
 */
int
rmc_comm_send_srecord_bp(caddr_t buf, int buflen,
    rmc_comm_msg_t *response_bp, uint32_t wait_time)
{
	struct rmc_comm_state	*rcs;
	rmc_comm_dp_state_t	*dps;
	dp_req_resp_t		*drr;
	dp_message_t		*resp_bp;
	clock_t			stop_clockt;
	int			err;

	/*
	 * get the soft state struct (instance 0)
	 */
	if ((rcs = rmc_comm_getstate(NULL, 0,
				"rmc_comm_request_response_bp")) == NULL)
		return (RCENOSOFTSTATE);

	/*
	 * sanity check: response_bp buffer must always be provided
	 */
	if (buf == NULL || response_bp == NULL) {
		DPRINTF(rcs, DAPI, (CE_CONT, "send_srecord_bp,invalid args\n"));
		return (RCEINVARG);
	}

	DPRINTF(rcs, DAPI, (CE_CONT, "send_srecord_bp, buflen=%d\n", buflen));

	dps = &rcs->dp_state;
	drr = &dps->req_resp;
	resp_bp = &drr->response;

	mutex_enter(dps->dp_mutex);

	rmc_comm_wait_enable_to_send(rcs, dps);

	/*
	 * initialization of the request/response data structure
	 */
	drr->flags = 0;
	drr->error_status = 0;

	/*
	 * set the reply buffer: get the buffer already allocated
	 * for the response
	 */
	resp_bp->msg_buf = (uint8_t *)response_bp->msg_buf;
	resp_bp->msg_bufsiz = (uint16_t)response_bp->msg_len;

	/*
	 * send raw data (s-record) and wait for the reply (BP message)
	 */

	rmc_comm_bp_srecsend(rcs, (char *)buf, buflen);

	/*
	 * place a lower limit on the shortest amount of time to be
	 * waited before timing out while communicating with the RMC
	 */
	if (wait_time < DP_MIN_TIMEOUT)
		wait_time = DP_MIN_TIMEOUT;

	stop_clockt = ddi_get_lbolt() + drv_usectohz(wait_time * 1000);

	if ((err = rmc_comm_wait_bp_reply(rcs, dps, drr,
	    stop_clockt)) == RCNOERR) {
		/*
		 * get the actual length of the msg
		 * a negative value means that the reply message
		 * was too big for the receiver buffer
		 */
		response_bp->msg_bytes = resp_bp->msg_msglen;
		if (response_bp->msg_bytes < 0) {
			err = RCEREPTOOBIG;
		}
	}

	rmc_comm_dp_mcleanup(rcs);

	rmc_comm_wake_up_next(rcs);

	mutex_exit(dps->dp_mutex);

	return (err);
}

/*
 * To wait for (any) BP message to be received.
 * (dp_mutex must be held)
 */
static int
rmc_comm_wait_bp_reply(struct rmc_comm_state *rcs, rmc_comm_dp_state_t *dps,
    dp_req_resp_t *drr, clock_t stop_clockt)
{
	clock_t clockleft = 1;
	int err = RCNOERR;

	clockleft = cv_timedwait(drr->cv_wait_reply, dps->dp_mutex,
	    stop_clockt);


	DPRINTF(rcs, DAPI, (CE_CONT,
	    "reqresp_bp, send: flags=%02x, clktick left=%ld\n",
	    drr->flags, clockleft));

	/*
	 * Check for error condition first.
	 * Then, check if it has timeout.
	 * Then, check if the command has been replied.
	 */
	if ((drr->flags & MSG_ERROR) != 0) {

		err = RCEGENERIC;

	} else if (clockleft <= 0) {
		/*
		 * timeout
		 */

		err = RCETIMEOUT;

	} else if ((drr->flags & MSG_RXED_BP) == 0) {

		err = RCEGENERIC;
	}

	return (err);
}

/*
 * Wait for the pending_request flag to be cleared and acquire it for our
 * own use. The caller is then allowed to start a new request/response
 * session with the RMC.
 * Note that all send-receive actions to the RMC include a time-out, so
 * the pending-request must eventually go away - even if the RMC is down.
 * Hence there is no need to timeout the wait action of this function.
 * (dp_mutex must be held on entry).
 */
static void
rmc_comm_wait_enable_to_send(struct rmc_comm_state *rcs,
    rmc_comm_dp_state_t *dps)
{
	DPRINTF(rcs, DAPI, (CE_CONT, "pending request=%d\n",
	    dps->pending_request));

	/*
	 * A new message can actually grab the lock before the thread
	 * that has just been signaled.  Therefore, we need to double
	 * check to make sure that pending_request is not already set
	 * after we wake up.
	 *
	 * Potentially this could mean starvation for certain unfortunate
	 * threads that keep getting woken up and putting back to sleep.
	 * But the window of such contention is very small to begin with.
	 */

	while (dps->pending_request) {
		/*
		 * just 'sit and wait' until there are no pending requests
		 */

		cv_wait(dps->cv_ok_to_send, dps->dp_mutex);
	}

	/*
	 * now a request/response can be started. Set the flag so that nobody
	 * else will be able to send anything.
	 */
	dps->pending_request = 1;
}

/*
 * To wake up one of the threads (if any) waiting for starting a
 * request/response session.
 * (dp_mutex must be held)
 */
static void
rmc_comm_wake_up_next(struct rmc_comm_state *rcs)
{
	/*
	 * wake up eventual waiting threads...
	 */

	rcs->dp_state.pending_request = 0;
	cv_signal(rcs->dp_state.cv_ok_to_send);
}


/*
 * thread which delivers pending request message to the rmc. Some leaf drivers
 * cannot afford to wait for a request to be replied/ACKed. Hence, a request
 * message is stored temporarily in the state structure and this thread
 * gets woken up to deliver it.
 */
static void
rmc_comm_send_pend_req(caddr_t arg)
{
	struct rmc_comm_state		*rcs;
	rmc_comm_drvintf_state_t	*dis;
	callb_cpr_t			cprinfo;

	if (arg == NULL) {
		thread_exit();
		/* NOTREACHED */
	}

	rcs = (struct rmc_comm_state *)arg;
	dis = &rcs->drvi_state;

	CALLB_CPR_INIT(&cprinfo, dis->dreq_mutex, callb_generic_cpr,
	    "rmc_comm_send_pend_req");

	mutex_enter(dis->dreq_mutex);

	if (dis->dreq_state <= RMC_COMM_DREQ_ST_READY)
		dis->dreq_state = RMC_COMM_DREQ_ST_WAIT;

	for (;;) {

		/*
		 * Wait for someone to tell me to continue.
		 */
		while (dis->dreq_state == RMC_COMM_DREQ_ST_WAIT) {
			CALLB_CPR_SAFE_BEGIN(&cprinfo);
			cv_wait(dis->dreq_sig_cv, dis->dreq_mutex);
			CALLB_CPR_SAFE_END(&cprinfo, dis->dreq_mutex);
		}

		/* RMC_COMM_DREQ_ST_EXIT implies signal by _detach(). */
		if (dis->dreq_state == RMC_COMM_DREQ_ST_EXIT) {
			dis->dreq_state = RMC_COMM_DREQ_ST_NOTSTARTED;
			dis->dreq_tid = 0;

			/* dis->dreq_mutex is held at this point! */
			CALLB_CPR_EXIT(&cprinfo);

			thread_exit();
			/* NOTREACHED */
		}

		ASSERT(dis->dreq_state == RMC_COMM_DREQ_ST_PROCESS);
		mutex_exit(dis->dreq_mutex);

		/*
		 * deliver the request (and wait...)
		 */
		while (rmc_comm_send_req_resp(rcs, &dis->dreq_request, NULL,
		    RMC_COMM_DREQ_DEFAULT_TIME) == RCEGENERIC) {
		}

		mutex_enter(dis->dreq_mutex);
		if (dis->dreq_state != RMC_COMM_DREQ_ST_EXIT)
			dis->dreq_state = RMC_COMM_DREQ_ST_WAIT;
	}
}

/*
 * start thread to deal with pending requests to be delivered asynchronously
 * (i.e. leaf driver do not have to/cannot wait for a reply/ACk of a request)
 */
static int
rmc_comm_dreq_thread_start(struct rmc_comm_state *rcs)
{
	rmc_comm_drvintf_state_t *dis = &rcs->drvi_state;
	int err = 0;
	kthread_t *tp;

	mutex_enter(dis->dreq_mutex);

	if (dis->dreq_state == RMC_COMM_DREQ_ST_NOTSTARTED) {

		tp = thread_create(NULL, 0, rmc_comm_send_pend_req,
		    (caddr_t)rcs, 0, &p0, TS_RUN, maxclsyspri);
		dis->dreq_state = RMC_COMM_DREQ_ST_READY;
		dis->dreq_tid = tp->t_did;
	}

	mutex_exit(dis->dreq_mutex);

	return (err);
}

/*
 * stop the thread (to deliver pending request messages)
 */
static void
rmc_comm_dreq_thread_kill(struct rmc_comm_state *rcs)
{
	rmc_comm_drvintf_state_t *dis = &rcs->drvi_state;
	kt_did_t tid;

	mutex_enter(dis->dreq_mutex);
	tid = dis->dreq_tid;
	if (tid != 0) {
		dis->dreq_state = RMC_COMM_DREQ_ST_EXIT;
		dis->dreq_tid = 0;
		cv_signal(dis->dreq_sig_cv);
	}
	mutex_exit(dis->dreq_mutex);

	/*
	 * Wait for rmc_comm_send_pend_req() to finish
	 */
	if (tid != 0)
		thread_join(tid);
}

/*
 * init function - start thread to deal with pending requests (no-wait requests)
 */
int
rmc_comm_drvintf_init(struct rmc_comm_state *rcs)
{
	int err = 0;

	DPRINTF(rcs, DGEN, (CE_CONT, "rmc_comm_drvintf_init\n"));
	rcs->drvi_state.dreq_state = RMC_COMM_DREQ_ST_NOTSTARTED;
	rcs->drvi_state.dreq_tid = 0;

	mutex_init(rcs->drvi_state.dreq_mutex, NULL, MUTEX_DRIVER, NULL);
	cv_init(rcs->drvi_state.dreq_sig_cv, NULL, CV_DRIVER, NULL);

	err = rmc_comm_dreq_thread_start(rcs);
	if (err != 0) {
		cv_destroy(rcs->drvi_state.dreq_sig_cv);
		mutex_destroy(rcs->drvi_state.dreq_mutex);
	}

	DPRINTF(rcs, DGEN, (CE_CONT, "thread started? err=%d\n", err));

	return (err);
}

/*
 * fini function - kill thread to deal with pending requests (no-wait requests)
 */
void
rmc_comm_drvintf_fini(struct rmc_comm_state *rcs)
{
	DPRINTF(rcs, DGEN, (CE_CONT, "rmc_comm_drvintf_fini:stop thread\n"));

	rmc_comm_dreq_thread_kill(rcs);

	DPRINTF(rcs, DGEN, (CE_CONT, "rmc_comm_drvintf_fini:destroy Mx/CVs\n"));

	cv_destroy(rcs->drvi_state.dreq_sig_cv);
	mutex_destroy(rcs->drvi_state.dreq_mutex);
}