root/sys/arch/playstation2/ee/gsfb.c
/*      $NetBSD: gsfb.c,v 1.24 2021/08/07 16:19:02 thorpej Exp $        */

/*-
 * Copyright (c) 2001 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by UCHIYAMA Yasushi.
 *
 * 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: gsfb.c,v 1.24 2021/08/07 16:19:02 thorpej Exp $");

#include "debug_playstation2.h"

#include <sys/param.h>
#include <sys/systm.h>

#include <machine/autoconf.h>

#include <dev/cons.h> 

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

#include <dev/wsfont/wsfont.h>

#include <playstation2/ee/eevar.h>
#include <playstation2/ee/gsvar.h>
#include <playstation2/ee/gsreg.h>
#include <playstation2/ee/dmacvar.h>
#include <playstation2/ee/dmacreg.h>

#ifdef DEBUG
#define STATIC
#else
#define STATIC  static
#endif

struct gsfb_softc {
        device_t sc_dev;
        const struct wsscreen_descr *sc_screen;
        struct wsdisplay_font *sc_font;
        bool sc_is_console;
};

static int gsfb_is_console;
static struct gsfb_softc gsfb_console_softc;

STATIC void gsfb_dma_kick(paddr_t, size_t);
STATIC void gsfb_font_expand_psmct32(const struct wsdisplay_font *, u_int,
    long, u_int32_t *);
STATIC inline void gsfb_set_cursor_pos(u_int32_t *, int, int, int, int);

#define ATTR_FG_GET(a)  (((a )>> 24) & 0xf)
#define ATTR_BG_GET(a)  (((a )>> 16) & 0xf)
#define ATTR_FG_SET(x)  (((x) << 24) & 0x0f000000)
#define ATTR_BG_SET(x)  (((x) << 16) & 0x000f0000)

STATIC const u_int32_t gsfb_ansi_psmct32[] = {
        0x80000000, /* black */
        0x800000aa, /* red */
        0x8000aa00, /* green */
        0x8000aaaa, /* brown */
        0x80aa0000, /* blue */
        0x80aa00aa, /* magenta */
        0x80aaaa00, /* cyan */
        0x80aaaaaa, /* white */
        0x80000000, /* black */
        0x800000ff, /* red */
        0x8000ff00, /* green */
        0x8000ffff, /* brown */
        0x80ff0000, /* blue */
        0x80ff00ff, /* magenta */
        0x80ffff00, /* cyan */
        0x80ffffff, /* black */
};

#define TRXPOS_DXY(f, x, y)                                             \
({                                                                      \
        f[9] = ((x) & 0x000007ff) | (((y) << 16) & 0x07ff0000);         \
})

#define TRXPOS_SY_DY(f, sy, dy)                                         \
({                                                                      \
        f[8] = (((sy) << 16) & 0x07ff0000);                             \
        f[9] = (((dy) << 16) & 0x07ff0000);                             \
})

#define TRXPOS_DXY_SXY(f, dx, dy, sx, sy)                               \
({                                                                      \
        f[8] = ((((sy) << 16) & 0x07ff0000) | ((sx) & 0x000007ff));     \
        f[9] = ((((dy) << 16) & 0x07ff0000) | ((dx) & 0x000007ff));     \
})

STATIC u_int32_t gsfb_scroll_cmd_640x16[] __attribute__((__aligned__(16))) = {
        0x00008004, 0x10000000, 0x0000000e, 0x00000000,
        0x000a0000, 0x000a0000, 0x00000050, 0x00000000,
        0x07ff0000, 0x07ff0000, 0x00000051, 0x00000000,
        0x00000280, 0x00000010, 0x00000052, 0x00000000,
        0x00000002, 0x00000000, 0x00000053, 0x00000000,
};

