/* $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: lpmod.c 1.17 90/01/10 $"

/* $Log:	lpmod.c,v $
 */


/*
 * Parallel printer canonical processing module 
 */

#include "sys/types.h"
#include "sys/param.h"
#include "sys/sysmacros.h"
#include "sys/mutex.h"
#include "sys/clock.h"
#include "sys/intctl.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/stropts.h"
#include "sys/strid.h"
#include "sys/dir.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/cmn_err.h"
#include "sys/errno.h"
#include "sys/printer.h"
#include "sys/lpmod.h"

static struct module_info lpmod_info = {
	STRID_LPMOD, "lpmod", 0, INFPSZ, 4096, 512,
};

static int lpmodopen(), lpmodclose(), lpmodwput(),
	lpmodwsrv(), lpmodrput();

static mblk_t *lpmod_canon();

static struct qinit rinit = {
	lpmodrput, NULL, lpmodopen, lpmodclose, NULL, &lpmod_info, NULL,
};

static struct qinit winit = {
	lpmodwput, lpmodwsrv, NULL, NULL, NULL, &lpmod_info, NULL,
};

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

extern struct lpmod_info lpm_info[];	/* static table of lpmod info structs */
extern int lpm_cnt;			/* no. of entries in lpm_info */

lock_t lpmod_lock;			/* lock for finding free structure */

/*
 * second call to bappend assumes we could never reach the data limit of
 * a new buffer by adding one character!
 */
#define PUTC(hd, cur, c) { \
	if ( !bappend(&(cur), (c))) { \
		if ((hd) == NULL) \
			(hd) = (cur); \
		else \
			linkb((hd), (cur)); \
		(cur) = NULL; \
		(void)bappend(&(cur), (c)); \
	} \
}

/*
 * Called from main at boot time.
 */
void
lpmod_init()
{
	/*
	 * This lock protects the allocation of entries from
	 * the lpm_info table. 
	 */
	init_lock(&lpmod_lock);
}

/*
 * lpmodopen - streams module open
 *	set up data structures, request configuration info
 *	from driver
 */
/*ARGSUSED*/
static int
lpmodopen(q, dev, flag, sflag)
	queue_t *q;		/* read queue */
{
	struct lpmod_info *info;
	struct pr_config *cfg;
	mblk_t *bp;
	spl_t s;
	int i;

	if (sflag != MODOPEN)
		return (OPENFAIL);

	if (q->q_ptr) {
		/*
		 * Device is already open.  Don't need to reinitialize
		 * fields.
		 */
		 return(0);
	}

	/*
	 * Allocate an lpmod_info structure for this stream.
	 */
	s = p_lock(&lpmod_lock, LPMOD_SPL);
	for (info = NULL, i = 0; i < lpm_cnt; i++) {
		if (lpm_info[i].lpm_qptr == NULL) {
			info = &lpm_info[i];
			info->lpm_qptr = WR(q);
			break;
		}
	}
	v_lock(&lpmod_lock, s);
	if (info == NULL) {
		cmn_err(CE_NOTE, "lpmod: No lpmod structures.  All %d used",
			lpm_cnt);
		/*
		 *+ A streams push of the lpmod printer module failed
		 *+ because no lpmod data structures were free.  
		 *+ To increase the number of available structures,
		 *+ change NLPM_DEVS in the lpmod configuration file.
		 */ 
		u.u_error = ENOSPC;
		return(OPENFAIL);
	}

	/*
	 * Set up default configuration values in case driver doesn't
	 * respond to request, then send a message to driver
	 * requesting device-specific config information.
	 * (Driver is expected to handle this request immediately
	 * rather than queuing it to be handled by a service routine.)
	 */
	q->q_ptr = (caddr_t)info;
	WR(q)->q_ptr = (caddr_t)info;

	init_sema(&info->lpm_wait, 0);
	init_lock(&info->lpm_lock);
	info->lpm_state = 0;
	info->lpm_ops = LPM_DEF_OPS;
	info->lpm_width = LPM_DEF_WIDTH;
	info->lpm_height = LPM_DEF_HEIGHT;
	if (bp = allocb(sizeof(struct pr_config), BPRI_MED)) { 
		bp->b_datap->db_type = M_PCPROTO;
		cfg = (struct pr_config *)bp->b_rptr;
		bp->b_wptr += sizeof(struct pr_config);
		cfg->p_type = REQUEST_CFG;
		putnext(WR(q), bp);
		if (cfg->p_type == REQUEST_CFG_RESPONSE) {
			info->lpm_ops = cfg->p_ops;
			info->lpm_width = cfg->p_width;
			info->lpm_height = cfg->p_height;
			freemsg(bp);
		}
	}
	if (info->lpm_width > LPMLONGLINE)
		info->lpm_width = LPMLONGLINE;
	info->lpm_line = 0;
	info->lpm_col = 0;
	info->lpm_lcol = 0;

	enable_procs(q);	/* enable put/service procedures */
	/* If not in raw mode, put a formfeed onto the queue */
	if (info->lpm_ops != PRRAW && (bp = allocb(1, BPRI_MED))) {
		*bp->b_wptr++ = FORMFEED;
		putq(WR(q), bp);
	}
	return(0);
}

