#if !defined(lint) && !defined(NOID)
static char sccsid[] = "@(#)cgsix.c 1.1 94/10/31";
#endif

/* This driver fixes 1111182: system hangs when running DGA on gx on sun4
 *
 * There are two changes here.  First, the lock fault routine
 * is within an xc_attention()/xc_dismissed() pair.  Second, the
 * lock timeout routine no longer validates mappings, but simply
 * flags the lock as broken and wakes up.  The pagefault routine
 * will validate the mappings a moment later.
 */

/*
 * Copyright 1989, 1993, Sun Microsystems, Inc.
 */

/*
 * Sbus accelerated 8 bit color frame buffer driver
 */

#include "win.h"

#include <sys/param.h>
#include <sys/time.h>
#include <sys/buf.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/map.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/vmmac.h>
#include <sys/mman.h>

#include <sys/kmem_alloc.h>

#include <sun/fbio.h>

#include <sundev/mbvar.h>
#include <sundev/cg6reg.h>

#include <machine/mmu.h>
#include <machine/pte.h>

#include <pixrect/pixrect.h>
#include <pixrect/pr_impl_util.h>
#include <pixrect/pr_planegroups.h>
#include <pixrect/memvar.h>
#include <pixrect/cg3var.h>	/* for CG3_MMAP_OFFSET */
#include <pixrect/cg6var.h>

#include <machine/vm_hat.h>
#include <vm/as.h>
#include <vm/seg.h>

addr_t map_regs();
void unmap_regs();
void report_dev();

/*
 * This flag indicates that we should use kcopy instead of copyin
 * for cursor ioctls.
 */
int fbio_kcopy;
extern int copyin(), kcopy();

#define	CG6_FBC_WAIT	500000		/* .5 seconds */

/*
 * A bunch of places in here need to know whether
 * we are supporting the "P4" or the "SBus" version.
 */
#if defined(sun4c) || defined(sun4m)
#define	SBUS_CGSIX
#endif

/* configuration options */
#ifndef OLDDEVSW
#define	OLDDEVSW 1
#endif  OLDDEVSW

#ifndef NOHWINIT
#define	NOHWINIT 0
#endif NOHWINIT

#ifndef CG6DEBUG
#define	CG6DEBUG 0
#endif CG6DEBUG

#if NOHWINIT
int cg6_hwinit = 1;
#endif NOHWINIT

#if CG6DEBUG
int cg6_debug = 0;
#define	DEBUGF(level, args)	_STMT(if (cg6_debug >= (level)) printf args;)
#define	DUMP_SEGS(level, c)	_STMT(if (cg6_debug >= (level)) dump_segs(c);)
#else CG6DEBUG
#define	DEBUGF(level, args)	/* nothing */
#define	DUMP_SEGS(level, c)	/* nothing */
#endif CG6DEBUG

#if OLDDEVSW
#define	STATIC /* nothing */
#define	cg6_open	cgsixopen
#define	cg6_close	cgsixclose
#define	cg6_mmap	cgsixmmap
#define	cg6_ioctl	cgsixioctl
#else OLDDEVSW
#define	STATIC static
#endif OLDDEVSW

typedef	int	(*ifunc)() ;

	/* memory-leak testing */

#if CG6DEBUG
static	int	total_memory = 0 ;

static caddr_t
my_kmem_zalloc(size,flag)
	u_int	size;
	int	flag;
{
	total_memory += size ;
	DEBUGF(5, ("zalloc %d bytes, total=%d\n",size, total_memory));
	return new_kmem_zalloc(size,flag) ;
}

static caddr_t
my_kmem_alloc(size,flag)
	u_int	size;
	int	flag;
{
	total_memory += size ;
	DEBUGF(5, ("alloc %d bytes, total=%d\n",size, total_memory));
	return new_kmem_alloc(size,flag) ;
}

static
my_kmem_free(ptr,size)
	caddr_t	ptr;
	u_int	size;
{
	total_memory -= size ;
	DEBUGF(5, ("free %d bytes, total=%d\n",size, total_memory));
	kmem_free(ptr, size) ;
}

#define	new_kmem_zalloc	my_kmem_zalloc
#define	new_kmem_alloc	my_kmem_alloc
#define	kmem_free	my_kmem_free

#endif CG6DEBUG


#define	PPID(pp)	((pp==NULL) ? -1 : pp->p_pid)
#define	LPID(lp)	\
  ((lp==NULL || lp->owner==NULL) ? -1 : PPID(lp->owner->procp))
#define	LPOFF(lp)	(lp==NULL ? 0 : lp->offset)


#if CG6DEBUG
static
cg6_assfail(line,ass)
	int	line ;
	char	*ass ;
{
	printf("cg6: line %d: assertion failed \"%s\"\n", line,ass) ;
}

#define	ASRT(e) do{if(!(e)) cg6_assfail(__LINE__,"e");}while(0)

#else
#define	ASRT(e)			/* nothing */
#endif CG6DEBUG


/* config info */
static int cg6_identify();
static int cg6_attach();
STATIC int cg6_open();
STATIC int cg6_close();
STATIC int cg6_ioctl();
STATIC int cgsixsegmap();

struct dev_ops cgsix_ops = {
	0,		/* revision */
	cg6_identify,
	cg6_attach,
	cg6_open,
	cg6_close,
	0, 0, 0, 0, 0,
	cg6_ioctl,
	0,
	cgsixsegmap,
};

/*
 * Table of CG6 addresses
 */
static	struct mapping {
	int	vaddr;		/* virtual (mmap offset) address */
	int	paddr;		/* physical (byte) address */
	int	size;		/* size in bytes (page multiple) */
} cg6_map[] = {
	{ CG6_VADDR_FBCTEC,	CG6_ADDR_FBC,	CG6_FBCTEC_SZ	},
	{ CG6_VADDR_CMAP,	CG6_ADDR_CMAP,	CG6_CMAP_SZ	},
	{ CG6_VADDR_FHCTHC,	CG6_ADDR_FHCTHC, CG6_FHCTHC_SZ	},
	{ CG6_VADDR_ROM,	CG6_ADDR_ROM,	CG6_ROM_SZ	},
	{ CG6_VADDR_DHC,	CG6_ADDR_DHC,	CG6_DHC_SZ	},
	{ CG6_VADDR_ALT,	CG6_ADDR_ALT,	CG6_ALT_SZ	},
	{ 0 }
};

#define MAP_FBCTEC      0

/* how much to map */
#define	CG6MAPSIZE	MMAPSIZE(0)

/* vertical retrace counter page */
#define	VRT_OFFSET	(CG6_VADDR_COLOR+CG6_FB_SZ)

/* per-unit data */
struct cg6_softc
{
#if NWIN > 0
	Pixrect		pr;		/* kernel pixrect */
	struct mprp_data prd;		/* pixrect private data */
#define	_w		pr.pr_size.x
#define	_h		pr.pr_size.y
#define	_fb		prd.mpr.md_image
#define	_linebytes	prd.mpr.md_linebytes
#else NWIN > 0
	int		_w, _h;		/* resolution */
	MPR_T		*_fb;		/* frame buffer address */
#endif NWIN > 0
	int		size;		/* total size of frame buffer */
	int		dummysize;	/* total size of overlay plane */
	addr_t		base;		/* mapped base address */
	int		basepage;	/* page # for base address */

	struct softcur {
		short enable;		/* cursor enable */
		short pad1;
		struct fbcurpos pos;	/* cursor position */
		struct fbcurpos hot;	/* cursor hot spot */
		struct fbcurpos size;	/* cursor bitmap size */
		u_long image[32];	/* cursor image bitmap */
		u_long mask[32];	/* cursor mask bitmap */
	} cur;

	union {				/* shadow overlay color map */
		u_long	omap_long[2];	/* cheating here to save space */
		u_char	omap_char[3][2];
	} omap_image;
#define	omap_rgb	omap_image.omap_char[0]
	u_short		omap_update;	/* overlay colormap update flag */

	u_short		cmap_index;	/* colormap update index */
	u_short		cmap_count;	/* colormap update count */
	union {				/* shadow color map */
		u_long	cmap_long[CG6_CMAP_ENTRIES * 3 / sizeof (u_long)];
		u_char	cmap_char[3][CG6_CMAP_ENTRIES];
	} cmap_image;
#define	cmap_rgb	cmap_image.cmap_char[0]

#define	CG6VRTIOCTL	1	/* FBIOVERTICAL in effect */
#define	CG6VRTCTR	2	/* OWGX vertical retrace counter */
	unsigned long	fbmappable;	/* bytes mappable */
	int		*vrtpage;	/* pointer to VRT page */
	int		vrtmaps;	/* number of VRT page maps */
	int		vrtflag;	/* vrt interrupt flag */
	struct cg6_info	cg6info;	/* info about this cg6 */
	struct mon_info	moninfo;	/* info about this monitor */
	struct cg6_cntxt *curcntxt;	/* context switching */
	int		chiprev ;	/* fbc chip revision # */
#ifdef	MULTIPROCESSOR
	struct segcg6_data *mappings ;	/* full list of mappings */
        int    cmap_sleeping;           /* did we have a mem fault
                                        because the kernel was in the process
                                        of cmap change, and had mapped out
                                        the dac, but a user had mapped
                                        the dac and tried to access it? */

#endif	MULTIPROCESSOR
};

/* Per-segment info:
 *	Some, but not all, segments are part of a context.
 *	Any segment that is a MAP_PRIVATE mapping to the TEC or FBC
 *	will be part of a unique context.  MAP_SHARED mappings are part
 *	of the shared context and all such programs must arbitrate among
 *	themselves to keep from stepping on each other's register settings.
 *	Mappings to the framebuffer may or may not be part of a context,
 *	depending on exact hardware type.
 */
struct segcg6_data {
	struct seg		*link;		/* next segment in ctx */
	struct seg		*seg;		/* pointer to segment */
	int			flag;		/* see below */
	struct cg6_cntxt	*cntxtp; 	/* pointer to context */
	dev_t			dev;
	u_int			offset;
	u_char			prot, maxprot ;
#ifdef	MULTIPROCESSOR
	struct segcg6_data	*next_map ;	/* link to next mapping */
#endif	MULTIPROCESSOR
};

#define	SEG_CTXINIT	(1)	/* part of a context */
#define	SEG_SHARED	(2)	/* shared context */
#define	SEG_LOCK	(4)	/* lock page */
#define	SEG_VRT		(8)	/* vrt page */
#define	SEG_FBC		(0X10)	/* segment includes fbc */
#define	SEG_TEC		(0X40)	/* segment includes tec */
#define	SEG_FB		(0X100)	/* segment includes framebuffer */
#define	SEG_CMAP	(0X400)	/* segment includes dacs */

#define	SEG_GLOBAL_MAP	(SEG_CMAP)	/* segs that need to be in map list */


static int ncg6;
static struct cg6_softc *cg6_softc = NULL ;

/* default structure for FBIOGATTR ioctl */
static struct fbgattr cg6_attr =  {
/*	real_type         owner */
	FBTYPE_SUNFAST_COLOR, 0,
/* fbtype: type             h  w  depth cms  size */
	{ FBTYPE_SUNFAST_COLOR, 0, 0, 8,    256,  0 },
/* fbsattr: flags emu_type      dev_specific */
	{ 0, FBTYPE_SUN4COLOR, { 0 } },
/*        emu_types */
	{ FBTYPE_SUNFAST_COLOR, FBTYPE_SUN3COLOR, FBTYPE_SUN4COLOR, -1}
};


/*
 * handy macros
 */

#define	getsoftc(unit)	(&cg6_softc[unit])

#define	btob(n)		ctob(btoc(n))		/* XXX */

#define	BZERO(d,c)	bzero((caddr_t) (d), (u_int) (c))
#define	COPY(f,s,d,c)	(f((caddr_t) (s), (caddr_t) (d), (u_int) (c)))
#define	CG6BCOPY(s,d,c)	COPY(bcopy,(s),(d),(c))
#define	COPYIN(s,d,c)	COPY(copyin,(s),(d),(c))
#define	COPYOUT(s,d,c)	COPY(copyout,(s),(d),(c))