STATIC u_int32_t gsfb_cursor_cmd[] __attribute__((__aligned__(16))) = {
        0x00008007, 0x10000000, 0x0000000e, 0x00000000,
        0x00000001, 0x00000000, 0x0000001a, 0x00000000,
        0x000000a4, 0x00000080, 0x00000042, 0x00000000,
        0x00000046, 0x00000000, 0x00000000, 0x00000000,
        0x80ffffff, 0x00000000, 0x00000001, 0x00000000,
        0x00000000, 0x00000000, 0x0000000d, 0x00000000,
        0x80ffffff, 0x00000000, 0x00000001, 0x00000000,
        0x00000000, 0x00000000, 0x00000005, 0x00000000,
};

STATIC u_int32_t gsfb_copy_cmd_8x16[] __attribute__((__aligned__(16))) = {
        0x00008004, 0x10000000, 0x0000000e, 0x00000000,
        0x000a0000, 0x000a0000, 0x00000050, 0x00000000,
        0x07ff07ff, 0x07ff07ff, 0x00000051, 0x00000000,
        0x00000008, 0x00000010, 0x00000052, 0x00000000,
        0x00000002, 0x00000000, 0x00000053, 0x00000000,
};

STATIC u_int32_t gsfb_init_cmd_640x480[] __attribute__((__aligned__(16))) = {
        0x00008008, 0x10000000, 0x0000000e, 0x00000000,
        0x000a0000, 0x00000000, 0x0000004c, 0x00000000,
        0x00000096, 0x00000000, 0x0000004e, 0x00000000,
        0x02800000, 0x01e00000, 0x00000040, 0x00000000,
        0x00000006, 0x00000000, 0x00000000, 0x00000000,
        0x80000000, 0x00000000, 0x00000001, 0x00000000,
        0x00000000, 0x00000000, 0x0000000d, 0x00000000,
        0x80000000, 0x00000000, 0x00000001, 0x00000000,
        0x1e002800, 0x00000000, 0x00000005, 0x00000000,
};

STATIC u_int32_t gsfb_load_cmd_8x16_psmct32[(6 + 32) * 4]
        __attribute__((__aligned__(16))) = {
        /* GIF tag + GS command */
        0x00000004, 0x10000000, 0x0000000e, 0x00000000,
        0x00000000, 0x000a0000, 0x00000050, 0x00000000,
        0x00000000, 0x00000000, 0x00000051, 0x00000000,
        0x00000008, 0x00000016, 0x00000052, 0x00000000,
        0x00000000, 0x00000000, 0x00000053, 0x00000000,
        0x00008020, 0x08000000, 0x00000000, 0x00000000,
        /* Load area */
#define FONT_SCRATCH_BASE       (6 * 4)
};

#ifdef GSFB_DEBUG_MONITOR
#include <machine/stdarg.h>
STATIC const struct _gsfb_debug_window {
        int start, nrow, attr;
} _gsfb_debug_window[3] = {
        { 24, 2 , ATTR_BG_SET(WSCOL_BROWN) | ATTR_FG_SET(WSCOL_BLUE) },
        { 26, 2 , ATTR_BG_SET(WSCOL_CYAN) | ATTR_FG_SET(WSCOL_BLUE) },
        { 28, 2 , ATTR_BG_SET(WSCOL_WHITE) | ATTR_FG_SET(WSCOL_BLUE) },
};
STATIC char _gsfb_debug_buf[80 * 2];
#endif /* GSFB_DEBUG_MONITOR */

STATIC int gsfb_match(device_t, cfdata_t, void *);
STATIC void gsfb_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(gsfb, sizeof(struct gsfb_softc),
    gsfb_match, gsfb_attach, NULL, NULL);

STATIC void gsfb_hwinit(void);
STATIC int gsfb_swinit(struct gsfb_softc*);

/* console */
void gsfbcnprobe(struct consdev *);
void gsfbcninit(struct consdev *);