/*
 * lpmodclose - close streams module
 *	drain output queue and mark data structures as
 *	closed
 */
static int
lpmodclose(q)
	queue_t *q;		/* read queue */
{
	register struct lpmod_info *info;
	mblk_t *bp;
	spl_t s;

	info = (struct lpmod_info *)q->q_ptr;

	/* drain queue */
	s = p_lock(&info->lpm_lock, LPMOD_SPL);
	if (info->lpm_ops != PRRAW && (bp = allocb(1, BPRI_MED))) {
		*bp->b_wptr++ = FORMFEED;
		info->lpm_state |= LPM_MESSAGES;
		putq(WR(q), bp);
	}
	while (info->lpm_state & LPM_MESSAGES) {
		info->lpm_state |= LPM_SLEEP;
		if (p_sema_v_lock(&info->lpm_wait, (PZERO+1)|PCATCH,
				  &info->lpm_lock) == PCATCH) {
			/*
			 * woken up by a signal so cleanup
			 */
			u.u_error = EINTR;
			disable_procs(q);
			info->lpm_qptr = NULL;	/* mark struct as unused */
			return;
		}
		s = p_lock(&info->lpm_lock, LPMOD_SPL);
	}
	v_lock(&info->lpm_lock, s);
	disable_procs(q);
	info->lpm_qptr = NULL;		/* mark structure as unused */
	return;
}

/*
 * lpmodrput - streams read put procedure
 *	handle messages passed from driver
 *	(this is only here to handle M_FLUSH, etc.)
 */
static int
lpmodrput(q, mp)
	queue_t *q;		/* read queue */
	register mblk_t *mp;	/* message pointer */
{
	struct lpmod_info *info;
	struct pr_config *cfg;

	switch (mp->b_datap->db_type) {

	case M_FLUSH:
		/* Canonical flush handling */
		if (*mp->b_rptr & FLUSHR)
			flushq(q, FLUSHDATA);
		putnext(q, mp);
		break;

	case M_PCPROTO:
		/*
		 * Check for REQUEST_CFG response
		 */
		cfg = (struct pr_config *)mp->b_rptr;
		if (cfg->p_type == REQUEST_CFG_RESPONSE) {
			info = (struct lpmod_info *)q->q_ptr;
			info->lpm_ops = cfg->p_ops;
			info->lpm_width = cfg->p_width;
			info->lpm_height = cfg->p_height;
			freemsg(mp);
		} else {
			putnext(q, mp);
		}
		break;

	default:
		putnext(q, mp);
		break;

	}	/* end switch */
	return;
}

/*
 * lpmodwput - streams write put procedure
 *	process downstream messages; either deal
 *	with them here, pass down, or queue for later
 */
static int
lpmodwput(q, mp)
	queue_t *q;		/* write queue */
	register mblk_t *mp;	/* message pointer */
{
	struct lpmod_info *info;
	spl_t s;

	info = (struct lpmod_info *)q->q_ptr;

	switch (mp->b_datap->db_type) {

	case M_FLUSH:
		/* Canonical flush handling */
		if (*mp->b_rptr & FLUSHW)
			flushq(q, FLUSHDATA);
		putnext(q, mp);
		break;

	default:
		if (mp->b_datap->db_type > QPCTL) {
			putnext(q, mp);
		} else {
			s = p_lock(&info->lpm_lock, LPMOD_SPL);
			info->lpm_state |= LPM_MESSAGES;
			putq(q, mp);
			v_lock(&info->lpm_lock, s);
		}
		break;

	}	/* end switch */
	return;
}

/*
 * lpmodwsrv - streams write service procedure
 *	process messages on queue and pass on to driver 
 */
