* Copyright (c) 1982, 1986 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
* @(#)ts.c 7.9 (Berkeley) %G%
* NB: This driver takes advantage of the fact that there is only one
* There is a ctsbuf per tape controller.
* It is used as the token to pass to the internal routines
* to execute tape ioctls.
* In particular, when the tape is rewinding on close we release
* the user process but any further attempts to use the tape drive
* before the rewind completes will hang waiting for ctsbuf.
* Driver unibus interface routines and variables.
int tsprobe(), tsslave(), tsattach(), tsdgo(), tsintr();
struct uba_ctlr
*tsminfo
[NTS
];
struct uba_device
*tsdinfo
[NTS
];
u_short tsstd
[] = { 0772520, 0 };
/* need all these even though controller == drive */
struct uba_driver zsdriver
=
{ tsprobe
, tsslave
, tsattach
, tsdgo
, tsstd
, "ts", tsdinfo
, "zs", tsminfo
};
/* bits in minor device */
#define TSUNIT(dev) (minor(dev)&03)
#define INF (daddr_t)1000000L
* Software state per tape transport.
* Also contains hardware state in message packets.
* 1. A tape drive is a unique-open device; we refuse opens when it is already.
* 2. We keep track of the current position on a block tape and seek
* before operations by forward/back spacing if necessary.
* 3. We remember if the last operation was a write on a tape, so if a tape
* is open read write and the last thing done is a write we can
* write a standard end of tape mark (two eofs).
* 4. We remember the status registers after the last command, using
* then internally and returning them to the SENSE ioctl.
struct ts_tsdata
{ /* data shared with ts11 controller */
struct ts_cmd t_cmd
; /* the command packet (must be first) */
struct ts_sts t_sts
; /* status packet, for returned status */
struct ts_char t_char
; /* characteristics packet */
char sc_openf
; /* lock against multiple opens */
char sc_lastiow
; /* last op was a write */
short sc_resid
; /* copy of last bc */
daddr_t sc_blkno
; /* block number, for block device tape */
daddr_t sc_nxrec
; /* position of end of tape, if known */
struct ts_tsdata sc_ts
;/* command and status packets */
struct ts_tsdata
*sc_ubaddr
; /* Unibus address of tsdata structure */
u_short sc_uba
; /* Unibus addr of cmd pkt for tsdb */
short sc_density
; /* value |'ed into char_mode for TC13 density */
caddr_t sc_ctty
; /* user's controlling tty (vnode) */
int sc_blks
; /* number of I/O operations since open */
int sc_softerrs
; /* number of soft I/O errors since open */
* States for um->um_tab.b_active, the per controller state flag.
* This is used to sequence control in the driver.
#define SSEEK 1 /* seeking */
#define SIO 2 /* doing seq i/o */
#define SCOM 3 /* sending control command */
#define SREW 4 /* sending a drive rewind */
* Determine if there is a controller for
* a ts at address reg. Our goal is to make the
register int br
, cvec
; /* must be r11,r10; value-result */
register struct tsdevice
*addr
= (struct tsdevice
*)reg
;
register struct ts_softc
*sc
;
br
= 0; cvec
= br
; br
= cvec
;
addr
->tssr
= 0; /* initialize subsystem */
if ((addr
->tssr
& TS_NBA
) == 0)
* TS_SETCHR|TS_IE alone refuses to interrupt for me.
i
= (int)&sc
->sc_ubaddr
->t_char
;
sc
->sc_ts
.t_cmd
.c_loba
= i
;
sc
->sc_ts
.t_cmd
.c_hiba
= (i
>> 16) & 3;
sc
->sc_ts
.t_cmd
.c_size
= sizeof(struct ts_char
);
sc
->sc_ts
.t_cmd
.c_cmd
= TS_ACK
| TS_SETCHR
;
sc
->sc_ts
.t_char
.char_addr
= (int)&sc
->sc_ubaddr
->t_sts
;
sc
->sc_ts
.t_char
.char_size
= sizeof(struct ts_sts
);
sc
->sc_ts
.t_char
.char_mode
= 0; /* mode is unimportant */
sc
->sc_ts
.t_cmd
.c_cmd
= TS_ACK
| TS_CVC
| TS_IE
| TS_SENSE
;
sc
->sc_ts
.t_cmd
.c_repcnt
= 1;
/* should have interrupted by now */
if (cvec
== 0 || cvec
== 0x200) /* no interrupt */
return (sizeof (struct tsdevice
));
* Map in the command, status, and characteristics packet. We
* make them contiguous to keep overhead down. This also sets
* sc_uba (which then never changes).
register struct ts_softc
*sc
;
i
= uballoc(uban
, (caddr_t
)&sc
->sc_ts
, sizeof(sc
->sc_ts
), 0);
sc
->sc_ubaddr
= (struct ts_tsdata
*)i
;
* Note that i == the Unibus address of the command packet,
* and that it is a multiple of 4 (guaranteed by the compiler).
sc
->sc_uba
= i
+ ((i
>> 16) & 3);
* TS11 only supports one drive per controller;
* check for ui_slave == 0.
return (ui
->ui_slave
== 0); /* non-zero slave not allowed */
* Record attachment of the unit to the controller.
* Open the device. Tapes are unique open
* devices, so we refuse if it is already open.
register int tsunit
= TSUNIT(dev
);
register struct uba_device
*ui
;
register struct ts_softc
*sc
;
if (tsunit
>= NTS
|| (ui
= tsdinfo
[tsunit
]) == 0 || ui
->ui_alive
== 0)
if ((sc
= &ts_softc
[ui
->ui_ctlr
])->sc_openf
)
sc
->sc_density
= (minor(dev
) & T_1600BPI
) ? TS_NRZI
: 0;
tscommand(dev
, TS_SENSE
, 1);
if (ctsbuf
[tsunit
].b_flags
& B_ERROR
)
* Try it again in case it went off-line
tscommand(dev
, TS_SENSE
, 1);
if (tsinit(ui
->ui_ctlr
)) {
if ((sc
->sc_ts
.t_sts
.s_xs0
&TS_ONL
) == 0) {
uprintf("ts%d: not online\n", tsunit
);
if ((flag
&FWRITE
) && (sc
->sc_ts
.t_sts
.s_xs0
&TS_WLK
)) {
uprintf("ts%d: no write ring\n", tsunit
);
sc
->sc_blkno
= (daddr_t
)0;
sc
->sc_ctty
= (caddr_t
)(u
.u_procp
->p_flag
&SCTTY
?
u
.u_procp
->p_session
->s_ttyvp
: 0);
* If tape was open for writing or last operation was
* a write, then write two EOF's and backspace over the last one.
* Unless this is a non-rewinding special file, rewind the tape.
* Make the tape available to others.
register struct ts_softc
*sc
= &ts_softc
[TSUNIT(dev
)];
if (flag
== FWRITE
|| (flag
&FWRITE
) && sc
->sc_lastiow
) {
tscommand(dev
, TS_WEOF
, 1);
tscommand(dev
, TS_WEOF
, 1);
tscommand(dev
, TS_SREV
, 1);
if ((minor(dev
)&T_NOREWIND
) == 0)
* 0 count means don't hang waiting for rewind complete
* rather ctsbuf stays busy until the operation completes
* preventing further opens from completing by
* preventing a TS_SENSE from completing.
tscommand(dev
, TS_REW
, 0);
if (sc
->sc_blks
> 100 && sc
->sc_softerrs
> sc
->sc_blks
/ 100)
log(LOG_INFO
, "ts%d: %d soft errors in %d blocks\n",
TSUNIT(dev
), sc
->sc_softerrs
, sc
->sc_blks
);
* Initialize a TS11. Set device characteristics.
register struct ts_softc
*sc
= &ts_softc
[ctlr
];
register struct uba_ctlr
*um
= tsminfo
[ctlr
];
register struct tsdevice
*addr
= (struct tsdevice
*)um
->um_addr
;
if (addr
->tssr
& (TS_NBA
|TS_OFL
) || sc
->sc_ts
.t_sts
.s_xs0
& TS_BOT
) {
addr
->tssr
= 0; /* subsystem initialize */
i
= (int)&sc
->sc_ubaddr
->t_char
;
sc
->sc_ts
.t_cmd
.c_loba
= i
;
sc
->sc_ts
.t_cmd
.c_hiba
= (i
>> 16) & 3;
sc
->sc_ts
.t_cmd
.c_size
= sizeof(struct ts_char
);
sc
->sc_ts
.t_cmd
.c_cmd
= TS_ACK
| TS_CVC
| TS_SETCHR
;
sc
->sc_ts
.t_char
.char_addr
= (int)&sc
->sc_ubaddr
->t_sts
;
sc
->sc_ts
.t_char
.char_size
= sizeof(struct ts_sts
);
sc
->sc_ts
.t_char
.char_mode
= TS_ESS
| TS_EAI
| TS_ERI
|
/* TS_ENB | */ sc
->sc_density
;
* Execute a command on the tape drive
* a specified number of times.
tscommand(dev
, com
, count
)
bp
= &ctsbuf
[TSUNIT(dev
)];
while (bp
->b_flags
&B_BUSY
) {
* This special check is because B_BUSY never
* gets cleared in the non-waiting rewind case.
if (bp
->b_repcnt
== 0 && (bp
->b_flags
&B_DONE
))
sleep((caddr_t
)bp
, PRIBIO
);
bp
->b_flags
= B_BUSY
|B_READ
;
(void) tsinit(tsdinfo
[TSUNIT(dev
)]->ui_ctlr
);
* In case of rewind from close, don't wait.
* This is the only case where count can be 0.
if (bp
->b_flags
&B_WANTED
)
* Queue a tape operation.
register int tsunit
= TSUNIT(bp
->b_dev
);
register struct uba_ctlr
*um
;
* Put transfer at end of controller queue
um
= tsdinfo
[tsunit
]->ui_mi
;
dp
->b_actl
->av_forw
= bp
;
um
->um_tab
.b_actf
= um
->um_tab
.b_actl
= dp
;
* If the controller is not busy, get
if (um
->um_tab
.b_active
== 0)
* Start activity on a ts controller.
register struct uba_ctlr
*um
;
register struct tsdevice
*addr
= (struct tsdevice
*)um
->um_addr
;
register struct ts_softc
*sc
;
register struct uba_device
*ui
;
* Start the controller if there is something for it to do.
if ((bp
= um
->um_tab
.b_actf
->b_actf
) == NULL
) {
tsunit
= TSUNIT(bp
->b_dev
);
* Default is that last command was NOT a write command;
* if we finish a write command we will notice this in tsintr().
if (sc
->sc_openf
< 0 || (addr
->tssr
&TS_OFL
)) {
* Have had a hard error on a non-raw tape
* or the tape unit is now unavailable
if (bp
== &ctsbuf
[tsunit
]) {
* Execute control operation with the specified count.
bp
->b_command
== TS_REW
? SREW
: SCOM
;
sc
->sc_ts
.t_cmd
.c_repcnt
= bp
->b_repcnt
;
* For raw I/O, save the current block
* number in case we have to retry.
if (bp
->b_flags
& B_RAW
) {
if (um
->um_tab
.b_errcnt
== 0)
sc
->sc_blkno
= bdbtofsb(bp
->b_blkno
);
* Handle boundary cases for operation
if (bdbtofsb(bp
->b_blkno
) > sc
->sc_nxrec
) {
* Can't read past known end-of-file.
if (bdbtofsb(bp
->b_blkno
) == sc
->sc_nxrec
&&
* Reading at end of file returns 0 bytes.
bp
->b_resid
= bp
->b_bcount
;
if ((bp
->b_flags
&B_READ
) == 0)
sc
->sc_nxrec
= bdbtofsb(bp
->b_blkno
) + 1;
* If the data transfer command is in the correct place,
* set up all the registers except the csr, and give
* control over to the UNIBUS adapter routines, to
* wait for resources to start the i/o.
if ((blkno
= sc
->sc_blkno
) == bdbtofsb(bp
->b_blkno
)) {
sc
->sc_ts
.t_cmd
.c_size
= bp
->b_bcount
;
if ((bp
->b_flags
&B_READ
) == 0)
um
->um_tab
.b_active
= SIO
;
sc
->sc_ts
.t_cmd
.c_cmd
= TS_ACK
| TS_CVC
| TS_IE
| cmd
;
* Tape positioned incorrectly;
* set to seek forwards or backwards to the correct spot.
* This happens for raw tapes only on error retries.
um
->um_tab
.b_active
= SSEEK
;
if (blkno
< bdbtofsb(bp
->b_blkno
)) {
bp
->b_command
= TS_SFORW
;
sc
->sc_ts
.t_cmd
.c_repcnt
= bdbtofsb(bp
->b_blkno
) - blkno
;
sc
->sc_ts
.t_cmd
.c_repcnt
= blkno
- bdbtofsb(bp
->b_blkno
);
sc
->sc_ts
.t_cmd
.c_cmd
= TS_ACK
| TS_CVC
| TS_IE
| bp
->b_command
;
* Done with this operation due to error or
* the fact that it doesn't do anything.
* Release UBA resources (if any), dequeue
* the transfer and continue processing this slave.
um
->um_tab
.b_actf
->b_actf
= bp
->av_forw
;
* The UNIBUS resources we needed have been
* allocated to us; start the device.
register struct uba_ctlr
*um
;
register struct ts_softc
*sc
= &ts_softc
[um
->um_ctlr
];
* The uba code uses byte-offset mode if using bdp;
* mask off the low bit here.
i
= UBAI_ADDR(um
->um_ubinfo
);
if (UBAI_BDP(um
->um_ubinfo
))
sc
->sc_ts
.t_cmd
.c_loba
= i
;
sc
->sc_ts
.t_cmd
.c_hiba
= (i
>> 16) & 3;
((struct tsdevice
*)um
->um_addr
)->tsdb
= sc
->sc_uba
;
register struct uba_ctlr
*um
;
register struct tsdevice
*addr
;
register struct ts_softc
*sc
;
um
= tsdinfo
[tsunit
]->ui_mi
;
if ((bp
= um
->um_tab
.b_actf
->b_actf
) == NULL
)
addr
= (struct tsdevice
*)um
->um_addr
;
* If last command was a rewind, and tape is still
* rewinding, wait for the rewind complete interrupt.
* SHOULD NEVER GET AN INTERRUPT IN THIS STATE.
if (um
->um_tab
.b_active
== SREW
) {
um
->um_tab
.b_active
= SCOM
;
if ((addr
->tssr
&TS_SSR
) == 0)
* An operation completed... record status
sc
= &ts_softc
[um
->um_ctlr
];
if ((bp
->b_flags
& B_READ
) == 0)
state
= um
->um_tab
.b_active
;
switch (addr
->tssr
& TS_TC
) {
case TS_UNREC
: /* unrecoverable */
case TS_FATAL
: /* fatal error */
case TS_RECNM
: /* recoverable, no motion */
case TS_ATTN
: /* attention */
if (sc
->sc_ts
.t_sts
.s_xs0
& TS_VCK
) {
/* volume check - may have changed tapes */
case TS_SUCC
: /* success termination */
printf("ts%d: success\n", tsunit
);
case TS_ALERT
: /* tape status alert */
* If we hit the end of the tape file,
if (sc
->sc_ts
.t_sts
.s_xs0
& (TS_TMK
|TS_EOT
)) {
tsseteof(bp
); /* set blkno and nxrec */
state
= SCOM
; /* force completion */
* Stuff bc so it will be unstuffed correctly
sc
->sc_ts
.t_sts
.s_rbpcr
= bp
->b_bcount
;
* If we were reading raw tape and the record was too
* long or too short, we don't consider this an error.
if ((bp
->b_flags
& (B_READ
|B_RAW
)) == (B_READ
|B_RAW
) &&
sc
->sc_ts
.t_sts
.s_xs0
&(TS_RLS
|TS_RLL
))
case TS_RECOV
: /* recoverable, tape moved */
* If this was an i/o operation retry up to 8 times.
if (++um
->um_tab
.b_errcnt
< 7) {
* Non-i/o errors on non-raw tape
if ((bp
->b_flags
&B_RAW
) == 0 &&
case TS_REJECT
: /* function reject */
if (state
== SIO
&& sc
->sc_ts
.t_sts
.s_xs0
& TS_WLE
)
tprintf(sc
->sc_ctty
, "ts%d: write locked\n",
if ((sc
->sc_ts
.t_sts
.s_xs0
& TS_ONL
) == 0)
tprintf(sc
->sc_ctty
, "ts%d: offline\n",
tprintf(sc
->sc_ctty
, "ts%d: hard error bn%d tssr=%b xs0=%b",
tsunit
, bp
->b_blkno
, addr
->tssr
, TSSR_BITS
,
sc
->sc_ts
.t_sts
.s_xs0
, TSXS0_BITS
);
if (sc
->sc_ts
.t_sts
.s_xs1
)
tprintf(sc
->sc_ctty
, " xs1=%b", sc
->sc_ts
.t_sts
.s_xs1
,
if (sc
->sc_ts
.t_sts
.s_xs2
)
tprintf(sc
->sc_ctty
, " xs2=%b", sc
->sc_ts
.t_sts
.s_xs2
,
if (sc
->sc_ts
.t_sts
.s_xs3
)
tprintf(sc
->sc_ctty
, " xs3=%b", sc
->sc_ts
.t_sts
.s_xs3
,
tprintf(sc
->sc_ctty
, "\n");
* Advance tape control FSM.
* Read/write increments tape block number
* For forward/backward space record update current position.
if (bp
== &ctsbuf
[tsunit
])
switch ((int)bp
->b_command
) {
sc
->sc_blkno
+= bp
->b_repcnt
;
sc
->sc_blkno
-= bp
->b_repcnt
;
sc
->sc_blkno
= bdbtofsb(bp
->b_blkno
);
* Reset error count and remove
um
->um_tab
.b_actf
->b_actf
= bp
->av_forw
;
bp
->b_resid
= sc
->sc_ts
.t_sts
.s_rbpcr
;
if (um
->um_tab
.b_actf
->b_actf
== 0) {
register int tsunit
= TSUNIT(bp
->b_dev
);
register struct ts_softc
*sc
= &ts_softc
[tsdinfo
[tsunit
]->ui_ctlr
];
if (bp
== &ctsbuf
[tsunit
]) {
if (sc
->sc_blkno
> bdbtofsb(bp
->b_blkno
)) {
sc
->sc_nxrec
= bdbtofsb(bp
->b_blkno
) -
sc
->sc_blkno
= sc
->sc_nxrec
;
sc
->sc_blkno
= bdbtofsb(bp
->b_blkno
) +
sc
->sc_nxrec
= sc
->sc_blkno
- 1;
sc
->sc_nxrec
= bdbtofsb(bp
->b_blkno
);
register struct uba_ctlr
*um
;
register struct uba_device
*ui
;
for (ts11
= 0; ts11
< NTS
; ts11
++) {
if ((um
= tsminfo
[ts11
]) == 0 || um
->um_alive
== 0 ||
um
->um_tab
.b_actf
= um
->um_tab
.b_actl
= 0;
if (ts_softc
[ts11
].sc_openf
> 0)
ts_softc
[ts11
].sc_openf
= -1;
printf("<%d>", UBAI_BDP(um
->um_ubinfo
));
* tsdinfo should be 1-to-1 with tsminfo, but someone
* might have screwed up the config file:
for (i
= 0; i
< NTS
; i
++) {
if ((ui
= tsdinfo
[i
]) != NULL
&&
ui
->ui_alive
&& ui
->ui_mi
== um
) {
um
->um_tab
.b_actf
= um
->um_tab
.b_actl
=
tsmap(&ts_softc
[ts11
], uban
, (int *)NULL
);
tsioctl(dev
, cmd
, data
, flag
)
int tsunit
= TSUNIT(dev
);
register struct ts_softc
*sc
= &ts_softc
[tsdinfo
[tsunit
]->ui_ctlr
];
register struct buf
*bp
= &ctsbuf
[TSUNIT(dev
)];
/* we depend of the values and order of the MT codes here */
{TS_WEOF
,TS_SFORWF
,TS_SREVF
,TS_SFORW
,TS_SREV
,TS_REW
,TS_OFFL
,TS_SENSE
};
case MTIOCTOP
: /* tape operation */
mtop
= (struct mtop
*)data
;
callcount
= mtop
->mt_count
;
case MTREW
: case MTOFFL
: case MTNOP
:
if (callcount
<= 0 || fcount
<= 0)
while (--callcount
>= 0) {
tscommand(dev
, tsops
[mtop
->mt_op
], fcount
);
if ((mtop
->mt_op
== MTFSR
|| mtop
->mt_op
== MTBSR
) &&
if ((bp
->b_flags
&B_ERROR
) ||
sc
->sc_ts
.t_sts
.s_xs0
&TS_BOT
)
mtget
= (struct mtget
*)data
;
mtget
->mt_erreg
= sc
->sc_ts
.t_sts
.s_xs0
;
mtget
->mt_resid
= sc
->sc_resid
;
mtget
->mt_type
= MT_ISTS
;
register struct uba_device
*ui
;
register struct uba_regs
*uba
;
register struct tsdevice
*addr
;
int blk
, num
, unit
, reg
, start
;
struct ts_tsdata
*tc
, *tc_ubaddr
;
#define phys(a,b) ((b)((int)(a)&0x7fffffff))
ui
= phys(tsdinfo
[unit
], struct uba_device
*);
uba
= phys(ui
->ui_hd
, struct uba_hd
*)->uh_physuba
;
addr
= (struct tsdevice
*)ui
->ui_physaddr
;
/* map a ts_tsdata structure */
tc
= phys(&ts_softc
[0].sc_ts
, struct ts_tsdata
*);
num
= btoc(sizeof(struct ts_tsdata
)) + 1;
io
= &uba
->uba_map
[reg
= NUBMREG
- num
];
for (i
= 0; i
< num
; i
++)
*(int *)io
++ = UBAMR_MRV
| (btop(tc
) + i
);
i
= (((int)tc
& PGOFSET
) | (reg
<< 9));
tc_ubaddr
= (struct ts_tsdata
*)i
;
db
= i
+ ((i
>> 16) & 3);
if ((addr
->tssr
& (TS_NBA
|TS_OFL
)) != TS_NBA
)
/* set characteristics */
i
= (int)&tc_ubaddr
->t_char
;
tc
->t_cmd
.c_hiba
= (i
>> 16) & 3;
tc
->t_cmd
.c_size
= sizeof(struct ts_char
);
tc
->t_cmd
.c_cmd
= TS_ACK
| TS_CVC
| TS_SETCHR
;
tc
->t_char
.char_addr
= (int)&tc_ubaddr
->t_sts
;
tc
->t_char
.char_size
= sizeof(struct ts_sts
);
tc
->t_char
.char_mode
= TS_ESS
;
tc
->t_cmd
.c_cmd
= TS_ACK
| TS_WCOM
;
for (start
= 0, num
= maxfree
; num
> 0; start
+= blk
, num
-= blk
) {
blk
= num
> DBSIZE
? DBSIZE
: num
;
for (i
= 0; i
< blk
; i
++)
*(int *)io
++ = UBAMR_MRV
| (1 << UBAMR_DPSHIFT
) |
tc
->t_cmd
.c_cmd
= TS_WEOF
;
register struct tsdevice
*addr
;
while ((addr
->tssr
& TS_SSR
) == 0)