/* emul ops */
STATIC void _gsfb_cursor(void *, int, int, int);
STATIC int _gsfb_mapchar(void *, int, unsigned int *);
STATIC void _gsfb_putchar(void *, int, int, u_int, long);
STATIC void _gsfb_copycols(void *, int, int, int, int);
STATIC void _gsfb_erasecols(void *, int, int, int, long);
STATIC void _gsfb_copyrows(void *, int, int, int);
STATIC void _gsfb_eraserows(void *, int, int, long);
STATIC int _gsfb_allocattr(void *, int, int, int, long *);

/* access ops */
STATIC int _gsfb_ioctl(void *, void *, u_long, void *, int, struct lwp *);
STATIC paddr_t _gsfb_mmap(void *, void *, off_t, int);
STATIC int _gsfb_alloc_screen(void *, const struct wsscreen_descr *, void **,
    int *, int *, long *);
STATIC void _gsfb_free_screen(void *, void *);
STATIC int _gsfb_show_screen(void *, void *, int, void (*)(void *, int, int),
    void *);
STATIC void _gsfb_pollc(void *, int);

/* 
 * wsdisplay attach args 
 *   std: screen size 640 x 480, font size 8 x 16
 */
#define GSFB_STD_SCREEN_WIDTH           640
#define GSFB_STD_SCREEN_HEIGHT          480
#define GSFB_STD_FONT_WIDTH             8
#define GSFB_STD_FONT_HEIGHT            16
const struct wsdisplay_emulops _gsfb_emulops = {
        .cursor         = _gsfb_cursor,
        .mapchar        = _gsfb_mapchar,
        .putchar        = _gsfb_putchar,
        .copycols       = _gsfb_copycols,
        .erasecols      = _gsfb_erasecols,
        .copyrows       = _gsfb_copyrows,
        .eraserows      = _gsfb_eraserows,
        .allocattr      = _gsfb_allocattr
};

const struct wsscreen_descr _gsfb_std_screen = {
        .name           = "std",
        .ncols          = 80,
#ifdef GSFB_DEBUG_MONITOR
        .nrows          = 24,
#else
        .nrows          = 30,
#endif
        .textops        = &_gsfb_emulops,
        .fontwidth      = 8,
        .fontheight     = 16,
        .capabilities   = WSSCREEN_UNDERLINE | WSSCREEN_HILIT |
        WSSCREEN_WSCOLORS
};

const struct wsscreen_descr *_gsfb_screen_table[] = {
        &_gsfb_std_screen,
};

struct wsscreen_list _gsfb_screen_list = {
        .nscreens       = sizeof(_gsfb_screen_table) /
        sizeof(_gsfb_screen_table[0]),
        .screens        = _gsfb_screen_table
};

struct wsdisplay_accessops _gsfb_accessops = {
        .ioctl          = _gsfb_ioctl,
        .mmap           = _gsfb_mmap,
        .alloc_screen   = _gsfb_alloc_screen,
        .free_screen    = _gsfb_free_screen,
        .show_screen    = _gsfb_show_screen,
        .load_font      = 0,
        .pollc          = _gsfb_pollc
};

int
gsfb_match(device_t parent, cfdata_t cf, void *aux)
{
        extern struct cfdriver gsfb_cd;
        struct mainbus_attach_args *ma = aux;

        if (strcmp(ma->ma_name, gsfb_cd.cd_name) != 0)
                return (0);

        return 1;
}

void
gsfb_attach(device_t parent, device_t self, void *aux)
{
        struct wsemuldisplaydev_attach_args wa;
        struct gsfb_softc *sc = device_private(self);

        if (gsfb_is_console) {
                memcpy(sc, &gsfb_console_softc, sizeof(gsfb_console_softc));
                sc->sc_is_console = true;
        }
        sc->sc_dev = self;

        if (!sc->sc_is_console && !gsfb_swinit(sc) != 0)
                return;

        printf("\n");

        wa.console      = sc->sc_is_console;
        wa.scrdata      = &_gsfb_screen_list;
        wa.accessops    = &_gsfb_accessops;
        wa.accesscookie = sc;

        config_found(self, &wa, wsdisplaydevprint, CFARGS_NONE);
}

