/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident		"$Header: wp.c 1.22 90/10/22 $"

/*
 *     SSM parallel printer driver software
 *
 */

/* $Log:	wp.c,v $
 */
#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/sysmacros.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/debug.h"
#include "sys/autoconf.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/clock.h"
#include "sys/uio.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/stropts.h"
#include "sys/file.h"
#include "sys/cmn_err.h"
#include "sys/cfg.h"
#include "sys/slic.h"
#include "sys/intctl.h"
#include "sys/ssm.h"
#include "sys/ssm_device.h"
#include "sys/ssm_misc.h"
#include "sys/ssm_cb.h"
#include "sys/wp.h"
#include "sys/printer.h"
#include "sys/strid.h"

#ifdef DEBUG
int wp_debug = 0;
#define WPDEBUG		if (wp_debug) CPRINTF
#endif /* DEBUG */

static struct module_info info = {
	STRID_WP, "wp", 0, INFPSZ, 4096, 512,
};

static int wpopen(), wpclose(), wpwput();

static struct qinit rinit = {
	NULL,  NULL, wpopen, wpclose, NULL, &info, NULL,
};

static struct qinit winit = {
	wpwput, NULL, NULL, NULL, NULL, &info, NULL,
};

struct streamtab wpinfo = {
	&rinit, &winit, NULL, NULL,
};

extern unchar wp_bin;

int wp_timer();
void wpboot(), wpintr();

static int wpmaxdev;
static int wp_base_vector;
static struct wp_info **wp_info;

/*
 * External variables declared in wp_space.c
 */
extern struct wp_printer wpconfig[];	/* binary configuration table */
extern int wpprinters;			/* no. of entries in wpconfig */ 
extern struct devops	wpdevops;	/* devops for wp driver */
extern int		wp_c_major;	/* major device number for wp */

/*
 * wpboot - allocate data structures, etc at beginning of time.
 *
 * Called by ssm_init with the ssm driver record.  We allocate the
 * necessary structures and fill them in.
 */
void
wpboot(driver)
	struct driver_module *driver;
{
	register struct ssm_device *devp;
	struct wp_info	*wp;
	int pindex; 
	int dev;
	unchar vector;
	char *ssm_alloc();
	struct dev *dp;
	
#ifdef DEBUG
	WPDEBUG("wpboot: start: ndevs = %d\n", driver->numdev);
#endif /* DEBUG */
	wp_base_vector = ivecpeek(wp_bin); 
	
	/* Allocate an array of pointer to info structures */
	wp_info = (struct wp_info **)calloc(driver->numdev * sizeof(struct wp_info *));
	
	/* Set up info structure for each SSM device in system */
	for (dev = 0, devp = (struct ssm_device *)driver->devices; 
		dev < driver->numdev; dev++, devp++) {
		/* Always allocate vector, even if device dead. */
		vector = ivecall(wp_bin);
		(void) ivecall(wp_bin);		/* dual ints */
		if (!devp->driver)		/* device not alive */
			continue;
		if (SSM_desc[dev].ssm_diag_flags & CFG_SSM_PRINT)
			continue;		/* device failed diagnostics */

		if ( !SSM_desc[dev].ssm_is_cons )	/* only want SSM2 console lp */
			continue;

		dp = (struct dev *)calloc(sizeof(struct dev));
		dp->d_dev = makedev(wp_c_major, dev);
		dp->d_flags = 0;
		dp->d_ops = &wpdevops;
		if (devsw_add(dp) < 0) {
			CPRINTF("wp%d: problem adding device to devsw\n",
				dp->d_dev);
			continue;
		}
		
		wp_info[dev] = wp = (struct wp_info *)
				    calloc(sizeof(struct wp_info));
		wp->wp_alive = 1;
		wp->wp_ssm = SSM_desc + dev;
		wp->wp_devno = dev; 
		if (dev >= wpprinters) {

			/*
			 * If there are more printers than there are
			 * entries in wpconfig, use the last entry in
			 * the table for the remaining devices.
			 */
			cmn_err(CE_NOTE,
				"wp: printer %d not configured.",
				dev);
			/*
			 *+ There are more printers than have been
			 *+ specified in the wpconfig table.  Excess
			 *+ printers will use the spconfig data from
			 *+ the last entry in the table.
			 */
			 cmn_err(CE_CONT, 
				"using printer %d's configuration\n",
				 wpprinters-1);
			pindex = wpprinters-1;
		} else
			pindex = dev;
		wp->wp_width = (WPLONGLINE < wpconfig[pindex].wp_width) ?
				WPLONGLINE : wpconfig[pindex].wp_width;
		wp->wp_ops = wpconfig[pindex].wp_ops;
		wp->wp_state = 0;
		ivecinit(wp_bin, vector, wpintr);
		ivecinit(wp_bin, vector+1, wpintr);
		init_ssm_prnt_dev(wp->wp_ssm->ssm_slicaddr,
				  wp->wp_ssm->ssm_prnt_cbs,
				  wp_bin, vector, dev,
				  wpconfig[pindex].wp_interface);
		init_sema(&wp->wp_sema, 0);
		init_lock(&wp->wp_lock);
#ifdef DEBUG
		WPDEBUG("wpboot: end\n");
#endif /* DEBUG */
	}
	wpmaxdev = driver->numdev - 1;
}

