NetBSD/sys/arch/sparc/dev/zs.c
pk ee08e9085b Extend the size of the "soft" FIFOs which reportedly reduces the number
of FIFO overflows on high baud rates.
However, doing so on all 4 ports would cost a whopping 64KB (at 4096 entries
per FIFO) of kernel memory. So, the FIFOs are now allocated at attach time
allowing the size for the keyboard and mouse ports to be reduced (to 128)
which should be adequate for the 1200 baud they use.
1995-11-29 23:41:31 +00:00

1548 lines
36 KiB
C

/* $NetBSD: zs.c,v 1.32 1995/11/29 23:41:35 pk Exp $ */
/*
* Copyright (c) 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* This software was developed by the Computer Systems Engineering group
* at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
* contributed to Berkeley.
*
* All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Lawrence Berkeley Laboratory.
*
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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.
*
* @(#)zs.c 8.1 (Berkeley) 7/19/93
*/
/*
* Zilog Z8530 (ZSCC) driver.
*
* Runs two tty ports (ttya and ttyb) on zs0,
* and runs a keyboard and mouse on zs1.
*
* This driver knows far too much about chip to usage mappings.
*/
#define NZS 2 /* XXX */
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/tty.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/syslog.h>
#include <machine/autoconf.h>
#include <machine/cpu.h>
#include <sparc/sparc/vaddrs.h>
#include <sparc/sparc/auxreg.h>
#include <machine/kbd.h>
#include <dev/ic/z8530reg.h>
#include <sparc/dev/zsvar.h>
#ifdef KGDB
#include <machine/remote-sl.h>
#endif
#define ZSMAJOR 12 /* XXX */
#define ZS_KBD 2 /* XXX */
#define ZS_MOUSE 3 /* XXX */
/* the magic number below was stolen from the Sprite source. */
#define PCLK (19660800/4) /* PCLK pin input clock rate */
/*
* Select software interrupt bit based on TTY ipl.
*/
#if PIL_TTY == 1
# define IE_ZSSOFT IE_L1
#elif PIL_TTY == 4
# define IE_ZSSOFT IE_L4
#elif PIL_TTY == 6
# define IE_ZSSOFT IE_L6
#else
# error "no suitable software interrupt bit"
#endif
/*
* Software state per found chip. This would be called `zs_softc',
* but the previous driver had a rather different zs_softc....
*/
struct zsinfo {
struct device zi_dev; /* base device */
volatile struct zsdevice *zi_zs;/* chip registers */
struct zs_chanstate zi_cs[2]; /* channel A and B software state */
};
/* Definition of the driver for autoconfig. */
static int zsmatch __P((struct device *, void *, void *));
static void zsattach __P((struct device *, struct device *, void *));
struct cfdriver zscd =
{ NULL, "zs", zsmatch, zsattach, DV_TTY, sizeof(struct zsinfo) };
/* Interrupt handlers. */
static int zshard __P((void *));
static struct intrhand levelhard = { zshard };
static int zssoft __P((void *));
static struct intrhand levelsoft = { zssoft };
struct zs_chanstate *zslist;
/* Routines called from other code. */
static void zsiopen __P((struct tty *));
static void zsiclose __P((struct tty *));
static void zsstart __P((struct tty *));
void zsstop __P((struct tty *, int));
static int zsparam __P((struct tty *, struct termios *));
/* Routines purely local to this driver. */
static int zs_getspeed __P((volatile struct zschan *));
static void zs_reset __P((volatile struct zschan *, int, int));
static void zs_modem __P((struct zs_chanstate *, int));
static void zs_loadchannelregs __P((volatile struct zschan *, u_char *));
/* Console stuff. */
static struct tty *zs_ctty; /* console `struct tty *' */
static int zs_consin = -1, zs_consout = -1;
static int zscnputc __P((int)); /* console putc function */
static volatile struct zschan *zs_conschan;
static struct tty *zs_checkcons __P((struct zsinfo *, int, struct zs_chanstate *));
#ifdef KGDB
/* KGDB stuff. Must reboot to change zs_kgdbunit. */
extern int kgdb_dev, kgdb_rate;
static int zs_kgdb_savedspeed;
static void zs_checkkgdb __P((int, struct zs_chanstate *, struct tty *));
#endif
extern volatile struct zsdevice *findzs(int);
static volatile struct zsdevice *zsaddr[NZS]; /* XXX, but saves work */
/*
* Console keyboard L1-A processing is done in the hardware interrupt code,
* so we need to duplicate some of the console keyboard decode state. (We
* must not use the regular state as the hardware code keeps ahead of the
* software state: the software state tracks the most recent ring input but
* the hardware state tracks the most recent ZSCC input.) See also kbd.h.
*/
static struct conk_state { /* console keyboard state */
char conk_id; /* true => ID coming up (console only) */
char conk_l1; /* true => L1 pressed (console only) */
} zsconk_state;
int zshardscope;
int zsshortcuts; /* number of "shortcut" software interrupts */
#ifdef SUN4
static u_char
zs_read(zc, reg)
volatile struct zschan *zc;
u_char reg;
{
u_char val;
zc->zc_csr = reg;
ZS_DELAY();
val = zc->zc_csr;
ZS_DELAY();
return val;
}
static u_char
zs_write(zc, reg, val)
volatile struct zschan *zc;
u_char reg, val;
{
zc->zc_csr = reg;
ZS_DELAY();
zc->zc_csr = val;
ZS_DELAY();
return val;
}
#endif /* SUN4 */
/*
* Match slave number to zs unit number, so that misconfiguration will
* not set up the keyboard as ttya, etc.
*/
static int
zsmatch(parent, vcf, aux)
struct device *parent;
void *vcf, *aux;
{
struct cfdata *cf = vcf;
struct confargs *ca = aux;
struct romaux *ra = &ca->ca_ra;
if (strcmp(cf->cf_driver->cd_name, ra->ra_name))
return (0);
if (ca->ca_bustype==BUS_MAIN && cputyp!=CPU_SUN4)
return (getpropint(ra->ra_node, "slave", -2) == cf->cf_unit);
ra->ra_len = NBPG;
return (probeget(ra->ra_vaddr, 1) != -1);
}
/*
* Attach a found zs.
*
* USE ROM PROPERTIES port-a-ignore-cd AND port-b-ignore-cd FOR
* SOFT CARRIER, AND keyboard PROPERTY FOR KEYBOARD/MOUSE?
*/
static void
zsattach(parent, dev, aux)
struct device *parent;
struct device *dev;
void *aux;
{
register int zs = dev->dv_unit, unit;
register struct zsinfo *zi;
register struct zs_chanstate *cs;
register volatile struct zsdevice *addr;
register struct tty *tp, *ctp;
register struct confargs *ca = aux;
register struct romaux *ra = &ca->ca_ra;
int pri;
static int didintr, prevpri;
int ringsize;
if ((addr = zsaddr[zs]) == NULL)
addr = zsaddr[zs] = findzs(zs);
if (ca->ca_bustype==BUS_MAIN)
if ((void *)addr != ra->ra_vaddr)
panic("zsattach");
if (ra->ra_nintr != 1) {
printf(": expected 1 interrupt, got %d\n", ra->ra_nintr);
return;
}
pri = ra->ra_intr[0].int_pri;
printf(" pri %d, softpri %d\n", pri, PIL_TTY);
if (!didintr) {
didintr = 1;
prevpri = pri;
intr_establish(pri, &levelhard);
intr_establish(PIL_TTY, &levelsoft);
} else if (pri != prevpri)
panic("broken zs interrupt scheme");
zi = (struct zsinfo *)dev;
zi->zi_zs = addr;
unit = zs * 2;
cs = zi->zi_cs;
cs->cs_ttyp = tp = ttymalloc();
/* link into interrupt list with order (A,B) (B=A+1) */
cs[0].cs_next = &cs[1];
cs[1].cs_next = zslist;
zslist = cs;
cs->cs_unit = unit;
cs->cs_speed = zs_getspeed(&addr->zs_chan[ZS_CHAN_A]);
cs->cs_zc = &addr->zs_chan[ZS_CHAN_A];
tp->t_dev = makedev(ZSMAJOR, unit);
tp->t_oproc = zsstart;
tp->t_param = zsparam;
if ((ctp = zs_checkcons(zi, unit, cs)) != NULL)
cs->cs_ttyp = tp = ctp;
#ifdef KGDB
if (ctp == NULL)
zs_checkkgdb(unit, cs, tp);
#endif
if (unit == ZS_KBD) {
/*
* Keyboard: tell /dev/kbd driver how to talk to us.
*/
tp->t_ispeed = tp->t_ospeed = cs->cs_speed;
tp->t_cflag = CS8;
kbd_serial(tp, zsiopen, zsiclose);
cs->cs_conk = 1; /* do L1-A processing */
ringsize = 128;
} else
ringsize = 4096;
cs->cs_ringmask = ringsize - 1;
cs->cs_rbuf = malloc((u_long)ringsize * sizeof(*cs->cs_rbuf),
M_DEVBUF, M_NOWAIT);
unit++;
cs++;
cs->cs_ttyp = tp = ttymalloc();
cs->cs_unit = unit;
cs->cs_speed = zs_getspeed(&addr->zs_chan[ZS_CHAN_B]);
cs->cs_zc = &addr->zs_chan[ZS_CHAN_B];
tp->t_dev = makedev(ZSMAJOR, unit);
tp->t_oproc = zsstart;
tp->t_param = zsparam;
if ((ctp = zs_checkcons(zi, unit, cs)) != NULL)
cs->cs_ttyp = tp = ctp;
#ifdef KGDB
if (ctp == NULL)
zs_checkkgdb(unit, cs, tp);
#endif
if (unit == ZS_MOUSE) {
/*
* Mouse: tell /dev/mouse driver how to talk to us.
*/
tp->t_ispeed = tp->t_ospeed = cs->cs_speed;
tp->t_cflag = CS8;
ms_serial(tp, zsiopen, zsiclose);
ringsize = 128;
} else
ringsize = 4096;
cs->cs_ringmask = ringsize - 1;
cs->cs_rbuf = malloc((u_long)ringsize * sizeof(*cs->cs_rbuf),
M_DEVBUF, M_NOWAIT);
}
/*
* Put a channel in a known state. Interrupts may be left disabled
* or enabled, as desired.
*/
static void
zs_reset(zc, inten, speed)
volatile struct zschan *zc;
int inten, speed;
{
int tconst;
static u_char reg[16] = {
0,
0,
0,
ZSWR3_RX_8 | ZSWR3_RX_ENABLE,
ZSWR4_CLK_X16 | ZSWR4_ONESB | ZSWR4_EVENP,
ZSWR5_TX_8 | ZSWR5_TX_ENABLE,
0,
0,
0,
0,
ZSWR10_NRZ,
ZSWR11_TXCLK_BAUD | ZSWR11_RXCLK_BAUD,
0,
0,
ZSWR14_BAUD_FROM_PCLK | ZSWR14_BAUD_ENA,
ZSWR15_BREAK_IE | ZSWR15_DCD_IE,
};
reg[9] = inten ? ZSWR9_MASTER_IE | ZSWR9_NO_VECTOR : ZSWR9_NO_VECTOR;
tconst = BPS_TO_TCONST(PCLK / 16, speed);
reg[12] = tconst;
reg[13] = tconst >> 8;
zs_loadchannelregs(zc, reg);
}
/*
* Declare the given tty (which is in fact &cons) as a console input
* or output. This happens before the zs chip is attached; the hookup
* is finished later, in zs_setcons() below.
*
* This is used only for ports a and b. The console keyboard is decoded
* independently (we always send unit-2 input to /dev/kbd, which will
* direct it to /dev/console if appropriate).
*/
void
zsconsole(tp, unit, out, fnstop)
register struct tty *tp;
register int unit;
int out;
void (**fnstop) __P((struct tty *, int));
{
extern int (*v_putc)();
int zs;
volatile struct zsdevice *addr;
if (unit >= ZS_KBD)
panic("zsconsole");
if (out) {
zs_consout = unit;
zs = unit >> 1;
if ((addr = zsaddr[zs]) == NULL)
addr = zsaddr[zs] = findzs(zs);
zs_conschan = (unit & 1) == 0 ? &addr->zs_chan[ZS_CHAN_A] :
&addr->zs_chan[ZS_CHAN_B];
v_putc = zscnputc;
} else
zs_consin = unit;
if(fnstop)
*fnstop = &zsstop;
zs_ctty = tp;
}
/*
* Polled console output putchar.
*/
static int
zscnputc(c)
int c;
{
register volatile struct zschan *zc = zs_conschan;
register int s;
if (c == '\n')
zscnputc('\r');
/*
* Must block output interrupts (i.e., raise to >= splzs) without
* lowering current ipl. Need a better way.
*/
s = splhigh();
#ifdef SUN4C /* XXX */
if (cputyp==CPU_SUN4C && s <= (12 << 8))
(void) splzs();
#endif
while ((zc->zc_csr & ZSRR0_TX_READY) == 0)
ZS_DELAY();
zc->zc_data = c;
ZS_DELAY();
splx(s);
}
/*
* Set up the given unit as console input, output, both, or neither, as
* needed. Return console tty if it is to receive console input.
*/
static struct tty *
zs_checkcons(zi, unit, cs)
struct zsinfo *zi;
int unit;
struct zs_chanstate *cs;
{
register struct tty *tp;
char *i, *o;
if ((tp = zs_ctty) == NULL) /* XXX */
return (0);
i = zs_consin == unit ? "input" : NULL;
o = zs_consout == unit ? "output" : NULL;
if (i == NULL && o == NULL)
return (0);
/* rewire the minor device (gack) */
tp->t_dev = makedev(major(tp->t_dev), unit);
/*
* Rewire input and/or output. Note that baud rate reflects
* input settings, not output settings, but we can do no better
* if the console is split across two ports.
*
* XXX split consoles don't work anyway -- this needs to be
* thrown away and redone
*/
if (i) {
tp->t_param = zsparam;
tp->t_ispeed = tp->t_ospeed = cs->cs_speed;
tp->t_cflag = CS8;
ttsetwater(tp);
}
if (o) {
tp->t_oproc = zsstart;
}
printf("%s%c: console %s\n",
zi->zi_dev.dv_xname, (unit & 1) + 'a', i ? (o ? "i/o" : i) : o);
cs->cs_consio = 1;
cs->cs_brkabort = 1;
return (tp);
}
#ifdef KGDB
/*
* The kgdb zs port, if any, was altered at boot time (see zs_kgdb_init).
* Pick up the current speed and character size and restore the original
* speed.
*/
static void
zs_checkkgdb(unit, cs, tp)
int unit;
struct zs_chanstate *cs;
struct tty *tp;
{
if (kgdb_dev == makedev(ZSMAJOR, unit)) {
tp->t_ispeed = tp->t_ospeed = kgdb_rate;
tp->t_cflag = CS8;
cs->cs_kgdb = 1;
cs->cs_speed = zs_kgdb_savedspeed;
(void) zsparam(tp, &tp->t_termios);
}
}
#endif
/*
* Compute the current baud rate given a ZSCC channel.
*/
static int
zs_getspeed(zc)
register volatile struct zschan *zc;
{
register int tconst;
tconst = ZS_READ(zc, 12);
tconst |= ZS_READ(zc, 13) << 8;
return (TCONST_TO_BPS(PCLK / 16, tconst));
}
/*
* Do an internal open.
*/
static void
zsiopen(tp)
struct tty *tp;
{
(void) zsparam(tp, &tp->t_termios);
ttsetwater(tp);
tp->t_state = TS_ISOPEN | TS_CARR_ON;
}
/*
* Do an internal close. Eventually we should shut off the chip when both
* ports on it are closed.
*/
static void
zsiclose(tp)
struct tty *tp;
{
ttylclose(tp, 0); /* ??? */
ttyclose(tp); /* ??? */
tp->t_state = 0;
}
/*
* Open a zs serial port. This interface may not be used to open
* the keyboard and mouse ports. (XXX)
*/
int
zsopen(dev, flags, mode, p)
dev_t dev;
int flags;
int mode;
struct proc *p;
{
register struct tty *tp;
register struct zs_chanstate *cs;
struct zsinfo *zi;
int unit = minor(dev), zs = unit >> 1, error, s;
if (zs >= zscd.cd_ndevs || (zi = zscd.cd_devs[zs]) == NULL ||
unit == ZS_KBD || unit == ZS_MOUSE)
return (ENXIO);
cs = &zi->zi_cs[unit & 1];
if (cs->cs_consio)
return (ENXIO); /* ??? */
tp = cs->cs_ttyp;
s = spltty();
if ((tp->t_state & TS_ISOPEN) == 0) {
ttychars(tp);
if (tp->t_ispeed == 0) {
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_cflag = TTYDEF_CFLAG;
tp->t_lflag = TTYDEF_LFLAG;
tp->t_ispeed = tp->t_ospeed = cs->cs_speed;
}
(void) zsparam(tp, &tp->t_termios);
ttsetwater(tp);
} else if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) {
splx(s);
return (EBUSY);
}
error = 0;
for (;;) {
register int rr0;
/* loop, turning on the device, until carrier present */
zs_modem(cs, 1);
/* May never get status intr if carrier already on. -gwr */
rr0 = cs->cs_zc->zc_csr;
ZS_DELAY();
if ((rr0 & ZSRR0_DCD) || cs->cs_softcar)
tp->t_state |= TS_CARR_ON;
if (flags & O_NONBLOCK || tp->t_cflag & CLOCAL ||
tp->t_state & TS_CARR_ON)
break;
tp->t_state |= TS_WOPEN;
if (error = ttysleep(tp, (caddr_t)&tp->t_rawq, TTIPRI | PCATCH,
ttopen, 0)) {
if (!(tp->t_state & TS_ISOPEN)) {
zs_modem(cs, 0);
tp->t_state &= ~TS_WOPEN;
ttwakeup(tp);
}
splx(s);
return error;
}
}
splx(s);
if (error == 0)
error = linesw[tp->t_line].l_open(dev, tp);
if (error)
zs_modem(cs, 0);
return (error);
}
/*
* Close a zs serial port.
*/
int
zsclose(dev, flags, mode, p)
dev_t dev;
int flags;
int mode;
struct proc *p;
{
register struct zs_chanstate *cs;
register struct tty *tp;
struct zsinfo *zi;
int unit = minor(dev), s;
zi = zscd.cd_devs[unit >> 1];
cs = &zi->zi_cs[unit & 1];
tp = cs->cs_ttyp;
linesw[tp->t_line].l_close(tp, flags);
if (tp->t_cflag & HUPCL || tp->t_state & TS_WOPEN ||
(tp->t_state & TS_ISOPEN) == 0) {
zs_modem(cs, 0);
/* hold low for 1 second */
(void) tsleep((caddr_t)cs, TTIPRI, ttclos, hz);
}
if (cs->cs_creg[5] & ZSWR5_BREAK)
{
s = splzs();
cs->cs_preg[5] &= ~ZSWR5_BREAK;
cs->cs_creg[5] &= ~ZSWR5_BREAK;
ZS_WRITE(cs->cs_zc, 5, cs->cs_creg[5]);
splx(s);
}
ttyclose(tp);
#ifdef KGDB
/* Reset the speed if we're doing kgdb on this port */
if (cs->cs_kgdb) {
tp->t_ispeed = tp->t_ospeed = kgdb_rate;
(void) zsparam(tp, &tp->t_termios);
}
#endif
return (0);
}
/*
* Read/write zs serial port.
*/
int
zsread(dev, uio, flags)
dev_t dev;
struct uio *uio;
int flags;
{
register struct zs_chanstate *cs;
register struct zsinfo *zi;
register struct tty *tp;
int unit = minor(dev);
zi = zscd.cd_devs[unit >> 1];
cs = &zi->zi_cs[unit & 1];
tp = cs->cs_ttyp;
return (linesw[tp->t_line].l_read(tp, uio, flags));
}
int
zswrite(dev, uio, flags)
dev_t dev;
struct uio *uio;
int flags;
{
register struct zs_chanstate *cs;
register struct zsinfo *zi;
register struct tty *tp;
int unit = minor(dev);
zi = zscd.cd_devs[unit >> 1];
cs = &zi->zi_cs[unit & 1];
tp = cs->cs_ttyp;
return (linesw[tp->t_line].l_write(tp, uio, flags));
}
struct tty *
zstty(dev)
dev_t dev;
{
register struct zs_chanstate *cs;
register struct zsinfo *zi;
int unit = minor(dev);
zi = zscd.cd_devs[unit >> 1];
cs = &zi->zi_cs[unit & 1];
return (cs->cs_ttyp);
}
/*
* ZS hardware interrupt. Scan all ZS channels. NB: we know here that
* channels are kept in (A,B) pairs.
*
* Do just a little, then get out; set a software interrupt if more
* work is needed.
*
* We deliberately ignore the vectoring Zilog gives us, and match up
* only the number of `reset interrupt under service' operations, not
* the order.
*/
/* ARGSUSED */
int
zshard(intrarg)
void *intrarg;
{
register struct zs_chanstate *a;
#define b (a + 1)
register volatile struct zschan *zc;
register int rr3, intflags = 0, v, i, ringmask;
static int zsrint(struct zs_chanstate *, volatile struct zschan *);
static int zsxint(struct zs_chanstate *, volatile struct zschan *);
static int zssint(struct zs_chanstate *, volatile struct zschan *);
for (a = zslist; a != NULL; a = b->cs_next) {
ringmask = a->cs_ringmask;
rr3 = ZS_READ(a->cs_zc, 3);
if (rr3 & (ZSRR3_IP_A_RX|ZSRR3_IP_A_TX|ZSRR3_IP_A_STAT)) {
intflags |= 2;
zc = a->cs_zc;
i = a->cs_rbput;
if (rr3 & ZSRR3_IP_A_RX && (v = zsrint(a, zc)) != 0) {
a->cs_rbuf[i++ & ringmask] = v;
intflags |= 1;
}
if (rr3 & ZSRR3_IP_A_TX && (v = zsxint(a, zc)) != 0) {
a->cs_rbuf[i++ & ringmask] = v;
intflags |= 1;
}
if (rr3 & ZSRR3_IP_A_STAT && (v = zssint(a, zc)) != 0) {
a->cs_rbuf[i++ & ringmask] = v;
intflags |= 1;
}
a->cs_rbput = i;
}
if (rr3 & (ZSRR3_IP_B_RX|ZSRR3_IP_B_TX|ZSRR3_IP_B_STAT)) {
intflags |= 2;
zc = b->cs_zc;
i = b->cs_rbput;
if (rr3 & ZSRR3_IP_B_RX && (v = zsrint(b, zc)) != 0) {
b->cs_rbuf[i++ & ringmask] = v;
intflags |= 1;
}
if (rr3 & ZSRR3_IP_B_TX && (v = zsxint(b, zc)) != 0) {
b->cs_rbuf[i++ & ringmask] = v;
intflags |= 1;
}
if (rr3 & ZSRR3_IP_B_STAT && (v = zssint(b, zc)) != 0) {
b->cs_rbuf[i++ & ringmask] = v;
intflags |= 1;
}
b->cs_rbput = i;
}
}
#undef b
if (intflags & 1) {
#if defined(SUN4C) || defined(SUN4M)
if (cputyp==CPU_SUN4M || cputyp==CPU_SUN4C) {
/* XXX -- but this will go away when zshard moves to locore.s */
struct clockframe *p = intrarg;
if ((p->psr & PSR_PIL) < (PIL_TTY << 8)) {
zsshortcuts++;
(void) spltty();
if (zshardscope) {
LED_ON;
LED_OFF;
}
return (zssoft(intrarg));
}
}
#endif
ienab_bis(IE_ZSSOFT);
}
return (intflags & 2);
}
static int
zsrint(cs, zc)
register struct zs_chanstate *cs;
register volatile struct zschan *zc;
{
register int c = zc->zc_data;
ZS_DELAY();
if (cs->cs_conk) {
register struct conk_state *conk = &zsconk_state;
/*
* Check here for console abort function, so that we
* can abort even when interrupts are locking up the
* machine.
*/
if (c == KBD_RESET) {
conk->conk_id = 1; /* ignore next byte */
conk->conk_l1 = 0;
} else if (conk->conk_id)
conk->conk_id = 0; /* stop ignoring bytes */
else if (c == KBD_L1)
conk->conk_l1 = 1; /* L1 went down */
else if (c == (KBD_L1|KBD_UP))
conk->conk_l1 = 0; /* L1 went up */
else if (c == KBD_A && conk->conk_l1) {
zsabort();
conk->conk_l1 = 0; /* we never see the up */
goto clearit; /* eat the A after L1-A */
}
}
#ifdef KGDB
if (c == FRAME_START && cs->cs_kgdb &&
(cs->cs_ttyp->t_state & TS_ISOPEN) == 0) {
zskgdb(cs->cs_unit);
goto clearit;
}
#endif
/* compose receive character and status */
c <<= 8;
c |= ZS_READ(zc, 1);
/* clear receive error & interrupt condition */
zc->zc_csr = ZSWR0_RESET_ERRORS;
ZS_DELAY();
zc->zc_csr = ZSWR0_CLR_INTR;
ZS_DELAY();
return (ZRING_MAKE(ZRING_RINT, c));
clearit:
zc->zc_csr = ZSWR0_RESET_ERRORS;
ZS_DELAY();
zc->zc_csr = ZSWR0_CLR_INTR;
ZS_DELAY();
return (0);
}
static int
zsxint(cs, zc)
register struct zs_chanstate *cs;
register volatile struct zschan *zc;
{
register int i = cs->cs_tbc;
if (i == 0) {
zc->zc_csr = ZSWR0_RESET_TXINT;
ZS_DELAY();
zc->zc_csr = ZSWR0_CLR_INTR;
ZS_DELAY();
return (ZRING_MAKE(ZRING_XINT, 0));
}
cs->cs_tbc = i - 1;
zc->zc_data = *cs->cs_tba++;
ZS_DELAY();
zc->zc_csr = ZSWR0_CLR_INTR;
ZS_DELAY();
return (0);
}
static int
zssint(cs, zc)
register struct zs_chanstate *cs;
register volatile struct zschan *zc;
{
register int rr0;
rr0 = zc->zc_csr;
ZS_DELAY();
zc->zc_csr = ZSWR0_RESET_STATUS;
ZS_DELAY();
zc->zc_csr = ZSWR0_CLR_INTR;
ZS_DELAY();
/*
* The chip's hardware flow control is, as noted in zsreg.h,
* busted---if the DCD line goes low the chip shuts off the
* receiver (!). If we want hardware CTS flow control but do
* not have it, and carrier is now on, turn HFC on; if we have
* HFC now but carrier has gone low, turn it off.
*/
if (rr0 & ZSRR0_DCD) {
if (cs->cs_ttyp->t_cflag & CCTS_OFLOW &&
(cs->cs_creg[3] & ZSWR3_HFC) == 0) {
cs->cs_creg[3] |= ZSWR3_HFC;
ZS_WRITE(zc, 3, cs->cs_creg[3]);
}
} else {
if (cs->cs_creg[3] & ZSWR3_HFC) {
cs->cs_creg[3] &= ~ZSWR3_HFC;
ZS_WRITE(zc, 3, cs->cs_creg[3]);
}
}
if ((rr0 & ZSRR0_BREAK) && cs->cs_brkabort) {
#ifdef SUN4
/*
* XXX This might not be necessary. Test and
* delete if it isn't.
*/
if (cputyp==CPU_SUN4) {
while (zc->zc_csr & ZSRR0_BREAK)
ZS_DELAY();
}
#endif
zsabort();
return (0);
}
return (ZRING_MAKE(ZRING_SINT, rr0));
}
zsabort()
{
#ifdef DDB
Debugger();
#else
printf("stopping on keyboard abort\n");
callrom();
#endif
}
#ifdef KGDB
/*
* KGDB framing character received: enter kernel debugger. This probably
* should time out after a few seconds to avoid hanging on spurious input.
*/
zskgdb(unit)
int unit;
{
printf("zs%d%c: kgdb interrupt\n", unit >> 1, (unit & 1) + 'a');
kgdb_connect(1);
}
#endif
/*
* Print out a ring or fifo overrun error message.
*/
static void
zsoverrun(unit, ptime, what)
int unit;
long *ptime;
char *what;
{
if (*ptime != time.tv_sec) {
*ptime = time.tv_sec;
log(LOG_WARNING, "zs%d%c: %s overrun\n", unit >> 1,
(unit & 1) + 'a', what);
}
}
/*
* ZS software interrupt. Scan all channels for deferred interrupts.
*/
int
zssoft(arg)
void *arg;
{
register struct zs_chanstate *cs;
register volatile struct zschan *zc;
register struct linesw *line;
register struct tty *tp;
register int get, n, c, cc, unit, s, ringmask, ringsize;
int retval = 0;
for (cs = zslist; cs != NULL; cs = cs->cs_next) {
ringmask = cs->cs_ringmask;
get = cs->cs_rbget;
again:
n = cs->cs_rbput; /* atomic */
if (get == n) /* nothing more on this line */
continue;
retval = 1;
unit = cs->cs_unit; /* set up to handle interrupts */
zc = cs->cs_zc;
tp = cs->cs_ttyp;
line = &linesw[tp->t_line];
/*
* Compute the number of interrupts in the receive ring.
* If the count is overlarge, we lost some events, and
* must advance to the first valid one. It may get
* overwritten if more data are arriving, but this is
* too expensive to check and gains nothing (we already
* lost out; all we can do at this point is trade one
* kind of loss for another).
*/
ringsize = ringmask + 1;
n -= get;
if (n > ringsize) {
zsoverrun(unit, &cs->cs_rotime, "ring");
get += n - ringsize;
n = ringsize;
}
while (--n >= 0) {
/* race to keep ahead of incoming interrupts */
c = cs->cs_rbuf[get++ & ringmask];
switch (ZRING_TYPE(c)) {
case ZRING_RINT:
c = ZRING_VALUE(c);
if (c & ZSRR1_DO)
zsoverrun(unit, &cs->cs_fotime, "fifo");
cc = c >> 8;
if (c & ZSRR1_FE)
cc |= TTY_FE;
if (c & ZSRR1_PE)
cc |= TTY_PE;
/*
* this should be done through
* bstreams XXX gag choke
*/
if (unit == ZS_KBD)
kbd_rint(cc);
else if (unit == ZS_MOUSE)
ms_rint(cc);
else
line->l_rint(cc, tp);
break;
case ZRING_XINT:
/*
* Transmit done: change registers and resume,
* or clear BUSY.
*/
if (cs->cs_heldchange) {
s = splzs();
c = zc->zc_csr;
ZS_DELAY();
if ((c & ZSRR0_DCD) == 0)
cs->cs_preg[3] &= ~ZSWR3_HFC;
bcopy((caddr_t)cs->cs_preg,
(caddr_t)cs->cs_creg, 16);
zs_loadchannelregs(zc, cs->cs_creg);
splx(s);
cs->cs_heldchange = 0;
if (cs->cs_heldtbc &&
(tp->t_state & TS_TTSTOP) == 0) {
cs->cs_tbc = cs->cs_heldtbc - 1;
zc->zc_data = *cs->cs_tba++;
ZS_DELAY();
goto again;
}
}
tp->t_state &= ~TS_BUSY;
if (tp->t_state & TS_FLUSH)
tp->t_state &= ~TS_FLUSH;
else
ndflush(&tp->t_outq,
cs->cs_tba - (caddr_t)tp->t_outq.c_cf);
line->l_start(tp);
break;
case ZRING_SINT:
/*
* Status line change. HFC bit is run in
* hardware interrupt, to avoid locking
* at splzs here.
*/
c = ZRING_VALUE(c);
if ((c ^ cs->cs_rr0) & ZSRR0_DCD) {
cc = (c & ZSRR0_DCD) != 0;
if (line->l_modem(tp, cc) == 0)
zs_modem(cs, cc);
}
cs->cs_rr0 = c;
break;
default:
log(LOG_ERR, "zs%d%c: bad ZRING_TYPE (%x)\n",
unit >> 1, (unit & 1) + 'a', c);
break;
}
}
cs->cs_rbget = get;
goto again;
}
return (retval);
}
int
zsioctl(dev, cmd, data, flag, p)
dev_t dev;
u_long cmd;
caddr_t data;
int flag;
struct proc *p;
{
int unit = minor(dev);
struct zsinfo *zi = zscd.cd_devs[unit >> 1];
register struct zs_chanstate *cs = &zi->zi_cs[unit & 1];
register struct tty *tp = cs->cs_ttyp;
register int error, s;
error = linesw[tp->t_line].l_ioctl(tp, cmd, data, flag, p);
if (error >= 0)
return (error);
error = ttioctl(tp, cmd, data, flag, p);
if (error >= 0)
return (error);
switch (cmd) {
case TIOCSBRK:
s = splzs();
cs->cs_preg[5] |= ZSWR5_BREAK;
cs->cs_creg[5] |= ZSWR5_BREAK;
ZS_WRITE(cs->cs_zc, 5, cs->cs_creg[5]);
splx(s);
break;
case TIOCCBRK:
s = splzs();
cs->cs_preg[5] &= ~ZSWR5_BREAK;
cs->cs_creg[5] &= ~ZSWR5_BREAK;
ZS_WRITE(cs->cs_zc, 5, cs->cs_creg[5]);
splx(s);
break;
case TIOCGFLAGS: {
int bits = 0;
if (cs->cs_softcar)
bits |= TIOCFLAG_SOFTCAR;
if (cs->cs_creg[15] & ZSWR15_DCD_IE)
bits |= TIOCFLAG_CLOCAL;
if (cs->cs_creg[3] & ZSWR3_HFC)
bits |= TIOCFLAG_CRTSCTS;
*(int *)data = bits;
break;
}
case TIOCSFLAGS: {
int userbits, driverbits = 0;
error = suser(p->p_ucred, &p->p_acflag);
if (error != 0)
return (EPERM);
userbits = *(int *)data;
/*
* can have `local' or `softcar', and `rtscts' or `mdmbuf'
# defaulting to software flow control.
*/
if (userbits & TIOCFLAG_SOFTCAR && userbits & TIOCFLAG_CLOCAL)
return(EINVAL);
if (userbits & TIOCFLAG_MDMBUF) /* don't support this (yet?) */
return(ENXIO);
s = splzs();
if ((userbits & TIOCFLAG_SOFTCAR) || cs->cs_consio) {
cs->cs_softcar = 1; /* turn on softcar */
cs->cs_preg[15] &= ~ZSWR15_DCD_IE; /* turn off dcd */
cs->cs_creg[15] &= ~ZSWR15_DCD_IE;
ZS_WRITE(cs->cs_zc, 15, cs->cs_creg[15]);
} else if (userbits & TIOCFLAG_CLOCAL) {
cs->cs_softcar = 0; /* turn off softcar */
cs->cs_preg[15] |= ZSWR15_DCD_IE; /* turn on dcd */
cs->cs_creg[15] |= ZSWR15_DCD_IE;
ZS_WRITE(cs->cs_zc, 15, cs->cs_creg[15]);
tp->t_termios.c_cflag |= CLOCAL;
}
if (userbits & TIOCFLAG_CRTSCTS) {
cs->cs_preg[15] |= ZSWR15_CTS_IE;
cs->cs_creg[15] |= ZSWR15_CTS_IE;
ZS_WRITE(cs->cs_zc, 15, cs->cs_creg[15]);
cs->cs_preg[3] |= ZSWR3_HFC;
cs->cs_creg[3] |= ZSWR3_HFC;
ZS_WRITE(cs->cs_zc, 3, cs->cs_creg[3]);
tp->t_termios.c_cflag |= CRTSCTS;
} else {
/* no mdmbuf, so we must want software flow control */
cs->cs_preg[15] &= ~ZSWR15_CTS_IE;
cs->cs_creg[15] &= ~ZSWR15_CTS_IE;
ZS_WRITE(cs->cs_zc, 15, cs->cs_creg[15]);
cs->cs_preg[3] &= ~ZSWR3_HFC;
cs->cs_creg[3] &= ~ZSWR3_HFC;
ZS_WRITE(cs->cs_zc, 3, cs->cs_creg[3]);
tp->t_termios.c_cflag &= ~CRTSCTS;
}
splx(s);
break;
}
case TIOCSDTR:
zs_modem(cs, 1);
break;
case TIOCCDTR:
zs_modem(cs, 0);
break;
case TIOCMSET:
case TIOCMBIS:
case TIOCMBIC:
case TIOCMGET:
default:
return (ENOTTY);
}
return (0);
}
/*
* Start or restart transmission.
*/
static void
zsstart(tp)
register struct tty *tp;
{
register struct zs_chanstate *cs;
register int s, nch;
int unit = minor(tp->t_dev);
struct zsinfo *zi = zscd.cd_devs[unit >> 1];
cs = &zi->zi_cs[unit & 1];
s = spltty();
/*
* If currently active or delaying, no need to do anything.
*/
if (tp->t_state & (TS_TIMEOUT | TS_BUSY | TS_TTSTOP))
goto out;
/*
* If there are sleepers, and output has drained below low
* water mark, awaken.
*/
if (tp->t_outq.c_cc <= tp->t_lowat) {
if (tp->t_state & TS_ASLEEP) {
tp->t_state &= ~TS_ASLEEP;
wakeup((caddr_t)&tp->t_outq);
}
selwakeup(&tp->t_wsel);
}
nch = ndqb(&tp->t_outq, 0); /* XXX */
if (nch) {
register char *p = tp->t_outq.c_cf;
/* mark busy, enable tx done interrupts, & send first byte */
tp->t_state |= TS_BUSY;
(void) splzs();
cs->cs_preg[1] |= ZSWR1_TIE;
cs->cs_creg[1] |= ZSWR1_TIE;
ZS_WRITE(cs->cs_zc, 1, cs->cs_creg[1]);
cs->cs_zc->zc_data = *p;
ZS_DELAY();
cs->cs_tba = p + 1;
cs->cs_tbc = nch - 1;
} else {
/*
* Nothing to send, turn off transmit done interrupts.
* This is useful if something is doing polled output.
*/
(void) splzs();
cs->cs_preg[1] &= ~ZSWR1_TIE;
cs->cs_creg[1] &= ~ZSWR1_TIE;
ZS_WRITE(cs->cs_zc, 1, cs->cs_creg[1]);
}
out:
splx(s);
}
/*
* Stop output, e.g., for ^S or output flush.
*/
void
zsstop(tp, flag)
register struct tty *tp;
int flag;
{
register struct zs_chanstate *cs;
register int s, unit = minor(tp->t_dev);
struct zsinfo *zi = zscd.cd_devs[unit >> 1];
cs = &zi->zi_cs[unit & 1];
s = splzs();
if (tp->t_state & TS_BUSY) {
/*
* Device is transmitting; must stop it.
*/
cs->cs_tbc = 0;
if ((tp->t_state & TS_TTSTOP) == 0)
tp->t_state |= TS_FLUSH;
}
splx(s);
}
/*
* Set ZS tty parameters from termios.
*
* This routine makes use of the fact that only registers
* 1, 3, 4, 5, 9, 10, 11, 12, 13, 14, and 15 are written.
*/
static int
zsparam(tp, t)
register struct tty *tp;
register struct termios *t;
{
int unit = minor(tp->t_dev);
struct zsinfo *zi = zscd.cd_devs[unit >> 1];
register struct zs_chanstate *cs = &zi->zi_cs[unit & 1];
register int tmp, tmp5, cflag, s;
/*
* Because PCLK is only run at 4.9 MHz, the fastest we
* can go is 51200 baud (this corresponds to TC=1).
* This is somewhat unfortunate as there is no real
* reason we should not be able to handle higher rates.
*/
tmp = t->c_ospeed;
if (tmp < 0 || (t->c_ispeed && t->c_ispeed != tmp))
return (EINVAL);
if (tmp == 0) {
/* stty 0 => drop DTR and RTS */
zs_modem(cs, 0);
return (0);
}
tmp = BPS_TO_TCONST(PCLK / 16, tmp);
if (tmp < 2)
return (EINVAL);
cflag = t->c_cflag;
tp->t_ispeed = tp->t_ospeed = TCONST_TO_BPS(PCLK / 16, tmp);
tp->t_cflag = cflag;
/*
* Block interrupts so that state will not
* be altered until we are done setting it up.
*/
s = splzs();
cs->cs_preg[12] = tmp;
cs->cs_preg[13] = tmp >> 8;
cs->cs_preg[1] = ZSWR1_RIE | ZSWR1_TIE | ZSWR1_SIE;
switch (cflag & CSIZE) {
case CS5:
tmp = ZSWR3_RX_5;
tmp5 = ZSWR5_TX_5;
break;
case CS6:
tmp = ZSWR3_RX_6;
tmp5 = ZSWR5_TX_6;
break;
case CS7:
tmp = ZSWR3_RX_7;
tmp5 = ZSWR5_TX_7;
break;
case CS8:
default:
tmp = ZSWR3_RX_8;
tmp5 = ZSWR5_TX_8;
break;
}
/*
* Output hardware flow control on the chip is horrendous: if
* carrier detect drops, the receiver is disabled. Hence we
* can only do this when the carrier is on.
*/
tmp |= ZSWR3_RX_ENABLE;
if (cflag & CCTS_OFLOW) {
if (cs->cs_zc->zc_csr & ZSRR0_DCD)
tmp |= ZSWR3_HFC;
ZS_DELAY();
}
cs->cs_preg[3] = tmp;
cs->cs_preg[5] = tmp5 | ZSWR5_TX_ENABLE | ZSWR5_DTR | ZSWR5_RTS;
tmp = ZSWR4_CLK_X16 | (cflag & CSTOPB ? ZSWR4_TWOSB : ZSWR4_ONESB);
if ((cflag & PARODD) == 0)
tmp |= ZSWR4_EVENP;
if (cflag & PARENB)
tmp |= ZSWR4_PARENB;
cs->cs_preg[4] = tmp;
cs->cs_preg[9] = ZSWR9_MASTER_IE | ZSWR9_NO_VECTOR;
cs->cs_preg[10] = ZSWR10_NRZ;
cs->cs_preg[11] = ZSWR11_TXCLK_BAUD | ZSWR11_RXCLK_BAUD;
cs->cs_preg[14] = ZSWR14_BAUD_FROM_PCLK | ZSWR14_BAUD_ENA;
cs->cs_preg[15] = ZSWR15_BREAK_IE | ZSWR15_DCD_IE;
/*
* If nothing is being transmitted, set up new current values,
* else mark them as pending.
*/
if (cs->cs_heldchange == 0) {
if (cs->cs_ttyp->t_state & TS_BUSY) {
cs->cs_heldtbc = cs->cs_tbc;
cs->cs_tbc = 0;
cs->cs_heldchange = 1;
} else {
bcopy((caddr_t)cs->cs_preg, (caddr_t)cs->cs_creg, 16);
zs_loadchannelregs(cs->cs_zc, cs->cs_creg);
}
}
splx(s);
return (0);
}
/*
* Raise or lower modem control (DTR/RTS) signals. If a character is
* in transmission, the change is deferred.
*/
static void
zs_modem(cs, onoff)
struct zs_chanstate *cs;
int onoff;
{
int s, bis, and;
if (onoff) {
bis = ZSWR5_DTR | ZSWR5_RTS;
and = ~0;
} else {
bis = 0;
and = ~(ZSWR5_DTR | ZSWR5_RTS);
}
s = splzs();
cs->cs_preg[5] = (cs->cs_preg[5] | bis) & and;
if (cs->cs_heldchange == 0) {
if (cs->cs_ttyp->t_state & TS_BUSY) {
cs->cs_heldtbc = cs->cs_tbc;
cs->cs_tbc = 0;
cs->cs_heldchange = 1;
} else {
cs->cs_creg[5] = (cs->cs_creg[5] | bis) & and;
ZS_WRITE(cs->cs_zc, 5, cs->cs_creg[5]);
}
}
splx(s);
}
/*
* Write the given register set to the given zs channel in the proper order.
* The channel must not be transmitting at the time. The receiver will
* be disabled for the time it takes to write all the registers.
*/
static void
zs_loadchannelregs(zc, reg)
volatile struct zschan *zc;
u_char *reg;
{
int i;
zc->zc_csr = ZSM_RESET_ERR; /* reset error condition */
ZS_DELAY();
i = zc->zc_data; /* drain fifo */
ZS_DELAY();
i = zc->zc_data;
ZS_DELAY();
i = zc->zc_data;
ZS_DELAY();
ZS_WRITE(zc, 4, reg[4]);
ZS_WRITE(zc, 10, reg[10]);
ZS_WRITE(zc, 3, reg[3] & ~ZSWR3_RX_ENABLE);
ZS_WRITE(zc, 5, reg[5] & ~ZSWR5_TX_ENABLE);
ZS_WRITE(zc, 1, reg[1]);
ZS_WRITE(zc, 9, reg[9]);
ZS_WRITE(zc, 11, reg[11]);
ZS_WRITE(zc, 12, reg[12]);
ZS_WRITE(zc, 13, reg[13]);
ZS_WRITE(zc, 14, reg[14]);
ZS_WRITE(zc, 15, reg[15]);
ZS_WRITE(zc, 3, reg[3]);
ZS_WRITE(zc, 5, reg[5]);
}
#ifdef KGDB
/*
* Get a character from the given kgdb channel. Called at splhigh().
*/
static int
zs_kgdb_getc(arg)
void *arg;
{
register volatile struct zschan *zc = (volatile struct zschan *)arg;
while ((zc->zc_csr & ZSRR0_RX_READY) == 0)
ZS_DELAY();
return (zc->zc_data);
}
/*
* Put a character to the given kgdb channel. Called at splhigh().
*/
static void
zs_kgdb_putc(arg, c)
void *arg;
int c;
{
register volatile struct zschan *zc = (volatile struct zschan *)arg;
while ((zc->zc_csr & ZSRR0_TX_READY) == 0)
ZS_DELAY();
zc->zc_data = c;
ZS_DELAY();
}
/*
* Set up for kgdb; called at boot time before configuration.
* KGDB interrupts will be enabled later when zs0 is configured.
*/
void
zs_kgdb_init()
{
volatile struct zsdevice *addr;
volatile struct zschan *zc;
int unit, zs;
if (major(kgdb_dev) != ZSMAJOR)
return;
unit = minor(kgdb_dev);
/*
* Unit must be 0 or 1 (zs0).
*/
if ((unsigned)unit >= ZS_KBD) {
printf("zs_kgdb_init: bad minor dev %d\n", unit);
return;
}
zs = unit >> 1;
if ((addr = zsaddr[zs]) == NULL)
addr = zsaddr[zs] = findzs(zs);
unit &= 1;
zc = unit == 0 ? &addr->zs_chan[ZS_CHAN_A] : &addr->zs_chan[ZS_CHAN_B];
zs_kgdb_savedspeed = zs_getspeed(zc);
printf("zs_kgdb_init: attaching zs%d%c at %d baud\n",
zs, unit + 'a', kgdb_rate);
zs_reset(zc, 1, kgdb_rate);
kgdb_attach(zs_kgdb_getc, zs_kgdb_putc, (void *)zc);
}
#endif /* KGDB */