NetBSD/sys/arch/alpha/a12/a12dc.c

675 lines
14 KiB
C

/* $NetBSD: a12dc.c,v 1.1 1998/09/23 21:14:02 ross Exp $ */
/* [Notice revision 2.2]
* Copyright (c) 1997, 1998 Avalon Computer Systems, Inc.
* All rights reserved.
*
* Author: Ross Harvey
*
* 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 and
* author 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 Avalon Computer Systems, Inc. nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. This copyright will be assigned to The NetBSD Foundation on
* 1/1/2000 unless these terms (including possibly the assignment
* date) are updated in writing by Avalon prior to the latest specified
* assignment date.
*
* THIS SOFTWARE IS PROVIDED BY AVALON COMPUTER SYSTEMS, 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 AVALON OR THE 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.
*/
/*
* The A12 uses what DEC calls a "detached console", i.e., some of the console
* implementation is on a dedicated processor with its own RAM.
*
* The A12 Detached Console interface uses two 16 bit registers (per CPU), one
* going from the CPU to the a12ctrl processor and one going back the other
* way. The first is polled, the second produces a GInt.
*
* In the very early days we loaded program images through this interface.
*
* Consequently, it developed an overly complicated (but sort of fast)
* inverting sync/ack that isn't needed at all for its present application as
* a text console device.
*
* One possible solution: most of the channels are undefined, so a console
* channel using a stateless ack could be defined, with corresponding changes
* to the backplane 68360 code.
*
* This file is complicated somewhat by its use in three different kernels:
* NetBSD, the A12 CPU-resident console, and the a12ctrl backplane processor.
* (The protocol is symmetrical.)
*/
#include "opt_avalon_a12.h" /* Config options headers */
#ifndef BSIDE
#include <sys/cdefs.h> /* RCS ID & Copyright macro defns */
__KERNEL_RCSID(0, "$NetBSD: a12dc.c,v 1.1 1998/09/23 21:14:02 ross Exp $");
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/tty.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/uio.h>
#include <sys/device.h>
#include <dev/cons.h>
#include <machine/conf.h>
#include <machine/autoconf.h>
#include <machine/rpb.h>
#include <alpha/pci/a12creg.h>
#include <alpha/pci/a12cvar.h>
#include <alpha/pci/pci_a12.h>
#include "a12dcreg.h"
#define A12DC() /* Generate ctags(1) key */
#define MAX_MODULES 1
int a12dcmatch __P((struct device *, struct cfdata *, void *));
void a12dcattach __P((struct device *, struct device *, void *));
struct a12dc_softc {
struct device sc_dev;
} a12dc_softc;
struct cfattach a12dc_ca = {
sizeof(struct a12dc_softc), a12dcmatch, a12dcattach,
};
extern struct cfdriver a12dc_cd;
int a12dcfound; /* There Can Be Only One. */
struct a12dc_config { int im_not_used; } a12dc_configuration;
static struct tty *a12dc_tty[1];
void a12dcstart __P((struct tty *));
void a12dctimeout __P((void *));
int a12dcparam __P((struct tty *, struct termios *));
void a12dc_init(struct a12dc_config *, int);
static void a12cdrputc __P((int));
int a12dccngetc(dev_t);
void a12dccnputc(dev_t, int);
void a12dccnpollc(dev_t, int);
/* static int get_bc_char(int mn, int chan); */
/* static int get_bc_any(int,int *,int *); */
int a12dcintr __P((void *));
static void a12_worry(int worry_number);
static void A12InitBackDriver(int);
int
a12dcmatch(parent, match, aux)
struct device *parent;
struct cfdata *match;
void *aux;
{
struct pcibus_attach_args *pba = aux;
return cputype == ST_AVALON_A12
&& strcmp(pba->pba_busname, a12dc_cd.cd_name) == 0
&& !a12dcfound;
}
void
a12dcattach(parent, self, aux)
struct device *parent, *self;
void *aux;
{
struct tty *tp;
struct a12dc_config *ccp;
/* note that we've attached the chipset; can't have 2 A12Cs. */
a12dcfound = 1;
printf(": driver %s\n", "$Revision: 1.1 $");
tp = a12dc_tty[0] = ttymalloc();
tp->t_oproc = a12dcstart;
tp->t_param = a12dcparam;
tty_attach(tp);
ccp = &a12dc_configuration;
a12dc_init(ccp, 1);
}
void
a12dc_init(ccp, mallocsafe)
struct a12dc_config *ccp;
int mallocsafe;
{
}
/*
* XXX definitions specific to only one of the kernels will be moved into
* kernel-local include files. (One of these days.)
*/
#define spla12dc() spltty()
#define a12yield()
#define CDRADDR(m) ((mmreg_t *)(REGADDR(A12_CDR))) /* ignore m */
#define modenormal()
#define mb() alpha_mb()
#define wmb() alpha_wmb()
#else
#include "product.def"
#include "ghs.h"
#include "a12ctrl.h"
#define wmb()
#define mb()
#define swpipl(a) (a)
#define splx(a) (a)
#define spla12dc()
#define hrhpanic(s,v) panic((s))
#define CDRADDR(m) ((mmreg_t *)(A12_BACKPLANE+(m)))
#endif
static int msgetput __P((register mstate_type *, int, int));
static void checkinit __P((void));
static void
A12InitBackDriver(int m)
{
/*
* XXX memset() would be good here, but there is a (temporary) reason
* for all this right now
*/
a12_mstate[m].xcdr = CDRADDR(m);
a12_mstate[m].txrdy_out = A12C_TXRDY;
a12_mstate[m].txrdy_in = A12C_TXRDY;
a12_mstate[m].txack_out = A12C_TXACK;
a12_mstate[m].txack_in = A12C_TXACK;
a12_mstate[m].txsv = tx_idle;
a12_mstate[m].rxsv = rx_idle;
a12_mstate[m].reset_scanner= 1;
a12_mstate[m].reset_loader = 1;
a12_mstate[m].up = 1;
a12_mstate[m].lastr = 0; /* last char. received */
a12_mstate[m].lastrc = 0; /* last received channel */
a12_mstate[m].rx_busy_wait = 0; /* last received busy ticks */
a12_mstate[m].rx_busy_cnt = 0; /* last received start clock */
a12_mstate[m].lastt = 0; /* last char. trasmitted */
a12_mstate[m].lasttc = 0; /* last transmit channel */
a12_mstate[m].tx_busy_wait = 0; /* last transmitted busy ticks */
a12_mstate[m].tx_busy_cnt = 0; /* last transmitted start clock */
a12_mstate[m].tbytes = 0;
a12_mstate[m].tblkbytes = 0;
a12_mstate[m].tblks = 0;
a12_mstate[m].blwobe = 0;
a12_mstate[m].blwbe = 0;
a12_mstate[m].max_blkretry = 0;
a12_mstate[m].max_blktime = 0;
a12_mstate[m].max_blktimesz = 0;
a12_mstate[m].avg_blksz = 0;
a12_mstate[m].avg_blktime = 0;
a12_mstate[m].retry_time = 0;
}
static void
checkinit()
{
static int did_init;
if (!did_init) {
A12InitBackDriver(0);
did_init = 1;
}
}
int
a12dcopen(dev, flag, mode, p)
dev_t dev;
int flag, mode;
struct proc *p;
{
int unit = minor(dev);
struct tty *tp;
int s;
if (unit >= 1)
return ENXIO;
#ifdef KGDB
if (flags & COM_HW_KGDB)
return EBUSY;
#endif
if (!a12dc_tty[unit]) {
tp = a12dc_tty[unit] = ttymalloc();
tty_attach(tp);
} else
tp = a12dc_tty[unit];
if ((tp->t_state & TS_ISOPEN) &&
(tp->t_state & TS_XCLUDE) &&
p->p_ucred->cr_uid != 0)
return EBUSY;
s = spltty();
tp->t_oproc = a12dcstart;
tp->t_param = a12dcparam;
tp->t_dev = dev;
if ((tp->t_state & TS_ISOPEN) == 0) {
tp->t_state |= TS_CARR_ON;
ttychars(tp);
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_cflag = TTYDEF_CFLAG|CLOCAL;
tp->t_lflag = TTYDEF_LFLAG;
tp->t_ispeed = tp->t_ospeed = 9600;
ttsetwater(tp);
/* XXX XXX XXX
a12_intr_register_icw(a12dcintr);
*/
} else if (tp->t_state&TS_XCLUDE && p->p_ucred->cr_uid != 0) {
splx(s);
return EBUSY;
}
splx(s);
return (*linesw[tp->t_line].l_open)(dev, tp);
}
int
a12dcclose(dev, flag, mode, p)
dev_t dev;
int flag, mode;
struct proc *p;
{
int unit = minor(dev);
struct tty *tp = a12dc_tty[unit];
(*linesw[tp->t_line].l_close)(tp, flag);
ttyclose(tp);
return 0;
}
int
a12dcread(dev, uio, flag)
dev_t dev;
struct uio *uio;
int flag;
{
struct tty *tp = a12dc_tty[minor(dev)];
return ((*linesw[tp->t_line].l_read)(tp, uio, flag));
}
int
a12dcwrite(dev, uio, flag)
dev_t dev;
struct uio *uio;
int flag;
{
struct tty *tp = a12dc_tty[minor(dev)];
return ((*linesw[tp->t_line].l_write)(tp, uio, flag));
}
int
a12dcioctl(dev, cmd, data, flag, p)
dev_t dev;
u_long cmd;
caddr_t data;
int flag;
struct proc *p;
{
int unit = minor(dev);
struct tty *tp = a12dc_tty[unit];
int error;
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;
return ENOTTY;
}
int
a12dcparam(tp, t)
struct tty *tp;
struct termios *t;
{
return 0;
}
void
a12dcstart(tp)
struct tty *tp;
{
int s;
s = spltty();
if (tp->t_state & (TS_TTSTOP | TS_BUSY))
goto out;
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);
}
tp->t_state |= TS_BUSY;
while (tp->t_outq.c_cc != 0)
a12dccnputc(tp->t_dev, getc(&tp->t_outq));
tp->t_state &= ~TS_BUSY;
out:
splx(s);
}
/*
* Stop output on a line.
*/
void
a12dcstop(tp, flag)
struct tty *tp;
{
int s;
s = spltty();
if (tp->t_state & TS_BUSY)
if ((tp->t_state & TS_TTSTOP) == 0)
tp->t_state |= TS_FLUSH;
splx(s);
}
int
a12dcintr(v)
void *v;
{
#if 1
DIE();
#else
struct tty *tp = v;
u_char c;
while (a12dccnlookc(tp->t_dev, &c)) {
if (tp->t_state & TS_ISOPEN)
(*linesw[tp->t_line].l_rint)(c, tp);
}
#endif
}
struct tty *
a12dctty(dev)
dev_t dev;
{
if (minor(dev) != 0)
panic("a12dctty: bogus");
return a12dc_tty[0];
}
int
a12dccnattach()
{
int i;
static struct consdev a12dccons = {
NULL, NULL, a12dccngetc, a12dccnputc, a12dccnpollc, NODEV,
CN_NORMAL
};
for(i = 0; i < nchrdev; ++i)
if (cdevsw[i].d_open == a12dcopen)
a12dccons.cn_dev = makedev(i, 0);
cn_tab = &a12dccons;
return 0;
}
int
a12dccngetc(dev)
dev_t dev;
{
for(;;)
continue;
}
void
a12dccnputc(dev, c)
dev_t dev;
int c;
{
a12cdrputc(c);
}
void
a12dccnpollc(dev, on)
dev_t dev;
int on;
{
}
static void
a12cdrputc(int c)
{
int s = spla12dc();
checkinit();
while(msgetput(MSP(0),CHANNEL_MONITOR,c)) {
/*if(check_cdr_ok)
check_cdr();*/
DELAY(100);
}
splx(s);
}
#if 0
static void
check_cdr()
{
int bpchar,cnumber;
static int srom_word_address,
kmsg;
int push_check_ok;
int ipl;
if(!get_bc_any(0,&bpchar,&cnumber))
return;
switch(cnumber) {
default:
printf("Unknown cdr channel %d",cnumber);
break;
case CHANNEL_KDATA:
if(!kmsg)
printf("Ignoring new kernel\n");;
kmsg = 1;
break;
case CHANNEL_KMARK:
kmsg = 0;
break;
#ifndef _KERNEL /* NetBSD kernel, that is. "if defined: rtmon kernel" */
case CHANNEL_SROM_A:
srom_word_address = bpchar;
break;
case CHANNEL_SROM_D:
push_check_ok = check_cdr_ok;
check_cdr_ok = 0;
ipl = swpipl(7);
write_ethernet_srom(srom_word_address,bpchar);
++srom_word_address;
(void)swpipl(ipl);
check_cdr_ok = push_check_ok;
break;
case CHANNEL_MULTI:
logmchar(bpchar,"<");
SerialByteReceived(bpchar);
break;
case CHANNEL_MONITOR:
if (bpchar == CPX_PANIC)
/* TJF - Could kill all processes and then panic? */
hrhpanic("Panic in cooperating CPU.",0);
else cpxchar(bpchar);
break;
#endif
case CHANNEL_CONSOLE:
#ifdef A12CON /* XXX this can be done much better */
virtual_keyboard_byte(bpchar);
#else
ipl7putc(bpchar);
#endif
break;
}
}
#endif
#if 0
static int
get_bc_any(int mn, int *c, int *chan) {
mstate_type *ms = &a12_mstate[mn];
(void)mcgetput(mn, 0, -1);
if(ms->rxsv==rx_busy) {
*c = ms->c2b_char & 0xff;
*chan = ms->c2b_channel;
ms->rxsv = rx_idle;
return 1;
}
return 0;
}
static int
get_bc_char(int mn, int chan) {
int newc,newchan;
if(get_bc_any(mn,&newc,&newchan)) {
if(newchan!=chan)
printf("receiver busy on dumb channel %d", newchan);
return newc;
}
return -1;
}
#endif
static int
msgetput(register mstate_type *ms, int channel, int c)
{
int i,t;
int ipl;
if(c==-2 || ms->up==0) {
if(c!=-2)
a12_worry(10);
ms->txsv = tx_idle;
ms->rxsv = rx_idle;
ms->cdr = 0;
return 0;
}
if(!(0<=channel && channel<64)) {
a12_worry(7);
return 0;
}
ipl = spla12dc();
for(i=0; i<20; ++i) {
switch(ms->txsv) {
case tx_idle:
if(c!=-1) {
ms->lastt = c;
ms->lasttc = channel;
ms->cdr = (ms->cdr & (A12C_TXACK | A12C_TXRDY))
| channel<<8
| c;
*ms->xcdr = ms->cdr;
wmb();
ms->cdr = ((ms->cdr & A12C_TXACK) &~ A12C_TXRDY)
| ms->txrdy_out
| channel<<8
| c;
*ms->xcdr = ms->cdr;
wmb();
ms->txsv = tx_braaw;
c = -1;
continue;
}
break;
case tx_braaw:
mb();
if((*ms->xcdr & A12C_TXACK)==ms->txack_in) {
ms->txrdy_out ^= A12C_TXRDY;
ms->txack_in ^= A12C_TXACK;
ms->txsv = tx_idle;
ms->tbytes++;
continue;
}
break;
default:
(void)splx(ipl);
a12_worry(5);
return 0;
}
switch(ms->rxsv) {
case rx_idle:
mb();
t = *ms->xcdr;
if((t & A12C_TXRDY)==ms->txrdy_in) {
ms->cdr = (ms->cdr & ~A12C_TXACK)
| ms->txack_out;
*ms->xcdr = ms->cdr;
wmb();
ms->c2b_char = t & 0xff;
ms->lastr = ms->c2b_char;
ms->c2b_channel = (t & CHMASK) >> 8;
ms->lastrc = ms->c2b_channel;
ms->txrdy_in ^= A12C_TXRDY;
ms->txack_out^= A12C_TXACK;
ms->rxsv = rx_busy;
continue;
}
break;
case rx_busy:
break;
default:
(void)splx(ipl);
a12_worry(6);
return 0;
}
if(c==-1)
break;
}
(void)splx(ipl);
return c!=-1;
}
static void
a12_worry(int worry_number)
{
a12console_last_unexpected_error = worry_number;
}