* Copyright (c) 1988 University of Utah.
* Copyright (c) 1982, 1986, 1990 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* the Systems Programming Group of the University of Utah Computer
* %sccs.include.redist.c%
* from: $Hdr: dcm.c 1.17 89/10/01$
* @(#)dcm.c 7.4 (Berkeley) %G%
* Test console/kgdb support.
#ifndef DEFAULT_BAUD_RATE
#define DEFAULT_BAUD_RATE 9600
int dcmprobe(), dcmstart(), dcmintr(), dcmparam();
struct driver dcmdriver
= {
#define NDCMLINE (NDCM*4)
struct tty dcm_tty
[NDCMLINE
];
struct dcmdevice
*dcm_addr
[NDCM
];
struct speedtab dcmspeedtab
[] = {
/* u-sec per character based on baudrate (assumes 1 start/8 data/1 stop bit) */
#define DCM_USPERCH(s) (10000000 / (s))
* Per board interrupt scheme. 16.7ms is the polling interrupt rate
* (16.7ms is about 550 buad, 38.4k is 72 chars in 16.7ms).
int dcmistype
= -1; /* -1 == dynamic, 0 == timer, 1 == perchar */
int dcminterval
= 5; /* interval (secs) between checks */
int dis_perchar
; /* non-zero if interrupting per char */
long dis_time
; /* last time examined */
int dis_intr
; /* recv interrupts during last interval */
int dis_char
; /* characters read during last interval */
int dcmdefaultrate
= DEFAULT_BAUD_RATE
;
extern struct tty
*constty
;
extern int kgdb_debug_init
;
#define DDB_OPENCLOSE 0x100
long xints
; /* # of xmit ints */
long xchars
; /* # of xmit chars */
long xempty
; /* times outq is empty in dcmstart */
long xrestarts
; /* times completed while xmitting */
long rints
; /* # of recv ints */
long rchars
; /* # of recv chars */
long xsilo
[DCMXBSIZE
+2]; /* times this many chars xmit on one int */
long rsilo
[DCMRBSIZE
+2]; /* times this many chars read on one int */
#define BOARD(x) (((x) >> 2) & 0x3f)
#define PORT(x) ((x) & 3)
#define MKUNIT(b,p) (((b) << 2) | (p))
register struct hp_device
*hd
;
register struct dcmdevice
*dcm
;
dcm
= (struct dcmdevice
*)hd
->hp_addr
;
if ((dcm
->dcm_rsid
& 0x1f) != DCMID
)
isconsole
= (brd
== BOARD(dcmconsole
));
* XXX selected console device (CONSUNIT) as determined by
* dcmcnprobe does not agree with logical numbering imposed
* by the config file (i.e. lowest address DCM is not unit
* CONSUNIT). Don't recognize this card.
if (isconsole
&& dcm
!= dcm_addr
[BOARD(dcmconsole
)])
* Empirically derived self-test magic
DELAY(50000); /* 5000 is not long enough */
while ((dcm
->dcm_ic
& IC_IR
) == 0)
DELAY(50000) /* XXX why is this needed ???? */
while ((dcm
->dcm_iir
& IIR_SELFT
) == 0)
DELAY(50000) /* XXX why is this needed ???? */
if (dcm
->dcm_stcon
!= ST_OK
) {
printf("dcm%d: self test failed: %x\n",
hd
->hp_ipl
= DCMIPL(dcm
->dcm_ic
);
dcmsoftCAR
[brd
] = hd
->hp_flags
;
dcmisr
[brd
].isr_ipl
= hd
->hp_ipl
;
dcmisr
[brd
].isr_arg
= brd
;
dcmisr
[brd
].isr_intr
= dcmintr
;
if (major(kgdb_dev
) == 2 && BOARD(kgdb_dev
) == brd
) {
if (dcmconsole
== UNIT(kgdb_dev
))
kgdb_dev
= -1; /* can't debug over console port */
(void) dcminit(kgdb_dev
, kgdb_rate
);
printf("dcm%d: kgdb waiting...",
printf("dcm%d: kgdb enabled\n",
if (dcmistype
== DIS_TIMER
)
dcmsetischeme(brd
, DIS_RESET
|DIS_TIMER
);
dcmsetischeme(brd
, DIS_RESET
|DIS_PERCHAR
);
dcm
->dcm_mdmmsk
= MI_CD
; /* enable modem carrier detect intr */
dcm
->dcm_ic
= IC_IE
; /* turn all interrupts on */
* Need to reset baud rate, etc. of next print so reset dcmconsole.
* Also make sure console is always "hardwired"
dcmsoftCAR
[brd
] |= (1 << PORT(dcmconsole
));
if (unit
>= NDCMLINE
|| (dcm_active
& (1 << brd
)) == 0)
if (unit
== UNIT(kgdb_dev
))
if ((tp
->t_state
& TS_ISOPEN
) == 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
= TTYDEF_SPEED
;
(void) dcmparam(tp
, &tp
->t_termios
);
} else if (tp
->t_state
&TS_XCLUDE
&& u
.u_uid
!= 0)
if (PORT(unit
) == 0) /* enable port 0 */
(void) dcmmctl(dev
, MO_ON
, DMSET
);
if (dcmsoftCAR
[brd
] & (1 << PORT(unit
)))
tp
->t_state
|= TS_CARR_ON
;
else if (PORT(unit
)) /* Only port 0 has modem control */
tp
->t_state
|= TS_CARR_ON
;
else if (dcmmctl(dev
, MO_OFF
, DMGET
) & MI_CD
)
tp
->t_state
|= TS_CARR_ON
;
while (!(flag
&O_NONBLOCK
) && !(tp
->t_cflag
&CLOCAL
) &&
(tp
->t_state
& TS_CARR_ON
) == 0) {
if ((error
= tsleep((caddr_t
)&tp
->t_rawq
, TTIPRI
| PCATCH
,
(error
= ttclosed(tp
))) {
tp
->t_state
&= ~TS_WOPEN
;
if (dcmdebug
& DDB_OPENCLOSE
)
printf("dcmopen: u %x st %x fl %x\n",
unit
, tp
->t_state
, tp
->t_flags
);
return ((*linesw
[tp
->t_line
].l_open
)(dev
, tp
));
(*linesw
[tp
->t_line
].l_close
)(tp
);
if (tp
->t_cflag
&HUPCL
|| tp
->t_state
&TS_WOPEN
||
(tp
->t_state
&TS_ISOPEN
) == 0)
(void) dcmmctl(dev
, MO_OFF
, DMSET
);
if (dcmdebug
& DDB_OPENCLOSE
)
printf("dcmclose: u %x st %x fl %x\n",
unit
, tp
->t_state
, tp
->t_flags
);
tp
= &dcm_tty
[UNIT(dev
)];
return ((*linesw
[tp
->t_line
].l_read
)(tp
, uio
, flag
));
* XXX we disallow virtual consoles if the physical console is
* a serial port. This is in case there is a display attached that
* is not the console. In that situation we don't need/want the X
* server taking over the console.
if (constty
&& unit
== dcmconsole
)
return ((*linesw
[tp
->t_line
].l_write
)(tp
, uio
, flag
));
register struct dcmdevice
*dcm
= dcm_addr
[brd
];
register struct dcmischeme
*dis
;
int i
, code
, pcnd
[4], mcnd
, delta
;
* Do all guarded register accesses right off to minimize
if ((dcm
->dcm_ic
& IC_IR
) == 0) {
for (i
= 0; i
< 4; i
++) {
pcnd
[i
] = dcm
->dcm_icrtab
[i
].dcm_data
;
dcm
->dcm_icrtab
[i
].dcm_data
= 0;
code
= dcm
->dcm_iir
& IIR_MASK
;
dcm
->dcm_iir
= 0; /* XXX doc claims read clears interrupt?! */
printf("dcmintr(%d): iir %x p0 %x p1 %x p2 %x p3 %x m %x\n",
brd
, code
, pcnd
[0], pcnd
[1], pcnd
[2], pcnd
[3], mcnd
);
dcmpint(MKUNIT(brd
, 0), pcnd
[0], dcm
);
dcmpint(MKUNIT(brd
, 1), pcnd
[1], dcm
);
dcmpint(MKUNIT(brd
, 2), pcnd
[2], dcm
);
dcmpint(MKUNIT(brd
, 3), pcnd
[3], dcm
);
dcmmint(MKUNIT(brd
, 0), mcnd
, dcm
); /* always port 0 */
* Chalk up a receiver interrupt if the timer running or one of
* the ports reports a special character interrupt.
if ((code
& IIR_TIMEO
) ||
((pcnd
[0]|pcnd
[1]|pcnd
[2]|pcnd
[3]) & IT_SPEC
))
* See if it is time to check/change the interrupt rate.
(delta
= time
.tv_sec
- dis
->dis_time
) >= dcminterval
) {
* If currently per-character and averaged over 70 interrupts
* per-second (66 is threshold of 600 baud) in last interval,
* XXX decay counts ala load average to avoid spikes?
if (dis
->dis_perchar
&& dis
->dis_intr
> 70 * delta
)
dcmsetischeme(brd
, DIS_TIMER
);
* If currently using timer and had more interrupts than
* received characters in the last interval, switch back
* to per-character. Note that after changing to per-char
* we must process any characters already in the queue
* since they may have arrived before the bitmap was setup.
else if (!dis
->dis_perchar
&& dis
->dis_intr
> dis
->dis_char
) {
dcmsetischeme(brd
, DIS_PERCHAR
);
dis
->dis_intr
= dis
->dis_char
= 0;
dis
->dis_time
= time
.tv_sec
;
* Port interrupt. Can be two things:
* First, it might be a special character (exception interrupt);
* Second, it may be a buffer empty (transmit interrupt);
struct tty
*tp
= &dcm_tty
[unit
];
dcmreadbuf(unit
, dcm
, tp
);
register struct dcmdevice
*dcm
;
for (i
= 0; i
< 4; i
++, tp
++, unit
++)
dcmreadbuf(unit
, dcm
, tp
);
dcmreadbuf(unit
, dcm
, tp
)
register struct dcmdevice
*dcm
;
register struct dcmpreg
*pp
= dcm_preg(dcm
, port
);
register struct dcmrfifo
*fifo
;
struct dcmstats
*dsp
= &dcmstats
[BOARD(unit
)];
* TS_WOPEN catches a race when switching to polling mode from dcmrint
if ((tp
->t_state
& (TS_ISOPEN
|TS_WOPEN
)) == 0) {
if (unit
== UNIT(kgdb_dev
) &&
(head
= pp
->r_head
& RX_MASK
) != (pp
->r_tail
& RX_MASK
) &&
dcm
->dcm_rfifos
[3-port
][head
>>1].data_char
== '!') {
pp
->r_head
= (head
+ 2) & RX_MASK
;
printf("kgdb trap from dcm%d\n", unit
);
pp
->r_head
= pp
->r_tail
& RX_MASK
;
head
= pp
->r_head
& RX_MASK
;
fifo
= &dcm
->dcm_rfifos
[3-port
][head
>>1];
* XXX upper bound on how many chars we will take in one swallow?
while (head
!= (pp
->r_tail
& RX_MASK
)) {
* Get character/status and update head pointer as fast
* as possible to make room for more characters.
head
= (head
+ 2) & RX_MASK
;
fifo
= head
? fifo
+1 : &dcm
->dcm_rfifos
[3-port
][0];
if (dcmdebug
& DDB_INPUT
)
printf("dcmreadbuf(%d): c%x('%c') s%x f%x h%x t%x\n",
unit
, c
&0xFF, c
, stat
&0xFF,
tp
->t_flags
, head
, pp
->r_tail
);
* Check for and handle errors
if (dcmdebug
& (DDB_INPUT
|DDB_SIOERR
))
printf("dcmreadbuf(%d): err: c%x('%c') s%x\n",
if (stat
& (RD_BD
| RD_FE
))
"dcm%d: silo overflow\n", unit
);
"dcm%d: uart overflow\n", unit
);
(*linesw
[tp
->t_line
].l_rint
)(c
, tp
);
dcmischeme
[BOARD(unit
)].dis_char
+= nch
;
dsp
->rsilo
[DCMRBSIZE
+1]++;
if (tp
->t_state
& TS_FLUSH
)
tp
->t_state
&= ~TS_FLUSH
;
(*linesw
[tp
->t_line
].l_start
)(tp
);
register struct dcmdevice
*dcm
;
if (dcmdebug
& DDB_MODEM
)
printf("dcmmint: unit %x mcnd %x\n", unit
, mcnd
);
if ((dcmsoftCAR
[BOARD(unit
)] & (1 << PORT(unit
))) == 0) {
(void) (*linesw
[tp
->t_line
].l_modem
)(tp
, 1);
else if ((*linesw
[tp
->t_line
].l_modem
)(tp
, 0) == 0) {
dcm
->dcm_mdmout
&= ~(MO_DTR
| MO_RTS
);
DELAY(10); /* time to change lines */
dcmioctl(dev
, cmd
, data
, flag
)
register int unit
= UNIT(dev
);
register struct dcmdevice
*dcm
;
if (dcmdebug
& DDB_IOCTL
)
printf("dcmioctl: unit %d cmd %x data %x flag %x\n",
error
= (*linesw
[tp
->t_line
].l_ioctl
)(tp
, cmd
, data
, flag
);
error
= ttioctl(tp
, cmd
, data
, flag
);
dcm
= dcm_addr
[BOARD(unit
)];
* Wait for transmitter buffer to empty
while (dcm
->dcm_thead
[port
].ptr
!= dcm
->dcm_ttail
[port
].ptr
)
DELAY(DCM_USPERCH(tp
->t_ospeed
));
dcm
->dcm_cmdtab
[port
].dcm_data
|= CT_BRK
;
dcm
->dcm_cr
|= (1 << port
); /* start break */
dcm
->dcm_cmdtab
[port
].dcm_data
|= CT_BRK
;
dcm
->dcm_cr
|= (1 << port
); /* end break */
(void) dcmmctl(dev
, MO_ON
, DMBIS
);
(void) dcmmctl(dev
, MO_ON
, DMBIC
);
(void) dcmmctl(dev
, *(int *)data
, DMSET
);
(void) dcmmctl(dev
, *(int *)data
, DMBIS
);
(void) dcmmctl(dev
, *(int *)data
, DMBIC
);
*(int *)data
= dcmmctl(dev
, 0, DMGET
);
register struct termios
*t
;
register struct dcmdevice
*dcm
;
register int port
, mode
, cflag
= t
->c_cflag
;
int ospeed
= ttspeedtab(t
->c_ospeed
, dcmspeedtab
);
/* check requested parameters */
if (ospeed
< 0 || (t
->c_ispeed
&& t
->c_ispeed
!= t
->c_ospeed
))
tp
->t_ispeed
= t
->c_ispeed
;
tp
->t_ospeed
= t
->c_ospeed
;
(void) dcmmctl(UNIT(tp
->t_dev
), MO_OFF
, DMSET
);
if (dcmdebug
& DDB_PARAM
)
printf("dcmparam(%d): cflag %x mode %x speed %d uperch %d\n",
UNIT(tp
->t_dev
), cflag
, mode
, tp
->t_ospeed
,
DCM_USPERCH(tp
->t_ospeed
));
dcm
= dcm_addr
[BOARD(tp
->t_dev
)];
* Wait for transmitter buffer to empty.
while (dcm
->dcm_thead
[port
].ptr
!= dcm
->dcm_ttail
[port
].ptr
)
DELAY(DCM_USPERCH(tp
->t_ospeed
));
* Make changes known to hardware.
dcm
->dcm_data
[port
].dcm_baud
= ospeed
;
dcm
->dcm_data
[port
].dcm_conf
= mode
;
dcm
->dcm_cmdtab
[port
].dcm_data
|= CT_CON
;
dcm
->dcm_cr
|= (1 << port
);
* Delay for config change to take place. Weighted by buad.
DELAY(16 * DCM_USPERCH(tp
->t_ospeed
));
register struct dcmdevice
*dcm
;
register struct dcmpreg
*pp
;
register struct dcmtfifo
*fifo
;
register unsigned tail
, next
;
struct dcmstats
*dsp
= &dcmstats
[BOARD(tp
->t_dev
)];
if (dcmdebug
& DDB_OUTPUT
)
printf("dcmstart(%d): state %x flags %x outcc %d\n",
UNIT(tp
->t_dev
), tp
->t_state
, tp
->t_flags
,
if (tp
->t_state
& (TS_TIMEOUT
|TS_BUSY
|TS_TTSTOP
))
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_WCOLL
);
tp
->t_state
&= ~TS_WCOLL
;
if (tp
->t_outq
.c_cc
== 0) {
dcm
= dcm_addr
[BOARD(tp
->t_dev
)];
pp
= dcm_preg(dcm
, port
);
tail
= pp
->t_tail
& TX_MASK
;
next
= (tail
+ 1) & TX_MASK
;
head
= pp
->t_head
& TX_MASK
;
fifo
= &dcm
->dcm_tfifos
[3-port
][tail
];
nch
= q_to_b(&tp
->t_outq
, buf
, (head
- next
) & TX_MASK
);
if (dcmdebug
& DDB_OUTPUT
)
printf("\thead %x tail %x nch %d\n", head
, tail
, nch
);
* Loop transmitting all the characters we can.
for (bp
= buf
; --nch
>= 0; bp
++) {
* If this is the first character,
* get the hardware moving right now.
dcm
->dcm_cmdtab
[port
].dcm_data
|= CT_TX
;
dcm
->dcm_cr
|= (1 << port
);
fifo
= tail
? fifo
+1 : &dcm
->dcm_tfifos
[3-port
][0];
next
= (next
+ 1) & TX_MASK
;
* Head changed while we were loading the buffer,
* go back and load some more if we can.
if (tp
->t_outq
.c_cc
&& head
!= (pp
->t_head
& TX_MASK
)) {
head
= pp
->t_head
& TX_MASK
;
* Kick it one last time in case it finished while we were
dcm
->dcm_cmdtab
[port
].dcm_data
|= CT_TX
;
dcm
->dcm_cr
|= (1 << port
);
printf("dcmstart(%d): head %x tail %x outqcc %d ch %d\n",
UNIT(tp
->t_dev
), head
, tail
, tp
->t_outq
.c_cc
, tch
);
dsp
->xsilo
[DCMXBSIZE
+1]++;
if (tp
->t_state
& TS_BUSY
) {
/* XXX is there some way to safely stop transmission? */
if ((tp
->t_state
&TS_TTSTOP
)==0)
register struct dcmdevice
*dcm
;
* Only port 0 has modem control lines.
* XXX ok for now but needs to changed for the 8 port board.
if (PORT(UNIT(dev
)) != 0)
dcm
= dcm_addr
[BOARD(UNIT(dev
))];
dcm
->dcm_mdmout
&= ~bits
;
DELAY(10); /* delay until done */
* Set board to either interrupt per-character or at a fixed interval.
dcmsetischeme(brd
, flags
)
register struct dcmdevice
*dcm
= dcm_addr
[brd
];
register struct dcmischeme
*dis
= &dcmischeme
[brd
];
int perchar
= flags
& DIS_PERCHAR
;
if (dcmdebug
& DDB_INTSCHM
)
printf("dcmsetischeme(%d, %d): cur %d, ints %d, chars %d\n",
brd
, perchar
, dis
->dis_perchar
,
dis
->dis_intr
, dis
->dis_char
);
if ((flags
& DIS_RESET
) == 0 && perchar
== dis
->dis_perchar
) {
printf("dcmsetischeme(%d): redundent request %d\n",
* If perchar is non-zero, we enable interrupts on all characters
* otherwise we disable perchar interrupts and use periodic
dis
->dis_perchar
= perchar
;
mask
= perchar
? 0xf : 0x0;
for (i
= 0; i
< 256; i
++)
dcm
->dcm_bmap
[i
].data_data
= mask
;
* Don't slow down tandem mode, interrupt on flow control
* chars for any port on the board.
register struct tty
*tp
= &dcm_tty
[MKUNIT(brd
, 0)];
for (i
= 0; i
< 4; i
++, tp
++) {
if ((c
= tp
->t_cc
[VSTART
]) != _POSIX_VDISABLE
)
dcm
->dcm_bmap
[c
].data_data
|= (1 << i
);
if ((c
= tp
->t_cc
[VSTOP
]) != _POSIX_VDISABLE
)
dcm
->dcm_bmap
[c
].data_data
|= (1 << i
);
* Board starts with timer disabled so if first call is to
* set perchar mode then we don't want to toggle the timer.
if (flags
== (DIS_RESET
|DIS_PERCHAR
))
* Toggle card 16.7ms interrupts (we first make sure that card
* has cleared the bit so it will see the toggle).
while (dcm
->dcm_cr
& CR_TIMER
)
* Following are all routines needed for DCM to act as console
#include "machine/cons.h"
register struct hp_hw
*hw
;
* Implicitly assigns the lowest select code DCM card found to be
* logical unit 0 (actually CONUNIT). If your config file does
* anything different, you're screwed.
for (hw
= sc_table
; hw
->hw_type
; hw
++)
if (hw
->hw_type
== COMMDCM
&& !badaddr((short *)hw
->hw_addr
))
if (hw
->hw_type
!= COMMDCM
) {
dcm_addr
[BOARD(CONUNIT
)] = (struct dcmdevice
*)hw
->hw_addr
;
/* locate the major number */
for (i
= 0; i
< nchrdev
; i
++)
if (cdevsw
[i
].d_open
== dcmopen
)
/* initialize required fields */
cp
->cn_dev
= makedev(i
, unit
);
cp
->cn_tp
= &dcm_tty
[unit
];
switch (dcm_addr
[BOARD(unit
)]->dcm_rsid
) {
dcminit(cp
->cn_dev
, dcmdefaultrate
);
dcmconsole
= UNIT(cp
->cn_dev
);
register struct dcmdevice
*dcm
= dcm_addr
[BOARD(dev
)];
mode
= LC_8BITS
| LC_1STOP
;
* Wait for transmitter buffer to empty.
while (dcm
->dcm_thead
[port
].ptr
!= dcm
->dcm_ttail
[port
].ptr
)
DELAY(DCM_USPERCH(rate
));
* Make changes known to hardware.
dcm
->dcm_data
[port
].dcm_baud
= ttspeedtab(rate
, dcmspeedtab
);
dcm
->dcm_data
[port
].dcm_conf
= mode
;
dcm
->dcm_cmdtab
[port
].dcm_data
|= CT_CON
;
dcm
->dcm_cr
|= (1 << port
);
* Delay for config change to take place. Weighted by buad.
DELAY(16 * DCM_USPERCH(rate
));
register struct dcmdevice
*dcm
= dcm_addr
[BOARD(dev
)];
register struct dcmrfifo
*fifo
;
register struct dcmpreg
*pp
;
pp
= dcm_preg(dcm
, port
);
head
= pp
->r_head
& RX_MASK
;
fifo
= &dcm
->dcm_rfifos
[3-port
][head
>>1];
while (head
== (pp
->r_tail
& RX_MASK
))
* If board interrupts are enabled, just let our received char
* interrupt through in case some other port on the board was
* busy. Otherwise we must clear the interrupt.
if ((dcm
->dcm_ic
& IC_IE
) == 0)
pp
->r_head
= (head
+ 2) & RX_MASK
;
* Console kernel output character routine.
register struct dcmdevice
*dcm
= dcm_addr
[BOARD(dev
)];
register struct dcmpreg
*pp
;
pp
= dcm_preg(dcm
, port
);
(void) dcminit(dev
, dcmdefaultrate
);
tail
= pp
->t_tail
& TX_MASK
;
while (tail
!= (pp
->t_head
& TX_MASK
))
dcm
->dcm_tfifos
[3-port
][tail
].data_char
= c
;
pp
->t_tail
= tail
= (tail
+ 1) & TX_MASK
;
dcm
->dcm_cmdtab
[port
].dcm_data
|= CT_TX
;
dcm
->dcm_cr
|= (1 << port
);
while (tail
!= (pp
->t_head
& TX_MASK
))
* If board interrupts are enabled, just let our completion
* interrupt through in case some other port on the board
* was busy. Otherwise we must clear the interrupt.
if ((dcm
->dcm_ic
& IC_IE
) == 0) {