/*
 * wpopen - open an SSM printer port
 *
 * Open the device for exclusive use, initialize fields in device
 * structure, and wait for printer to be ready before returning.
 */
/*ARGSUSED*/
static int
wpopen(q, dev, flag, sflag)
	dev_t dev;
	queue_t *q;
{
	register struct wp_info *wp;
	int unit = minor(dev);
	spl_t s;

#ifdef DEBUG
	WPDEBUG("wpopen: start: unit = %d\n", unit);
#endif /* DEBUG */

	if (sflag)
		/* non-driver open */
		return (OPENFAIL);

	/*
	 * Check for bad device number. 
	 */
	if (unit > wpmaxdev || (wp = wp_info[unit]) == NULL || 
			!wp->wp_alive)
		return (OPENFAIL);
	
	/*
	 * Lock the device.
	 */
	s = p_lock(&wp->wp_lock, WP_SPL);

	if (q->q_ptr) {
		/*
		 * Device is already open.  This is an exclusive
		 * access device.
		 */
#ifdef DEBUG
		WPDEBUG("wpopen: device already open\n");
#endif /* DEBUG */
		u.u_error = EBUSY;
		v_lock(&wp->wp_lock, s);
		return (OPENFAIL);
	}

	if (!(flag & FNDELAY) && !wp_ready(wp)) {
#ifdef DEBUG
		WPDEBUG("wpopen: device not ready - going to sleep\n");
#endif /* DEBUG */
		wp->wp_state |= WP_TIMER;
		/*
		 * printer not ready so sleep until it is
		 */
		wp->wp_timeid = timeout(wp_timer, (caddr_t)wp, 2*HZ);
		while (wp->wp_state & WP_TIMER) {
			/*
			 * Must loop until ready because could be woken
			 * as a result of SIGSTOP/SIGCONT
			 */
			if (p_sema_v_lock(&wp->wp_sema, PCATCH|(PZERO+1), 
					  &wp->wp_lock) == PCATCH) {
				u.u_error = EINTR;
				untimeout(wp->wp_timeid);
#ifdef DEBUG
				WPDEBUG("wpopen: interrupt from open\n");
#endif /* DEBUG */
				return(OPENFAIL);
			}
			s = p_lock(&wp->wp_lock, WP_SPL);
		}
	}
#ifdef DEBUG
	WPDEBUG("wpopen: printer ready\n");
#endif /* DEBUG */

	wp->wp_state = 0;
	wp->wp_msg = NULL;
	q->q_ptr = (caddr_t)wp;
	WR(q)->q_ptr = (caddr_t)wp;
	wp->wp_qptr = WR(q);
	v_lock(&wp->wp_lock, s);
#ifdef DEBUG
	WPDEBUG("wpopen: end\n");
#endif /* DEBUG */
	return(OPENOK);
}

