root/sys/arch/vax/vsa/smg.c
/*      $NetBSD: smg.c,v 1.67 2024/02/03 16:35:10 tsutsui Exp $ */
/*      $OpenBSD: smg.c,v 1.28 2014/12/23 21:39:12 miod Exp $   */
/*
 * Copyright (c) 2006, Miodrag Vallat
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/*-
 * Copyright (c) 2000 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Tohru Nishimura.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Copyright (c) 1998 Ludd, University of Lule}, Sweden.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * Copyright (c) 1996 Jason R. Thorpe.  All rights reserved.
 * Copyright (c) 1991 University of Utah.
 * Copyright (c) 1990, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the Systems Programming Group of the University of Utah Computer
 * Science Department and Mark Davies of the Department of Computer
 * Science, Victoria University of Wellington, New Zealand.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * from: Utah $Hdr: grf_hy.c 1.2 93/08/13$
 *
 *      @(#)grf_hy.c    8.4 (Berkeley) 1/12/94
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: smg.c,v 1.67 2024/02/03 16:35:10 tsutsui Exp $");

#include "dzkbd.h"
#include "wsdisplay.h"

#include <sys/param.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/conf.h>

#include <machine/vsbus.h>
#include <machine/sid.h>
#include <machine/cpu.h>
#include <machine/ka420.h>
#include <machine/scb.h>

#include <dev/cons.h>

#include <dev/ic/dc503reg.h>

#include <dev/dec/dzreg.h>
#include <dev/dec/dzvar.h>
#include <dev/dec/dzkbdvar.h>

#include <dev/wscons/wsconsio.h>
#include <dev/wscons/wsdisplayvar.h>
#include <dev/rasops/rasops.h>

/* Screen hardware defs */
#define SM_XWIDTH       1024
#define SM_YWIDTH       864

#define CUR_XBIAS       216     /* Add to cursor position */
#define CUR_YBIAS       33

struct  smg_screen {
        struct rasops_info ss_ri;
        uint8_t         *ss_addr;               /* frame buffer address */
        struct dc503reg *ss_cursor;             /* cursor registers */
        uint16_t        ss_curcmd;
        struct wsdisplay_curpos ss_curpos, ss_curhot;
        uint16_t        ss_curimg[PCC_CURSOR_SIZE];
        uint16_t        ss_curmask[PCC_CURSOR_SIZE];
};

struct  smg_softc {
        device_t sc_dev;
        struct smg_screen *sc_scr;
        int     sc_nscreens;
};

static int      smg_match(device_t, cfdata_t, void *);
static void     smg_attach(device_t, device_t, void *);

static int smg_setup_screen(struct smg_screen *);

static int smg_ioctl(void *, void *, u_long, void *, int, struct lwp *);
static paddr_t smg_mmap(void *, void *, off_t, int);
static int smg_alloc_screen(void *, const struct wsscreen_descr *,
    void **, int *, int *, long *);
static void smg_free_screen(void *, void *);
static int smg_show_screen(void *, void *, int, void (*) (void *, int, int),
    void *);

static int smg_getcursor(struct smg_screen *, struct wsdisplay_cursor *);
static int smg_setcursor(struct smg_screen *, struct wsdisplay_cursor *);
static void smg_updatecursor(struct smg_screen *, u_int);

static void smg_putchar(void *, int, int, u_int, long);
static void smg_cursor(void *, int, int, int);
static void smg_blockmove(struct rasops_info *, u_int, u_int, u_int, u_int,
    u_int, int);
static void smg_copycols(void *, int, int, int, int);
static void smg_erasecols(void *, int, int, int, long);

/* for console */
static struct smg_screen smg_consscr;

CFATTACH_DECL_NEW(smg, sizeof(struct smg_softc),
    smg_match, smg_attach, NULL, NULL);

static struct wsscreen_descr smg_stdscreen = {
        .name = "std",
};

static const struct wsscreen_descr *_smg_scrlist[] = {
        &smg_stdscreen,
};

static const struct wsscreen_list smg_screenlist = {
        .nscreens = sizeof(_smg_scrlist) / sizeof(struct wsscreen_descr *),
        .screens = _smg_scrlist,
};