/* enable/disable interrupt */
#define	TEC_EN_VBLANK_IRQ	0x20
#define	TEC_HCMISC_IRQBIT	0x10

/* position value to use to disable HW cursor */
#define	CG6_CURSOR_OFFPOS	(0xffe0ffe0)

#define	cg6_set_video(softc, on) \
	thc_set_video(CG6VA_TO_THC((softc)->base), (on))

#define	cg6_get_video(softc) \
	thc_get_video(CG6VA_TO_THC((softc)->base))

#define	cg6_int_enable(softc) \
	thc_int_enable(CG6VA_TO_THC((softc)->base))

#define	cg6_int_disable(softc) \
	thc_int_disable(CG6VA_TO_THC((softc)->base))

#define	cg6_int_pending(softc) \
	thc_int_pending(CG6VA_TO_THC((softc)->base))

/* check if color map update is pending */
#define	cg6_update_pending(softc) \
	((softc)->cmap_count || (softc)->omap_update)


/*
 * forward references
 */
static cg6_poll();
static void cg6_intr();
static void cg6_reset_cmap();
static void cg6_update_cmap();
static void cg6_cmap_bcopy();

#if NWIN > 0

/*
 * SunWindows specific stuff
 */
static cg6_rop(), cg6_putcolormap();

/* kernel pixrect ops vector */
static struct pixrectops cg6_pr_ops = {
	cg6_rop,
	cg6_putcolormap,
	mem_putattributes
};





/* XXX cursor turd avoidance -- there must be a better way to do this */

static cg6_rop_wait = 50;	/* milliseconds */

static
cg6_rop(dpr, dx, dy, dw, dh, op, spr, sx, sy)
	Pixrect *dpr;
	int dx, dy, dw, dh, op;
	Pixrect *spr;
	int sx, sy;
{
	Pixrect mpr;
	int unit;

	if (spr && spr->pr_ops == &cg6_pr_ops) {
		unit = mpr_d(spr)->md_primary;
		mpr = *spr;
		mpr.pr_ops = &mem_ops;
		spr = &mpr;
	}
	else
		unit = mpr_d(dpr)->md_primary;

	if (cg6_rop_wait) {
		register u_int *statp =
			&CG6VA_TO_FBC((getsoftc(unit)->base))->l_fbc_status;

		CDELAY(!(*statp & L_FBC_BUSY), cg6_rop_wait << 10);
	}

	return mem_rop(dpr, dx, dy, dw, dh, op, spr, sx, sy);
}

/* XXX can I share code with ioctl? */
/* XXX vestigial overlay stuff */
static
cg6_putcolormap(pr, index, count, red, green, blue)
	Pixrect *pr;
	int index, count;
	unsigned char *red, *green, *blue;
{
	register struct cg6_softc *softc = getsoftc(mpr_d(pr)->md_primary);
	register u_int rindex = (u_int) index;
	register u_int rcount = (u_int) count;
	register u_char *map;
	register u_int entries;

	DEBUGF(5, ("cg6_putcolormap unit=%d index=%d count=%d\n",
		mpr_d(pr)->md_primary, index, count));

#ifdef  MULTIPROCESSOR
            /*
             * blech, foo.  Need to keep user processes from touching the
             * colormap registers while we're touching them or *waiting* to do
             * so.  Do this by invalidating all user mappings here.  Only blow
             * away the cmap page from the mapping.
             */
            {  
              register struct segcg6_data *ptr ;
              softc->cmap_sleeping = 1;
                DUMP_SEGS(5, softc->curcntxt);
              for(ptr=softc->mappings; ptr != NULL; ptr = ptr->next_map)
                if( ptr->flag & SEG_CMAP ) {
                  DEBUGF(5, ("removing mapping for seg %x at %x\n",
                        ptr->seg,
                        ptr->seg->s_base + CG6_VADDR_CMAP - ptr->offset));
                  hat_unload(ptr->seg,
                        ptr->seg->s_base + CG6_VADDR_CMAP - ptr->offset,
                        CG6_CMAP_SZ) ;
                }
            }
#endif  MULTIPROCESSOR
 
	map = softc->cmap_rgb;
	entries = CG6_CMAP_ENTRIES;

	/* check arguments */
	if (rindex >= entries ||
		rindex + rcount > entries)
		return PIX_ERR;

	if (rcount == 0)
		return 0;

	/* lock out updates of the hardware colormap */
	if (cg6_update_pending(softc))
		cg6_int_disable(softc);

	map += rindex * 3;
	cg6_cmap_bcopy(red,   map,     rcount);
	cg6_cmap_bcopy(green, map + 1, rcount);
	cg6_cmap_bcopy(blue,  map + 2, rcount);

	cg6_update_cmap(softc, rindex, rcount);

	/* enable interrupt so we can load the hardware colormap */
	cg6_int_enable(softc);

	return 0;
}

#endif	NWIN > 0

static
cg6_identify(name)
	char *name;
{
	DEBUGF(1, ("cg6_identify(%s)\n", name));

	if (strcmp(name, "cgsix") == 0)
		return ++ncg6;
	else
		return 0;
}

static
cg6_attach(devi)
	struct dev_info *devi;
{
	register struct cg6_softc *softc;
	register addr_t reg;
	int w, h, bytes, basepage;
	static int unit;
	extern struct dev_info *top_devinfo;	/* in autoconf.c */

	DEBUGF(1, ("cg6_attach ncg6=%d unit=%d\n", ncg6, unit));

	/* Allocate softc structs on first attach */
	if (!cg6_softc) {
	    cg6_softc = (struct cg6_softc *)
		new_kmem_zalloc((u_int) sizeof (struct cg6_softc) * ncg6,1);
		unit = 0;
	    init_locks() ;
	}

	softc = getsoftc(unit);

	/* Grab properties from PROM */
	/* XXX don't really want default w, h */
	softc->_w = w = getprop(devi->devi_nodeid, "width", 1152);
	softc->_h = h = getprop(devi->devi_nodeid, "height", 900);
	bytes = getprop(devi->devi_nodeid, "linebytes", mpr_linebytes(w, 8));
#if NWIN > 0
	softc->_linebytes = bytes;
#endif NWIN > 0

	/* Compute size of color frame buffer */
	bytes = btob(bytes * h);
	softc->size = CG6MAPSIZE + bytes;

	/* Compute size of dummy overlay/enable planes */
	softc->dummysize = btob(mpr_linebytes(w, 1) * h) * 2;

#if NWIN > 0
	/* only use address property if we are console fb */
	if (reg = (addr_t) getprop(devi->devi_nodeid, "address", 0)) {
		int fbid = getprop(top_devinfo->devi_nodeid, "fb", -1);
		if (fbid == devi->devi_nodeid) {
			softc->_fb = (MPR_T *) reg;
			bytes = 0;
			DEBUGF(2, ("cg6 mapped by PROM\n"));
		}
	}
#else NWIN > 0
	bytes = 0;
#endif NWIN > 0
        softc->cg6info.line_bytes = softc->_linebytes;
        softc->cg6info.vmsize = getprop(devi->devi_nodeid, "vmsize", 1);
	if (softc->cg6info.vmsize > 1) {
	    softc->size = CG6MAPSIZE+8*1024*1024;
	    softc->fbmappable = 8*1024*1024;
	} else
	    softc->fbmappable = 1024*1024;
        softc->cg6info.accessible_width =
                                getprop(devi->devi_nodeid, "awidth", 1152);
        softc->cg6info.accessible_height =
            (softc->cg6info.vmsize*1024*1024)/softc->cg6info.accessible_width;
        softc->cg6info.hdb_capable =
                                getprop(devi->devi_nodeid, "dblbuf", 0);
        softc->cg6info.boardrev = getprop(devi->devi_nodeid, "boardrev", 0);
#ifdef SBUS_SIZE
	softc->cg6info.slot =
		((int) devi->devi_reg[0].reg_addr - SBUS_BASE) / SBUS_SIZE ;
#else
	softc->cg6info.slot = sbusslot((unsigned) devi->devi_reg[0].reg_addr);
#endif
	softc->vrtpage = NULL;
	softc->vrtmaps = 0;
	softc->vrtflag = 0;
#define	DEBUG
#ifdef DEBUG
	softc->cg6info.pad1 = CG6_VADDR_COLOR+CG6_FB_SZ;
#endif
        /*
         * get monitor attributes
         */
        softc->moninfo.mon_type = getprop(devi->devi_nodeid, "montype", 0);
        softc->moninfo.pixfreq = getprop(devi->devi_nodeid, "pixfreq", 929405);
        softc->moninfo.hfreq = getprop(devi->devi_nodeid, "hfreq", 61795);
        softc->moninfo.vfreq = getprop(devi->devi_nodeid, "vfreq", 65690);
        softc->moninfo.hfporch = getprop(devi->devi_nodeid, "hfporch", 32);
        softc->moninfo.vfporch = getprop(devi->devi_nodeid, "vfporch", 2);
        softc->moninfo.hbporch = getprop(devi->devi_nodeid, "hbporch", 192);
        softc->moninfo.vbporch = getprop(devi->devi_nodeid, "vbporch", 31);
        softc->moninfo.hsync = getprop(devi->devi_nodeid, "hsync", 128);
        softc->moninfo.vsync = getprop(devi->devi_nodeid, "vsync", 4);


	/*
	 * Allocate contiguous virtual space for registers and
	 * maybe frame buffer.  This gets overlayed a few lines down
	 * by fbmapin.  Kind of a retro-kludge; fixed in SVr4.
	 */
	if (!(reg = map_regs(devi->devi_reg[0].reg_addr,
		(u_int) (CG6MAPSIZE + bytes),
		devi->devi_reg[0].reg_bustype)))
		return -1;

	softc->base = reg;
	softc->basepage = basepage = fbgetpage(reg);
	softc->curcntxt = NULL ;
#ifdef	MULTIPROCESSOR
	softc->mappings = NULL ;
#endif	MULTIPROCESSOR

	/* next, replace this register mapping new ones.  Our
	 * offsets are based on the virtual addresses in cg6reg.h
	 */

#define	FBMAPIN(v,p,s)	fbmapin((addr_t)(reg + v - CG6_VBASE), \
    (int)(basepage + btoc(p)), (int)s)

	FBMAPIN(CG6_VADDR_FBCTEC, CG6_ADDR_FBC,    CG6_FBCTEC_SZ) ;
	FBMAPIN(CG6_VADDR_CMAP,   CG6_ADDR_CMAP,   CG6_CMAP_SZ) ;
	FBMAPIN(CG6_VADDR_FHCTHC, CG6_ADDR_FHCTHC, CG6_FHCTHC_SZ) ;
	FBMAPIN(CG6_VADDR_ROM,    CG6_ADDR_ROM,    CG6_ROM_SZ) ;


#if NWIN > 0
	/* map frame buffer if necessary */
	if (bytes) {
		fbmapin((addr_t) CG6VA_TO_DFB(reg),
			(int) (basepage + btoc(CG6_ADDR_COLOR)), bytes);
		softc->_fb = CG6VA_TO_DFB(reg);
	}
#endif NWIN > 0

	softc->chiprev =
	  (*CG6VA_TO_FHC(reg) >> FHC_CONFIG_REV_SHIFT) & FHC_CONFIG_REV_MASK ;

	DEBUGF(1, ("cg6_attach reg=0x%x/0x%x (0x%x)\n",
		(u_int) reg,
		fbgetpage((addr_t) reg) << PGSHIFT,
		fbgetpage((addr_t) reg)));

#if NOHWINIT
	if (cg6_hwinit)
		cg6_init(reg);
