/* $NetBSD: ctu.c,v 1.37 2018/04/02 22:49:48 mrg Exp $ */
/*
* Copyright (c) 1996 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.
*/
/*
* Device driver for 11/750 Console TU58.
*
* Writing of tapes does not work, by some unknown reason so far.
* It is almost useless to try to use this driver when running
* multiuser, because the serial device don't have any buffers
* so we will loose interrupts.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ctu.c,v 1.37 2018/04/02 22:49:48 mrg Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/callout.h>
#include <sys/conf.h>
#include <sys/cpu.h>
#include <sys/device.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <machine/rsp.h>
#include <machine/scb.h>
#undef TUDEBUG
#define TU_IDLE 0
#define TU_RESET 1
#define TU_RUNNING 2
#define TU_WORKING 3
#define TU_READING 4
#define TU_WRITING 5
#define TU_ENDPACKET 6
#define TU_RESTART 7
struct tu_softc {
int sc_state;
int sc_step;
char sc_rsp[15]; /* Should be struct rsb; but don't work */
int sc_tpblk; /* Start block number */
int sc_wto; /* Timeout counter */
int sc_xbytes; /* Number of xfer'd bytes */
int sc_op; /* Read/write */
struct bufq_state *sc_bufq; /* pending I/O requests */
} tu_sc;
struct ivec_dsp tu_recv, tu_xmit;
void ctuattach(void);
static void ctutintr(void *);
static void cturintr(void *);
static void ctustart(void);
static void ctuwatch(void *);
static u_short ctu_cksum(unsigned short *, int);
dev_type_open(ctuopen);
dev_type_close(ctuclose);
#if 0 /* not yet */
dev_type_read(cturead);
dev_type_write(ctuwrite);
#endif
dev_type_strategy(ctustrategy);
const struct bdevsw ctu_bdevsw = {
.d_open = ctuopen,
.d_close = ctuclose,
.d_strategy = ctustrategy,
.d_ioctl = noioctl,
.d_dump = nodump,
.d_psize = nosize,
.d_discard = nodiscard,
.d_flag = D_TAPE
};
#if 0 /* not yet */
const struct cdevsw ctu_cdevsw = {
.d_open = ctuopen,
.d_close = ctuclose,
.d_read = cturead,
.d_write = ctuwrite,
.d_ioctl = noioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = D_TAPE
};
#endif
static callout_t ctu_watch_ch;
void
ctuattach(void)
{
callout_init(&ctu_watch_ch, 0);
bufq_alloc(&tu_sc.sc_bufq, "fcfs", 0);
tu_recv = idsptch;
tu_recv.hoppaddr = cturintr;
scb->scb_csrint = (void *)&tu_recv;
tu_xmit = idsptch;
tu_xmit.hoppaddr = ctutintr;
scb->scb_cstint = (void *)&tu_xmit;
}
static void
ctuinit(void)
{
int s = spl7();
#define WAIT while ((mfpr(PR_CSTS) & 0x80) == 0)
/*
* Do a reset as described in the
* "TU58 DECtape II Users Guide".
*/
mtpr(0101, PR_CSTS); /* Enable transmit interrupt + send break */
WAIT;
mtpr(0, PR_CSTD); WAIT;
mtpr(0, PR_CSTD); WAIT;
mtpr(RSP_TYP_INIT, PR_CSTD); WAIT;
mtpr(RSP_TYP_INIT, PR_CSTD); WAIT;
#undef WAIT
splx(s);
}
int
ctuopen(dev_t dev, int oflags, int devtype, struct lwp *l)
{
int error;
if (minor(dev))
return ENXIO;
if (tu_sc.sc_state != TU_IDLE)
return EBUSY;
tu_sc.sc_state = TU_RESET;
tu_sc.sc_step = 0;
mtpr(0100, PR_CSRS); /* Enable receive interrupt */
mtpr(0101, PR_CSTS); /* Enable transmit interrupt + send break */
if ((error = tsleep((void *)&tu_sc, (PZERO + 10)|PCATCH, "reset", 0)))
return error;
#ifdef TUDEBUG
printf("ctuopen: running\n");
#endif
tu_sc.sc_state = TU_RUNNING;
callout_reset(&ctu_watch_ch, hz, ctuwatch, NULL);
return 0;
}
int
ctuclose(dev_t dev, int oflags, int devtype, struct lwp *l)
{
struct buf *bp;
int s = spl7();
while ((bp = bufq_get(tu_sc.sc_bufq)))
;
splx(s);
mtpr(0, PR_CSRS);
mtpr(0, PR_CSTS);
tu_sc.sc_state = TU_IDLE;
callout_stop(&ctu_watch_ch);
return 0;
}
void
ctustrategy(struct buf *bp)
{
int s, empty;
#ifdef TUDEBUG
printf("ctustrategy: bcount %ld blkno %d\n", bp->b_bcount, bp->b_blkno);
printf("ctustrategy: bp %p\n", bp);
#endif
s = spl7();
if (bp->b_blkno >= 512) {
bp->b_resid = bp->b_bcount;
biodone(bp);
splx(s);
return;
}
empty = (bufq_peek(tu_sc.sc_bufq) == NULL);
bufq_put(tu_sc.sc_bufq, bp);
if (empty)
ctustart();
splx(s);
}
void
ctustart(void)
{
struct rsp *rsp = (struct rsp *)tu_sc.sc_rsp;
struct buf *bp;
bp = bufq_peek(tu_sc.sc_bufq);
if (bp == NULL)
return;
#ifdef TUDEBUG
printf("ctustart: %s\n", bp->b_flags & B_READ ? "READING":"WRITING");
#endif
tu_sc.sc_tpblk = bp->b_blkno;
tu_sc.sc_xbytes = 0;
tu_sc.sc_op = bp->b_flags & B_READ ? RSP_OP_READ : RSP_OP_WRITE;
tu_sc.sc_step = 0;
bp->b_resid = bp->b_bcount;
tu_sc.sc_wto = 0;
rsp->rsp_typ = RSP_TYP_COMMAND;
rsp->rsp_sz = 012;
rsp->rsp_op = tu_sc.sc_op;
rsp->rsp_mod = 0;
rsp->rsp_drv = 0;
rsp->rsp_sw = rsp->rsp_xx1 = rsp->rsp_xx2 = 0;
rsp->rsp_cnt = bp->b_bcount;
rsp->rsp_blk = tu_sc.sc_tpblk;
rsp->rsp_sum = ctu_cksum((unsigned short *)rsp, 6);
tu_sc.sc_state = TU_WORKING;
ctutintr(NULL);
}
static int
readchr(void)
{
int i;
for (i = 0; i < 5000; i++)
if ((mfpr(PR_CSRS) & 0x80))
break;
if (i == 5000)
return -1;
return mfpr(PR_CSRD);
}
/*
* Loop in a tight (busy-wait-)loop when receiving packets, this is
* the only way to avoid loosing characters.
*/
void
cturintr(void *arg)
{
int status = mfpr(PR_CSRD);
struct buf *bp;
int i, c, tck;
unsigned short ck = 0;
char *buf;
bp = bufq_peek(tu_sc.sc_bufq);
buf = bp->b_data;
switch (tu_sc.sc_state) {
case TU_RESET:
if (status != RSP_TYP_CONTINUE)
printf("Bad response %d\n", status);
wakeup(&tu_sc);
return;
case TU_READING:
if (status != RSP_TYP_DATA)
bp->b_error = EIO;
tu_sc.sc_wto = 0;
for (i = 0; i < 131; i++) {
if ((c = readchr()) < 0) {
#ifdef TUDEBUG
printf("Timeout...%d\n", i);
#endif
goto bad;
}
if ((i > 0) && (i < 129))
buf[tu_sc.sc_xbytes++] = c;
if (i == 129)
ck = (c & 0xff);
if (i == 130)
ck |= ((c & 0xff) << 8);
}
tck = ctu_cksum((void *)&buf[tu_sc.sc_xbytes-128], 64);
tck += 0x8001; if (tck > 0xffff) tck -= 0xffff;
if (tck != ck) {
#ifdef TUDEBUG
int i;
printf("Bad cksum: tck %x != ck %x\n", tck, ck);
printf("block %d\n", tu_sc.sc_xbytes/128-1);
for (i = -128; i < 0; i+=16)
printf("%x %x %x %x\n",
*(int *)&bp->b_data[tu_sc.sc_xbytes+i],
*(int *)&bp->b_data[tu_sc.sc_xbytes+i+4],
*(int *)&bp->b_data[tu_sc.sc_xbytes+i+8],
*(int *)&bp->b_data[tu_sc.sc_xbytes+i+12]);
#endif
goto bad;
}
bp->b_resid = 0;
if (bp->b_bcount == tu_sc.sc_xbytes)
tu_sc.sc_state = TU_ENDPACKET;
return;
case TU_ENDPACKET:
if (status != RSP_TYP_COMMAND) {
#ifdef TUDEBUG
int g[14], j;
g[0] = status;
for (i = 1; i < 14; i++)
if ((g[i] = readchr()) < 0)
break;
j=0; while (readchr() >= 0)
j++;
for (i = 0; i < 14; i++)
printf("%d: %x\n", i, g[i]);
printf("Got %d char more\n", j);
printf("error: state %d xbytes %d status %d\n",
tu_sc.sc_state, tu_sc.sc_xbytes, status);
#endif
bp->b_error = EIO;
}
tu_sc.sc_wto = 0;
for (i = 0; i < 13; i++) {
if ((c = readchr()) < 0) {
#ifdef TUDEBUG
printf("Timeout epack %d\n", i);
#endif
goto bad;
}
if ((i == 2) &&
((c != RSP_MOD_OK) && (c != RSP_MOD_RETR))) {
#ifdef TUDEBUG
printf("end packet status bad: %d\n", c);
#endif
bp->b_error = EIO;
}
}
break;
case TU_WRITING:
#define WAIT while ((mfpr(PR_CSTS) & 0x80) == 0)
if (status != RSP_TYP_CONTINUE)
goto bad;
#ifdef TUDEBUG
printf("Writing byte %d\n", tu_sc.sc_xbytes);
#endif
WAIT;
mtpr(RSP_TYP_DATA, PR_CSTD);
WAIT;
mtpr(128, PR_CSTD);
for (i = 0; i < 128; i++) {
WAIT;
mtpr(buf[tu_sc.sc_xbytes++], PR_CSTD);
}
tck = ctu_cksum((void *)&buf[tu_sc.sc_xbytes-128], 64);
tck += 0x8001; if (tck > 0xffff) tck -= 0xffff;
WAIT;
mtpr(tck & 0xff, PR_CSTD);
WAIT;
mtpr((tck >> 8) & 0xff, PR_CSTD);
bp->b_resid = 0;
if (tu_sc.sc_xbytes == bp->b_bcount)
tu_sc.sc_state = TU_ENDPACKET;
return;
#undef WAIT
case TU_RESTART:
if (status != RSP_TYP_CONTINUE)
goto bad;
ctustart();
return;
default:
printf("bad rx state %d char %d\n", tu_sc.sc_state, status);
return;
}
if (bp->b_error == 0) {
(void)bufq_get(tu_sc.sc_bufq);
biodone(bp);
#ifdef TUDEBUG
printf("biodone %p\n", bp);
#endif
}
#ifdef TUDEBUG
else {
printf("error: state %d xbytes %d status %d\n",
tu_sc.sc_state, tu_sc.sc_xbytes, status);
}
#endif
bp->b_error = 0;
tu_sc.sc_state = TU_IDLE;
ctustart();
return;
bad: tu_sc.sc_state = TU_RESTART;
ctuinit();
}
void
ctutintr(void *arg)
{
while ((mfpr(PR_CSTS) & 0x80) == 0)
;
switch (tu_sc.sc_state) {
case TU_RESET:
switch (tu_sc.sc_step) {
case 0:
case 1:
mtpr(0, PR_CSTD);
break;
case 2:
case 3:
mtpr(RSP_TYP_INIT, PR_CSTD);
break;
default:
break;
}
tu_sc.sc_step++;
return;
case TU_WORKING:
if (tu_sc.sc_step == 14) {
if (tu_sc.sc_op == RSP_OP_READ)
tu_sc.sc_state = TU_READING;
else
tu_sc.sc_state = TU_WRITING;
} else
mtpr(tu_sc.sc_rsp[tu_sc.sc_step++], PR_CSTD);
return;
case TU_IDLE:
printf("Idle interrupt\n");
return;
case TU_ENDPACKET:
case TU_WRITING:
case TU_RESTART:
return;
default:
printf("bad tx state %d\n", tu_sc.sc_state);
}
}
unsigned short
ctu_cksum(unsigned short *buf, int words)
{
int i, cksum;
for (i = cksum = 0; i < words; i++)
cksum += buf[i];
hej: if (cksum > 65535) {
cksum = (cksum & 65535) + (cksum >> 16);
goto hej;
}
return cksum;
}
int oldtp;
/*
* Watch so that we don't get blocked unnecessary due to lost int's.
*/
void
ctuwatch(void *arg)
{
callout_reset(&ctu_watch_ch, hz, ctuwatch, NULL);
if (tu_sc.sc_state == TU_WORKING) {
/*
* Died in sending command.
* Wait 5 secs.
*/
if (tu_sc.sc_wto++ > 5) {
#ifdef TUDEBUG
printf("Died in sending command\n");
#endif
tu_sc.sc_state = TU_RESTART;
ctuinit();
}
}
if (tu_sc.sc_state == TU_READING || tu_sc.sc_state == TU_WRITING) {
/*
* Positioning, may take long time.
* Wait one minute.
*/
if (tu_sc.sc_wto++ > 60) {
#ifdef TUDEBUG
printf("Died in Positioning, wto %d\n", tu_sc.sc_wto);
#endif
tu_sc.sc_state = TU_RESTART;
ctuinit();
}
}
}