/*
 * wpclose - close an SSM printer port
 *
 * Flush remaining buffer contents and mark fields to indicate device
 * is closed.  Also check for and cleanup after signals.
 */
/*ARGSUSED*/
static int
wpclose(q)
	queue_t *q;			/* read queue */
{
	register struct print_xcb *xcb;
	struct wp_info *wp;
	spl_t s, sx;

#ifdef DEBUG
	WPDEBUG("wpclose: start\n");
#endif /* DEBUG */
	wp = (struct wp_info *)q->q_ptr;
	s = p_lock(&wp->wp_lock, WP_SPL);
	while (wp->wp_state & WP_BUSY) {
#ifdef DEBUG
		WPDEBUG("wpclose: device busy\n");
#endif /* DEBUG */
		if (p_sema_v_lock(&wp->wp_sema, (PZERO+1)|PCATCH,
				  &wp->wp_lock) == PCATCH) {
			/*
			 * woken up by a signal so cleanup
			 */
#ifdef DEBUG
			WPDEBUG("wpclose: interrupted\n");
#endif /* DEBUG */
			if (wp->wp_state & WP_TIMER)
				untimeout(wp->wp_timeid);
			u.u_error = EINTR;
			disable_procs(q);
			s = p_lock(&wp->wp_lock, WP_SPL);
			if (wp->wp_msg) {
				freemsg(wp->wp_msg);
				wp->wp_msg = NULL;
			}
			xcb = (struct print_xcb *) 
			      PRINT_BASE_CB(wp->wp_ssm->ssm_prnt_cbs, 
			      wp->wp_devno) + PCB_XMIT_CB;
			xcb->xcb_cmd = PCB_FLUSH;
			sx = splhi();
			mIntr(wp->wp_ssm->ssm_slicaddr, PRINT_BIN,
			      (unchar)PRVEC(PCB_FLUSH_V));
			splx(sx);
			while (xcb->xcb_status == PCB_BUSY)
				continue;
#ifdef DEBUG
			WPDEBUG("wpclose: flush completed\n");
#endif /* DEBUG */
			wp->wp_qptr = NULL;
			wp->wp_state = 0;
			v_lock(&wp->wp_lock, s);
			return;
		}
		s = p_lock(&wp->wp_lock, WP_SPL);
	}
	v_lock(&wp->wp_lock, s);
	disable_procs(q);
	s = p_lock(&wp->wp_lock, WP_SPL);
	wp->wp_qptr = NULL;
	wp->wp_state = 0;
	v_lock(&wp->wp_lock, s);
#ifdef DEBUG
	WPDEBUG("wpclose: end\n");
#endif /* DEBUG */
	return;
}

/*
 * wpwput 
 *	process streams messages by either handling them
 *	A now or queueing them for later
 */