#endif NOHWINIT
	cg6_reset(reg);

	/* save unit number */
	devi->devi_unit = unit;

	/* Notify VM system our interrupt handler calls the hat layer. */
	adddma(devi->devi_intr[0].int_pri);

	/* attach interrupt */
	addintr(devi->devi_intr[0].int_pri, cg6_poll, devi->devi_name, unit);

	/* save back pointer to softc */
	devi->devi_data = (addr_t) softc;

	/*
	 * Initialize hardware colormap and software colormap images.
	 * It might make sense to read the hardware colormap here.
	 */
	cg6_reset_cmap(softc->cmap_rgb, CG6_CMAP_ENTRIES);
	cg6_reset_cmap(softc->omap_rgb, 2);
	cg6_update_cmap(softc, (u_int) _ZERO_, CG6_CMAP_ENTRIES);
	cg6_update_cmap(softc, (u_int) _ZERO_, (u_int) _ZERO_);

	report_dev(devi);

	if ((* CG6VA_TO_FHC(reg) >> FHC_CONFIG_REV_SHIFT &
		FHC_CONFIG_REV_MASK) == 0)
		printf("Revision 0 FBC\n");

	printf("cgsix%d: screen %dx%d, %s buffered, %dM mappable, rev %d\n",
		unit, w,h, softc->cg6info.hdb_capable ? "double" : "single",
		softc->cg6info.vmsize, softc->chiprev ) ;

	/* prepare for next attach */
	unit++;

	return 0;
}

STATIC
cg6_open(dev, flag)
	dev_t dev;
	int flag;
{
	DEBUGF(2, ("cg6_open(%d), mem used=%d\n", minor(dev), total_memory));

	return fbopen(dev, flag, ncg6);
}

/*ARGSUSED*/
STATIC
cg6_close(dev, flag)
	dev_t dev;
	int flag;
{
	struct cg6_softc *softc = getsoftc(minor(dev));

	DEBUGF(2, ("cg6_close(%d), mem used=%d\n", minor(dev), total_memory));

	softc->cur.enable = 0;
	softc->curcntxt = NULL ;
	cg6_reset(softc->base);

	return 0;
}

/*ARGSUSED*/
STATIC
cg6_mmap(dev, off, prot)
	dev_t dev;
	register off_t off;
	int prot;
{
	register struct cg6_softc *softc = getsoftc(minor(dev));
	register int page = -1;

	DEBUGF(off ? 4 : 1, ("cg6_mmap(%d, 0x%x)\n", minor(dev), (u_int) off));

	if ((u_int) off < CG6_VBASE) {
		if (off >= CG3_MMAP_OFFSET)
			off += CG6_VADDR_COLOR - CG3_MMAP_OFFSET;
		else if ((off -= softc->dummysize) < 0)
			off = CG6_VADDR_ROM;
		else
			off += CG6_VADDR_COLOR;
	}

	if ((u_int) (off - CG6_VADDR_COLOR) < softc->fbmappable)
		page = CG6_ADDR_COLOR + (off - CG6_VADDR_COLOR);
	else {
		register struct mapping *mp;

		for (mp = cg6_map; mp->size; mp++)
			if ((u_int) (off - mp->vaddr) < mp->size)
				page = mp->paddr + (off - mp->vaddr);
	}

	if (page >= 0)
		page = softc->basepage + btop(page);

	DEBUGF(4, ("cg6_mmap returning 0x%x\n", page));

	return page;
}

/*ARGSUSED*/
STATIC
cg6_ioctl(dev, cmd, data, flag)
	dev_t dev;
	int cmd;
	caddr_t data;
	int flag;
{
	register struct cg6_softc *softc = getsoftc(minor(dev));
	int cursor_cmap;
	int (*copyfun)();
	static union {
		u_char cmap[CG6_CMAP_ENTRIES];
		u_long cur[32];
	} iobuf;

	DEBUGF(3, ("cg6_ioctl(%d, 0x%x)\n", minor(dev), cmd));

	/* default to updating normal colormap */
	cursor_cmap = 0;

	/* figure out if kernel or user mode call */
	copyfun = fbio_kcopy ? kcopy : copyin;

	switch (cmd) {

	case FBIOPUTCMAP:
	case FBIOGETCMAP: {
		struct fbcmap *cmap;
		u_int index;
		u_int count;
		u_char *map;
		u_int entries;

cmap_ioctl:
		cmap = (struct fbcmap *) data;
		index = (u_int) cmap->index;
		count = (u_int) cmap->count;

		if (cursor_cmap == 0)
			switch (PIX_ATTRGROUP(index)) {
			case 0:
			case PIXPG_8BIT_COLOR:
				map = softc->cmap_rgb;
				entries = CG6_CMAP_ENTRIES;
				break;
			default:
				return EINVAL;
			}
		else {
			map = softc->omap_rgb;
			entries = 2;
		}

		if ((index &= PIX_ALL_PLANES) >= entries ||
			index + count > entries)
			return EINVAL;

		if (count == 0)
			return 0;

		if (cmd == FBIOPUTCMAP) {
			register int error;

			DEBUGF(3, ("FBIOPUTCMAP\n"));
			if (cg6_update_pending(softc))
				cg6_int_disable(softc);
#ifdef  MULTIPROCESSOR
            /*
             * blech, foo.  Need to keep user processes from touching the
             * colormap registers while we're touching them or *waiting* to do
             * so.  Do this by invalidating all user mappings here.  Only blow
             * away the cmap page from the mapping.
             */
            {  
              register struct segcg6_data *ptr ;
              softc->cmap_sleeping = 1;
                DUMP_SEGS(5, softc->curcntxt);
              for(ptr=softc->mappings; ptr != NULL; ptr = ptr->next_map)
                if( ptr->flag & SEG_CMAP ) {
                  DEBUGF(5, ("removing mapping for seg %x at %x\n",
                        ptr->seg,
                        ptr->seg->s_base + CG6_VADDR_CMAP - ptr->offset));
                  hat_unload(ptr->seg,
                        ptr->seg->s_base + CG6_VADDR_CMAP - ptr->offset,
                        CG6_CMAP_SZ) ;
                }
            }
#endif  MULTIPROCESSOR

			map += index * 3;

			/* sorry */
			do {
				if (error = COPY(copyfun, cmap->red,
					iobuf.cmap, count))
					break;
				cg6_cmap_bcopy(iobuf.cmap, map++, count);

				if (error = COPY(copyfun, cmap->green,
 					iobuf.cmap, count))
					break;
				cg6_cmap_bcopy(iobuf.cmap, map++, count);

				if (error = COPY(copyfun, cmap->blue,
 					iobuf.cmap, count))
					break;
				cg6_cmap_bcopy(iobuf.cmap, map, count);
			} while (_ZERO_);

			if (error) {
				if (cg6_update_pending(softc))
					cg6_int_enable(softc);
				return EFAULT;
			}

			/* cursor colormap update */
			if (entries < CG6_CMAP_ENTRIES)
				count = 0;

			cg6_update_cmap(softc, index, count);
			cg6_int_enable(softc);
		}
		else {
			/* FBIOGETCMAP */
			DEBUGF(3, ("FBIOGETCMAP\n"));
			map += index * 3;
			cg6_cmap_bcopy(iobuf.cmap, map++, -count);
			if (COPYOUT(iobuf.cmap, cmap->red, count))
				return EFAULT;

			cg6_cmap_bcopy(iobuf.cmap, map++, -count);
			if (COPYOUT(iobuf.cmap, cmap->green, count))
				return EFAULT;

			cg6_cmap_bcopy(iobuf.cmap, map, -count);
			if (COPYOUT(iobuf.cmap, cmap->blue, count))
				return EFAULT;
		}
	}
	break;

	case FBIOGATTR: {
		register struct fbgattr *attr = (struct fbgattr *) data;

		DEBUGF(3, ("FBIOGATTR\n"));
		*attr = cg6_attr;
		data = (caddr_t) &attr->fbtype;
	}
	/* fall through */

	case FBIOGTYPE: {
		register struct fbtype *fb = (struct fbtype *) data;

		if (cmd == FBIOGTYPE) {
			DEBUGF(3, ("FBIOGTYPE\n"));
			*fb = cg6_attr.fbtype;
			fb->fb_type = FBTYPE_SUN4COLOR;
		}
		fb->fb_width = softc->_w;
		fb->fb_height = softc->_h;
		/* XXX not quite like a cg4 */
		fb->fb_size = softc->size;
	}
	break;

#if NWIN > 0

	case FBIOGPIXRECT:
		((struct fbpixrect *) data)->fbpr_pixrect = &softc->pr;

		DEBUGF(3, ("FBIOGPIXRECT\n"));
		/* initialize pixrect and private data */
		softc->pr.pr_ops = &cg6_pr_ops;
		/* pr_size set in attach */
		softc->pr.pr_depth = 8;
		softc->pr.pr_data = (caddr_t) &softc->prd;

		/* md_linebytes, md_image set in attach */
		/* md_offset already zero */
		softc->prd.mpr.md_primary = minor(dev);
		softc->prd.mpr.md_flags = MP_DISPLAY | MP_PLANEMASK;
		softc->prd.planes = 255;

		/* enable video */
		cg6_set_video(softc, _ONE_);

		break;

#endif NWIN > 0

	case FBIOSVIDEO:
		DEBUGF(3, ("FBIOSVIDEO\n"));
		cg6_set_video(softc, * (int *) data & FBVIDEO_ON);
		break;

	case FBIOGVIDEO:
		DEBUGF(3, ("FBIOGVIDEO\n"));
		* (int *) data =
			cg6_get_video(softc) ? FBVIDEO_ON : FBVIDEO_OFF;
		break;

        case GRABPAGEALLOC:     /* Window Grabber */
                return(segcg6_graballoc(data));
        case GRABATTACH:        /* attach to free memory */
                return(segcg6_grabattach(data));
        case GRABPAGEFREE:      /* Window Grabber */
                return(segcg6_grabfree(data));
        case GRABLOCKINFO:      /* Window Grabber */
                return(segcg6_grabinfo(data));

        /* informational ioctls */

        case    FBIOGXINFO:
                *(struct cg6_info *)data = softc->cg6info;
                return(0);

        case    FBIOMONINFO:
                *(struct mon_info *)data = softc->moninfo;
                return(0);

	/* vertical retrace interrupt */

	case 	FBIOVERTICAL:
		softc->vrtflag |= CG6VRTIOCTL;
		{   /* kludge alert. */
		    int	i;

		    i = splbio();
		    cg6_int_enable(softc);
		    /* look, ma! a critical section! right here! */
		    (void) sleep((caddr_t)softc, PRIBIO);
		    (void) splx(i);
		}   /* kludge complete */

		return(0);

	case	FBIOVRTOFFSET:
		*(int *)data = VRT_OFFSET;
		return(0);

	/* HW cursor control */
	case FBIOSCURSOR: {
		struct fbcursor *cp = (struct fbcursor *) data;
		int set = cp->set;
		int cbytes;

		if (set & FB_CUR_SETCUR)
			softc->cur.enable = cp->enable;

		if (set & FB_CUR_SETPOS)
			softc->cur.pos = cp->pos;

		if (set & FB_CUR_SETHOT)
			softc->cur.hot = cp->hot;

		/* update hardware */
		cg6_setcurpos(softc);

		if (set & FB_CUR_SETSHAPE) {
			if ((u_int) cp->size.x > 32 ||
				(u_int) cp->size.y > 32)
				return EINVAL;
			softc->cur.size = cp->size;

			/* compute cursor bitmap bytes */
			cbytes = softc->cur.size.y *
				sizeof softc->cur.image[0];

			/* copy cursor image into softc */
			if (COPY(copyfun, cp->image, iobuf.cur, cbytes))
				return EFAULT;
			BZERO(softc->cur.image, sizeof softc->cur.image);
			CG6BCOPY(iobuf.cur, softc->cur.image, cbytes);
			if (COPY(copyfun, cp->mask, iobuf.cur, cbytes))
				return EFAULT;
			BZERO(softc->cur.mask, sizeof softc->cur.mask);
			CG6BCOPY(iobuf.cur, softc->cur.mask, cbytes);

			/* load into hardware */
			cg6_setcurshape(softc);
		}

		/* load colormap */
		if (set & FB_CUR_SETCMAP) {
			cursor_cmap = 1;
			cmd = FBIOPUTCMAP;
			data = (caddr_t) &cp->cmap;
			goto cmap_ioctl;
		}

		break;
	}

	case FBIOGCURSOR: {
		struct fbcursor *cp = (struct fbcursor *) data;
		int cbytes;

		cp->set = 0;
		cp->enable = softc->cur.enable;
		cp->pos = softc->cur.pos;
		cp->hot = softc->cur.hot;
		cp->size = softc->cur.size;

		/* compute cursor bitmap bytes */
		cbytes = softc->cur.size.y * sizeof softc->cur.image[0];

		/* if image pointer is non-null copy both bitmaps */
		if (cp->image &&
			(COPYOUT(softc->cur.image, cp->image, cbytes) ||
			COPYOUT(softc->cur.mask, cp->mask, cbytes)))
			return EFAULT;

		/* if red pointer is non-null copy colormap */
		if (cp->cmap.red) {
			cursor_cmap = 1;
			cmd = FBIOGETCMAP;
			data = (caddr_t) &cp->cmap;
			goto cmap_ioctl;
		}
		/* just trying to find out colormap size */
		else {
			cp->cmap.index = 0;
			cp->cmap.count = 2;
		}
		break;
	}

	case FBIOSCURPOS:
		softc->cur.pos = * (struct fbcurpos *) data;
		cg6_setcurpos(softc);
		break;

	case FBIOGCURPOS:
		* (struct fbcurpos *) data = softc->cur.pos;
		break;

	case FBIOGCURMAX: {
		static struct fbcurpos curmax = { 32, 32 };

		* (struct fbcurpos *) data = curmax;
		break;
	}

#if CG6DEBUG
	case _IOW(F, 255, int): {		/* debug level */
	  cg6_debug = *(int *)data ;
	  break ;
	}

	case _IO(F, 254): {		/* dump segments */
	  dump_locks() ;
	  break ;
	}
#endif

	default:
		return ENOTTY;

	}

	return 0;
}