static const struct wsdisplay_accessops smg_accessops = {
        .ioctl = smg_ioctl,
        .mmap = smg_mmap,
        .alloc_screen = smg_alloc_screen,
        .free_screen = smg_free_screen,
        .show_screen = smg_show_screen,
        .load_font = NULL
};

static int
smg_match(device_t parent, cfdata_t cf, void *aux)
{
        struct vsbus_attach_args *va = aux;
        volatile short *curcmd;
        volatile short *cfgtst;
        short tmp, tmp2;

        switch (vax_boardtype) {
        default:
                return 0;

        case VAX_BTYP_410:
        case VAX_BTYP_420:
        case VAX_BTYP_43:
                if (va->va_paddr != KA420_CUR_BASE)
                        return 0;

                /* not present on microvaxes */
                if ((vax_confdata & KA420_CFG_MULTU) != 0)
                        return 0;

                /*
                 * If the color option board is present, do not attach
                 * unless we are explicitely asked to via device flags.
                 */
                if ((vax_confdata & KA420_CFG_VIDOPT) != 0 &&
                    (cf->cf_flags & 1) == 0)
                        return 0;
                break;
        }

        /* when already running as console, always fake things */
        if ((vax_confdata & (KA420_CFG_L3CON | KA420_CFG_VIDOPT)) == 0
#if NWSDISPLAY > 0
            && cn_tab->cn_putc == wsdisplay_cnputc
#endif
        ) {
                struct vsbus_softc *sc = device_private(parent);

                sc->sc_mask = 0x08;
                scb_fake(0x44, 0x15);
                return 20;
        } else {
                /*
                 * Try to find the cursor chip by testing the flip-flop.
                 * If nonexistent, no glass tty.
                 */
                curcmd = (short *)va->va_addr;
                cfgtst = (short *)vax_map_physmem(VS_CFGTST, 1);
                curcmd[0] = PCCCMD_HSHI | PCCCMD_FOPB;
                DELAY(300000);
                tmp = cfgtst[0];
                curcmd[0] = PCCCMD_TEST | PCCCMD_HSHI;
                DELAY(300000);
                tmp2 = cfgtst[0];
                vax_unmap_physmem((vaddr_t)cfgtst, 1);

                if (tmp2 != tmp)
                        return 20; /* Using periodic interrupt */
                else
                        return 0;
        }
}

static void
smg_attach(device_t parent, device_t self, void *aux)
{
        struct smg_softc *sc = device_private(self);
        struct smg_screen *scr;
        struct wsemuldisplaydev_attach_args aa;
        int console;

        console =
#if NWSDISPLAY > 0
            (vax_confdata & (KA420_CFG_L3CON | KA420_CFG_VIDOPT)) == 0 &&
            cn_tab->cn_putc == wsdisplay_cnputc;
#else
            (vax_confdata & (KA420_CFG_L3CON | KA420_CFG_VIDOPT)) == 0;
#endif
        if (console) {
                scr = &smg_consscr;
                sc->sc_nscreens = 1;
        } else {
                scr = kmem_zalloc(sizeof(*scr), KM_SLEEP);

                scr->ss_addr =
                    (void *)vax_map_physmem(SMADDR, SMSIZE / VAX_NBPG);
                if (scr->ss_addr == NULL) {
                        aprint_error(": can not map frame buffer\n");
                        kmem_free(scr, sizeof(*scr));
                        return;
                }

                scr->ss_cursor =
                    (struct dc503reg *)vax_map_physmem(KA420_CUR_BASE, 1);
                if (scr->ss_cursor == NULL) {
                        aprint_error(": can not map cursor chip\n");
                        vax_unmap_physmem((vaddr_t)scr->ss_addr,
                            SMSIZE / VAX_NBPG);
                        kmem_free(scr, sizeof(*scr));
                        return;
                }

                if (smg_setup_screen(scr) != 0) {
                        aprint_error(": initialization failed\n");
                        vax_unmap_physmem((vaddr_t)scr->ss_cursor, 1);
                        vax_unmap_physmem((vaddr_t)scr->ss_addr,
                            SMSIZE / VAX_NBPG);
                        kmem_free(scr, sizeof(*scr));
                        return;
                }
        }
        sc->sc_scr = scr;

        aprint_normal("\n");
        aprint_normal_dev(self, "%dx%d on-board monochrome framebuffer\n",
            SM_XWIDTH, SM_YWIDTH);

        aa.console = console;
        aa.scrdata = &smg_screenlist;
        aa.accessops = &smg_accessops;
        aa.accesscookie = sc;

        config_found(self, &aa, wsemuldisplaydevprint, CFARGS_NONE);
}