/*
 * console
 */
void
gsfbcnprobe(struct consdev *cndev)
{
        
        cndev->cn_pri = CN_INTERNAL;
}

void
gsfbcninit(struct consdev *cndev)
{
        paddr_t paddr = MIPS_KSEG0_TO_PHYS(gsfb_init_cmd_640x480);
        u_int32_t *buf = (void *)MIPS_PHYS_TO_KSEG1(paddr);
        long defattr =  ATTR_BG_SET(WS_DEFAULT_BG) | ATTR_FG_SET(WS_DEFAULT_FG);

        gsfb_is_console = 1;

        gsfb_hwinit();
        gsfb_swinit(&gsfb_console_softc);

        /* Set the screen to the default background color at boot */
        buf[28] = gsfb_ansi_psmct32[ATTR_BG_GET(defattr)];
        gsfb_dma_kick(paddr, sizeof gsfb_init_cmd_640x480);
#ifdef GSFB_DEBUG_MONITOR
        {
                const struct _gsfb_debug_window *win;
                int i;

                for (i = 0; i < 3; i++) {
                        win = &_gsfb_debug_window[i];
                        _gsfb_eraserows(0, win->start, win->nrow, win->attr);
                }
        }
#endif /* GSFB_DEBUG_MONITOR */

        wsdisplay_cnattach(&_gsfb_std_screen, &gsfb_console_softc, 0, 0,
            defattr);
}

void
gsfb_hwinit(void)
{
        /*
          gs_init(VESA_1A) hang up on SCPH-50000.
          use bootloader's setting.
          EN1 | CRTMOD | MMOD | AMOD | ALP(all 1.0)
        */
        _reg_write_8(GS_S_PMODE_REG, 0xffa5);

        dmac_init();

        /* reset GIF channel DMA */
        _reg_write_4(D2_QWC_REG, 0);
        _reg_write_4(D2_MADR_REG, 0);
        _reg_write_4(D2_TADR_REG, 0);
        _reg_write_4(D2_CHCR_REG, 0);
}

int
gsfb_swinit(struct gsfb_softc *sc)
{
        int font;

        wsfont_init();  
        font = wsfont_find(NULL, 8, 16, 0, WSDISPLAY_FONTORDER_L2R,
            WSDISPLAY_FONTORDER_L2R, WSFONT_FIND_BITMAP);
        if (font < 0)
                return (1);

        if (wsfont_lock(font, &sc->sc_font))
                return (1);

        sc->sc_screen = &_gsfb_std_screen;

        return (0);
}

/*
 * wsdisplay
 */
void
_gsfb_cursor(void *cookie, int on, int row, int col)
{
        struct gsfb_softc *sc = cookie;
        paddr_t paddr = MIPS_KSEG0_TO_PHYS(gsfb_cursor_cmd);
        u_int32_t *buf = (void *)MIPS_PHYS_TO_KSEG1(paddr);
        struct wsdisplay_font *font = sc->sc_font;

        gsfb_set_cursor_pos(buf, col, row, font->fontwidth, font->fontheight);

        gsfb_dma_kick(paddr, sizeof gsfb_cursor_cmd);
}

inline void
gsfb_set_cursor_pos(u_int32_t *p, int x, int y, int w, int h)
{

        x *= w;
        y *= h;
        p[20] = ((x << 4) & 0xffff) | ((y << 20) & 0xffff0000);
        p[28] = (((x + w) << 4) & 0xffff) | (((y + h) << 20) & 0xffff0000);
}

int
_gsfb_mapchar(void *cookie, int c, unsigned int *cp)
{
        struct gsfb_softc *sc = cookie;
        struct wsdisplay_font *font = sc->sc_font;
        
        if (font->encoding != WSDISPLAY_FONTENC_ISO)
                if ((c = wsfont_map_unichar(font, c)) < 0)
                        goto nomap;

        if (c < font->firstchar || c >= font->firstchar + font->numchars)
                        goto nomap;

        *cp = c;
        return (5);

 nomap:
        *cp = ' ';
        return (0);
}