static int
wpwput(q, mp)
	queue_t *q;			/* write queue */
	register mblk_t *mp;		/* message pointer */
{
	struct wp_info *wp;
	struct pr_config *cfg;
	struct iocblk *iocp;
	spl_t s;

#ifdef DEBUG
	WPDEBUG("wpwput: begin\n");
#endif /* DEBUG */
	wp = (struct wp_info *)q->q_ptr;

	switch (mp->b_datap->db_type) {

	case M_FLUSH:
		/* Canonical flush handling */
		if (*mp->b_rptr & FLUSHW) {
			flushq(q, FLUSHDATA);
			s = p_lock(&wp->wp_lock, WP_SPL);
			if (wp->wp_msg) {
				freemsg(wp->wp_msg);
				wp->wp_msg = NULL;
			}
			v_lock(&wp->wp_lock, s);
		}
		if (*mp->b_rptr & FLUSHR) {
			flushq(RD(q), FLUSHDATA);
			*mp->b_rptr &= ~FLUSHW;
			qreply(q, mp);
		} else
			freemsg(mp);
		break;

	case M_IOCTL:
		iocp = (struct iocblk *)mp->b_rptr;
		switch (iocp->ioc_cmd) {
		case PIOCSETCANON:
			switch (*(int *)mp->b_cont->b_rptr) {
			case PRCAPS:
				s = p_lock(&wp->wp_lock, WP_SPL);
				wp->wp_ops = PRCAPS;
				v_lock(&wp->wp_lock, s);
				mp->b_datap->db_type = M_IOCACK;
				break;
			case PRRAW:
				s = p_lock(&wp->wp_lock, WP_SPL);
				wp->wp_ops = PRRAW;
				v_lock(&wp->wp_lock, s);
				mp->b_datap->db_type = M_IOCACK;
				break;
			case PRSPECIAL:
				s = p_lock(&wp->wp_lock, WP_SPL);
				wp->wp_ops = PRSPECIAL;
				v_lock(&wp->wp_lock, s);
				mp->b_datap->db_type = M_IOCACK;
				break;
			default:
				mp->b_datap->db_type = M_IOCNAK;
				iocp->ioc_error = EINVAL;
				break;
			}
			qreply(q, mp);
			return;
		default:
			break;
		}
		/*FALLTHROUGH*/

	case M_PCIOCTL:
		/*
		 * wp printer doesn't support any priority
		 * ioctl's so NAK it.
		 */
#ifdef DEBUG
		WPDEBUG("wpwput: ioctl NAKed\n");
#endif /* DEBUG */
		mp->b_datap->db_type = M_IOCNAK;
		qreply(q, mp);
		break;

	case M_PCPROTO:
#ifdef DEBUG
		WPDEBUG("swput: M_PCPROTO\n");
#endif /* DEBUG */
		cfg = (struct pr_config *)mp->b_rptr;
		if (cfg->p_type == REQUEST_CFG) {
			cfg->p_type = REQUEST_CFG_RESPONSE;
			cfg->p_ops = wp->wp_ops;
			cfg->p_width = wp->wp_width;
		} else
			freemsg(mp);
		break;

	case M_DATA:
		/*
		 * Queue message and start device if not busy.
		 */
		putq(q, mp);
		s = p_lock(&wp->wp_lock, WP_SPL);
		if (!(wp->wp_state & WP_BUSY)) {
#ifdef DEBUG
			WPDEBUG("wpwput: calling wp_out\n");
#endif /* DEBUG */
			wp_out(wp);
		}
		v_lock(&wp->wp_lock, s);
		break;

	default:
		cmn_err(CE_WARN, "wpwput: unknown message type %d",
			mp->b_datap->db_type);
		/*
		 *+ The wp driver received a message it doesn't
		 *+ recognize and is discarding it.
		 */
		freemsg(mp);
		break;

	}	/* end switch */
}

/*
 * wp_out
 *	get next buffer with data and output via wp_start
 *
 * 	must be called with wp_info locked
 */
static
wp_out(wp)
	struct wp_info *wp;		/* device wpecific info */
{
	mblk_t *bp;
	queue_t *q;

	q = wp->wp_qptr;
loop:
	if ((bp = wp->wp_msg) == NULL) {
		if ((bp = getq(q)) == NULL) {
#ifdef DEBUG
			WPDEBUG("wp_out: queue empty\n");
#endif /* DEBUG */
			vall_sema(&wp->wp_sema);
			return;
		}
		wp->wp_msg = bp;
	}
	if (bp->b_rptr >= bp->b_wptr) {
		bp = wp->wp_msg->b_cont;
		wp->wp_msg->b_cont = NULL;
		freeb(wp->wp_msg);
		wp->wp_msg = bp;
		goto loop;
	}

	/*
	 * Found a message with data in it.
	 */
#ifdef DEBUG
	WPDEBUG("wpout: going to call wp_start\n");
#endif /* DEBUG */
	wp->wp_state |= WP_BUSY;
	wp->wp_count = (ushort)(bp->b_wptr - bp->b_rptr);
	wp->wp_buf = (char *)bp->b_rptr;
	wp_start(wp);
}