/*
 * Initialize anything necessary for an emulating wsdisplay to work (i.e.
 * pick a font, initialize a rasops structure, setup the accessops callbacks.)
 */
static int
smg_setup_screen(struct smg_screen *ss)
{
        struct rasops_info *ri = &ss->ss_ri;
        int cookie;

        memset(ri, 0, sizeof(*ri));
        ri->ri_depth = 1;
        ri->ri_width = SM_XWIDTH;
        ri->ri_height = SM_YWIDTH;
        ri->ri_stride = SM_XWIDTH >> 3;
        ri->ri_flg = RI_CLEAR | RI_CENTER;
        ri->ri_bits = (void *)ss->ss_addr;
        ri->ri_hw = ss;
        if (ss == &smg_consscr)
                ri->ri_flg |= RI_NO_AUTO;

        wsfont_init();
        cookie = wsfont_find(NULL, 12, 0, 0, WSDISPLAY_FONTORDER_R2L,
            WSDISPLAY_FONTORDER_R2L, WSFONT_FIND_BITMAP);
        if (cookie < 0)
                cookie = wsfont_find(NULL, 8, 0, 0, WSDISPLAY_FONTORDER_R2L,
                    WSDISPLAY_FONTORDER_R2L, WSFONT_FIND_BITMAP);
        if (cookie < 0)
                cookie = wsfont_find(NULL, 0, 0, 0, WSDISPLAY_FONTORDER_R2L,
                    WSDISPLAY_FONTORDER_R2L, WSFONT_FIND_BITMAP);
        if (cookie < 0)
                return -1;
        if (wsfont_lock(cookie, &ri->ri_font) != 0)
                return -1;
        ri->ri_wsfcookie = cookie;

        /*
         * Ask for an unholy big display, rasops will trim this to more
         * reasonable values.
         */
        if (rasops_init(ri, 160, 160) != 0)
                return -1;

        ri->ri_ops.cursor = smg_cursor;
        ri->ri_ops.putchar = smg_putchar;
        ri->ri_ops.copycols = smg_copycols;
        ri->ri_ops.erasecols = smg_erasecols;

        smg_stdscreen.ncols = ri->ri_cols;
        smg_stdscreen.nrows = ri->ri_rows;
        smg_stdscreen.textops = &ri->ri_ops;
        smg_stdscreen.fontwidth = ri->ri_font->fontwidth;
        smg_stdscreen.fontheight = ri->ri_font->fontheight;
        smg_stdscreen.capabilities = ri->ri_caps;

        ss->ss_curcmd = PCCCMD_HSHI;
        ss->ss_cursor->cmdr = ss->ss_curcmd;

        return 0;
}