static
cg6_poll()
{
	register int i, serviced = 0;
	register struct cg6_softc *softc;

	/*
	 * Look for any frame buffers that were expecting an interrupt.
	 */

	DEBUGF(7, ("cg6_poll\n"));
	for (softc = cg6_softc, i = ncg6; --i >= 0; softc++)
		if (cg6_int_pending(softc)) {
			if (cg6_update_pending(softc) || softc->vrtflag)
				cg6_intr(softc);
			else
				/* XXX catch stray interrupts? */
				cg6_int_disable(softc);
			serviced++;
		}

	return serviced;
}

static void
cg6_intr(softc)
	register struct cg6_softc *softc;
{
	struct cg6_cmap *cmap = CG6VA_TO_CMAP(softc->base);
	LOOP_T count = softc->cmap_count;
	u_long *in;
	u_long *out;
	u_long tmp;
        /*void	hat_unload();*/

	DEBUGF(7, ("cg6_intr(%d)\n", softc - cg6_softc));

	if (softc->vrtflag & CG6VRTCTR) {
	    if (softc->vrtmaps == 0) {
		softc->vrtflag &= ~CG6VRTCTR;
	    } else
		*softc->vrtpage += 1;
	}

	if (softc->vrtflag & CG6VRTIOCTL) {
	    softc->vrtflag &= ~CG6VRTIOCTL;
	    wakeup((caddr_t)softc);
	}

	if (cg6_update_pending(softc)) {
	    /* load cursor color map */
	    if (softc->omap_update) {
		    in = &softc->omap_image.omap_long[0];
		    out = (u_long *) &cmap->omap;

		    /* background color */
		    cmap->addr = 1 << 24;
		    tmp = in[0];
		    *out = tmp;
		    *out = tmp <<= 8;
		    *out = tmp <<= 8;

		    /* foreground color */
		    cmap->addr = 3 << 24;
		    *out = tmp <<= 8;
		    tmp = in[1];
		    *out = tmp;
		    *out = tmp <<= 8;
	    }

	    /* load main color map */
	    if (count) {
		    LOOP_T index = softc->cmap_index;

		    in = &softc->cmap_image.cmap_long[0];
		    out = (u_long *) &cmap->cmap;

		    /* count multiples of 4 RGB entries */
		    count = (count + (index & 3) + 3) >> 2;

		    /* round index to 4 entry boundary */
		    index &= ~3;

		    cmap->addr = index << 24;
		    PTR_INCR(u_long *, in, index * 3);

		    /* copy 4 bytes (4/3 RGB entries) per loop iteration */
		    count *= 3;
		    PR_LOOPV(count,
			    tmp = *in++;
			    *out = tmp;
			    *out = tmp <<= 8;
			    *out = tmp <<= 8;
			    *out = tmp <<= 8);

		    softc->cmap_count = 0;
	    }

	    softc->omap_update = 0;
	}
#ifdef  MULTIPROCESSOR
        if (softc->cmap_sleeping == 1) {
            wakeup((caddr_t)softc->cmap_sleeping);
            softc->cmap_sleeping = 0;
            }
#endif  /* MULTIPROCESSOR */

	cg6_int_disable(softc);
	if (softc->vrtflag)
	    cg6_int_enable(softc);
}

/*
 * Initialize a colormap: background = white, all others = black
 */
static void
cg6_reset_cmap(cmap, entries)
	register u_char *cmap;
	register u_int entries;
{
	bzero((char *) cmap, entries * 3);
	cmap[0] = 255;
	cmap[1] = 255;
	cmap[2] = 255;
}

/*
 * Compute color map update parameters: starting index and count.
 * If count is already nonzero, adjust values as necessary.
 * Zero count argument indicates cursor color map update desired.
 */
static void
cg6_update_cmap(softc, index, count)
	struct cg6_softc *softc;
	u_int index, count;
{
	u_int high, low;

	if (count == 0) {
		softc->omap_update = 1;
		return;
	}

	if (high = softc->cmap_count) {
		high += (low = softc->cmap_index);

		if (index < low)
			softc->cmap_index = low = index;

		if (index + count > high)
			high = index + count;

		softc->cmap_count = high - low;
	}
	else {
		softc->cmap_index = index;
		softc->cmap_count = count;
	}
}

/*
 * Copy colormap entries between red, green, or blue array and
 * interspersed rgb array.
 *
 * count > 0 : copy count bytes from buf to rgb
 * count < 0 : copy -count bytes from rgb to buf
 */
static void
cg6_cmap_bcopy(bufp, rgb, count)
	register u_char *bufp, *rgb;
	u_int count;
{
	register LOOP_T rcount = count;

	if (--rcount >= 0)
		PR_LOOPVP(rcount,
			*rgb = *bufp++;
			rgb += 3);
	else {
		rcount = -rcount - 2;
		PR_LOOPVP(rcount,
			*bufp++ = *rgb;
			rgb += 3);
	}
}

/*
 * enable/disable/update HW cursor
 */
static
cg6_setcurpos(softc)
	struct cg6_softc *softc;
{
	CG6VA_TO_THC(softc->base)->l_thc_cursor =
		softc->cur.enable ?
		((softc->cur.pos.x - softc->cur.hot.x) << 16) |
			((softc->cur.pos.y - softc->cur.hot.y) & 0xffff) :
		CG6_CURSOR_OFFPOS;
}

/*
 * load HW cursor bitmaps
 */
static
cg6_setcurshape(softc)
	struct cg6_softc *softc;
{
	u_long tmp, edge = 0;
	u_long *image, *mask, *hw;
	int i;

	/* compute right edge mask */
	if (softc->cur.size.x)
		edge = (u_long) ~0 << (32 - softc->cur.size.x);

	image = softc->cur.image;
	mask = softc->cur.mask;
	hw = (u_long *) &CG6VA_TO_THC(softc->base)->l_thc_cursora00;

	for (i = 0; i < 32; i++) {
		hw[i] = (tmp = mask[i] & edge);
		hw[i + 32] = tmp & image[i];
	}
}

static
cg6_reset(addr)
	addr_t addr;
{
	/* disable HW cursor */
	CG6VA_TO_THC(addr)->l_thc_cursor = CG6_CURSOR_OFFPOS;

	/* reinitialize TEC */
	{
		struct tec *tec = CG6VA_TO_TEC(addr);

		tec->l_tec_mv = 0;
		tec->l_tec_clip = 0;
		tec->l_tec_vdc = 0;
	}

	/* reinitialize FBC config register */
	{
		u_int *fhc = CG6VA_TO_FHC(addr), rev, conf;

		rev = *fhc >> FHC_CONFIG_REV_SHIFT & FHC_CONFIG_REV_MASK;
		if (rev <= 4) {

		    /* PROM knows how to deal with LSC and above */
		    /* rev == 0 : FBC 0 (not available to customers) */
		    /* rev == 1 : FBC 1 */
		    /* rev == 2 : FBC 2 */
		    /* rev == 3 : Toshiba (never built) */
		    /* rev == 4 : Standard Cell (not built yet) */
		    /* rev == 5 : LSC */
		    conf = *fhc & FHC_CONFIG_RES_MASK |
			    FHC_CONFIG_CPU_68020;
#if FBC_REV0
		    /* FBC0: test window = 0, disable fast rops */
		    if (rev == 0)
			    conf |= FHC_CONFIG_TEST |
				    FHC_CONFIG_FROP_DISABLE;
		    else
#endif FBC_REV0
		    /* test window = 1K x 1K */
		    conf |= FHC_CONFIG_TEST |
			    (10 + 1) << FHC_CONFIG_TESTX_SHIFT |
			    (10 + 1) << FHC_CONFIG_TESTY_SHIFT;

		    /* FBC[01]: disable destination cache */
		    if (rev <= 1)
			    conf |= FHC_CONFIG_DST_DISABLE;

		    *fhc = conf;
		}
	}

	/* reprogram DAC to enable HW cursor use */
	{
		struct cg6_cmap *cmap = CG6VA_TO_CMAP(addr);

		/* command register */
		cmap->addr = 6 << 24;

		/* turn on CR1:0, overlay enable */
		cmap->ctrl = cmap->ctrl | (0x3 << 24);
	}
}

#if NOHWINIT
cg6_init(addr)
	addr_t addr;
{
	/* Initialize DAC */
	{
		register struct cg6_cmap *cmap = CG6VA_TO_CMAP(addr);
		register char *p;

		static char dacval[] = {
			4, 0xff,
			5, 0,
			6, 0x73,
			7, 0,
			0
		};

		/* initialize DAC */
		for (p = dacval; *p; p += 2) {
			cmap->addr = p[0] << 24;
			cmap->ctrl = p[1] << 24;
		}
	}

	/* Initialize THC */
	{
		register struct thc *thc = CG6VA_TO_THC(addr);
		int vidon;

		vidon = thc_get_video(thc);
		thc->l_thc_hcmisc = THC_HCMISC_RESET | THC_HCMISC_INIT;
		thc->l_thc_hcmisc = THC_HCMISC_INIT;

		thc->l_thc_hchs = 0x010009;
		thc->l_thc_hchsdvs = 0x570000;
		thc->l_thc_hchd = 0x15005d;
		thc->l_thc_hcvs = 0x010005;
		thc->l_thc_hcvd = 0x2403a8;
		thc->l_thc_hcr = 0x00016b;

		thc->l_thc_hcmisc = THC_HCMISC_RESET | THC_HCMISC_INIT;
		thc->l_thc_hcmisc = THC_HCMISC_INIT;

		if (vidon)
			thc_set_video(thc, _ONE_);

		DEBUGF(1, ("TEC rev %d\n",
			thc->l_thc_hcmisc >> THC_HCMISC_REV_SHIFT &
			THC_HCMISC_REV_MASK));
	}

	/*
	 * Initialize FHC for 1152 X 900 screen
	 */
	{
		register u_int *fhc = CG6VA_TO_FHC(addr), rev;

		rev = *fhc >> FHC_CONFIG_REV_SHIFT & FHC_CONFIG_REV_MASK;
		DEBUGF(1, ("FBC rev %d\n", rev));

		/*
		 * FBC0: disable fast rops
		 * FBC[01]: disable destination cache
		 */
		*fhc = FHC_CONFIG_1152 |
			FHC_CONFIG_CPU_68020 |
			FHC_CONFIG_TEST |
#if FBC_REV0
			rev == 0 ? FHC_CONFIG_FROP_DISABLE : 0 |
#endif FBC_REV0
			rev <= 1 ? FHC_CONFIG_DST_DISABLE : 0;
	}
}
#endif NOHWINIT