/*
 * wp_start - Send characters to printer. 
 *
 * Send transmit command to SSM.
 * (must be called with wp_info structure locked)
 */
static
wp_start(wp)
	register struct wp_info *wp;
{
	register struct print_xcb *xcb;
	spl_t s;

#ifdef DEBUG
	WPDEBUG("    0x%x bytes at 0x%x\n", wp->wp_count, wp->wp_buf);
#endif /* DEBUG */
	xcb = (struct print_xcb *)
		PRINT_BASE_CB(wp->wp_ssm->ssm_prnt_cbs, wp->wp_devno) 
		+ PCB_XMIT_CB;
	xcb->xcb_cmd = PCB_XMIT | PCB_IENABLE;
	xcb->xcb_status = PCB_BUSY;
	xcb->xcb_count = wp->wp_count;
	xcb->xcb_addr = (ulong)(wp->wp_buf);
	s = splhi();
	mIntr(wp->wp_ssm->ssm_slicaddr, PRINT_BIN, (unchar)PRVEC(PCB_XMIT_V));
	splx(s);
}

/*
 * wpintr - SSM interrupt service routine
 */
void
wpintr(vector)
	int vector;
{
	int unit = (vector - wp_base_vector) / NVEC_FROM_SSM;
	int type = (vector - wp_base_vector) % NVEC_FROM_SSM;
	struct wp_info *wp;
	struct print_xcb *xcb;
	spl_t s;

#ifdef DEBUG
	WPDEBUG("wpintr: vector = %d, base = %d\n", vector, wp_base_vector);
#endif /* DEBUG */
	if (unit < 0 || unit > wpmaxdev) {
		cmn_err(CE_WARN, "wp: stray interrupt, unit out of range %d",
			unit);
		/*
		 *+ The wp driver received an interrupt for a nonexistent
		 *+ device.
		 */
		return;
	}
	if ((wp = wp_info[unit]) == NULL) {
		cmn_err(CE_WARN, "wp: stray interrupt, dead unit %d", unit);
		/*
		 *+ The wp driver received an interrupt for a device that was
		 *+ not configured.
		 */
		return;
	}
	s = p_lock(&wp->wp_lock, WP_SPL);
	if (wp->wp_qptr == NULL) {	/* device not open */
		v_lock(&wp->wp_lock, s);
		return;
	}
		
	if (type == PCB_READY) {
		/*
		 * printer ready so restart I/O 
		 */
#ifdef DEBUG
		WPDEBUG("wpintr: PCB_READY intr\n");
#endif /* DEBUG */
		wp->wp_state &= ~WP_BUSY;
		wp_out(wp);
		v_lock(&wp->wp_lock, s);
		return;
	}
	/*
	 *  This is a transmit complete interrupt.
	 *  Make sure there is an outstanding request...
	 */
	if (wp->wp_msg == NULL) {
		cmn_err(CE_WARN, "wp: invalid completion interrupt - unit %d", unit);
		/*
		 *+ The wp driver received a transmit complete
		 *+ interrupt for a device that had no outstanding
		 *+ I/O requests.  This can indicate a hardware
		 *+ problem.
		 */
		v_lock(&wp->wp_lock, s);
		return;
	}
	xcb = (struct print_xcb *)
			PRINT_BASE_CB(wp->wp_ssm->ssm_prnt_cbs, wp->wp_devno) 
			+ PCB_XMIT_CB;
	
	/*
	 * update transfer count and address
	 */
#ifdef DEBUG
	WPDEBUG("I: XMIT intr\n");
#endif /* DEBUG */
	wp->wp_msg->b_rptr = (unsigned char *)xcb->xcb_addr;
	if (xcb->xcb_status == PCB_ERR && xcb->xcb_pstatus != PCM_READY) {
		/*
		 * Printer became "not ready" (offline, out-of-paper..)
		 * while transmitting --- don't wakeup process until 
		 * it becomes ready again.
		 */
#ifdef DEBUG
		 WPDEBUG("I: printer offline\n");
#endif /* DEBUG */
		 v_lock(&wp->wp_lock, s);
		 return;
	} else if (xcb->xcb_status == PCB_ERR) {
		/*  firmware currently doesn't do this! */
#ifdef DEBUG
		WPDEBUG("wpintr: PCB_ERR\n");
#endif /* DEBUG */
		freemsg(wp->wp_msg);
		wp->wp_msg = NULL;
		v_lock(&wp->wp_lock, s);
		return;
	}
	wp->wp_state &= ~WP_BUSY;
	wp_out(wp);
#ifdef DEBUG
	WPDEBUG("I: end\n"); 
#endif /* DEBUG */
	v_lock(&wp->wp_lock, s);
}