static int
smg_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, struct lwp *l)
{
        struct smg_softc *sc = v;
        struct smg_screen *ss = sc->sc_scr;
        struct wsdisplay_fbinfo *wdf;
        struct wsdisplay_curpos *pos;

        switch (cmd) {
        case WSDISPLAYIO_GTYPE:
                *(u_int *)data = WSDISPLAY_TYPE_VAX_MONO;
                break;

        case WSDISPLAYIO_GINFO:
                wdf = (struct wsdisplay_fbinfo *)data;
                wdf->height = ss->ss_ri.ri_height;
                wdf->width = ss->ss_ri.ri_width;
                wdf->depth = ss->ss_ri.ri_depth;
                wdf->cmsize = 0;
                break;

        case WSDISPLAYIO_LINEBYTES:
                *(u_int *)data = ss->ss_ri.ri_stride;
                break;

        case WSDISPLAYIO_GETCMAP:
        case WSDISPLAYIO_PUTCMAP:
        case WSDISPLAYIO_GVIDEO:
        case WSDISPLAYIO_SVIDEO:
                break;

        case WSDISPLAYIO_GCURPOS:
                pos = (struct wsdisplay_curpos *)data;
                pos->x = ss->ss_curpos.x;
                pos->y = ss->ss_curpos.y;
                break;

        case WSDISPLAYIO_SCURPOS:
                pos = (struct wsdisplay_curpos *)data;
                ss->ss_curpos.x = pos->x;
                ss->ss_curpos.y = pos->y;
                smg_updatecursor(ss, WSDISPLAY_CURSOR_DOPOS);
                break;

        case WSDISPLAYIO_GCURMAX:
                pos = (struct wsdisplay_curpos *)data;
                pos->x = pos->y = PCC_CURSOR_SIZE;

        case WSDISPLAYIO_GCURSOR:
                return smg_getcursor(ss, (struct wsdisplay_cursor *)data);

        case WSDISPLAYIO_SCURSOR:
                return smg_setcursor(ss, (struct wsdisplay_cursor *)data);

        default:
                return EPASSTHROUGH;
        }
        return 0;
}

static paddr_t
smg_mmap(void *v, void *vs, off_t offset, int prot)
{

        if (offset >= SMSIZE || offset < 0)
                return -1;

        return (SMADDR + offset) >> PGSHIFT;
}

static int
smg_alloc_screen(void *v, const struct wsscreen_descr *type, void **cookiep,
    int *curxp, int *curyp, long *defattrp)
{
        struct smg_softc *sc = v;
        struct smg_screen *ss = sc->sc_scr;
        struct rasops_info *ri = &ss->ss_ri;

        if (sc->sc_nscreens > 0)
                return ENOMEM;

        *cookiep = ri;
        *curxp = *curyp = 0;
        (*ri->ri_ops.allocattr)(ri, 0, 0, 0, defattrp);
        sc->sc_nscreens++;

        return 0;
}

static void
smg_free_screen(void *v, void *cookie)
{
        struct smg_softc *sc = v;

        sc->sc_nscreens--;
}

static int
smg_show_screen(void *v, void *cookie, int waitok,
    void (*cb)(void *, int, int), void *cbarg)
{

        return 0;
}

static int
smg_getcursor(struct smg_screen *ss, struct wsdisplay_cursor *wdc)
{
        int error;

        if ((wdc->which & WSDISPLAY_CURSOR_DOCUR) != 0)
                wdc->enable = ss->ss_curcmd & PCCCMD_ENPA ? 1 : 0;
        if ((wdc->which & WSDISPLAY_CURSOR_DOPOS) != 0) {
                wdc->pos.x = ss->ss_curpos.x;
                wdc->pos.y = ss->ss_curpos.y;
        }
        if ((wdc->which & WSDISPLAY_CURSOR_DOHOT) != 0) {
                wdc->hot.x = ss->ss_curhot.x;
                wdc->hot.y = ss->ss_curhot.y;
        }
        if ((wdc->which & WSDISPLAY_CURSOR_DOCMAP) != 0) {
                wdc->cmap.index = 0;
                wdc->cmap.count = 0;
        }
        if ((wdc->which & WSDISPLAY_CURSOR_DOSHAPE) != 0) {
                wdc->size.x = wdc->size.y = PCC_CURSOR_SIZE;
                error = copyout(ss->ss_curimg, wdc->image,
                    sizeof(ss->ss_curimg));
                if (error != 0)
                        return error;
                error = copyout(ss->ss_curmask, wdc->mask,
                    sizeof(ss->ss_curmask));
                if (error != 0)
                        return error;
        }

        return 0;
}