static int
lpmodwsrv(q)
	queue_t *q;		/* write queue */
{
	struct lpmod_info *info;
	mblk_t *mp, *new;
	spl_t s;

	info = (struct lpmod_info *)q->q_ptr;
	while (mp = getq(q)) {

		if (canputnext(q)) {
			/*
			 * Do canonical processing on message if not
			 * in raw mode.
			 * (lpmod_canon allocates new msg buffer and,
			 * if successful, releases old one.)
			 */
			if (mp->b_datap->db_type == M_DATA
			    && info->lpm_ops != PRRAW) {
				if ((new = lpmod_canon(info, mp)) == mp) {
					/*
					 * unable to allocate new buffer so
					 * throw away data?
					 */
					freemsg(mp);
					continue;
				} else
					mp = new;
			}
			putnext(q, mp);
		} else {
			putbq(q, mp);
			return;
		}
	}
	/* 
	 * No more messages on queue, so wakeup processes
	 * waiting for queue to drain.
	 */
	info = (struct lpmod_info *)q->q_ptr;

	s = p_lock(&info->lpm_lock, LPMOD_SPL);
	info->lpm_state &= ~LPM_MESSAGES;
	if (info->lpm_state & LPM_SLEEP) {
		vall_sema(&info->lpm_wait);
	}
	v_lock(&info->lpm_lock, s);

	return;
}

/*
 * lpmod_canon
 *	do canonical processing on data buffers according
 *	to configured mode for this device
 */
static mblk_t *
lpmod_canon(info, mp)
	struct lpmod_info *info;	/* device info */
	register mblk_t *mp;		/* original message buffer */
{
	mblk_t *nmp = NULL, *nbp = NULL;	/* new message buffer */
	mblk_t *bp;		/* save original mp */
	unsigned char *sav_rptr;		/* save rptr */
	int logcol, line, col;
	register c;

	logcol = info->lpm_lcol;
	line = info->lpm_line;
	col = info->lpm_col;

	sav_rptr = mp->b_rptr;
	for (bp = mp; bp; bp = bp->b_cont) {
		while (bp->b_rptr < bp->b_wptr) {
			c = *bp->b_rptr++;
			if (info->lpm_ops == PRCAPS) {
				int c2;

				if (c >= 'a' && c <= 'z')
					c += 'A' - 'a';
				else {
					switch (c) {

					case '{':
						c2 = '(';
						goto esc;
					case '}':
						c2 = '(';
						goto esc;
					case '`':
						c2 = '\'';
						goto esc;
					case '|':
						c2 = '!';
						goto esc;
					case '~':
						c2 = '^';
esc:
						if (logcol < col) {
							PUTC(nmp, nbp, '\r');
							col = 0;
						}
						if (logcol < info->lpm_width) {
							while (logcol > col) {
								PUTC(nmp, nbp, ' ');
								col++;
							}
							PUTC(nmp, nbp, c2);
							col++;
						}
						c = '-';
						break;
					}
				}
			}	/* end if PRCAPS */

			if (c == ' ')
				logcol++;
			else switch(c) {
				case '\t':
					logcol = (logcol + 8) & ~7;
					break;
				case '\b':
					if (logcol > 0)
						logcol--;
					break;
				case '\f':
					if (line == 0 && col == 0)
						break;
					/* falls into ... */
				case '\n':
					PUTC(nmp, nbp, c);
					if (c == '\f')
						line = 0;
					else
						line++;
					col = 0;
					/* falls into ... */
				case '\r':
					logcol = 0;
					break;
				default:	/* a printable character */
					if (logcol < col) {
						PUTC(nmp, nbp, '\r');
						col = 0;
					}
					if (logcol < info->lpm_width) {
						while (logcol > col) {
							PUTC(nmp, nbp, ' ');
							col++;
						}
						PUTC(nmp, nbp, c);
						col++;
					}
					logcol++;
			}	/* end of switch */
			if (logcol > LPMLONGLINE)
				logcol = LPMLONGLINE;
		}
	}	/* end of for */

	info->lpm_lcol = logcol;
	info->lpm_col = col;
	info->lpm_line = line;

	if (nbp) {
		if (nmp == NULL)
			nmp = nbp;
		else
			linkb(nmp, nbp);
		freemsg(mp);
		return(nmp);
	}
	/*
	 * Couldn't allocate a new buffer, so return
	 * old buffer pointer.
	 */
	mp->b_rptr = sav_rptr;
	return(mp);
}

/*
 * bappend - add character to end of message buffer,
 *	     allocating new buffer if necessary
 */
static
bappend(bpp, ch)
	mblk_t **bpp;
	int ch;
{
	mblk_t *bp;

	if (bp = *bpp) {
		if (bp->b_wptr >= bp->b_datap->db_lim)
			return (0);
	} else if ((*bpp = bp = allocb(LPMBLKSIZ, BPRI_MED)) == NULL)
		return (1);
	*bp->b_wptr++ = ch;
	return (1);
}