/*
 * wp_ready - get current status of printer
 *		
 * Determine if printer is online, has paper, and has no
 * errors. Return 1 if ready, 0 if not.
 * (must be called with wp_info locked) 
 */
static
wp_ready(wp)
	register struct wp_info *wp;
{
	register struct print_mcb *mcb;
	spl_t s;

#ifdef DEBUG
	WPDEBUG("wp_ready: start\n");
#endif /* DEBUG */
	mcb = (struct print_mcb *)
			PRINT_BASE_CB(wp->wp_ssm->ssm_prnt_cbs, wp->wp_devno) 
			+ PCB_MSG_CB;
	mcb->mcb_cmd = PCB_GSTAT;
	mcb->mcb_status = PCB_BUSY;
	mcb->mcb_iface = 0;
	s = splhi();
	mIntr(wp->wp_ssm->ssm_slicaddr, PRINT_BIN, (unchar)PRVEC(PCB_MSG_V));
	splx(s);
	while (mcb->mcb_status == PCB_BUSY)
		continue;
	if (mcb->mcb_status == PCB_ERR || mcb->mcb_iface != PCM_READY) {
#ifdef DEBUG
		WPDEBUG("wp_ready: NOTREADY mcb_status %d, mcb_iface %d\n",
			mcb->mcb_status, mcb->mcb_iface);	
#endif /* DEBUG */
		return(0);
	} 
#ifdef DEBUG
	WPDEBUG("wp_ready: end (READY)\n");
#endif /* DEBUG */
	return(1);
}

/*
 * wp_timer - when printer is not ready, runs every 2*HZ to detect
 *	      change of state
 */
static
wp_timer(wp)
	struct wp_info *wp;
{
	spl_t s;

	s = p_lock(&wp->wp_lock, WP_SPL);
	if (wp_ready(wp)) {
#ifdef DEBUG
		WPDEBUG("wp_timer: ready!\n");
#endif /* DEBUG */
		wp->wp_state &= ~WP_TIMER;
		vall_sema(&wp->wp_sema);
	} else
		wp->wp_timeid = timeout(wp_timer, (caddr_t)wp, 2*HZ);
	v_lock(&wp->wp_lock, s);
}

/*
 * init_ssm_prnt_dev - initialize SSM printer port 
 *
 * Initializes an SSM printer port by sending a PCB_INIT 
 * message to the SSM.
 * Assumes that mIntr() retries messages until they succeed.
 */
init_ssm_prnt_dev(slic, pcbs, bin, basevec, unit, interface)
	unchar slic;
	struct print_cb *pcbs;
	unchar bin, basevec;
	short unit;
	unchar interface;
{
	register struct print_icb *iptr;
	spl_t s;

	iptr = (struct print_icb *) (PRINT_BASE_CB(pcbs, unit) + PCB_MSG_CB);
	iptr->icb_cmd = PCB_INIT;
	iptr->icb_dest = SL_GROUP | TMPOS_GROUP;
	iptr->icb_basevec = basevec;
	iptr->icb_interface = interface;
	iptr->icb_scmd = SL_MINTR | bin;
	iptr->icb_status = PCB_BUSY;

	s = splhi();
	mIntr(slic, PRINT_BIN, (unchar)PRVEC(PCB_MSG_V));
	splx(s);
	while (iptr->icb_status == PCB_BUSY)
		continue;
}