static int
smg_setcursor(struct smg_screen *ss, struct wsdisplay_cursor *wdc)
{
        uint16_t curfg[PCC_CURSOR_SIZE], curmask[PCC_CURSOR_SIZE];
        int error;

        if ((wdc->which & WSDISPLAY_CURSOR_DOCMAP) != 0) {
                /* No cursor colormap since we are a B&W device. */
                if (wdc->cmap.count != 0)
                        return EINVAL;
        }

        /*
         * First, do the userland-kernel data transfers, so that we can fail
         * if necessary before altering anything.
         */
        if ((wdc->which & WSDISPLAY_CURSOR_DOSHAPE) != 0) {
                if (wdc->size.x != PCC_CURSOR_SIZE ||
                    wdc->size.y != PCC_CURSOR_SIZE)
                        return EINVAL;
                error = copyin(wdc->image, curfg, sizeof(curfg));
                if (error != 0)
                        return error;
                error = copyin(wdc->mask, curmask, sizeof(curmask));
                if (error != 0)
                        return error;
        }

        /*
         * Now update our variables...
         */
        if ((wdc->which & WSDISPLAY_CURSOR_DOCUR) != 0) {
                if (wdc->enable)
                        ss->ss_curcmd |= PCCCMD_ENPB | PCCCMD_ENPA;
                else
                        ss->ss_curcmd &= ~(PCCCMD_ENPB | PCCCMD_ENPA);
        }
        if ((wdc->which & WSDISPLAY_CURSOR_DOPOS) != 0) {
                ss->ss_curpos.x = wdc->pos.x;
                ss->ss_curpos.y = wdc->pos.y;
        }
        if ((wdc->which & WSDISPLAY_CURSOR_DOHOT) != 0) {
                ss->ss_curhot.x = wdc->hot.x;
                ss->ss_curhot.y = wdc->hot.y;
        }
        if ((wdc->which & WSDISPLAY_CURSOR_DOSHAPE) != 0) {
                memcpy(ss->ss_curimg, curfg, sizeof(ss->ss_curimg));
                memcpy(ss->ss_curmask, curmask, sizeof(ss->ss_curmask));
        }

        /*
         * ...and update the cursor
         */
        smg_updatecursor(ss, wdc->which);

        return 0;
}

static void
smg_updatecursor(struct smg_screen *ss, u_int which)
{
        u_int i;

        if ((which & (WSDISPLAY_CURSOR_DOPOS | WSDISPLAY_CURSOR_DOHOT)) != 0) {
                ss->ss_cursor->xpos =
                    ss->ss_curpos.x - ss->ss_curhot.x + CUR_XBIAS;
                ss->ss_cursor->ypos =
                    ss->ss_curpos.y - ss->ss_curhot.y + CUR_YBIAS;
        }
        if ((which & WSDISPLAY_CURSOR_DOSHAPE) != 0) {
                ss->ss_cursor->cmdr = ss->ss_curcmd | PCCCMD_LODSA;
                for (i = 0; i < PCC_CURSOR_SIZE; i++)
                        ss->ss_cursor->load = ss->ss_curimg[i];
                for (i = 0; i < PCC_CURSOR_SIZE; i++)
                        ss->ss_cursor->load = ss->ss_curmask[i];
                ss->ss_cursor->cmdr = ss->ss_curcmd;
        } else
        if ((which & WSDISPLAY_CURSOR_DOCUR) != 0)
                ss->ss_cursor->cmdr = ss->ss_curcmd;
}

/*
 * Faster console operations
 */

#include <vax/vsa/maskbits.h>

/* putchar() and cursor() ops are taken from luna68k omrasops.c */

#define ALL1BITS        (~0U)
#define ALL0BITS        (0U)
#define BLITWIDTH       (32)
#define ALIGNMASK       (0x1f)
#define BYTESDONE       (4)

