cgsix.c revision 193974072f41a843678abf5f61979c748687e66b
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma weak setintrenable
/*
* Cgsix theory of operation:
*
* Most cg6 operations are done by mapping the cg6 components into
* user process memory. User processes that share mappings (typically
* pixrect programs) must cooperate among themselves to prevent damaging
* the state of the cg6. User processes may also acquire private
* mappings (MAP_PRIVATE flag to mmap(2)), in which case the cg6 segment
* driver will preserve device state for each mapping.
*
* Note that the segment driver may go away in the future.
*
* cg6_mmap interprets the device offset as follows:
*
* CG6_VBASE 0x70000000
* CG6_VADDR_FBC 0x70000000 fbc mapping
* CG6_VADDR_TEC 0x70001000 tec mapping
* CG6_VADDR_CMAP 0x70002000 colormap dacs
* CG6_VADDR_FHC 0x70004000 fhc mapping
* CG6_VADDR_THC 0x70005000 thc mapping
* CG6_VADDR_ROM 0x70006000 eprom mapping
* CG6_VADDR_COLOR 0x70016000 framebuffer mapping
* CG6_VADDR_DHC 0x78000000 dac hardware
* CG6_VADDR_ALT 0x78002000 alternate registers (?)
* CG6_VADDR_UART 0x78004000 uart, if any
* CG6_VADDR_VRT 0x78006000 vertical retrace counter page
*
* The lengths of these mappings should be:
*
* CG6_CMAP_SZ 0x2000
* CG6_FBCTEC_SZ 0x2000
* CG6_FHCTHC_SZ 0x2000
* CG6_ROM_SZ 0x10000
* CG6_FB_SZ 0x100000
* CG6_DHC_SZ 0x2000
* CG6_ALT_SZ 0x2000
*
* Mappings to the fbc and tec registers may be MAP_PRIVATE, in which case
* the segment driver keeps a per-context copy of the fbc and tec
* registers in local memory. Only one context at a time may have valid
* mappings. If a process tries to access the registers through an
* invalid mapping, the segment driver in invoked to swap register state
* and validate the mappings.
*
* In the case of the buggy LSC revision 2. chip, the framebuffer mapping
* is also considered part of a context. This is to ensure that the
* registers are idle before the framebuffer is touched.
*
* Mappings to FBC, TEC and framebuffer may be made seperately, in which
* case the driver uses heuristics to bind seperate mappings into a single
* context. These heuristics may break down if mappings are done in a
* funny order or in a multi-threaded environment, so seperate mappings
* are not recommended.
*
* Finally, processes have the option of mapping the "vertical retrace
* page". This is a page in shared memory containing a 32-bit integer
* that is incremented each time a vertical retrace interrupt occurs. It
* is used so that programs may synchronize themselves with vertical
* retrace.
*/
/*
* SBus accelerated 8 bit color frame buffer driver
*/
#include <sys/debug.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/buf.h>
#include <sys/errno.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/model.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/sysmacros.h>
#include <sys/mman.h>
#include <sys/cred.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/visual_io.h>
#include <sys/fbio.h>
#include <sys/cg6reg.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/vnode.h>
#include <vm/page.h>
#include <vm/as.h>
#include <vm/hat.h>
#include <vm/seg.h>
#include <sys/pixrect.h>
#include <sys/pr_impl_util.h>
#include <sys/pr_planegroups.h>
#include <sys/memvar.h>
#include <sys/cg3var.h> /* for CG3_MMAP_OFFSET */
#include <sys/cg6var.h>
#include <sys/kstat.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/devops.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/fs/snode.h>
#include <sys/modctl.h>
#include <sys/machsystm.h>
#define KIOIP KSTAT_INTR_PTR(softc->intrstats)
#define CG6DEBUG 0
/* configuration options */
#define CG6DELAY(c, n) \
{ \
register int N = n; \
while (--N > 0) { \
if (c) \
break; \
drv_usecwait(1); \
} \
}
#if CG6DEBUG >= 2
int cg6_debug = 0;
#define DEBUGF(level, args) \
{ if (cg6_debug >= (level)) cmn_err args; }
#define DUMP_SEGS(level, s, c) \
{ if (cg6_debug >= (level)) dump_segs(s, c); }
#else
#define DEBUGF(level, args) /* nothing */
#define DUMP_SEGS(level, s, c) /* nothing */
#endif
#define getprop(devi, name, def) \
ddi_getprop(DDI_DEV_T_ANY, (devi), \
DDI_PROP_DONTPASS, (name), (def))
/* config info */
static int cg6_open(dev_t *, int, int, cred_t *);
static int cg6_close(dev_t, int, int, cred_t *);
static int cg6_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int cg6_mmap(dev_t, off_t, int);
static int cg6_devmap(dev_t, devmap_cookie_t, offset_t, size_t,
size_t *, uint_t);
static int cg6_segmap(dev_t, off_t,
struct as *, caddr_t *, off_t, uint_t,
uint_t, uint_t, cred_t *);
static struct vis_identifier cg6_ident = { "SUNWcg6" };
static struct cb_ops cg6_cb_ops = {
cg6_open, /* open */
cg6_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
cg6_ioctl, /* ioctl */
cg6_devmap, /* devmap */
cg6_mmap, /* mmap */
cg6_segmap, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
0, /* streamtab */
D_NEW|D_MP|D_DEVMAP|D_HOTPLUG, /* Driver compatibility flag */
CB_REV, /* rev */
nodev, /* int (*cb_aread)() */
nodev /* int (*cb_awrite)() */
};
static int cg6_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
void *arg, void **result);
static int cg6_attach(dev_info_t *, ddi_attach_cmd_t);
static int cg6_detach(dev_info_t *, ddi_detach_cmd_t);
static int cg6_power(dev_info_t *, int, int);
struct dev_ops cgsix_ops = {
DEVO_REV, /* devo_rev, */
0, /* refcnt */
cg6_info, /* info */
nulldev, /* identify */
nulldev, /* probe */
cg6_attach, /* attach */
cg6_detach, /* detach */
nodev, /* reset */
&cg6_cb_ops, /* driver operations */
(struct bus_ops *)0, /* bus operations */
cg6_power,
ddi_quiesce_not_supported, /* devo_quiesce */
};
/*
* This stucture is used to contain the driver
* private mapping data (one for each requested
* device mapping). A pointer to this data is
* passed into each mapping callback routine.
*/
struct cg6map_pvt {
struct cg6_softc *softc;
devmap_cookie_t dhp; /* handle of devmap object */
uint_t type; /* mapping type */
off_t offset; /* starting offset of this map */
size_t len; /* length of this map */
struct cg6_cntxt *context; /* associated context */
struct cg6map_pvt *next; /* List of associated pvt's for */
/* this context */
};
static struct ddi_device_acc_attr endian_attr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
/* how much to map */
#define CG6MAPSIZE MMAPSIZE(0)
/* vertical retrace counter page */
#ifndef CG6_VRT_SZ
#define CG6_VRT_SZ 8192
#endif
/* boardrev bits */
#define BRDRV_SETRES 0x01 /* board supports set resolution */
#define BRDRV_TYPE 0x78 /* board type: */
#define BRDRV_GX 0x00
#define BRDRV_LSC 0x08
#define BRDRV_DUPLO 0x10
#define BRDRV_LEGOHR 0x18
#define BRDRV_QAUDRO 0x20
#define BRDRV_HIRES 0x80 /* hires dacs (junior vs. senior) */
#define CG6_FBC_WAIT 500000 /* .5 seconds */
/* 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 ((uint_t)0xffe0ffe0)
/*
* Per-context info:
* many registers in the tec and fbc do
* not need to be saved/restored.
*/
struct cg6_cntxt {
struct cg6_cntxt *link; /* link to next (private) context if any */
struct cg6map_pvt *pvt; /* List of associated pvt's for this context */
pid_t pid; /* "owner" of this context */
int flag;
struct {
uint_t mv;
uint_t clip;
uint_t vdc;
uint_t data[64][2];
} tec;
struct {
uint_t status;
uint_t clipcheck;
struct l_fbc_misc misc;
uint_t x0, y0, x1, y1, x2, y2, x3, y3;
uint_t rasteroffx, rasteroffy;
uint_t autoincx, autoincy;
uint_t clipminx, clipminy, clipmaxx, clipmaxy;
uint_t fcolor, bcolor;
struct l_fbc_rasterop rasterop;
uint_t planemask, pixelmask;
union l_fbc_pattalign pattalign;
uint_t pattern0, pattern1, pattern2, pattern3, pattern4, pattern5,
pattern6, pattern7;
} fbc;
};
/* per-unit data */
struct cg6_softc {
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
size_t size; /* total size of frame buffer */
size_t ndvramsz; /* size of non-display Video RAM */
caddr_t ndvram; /* Storage for nd-VRAM, while suspended */
size_t dummysize; /* total size of overlay plane */
kmutex_t interlock; /* interrupt locking */
off_t addr_rom; /* varies between p4 & sbus */
caddr_t fbctec; /* fbc&tec kernel map addr. */
caddr_t cmap; /* colormap kernel map addr. */
caddr_t fhcthc; /* fhc&thc kernel map addr. */
caddr_t rom; /* rom kernel map addr. */
caddr_t dhc; /* dac hardware */
caddr_t alt; /* alt registers */
caddr_t uart; /* uart registers */
pfn_t fbpfnum; /* pfn of fb for mmap() */
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 */
uint32_t image[32]; /* cursor image bitmap */
uint32_t mask[32]; /* cursor mask bitmap */
} cur;
union { /* shadow overlay color map */
uint32_t omap_int[2]; /* cheating here to save space */
uchar_t omap_char[3][2];
} omap_image;
#define omap_rgb omap_image.omap_char[0]
ushort_t omap_update; /* overlay colormap update flag */
uint32_t cmap_index; /* colormap update index */
uint32_t cmap_count; /* colormap update count */
union { /* shadow color map */
uint32_t cmap_int[CG6_CMAP_ENTRIES * 3 / sizeof (uint32_t)];
uchar_t 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 */
size_t fbmappable; /* bytes mappable */
int *vrtpage; /* pointer to VRT page */
ddi_umem_cookie_t vrtcookie; /* pointer to VRT allocation */
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 *curctx; /* context switching */
struct cg6_cntxt shared_ctx; /* shared context */
struct cg6_cntxt *pvt_ctx; /* list of non-shared contexts */
int chiprev; /* fbc chip revision # */
int emulation; /* emulation type, normally cgsix */
dev_info_t *devi; /* back pointer */
ddi_iblock_cookie_t iblock_cookie; /* block interrupts */
kmutex_t mutex; /* mutex locking */
kcondvar_t vrtsleep; /* for waiting on vertical retrace */
int mapped_by_prom; /* $#!@ SVr4 */
off_t mapped_by_driver;
int waiting;
int cg6_suspended; /* true if driver is suspended */
int vidon; /* video enable state */
int intr_flag;
kstat_t *intrstats; /* interrupt statistics */
};
static int cg6map_map(devmap_cookie_t, dev_t, uint_t, offset_t, size_t,
void **);
static int cg6map_contextmgt(devmap_cookie_t, void *, offset_t, size_t,
uint_t, uint_t);
static int cg6map_dup(devmap_cookie_t, void *, devmap_cookie_t, void **);
static void cg6map_unmap(devmap_cookie_t, void *, offset_t, size_t,
devmap_cookie_t, void **, devmap_cookie_t, void **);
static int cg6map_access(devmap_cookie_t, void *, offset_t, size_t,
uint_t, uint_t);
static
struct devmap_callback_ctl cg6map_ops = {
DEVMAP_OPS_REV, /* devmap_ops version number */
cg6map_map, /* devmap_ops map routine */
cg6map_access, /* devmap_ops access routine */
cg6map_dup, /* devmap_ops dup routine */
cg6map_unmap, /* devmap_ops unmap routine */
};
static size_t pagesize;
static void *cg6_softc_head;
clock_t cg6_ctxholdval = 1;
/* 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, CG6_DEPTH, CG6_CMAP_ENTRIES, 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(instance) \
((struct cg6_softc *)ddi_get_soft_state(cg6_softc_head, (instance)))
#define btob(n) ptob(btopr(n)) /* TODO, change this? */
/* convert softc to data pointers */
#define S_FBC(softc) ((struct fbc *)(softc)->fbctec)
#define S_TEC(softc) ((struct tec *)((softc)->fbctec + CG6_TEC_POFF))
#define S_FHC(softc) ((uint_t *)(softc)->fhcthc)
#define S_THC(softc) ((struct thc *)((softc)->fhcthc + CG6_TEC_POFF))
#define S_CMAP(softc) ((struct cg6_cmap *)(softc)->cmap)
#define cg6_set_video(softc, on) thc_set_video(S_THC(softc), (on))
#define cg6_get_video(softc) thc_get_video(S_THC(softc))
#define cg6_int_enable(softc) \
{\
thc_int_enable(S_THC(softc)); }
#define cg6_int_disable_intr(softc) \
{\
thc_int_disable(S_THC(softc)); }
#define cg6_int_disable(softc) \
{\
mutex_enter(&(softc)->interlock); \
softc->intr_flag = 1; \
cg6_int_disable_intr(softc); \
mutex_exit(&(softc)->interlock); }
#define cg6_int_pending(softc) thc_int_pending(S_THC(softc))
/* check if color map update is pending */
#define cg6_update_pending(softc) \
((softc)->cmap_count || (softc)->omap_update)
/*
* forward references
*/
static uint_t cg6_intr(caddr_t);
static void cg6_reset_cmap(volatile uchar_t *, uint_t);
static void cg6_update_cmap(struct cg6_softc *, uint_t, uint_t);
static void cg6_cmap_bcopy(uchar_t *, uchar_t *, uint_t);
static void cg6_restore_prom_cmap(struct cg6_softc *, volatile uchar_t *,
uint_t);
static void cg6_setcurpos(struct cg6_softc *);
static void cg6_setcurshape(struct cg6_softc *);
static void cg6_reset(struct cg6_softc *);
static int cg6_cntxsave(volatile struct fbc *, volatile struct tec *,
struct cg6_cntxt *);
static int cg6_cntxrestore(volatile struct fbc *, volatile struct tec *,
struct cg6_cntxt *);
static struct cg6_cntxt *ctx_map_insert(struct cg6_softc *, int);
static pid_t getpid(void);
/* Loadable Driver stuff */
static struct modldrv modldrv = {
&mod_driverops, /* Type of module. This one is a driver */
"cgsix driver", /* Name of the module. */
&cgsix_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1, (void *) &modldrv, NULL
};
int
_init(void)
{
register int e;
if ((e = ddi_soft_state_init(&cg6_softc_head,
sizeof (struct cg6_softc), 1)) != 0) {
DEBUGF(1, (CE_CONT, "done\n"));
return (e);
}
e = mod_install(&modlinkage);
if (e) {
ddi_soft_state_fini(&cg6_softc_head);
DEBUGF(1, (CE_CONT, "done\n"));
}
DEBUGF(1, (CE_CONT, "cgsix: _init done rtn=%d\n", e));
return (e);
}
int
_fini(void)
{
register int e;
DEBUGF(1, (CE_CONT, "cgsix: _fini, mem used=%d\n", total_memory));
if ((e = mod_remove(&modlinkage)) != 0)
return (e);
ddi_soft_state_fini(&cg6_softc_head);
return (0);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
cg6_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
register struct cg6_softc *softc;
caddr_t reg;
int w, h, bytes;
char *tmp;
char name[16];
int unit = ddi_get_instance(devi);
int proplen;
caddr_t fb_ndvram;
DEBUGF(1, (CE_CONT, "cg6_attach unit=%d cmd=%d\n", unit, (int)cmd));
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
if ((softc = ddi_get_driver_private(devi)) == NULL)
return (DDI_FAILURE);
if (!softc->cg6_suspended)
return (DDI_SUCCESS);
mutex_enter(&softc->mutex);
cg6_reset(softc);
if (softc->curctx) {
/* Restore the video state */
cg6_set_video(softc, softc->vidon);
/* Restore non display RAM */
if (ddi_map_regs(devi, 0, (caddr_t *)&fb_ndvram,
CG6_ADDR_COLOR + softc->_w * softc->_h,
softc->ndvramsz) == -1) {
mutex_exit(&softc->mutex);
return (DDI_FAILURE);
}
bcopy(softc->ndvram, fb_ndvram, softc->ndvramsz);
ddi_unmap_regs(devi, 0, (caddr_t *)&fb_ndvram,
CG6_ADDR_COLOR + softc->_w * softc->_h,
softc->ndvramsz);
kmem_free(softc->ndvram, softc->ndvramsz);
/* Restore other frame buffer state */
(void) cg6_cntxrestore(S_FBC(softc), S_TEC(softc),
softc->curctx);
cg6_setcurpos(softc);
cg6_setcurshape(softc);
cg6_update_cmap(softc, (uint_t)_ZERO_,
CG6_CMAP_ENTRIES);
cg6_int_enable(softc); /* Schedule the update */
}
softc->cg6_suspended = 0;
mutex_exit(&softc->mutex);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
DEBUGF(1, (CE_CONT, "cg6_attach unit=%d\n", unit));
pagesize = (size_t)ddi_ptob(devi, 1);
/* Allocate softc struct */
if (ddi_soft_state_zalloc(cg6_softc_head, unit) != 0) {
return (DDI_FAILURE);
}
softc = getsoftc(unit);
/* link it in */
softc->devi = devi;
DEBUGF(1, (CE_CONT, "cg6_attach devi=0x%x unit=%d\n", devi, unit));
ddi_set_driver_private(devi, softc);
/* Grab properties from PROM */
/* TODO don't really want default w, h */
if (ddi_prop_op(DDI_DEV_T_ANY, devi, PROP_LEN_AND_VAL_ALLOC,
DDI_PROP_DONTPASS, "emulation", (caddr_t)&tmp, &proplen) ==
DDI_PROP_SUCCESS) {
if (strcmp(tmp, "cgthree+") == 0)
softc->emulation = FBTYPE_SUN3COLOR;
else if (strcmp(tmp, "cgfour+") == 0)
softc->emulation = FBTYPE_SUN4COLOR;
else if (strcmp(tmp, "bwtwo+") == 0)
softc->emulation = FBTYPE_SUN2BW;
else
softc->emulation = FBTYPE_SUNFAST_COLOR;
kmem_free(tmp, proplen);
} else
softc->emulation = FBTYPE_SUNFAST_COLOR;
softc->_w = w = getprop(devi, "width", 1152);
softc->_h = h = getprop(devi, "height", 900);
bytes = getprop(devi, "linebytes", mpr_linebytes(w, 8));
softc->_linebytes = bytes;
/* Compute size of color frame buffer */
bytes = btob(bytes * h);
softc->size = (size_t)ddi_ptob(devi, ddi_btopr(devi, bytes));
softc->cg6info.vmsize = getprop(devi, "vmsize", 1);
if (softc->cg6info.vmsize > 1) {
softc->size = (size_t)ddi_ptob(devi,
ddi_btopr(devi, 8 * 1024 * 1024));
softc->fbmappable = 8 * 1024 * 1024;
} else
softc->fbmappable = 1024 * 1024;
/* Compute size of dummy overlay/enable planes */
softc->dummysize = btob(mpr_linebytes(w, 1) * h) * 2;
/*
* only use address property if we are console fb NOTE: if the prom has
* already mapped the fb *and* it has mapped all of fbmappable, then we
* don't need a new mapping
*/
if (reg = (caddr_t)(uintptr_t)getprop(devi, "address", 0)) {
softc->_fb = (MPR_T *) reg;
softc->mapped_by_prom = 1;
if (ddi_ptob(devi, ddi_btopr(devi, w * h)) <=
getprop(devi, "fbmapped", w * h))
bytes = 0;
DEBUGF(2, (CE_CONT, "cg6 mapped by PROM\n"));
}
softc->cg6info.line_bytes = softc->_linebytes;
softc->cg6info.accessible_width = getprop(devi, "awidth", 1152);
softc->cg6info.accessible_height = (uint_t)
(softc->cg6info.vmsize * 1024 * 1024) /
softc->cg6info.accessible_width;
softc->cg6info.hdb_capable = getprop(devi, "dblbuf", 0);
softc->cg6info.boardrev = getprop(devi, "boardrev", 0);
softc->vrtpage = NULL;
softc->vrtmaps = 0;
softc->vrtflag = 0;
#ifdef DEBUG
softc->cg6info.pad1 = CG6_VADDR_COLOR + CG6_FB_SZ;
#endif
/*
* get monitor attributes
*/
softc->moninfo.mon_type = getprop(devi, "montype", 0);
softc->moninfo.pixfreq = getprop(devi, "pixfreq", 929405);
softc->moninfo.hfreq = getprop(devi, "hfreq", 61795);
softc->moninfo.vfreq = getprop(devi, "vfreq", 66);
softc->moninfo.hfporch = getprop(devi, "hfporch", 32);
softc->moninfo.vfporch = getprop(devi, "vfporch", 2);
softc->moninfo.hbporch = getprop(devi, "hbporch", 192);
softc->moninfo.vbporch = getprop(devi, "vbporch", 31);
softc->moninfo.hsync = getprop(devi, "hsync", 128);
softc->moninfo.vsync = getprop(devi, "vsync", 4);
/*
* map in the registers. Map fbc&tec together. Likewise for fhc&thc.
*/
softc->addr_rom = CG6_ADDR_ROM_SBUS;
if (ddi_map_regs(devi, 0, &softc->fbctec, CG6_ADDR_FBC,
(off_t)CG6_FBCTEC_SZ) != 0) {
(void) cg6_detach(devi, DDI_DETACH);
return (DDI_FAILURE);
}
if (ddi_map_regs(devi, 0, &softc->cmap, CG6_ADDR_CMAP,
(off_t)CG6_CMAP_SZ) != 0) {
(void) cg6_detach(devi, DDI_DETACH);
return (DDI_FAILURE);
}
if (ddi_map_regs(devi, 0, &softc->fhcthc, CG6_ADDR_FHC,
(off_t)CG6_FHCTHC_SZ) != 0) {
(void) cg6_detach(devi, DDI_DETACH);
return (DDI_FAILURE);
}
softc->chiprev =
*S_FHC(softc) >> FHC_CONFIG_REV_SHIFT & FHC_CONFIG_REV_MASK;
cg6_reset(softc);
if (ddi_get_iblock_cookie(devi, 0, &softc->iblock_cookie)
!= DDI_SUCCESS) {
DEBUGF(2, (CE_CONT,
"cg6_attach%d ddi_get_iblock_cookie failed\n", unit));
(void) cg6_detach(devi, DDI_DETACH);
return (DDI_FAILURE);
}
mutex_init(&softc->interlock, NULL, MUTEX_DRIVER, softc->iblock_cookie);
mutex_init(&softc->mutex, NULL, MUTEX_DRIVER, softc->iblock_cookie);
cv_init(&softc->vrtsleep, NULL, CV_DRIVER, NULL);
if (ddi_add_intr(devi, 0, &softc->iblock_cookie, 0,
cg6_intr, (caddr_t)softc) != DDI_SUCCESS) {
DEBUGF(2, (CE_CONT,
"cg6_attach%d add_intr failed\n", unit));
(void) cg6_detach(devi, DDI_DETACH);
return (DDI_FAILURE);
}
/*
* 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, (uint_t)_ZERO_, CG6_CMAP_ENTRIES);
cg6_update_cmap(softc, (uint_t)_ZERO_, (uint_t)_ZERO_);
DEBUGF(2, (CE_CONT,
"cg6_attach%d just before create_minor node\n", unit));
(void) sprintf(name, "cgsix%d", unit);
if (ddi_create_minor_node(devi, name, S_IFCHR,
unit, DDI_NT_DISPLAY, NULL) == DDI_FAILURE) {
ddi_remove_minor_node(devi, NULL);
DEBUGF(2, (CE_CONT,
"cg6_attach%d create_minor node failed\n", unit));
return (DDI_FAILURE);
}
ddi_report_dev(devi);
if (softc->chiprev == 0)
cmn_err(CE_CONT, "?Revision 0 FBC\n");
cmn_err(CE_CONT,
"?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);
softc->pvt_ctx = NULL;
/*
* Initialize power management bookkeeping; components are created idle
*/
if (pm_create_components(devi, 2) == DDI_SUCCESS) {
(void) pm_busy_component(devi, 0);
pm_set_normal_power(devi, 0, 1);
pm_set_normal_power(devi, 1, 1);
(void) sprintf(name, "cgsixc%d", unit);
softc->intrstats = kstat_create("cgsix", unit, name,
"controller", KSTAT_TYPE_INTR,
1, KSTAT_FLAG_PERSISTENT);
if (softc->intrstats) {
kstat_install(softc->intrstats);
}
return (DDI_SUCCESS);
} else {
return (DDI_FAILURE);
}
}
static int
cg6_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
int instance = ddi_get_instance(devi);
register struct cg6_softc *softc = getsoftc(instance);
caddr_t fb_ndvram;
DEBUGF(1, (CE_CONT, "cg6_detach softc=%x, devi=0x%x\n", softc, devi));
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
if (softc == NULL)
return (DDI_FAILURE);
if (softc->cg6_suspended)
return (DDI_FAILURE);
mutex_enter(&softc->mutex);
if (softc->curctx) {
struct fbc *fbc0 = S_FBC(softc);
/* Save the video state */
softc->vidon = cg6_get_video(softc);
/* Save non display RAM */
softc->ndvramsz = (softc->cg6info.vmsize * 1024 * 1024)
- (softc->_w * softc->_h);
if ((softc->ndvram = kmem_alloc(softc->ndvramsz,
KM_NOSLEEP)) == NULL) {
mutex_exit(&softc->mutex);
return (DDI_FAILURE);
}
/*
* If FBC is busy, wait for maximum of 2 seconds for it
* to be idle
*/
CG6DELAY(!(fbc0->l_fbc_status & L_FBC_BUSY),
4*CG6_FBC_WAIT);
if (fbc0->l_fbc_status & L_FBC_BUSY) {
/*
* if still busy, try another 2 seconds before
* giving up
*/
CG6DELAY(!(fbc0->l_fbc_status & L_FBC_BUSY),
4*CG6_FBC_WAIT);
if (fbc0->l_fbc_status & L_FBC_BUSY)
cmn_err(CE_WARN, "cg6_detach: FBC still busy");
}
if (ddi_map_regs(devi, 0, &fb_ndvram, CG6_ADDR_COLOR +
softc->_w * softc->_h, softc->ndvramsz) == -1) {
kmem_free(softc->ndvram, softc->ndvramsz);
mutex_exit(&softc->mutex);
return (DDI_FAILURE);
}
bcopy(fb_ndvram, softc->ndvram, softc->ndvramsz);
ddi_unmap_regs(devi, 0, &fb_ndvram, CG6_ADDR_COLOR +
softc->_w * softc->_h, softc->ndvramsz);
/* Save other frame buffer state */
(void) cg6_cntxsave(S_FBC(softc), S_TEC(softc),
softc->curctx);
}
softc->cg6_suspended = 1;
mutex_exit(&softc->mutex);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/* shut off video if not console */
if (!softc->mapped_by_prom)
cg6_set_video(softc, 0);
mutex_enter(&softc->mutex);
cg6_int_disable(softc);
mutex_exit(&softc->mutex);
ddi_remove_intr(devi, 0, softc->iblock_cookie);
if (softc->fbctec)
ddi_unmap_regs(devi, 0,
&softc->fbctec, CG6_ADDR_FBC, CG6_FBCTEC_SZ);
if (softc->cmap)
ddi_unmap_regs(devi, 0, &softc->cmap, CG6_ADDR_CMAP,
CG6_CMAP_SZ);
if (softc->fhcthc)
ddi_unmap_regs(devi, 0,
&softc->fhcthc, CG6_ADDR_FHC, CG6_FHCTHC_SZ);
if (softc->intrstats) {
kstat_delete(softc->intrstats);
}
softc->intrstats = NULL;
if (softc->vrtpage != NULL)
ddi_umem_free(softc->vrtcookie);
mutex_destroy(&softc->mutex);
cv_destroy(&softc->vrtsleep);
ASSERT(softc->curctx == NULL);
/* free softc struct */
(void) ddi_soft_state_free(cg6_softc_head, instance);
pm_destroy_components(devi);
return (DDI_SUCCESS);
}
static int
cg6_power(dev_info_t *dip, int cmpt, int level)
{
struct cg6_softc *softc;
/*
* Framebuffer is represented by cmpt 0. In cg6, no power
* management is done on the framebuffer itself. Only the
* monitor (cmpt 1) is being power managed.
*/
if (cmpt == 0)
return (DDI_SUCCESS);
if (cmpt != 1 || 0 > level || level > 1 ||
(softc = ddi_get_driver_private(dip)) == NULL)
return (DDI_FAILURE);
if (level) {
/* Turn on sync and video. */
mutex_enter(&softc->mutex);
S_THC(softc)->l_thc_hcmisc |= THC_HCMISC_RESET;
drv_usecwait(500);
S_THC(softc)->l_thc_hcmisc |=
(THC_HCMISC_SYNCEN | THC_HCMISC_VIDEO);
S_THC(softc)->l_thc_hcmisc &= ~THC_HCMISC_RESET;
cg6_update_cmap(softc, (uint_t)_ZERO_, CG6_CMAP_ENTRIES);
cg6_int_enable(softc);
mutex_exit(&softc->mutex);
} else {
/* Turn off sync and video. */
mutex_enter(&softc->mutex);
S_THC(softc)->l_thc_hcmisc |= THC_HCMISC_RESET;
drv_usecwait(500);
S_THC(softc)->l_thc_hcmisc &=
~(THC_HCMISC_VIDEO | THC_HCMISC_SYNCEN);
S_THC(softc)->l_thc_hcmisc &= ~THC_HCMISC_RESET;
mutex_exit(&softc->mutex);
}
return (DDI_SUCCESS);
}
/* ARGSUSED */
static int
cg6_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int error = DDI_SUCCESS;
minor_t instance;
struct cg6_softc *softc;
instance = getminor((dev_t)arg);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
if ((softc = getsoftc(instance)) == NULL) {
error = DDI_FAILURE;
} else {
*result = (void *) softc->devi;
error = DDI_SUCCESS;
}
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *) (uintptr_t)instance;
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
}
return (error);
}
/*ARGSUSED*/
static int
cg6_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
int unit = getminor(*devp);
struct cg6_softc *softc = getsoftc(unit);
int error = 0;
DEBUGF(2, (CE_CONT, "cg6_open(%d), mem used=%d\n", unit, total_memory));
/*
* is this gorp necessary?
*/
if (otyp != OTYP_CHR) {
error = EINVAL;
} else
if (softc == NULL) {
error = ENXIO;
}
return (error);
}
/*ARGSUSED*/
static
int
cg6_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
int unit = getminor(dev);
struct cg6_softc *softc = getsoftc(unit);
int error = 0;
DEBUGF(2, (CE_CONT, "cg6_close(%d, %d, %d), mem used=%d\n",
unit, flag, otyp, total_memory));
if (otyp != OTYP_CHR) {
error = EINVAL;
} else if (softc == NULL) {
error = ENXIO;
} else {
mutex_enter(&softc->mutex);
cg6_reset_cmap(softc->cmap_rgb, CG6_CMAP_ENTRIES);
cg6_restore_prom_cmap(softc, softc->cmap_rgb, CG6_CMAP_ENTRIES);
softc->cur.enable = 0;
softc->curctx = NULL;
cg6_reset(softc);
mutex_exit(&softc->mutex);
}
return (error);
}
/*ARGSUSED*/
static int
cg6_mmap(dev_t dev, off_t off, int prot)
{
struct cg6_softc *softc = getsoftc(getminor(dev));
ssize_t diff;
caddr_t page;
intptr_t rval = 0;
DEBUGF(off ? 5 : 1, (CE_CONT, "cg6_mmap(%d, 0x%x)\n",
getminor(dev), (uint_t)off));
if ((diff = off - CG6_VADDR_COLOR) >= 0 && diff < softc->fbmappable)
rval = softc->fbpfnum + diff / pagesize;
else if ((diff = off - CG6_VADDR_FBC) >= 0 && diff < CG6_FBCTEC_SZ)
page = softc->fbctec + diff;
else if ((diff = off - CG6_VADDR_CMAP) >= 0 && diff < CG6_CMAP_SZ)
page = softc->cmap + diff;
else if ((diff = off - CG6_VADDR_FHC) >= 0 && diff < CG6_FHCTHC_SZ)
page = softc->fhcthc + diff;
else if ((diff = off - CG6_VADDR_ROM) >= 0 && diff < CG6_ROM_SZ)
page = softc->rom + diff;
else if ((diff = off - CG6_VADDR_DHC) >= 0 && diff < CG6_DHC_SZ)
page = softc->dhc + diff;
else if ((diff = off - CG6_VADDR_ALT) >= 0 && diff < CG6_ALT_SZ)
page = softc->alt + diff;
else if ((diff = off - CG6_VADDR_VRT) >= 0 && diff < CG6_VRT_SZ)
page = softc->vrtpage ?
(caddr_t)softc->vrtpage + diff : (caddr_t)-1;
else if ((diff = off - CG3_MMAP_OFFSET) >= 0 &&
diff < softc->fbmappable)
rval = softc->fbpfnum + diff / pagesize;
else if (off < CG6_VBASE) {
/*
* getting more and more complicated; what we return depends on what
* we're emulating
*/
if (softc->emulation == FBTYPE_SUN3COLOR) {
if (off >= 0 && off < softc->fbmappable)
rval = softc->fbpfnum + diff / pagesize;
else
page = (caddr_t)-1;
} else { /* softc->emulation == FBTYPE_SUN4COLOR */
if (off >= 0 && off < softc->dummysize)
page = softc->rom;
else if ((diff = off - softc->dummysize) < softc->fbmappable)
rval = softc->fbpfnum + diff / pagesize;
}
/* TODO: bw2? */
} else
page = (caddr_t)-1;
if (rval == 0)
if (page != (caddr_t)-1)
rval = hat_getkpfnum(page);
else
rval = -1;
DEBUGF(5, (CE_CONT, "cg6_mmap returning 0x%x\n", rval));
return ((int)rval); /* XXX64 */
}
/*ARGSUSED*/
static int
cg6_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *cred, int *rval)
{
struct cg6_softc *softc = getsoftc(getminor(dev));
int cursor_cmap;
int i;
uchar_t *iobuf_cmap_red;
uchar_t *iobuf_cmap_green;
uchar_t *iobuf_cmap_blue;
uchar_t *stack_cmap;
STRUCT_DECL(fbcmap, fbcmap);
STRUCT_DECL(fbcursor, fbcursor);
uint_t index;
uint_t count;
uchar_t *map;
uint_t entries;
DEBUGF(3, (CE_CONT, "cg6_ioctl(%d, 0x%x)\n", getminor(dev), cmd));
/* default to updating normal colormap */
cursor_cmap = 0;
switch (cmd) {
case VIS_GETIDENTIFIER:
if (ddi_copyout((caddr_t)&cg6_ident,
(caddr_t)data,
sizeof (struct vis_identifier),
mode))
return (EFAULT);
break;
case FBIOPUTCMAP:
case FBIOGETCMAP:
cmap_ioctl:
if (cursor_cmap == 0) {
STRUCT_INIT(fbcmap, mode);
if (ddi_copyin((caddr_t)data,
STRUCT_BUF(fbcmap), STRUCT_SIZE(fbcmap), mode))
return (EFAULT);
}
index = STRUCT_FGET(fbcmap, index);
count = STRUCT_FGET(fbcmap, count);
if (count == 0) {
return (0);
}
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);
}
/*
* Allocate memory for color map RGB entries.
*/
stack_cmap = kmem_alloc((CG6_CMAP_ENTRIES * 3), KM_SLEEP);
iobuf_cmap_red = stack_cmap;
iobuf_cmap_green = stack_cmap + CG6_CMAP_ENTRIES;
iobuf_cmap_blue = stack_cmap + (CG6_CMAP_ENTRIES * 2);
if (cmd == FBIOPUTCMAP) {
int error;
DEBUGF(3, (CE_CONT, "FBIOPUTCMAP\n"));
if (error = ddi_copyin(
STRUCT_FGETP(fbcmap, red),
iobuf_cmap_red, count, mode)) {
kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
return (error);
}
if (error = ddi_copyin(
STRUCT_FGETP(fbcmap, green),
iobuf_cmap_green, count, mode)) {
kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
return (error);
}
if (error = ddi_copyin(
STRUCT_FGETP(fbcmap, blue),
iobuf_cmap_blue, count, mode)) {
kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
return (error);
}
mutex_enter(&softc->mutex);
map += index * 3;
if (cg6_update_pending(softc))
cg6_int_disable(softc);
/*
* Copy color map entries from stack to the color map
* table in the softc area.
*/
cg6_cmap_bcopy(iobuf_cmap_red, map++, count);
cg6_cmap_bcopy(iobuf_cmap_green, map++, count);
cg6_cmap_bcopy(iobuf_cmap_blue, map, count);
/* cursor colormap update */
if (entries < CG6_CMAP_ENTRIES)
count = 0;
cg6_update_cmap(softc, index, count);
cg6_int_enable(softc);
mutex_exit(&softc->mutex);
} else {
/* FBIOGETCMAP */
DEBUGF(3, (CE_CONT, "FBIOGETCMAP\n"));
mutex_enter(&softc->mutex);
map += index * 3;
/*
* Copy color map entries from soft area to
* local storage and prepare for a copyout
*/
cg6_cmap_bcopy(iobuf_cmap_red, map++, -count);
cg6_cmap_bcopy(iobuf_cmap_green, map++, -count);
cg6_cmap_bcopy(iobuf_cmap_blue, map, -count);
mutex_exit(&softc->mutex);
if (ddi_copyout(iobuf_cmap_red,
STRUCT_FGETP(fbcmap, red), count,
mode)) {
kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
return (EFAULT);
}
if (ddi_copyout(iobuf_cmap_green,
STRUCT_FGETP(fbcmap, green), count,
mode)) {
kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
return (EFAULT);
}
if (ddi_copyout(iobuf_cmap_blue,
STRUCT_FGETP(fbcmap, blue), count,
mode)) {
kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
return (EFAULT);
}
}
kmem_free(stack_cmap, (CG6_CMAP_ENTRIES * 3));
break;
case FBIOSATTR: {
struct fbsattr attr;
if (ddi_copyin((caddr_t)data,
(caddr_t)&attr,
sizeof (attr),
mode))
return (EFAULT);
DEBUGF(3, (CE_CONT, "FBIOSATTR, type=%d\n", attr.emu_type));
if (attr.emu_type != -1)
switch (attr.emu_type) {
case FBTYPE_SUN3COLOR:
case FBTYPE_SUN4COLOR:
case FBTYPE_SUN2BW:
case FBTYPE_SUNFAST_COLOR:
mutex_enter(&softc->mutex);
softc->emulation = attr.emu_type;
mutex_exit(&softc->mutex);
break;
default:
return (EINVAL);
}
/* ignore device-dependent stuff */
}
break;
case FBIOGATTR: {
struct fbgattr attr;
DEBUGF(3, (CE_CONT, "FBIOGATTR, emu_type=%d\n",
softc->emulation));
bcopy((caddr_t)&cg6_attr, (caddr_t)&attr, sizeof (attr));
mutex_enter(&softc->mutex);
attr.fbtype.fb_type = softc->emulation;
attr.fbtype.fb_width = softc->_w;
attr.fbtype.fb_height = softc->_h;
/* XXX not quite like a cg4 */
attr.fbtype.fb_size = (int)softc->size;
attr.sattr.emu_type = softc->emulation;
mutex_exit(&softc->mutex);
if (ddi_copyout((caddr_t)&attr,
(caddr_t)data,
sizeof (struct fbgattr),
mode))
return (EFAULT);
}
break;
/*
* always claim to be a cg4 if they call this ioctl. This is to
* support older software which was staticly-linked before cg6 was
* invented, and to support newer software which has come to expect
* this behavior.
*/
case FBIOGTYPE: {
struct fbtype fb;
mutex_enter(&softc->mutex);
bcopy(&cg6_attr.fbtype, &fb, sizeof (struct fbtype));
DEBUGF(3, (CE_CONT, "FBIOGTYPE\n"));
fb.fb_type = FBTYPE_SUN4COLOR;
fb.fb_width = softc->_w;
fb.fb_height = softc->_h;
/* XXX not quite like a cg4 */
fb.fb_size = (int)softc->size;
mutex_exit(&softc->mutex);
if (ddi_copyout((caddr_t)&fb,
(caddr_t)data,
sizeof (struct fbtype),
mode))
return (EFAULT);
}
break;
case FBIOSVIDEO:
DEBUGF(3, (CE_CONT, "FBIOSVIDEO\n"));
if (ddi_copyin((caddr_t)data,
(caddr_t)&i,
sizeof (int),
mode))
return (EFAULT);
mutex_enter(&softc->mutex);
cg6_set_video(softc, i & FBVIDEO_ON);
mutex_exit(&softc->mutex);
break;
case FBIOGVIDEO:
DEBUGF(3, (CE_CONT, "FBIOGVIDEO\n"));
mutex_enter(&softc->mutex);
i = cg6_get_video(softc) ? FBVIDEO_ON : FBVIDEO_OFF;
mutex_exit(&softc->mutex);
if (ddi_copyout((caddr_t)&i,
(caddr_t)data,
sizeof (int),
mode))
return (EFAULT);
break;
/* informational ioctls */
case FBIOGXINFO:
if (ddi_copyout((caddr_t)&softc->cg6info,
(caddr_t)data,
sizeof (struct cg6_info),
mode))
return (EFAULT);
return (0);
case FBIOMONINFO:
if (ddi_copyout((caddr_t)&softc->moninfo,
(caddr_t)data,
sizeof (struct mon_info),
mode))
return (EFAULT);
return (0);
/* vertical retrace interrupt */
case FBIOVERTICAL:
mutex_enter(&softc->mutex);
softc->vrtflag |= CG6VRTIOCTL;
cg6_int_enable(softc);
cv_wait(&softc->vrtsleep, &softc->mutex);
mutex_exit(&softc->mutex);
return (0);
case FBIOVRTOFFSET:
i = CG6_VADDR_VRT;
if (ddi_copyout((caddr_t)&i,
(caddr_t)data,
sizeof (int),
mode))
return (EFAULT);
return (0);
/* HW cursor control */
case FBIOSCURSOR: {
int set;
ssize_t cbytes;
uint32_t stack_image[32], stack_mask[32];
STRUCT_INIT(fbcursor, mode);
if (ddi_copyin((void *)data, STRUCT_BUF(fbcursor),
STRUCT_SIZE(fbcursor), mode))
return (EFAULT);
set = STRUCT_FGET(fbcursor, set);
/* Compute cursor bitmap bytes */
cbytes = STRUCT_FGET(fbcursor, size.y) *
sizeof (softc->cur.image[0]);
if (set & FB_CUR_SETSHAPE) {
if (STRUCT_FGET(fbcursor, size.x) > 32 ||
STRUCT_FGET(fbcursor, size.y) > 32) {
return (EINVAL);
}
/* copy cursor image into softc */
if (STRUCT_FGETP(fbcursor, image) &&
ddi_copyin(STRUCT_FGETP(fbcursor, image),
&stack_image, cbytes, mode))
return (EFAULT);
if (STRUCT_FGETP(fbcursor, mask) &&
ddi_copyin(STRUCT_FGETP(fbcursor, mask),
&stack_mask, cbytes, mode))
return (EFAULT);
}
mutex_enter(&softc->mutex);
if (set & FB_CUR_SETCUR)
softc->cur.enable = STRUCT_FGET(fbcursor, enable);
if (set & FB_CUR_SETPOS)
softc->cur.pos = STRUCT_FGET(fbcursor, pos);
if (set & FB_CUR_SETHOT)
softc->cur.hot = STRUCT_FGET(fbcursor, hot);
/* update hardware */
cg6_setcurpos(softc);
if (set & FB_CUR_SETSHAPE) {
if (STRUCT_FGETP(fbcursor, image)) {
bzero((caddr_t)softc->cur.image,
sizeof (softc->cur.image));
bcopy((caddr_t)&stack_image,
(caddr_t)softc->cur.image,
cbytes);
}
if (STRUCT_FGETP(fbcursor, mask)) {
bzero((caddr_t)softc->cur.mask,
sizeof (softc->cur.mask));
bcopy((caddr_t)&stack_mask,
(caddr_t)softc->cur.mask,
cbytes);
}
/* load into hardware */
softc->cur.size = STRUCT_FGET(fbcursor, size);
cg6_setcurshape(softc);
}
mutex_exit(&softc->mutex);
/* load colormap */
if (set & FB_CUR_SETCMAP) {
cursor_cmap = 1;
cmd = FBIOPUTCMAP;
STRUCT_SET_HANDLE(fbcmap, mode,
STRUCT_FADDR(fbcursor, cmap));
goto cmap_ioctl;
}
}
break;
case FBIOGCURSOR: {
ssize_t cbytes;
uint32_t stack_image[32], stack_mask[32];
STRUCT_INIT(fbcursor, mode);
if (ddi_copyin((void *)data, STRUCT_BUF(fbcursor),
STRUCT_SIZE(fbcursor), mode))
return (EFAULT);
mutex_enter(&softc->mutex);
STRUCT_FSET(fbcursor, set, 0);
STRUCT_FSET(fbcursor, enable, softc->cur.enable);
STRUCT_FSET(fbcursor, pos, softc->cur.pos);
STRUCT_FSET(fbcursor, hot, softc->cur.hot);
STRUCT_FSET(fbcursor, size, softc->cur.size);
STRUCT_FSET(fbcursor, cmap.index, 0);
STRUCT_FSET(fbcursor, cmap.count, 2);
/* compute cursor bitmap bytes */
cbytes = softc->cur.size.y * sizeof (softc->cur.image[0]);
bcopy(softc->cur.image, &stack_image, cbytes);
bcopy(softc->cur.mask, &stack_mask, cbytes);
mutex_exit(&softc->mutex);
if (ddi_copyout(STRUCT_BUF(fbcursor), (void *)data,
STRUCT_SIZE(fbcursor), mode))
return (EFAULT);
/* if image pointer is non-null copy both bitmaps */
if (STRUCT_FGETP(fbcursor, image)) {
if (ddi_copyout(&stack_image,
STRUCT_FGETP(fbcursor, image),
cbytes, mode))
return (EFAULT);
if (ddi_copyout(&stack_mask,
STRUCT_FGETP(fbcursor, mask),
cbytes, mode))
return (EFAULT);
}
/* if red pointer is non-null copy colormap */
if (STRUCT_FGETP(fbcursor, cmap.red)) {
cursor_cmap = 1;
cmd = FBIOGETCMAP;
/*
* XX64 The code used to do this:
*
* data = (int)&((struct fbcursor *)data)->cmap;
*
* However the cmap_ioctl handler doesn't look
* at 'data' so this assignment doesn't do anything.
* Instead we made it set the correct field in the
* fbcurpos structure we were passed in from
* userland.
*/
STRUCT_SET_HANDLE(fbcmap, mode,
STRUCT_FADDR(fbcursor, cmap));
goto cmap_ioctl;
}
}
break;
case FBIOSCURPOS: {
struct fbcurpos stack_curpos; /* cursor position */
if (ddi_copyin((caddr_t)data,
(caddr_t)&stack_curpos,
sizeof (struct fbcurpos),
mode))
return (EFAULT);
mutex_enter(&softc->mutex);
bcopy((caddr_t)&stack_curpos, (caddr_t)&softc->cur.pos,
sizeof (struct fbcurpos));
cg6_setcurpos(softc);
mutex_exit(&softc->mutex);
}
break;
case FBIOGCURPOS: {
struct fbcurpos stack_curpos; /* cursor position */
mutex_enter(&softc->mutex);
bcopy((caddr_t)&softc->cur.pos, (caddr_t)&stack_curpos,
sizeof (struct fbcurpos));
mutex_exit(&softc->mutex);
if (ddi_copyout((caddr_t)&stack_curpos,
(caddr_t)data,
sizeof (struct fbcurpos),
mode))
return (EFAULT);
}
break;
case FBIOGCURMAX: {
static struct fbcurpos curmax = {32, 32};
if (ddi_copyout((caddr_t)&curmax,
(caddr_t)data,
sizeof (struct fbcurpos),
mode))
return (EFAULT);
}
break;
#if CG6DEBUG >= 3
case 255:
cg6_debug = (int)data;
if (cg6_debug == -1)
cg6_debug = CG6DEBUG;
cmn_err(CE_CONT, "cg6_debug is now %d\n", cg6_debug);
break;
#endif
default:
return (ENOTTY);
} /* switch(cmd) */
return (0);
}
static uint_t
cg6_intr(caddr_t arg)
{
struct cg6_softc *softc = (struct cg6_softc *)arg;
volatile uint32_t *in;
volatile uint32_t *out;
volatile uint32_t tmp;
DEBUGF(7, (CE_CONT,
"cg6_intr: softc=%x, vrtflag=%x\n", softc, softc->vrtflag));
mutex_enter(&softc->mutex);
mutex_enter(&softc->interlock);
if (!cg6_int_pending(softc)) {
if (softc->intr_flag) {
softc->intr_flag = 0;
} else {
if (softc->intrstats) {
KIOIP->intrs[KSTAT_INTR_SPURIOUS]++;
}
mutex_exit(&softc->interlock);
mutex_exit(&softc->mutex);
return (DDI_INTR_UNCLAIMED); /* nope, not mine */
}
}
if (!(cg6_update_pending(softc) || (softc)->vrtflag)) {
/* TODO catch stray interrupts? */
cg6_int_disable_intr(softc);
if (softc->intrstats) {
KIOIP->intrs[KSTAT_INTR_HARD]++;
}
mutex_exit(&softc->interlock);
mutex_exit(&softc->mutex);
return (DDI_INTR_CLAIMED);
}
if (softc->vrtflag & CG6VRTCTR) {
if (softc->vrtmaps == 0) {
softc->vrtflag &= ~CG6VRTCTR;
} else
*softc->vrtpage += 1;
}
if (softc->vrtflag & CG6VRTIOCTL) {
softc->vrtflag &= ~CG6VRTIOCTL;
cv_broadcast(&softc->vrtsleep);
}
if (cg6_update_pending(softc)) {
volatile struct cg6_cmap *cmap = S_CMAP(softc);
LOOP_T count = softc->cmap_count;
/* load cursor color map */
if (softc->omap_update) {
in = &softc->omap_image.omap_int[0];
out = (uint32_t *)& 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_int[0];
out = (uint32_t *)& 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(uint32_t *, in, index * 3);
/* copy 4 bytes (4/3 RGB entries) per loop iteration */
count *= 3;
/* CSTYLED */
PR_LOOPV(count, tmp = *in++;
*out = tmp;
*out = tmp <<= 8;
*out = tmp <<= 8;
/* CSTYLED */
*out = tmp <<= 8);
softc->cmap_count = 0;
}
softc->omap_update = 0;
}
cg6_int_disable_intr(softc);
if (softc->vrtflag)
cg6_int_enable(softc);
if (softc->intrstats) {
KIOIP->intrs[KSTAT_INTR_HARD]++;
}
mutex_exit(&softc->interlock);
mutex_exit(&softc->mutex);
return (DDI_INTR_CLAIMED);
}
/*
* Initialize a colormap: background = white, all others = black
*/
static void
cg6_reset_cmap(volatile uchar_t *cmap, uint_t 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(struct cg6_softc *softc, uint_t index, uint_t count)
{
uint_t high, low;
if (count == 0) {
softc->omap_update = 1;
return;
}
high = softc->cmap_count;
if (high != 0) {
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(uchar_t *bufp, uchar_t *rgb, uint_t count)
{
LOOP_T rcount = count;
if (--rcount >= 0)
PR_LOOPVP(rcount,
/* CSTYLED */
*rgb = *bufp++;
/* CSTYLED */
rgb += 3);
else {
rcount = -rcount - 2;
PR_LOOPVP(rcount,
/* CSTYLED */
*bufp++ = *rgb;
/* CSTYLED */
rgb += 3);
}
}
/*
* This routine restores the color map to it's post-attach time values.
*/
static void
cg6_restore_prom_cmap(struct cg6_softc *softc,
volatile uchar_t *cmap,
uint_t entries)
{
volatile struct cg6_cmap *hwcmap = S_CMAP(softc);
volatile uint32_t *in, *out, tmp;
LOOP_T count;
out = (uint32_t *)&hwcmap->cmap;
in = (uint32_t *)cmap;
if (entries != 0) {
hwcmap->addr = 0;
count = ((entries + 3) >> 2) * 3;
/* CSTYLED */
PR_LOOPV(count, tmp = *in++;
*out = tmp;
*out = tmp <<= 8;
*out = tmp <<= 8;
/* CSTYLED */
*out = tmp <<= 8);
}
}
/*
* enable/disable/update HW cursor
*/
static void
cg6_setcurpos(struct cg6_softc *softc)
{
volatile struct thc *thc = S_THC(softc);
thc->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 void
cg6_setcurshape(struct cg6_softc *softc)
{
uint_t tmp, edge = 0;
volatile uint_t *image, *mask, *hw;
volatile struct thc *thc = S_THC(softc);
int i;
/* compute right edge mask */
if (softc->cur.size.x)
edge = (uint_t)~ 0 << (32 - softc->cur.size.x);
image = softc->cur.image;
mask = softc->cur.mask;
hw = (uint_t *)&thc->l_thc_cursora00;
for (i = 0; i < 32; i++) {
hw[i] = (tmp = mask[i] & edge);
hw[i + 32] = tmp & image[i];
}
}
static void
cg6_reset(struct cg6_softc *softc)
{
volatile struct thc *thc = S_THC(softc);
/* disable HW cursor */
thc->l_thc_cursor = CG6_CURSOR_OFFPOS;
/* reinitialize TEC */
{
volatile struct tec *tec = S_TEC(softc);
tec->l_tec_mv = 0;
tec->l_tec_clip = 0;
tec->l_tec_vdc = 0;
}
/* reinitialize FBC config register */
{
volatile uint_t *fhc = S_FHC(softc);
uint_t 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 rev 2 (buggy) */
/* rev == 6 : LSC rev 3 */
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 */
{
volatile struct cg6_cmap *cmap = S_CMAP(softc);
/* command register */
cmap->addr = 6 << 24;
/* turn on CR1:0, overlay enable */
cmap->ctrl = cmap->ctrl | (0x3 << 24);
}
}
/*
* This code is no longer used, since OBP proms now do all device
* initialization. Nevertheless, it is instructive and I'm going to
* keep it in as a comment, should anyone ever want to know how to
* do minimal device initialization. Note the c++ style embedded
* comments.
*
* cg6_init(softc)
* struct cg6_softc *softc;
* {
* // Initialize DAC
* {
* register struct cg6_cmap *cmap = S_CMAP(softc);
* 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 = S_THC(softc);
* 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, (CE_CONT, "TEC rev %d\n",
* thc->l_thc_hcmisc >> THC_HCMISC_REV_SHIFT &
* THC_HCMISC_REV_MASK));
* }
*
* //
* // Initialize FHC for 1152 X 900 screen
* //
* {
* volatile uint_t *fhc = S_FHC(softc), rev;
*
* rev = *fhc >> FHC_CONFIG_REV_SHIFT & FHC_CONFIG_REV_MASK;
* DEBUGF(1, (CE_CONT, "cg6_init: 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
*
* (rev <= 1 ? FHC_CONFIG_DST_DISABLE : 0);
* }
* }
*/
/*
* 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-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.
*/
#define CG6MAP_SHARED 0x02 /* shared context */
#define CG6MAP_VRT 0x04 /* vrt page */
#define CG6MAP_FBCTEC 0X08 /* mapping includes fbc and/or tec */
#define CG6MAP_FB 0X10 /* mapping includes framebuffer */
#define CG6MAP_CTX (CG6MAP_FBCTEC | CG6MAP_FB) /* needs context */
static struct cg6map_pvt *
cg6_pvt_alloc(struct cg6_cntxt *ctx,
uint_t type,
offset_t off,
size_t len,
struct cg6_softc *softc)
{
struct cg6map_pvt *pvt;
/*
* create the private data portion of the devmap object
*/
pvt = kmem_zalloc(sizeof (struct cg6map_pvt), KM_SLEEP);
pvt->type = type;
pvt->offset = off;
pvt->len = len;
pvt->context = ctx;
pvt->softc = softc;
/*
* Link this pvt into the list of associated pvt's for this
* context
*/
pvt->next = ctx->pvt;
ctx->pvt = pvt;
return (pvt);
}
/*
* This routine is called through the cb_ops table to handle
* the creation of lego (cg6) segments.
*/
/*ARGSUSED*/
static int
cg6_segmap(dev_t dev,
off_t off,
struct as *as,
caddr_t *addrp,
off_t len,
uint_t prot,
uint_t maxprot,
uint_t flags,
cred_t *cred)
{
struct cg6_softc *softc = getsoftc(getminor(dev));
int error;
DEBUGF(3, (CE_CONT, "segmap: off=%x, len=%x\n", off, len));
mutex_enter(&softc->mutex);
/*
* check to see if this is a VRT page
*/
if (off == CG6_VADDR_VRT) {
if (len != pagesize) {
mutex_exit(&softc->mutex);
DEBUGF(3, (CE_CONT,
"rejecting because off=vrt and len=%x\n", len))
return (EINVAL);
}
if (softc->vrtmaps++ == 0) {
if (softc->vrtpage == NULL) {
softc->vrtpage = (int *)ddi_umem_alloc(
pagesize, KM_SLEEP,
(void **)&softc->vrtcookie);
}
*softc->vrtpage = 0;
softc->vrtflag |= CG6VRTCTR;
cg6_int_enable(softc);
}
}
/*
* use the devmap framework for setting up the user mapping.
*/
error = devmap_setup(dev, (offset_t)off, as, addrp, (size_t)len, prot,
maxprot, flags, cred);
mutex_exit(&softc->mutex);
return (error);
}
/* ARGSUSED */
static int
cg6map_map(devmap_cookie_t dhp, dev_t dev, uint_t flags, offset_t off,
size_t len, void **pvtp)
{
struct cg6_softc *softc = getsoftc(getminor(dev));
struct cg6_cntxt *ctx = (struct cg6_cntxt *)NULL;
struct cg6_cntxt *shared_ctx = &softc->shared_ctx;
struct cg6map_pvt *pvt;
uint_t maptype = 0;
DEBUGF(3, (CE_CONT, "cg6map_map: off = %x, len = %x\n",
(uint_t)off, (uint_t)len));
/*
* 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 registers 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, but since they don't map the chips, there's
* no context to switch.
*/
if (off + len > CG6_VADDR_FBC && off < CG6_VADDR_FBC + CG6_FBCTEC_SZ)
maptype |= CG6MAP_FBCTEC;
if (off + len > CG6_VADDR_COLOR && off < CG6_VADDR_COLOR + CG6_FB_SZ)
maptype |= CG6MAP_FB;
/*
* 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.
*/
if (softc->chiprev == 5 && (maptype & CG6MAP_FB))
flags = (flags & ~MAP_TYPE) | MAP_PRIVATE;
if (flags & MAP_SHARED) { /* shared mapping */
ctx = shared_ctx;
ctx->flag = CG6MAP_CTX;
} else {
ctx = ctx_map_insert(softc, maptype);
ctx->flag |= maptype;
DEBUGF(2, (CE_CONT, "cg6map_map: ** MAP_PRIVATE **. ctx = %x\n",
ctx));
}
pvt = cg6_pvt_alloc(ctx, maptype, off, len, softc);
pvt->dhp = dhp;
*pvtp = pvt;
devmap_set_ctx_timeout(dhp, cg6_ctxholdval);
return (DDI_SUCCESS);
}
/*
* An access has been made to a context other than the current one
*/
/* ARGSUSED */
static int
cg6map_access(devmap_cookie_t dhp, void *pvt, offset_t offset, size_t len,
uint_t type, uint_t rw)
{
return (devmap_do_ctxmgt(dhp, pvt, offset, len, type, rw,
cg6map_contextmgt));
}
/*
* called by the devmap framework to perform context switching.
*/
/* ARGSUSED */
static int
cg6map_contextmgt(devmap_cookie_t dhp, void *pvt, offset_t offset,
size_t len, uint_t type, uint_t rw)
{
struct cg6map_pvt *p = (struct cg6map_pvt *)pvt;
struct cg6map_pvt *pvts;
struct cg6_softc *softc = p->softc;
volatile struct fbc *fbc;
int err = 0;
ASSERT(pvt);
mutex_enter(&softc->mutex);
DEBUGF(6, (CE_CONT, "cg6map_contextmgt: pvt = %x, dhp = %x, \
curctx = %x, context = %x\n",
p, dhp, softc->curctx, p->context));
/*
* Do we need to switch contexts?
*/
if (softc->curctx != p->context) {
fbc = S_FBC(softc);
/*
* If there's a current context, save it
*/
if (softc->curctx != (struct cg6_cntxt *)NULL) {
/*
* Set segdev for current context and all associated
* handles to intercept references to their addresses
*/
ASSERT(softc->curctx->pvt);
for (pvts = softc->curctx->pvt; pvts != NULL;
pvts = pvts->next) {
err = devmap_unload(pvts->dhp, pvts->offset,
pvts->len);
if (err) {
mutex_exit(&softc->mutex);
return (err);
}
}
if (cg6_cntxsave(fbc, S_TEC(softc),
softc->curctx) == 0) {
DEBUGF(1, (CE_CONT,
"cgsix: context save failed\n"));
/*
* At this point we have no current context.
*/
softc->curctx = NULL;
mutex_exit(&softc->mutex);
return (-1);
}
}
/*
* Idle the chips
*/
CG6DELAY(!(fbc->l_fbc_status & L_FBC_BUSY), CG6_FBC_WAIT);
if (fbc->l_fbc_status & L_FBC_BUSY) {
DEBUGF(1, (CE_CONT, "cgsix: idle_cg6: status = %x\n",
fbc->l_fbc_status));
/*
* At this point we have no current context.
*/
softc->curctx = NULL;
mutex_exit(&softc->mutex);
return (-1);
}
DEBUGF(4, (CE_CONT, "loading context %x\n", p->context));
if (p->context->flag & CG6MAP_FBCTEC)
if (cg6_cntxrestore(fbc, S_TEC(softc),
p->context) == 0) {
DEBUGF(1, (CE_CONT,
"cgsix: context restore failed\n"));
/*
* At this point we have no current context.
*/
softc->curctx = NULL;
mutex_exit(&softc->mutex);
return (-1);
}
/*
* switch software "context"
*/
softc->curctx = p->context;
}
ASSERT(p->context->pvt);
if ((type == DEVMAP_LOCK) || (type == DEVMAP_UNLOCK)) {
if ((err = devmap_load(p->dhp, offset, len, type, rw)) != 0) {
mutex_exit(&softc->mutex);
return (err);
}
} else {
if ((err = devmap_load(p->dhp, p->offset, p->len, type,
rw)) != 0) {
mutex_exit(&softc->mutex);
return (err);
}
}
mutex_exit(&softc->mutex);
return (err);
}
/* ARGSUSED */
static void
cg6map_unmap(devmap_cookie_t dhp, void *pvtp, offset_t off, size_t len,
devmap_cookie_t new_dhp1, void **pvtp1,
devmap_cookie_t new_dhp2, void **pvtp2)
{
struct cg6map_pvt *p = (struct cg6map_pvt *)pvtp;
struct cg6_softc *softc = p->softc;
struct cg6_cntxt *ctx = p->context;
struct cg6map_pvt *ptmp;
struct cg6map_pvt *ppvts;
struct cg6_cntxt *shared_ctx = &softc->shared_ctx;
size_t length;
DEBUGF(3, (CE_CONT, "cg6map_unmap: pvt = %x, dhp = %x, \
off = %x, len = %x, dhp1 = %x, dhp2 = %x\n",
pvtp, dhp, (uint_t)off, len, new_dhp1, new_dhp2));
mutex_enter(&softc->mutex);
/*
* We are unmapping at the end of the mapping, if
* new_dhp1 is not NULL.
*/
if (new_dhp1 != NULL) {
ptmp = cg6_pvt_alloc(ctx, p->type,
p->offset,
(off - p->offset),
softc);
ptmp->dhp = new_dhp1;
*pvtp1 = ptmp;
}
/*
* We are unmapping at the beginning of the mapping, if
* new_dhp2 is not NULL.
*/
if (new_dhp2 != NULL) {
length = p->len - len - (off - p->offset);
ptmp = cg6_pvt_alloc(ctx, p->type, (off + len), length, softc);
ptmp->dhp = new_dhp2;
*pvtp2 = ptmp;
}
/*
* Remove the original pvt data
*/
ppvts = NULL;
for (ptmp = ctx->pvt; ptmp != NULL; ptmp = ptmp->next) {
if (ptmp == pvtp) {
if (ppvts == NULL) {
ctx->pvt = ptmp->next;
} else {
ppvts->next = ptmp->next;
}
kmem_free(pvtp, sizeof (struct cg6map_pvt));
break;
}
ppvts = ptmp;
}
/*
* We want to remove the conext if both new_dhp1 and new_dhp2 are NULL.
*/
if (new_dhp1 == NULL && new_dhp2 == NULL) {
/*
* Remove the context if this is not the shared context
* xand there are no more associated pvt's
*/
if ((ctx != shared_ctx) && (ctx->pvt == NULL)) {
struct cg6_cntxt *ctxptr;
if (ctx == softc->curctx)
softc->curctx = NULL;
/*
* Scan private context list for entry to remove.
* Check first to see if it's the head of our list.
*/
if (softc->pvt_ctx == ctx) {
softc->pvt_ctx = ctx->link;
kmem_free(ctx, sizeof (struct cg6_cntxt));
} else {
for (ctxptr = softc->pvt_ctx; ctxptr != NULL;
ctxptr = ctxptr->link) {
if (ctxptr->link == ctx) {
ctxptr->link = ctx->link;
kmem_free(ctx,
sizeof (struct cg6_cntxt));
}
}
}
}
/*
* If the curctx is the shared context, and there are no
* more pvt's for the shared context, set the curctx to
* NULL to force a context switch on the next device access.
*/
if ((softc->curctx == shared_ctx) && (softc->curctx->pvt ==
NULL)) {
softc->curctx = NULL;
}
}
mutex_exit(&softc->mutex);
}
/* ARGSUSED */
static int
cg6map_dup(devmap_cookie_t dhp, void *oldpvt, devmap_cookie_t new_dhp,
void **newpvt)
{
struct cg6map_pvt *p = (struct cg6map_pvt *)oldpvt;
struct cg6_softc *softc = p->softc;
struct cg6map_pvt *pvt;
struct cg6_cntxt *ctx;
uint_t maptype;
DEBUGF(3, (CE_CONT, "cg6map_dup: pvt=%x, dhp=%x, newdhp=%x\n",
oldpvt, dhp, new_dhp));
mutex_enter(&softc->mutex);
if (p->context != &softc->shared_ctx) {
maptype = p->type;
ctx = ctx_map_insert(softc, maptype);
} else
ctx = &softc->shared_ctx;
pvt = cg6_pvt_alloc(ctx, p->type, p->offset, p->len, softc);
pvt->dhp = new_dhp;
*newpvt = pvt;
if (p->context && (p->context->flag & CG6MAP_VRT)) {
softc->vrtflag |= CG6VRTCTR;
if (softc->vrtmaps == 0)
cg6_int_enable(softc);
softc->vrtmaps++;
}
mutex_exit(&softc->mutex);
return (0);
}
/*
* please don't mess with these defines... they may look like
* a strange place for defines, but the context management code
* wants them as they are. JMP
*
*/
#undef L_TEC_VDC_INTRNL0
#define L_TEC_VDC_INTRNL0 0x8000
#undef L_TEC_VDC_INTRNL1
#define L_TEC_VDC_INTRNL1 0xa000
static int
cg6_cntxsave(fbc, tec, saved)
volatile struct fbc *fbc;
volatile struct tec *tec;
struct cg6_cntxt *saved;
{
int dreg; /* counts through the data registers */
uint_t *dp; /* points to a tec data register */
DEBUGF(5, (CE_CONT, "saving registers for %d\n", saved->pid));
CDELAY(!(fbc->l_fbc_status & L_FBC_BUSY), CG6_FBC_WAIT);
if (fbc->l_fbc_status & L_FBC_BUSY) {
DEBUGF(1, (CE_CONT, "cgsix: idle_cg6: status = %x\n",
fbc->l_fbc_status));
return (0);
}
/*
* 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 = (uint_t)L_TEC_VDC_INTRNL0;
for (dreg = 0, dp = (uint_t *)&tec->l_tec_data00; dreg < 64;
dreg++, dp++) {
saved->tec.data[dreg][0] = *dp;
}
tec->l_tec_vdc = (uint_t)L_TEC_VDC_INTRNL1;
for (dreg = 0, dp = (uint_t *)&tec->l_tec_data00; dreg < 64;
dreg++, dp++) {
saved->tec.data[dreg][1] = *dp;
}
return (1);
}
static int
cg6_cntxrestore(fbc, tec, saved)
volatile struct fbc *fbc;
volatile struct tec *tec;
struct cg6_cntxt *saved;
{
int dreg;
uint_t *dp;
DEBUGF(5, (CE_CONT, "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 = (uint_t)L_TEC_VDC_INTRNL0;
for (dreg = 0, dp = (uint_t *)&tec->l_tec_data00;
dreg < 64; dreg++, dp++) {
*dp = saved->tec.data[dreg][0];
}
tec->l_tec_vdc = (uint_t)L_TEC_VDC_INTRNL1;
for (dreg = 0, dp = (uint_t *)&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);
}
/*
* ctx_map_insert()
*
* Insert a mapping into the mapping list of a private context. First
* determine if there's an existing context (e.g. one with the same PID
* as the current one and that does not already have a mapping of this
* type yet). If not, allocate a new one. Then insert mapping into this
* context's list.
*
* The softc mutex must be held across calls to this routine.
*/
static
struct cg6_cntxt *
ctx_map_insert(struct cg6_softc *softc, int maptype)
{
struct cg6_cntxt *ctx;
pid_t curpid = getpid();
DEBUGF(4, (CE_CONT, "ctx_map_insert: maptype=0x%x curpid=%d\n",
maptype, curpid));
/*
* If this is the first time we're here, then alloc space
* for new context and depart.
*/
if (softc->pvt_ctx == NULL) {
ctx = (struct cg6_cntxt *)
kmem_zalloc(sizeof (struct cg6_cntxt), KM_SLEEP);
ctx->pid = curpid;
ctx->link = NULL;
softc->pvt_ctx = ctx;
return (ctx);
}
/*
* Find existing context if one exists. We have a match if
* we're the same process *and* there's not already a
* mapping of this type assigned.
*/
for (ctx = softc->pvt_ctx; ctx != NULL; ctx = ctx->link) {
if (ctx->pid == curpid &&
(maptype & ctx->flag & (CG6MAP_FBCTEC|CG6MAP_FB)) == 0)
break;
}
/* no match, create a new one and add to softc list */
if (ctx == NULL) {
ctx = (struct cg6_cntxt *)
kmem_zalloc(sizeof (struct cg6_cntxt), KM_SLEEP);
ctx->pid = curpid;
ctx->link = softc->pvt_ctx;
softc->pvt_ctx = ctx;
}
DEBUGF(4, (CE_CONT, "ctx_map_insert: returning ctx=0x%x\n", ctx));
return (ctx);
}
/*
* getpid()
*
* Simple wrapper around process ID call to drv_getparm(9f).
*/
static pid_t
getpid()
{
pid_t mypid;
if (drv_getparm(PPID, &mypid) == -1)
return (0);
return (mypid);
}
#define DEVMEMORY 1
#define KERNELMEMORY 2
/*ARGSUSED*/
static int
cg6_devmap(dev_t dev, devmap_cookie_t dhp, offset_t off, size_t len,
size_t *maplen, uint_t model)
{
struct cg6_softc *softc = getsoftc(getminor(dev));
dev_info_t *dip = softc->devi;
ssize_t diff;
int err = 0;
caddr_t kvaddr = NULL;
ddi_umem_cookie_t cookie = NULL;
offset_t offset = 0;
uint_t rnumber = 0;
uint_t type = DEVMEMORY;
size_t length = len;
uint_t map_type = 0;
uint_t ctxmap = 0;
struct devmap_callback_ctl *callbackops = &cg6map_ops;
DEBUGF(2, (CE_CONT, "cg6_devmap(%d), off=0x%x, len=%x, dhp=%x\n",
getminor(dev), (uint_t)off, len, dhp));
if ((diff = off - CG6_VADDR_COLOR) >= 0 && diff < softc->fbmappable) {
if ((len + off) > (CG6_VADDR_COLOR + softc->fbmappable))
length = CG6_VADDR_COLOR + softc->fbmappable - off;
offset = CG6_ADDR_COLOR + diff;
map_type = CG6MAP_FB;
} else if ((diff = off - CG6_VADDR_FBC) >= 0 && diff < CG6_FBCTEC_SZ) {
if ((len + off) > (CG6_VADDR_FBC + CG6_FBCTEC_SZ))
length = (CG6_VADDR_FBC + CG6_FBCTEC_SZ) - off;
offset = CG6_ADDR_FBC + diff;
map_type = CG6MAP_FBCTEC;
} else if ((diff = off - CG6_VADDR_CMAP) >= 0 && diff < CG6_CMAP_SZ) {
if ((len + off) > (CG6_VADDR_CMAP + CG6_CMAP_SZ))
length = (CG6_VADDR_CMAP + CG6_CMAP_SZ) - off;
offset = CG6_ADDR_CMAP + diff;
} else if ((diff = off - CG6_VADDR_FHC) >= 0 && diff < CG6_FHCTHC_SZ) {
if ((len + off) > (CG6_VADDR_FHC + CG6_FHCTHC_SZ))
length = (CG6_VADDR_FHC + CG6_FHCTHC_SZ) - off;
offset = CG6_ADDR_FHC + diff;
} else if ((diff = off - CG6_VADDR_ROM) >= 0 && diff < CG6_ROM_SZ) {
if ((len + off) > (CG6_VADDR_ROM + CG6_ROM_SZ))
length = (CG6_VADDR_ROM + CG6_ROM_SZ) - off;
offset = softc->addr_rom + diff;
} else if ((diff = off - CG6_VADDR_DHC) >= 0 && diff < CG6_DHC_SZ) {
if ((len + off) > (CG6_VADDR_DHC + CG6_DHC_SZ))
length = (CG6_VADDR_DHC + CG6_DHC_SZ) - off;
offset = CG6_ADDR_DHC + diff;
} else if ((diff = off - CG6_VADDR_ALT) >= 0 && diff < CG6_ALT_SZ) {
if ((len + off) > (CG6_VADDR_ALT + CG6_ALT_SZ))
length = (CG6_VADDR_ALT + CG6_ALT_SZ) - off;
offset = CG6_ADDR_ALT + diff;
} else if ((diff = off - CG6_VADDR_VRT) >= 0 && diff < CG6_VRT_SZ) {
if ((len + off) > (CG6_VADDR_VRT + CG6_VRT_SZ))
length = (CG6_VADDR_VRT + CG6_VRT_SZ) - off;
type = KERNELMEMORY;
if (softc->vrtpage != NULL)
offset = diff;
else
kvaddr = (caddr_t)-1;
cookie = softc->vrtcookie;
} else if ((diff = off - CG3_MMAP_OFFSET) >= 0 &&
diff < softc->fbmappable) {
if ((len + off) > (CG3_MMAP_OFFSET + softc->fbmappable))
length = CG3_MMAP_OFFSET + softc->fbmappable - off;
offset = CG6_ADDR_COLOR + diff;
} else if (off < CG6_VBASE) {
if (softc->emulation == FBTYPE_SUN3COLOR) {
if (off >= 0 && off < softc->fbmappable) {
if ((len + off) > softc->fbmappable)
length = softc->fbmappable - off;
offset = CG6_ADDR_COLOR + diff;
} else
kvaddr = (caddr_t)-1;
} else { /* softc->emulation == FBTYPE_SUN4COLOR */
if (off >= 0 && off < softc->dummysize) {
if ((len + off) > softc->dummysize)
length = softc->dummysize - off;
offset = CG6_ADDR_COLOR + diff;
} else if ((diff = off - softc->dummysize) <
softc->fbmappable) {
if ((len + off) >
(softc->dummysize + softc->fbmappable))
length = softc->fbmappable - off;
offset = CG6_ADDR_COLOR + diff;
}
}
} else
kvaddr = (caddr_t)-1;
if (kvaddr == (caddr_t)-1) {
DEBUGF(1, (CE_CONT, "cg6_devmap: no mapping off=0x%x, len=%x\n",
(uint_t)off, len));
return (-1);
}
DEBUGF(2, (CE_CONT, "cg6_devmap: offset=0x%x, kvaddr=%x, length=%x\n",
(uint_t)offset, kvaddr, length));
/*
* 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 registers 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, but since they don't map the chips, there's
* no context to switch.
*/
ctxmap = (softc->chiprev == 5) ?
(CG6MAP_FBCTEC|CG6MAP_FB) : CG6MAP_FBCTEC;
/*
* do context switching on the TEC and FBC registers.
*/
if (map_type & ctxmap)
callbackops = &cg6map_ops;
else
callbackops = NULL;
if (type == DEVMEMORY) {
if ((err = devmap_devmem_setup(dhp, dip, callbackops, rnumber,
offset, length, PROT_ALL, DEVMAP_DEFAULTS,
&endian_attr)) < 0)
return (err);
} else {
if ((err = devmap_umem_setup(dhp, dip, callbackops, cookie,
offset, length, PROT_ALL, DEVMAP_DEFAULTS,
&endian_attr)) < 0)
return (err);
}
*maplen = roundup(length, PAGESIZE);
return (0);
}