/* external routines */
extern	caddr_t getpages();

/*
 * from here on down, is the lego segment driver.  this virtualizes the
 * lego register file by associating a register save area with each
 * mapping of the lego device (each lego segment).  only one of these
 * mappings is valid at any time; a page fault on one of the invalid
 * mappings saves off the current lego context, invalidates the current
 * valid mapping, restores the former register contents appropriate to
 * the faulting mapping, and then validates it.
 *
 * this implements a graphical context switch that is transparent to the user.
 *
 * the TEC and FBC contain the interesting context registers.
 */

/*
 * Per-context info:
 *	many registers in the tec and fbc do
 *	not need to be saved/restored.
 */
struct cg6_cntxt
{
	struct cg6_cntxt	*link ;		/* must be first */
	struct seg		*seg_list ;	/* segments in this context */
	int			pid ;
	int			flag ;
	struct {
	        u_int	mv;
        	u_int	clip;
        	u_int	vdc;
		u_int	data[64][2];
	} tec;
	struct {
		u_int	status;
		u_int	clipcheck;
		struct	l_fbc_misc	misc;
		u_int	x0, y0, x1, y1, x2, y2, x3, y3;
		u_int	rasteroffx, rasteroffy;
		u_int	autoincx, autoincy;
		u_int	clipminx, clipminy, clipmaxx, clipmaxy;
		u_int	fcolor, bcolor;
		struct	l_fbc_rasterop	rasterop;
		u_int	planemask, pixelmask;
		union	l_fbc_pattalign	 pattalign;
		u_int	pattern0, pattern1, pattern2, pattern3,
			pattern4, pattern5, pattern6, pattern7;
	} fbc;
};

static	struct cg6_cntxt       shared_context = {NULL,NULL,0,} ;






/*
 *      Window Grabber lock management
 */

#define	LOCK_PAGESIZE	(8*1024)
#define LOCK_OFFBASE    VRT_OFFSET+PAGESIZE

#define NLOCKS  	16
#define	LOCKTIME	3	/* seconds */

/* flag bits */
#define LOCKMAP         0x01
#define UNLOCKMAP       0x02
#define ATTACH          0x04
#define TRASHPAGE       0x08
#define UNGRAB          0x10    /* ungrab called */

struct  segproc {
        int             flag;
        struct  proc    *procp;
        caddr_t         lockaddr;
        struct  seg     *locksegp;
        caddr_t         unlockaddr;
        struct  seg     *unlocksegp;
};

struct  seglock {
        short           sleepf;         /* sleep flag */
	short		allocf;		/* allocated flag */
        off_t           offset;         /* offset into device */
        int    		*page;          /* virtual address of lock page */
        struct  segproc cr;             /* creator */
        struct  segproc cl;             /* client */
        struct  segproc *owner;         /* current owner of lock */
        struct  segproc *other;         /* not owner of lock */
};

static	caddr_t trashpage=NULL;		/* for trashing unwanted writes */

static  struct  seglock lock_list[NLOCKS];
struct  seglock *segcg6_findlock();


/*
 * This routine is called through the cdevsw[] table to handle
 * the creation of lego (cg6) segments.
 */
/*ARGSUSED*/
int
cgsixsegmap(dev, offset, as, addr, len, prot, maxprot, flags, cred)
	dev_t dev;
	register u_int offset;
	struct as *as;
	addr_t *addr;
	register u_int len;
	u_int prot, maxprot;
	u_int flags;
	struct ucred *cred;
{
	register struct cg6_softc *softc = getsoftc(minor(dev));
	struct segcg6_data dev_a;
	int	segcg6_create();
	register	i;
	caddr_t		seglock = 0 ;
	int		segvrt = 0;

	enum	as_res	as_unmap();
	int	as_map();

	DEBUGF(3, ("segmap: d=%x, of=%x, adr=%x, l=%x, p=%x, maxp=%x, f=%x\n",
		dev, offset, addr, len, prot, maxprot, flags));

	/*
	 * we now support MAP_SHARED and MAP_PRIVATE:
	 *
	 *	MAP_SHARED means you get the shared context
	 *	which is the traditional mapping method.
	 *
	 *	MAP_PRIVATE means you get your very own
	 *	LEGO context.
	 *
	 *	Note that you can't get to here without
	 *	asking for one or the other, but not both.
	 */

	dev_a.flag = 0 ;
	dev_a.link = NULL;
	dev_a.cntxtp = NULL;
	dev_a.prot = prot ;
	dev_a.maxprot = maxprot ;

	/*
	 * check to see if this is a VRT page
	 */

	if (offset == VRT_OFFSET) {

	    if (len != PAGESIZE)
		return(EINVAL);

	    segvrt++;
	    if (softc->vrtmaps++ == 0) {
		if (softc->vrtpage == NULL)
		    softc->vrtpage = (int *)getpages(1, KMEM_SLEEP);
		*softc->vrtpage = 0;
		softc->vrtflag |= CG6VRTCTR;
		cg6_int_enable(softc);
	    }

	} else

	/*
         * Check to see if this is an allocated lock page.
         */

        if ((seglock = (caddr_t)segcg6_findlock((off_t)offset)) !=
	    (caddr_t) NULL) {
	      if (len != LOCK_PAGESIZE)
		      return(EINVAL);
	} else {
                /*
                 * Check to insure that the entire range is
                 * legal and we are not trying to map in
                 * more than the device will let us.
                 */
                for (i = 0; i < len; i += PAGESIZE)
		    if (cgsixmmap(dev, (off_t)offset + i, (int)maxprot) == -1)
			return (ENXIO);

		/* classify it */
		if( offset+len > CG6_VADDR_FBC  &&
		    offset < CG6_VADDR_FBC+CG6_TEC_POFF )
		  dev_a.flag |= SEG_FBC ;
		if( offset+len > CG6_VADDR_TEC  &&
		    offset < CG6_VADDR_FBC+CG6_FBCTEC_SZ )
		  dev_a.flag |= SEG_TEC ;
		if( offset+len > CG6_VADDR_COLOR  &&
		    offset < CG6_VADDR_COLOR+softc->fbmappable )
		  dev_a.flag |= SEG_FB ;
#ifdef	MULTIPROCESSOR
		if( offset+len > CG6_VADDR_CMAP  &&
		    offset < CG6_VADDR_CMAP+CG6_CMAP_SZ )
		  dev_a.flag |= SEG_CMAP ;
#endif	MULTIPROCESSOR
DEBUGF(3,("segment flags=%x\n", dev_a.flag));
        }

	/* LSC DFB BUG KLUDGE:  DFB must always be mapped private
	 * on the buggy (chip rev. 5) LSC chip.  This is done to ensure
	 * that nobody ever touches the framebuffer without the segment
	 * driver getting involved to make sure the chips are idle.  This
	 * involves taking a page fault, invalidating all other process's
	 * mappings to the fb, (and performing a context switch?)
	 *
	 * Under pixrects, which maps the chips and the FB all at once,
	 * the entire mapping becomes a context.  This won't hurt
	 * pixrects but entails unnecessary context switching.  Under
	 * other libraries such as XGL, which maps the chips private and
	 * the FB shared, the FB becomes part of the context.  Programs
	 * which only map the FB will also become contexts which also
	 * entails unnecessary context switching.  Such is life.
	 */
#ifdef	COMMENT
	if( softc->chiprev == 5 && (dev_a.flag & SEG_FB) )
#endif	/* COMMENT */
	  flags = (flags & ~MAP_TYPE) | MAP_PRIVATE ;

	/* decide if this segment is part of a context. */

	if( dev_a.flag & (softc->chiprev == 5 ? (SEG_FBC|SEG_TEC|SEG_FB) :
						(SEG_FBC|SEG_TEC)) ) {
	  dev_a.flag |= SEG_CTXINIT ;
	}

	if ((flags & MAP_FIXED) == 0) {
		/*
		 * Pick an address w/o worrying about
		 * any vac alignment contraints.
		 */
		map_addr(addr, len, (off_t)0, 0);
		if (*addr == NULL)
			return (ENOMEM);
	} else {
		/*
		 * User specified address -
		 * Blow away any previous mappings.
		 */
		 i = (int)as_unmap((struct as *)as, *addr, len);
	}

	/*
	 * record device number; mapping function doesn't do anything smart
	 * with it, but it takes it as an argument.  the offset is needed
	 * for mapping objects beginning some ways into them.
	 */

	dev_a.dev = dev;
	dev_a.offset = offset;

	/*
	 * determine whether this is a shared mapping, or a private
  	 * one. if shared, link it onto the shared list, if private,
	 * create a private LEGO context.
	 */
	if (segvrt) {
                dev_a.flag = SEG_VRT;
        } else
	if (seglock) {
                dev_a.flag = SEG_LOCK;
        }
#ifdef	COMMENT
	else if ((flags & MAP_TYPE) == MAP_SHARED) { /* shared mapping */
		dev_a.flag |= SEG_SHARED;
	}
#endif	/* COMMENT */

    return(as_map((struct as *)as, *addr, len, segcg6_create, (caddr_t)&dev_a));
}

static	int segcg6_dup(/* seg, newsegp */);
static	int segcg6_unmap(/* seg, addr, len */);
static	int segcg6_free(/* seg */);
static	faultcode_t segcg6_fault(/* seg, addr, len, type, rw */);
static	int segcg6_checkprot(/* seg, addr, size, prot */);
static	int segcg6_badop();
static	int segcg6_incore();

static	struct seg_ops	segcg6_ops =  {
	segcg6_dup,	/* dup */
	segcg6_unmap,	/* unmap */
	segcg6_free,	/* free */
	segcg6_fault,	/* fault */
	segcg6_badop,	/* faulta */
	segcg6_badop,	/* unload */
	segcg6_badop,	/* setprot */
	segcg6_checkprot,/* checkprot */
	segcg6_badop,	/* kluster */
	(u_int (*)()) NULL,	/* swapout */
	segcg6_badop,	/* sync */
	segcg6_incore	/* incore */
};

/*
 * Window Grabber Page Management
 */

/*
 * search the lock_list list for the specified offset
 *
 */


static
init_locks()
{
	int	i ;
	struct seglock *lp = lock_list ;
	for(i=0; i<NLOCKS;i++,lp++)
	  lp->page = NULL ;
}

/*ARGSUSED*/
static
struct seglock *
segcg6_findlock(off)
off_t   off;
{
        register int i;

	if ((unsigned long)off < LOCK_OFFBASE)
                return((struct seglock *)NULL);
        for(i=0;i<NLOCKS;i++)
            if  (off == lock_list[i].offset)
                return(lock_list+i);
        return((struct seglock *)NULL);
}

/*ARGSUSED*/
int
segcg6_grabattach(cookiep)      /* IOCTL */
caddr_t cookiep;
{

    return(EINVAL);
}

/*ARGSUSED*/
static int
segcg6_grabinfo(pp)    /* IOCTL */
caddr_t pp;
{
        *(int *)pp = 0;
        return (0);
}

/*ARGSUSED*/
int
segcg6_graballoc(pp)    /* IOCTL */
caddr_t pp;
{
        register        int i;
	register	struct seglock	*lp = lock_list;

	if (trashpage == NULL) {
		trashpage = getpages(1,KMEM_SLEEP); /* for trashing unwanted writes */
		for(i=0;i<NLOCKS;i++) 	/* initialize offsets */
		    lock_list[i].offset = (off_t)(LOCK_OFFBASE+LOCK_PAGESIZE*i);
	}

        for(i=0;i<NLOCKS;i++,lp++)
            if (lp->allocf == 0) {
		lp->allocf = 1;
		if (lp->page == (int *)NULL)
		    lp->page = (int *)getpages(1,KMEM_SLEEP);
                lp->cr.flag = 0;
                lp->cl.flag = 0;
                lp->cr.procp = u.u_procp;
                lp->cl.procp = NULL;
                lp->cr.locksegp = NULL;
                lp->cl.locksegp = NULL;
                lp->cr.unlocksegp = NULL;
                lp->cl.unlocksegp = NULL;
                lp->owner = NULL;
                lp->other = NULL;
                lp->sleepf = 0;
                *lp->page = 0;
                *(int *)pp = (int)lp->offset;
                return(0);
            }
        return(ENOMEM);
}