void
_gsfb_putchar(void *cookie, int row, int col, u_int uc, long attr)
{
        struct gsfb_softc *sc = cookie;
        paddr_t paddr = MIPS_KSEG0_TO_PHYS(gsfb_load_cmd_8x16_psmct32);
        u_int32_t *buf = (void *)MIPS_PHYS_TO_KSEG1(paddr);
        struct wsdisplay_font *font = sc->sc_font;

        /* copy font data to DMA region */
        gsfb_font_expand_psmct32(font, uc, attr, &buf[FONT_SCRATCH_BASE]);

        /* set destination position */
        TRXPOS_DXY(buf, col * font->fontwidth, row * font->fontheight);

        /* kick to GIF */
        gsfb_dma_kick(paddr, sizeof gsfb_load_cmd_8x16_psmct32);
}

void
_gsfb_copycols(void *cookie, int row, int srccol, int dstcol, int ncols)
{
        struct gsfb_softc *sc = cookie;
        paddr_t paddr = MIPS_KSEG0_TO_PHYS(gsfb_copy_cmd_8x16);
        u_int32_t *cmd = (void *)MIPS_PHYS_TO_KSEG1(paddr);
        int y = sc->sc_font->fontheight * row;
        int w = sc->sc_font->fontwidth;
        int i;

        if (dstcol > srccol) {
                for (i = ncols - 1; i >= 0; i--) {
                        TRXPOS_DXY_SXY(cmd, (dstcol + i) * w, y, (srccol + i) * w, y);
                        gsfb_dma_kick(paddr, sizeof gsfb_copy_cmd_8x16);
                }
        } else {
                for (i = 0; i < ncols; i++) {
                        TRXPOS_DXY_SXY(cmd, (dstcol + i) * w, y, (srccol + i) * w, y);
                        gsfb_dma_kick(paddr, sizeof gsfb_copy_cmd_8x16);
                }
        }
}

void
_gsfb_erasecols(void *cookie, int row, int startcol, int ncols, long attr)
{
        int i;
        
        for (i = 0; i < ncols; i++)
                _gsfb_putchar(cookie, row, startcol + i, ' ', attr);
}

void
_gsfb_copyrows(void *cookie, int src, int dst, int num)
{
        struct gsfb_softc *sc = cookie;
        paddr_t paddr = MIPS_KSEG0_TO_PHYS(gsfb_scroll_cmd_640x16);
        u_int32_t *cmd = (void *)MIPS_PHYS_TO_KSEG1(paddr);
        int i;
        int h = sc->sc_font->fontheight;

        if (dst > src) {
                for (i = num - 1; i >= 0; i--) {
                        TRXPOS_SY_DY(cmd, (src + i) * h, (dst  + i) * h);
                        gsfb_dma_kick(paddr, sizeof gsfb_scroll_cmd_640x16);
                }
        } else {
                for (i = 0; i < num; i++) {
                        TRXPOS_SY_DY(cmd, (src + i) * h, (dst  + i) * h);
                        gsfb_dma_kick(paddr, sizeof gsfb_scroll_cmd_640x16);
                }
        }
}

void
_gsfb_eraserows(void *cookie, int row, int nrow, long attr)
{
        struct gsfb_softc *sc = cookie;
        int i, j;
        
        for (j = 0; j < nrow; j++)
                for (i = 0; i < sc->sc_screen->ncols; i++)
                        _gsfb_putchar(cookie, row + j, i, ' ', attr);
}

int
_gsfb_allocattr(void *cookie, int fg, int bg, int flags, long *attr)
{

        if ((flags & WSATTR_BLINK) != 0)
                return (EINVAL);

        if ((flags & WSATTR_WSCOLORS) == 0) {
                fg = WS_DEFAULT_FG;
                bg = WS_DEFAULT_BG;
        }

        if ((flags & WSATTR_HILIT) != 0)
                fg += 8;

        flags = (flags & WSATTR_UNDERLINE) ? 1 : 0;


        *attr = ATTR_BG_SET(bg) | ATTR_FG_SET(fg) | flags;

        return (0);
}