static void
smg_putchar(void *cookie, int row, int startcol, u_int uc, long attr)
{
        struct rasops_info *ri = cookie;
        uint8_t *p;
        int scanspan, startx, height, width, align, y;
        uint32_t lmask, rmask, glyph, inverse;
        int i;
        uint8_t *fb;

        scanspan = ri->ri_stride;
        y = ri->ri_font->fontheight * row;
        startx = ri->ri_font->fontwidth * startcol;
        height = ri->ri_font->fontheight;
        fb = (uint8_t *)ri->ri_font->data +
            (uc - ri->ri_font->firstchar) * ri->ri_fontscale;
        inverse = ((attr & WSATTR_REVERSE) != 0) ? ALL1BITS : ALL0BITS;

        p = (uint8_t *)ri->ri_bits + y * scanspan + ((startx / 32) * 4);
        align = startx & ALIGNMASK;
        width = ri->ri_font->fontwidth + align;
        /* lmask and rmask are in WSDISPLAY_FONTORDER_R2L bitorder */
        lmask = ALL1BITS << align;
        rmask = ALL1BITS >> (-width & ALIGNMASK);
        if (width <= BLITWIDTH) {
                uint32_t mask = lmask & rmask;
                while (height > 0) {
                        uint32_t image;
                        /*
                         * The font glyph is stored in byteorder and bitorder
                         * WSDISPLAY_FONTORDER_R2L to use proper shift ops.
                         * On the other hand, VRAM data is stored in
                         * WSDISPLAY_FONTORDER_R2L bitorder and
                         * WSDISPLAY_FONTORDER_L2R byteorder.
                         */
                        glyph = 0;
                        for (i = ri->ri_font->stride; i != 0; i--)
                                glyph = (glyph << 8) | *fb++;
                        glyph = (glyph << align) ^ inverse;
                        image = *(uint32_t *)p;
                        *(uint32_t *)p = (image & ~mask) | (glyph & mask);
                        p += scanspan;
                        height--;
                }
        } else {
                uint8_t *q = p;
                uint32_t lhalf, rhalf;

                while (height > 0) {
                        uint32_t image;
                        glyph = 0;
                        for (i = ri->ri_font->stride; i != 0; i--)
                                glyph = (glyph << 8) | *fb++;
                        lhalf = (glyph << align) ^ inverse;
                        image = *(uint32_t *)p;
                        *(uint32_t *)p = (image & ~lmask) | (lhalf & lmask);
                        p += BYTESDONE;
                        rhalf = (glyph >> (BLITWIDTH - align)) ^ inverse;
                        image = *(uint32_t *)p;
                        *(uint32_t *)p = (rhalf & rmask) | (image & ~rmask);

                        p = (q += scanspan);
                        height--;
                }
        }
}

static void
smg_cursor(void *cookie, int on, int row, int col)
{
        struct rasops_info *ri = cookie;
        uint8_t *p;
        int scanspan, startx, height, width, align, y;
        uint32_t lmask, rmask, image;

        if (!on) {
                /* make sure it's on */
                if ((ri->ri_flg & RI_CURSOR) == 0)
                        return;

                row = ri->ri_crow;
                col = ri->ri_ccol;
        } else {
                /* unpaint the old copy. */
                ri->ri_crow = row;
                ri->ri_ccol = col;
        }

        scanspan = ri->ri_stride;
        y = ri->ri_font->fontheight * row;
        startx = ri->ri_font->fontwidth * col;
        height = ri->ri_font->fontheight;

        p = (uint8_t *)ri->ri_bits + y * scanspan + ((startx / 32) * 4);
        align = startx & ALIGNMASK;
        width = ri->ri_font->fontwidth + align;
        /* lmask and rmask are in WSDISPLAY_FONTORDER_R2L bitorder */
        lmask = ALL1BITS << align;
        rmask = ALL1BITS >> (-width & ALIGNMASK);
        if (width <= BLITWIDTH) {
                uint32_t mask = lmask & rmask;
                while (height > 0) {
                        image = *(uint32_t *)p;
                        *(uint32_t *)p =
                            (image & ~mask) | ((image ^ ALL1BITS) & mask);
                        p += scanspan;
                        height--;
                }
        } else {
                uint8_t *q = p;

                while (height > 0) {
                        image = *(uint32_t *)p;
                        *(uint32_t *)p =
                            (image & ~lmask) | ((image ^ ALL1BITS) & lmask);
                        p += BYTESDONE;
                        image = *(uint32_t *)p;
                        *(uint32_t *)p =
                            ((image ^ ALL1BITS) & rmask) | (image & ~rmask);

                        p = (q += scanspan);
                        height--;
                }
        }
        ri->ri_flg ^= RI_CURSOR;
}