/*ARGSUSED*/
int
segcg6_grabfree(pp)     /* IOCTL */
caddr_t pp;
{
    register    struct seglock  *lp;
    void	segcg6_timeout();

    if ((lp = segcg6_findlock((off_t)*(int *)pp)) == NULL)
        return(EINVAL);
    lp->cr.flag |= UNGRAB;
    lp->allocf = 0;
    if (lp->sleepf) {   /* client will acquire the lock in this case */
	untimeout((ifunc)segcg6_timeout,(caddr_t)lp);
        wakeup((caddr_t)lp->page);
    }
    return(0);
}


#if	CG6DEBUG
static
dump_segs(cur)
	struct cg6_cntxt *cur ;
{
	register struct seg *segp ;
	register struct segcg6_data *ptr ;
	register struct cg6_cntxt *ctx = &shared_context ;

	for(; ctx; ctx=ctx->link)
	{
	  printf("  ctx %x%s%s: link=%x, seg_list=%x, pid=%d, flag=%x\n",
	  ctx, ctx==&shared_context ? " (shared)" : "",
	  ctx==cur ? "(!)" : "", ctx->link, ctx->seg_list,
	  ctx->pid, ctx->flag) ;
	  for(segp = ctx->seg_list; segp; segp=ptr->link)
	  {
	    ptr = (struct segcg6_data *)segp->s_data ;
	    printf("   seg %x: sd=%x, link=%x, flag=%x, cntxtp=%x\n",
	      segp, ptr, ptr->link, ptr->flag, ptr->cntxtp) ;
	    printf("     base=%x, size=%x, ofs=%x\n",
	      segp->s_base, segp->s_size, ptr->offset) ;
	  }
	}
}

static
dump_locks()
{
	int	i ;
	struct seglock *lp = lock_list ;
	printf("n offset   lock sleep  cr  cl owner own-f  oth-f\n") ;
	for(i=0; i<NLOCKS;i++,lp++)
	{
	  if( lp->allocf && lp->page != NULL ) {
	    printf("%d %x    %d     %d %d %d %d   %x     %x\n",
	      i, lp->offset, *lp->page, lp->sleepf,
	      PPID(lp->cr.procp), PPID(lp->cl.procp), LPID(lp),
	      lp->owner!=NULL?lp->owner->flag:0,
	      lp->other!=NULL?lp->other->flag:0) ;
#ifdef	COMMENT
	    printf("%d: off=%x, lock=%x, sleepf=%x, allocf=%x, cr=%d, cl=%d, owner=%d\n",
	      i, lp->offset, *lp->page, lp->sleepf, lp->allocf,
	      PPID(lp->cr.procp), PPID(lp->cl.procp), LPID(lp));
	    printf("    owner-flags=%x, other-flags=%x\n",
	      lp->owner!=NULL?lp->owner->flag:0,
	      lp->other!=NULL?lp->other->flag:0) ;
#endif	/* COMMENT */
	  }
	}
}
#endif	CG6DEBUG

/*
 * Insert a segment into the segment list of a context:
 *
 * 1) find out which context this belongs in:
 *    search all contexts for a context that matches the current PID
 *    and for which no segments of this type (fbc/tec/fb) have yet
 *    been assigned.  If no such context is found, create one.
 * 2) insert into list
 */
static
ctx_seg_insert(seg)
	struct seg *seg;
{
	register struct segcg6_data *dp = (struct segcg6_data *) seg->s_data ;
	register struct cg6_cntxt *ctx ;

	if( dp->flag & SEG_SHARED )
	  ctx = &shared_context ;
	else
	{
	  for(ctx = shared_context.link; ctx != NULL; ctx=ctx->link)
	    if( ctx->pid == u.u_procp->p_pid  &&
		(ctx->flag & dp->flag & (SEG_FBC|SEG_TEC|SEG_FB)) == 0 )
	      break ;		/* use this context */

	  if( ctx == NULL )	/* no match, create a new one */
	  {
	    ctx = (struct cg6_cntxt *)
		new_kmem_zalloc( sizeof (struct cg6_cntxt),KMEM_SLEEP) ;
	    if( ctx == NULL )
 		panic("cgsix: out of memory in ctx_seg_insert");
	    /* TODO: MT-Safe */
	    ctx->link = shared_context.link ;
	    shared_context.link = ctx ;
	    /* END: MT-Safe */
	    ctx->pid = u.u_procp->p_pid ;
	    ctx->flag = 0 ;
	  }
	}
	dp->cntxtp = ctx ;
	DEBUGF(3, ("ctx_seg_insert: seg=%x, dp=%x, ctx=%x\n", seg,dp,ctx));


	/* now that we have the context, link this segment into it */

	/* TODO: MT-Safe */
	dp->link = ctx->seg_list ;
	ctx->seg_list = seg ;
	ctx->flag |= dp->flag ;
	/* END: MT-Safe */
}

/*
 * Create a device segment.  the lego context is initialized by the create args.
 */
static
int
segcg6_create(seg, argsp)
	struct seg *seg;
	caddr_t argsp;
{
	register struct segcg6_data *dp;

	dp = (struct segcg6_data *)
		new_kmem_zalloc(sizeof (struct segcg6_data),KMEM_SLEEP);
	*dp = *((struct segcg6_data *)argsp);

	dp->seg = seg ;
	seg->s_ops = &segcg6_ops;
	seg->s_data = (char *)dp;

	/* if this segment is part of a context, link it into that context */
	if( dp->flag & SEG_CTXINIT )
	  ctx_seg_insert(seg) ;

#ifdef	MULTIPROCESSOR
	if( dp->flag & SEG_GLOBAL_MAP ) {
	  register struct cg6_softc *softc = getsoftc(minor(dp->dev));
DEBUGF(3,("adding dp %x to softc list %x\n", dp, softc->mappings));
	  dp->next_map = softc->mappings ;
	  softc->mappings = dp ;
	}
#endif	MULTIPROCESSOR

	return (0);
}

/*
 * Duplicate seg and return new segment in newsegp. copy original lego context.
 */
static
int
segcg6_dup(seg, newseg)
	struct seg *seg, *newseg;
{
	register struct segcg6_data *sdp = (struct segcg6_data *)seg->s_data;
	struct segcg6_data dev_a;

	dev_a = *sdp ;
	dev_a.link = NULL ;
	dev_a.cntxtp = NULL ;

	return segcg6_create(newseg, (caddr_t) &dev_a);
}

static
int
segcg6_unmap(seg, addr, len)
	register struct seg *seg;
	register addr_t addr;
	u_int len;
{
        register struct segcg6_data *sdp = (struct segcg6_data *)seg->s_data;
        register struct segcg6_data *ndp ;
        register struct seg *nseg;
        addr_t nbase;
        u_int nsize;
	/*void	hat_unload();*/
	void	hat_newseg();
        static  segcg6_lockfree();

        segcg6_lockfree(seg);

	DEBUGF(3, ("segcg6_unmap: seg=%x, addr=%x, len=%x\n", seg, addr, len));
	DEBUGF(3, ("  seg %x: base=%x, size=%x\n",
		seg, seg->s_base, seg->s_size));
	DEBUGF(3, ("  sdp %x: link=%x, flag=%x, cntxtp=%x, dev=%x, offset=%x\n",
	  sdp, sdp->link, sdp->flag, sdp->cntxtp, sdp->dev,
	  sdp->offset)) ;

	/*
 	 * Check for bad sizes
 	 */
 	if (addr < seg->s_base || addr + len > seg->s_base + seg->s_size ||
 	    (len & PAGEOFFSET) || ((u_int)addr & PAGEOFFSET))
 		panic("segcg6_unmap");

        /*
         * Unload any hardware translations in the range to be taken out.
         */

	hat_unload(seg,addr,len);

        /*
         * Check for entire segment
         */

 	if (addr == seg->s_base && len == seg->s_size) {
		DEBUGF(3, ("  entire segment\n"));
 		seg_free(seg);
 		return (0);
	}

 	/*
 	 * Check for beginning of segment
 	 */

 	if (addr == seg->s_base) {
		DEBUGF(3, ("  beginning of segment\n"));
 		sdp->offset += len;
 		seg->s_base += len;
 		seg->s_size -= len;
 		return (0);
 	}

 	/*
 	 * Check for end of segment
 	 */
 	if (addr + len == seg->s_base + seg->s_size) {
		DEBUGF(3, ("  end of segment\n"));
 		seg->s_size -= len;
 		return (0);
 	}

 	/*
 	 * The section to go is in the middle of the segment,
 	 * have to make it into two segments.  nseg is made for
 	 * the high end while seg is cut down at the low end.
 	 */
	DEBUGF(3, ("  middle of segment\n"));
 	nbase = addr + len;				/* new seg base */
 	nsize = (seg->s_base + seg->s_size) - nbase;	/* new seg size */
 	seg->s_size = addr - seg->s_base;		/* shrink old seg */
 	nseg = seg_alloc(seg->s_as, nbase, nsize);
 	if (nseg == NULL)
 		panic("segcg6_unmap seg_alloc");

 	nseg->s_ops = seg->s_ops;

 	/* figure out what to do about the new context */

 	ndp = (struct segcg6_data *)
		new_kmem_alloc(sizeof (struct segcg6_data),KMEM_SLEEP);
 	nseg->s_data = (caddr_t)ndp ;

	*ndp = *sdp ;
	ndp->link = NULL ;
	ndp->dev = sdp->dev ;
	ndp->offset += addr - seg->s_base ;

	if( ndp->flag & SEG_CTXINIT )
	{
	  /* this is part of the context, link it in after seg */
	  /* TODO: MT-Safe */
	  ndp->link = sdp->link ;
	  sdp->link = nseg ;
	  /* END: MT-Safe */
	}

 	/*
 	 * Now we do something so that all the translations which used
 	 * to be associated with seg but are now associated with nseg.
 	 */
 	hat_newseg(seg, nseg->s_base, nseg->s_size, nseg);
	return(0);
}

/*
 * free a lego segment and clean up its context.
 */
static
int
segcg6_free(seg)
	struct seg *seg;
{
        register struct segcg6_data *sdp = (struct segcg6_data *)seg->s_data;
        register struct cg6_softc *softc = getsoftc(minor(sdp->dev));

	DEBUGF(3, ("segcg6_free: seg %x: base=%x, size=%x\n",
	      seg, seg->s_base, seg->s_size));

	if (sdp->flag & SEG_VRT) {
	    if (--softc->vrtmaps == 0)
		softc->vrtflag &= ~CG6VRTCTR;
	}
        if (sdp->flag & SEG_LOCK)
            segcg6_lockfree(seg);

	/* if this segment was part of a context, remove it from the
	 * context's segment list.  If the segment list becomes empty
	 * and the context is not the shared context, remove that
	 * context from the context list.
	 */
	if( sdp->flag & SEG_CTXINIT )
	{
	  register struct segcg6_data *ptr ;
	  for( ptr = (struct segcg6_data *) &sdp->cntxtp->seg_list ;
	       ptr != NULL ;
	       ptr = (struct segcg6_data *) ptr->link->s_data )
	    if( ptr->link == seg ) {
	      ptr->link = sdp->link ;
	      break ;
	    }
	  /* did that empty the context? */
	  if( sdp->cntxtp != &shared_context && sdp->cntxtp->seg_list == NULL )
	  {
	    register struct cg6_cntxt *ctx ;

	    if( sdp->cntxtp == softc->curcntxt )
	      softc->curcntxt = NULL ;
	    for( ctx = (struct cg6_cntxt *)&shared_context.link;
		 ctx != NULL ;
		 ctx = ctx->link )
	      if( ctx->link == sdp->cntxtp ) {
		ctx->link = sdp->cntxtp->link ;
		break ;
	      }
	    kmem_free((char *)sdp->cntxtp, sizeof(*sdp->cntxtp)) ;
	  }
	}

#ifdef	MULTIPROCESSOR
	/* remove this segment from the device's global mapping list */
	if( sdp->flag & SEG_GLOBAL_MAP ) {
DEBUGF(3,("removing sdp %x from list %x->%x\n", sdp, softc, softc->mappings));
	  if( softc->mappings == sdp )
	    softc->mappings = sdp->next_map ;
	  else {
	    register struct segcg6_data *ptr ;
	    for( ptr=softc->mappings;
		 ptr != NULL && ptr->next_map != sdp;
		 ptr = ptr->next_map ) ;
	    if( ptr != NULL )
	      ptr->next_map = sdp->next_map ;
	  }
	}
#endif	MULTIPROCESSOR