int
_gsfb_ioctl(void *v, void *vs, u_long cmd, void *data, int flag,
        struct lwp *l)
{

        return (EPASSTHROUGH); /* Inappropriate ioctl for device */
}

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

        return (-1); /* can't mmap */
}

int
_gsfb_alloc_screen(void *v, const struct wsscreen_descr *type, void **cookiep,
    int *curxp, int *curyp, long *attrp)
{

        *attrp = ATTR_BG_SET(WS_DEFAULT_BG) | ATTR_FG_SET(WS_DEFAULT_FG);

        return (0);
}

void
_gsfb_free_screen(void *v, void *cookie)
{
}

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

        return (0);
}

void
_gsfb_pollc(void *v, int on)
{

}

/* 
 * font expansion 
 *   PSMCT32 only
 */
void
gsfb_font_expand_psmct32(const struct wsdisplay_font *font, u_int c, long attr,
    u_int32_t *buf)
{
        u_int32_t fg, bg;
        u_int8_t *bitmap;
        int i, j;
        
        KDASSERT(((u_int32_t)buf & 15) == 0);

        fg = gsfb_ansi_psmct32[ATTR_FG_GET(attr)];
        bg = gsfb_ansi_psmct32[ATTR_BG_GET(attr)];

        bitmap = (u_int8_t *)font->data + (c - font->firstchar) * 
            font->fontheight * font->stride;
        for (i = 0; i < font->fontheight; i++, bitmap++) {
                u_int32_t b = *bitmap;
                for (j = 0; j < font->fontwidth; j++, b <<= 1)
                        *buf++ = (b & 0x80) ? fg : bg;
        }
}

void 
gsfb_dma_kick(paddr_t addr, size_t size)
{
        /* Wait for previous DMA request complete */
        while (_reg_read_4(D2_QWC_REG))
                ;

        /* Wait until GS FIFO empty */
        while ((_reg_read_8(GS_S_CSR_REG) & (3 << 14)) != (1 << 14))
                ;

        /* wait for DMA complete */
        dmac_bus_poll(D_CH2_GIF);

        /* transfer addr */
        _reg_write_4(D2_MADR_REG, addr);
        /* transfer data size (unit qword) */
        _reg_write_4(D2_QWC_REG, bytetoqwc(size));
        
        /* kick DMA (normal-mode) */
        dmac_chcr_write(D_CH2_GIF, D_CHCR_STR);
}

#ifdef GSFB_DEBUG_MONITOR
void
__gsfb_print(int window, const char *fmt, ...)
{
        const struct _gsfb_debug_window *win;
        int i, s, x, y, n, a;
        u_int c;
        va_list ap;
        
        if (!gsfb.initialized)
                return;
        
        s = _intr_suspend();
        win = &_gsfb_debug_window[window];
        x = 0;
        y = win->start;
        n = win->nrow * 80;
        a = win->attr;

        va_start(ap, fmt);
        vsnprintf(_gsfb_debug_buf, n, fmt, ap);
        va_end(ap);

        _gsfb_eraserows(0, y, win->nrow, a);

        for (i = 0; i < n &&
            (c = (u_int)_gsfb_debug_buf[i] & 0x7f) != 0; i++) {
                if (c == '\n')
                        x = 0, y++;
                else
                        _gsfb_putchar(0, y, x++, c, a);
        }

        _intr_resume(s);
}

void
__gsfb_print_hex(int a0, int a1, int a2, int a3)
{
        __gsfb_print(2, "a0=%08x a1=%08x a2=%08x a3=%08x",
            a0, a1, a2, a3);
}
#endif /* GSFB_DEBUG_MONITOR */