static void
smg_blockmove(struct rasops_info *ri, u_int sx, u_int y, u_int dx, u_int cx,
    u_int cy, int rop)
{
        int width;              /* add to get to same position in next line */

        unsigned int *psrcLine, *pdstLine;
                                /* pointers to line with current src and dst */
        unsigned int *psrc;     /* pointer to current src longword */
        unsigned int *pdst;     /* pointer to current dst longword */

                                /* following used for looping through a line */
        unsigned int startmask, endmask;  /* masks for writing ends of dst */
        int nlMiddle;           /* whole longwords in dst */
        int nl;                 /* temp copy of nlMiddle */
        int xoffSrc;            /* offset (>= 0, < 32) from which to
                                   fetch whole longwords fetched in src */
        int nstart;             /* number of ragged bits at start of dst */
        int nend;               /* number of ragged bits at end of dst */
        int srcStartOver;       /* pulling nstart bits from src
                                   overflows into the next word? */

        width = SM_XWIDTH >> 5;

        /* start at first scanline */
        psrcLine = pdstLine = ((u_int *)ri->ri_bits) + (y * width);

        /* x direction doesn't matter for < 1 longword */
        if (cx <= 32) {
                int srcBit, dstBit;     /* bit offset of src and dst */

                pdstLine += (dx >> 5);
                psrcLine += (sx >> 5);
                psrc = psrcLine;
                pdst = pdstLine;

                srcBit = sx & ALIGNMASK;
                dstBit = dx & ALIGNMASK;

                while (cy--) {
                        getandputrop(psrc, srcBit, dstBit, cx, pdst, rop);
                        pdst += width;
                        psrc += width;
                }
        } else {
                startmask = ALL1BITS << (dx & ALIGNMASK);
                endmask   = ALL1BITS >> (~cx & ALIGNMASK);
                if (startmask)
                        nlMiddle = (cx - (32 - (dx & ALIGNMASK))) >> 5;
                else
                        nlMiddle = cx >> 5;

                if (startmask)
                        nstart = 32 - (dx & ALIGNMASK);
                else
                        nstart = 0;
                if (endmask)
                        nend = (dx + cx) & ALIGNMASK;
                else
                        nend = 0;

                xoffSrc = ((sx & ALIGNMASK) + nstart) & ALIGNMASK;
                srcStartOver = ((sx & ALIGNMASK) + nstart) > 31;

                if (sx >= dx) { /* move left to right */
                        pdstLine += (dx >> 5);
                        psrcLine += (sx >> 5);

                        while (cy--) {
                                psrc = psrcLine;
                                pdst = pdstLine;

                                if (startmask) {
                                        getandputrop(psrc, (sx & ALIGNMASK),
                                            (dx & ALIGNMASK), nstart, pdst,
                                            rop);
                                        pdst++;
                                        if (srcStartOver)
                                                psrc++;
                                }

                                /* special case for aligned operations */
                                if (xoffSrc == 0) {
                                        nl = nlMiddle;
                                        while (nl--) {
                                                switch (rop) {
                                                case RR_CLEAR:
                                                        *pdst = 0;
                                                        break;
                                                case RR_SET:
                                                        *pdst = ~0;
                                                        break;
                                                default:
                                                        *pdst = *psrc;
                                                        break;
                                                }
                                                psrc++;
                                                pdst++;
                                        }
                                } else {
                                        nl = nlMiddle + 1;
                                        while (--nl) {
                                                switch (rop) {
                                                case RR_CLEAR:
                                                        *pdst = 0;
                                                        break;
                                                case RR_SET:
                                                        *pdst = ~0;
                                                        break;
                                                default:
                                                        getunalignedword(psrc,
                                                            xoffSrc, *pdst);
                                                        break;
                                                }
                                                pdst++;
                                                psrc++;
                                        }
                                }

                                if (endmask) {
                                        getandputrop(psrc, xoffSrc, 0, nend,
                                            pdst, rop);
                                }

                                pdstLine += width;
                                psrcLine += width;
                        }
                } else {        /* move right to left */
                        pdstLine += ((dx + cx) >> 5);
                        psrcLine += ((sx + cx) >> 5);
                        /*
                         * If fetch of last partial bits from source crosses
                         * a longword boundary, start at the previous longword
                         */
                        if (xoffSrc + nend >= 32)
                                --psrcLine;

                        while (cy--) {
                                psrc = psrcLine;
                                pdst = pdstLine;

                                if (endmask) {
                                        getandputrop(psrc, xoffSrc, 0, nend,
                                            pdst, rop);
                                }

                                nl = nlMiddle + 1;
                                while (--nl) {
                                        --psrc;
                                        --pdst;
                                        switch (rop) {
                                        case RR_CLEAR:
                                                *pdst = 0;
                                                break;
                                        case RR_SET:
                                                *pdst = ~0;
                                                break;
                                        default:
                                                getunalignedword(psrc, xoffSrc,
                                                    *pdst);
                                                break;
                                        }
                                }

                                if (startmask) {
                                        if (srcStartOver)
                                                --psrc;
                                        --pdst;
                                        getandputrop(psrc, (sx & ALIGNMASK),
                                            (dx & ALIGNMASK), nstart, pdst,
                                            rop);
                                }

                                pdstLine += width;
                                psrcLine += width;
                        }
                }
        }
}