	kmem_free((char *)sdp, sizeof(*sdp)) ;
	DUMP_SEGS(3, sdp->cntxtp) ;

	return(0);
}


#define CG6MAP(sp,addr,page)            \
        hat_devload(sp,                 \
                    addr,               \
                    fbgetpage((caddr_t)page),    \
                    PROT_READ|PROT_WRITE|PROT_USER,0)

#define CG6UNMAP(segp)          \
        hat_unload(segp,                \
                   (segp)->s_base,      \
                   (segp)->s_size);

/*ARGSUSED*/
static
segcg6_lockfree(seg)
struct seg *seg;
{
    register struct segcg6_data *sdp = (struct segcg6_data *)seg->s_data;
    struct	seglock	*lp;
    void	segcg6_timeout();

    if ((lp = segcg6_findlock((off_t)sdp->offset))==NULL)
        return;
    lp->cr.flag |= UNGRAB;
    if (lp->sleepf) {
	untimeout((ifunc)segcg6_timeout,(caddr_t)lp);
        wakeup((caddr_t)lp->page);
    }
}

static void
segcg6_timeout(lp)
struct	seglock *lp;
{
    struct  segproc	*np;
    void	hat_devload();

    /* bug 1111182.  Can't touch hat layer in timer interrupt routine,
     * flag lock as unlocked and return instead.  Pagefault routine will
     * do the hat layer stuff instead.
     */


#ifdef	COMMENT
    np = &lp->cr;
    if (np->flag & LOCKMAP) {
	CG6UNMAP(np->locksegp);
	np->flag &= ~LOCKMAP;
    }
    if (np->flag & UNLOCKMAP)
	CG6UNMAP(np->unlocksegp);
    CG6MAP(np->unlocksegp,np->unlockaddr,lp->page);
    np->flag |= UNLOCKMAP;
    np->flag &= ~TRASHPAGE;

    np = &lp->cl;
    if (np->locksegp && np->flag & LOCKMAP) {
	CG6UNMAP(np->locksegp);
	np->flag &= ~LOCKMAP;
    }
    if (np->unlocksegp) {
	if (np->flag & UNLOCKMAP)
	    CG6UNMAP(np->unlocksegp);
	CG6MAP(np->unlocksegp,np->unlockaddr,trashpage);
	np->flag |= (UNLOCKMAP | TRASHPAGE);
    }
#endif	/* COMMENT */

    *lp->page = 0 ;
    lp->sleepf = 0 ;
    wakeup((caddr_t)lp->page);
}

/*
 * Handle lock segment faults here...
 *
 */

static
int
segcg6_lockfault(seg,addr)
register struct seg *seg;
addr_t  addr;
{
        register struct seglock *lp;
        register struct segcg6_data *current
                = (struct segcg6_data *)seg->s_data;
        struct  segproc *sp;
	void	hat_devload();
	/*void	hat_unload();*/
	extern	int	hz;
	int	s;

	/* Logic cases:
	 *
	 * 1. Any page fault, no lock:
	 *	faulting process becomes owner of lock, other process
	 *	has all mappings invalidated.  There shouldn't be any
	 *	sleeping processes, but if there are, they are woken.
	 *	New owner has mapping validated and continues.
	 *
	 * 2. Owner page fault, page is locked:
	 *  a. owner faults on lock page: (shouldn't happen)
	 *	owner gets valid lock page mapping and continues
	 *  b. owner faults on unlock page, nobody sleeping:
	 *	owner gets valid unlock page mapping and continues
	 *  c. owner faults on unlock page, somebody sleeping:
	 *	sleeping process(es) woken up, owner gets trashpage mapping
	 *	and continues.
	 *
	 * 3. Other page fault, page is locked:
	 *  a. other faults on lock page:
	 *	owner loses unlock mapping, other goes to sleep.  If other
	 *	is the creator of the lock, a 3-second timeout is set.
	 *  b. other faults on unlock page: (shouldn't happen)
	 *	This may occur as a result of a timeout or something.
	 *	other gets trashpage mapping and continues.
	 */

        /* look up the segment in the lock_list */

        lp = segcg6_findlock((off_t)current->offset);
        if (lp == NULL)
            return (FC_MAKE_ERR(EFAULT));

	s = splsoftclock();

	ASRT(lp != NULL) ;
        if (lp->cr.flag & UNGRAB) {
            CG6MAP(seg,addr,trashpage);
	    (void) splx(s);
            return(0);
        }

        /* initialization necessary? */

        if (lp->cr.procp == u.u_procp && lp->cr.locksegp == NULL) {
            lp->cr.locksegp = seg;
            lp->cr.lockaddr = addr;
        } else
        if (lp->cr.procp != u.u_procp && lp->cl.locksegp == NULL) {
            lp->cl.procp = u.u_procp;
            lp->cl.locksegp = seg;
            lp->cl.lockaddr = addr;
        } else
        if (lp->cr.procp == u.u_procp && lp->cr.locksegp != seg) {
            lp->cr.unlocksegp = seg;
            lp->cr.unlockaddr = addr;
        } else
        if (lp->cl.procp == u.u_procp && lp->cl.locksegp != seg) {
            lp->cl.unlocksegp = seg;
            lp->cl.unlockaddr = addr;
        }


	ASRT(lp->page != NULL) ;

	/* if there's no owner, this process becomes owner by default */

	if (lp->owner == NULL) {
	    if (lp->cr.procp == u.u_procp) {
		lp->owner = &lp->cr;
		lp->other = &lp->cl;
	    } else {
		lp->owner = &lp->cl;
		lp->other = &lp->cr;
	    }
	}

	/* if there's no lock, then this is the new owner */

	if( *(int *)lp->page == 0  &&  u.u_procp != lp->owner->procp )
	{
	    sp = lp->owner;
	    lp->owner = lp->other;
	    lp->other = sp;

	    /* other loses all mappings */
	    if( lp->other->flag & LOCKMAP )
		CG6UNMAP(lp->other->locksegp);
	    lp->other->flag &= ~LOCKMAP ;
	    if( (lp->other->flag & (UNLOCKMAP|TRASHPAGE)) == UNLOCKMAP )
	    {
		CG6UNMAP(lp->other->unlocksegp);
		lp->other->flag &= ~(UNLOCKMAP|TRASHPAGE) ;
	    }
	}



	/* if this is the owner and this is the lock mapping or
	 * there's nobody waiting for the lock, validate and return.
	 * Otherwise, owner gets trashpage mapping and sleepers wake up.
	 */

	if( u.u_procp == lp->owner->procp )
	{
	    if( seg == lp->owner->locksegp )	/*   on lock page */
	    {
		CG6MAP(lp->owner->locksegp,lp->owner->lockaddr,lp->page);
		lp->owner->flag |= LOCKMAP;
		if( lp->owner->flag & TRASHPAGE )
		{
		    CG6UNMAP(lp->owner->unlocksegp);
		    lp->owner->flag &= ~(UNLOCKMAP|TRASHPAGE);
		}
	    }
	    else if( !lp->sleepf )	/* unlock page, no sleepers */
	    {
		/* just validate the mapping and get out */
		if (lp->owner->flag & TRASHPAGE) {		/***/
		    CG6UNMAP(lp->owner->unlocksegp);
		}
		CG6MAP(lp->owner->unlocksegp, lp->owner->unlockaddr,lp->page);
		lp->owner->flag &= ~TRASHPAGE;
		lp->owner->flag |= UNLOCKMAP;
	    }
	    else			/* unlock page, sleepers */
	    {
		/* give owner a trashpage mapping and wake up sleepers */
		if (lp->owner->flag & UNLOCKMAP)		/***/
		    CG6UNMAP(lp->owner->unlocksegp);
		CG6MAP(lp->owner->unlocksegp, lp->owner->unlockaddr,trashpage);
		lp->owner->flag |= (TRASHPAGE|UNLOCKMAP);

		*(int *)lp->page = 0 ;
		untimeout((ifunc)segcg6_timeout,(caddr_t)lp);
		wakeup((caddr_t)lp->page);       /* wake up sleeper */
	    }
	}



	else		/* this is not the owner, and there is a lock */
	{
	    ASRT(lp->other != NULL) ;
	    if( seg == lp->other->locksegp ) {	/*   on lock page */
		ASRT(lp->owner != NULL) ;
		if (lp->owner->flag & UNLOCKMAP) {
		    CG6UNMAP(lp->owner->unlocksegp);
		    lp->owner->flag &= ~(TRASHPAGE|UNLOCKMAP);
		}

		lp->sleepf = 1;

		if (lp->cr.procp == u.u_procp) /* creator about to sleep */
		    timeout((ifunc)segcg6_timeout,(caddr_t)lp,LOCKTIME*hz);

#ifdef	MULTIPROCESSOR
		xc_dismissed();
#endif	MULTIPROCESSOR
		(void)sleep((caddr_t)lp->page,PRIBIO);	/* goodnight, gracie */
#ifdef	MULTIPROCESSOR
		xc_attention();
#endif	MULTIPROCESSOR
					    /* we wake up */
		lp->sleepf = 0;             /* clear sleepf */

		/* now return.  We'll fault again, and get ownership then. */
	    }
	    else				/*   on unlock page */
	    {
		/* this shouldn't happen, but let's be safe. */
		if (lp->other->flag & UNLOCKMAP)		/***/
		    CG6UNMAP(lp->other->unlocksegp);
		CG6MAP(lp->other->unlocksegp, lp->other->unlockaddr,trashpage);
		lp->other->flag |= (TRASHPAGE|UNLOCKMAP);
	    }
	}


	(void) splx(s);
	return(0);
}



/*
 * Handle a fault on a lego segment.  The only tricky part is that only
 * one valid mapping at a time is allowed.  Whenever a new mapping is
 * called for, the current values of the TEC and FBC registers in the
 * old context are saved away, and the old values in the new context
 * are restored.
 */

/*ARGSUSED*/
static
faultcode_t
segcg6_fault(seg, addr, len, type, rw)
	register struct seg *seg;
	addr_t addr;
	u_int len;
	enum fault_type type;
	enum seg_rw rw;
{
	register addr_t adr;
	register struct segcg6_data *current
	 	= (struct segcg6_data *)seg->s_data;
      	int pf, s;
        register struct cg6_softc *softc = getsoftc(minor(current->dev));
	void	hat_devload();
	/*void	hat_unload();*/
	register struct fbc *fbc = CG6VA_TO_FBC(softc->base) ;


#ifdef  MULTIPROCESSOR
        if ((type & SEG_CMAP ) && (softc->cmap_sleeping == 1)) {
           (void) sleep((caddr_t)softc->cmap_sleeping, PRIBIO);
            return(0);
        }
#endif  MULTIPROCESSOR


	if (type != F_INVAL) {
	    return(FC_MAKE_ERR(EFAULT));
	}

	if (current->flag & SEG_VRT) {
	    CG6MAP(seg, addr, softc->vrtpage);
	    return(0);
	}

        if (current->flag & SEG_LOCK) {
	    faultcode_t rval ;
#ifdef	MULTIPROCESSOR
	    xc_attention();
#endif	MULTIPROCESSOR
	    rval = segcg6_lockfault(seg,addr) ;
#ifdef	MULTIPROCESSOR
	    xc_dismissed();
#endif	MULTIPROCESSOR
	    return rval ;
	}