static void
smg_copycols(void *cookie, int row, int src, int dst, int n)
{
        struct rasops_info *ri = cookie;

        n *= ri->ri_font->fontwidth;
        src *= ri->ri_font->fontwidth;
        dst *= ri->ri_font->fontwidth;
        row *= ri->ri_font->fontheight;

        smg_blockmove(ri, src, row, dst, n, ri->ri_font->fontheight,
            RR_COPY);
}

static void
smg_erasecols(void *cookie, int row, int col, int num, long attr)
{
        struct rasops_info *ri = cookie;
        int fg, bg;

        rasops_unpack_attr(attr, &fg, &bg, NULL);

        num *= ri->ri_font->fontwidth;
        col *= ri->ri_font->fontwidth;
        row *= ri->ri_font->fontheight;

        smg_blockmove(ri, col, row, col, num, ri->ri_font->fontheight,
            bg == 0 ? RR_CLEAR : RR_SET);
}

/*
 * Console support code
 */

cons_decl(smg);

void
smgcnprobe(struct consdev *cndev)
{
        extern const struct cdevsw wsdisplay_cdevsw;

        switch (vax_boardtype) {
        case VAX_BTYP_410:
        case VAX_BTYP_420:
        case VAX_BTYP_43:
                if ((vax_confdata & (KA420_CFG_L3CON | KA420_CFG_MULTU)) != 0)
                        break;  /* doesn't use graphics console */

                if ((vax_confdata & KA420_CFG_VIDOPT) != 0)
                        break;  /* there is a color option */

                cndev->cn_pri = CN_INTERNAL;
                cndev->cn_dev =
                    makedev(cdevsw_lookup_major(&wsdisplay_cdevsw), 0);
                break;

        default:
                break;
        }
}

/*
 * Called very early to setup the glass tty as console.
 * Because it's called before the VM system is initialized, virtual memory
 * for the framebuffer can be stolen directly without disturbing anything.
 */
void
smgcninit(struct consdev *cndev)
{
        struct smg_screen *ss = &smg_consscr;
        vaddr_t ova;
        long defattr;
        struct rasops_info *ri;
        extern vaddr_t virtual_avail;

        ova = virtual_avail;

        ss->ss_addr = (uint8_t *)virtual_avail;
        ioaccess(virtual_avail, SMADDR, SMSIZE / VAX_NBPG);
        virtual_avail += SMSIZE;

        ss->ss_cursor = (struct dc503reg *)virtual_avail;
        ioaccess(virtual_avail, KA420_CUR_BASE, 1);
        virtual_avail += VAX_NBPG;

        virtual_avail = round_page(virtual_avail);

        /* this had better not fail */
        if (smg_setup_screen(ss) != 0) {
                iounaccess((vaddr_t)ss->ss_addr, SMSIZE / VAX_NBPG);
                iounaccess((vaddr_t)ss->ss_cursor, 1);
                virtual_avail = ova;
                return;
        }

        ri = &ss->ss_ri;
        (*ri->ri_ops.allocattr)(ri, 0, 0, 0, &defattr);
        wsdisplay_cnattach(&smg_stdscreen, ri, 0, 0, defattr);
        cn_tab->cn_pri = CN_INTERNAL;

#if NDZKBD > 0
        dzkbd_cnattach(0); /* Connect keyboard and screen together */
#endif
}