	s = splvm();
#ifdef	MULTIPROCESSOR
	xc_attention();
#endif	MULTIPROCESSOR
	if (current->cntxtp && softc->curcntxt != current->cntxtp) {
		/*
		 * time to switch lego contexts.
		 */
		if (softc->curcntxt != NULL) {
			register struct seg *segp ;

			DEBUGF(5, ( "unloading context %x, pid=%d\n",
			    softc->curcntxt, softc->curcntxt->pid)) ;

			segp = softc->curcntxt->seg_list ;
			while( segp != NULL )
			{
			    /*
			     * wipe out old valid mapping.
			     */

			    hat_unload(segp, segp->s_base, segp->s_size);
			    segp = ((struct segcg6_data *)segp->s_data)->link ;
			}

			/*
			 * switch hardware contexts: if and only if
			 * there is a legitimate context to restore.
			 */

			if( softc->curcntxt->flag & (SEG_FBC|SEG_TEC) )
			  if( !cg6_cntxsave(fbc,
				CG6VA_TO_TEC(softc->base), softc->curcntxt)) {
				  DEBUGF(1, ("cgsix: context save failed\n"));
#ifdef	MULTIPROCESSOR
				  xc_dismissed();
#endif	MULTIPROCESSOR
				  (void)splx(s);
				  return(FC_HWERR);
			  }
		}
		DEBUGF(5, ("loading context %x, pid=%d\n",
		    current->cntxtp, current->cntxtp->pid));

		if( current->cntxtp->flag & (SEG_FBC|SEG_TEC) )
		  if (!cg6_cntxrestore( fbc,
			CG6VA_TO_TEC(softc->base), current->cntxtp)) {
			      DEBUGF(1, ("cgsix: context load failed\n"));
#ifdef	MULTIPROCESSOR
			      xc_dismissed();
#endif	MULTIPROCESSOR
			      (void)splx(s);
			      return(FC_HWERR);
		      }

		/*
		 * switch software "context"
		 */
		softc->curcntxt = current->cntxtp;
	}

	for (adr = addr; adr < addr + len; adr += PAGESIZE) {
		pf = cgsixmmap(current->dev,
				(off_t)current->offset+(adr-seg->s_base),
				PROT_READ|PROT_WRITE);
		if (pf == -1) {
#ifdef	MULTIPROCESSOR
			xc_dismissed();
#endif	MULTIPROCESSOR
			(void)splx(s);
			return (FC_MAKE_ERR(EFAULT));
		}

		hat_devload(seg, adr, pf, current->prot|PROT_USER, 0 );
	}
#ifdef	MULTIPROCESSOR
	xc_dismissed();
#endif	MULTIPROCESSOR
	(void)splx(s);
	return(0);
}

/*ARGSUSED*/
static
int
segcg6_checkprot(seg, addr, len, prot)
	struct seg *seg;
	addr_t addr;
	u_int len, prot;
{
	register struct segcg6_data *sdp = (struct segcg6_data *)seg->s_data;

	return (sdp->prot & prot) == prot ? 0 : -1 ;
}

/*
 * segdev pages are always "in core".
 */
/*ARGSUSED*/
static
segcg6_incore(seg, addr, len, vec)
        struct seg *seg;
        addr_t addr;
        register u_int len;
        register char *vec;
{
        u_int v = 0;

        for (len = (len + PAGEOFFSET) & PAGEMASK; len; len -= PAGESIZE,
            v += PAGESIZE)
                *vec++ = 1;
        return (v);
}

static
segcg6_badop()
{
	/*
	 * silently fail.
	 */
	return(0);
}

#undef L_TEC_VDC_INTRNL0
#define L_TEC_VDC_INTRNL0 0x8000
#undef L_TEC_VDC_INTRNL1
#define L_TEC_VDC_INTRNL1 0xa000


static
cg6_cntxsave(fbc, tec, saved)
	struct fbc	*fbc;
	struct tec	*tec;
	struct cg6_cntxt	*saved;
{
	int	dreg;	/* counts through the data registers */
	u_int	*dp;	/* points to a tec data register */

	/*
	 * wait for fbc not busy; but not forever
	 * XXX - what *should* we do if the FBC never goes idle?
	 */

	CDELAY(!(fbc->l_fbc_status & L_FBC_BUSY), CG6_FBC_WAIT);
	if (fbc->l_fbc_status & L_FBC_BUSY) {
		printf("cgsix: cntxsave: fbc stayed busy\n");
		return 0;
	}

	DEBUGF(5, ("saving registers for %d\n", saved->pid)) ;

	/*
	 * start dumping stuff out.
	 */
	saved->fbc.status = fbc->l_fbc_status;
	saved->fbc.clipcheck = fbc->l_fbc_clipcheck;
	saved->fbc.misc = fbc->l_fbc_misc;
	saved->fbc.x0 = fbc->l_fbc_x0;
	saved->fbc.y0 = fbc->l_fbc_y0;
	saved->fbc.x1 = fbc->l_fbc_x1;
	saved->fbc.y1 = fbc->l_fbc_y1;
	saved->fbc.x2 = fbc->l_fbc_x2;
	saved->fbc.y2 = fbc->l_fbc_y2;
	saved->fbc.x3 = fbc->l_fbc_x3;
	saved->fbc.y3 = fbc->l_fbc_y3;
	saved->fbc.rasteroffx = fbc->l_fbc_rasteroffx;
	saved->fbc.rasteroffy = fbc->l_fbc_rasteroffy;
	saved->fbc.autoincx = fbc->l_fbc_autoincx;
	saved->fbc.autoincy = fbc->l_fbc_autoincy;
	saved->fbc.clipminx = fbc->l_fbc_clipminx;
	saved->fbc.clipminy = fbc->l_fbc_clipminy;
	saved->fbc.clipmaxx = fbc->l_fbc_clipmaxx;
	saved->fbc.clipmaxy = fbc->l_fbc_clipmaxy;
	saved->fbc.fcolor = fbc->l_fbc_fcolor;
	saved->fbc.bcolor = fbc->l_fbc_bcolor;
	saved->fbc.rasterop = fbc->l_fbc_rasterop;
	saved->fbc.planemask = fbc->l_fbc_planemask;
	saved->fbc.pixelmask = fbc->l_fbc_pixelmask;
	saved->fbc.pattalign = fbc->l_fbc_pattalign;
	saved->fbc.pattern0 = fbc->l_fbc_pattern0;
	saved->fbc.pattern1 = fbc->l_fbc_pattern1;
	saved->fbc.pattern2 = fbc->l_fbc_pattern2;
	saved->fbc.pattern3 = fbc->l_fbc_pattern3;
	saved->fbc.pattern4 = fbc->l_fbc_pattern4;
	saved->fbc.pattern5 = fbc->l_fbc_pattern5;
	saved->fbc.pattern6 = fbc->l_fbc_pattern6;
	saved->fbc.pattern7 = fbc->l_fbc_pattern7;

	/*
	 * the tec matrix and clipping registers are easy.
	 */
	saved->tec.mv = tec->l_tec_mv;
	saved->tec.clip = tec->l_tec_clip;
	saved->tec.vdc = tec->l_tec_vdc;

	/*
	 * the tec data registers are a little more non-obvious.
	 * internally, they are 36 bits.  what we see in the
	 * register file is a 32-bit window onto the underlying
	 * data register.  changing the data-type in the VDC
	 * gets us either of two parts of the data register.
	 * the internal format is opaque to us.
	 */
	tec->l_tec_vdc = (u_int)L_TEC_VDC_INTRNL0;
	for (dreg = 0, dp = &tec->l_tec_data00; dreg < 64; dreg++, dp++) {
		saved->tec.data[dreg][0] = *dp;
	}
	tec->l_tec_vdc = (u_int)L_TEC_VDC_INTRNL1;
	for (dreg = 0, dp = &tec->l_tec_data00; dreg < 64; dreg++, dp++) {
		saved->tec.data[dreg][1] = *dp;
	}
	return(1);
}

static
cg6_cntxrestore(fbc, tec, saved)
	struct fbc	*fbc;
	struct tec	*tec;
	struct cg6_cntxt	*saved;
{
	int	dreg;
	u_int	*dp;

	/*
	 * wait for fbc not busy; but not forever
	 * XXX - what *should* we do if the FBC never goes idle?
	 */

	CDELAY(!(fbc->l_fbc_status & L_FBC_BUSY), CG6_FBC_WAIT);
	if (fbc->l_fbc_status & L_FBC_BUSY) {
		printf("cgsix: cntxrestore: fbc stayed busy\n");
		return 0;
	}

	DEBUGF(5, ("restoring registers for %d\n", saved->pid)) ;

	/*
	 * reload the tec data registers.  see above for
	 * "how do they get 36 bits in that itty-bitty int"
	 */
	tec->l_tec_vdc = (u_int)L_TEC_VDC_INTRNL0;
	for (dreg = 0, dp = &tec->l_tec_data00; dreg < 64; dreg++, dp++) {
		*dp = saved->tec.data[dreg][0];
	}
	tec->l_tec_vdc = (u_int)L_TEC_VDC_INTRNL1;
	for (dreg = 0, dp = &tec->l_tec_data00; dreg < 64; dreg++, dp++) {
		*dp = saved->tec.data[dreg][1];
	}

	/*
	 * the tec matrix and clipping registers are next.
	 */
	tec->l_tec_mv = saved->tec.mv;
	tec->l_tec_clip = saved->tec.clip;
	tec->l_tec_vdc = saved->tec.vdc;

	/*
	 * now the FBC vertex and address registers
	 */
	fbc->l_fbc_x0 = saved->fbc.x0;
	fbc->l_fbc_y0 = saved->fbc.y0;
	fbc->l_fbc_x1 = saved->fbc.x1;
	fbc->l_fbc_y1 = saved->fbc.y1;
	fbc->l_fbc_x2 = saved->fbc.x2;
	fbc->l_fbc_y2 = saved->fbc.y2;
	fbc->l_fbc_x3 = saved->fbc.x3;
	fbc->l_fbc_y3 = saved->fbc.y3;
	fbc->l_fbc_rasteroffx = saved->fbc.rasteroffx;
	fbc->l_fbc_rasteroffy = saved->fbc.rasteroffy;
	fbc->l_fbc_autoincx = saved->fbc.autoincx;
	fbc->l_fbc_autoincy = saved->fbc.autoincy;
	fbc->l_fbc_clipminx = saved->fbc.clipminx;
	fbc->l_fbc_clipminy = saved->fbc.clipminy;
	fbc->l_fbc_clipmaxx = saved->fbc.clipmaxx;
	fbc->l_fbc_clipmaxy = saved->fbc.clipmaxy;

	/*
	 * restoring the attribute registers
	 */
	fbc->l_fbc_fcolor = saved->fbc.fcolor;
	fbc->l_fbc_bcolor = saved->fbc.bcolor;
	fbc->l_fbc_rasterop = saved->fbc.rasterop;
	fbc->l_fbc_planemask = saved->fbc.planemask;
	fbc->l_fbc_pixelmask = saved->fbc.pixelmask;
	fbc->l_fbc_pattalign = saved->fbc.pattalign;
	fbc->l_fbc_pattern0 = saved->fbc.pattern0;
	fbc->l_fbc_pattern1 = saved->fbc.pattern1;
	fbc->l_fbc_pattern2 = saved->fbc.pattern2;
	fbc->l_fbc_pattern3 = saved->fbc.pattern3;
	fbc->l_fbc_pattern4 = saved->fbc.pattern4;
	fbc->l_fbc_pattern5 = saved->fbc.pattern5;
	fbc->l_fbc_pattern6 = saved->fbc.pattern6;
	fbc->l_fbc_pattern7 = saved->fbc.pattern7;

	fbc->l_fbc_clipcheck = saved->fbc.clipcheck;
	fbc->l_fbc_misc = saved->fbc.misc;

	/*
	 * lastly, let's restore the status
	 */
	fbc->l_fbc_status = saved->fbc.status;
	return(1);
}
