1492 lines
37 KiB
C
1492 lines
37 KiB
C
/* $NetBSD: seagate.c,v 1.49 2002/04/05 18:27:54 bouyer Exp $ */
|
|
|
|
/*
|
|
* ST01/02, Future Domain TMC-885, TMC-950 SCSI driver
|
|
*
|
|
* Copyright 1994, Charles M. Hannum (mycroft@ai.mit.edu)
|
|
* Copyright 1994, Kent Palmkvist (kentp@isy.liu.se)
|
|
* Copyright 1994, Robert Knier (rknier@qgraph.com)
|
|
* Copyright 1992, 1994 Drew Eckhardt (drew@colorado.edu)
|
|
* Copyright 1994, Julian Elischer (julian@tfs.com)
|
|
*
|
|
* Others that has contributed by example code is
|
|
* Glen Overby (overby@cray.com)
|
|
* Tatu Yllnen
|
|
* Brian E Litzinger
|
|
*
|
|
* 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 DEVELOPERS ``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 DEVELOPERS 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.
|
|
*/
|
|
|
|
/*
|
|
* kentp 940307 alpha version based on newscsi-03 version of Julians SCSI-code
|
|
* kentp 940314 Added possibility to not use messages
|
|
* rknier 940331 Added fast transfer code
|
|
* rknier 940407 Added assembler coded data transfers
|
|
*/
|
|
|
|
/*
|
|
* What should really be done:
|
|
*
|
|
* Add missing tests for timeouts
|
|
* Restructure interrupt enable/disable code (runs to long with int disabled)
|
|
* Find bug? giving problem with tape status
|
|
* Add code to handle Future Domain 840, 841, 880 and 881
|
|
* adjust timeouts (startup is very slow)
|
|
* add code to use tagged commands in SCSI2
|
|
* Add code to handle slow devices better (sleep if device not disconnecting)
|
|
* Fix unnecessary interrupts
|
|
*/
|
|
|
|
/*
|
|
* Note to users trying to share a disk between DOS and unix:
|
|
* The ST01/02 is a translating host-adapter. It is not giving DOS
|
|
* the same number of heads/tracks/sectors as specified by the disk.
|
|
* It is therefore important to look at what numbers DOS thinks the
|
|
* disk has. Use these to disklabel your disk in an appropriate manner
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: seagate.c,v 1.49 2002/04/05 18:27:54 bouyer Exp $");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/device.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/user.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <machine/intr.h>
|
|
#include <machine/pio.h>
|
|
|
|
#include <dev/scsipi/scsi_all.h>
|
|
#include <dev/scsipi/scsipi_all.h>
|
|
#include <dev/scsipi/scsi_message.h>
|
|
#include <dev/scsipi/scsiconf.h>
|
|
|
|
#include <dev/isa/isareg.h>
|
|
#include <dev/isa/isavar.h> /* XXX USES ISA HOLE DIRECTLY */
|
|
|
|
#define SEA_SCB_MAX 32 /* allow maximally 8 scsi control blocks */
|
|
#define SCB_TABLE_SIZE 8 /* start with 8 scb entries in table */
|
|
#define BLOCK_SIZE 512 /* size of READ/WRITE areas on SCSI card */
|
|
|
|
/*
|
|
* defining SEA_BLINDTRANSFER will make DATA IN and DATA OUT to be done with
|
|
* blind transfers, i.e. no check is done for scsi phase changes. This will
|
|
* result in data loss if the scsi device does not send its data using
|
|
* BLOCK_SIZE bytes at a time.
|
|
* If SEA_BLINDTRANSFER defined and SEA_ASSEMBLER also defined will result in
|
|
* the use of blind transfers coded in assembler. SEA_ASSEMBLER is no good
|
|
* without SEA_BLINDTRANSFER defined.
|
|
*/
|
|
#define SEA_BLINDTRANSFER /* do blind transfers */
|
|
#define SEA_ASSEMBLER /* Use assembly code for fast transfers */
|
|
|
|
/*
|
|
* defining SEA_NOMSGS causes messages not to be used (thereby disabling
|
|
* disconnects)
|
|
*/
|
|
#undef SEA_NOMSGS
|
|
|
|
/*
|
|
* defining SEA_NODATAOUT makes dataout phase being aborted
|
|
*/
|
|
#undef SEA_NODATAOUT
|
|
|
|
/* Debugging definitions. Should not be used unless you want a lot of
|
|
printouts even under normal conditions */
|
|
|
|
#undef SEA_DEBUGQUEUE /* Display info about queue-lengths */
|
|
|
|
/******************************* board definitions **************************/
|
|
/*
|
|
* CONTROL defines
|
|
*/
|
|
#define CMD_RST 0x01 /* scsi reset */
|
|
#define CMD_SEL 0x02 /* scsi select */
|
|
#define CMD_BSY 0x04 /* scsi busy */
|
|
#define CMD_ATTN 0x08 /* scsi attention */
|
|
#define CMD_START_ARB 0x10 /* start arbitration bit */
|
|
#define CMD_EN_PARITY 0x20 /* enable scsi parity generation */
|
|
#define CMD_INTR 0x40 /* enable scsi interrupts */
|
|
#define CMD_DRVR_ENABLE 0x80 /* scsi enable */
|
|
|
|
/*
|
|
* STATUS
|
|
*/
|
|
#define STAT_BSY 0x01 /* scsi busy */
|
|
#define STAT_MSG 0x02 /* scsi msg */
|
|
#define STAT_IO 0x04 /* scsi I/O */
|
|
#define STAT_CD 0x08 /* scsi C/D */
|
|
#define STAT_REQ 0x10 /* scsi req */
|
|
#define STAT_SEL 0x20 /* scsi select */
|
|
#define STAT_PARITY 0x40 /* parity error bit */
|
|
#define STAT_ARB_CMPL 0x80 /* arbitration complete bit */
|
|
|
|
/*
|
|
* REQUESTS
|
|
*/
|
|
#define PH_DATAOUT (0)
|
|
#define PH_DATAIN (STAT_IO)
|
|
#define PH_CMD (STAT_CD)
|
|
#define PH_STAT (STAT_CD | STAT_IO)
|
|
#define PH_MSGOUT (STAT_MSG | STAT_CD)
|
|
#define PH_MSGIN (STAT_MSG | STAT_CD | STAT_IO)
|
|
|
|
#define PH_MASK (STAT_MSG | STAT_CD | STAT_IO)
|
|
|
|
#define PH_INVALID 0xff
|
|
|
|
#define SEA_RAMOFFSET 0x00001800
|
|
|
|
#define BASE_CMD (CMD_INTR | CMD_EN_PARITY)
|
|
|
|
#define SEAGATE 1 /* Seagate ST0[12] */
|
|
#define FDOMAIN 2 /* Future Domain TMC-{885,950} */
|
|
#define FDOMAIN840 3 /* Future Domain TMC-{84[01],88[01]} */
|
|
|
|
/******************************************************************************/
|
|
|
|
/* scsi control block used to keep info about a scsi command */
|
|
struct sea_scb {
|
|
u_char *data; /* position in data buffer so far */
|
|
int datalen; /* bytes remaining to transfer */
|
|
TAILQ_ENTRY(sea_scb) chain;
|
|
struct scsipi_xfer *xs; /* the scsipi_xfer for this cmd */
|
|
int flags; /* status of the instruction */
|
|
#define SCB_FREE 0
|
|
#define SCB_ACTIVE 1
|
|
#define SCB_ABORTED 2
|
|
#define SCB_TIMEOUT 4
|
|
#define SCB_ERROR 8
|
|
};
|
|
|
|
/*
|
|
* data structure describing current status of the scsi bus. One for each
|
|
* controller card.
|
|
*/
|
|
struct sea_softc {
|
|
struct device sc_dev;
|
|
void *sc_ih;
|
|
|
|
int type; /* board type */
|
|
caddr_t maddr; /* Base address for card */
|
|
caddr_t maddr_cr_sr; /* Address of control and status reg */
|
|
caddr_t maddr_dr; /* Address of data register */
|
|
|
|
struct scsipi_adapter sc_adapter;
|
|
struct scsipi_channel sc_channel;
|
|
|
|
TAILQ_HEAD(, sea_scb) free_list, ready_list, nexus_list;
|
|
struct sea_scb *nexus; /* currently connected command */
|
|
int numscbs; /* number of scsi control blocks */
|
|
struct sea_scb scb[SCB_TABLE_SIZE];
|
|
|
|
int our_id; /* our scsi id */
|
|
u_char our_id_mask;
|
|
volatile u_char busy[8]; /* index=target, bit=lun, Keep track of
|
|
busy luns at device target */
|
|
};
|
|
|
|
/* flag showing if main routine is running. */
|
|
static volatile int main_running = 0;
|
|
|
|
#define STATUS (*(volatile u_char *)sea->maddr_cr_sr)
|
|
#define CONTROL STATUS
|
|
#define DATA (*(volatile u_char *)sea->maddr_dr)
|
|
|
|
/*
|
|
* These are "special" values for the tag parameter passed to sea_select
|
|
* Not implemented right now.
|
|
*/
|
|
#define TAG_NEXT -1 /* Use next free tag */
|
|
#define TAG_NONE -2 /*
|
|
* Establish I_T_L nexus instead of I_T_L_Q
|
|
* even on SCSI-II devices.
|
|
*/
|
|
|
|
typedef struct {
|
|
char *signature;
|
|
int offset, length;
|
|
int type;
|
|
} BiosSignature;
|
|
|
|
/*
|
|
* Signatures for automatic recognition of board type
|
|
*/
|
|
static const BiosSignature signatures[] = {
|
|
{"ST01 v1.7 (C) Copyright 1987 Seagate", 15, 37, SEAGATE},
|
|
{"SCSI BIOS 2.00 (C) Copyright 1987 Seagate", 15, 40, SEAGATE},
|
|
|
|
/*
|
|
* The following two lines are NOT mistakes. One detects ROM revision
|
|
* 3.0.0, the other 3.2. Since seagate has only one type of SCSI adapter,
|
|
* and this is not going to change, the "SEAGATE" and "SCSI" together
|
|
* are probably "good enough"
|
|
*/
|
|
{"SEAGATE SCSI BIOS ", 16, 17, SEAGATE},
|
|
{"SEAGATE SCSI BIOS ", 17, 17, SEAGATE},
|
|
|
|
/*
|
|
* However, future domain makes several incompatible SCSI boards, so specific
|
|
* signatures must be used.
|
|
*/
|
|
{"FUTURE DOMAIN CORP. (C) 1986-1989 V5.0C2/14/89", 5, 45, FDOMAIN},
|
|
{"FUTURE DOMAIN CORP. (C) 1986-1989 V6.0A7/28/89", 5, 46, FDOMAIN},
|
|
{"FUTURE DOMAIN CORP. (C) 1986-1990 V6.0105/31/90",5, 47, FDOMAIN},
|
|
{"FUTURE DOMAIN CORP. (C) 1986-1990 V6.0209/18/90",5, 47, FDOMAIN},
|
|
{"FUTURE DOMAIN CORP. (C) 1986-1990 V7.009/18/90", 5, 46, FDOMAIN},
|
|
{"FUTURE DOMAIN CORP. (C) 1992 V8.00.004/02/92", 5, 44, FDOMAIN},
|
|
{"FUTURE DOMAIN TMC-950", 5, 21, FDOMAIN},
|
|
};
|
|
|
|
#define nsignatures (sizeof(signatures) / sizeof(signatures[0]))
|
|
|
|
#ifdef notdef
|
|
static const char *bases[] = {
|
|
(char *) 0xc8000, (char *) 0xca000, (char *) 0xcc000,
|
|
(char *) 0xce000, (char *) 0xdc000, (char *) 0xde000
|
|
};
|
|
|
|
#define nbases (sizeof(bases) / sizeof(bases[0]))
|
|
#endif
|
|
|
|
int seaintr __P((void *));
|
|
void sea_scsipi_request __P((struct scsipi_channel *,
|
|
scsipi_adapter_req_t, void *));
|
|
void sea_timeout __P((void *));
|
|
void sea_done __P((struct sea_softc *, struct sea_scb *));
|
|
struct sea_scb *sea_get_scb __P((struct sea_softc *, int));
|
|
void sea_free_scb __P((struct sea_softc *, struct sea_scb *, int));
|
|
static void sea_main __P((void));
|
|
static void sea_information_transfer __P((struct sea_softc *));
|
|
int sea_poll __P((struct sea_softc *, struct scsipi_xfer *, int));
|
|
void sea_init __P((struct sea_softc *));
|
|
void sea_send_scb __P((struct sea_softc *sea, struct sea_scb *scb));
|
|
void sea_reselect __P((struct sea_softc *sea));
|
|
int sea_select __P((struct sea_softc *sea, struct sea_scb *scb));
|
|
int sea_transfer_pio __P((struct sea_softc *sea, u_char *phase,
|
|
int *count, u_char **data));
|
|
int sea_abort __P((struct sea_softc *, struct sea_scb *scb));
|
|
|
|
void sea_grow_scb __P((struct sea_softc *));
|
|
|
|
int seaprobe __P((struct device *, struct cfdata *, void *));
|
|
void seaattach __P((struct device *, struct device *, void *));
|
|
|
|
struct cfattach sea_ca = {
|
|
sizeof(struct sea_softc), seaprobe, seaattach
|
|
};
|
|
|
|
extern struct cfdriver sea_cd;
|
|
|
|
#ifdef SEA_DEBUGQUEUE
|
|
void
|
|
sea_queue_length(sea)
|
|
struct sea_softc *sea;
|
|
{
|
|
struct sea_scb *scb;
|
|
int connected, issued, disconnected;
|
|
|
|
connected = sea->nexus ? 1 : 0;
|
|
for (scb = sea->ready_list.tqh_first, issued = 0; scb;
|
|
scb = scb->chain.tqe_next, issued++);
|
|
for (scb = sea->nexus_list.tqh_first, disconnected = 0; scb;
|
|
scb = scb->chain.tqe_next, disconnected++);
|
|
printf("%s: length: %d/%d/%d\n", sea->sc_dev.dv_xname, connected,
|
|
issued, disconnected);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Check if the device can be found at the port given and if so, detect the
|
|
* type the type of board. Set it up ready for further work. Takes the isa_dev
|
|
* structure from autoconf as an argument.
|
|
* Returns 1 if card recognized, 0 if errors.
|
|
*/
|
|
int
|
|
seaprobe(parent, match, aux)
|
|
struct device *parent;
|
|
struct cfdata *match;
|
|
void *aux;
|
|
{
|
|
struct isa_attach_args *ia = aux;
|
|
int i, type = 0;
|
|
caddr_t maddr;
|
|
|
|
if (ia->ia_niomem < 1)
|
|
return (0);
|
|
if (ia->ia_nirq < 1)
|
|
return (0);
|
|
|
|
if (ISA_DIRECT_CONFIG(ia))
|
|
return (0);
|
|
|
|
if (ia->ia_iomem[0].ir_addr == ISACF_IOMEM_DEFAULT)
|
|
return (0);
|
|
if (ia->ia_irq[0].ir_irq == ISACF_IRQ_DEFAULT)
|
|
return (0);
|
|
|
|
/* XXX XXX XXX */
|
|
maddr = ISA_HOLE_VADDR(ia->ia_iomem[0].ir_addr);
|
|
|
|
/* check board type */ /* No way to define this through config */
|
|
for (i = 0; i < nsignatures; i++)
|
|
if (!memcmp(maddr + signatures[i].offset,
|
|
signatures[i].signature, signatures[i].length)) {
|
|
type = signatures[i].type;
|
|
break;
|
|
}
|
|
|
|
/* Find controller and data memory addresses */
|
|
switch (type) {
|
|
case SEAGATE:
|
|
case FDOMAIN840:
|
|
case FDOMAIN:
|
|
break;
|
|
default:
|
|
#ifdef DEBUG
|
|
printf("seaprobe: board type unknown at address %p\n", maddr);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
ia->ia_niomem = 1;
|
|
ia->ia_iomem[0].ir_size = 0x2000;
|
|
|
|
ia->ia_nirq = 1;
|
|
|
|
ia->ia_nio = 0;
|
|
ia->ia_ndrq = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Attach all sub-devices we can find
|
|
*/
|
|
void
|
|
seaattach(parent, self, aux)
|
|
struct device *parent, *self;
|
|
void *aux;
|
|
{
|
|
struct isa_attach_args *ia = aux;
|
|
struct sea_softc *sea = (void *)self;
|
|
struct scsipi_adapter *adapt = &sea->sc_adapter;
|
|
struct scsipi_channel *chan = &sea->sc_channel;
|
|
int i;
|
|
|
|
/* XXX XXX XXX */
|
|
sea->maddr = ISA_HOLE_VADDR(ia->ia_iomem[0].ir_addr);
|
|
|
|
/* check board type */ /* No way to define this through config */
|
|
for (i = 0; i < nsignatures; i++)
|
|
if (!memcmp(sea->maddr + signatures[i].offset,
|
|
signatures[i].signature, signatures[i].length)) {
|
|
sea->type = signatures[i].type;
|
|
break;
|
|
}
|
|
|
|
/* Find controller and data memory addresses */
|
|
switch (sea->type) {
|
|
case SEAGATE:
|
|
case FDOMAIN840:
|
|
sea->maddr_cr_sr =
|
|
(void *) (((u_char *)sea->maddr) + 0x1a00);
|
|
sea->maddr_dr =
|
|
(void *) (((u_char *)sea->maddr) + 0x1c00);
|
|
break;
|
|
case FDOMAIN:
|
|
sea->maddr_cr_sr =
|
|
(void *) (((u_char *)sea->maddr) + 0x1c00);
|
|
sea->maddr_dr =
|
|
(void *) (((u_char *)sea->maddr) + 0x1e00);
|
|
break;
|
|
default:
|
|
#ifdef DEBUG
|
|
printf("%s: board type unknown at address %p\n",
|
|
sea->sc_dev.dv_xname, sea->maddr);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/* Test controller RAM (works the same way on future domain cards?) */
|
|
*((u_char *)sea->maddr + SEA_RAMOFFSET) = 0xa5;
|
|
*((u_char *)sea->maddr + SEA_RAMOFFSET + 1) = 0x5a;
|
|
|
|
if ((*((u_char *)sea->maddr + SEA_RAMOFFSET) != 0xa5) ||
|
|
(*((u_char *)sea->maddr + SEA_RAMOFFSET + 1) != 0x5a)) {
|
|
printf("%s: board RAM failure\n", sea->sc_dev.dv_xname);
|
|
return;
|
|
}
|
|
|
|
sea_init(sea);
|
|
|
|
/*
|
|
* Fill in the scsipi_adapter.
|
|
*/
|
|
memset(adapt, 0, sizeof(*adapt));
|
|
adapt->adapt_dev = &sea->sc_dev;
|
|
adapt->adapt_nchannels = 1;
|
|
adapt->adapt_openings = sea->numscbs;
|
|
adapt->adapt_max_periph = 1;
|
|
adapt->adapt_request = sea_scsipi_request;
|
|
adapt->adapt_minphys = minphys;
|
|
|
|
/*
|
|
* Fill in the scsipi_channel.
|
|
*/
|
|
memset(chan, 0, sizeof(*chan));
|
|
chan->chan_adapter = adapt;
|
|
chan->chan_bustype = &scsi_bustype;
|
|
chan->chan_channel = 0;
|
|
chan->chan_ntargets = 8;
|
|
chan->chan_nluns = 8;
|
|
chan->chan_id = sea->our_id;
|
|
chan->chan_flags = SCSIPI_CHAN_CANGROW;
|
|
|
|
printf("\n");
|
|
|
|
sea->sc_ih = isa_intr_establish(ia->ia_ic, ia->ia_irq[0].ir_irq,
|
|
IST_EDGE, IPL_BIO, seaintr, sea);
|
|
|
|
/*
|
|
* ask the adapter what subunits are present
|
|
*/
|
|
config_found(self, &sea->sc_channel, scsiprint);
|
|
}
|
|
|
|
/*
|
|
* Catch an interrupt from the adaptor
|
|
*/
|
|
int
|
|
seaintr(arg)
|
|
void *arg;
|
|
{
|
|
struct sea_softc *sea = arg;
|
|
|
|
#ifdef DEBUG /* extra overhead, and only needed for intr debugging */
|
|
if ((STATUS & STAT_PARITY) == 0 &&
|
|
(STATUS & (STAT_SEL | STAT_IO)) != (STAT_SEL | STAT_IO))
|
|
return 0;
|
|
#endif
|
|
|
|
loop:
|
|
/* dispatch to appropriate routine if found and done=0 */
|
|
/* should check to see that this card really caused the interrupt */
|
|
|
|
if (STATUS & STAT_PARITY) {
|
|
/* Parity error interrupt */
|
|
printf("%s: parity error\n", sea->sc_dev.dv_xname);
|
|
return 1;
|
|
}
|
|
|
|
if ((STATUS & (STAT_SEL | STAT_IO)) == (STAT_SEL | STAT_IO)) {
|
|
/* Reselect interrupt */
|
|
sea_reselect(sea);
|
|
if (!main_running)
|
|
sea_main();
|
|
goto loop;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Setup data structures, and reset the board and the SCSI bus.
|
|
*/
|
|
void
|
|
sea_init(sea)
|
|
struct sea_softc *sea;
|
|
{
|
|
int i;
|
|
|
|
/* Reset the scsi bus (I don't know if this is needed */
|
|
CONTROL = BASE_CMD | CMD_DRVR_ENABLE | CMD_RST;
|
|
delay(25); /* hold reset for at least 25 microseconds */
|
|
CONTROL = BASE_CMD;
|
|
delay(10); /* wait a Bus Clear Delay (800 ns + bus free delay (800 ns) */
|
|
|
|
/* Set our id (don't know anything about this) */
|
|
switch (sea->type) {
|
|
case SEAGATE:
|
|
sea->our_id = 7;
|
|
break;
|
|
case FDOMAIN:
|
|
case FDOMAIN840:
|
|
sea->our_id = 6;
|
|
break;
|
|
}
|
|
sea->our_id_mask = 1 << sea->our_id;
|
|
|
|
/* init fields used by our routines */
|
|
sea->nexus = 0;
|
|
TAILQ_INIT(&sea->ready_list);
|
|
TAILQ_INIT(&sea->nexus_list);
|
|
TAILQ_INIT(&sea->free_list);
|
|
for (i = 0; i < 8; i++)
|
|
sea->busy[i] = 0x00;
|
|
|
|
/* link up the free list of scbs */
|
|
sea->numscbs = SCB_TABLE_SIZE;
|
|
for (i = 0; i < SCB_TABLE_SIZE; i++) {
|
|
TAILQ_INSERT_TAIL(&sea->free_list, &sea->scb[i], chain);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* start a scsi operation given the command and the data address. Also needs
|
|
* the unit, target and lu.
|
|
*/
|
|
void
|
|
sea_scsipi_request(chan, req, arg)
|
|
struct scsipi_channel *chan;
|
|
scsipi_adapter_req_t req;
|
|
void *arg;
|
|
{
|
|
struct scsipi_xfer *xs;
|
|
struct scsipi_periph *periph;
|
|
struct sea_softc *sea = (void *)chan->chan_adapter->adapt_dev;
|
|
struct sea_scb *scb;
|
|
int flags;
|
|
int s;
|
|
|
|
switch (req) {
|
|
case ADAPTER_REQ_RUN_XFER:
|
|
xs = arg;
|
|
periph = xs->xs_periph;
|
|
flags = xs->xs_control;
|
|
|
|
SC_DEBUG(periph, SCSIPI_DB2, ("sea_scsipi_requeset\n"));
|
|
|
|
/* XXX Reset not implemented. */
|
|
if (flags & XS_CTL_RESET) {
|
|
printf("%s: resetting\n", sea->sc_dev.dv_xname);
|
|
xs->error = XS_DRIVER_STUFFUP;
|
|
scsipi_done(xs);
|
|
return;
|
|
}
|
|
|
|
/* Get an SCB to use. */
|
|
scb = sea_get_scb(sea, flags);
|
|
#ifdef DIAGNOSTIC
|
|
/*
|
|
* This should never happen as we track the resources
|
|
* in the mid-layer.
|
|
*/
|
|
if (scb == NULL) {
|
|
scsipi_printaddr(periph);
|
|
printf("unable to allocate scb\n");
|
|
panic("sea_scsipi_request");
|
|
}
|
|
#endif
|
|
|
|
scb->flags = SCB_ACTIVE;
|
|
scb->xs = xs;
|
|
|
|
/*
|
|
* Put all the arguments for the xfer in the scb
|
|
*/
|
|
scb->datalen = xs->datalen;
|
|
scb->data = xs->data;
|
|
|
|
#ifdef SEA_DEBUGQUEUE
|
|
sea_queue_length(sea);
|
|
#endif
|
|
|
|
s = splbio();
|
|
|
|
sea_send_scb(sea, scb);
|
|
|
|
if ((flags & XS_CTL_POLL) == 0) {
|
|
callout_reset(&scb->xs->xs_callout,
|
|
mstohz(xs->timeout), sea_timeout, scb);
|
|
splx(s);
|
|
return;
|
|
}
|
|
|
|
splx(s);
|
|
|
|
/*
|
|
* If we can't use interrupts, poll on completion
|
|
*/
|
|
if (sea_poll(sea, xs, xs->timeout)) {
|
|
sea_timeout(scb);
|
|
if (sea_poll(sea, xs, 2000))
|
|
sea_timeout(scb);
|
|
}
|
|
return;
|
|
|
|
case ADAPTER_REQ_GROW_RESOURCES:
|
|
sea_grow_scb(sea);
|
|
return;
|
|
|
|
case ADAPTER_REQ_SET_XFER_MODE:
|
|
{
|
|
struct scsipi_xfer_mode *xm = arg;
|
|
|
|
/*
|
|
* We don't support sync or wide or tagged queueing,
|
|
* so announce that now.
|
|
*/
|
|
xm->xm_mode = 0;
|
|
xm->xm_period = 0;
|
|
xm->xm_offset = 0;
|
|
scsipi_async_event(chan, ASYNC_EVENT_XFER_MODE, xm);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get a free scb. If there are none, see if we can allocate a new one. If so,
|
|
* put it in the hash table too; otherwise return an error or sleep.
|
|
*/
|
|
struct sea_scb *
|
|
sea_get_scb(sea, flags)
|
|
struct sea_softc *sea;
|
|
int flags;
|
|
{
|
|
int s;
|
|
struct sea_scb *scb;
|
|
|
|
s = splbio();
|
|
if ((scb = TAILQ_FIRST(&sea->free_list)) != NULL)
|
|
TAILQ_REMOVE(&sea->free_list, scb, chain);
|
|
splx(s);
|
|
|
|
return (scb);
|
|
}
|
|
|
|
/*
|
|
* Try to send this command to the board. Because this board does not use any
|
|
* mailboxes, this routine simply adds the command to the queue held by the
|
|
* sea_softc structure.
|
|
* A check is done to see if the command contains a REQUEST_SENSE command, and
|
|
* if so the command is put first in the queue, otherwise the command is added
|
|
* to the end of the queue. ?? Not correct ??
|
|
*/
|
|
void
|
|
sea_send_scb(sea, scb)
|
|
struct sea_softc *sea;
|
|
struct sea_scb *scb;
|
|
{
|
|
|
|
TAILQ_INSERT_TAIL(&sea->ready_list, scb, chain);
|
|
/* Try to do some work on the card. */
|
|
if (!main_running)
|
|
sea_main();
|
|
}
|
|
|
|
/*
|
|
* Coroutine that runs as long as more work can be done on the seagate host
|
|
* adapter in a system. Both sea_scsi_cmd and sea_intr will try to start it in
|
|
* case it is not running.
|
|
*/
|
|
|
|
void
|
|
sea_main()
|
|
{
|
|
struct sea_softc *sea;
|
|
struct sea_scb *scb;
|
|
int done;
|
|
int unit;
|
|
int s;
|
|
|
|
main_running = 1;
|
|
|
|
/*
|
|
* This should not be run with interrupts disabled, but use the splx
|
|
* code instead.
|
|
*/
|
|
loop:
|
|
done = 1;
|
|
for (unit = 0; unit < sea_cd.cd_ndevs; unit++) {
|
|
sea = device_lookup(&sea_cd, unit);
|
|
if (!sea)
|
|
continue;
|
|
s = splbio();
|
|
if (!sea->nexus) {
|
|
/*
|
|
* Search through the ready_list for a command
|
|
* destined for a target that's not busy.
|
|
*/
|
|
for (scb = sea->ready_list.tqh_first; scb;
|
|
scb = scb->chain.tqe_next) {
|
|
if (!(sea->busy[scb->xs->xs_periph->periph_target] &
|
|
(1 << scb->xs->xs_periph->periph_lun))) {
|
|
TAILQ_REMOVE(&sea->ready_list, scb,
|
|
chain);
|
|
|
|
/* Re-enable interrupts. */
|
|
splx(s);
|
|
|
|
/*
|
|
* Attempt to establish an I_T_L nexus.
|
|
* On success, sea->nexus is set.
|
|
* On failure, we must add the command
|
|
* back to the issue queue so we can
|
|
* keep trying.
|
|
*/
|
|
|
|
/*
|
|
* REQUEST_SENSE commands are issued
|
|
* without tagged queueing, even on
|
|
* SCSI-II devices because the
|
|
* contingent alligence condition
|
|
* exists for the entire unit.
|
|
*/
|
|
|
|
/*
|
|
* First check that if any device has
|
|
* tried a reconnect while we have done
|
|
* other things with interrupts
|
|
* disabled.
|
|
*/
|
|
|
|
if ((STATUS & (STAT_SEL | STAT_IO)) ==
|
|
(STAT_SEL | STAT_IO)) {
|
|
sea_reselect(sea);
|
|
break;
|
|
}
|
|
if (sea_select(sea, scb)) {
|
|
s = splbio();
|
|
TAILQ_INSERT_HEAD(&sea->ready_list,
|
|
scb, chain);
|
|
splx(s);
|
|
} else
|
|
break;
|
|
} /* if target/lun is not busy */
|
|
} /* for scb */
|
|
if (!sea->nexus) {
|
|
/* check for reselection phase */
|
|
if ((STATUS & (STAT_SEL | STAT_IO)) ==
|
|
(STAT_SEL | STAT_IO)) {
|
|
sea_reselect(sea);
|
|
}
|
|
}
|
|
} /* if (!sea->nexus) */
|
|
|
|
splx(s);
|
|
if (sea->nexus) { /* we are connected. Do the task */
|
|
sea_information_transfer(sea);
|
|
done = 0;
|
|
} else
|
|
break;
|
|
} /* for instance */
|
|
|
|
if (!done)
|
|
goto loop;
|
|
|
|
main_running = 0;
|
|
}
|
|
|
|
/*
|
|
* Allocate an scb and add it to the free list.
|
|
* We are called at splbio.
|
|
*/
|
|
void
|
|
sea_grow_scb(sea)
|
|
struct sea_softc *sea;
|
|
{
|
|
struct sea_scb *scb;
|
|
|
|
if (sea->numscbs == SEA_SCB_MAX) {
|
|
sea->sc_channel.chan_flags &= ~SCSIPI_CHAN_CANGROW;
|
|
return;
|
|
}
|
|
|
|
scb = malloc(sizeof(struct sea_scb), M_DEVBUF, M_NOWAIT|M_ZERO);
|
|
if (scb == NULL)
|
|
return;
|
|
|
|
TAILQ_INSERT_TAIL(&sea->free_list, scb, chain);
|
|
sea->numscbs++;
|
|
sea->sc_adapter.adapt_openings++;
|
|
}
|
|
void
|
|
sea_free_scb(sea, scb, flags)
|
|
struct sea_softc *sea;
|
|
struct sea_scb *scb;
|
|
int flags;
|
|
{
|
|
int s;
|
|
|
|
s = splbio();
|
|
scb->flags = SCB_FREE;
|
|
TAILQ_INSERT_HEAD(&sea->free_list, scb, chain);
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
sea_timeout(arg)
|
|
void *arg;
|
|
{
|
|
struct sea_scb *scb = arg;
|
|
struct scsipi_xfer *xs = scb->xs;
|
|
struct scsipi_periph *periph = xs->xs_periph;
|
|
struct sea_softc *sea =
|
|
(void *)periph->periph_channel->chan_adapter->adapt_dev;
|
|
int s;
|
|
|
|
scsipi_printaddr(periph);
|
|
printf("timed out");
|
|
|
|
s = splbio();
|
|
|
|
/*
|
|
* If it has been through before, then
|
|
* a previous abort has failed, don't
|
|
* try abort again
|
|
*/
|
|
if (scb->flags & SCB_ABORTED) {
|
|
/* abort timed out */
|
|
printf(" AGAIN\n");
|
|
scb->xs->xs_retries = 0;
|
|
scb->flags |= SCB_ABORTED;
|
|
sea_done(sea, scb);
|
|
} else {
|
|
/* abort the operation that has timed out */
|
|
printf("\n");
|
|
scb->flags |= SCB_ABORTED;
|
|
sea_abort(sea, scb);
|
|
/* 2 secs for the abort */
|
|
if ((xs->xs_control & XS_CTL_POLL) == 0)
|
|
callout_reset(&scb->xs->xs_callout, 2 * hz,
|
|
sea_timeout, scb);
|
|
}
|
|
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
sea_reselect(sea)
|
|
struct sea_softc *sea;
|
|
{
|
|
u_char target_mask;
|
|
int i;
|
|
u_char lun, phase;
|
|
u_char msg[3];
|
|
int len;
|
|
u_char *data;
|
|
struct sea_scb *scb;
|
|
int abort = 0;
|
|
|
|
if (!((target_mask = STATUS) & STAT_SEL)) {
|
|
printf("%s: wrong state 0x%x\n", sea->sc_dev.dv_xname,
|
|
target_mask);
|
|
return;
|
|
}
|
|
|
|
/* wait for a device to win the reselection phase */
|
|
/* signals this by asserting the I/O signal */
|
|
for (i = 10; i && (STATUS & (STAT_SEL | STAT_IO | STAT_BSY)) !=
|
|
(STAT_SEL | STAT_IO | 0); i--);
|
|
/* !! Check for timeout here */
|
|
/* the data bus contains original initiator id ORed with target id */
|
|
target_mask = DATA;
|
|
/* see that we really are the initiator */
|
|
if (!(target_mask & sea->our_id_mask)) {
|
|
printf("%s: polled reselection was not for me: 0x%x\n",
|
|
sea->sc_dev.dv_xname, target_mask);
|
|
return;
|
|
}
|
|
/* find target who won */
|
|
target_mask &= ~sea->our_id_mask;
|
|
/* host responds by asserting the BSY signal */
|
|
CONTROL = BASE_CMD | CMD_DRVR_ENABLE | CMD_BSY;
|
|
/* target should respond by deasserting the SEL signal */
|
|
for (i = 50000; i && (STATUS & STAT_SEL); i++);
|
|
/* remove the busy status */
|
|
CONTROL = BASE_CMD | CMD_DRVR_ENABLE;
|
|
/* we are connected. Now we wait for the MSGIN condition */
|
|
for (i = 50000; i && !(STATUS & STAT_REQ); i--);
|
|
/* !! Add timeout check here */
|
|
/* hope we get an IDENTIFY message */
|
|
len = 3;
|
|
data = msg;
|
|
phase = PH_MSGIN;
|
|
sea_transfer_pio(sea, &phase, &len, &data);
|
|
|
|
if (!MSG_ISIDENTIFY(msg[0])) {
|
|
printf("%s: expecting IDENTIFY message, got 0x%x\n",
|
|
sea->sc_dev.dv_xname, msg[0]);
|
|
abort = 1;
|
|
scb = NULL;
|
|
} else {
|
|
lun = msg[0] & 0x07;
|
|
|
|
/*
|
|
* Find the command corresponding to the I_T_L or I_T_L_Q nexus
|
|
* we just reestablished, and remove it from the disconnected
|
|
* queue.
|
|
*/
|
|
for (scb = sea->nexus_list.tqh_first; scb;
|
|
scb = scb->chain.tqe_next)
|
|
if (target_mask == (1 << scb->xs->xs_periph->periph_target) &&
|
|
lun == scb->xs->xs_periph->periph_lun) {
|
|
TAILQ_REMOVE(&sea->nexus_list, scb,
|
|
chain);
|
|
break;
|
|
}
|
|
if (!scb) {
|
|
printf("%s: target %02x lun %d not disconnected\n",
|
|
sea->sc_dev.dv_xname, target_mask, lun);
|
|
/*
|
|
* Since we have an established nexus that we can't do
|
|
* anything with, we must abort it.
|
|
*/
|
|
abort = 1;
|
|
}
|
|
}
|
|
|
|
if (abort) {
|
|
msg[0] = MSG_ABORT;
|
|
len = 1;
|
|
data = msg;
|
|
phase = PH_MSGOUT;
|
|
CONTROL = BASE_CMD | CMD_ATTN;
|
|
sea_transfer_pio(sea, &phase, &len, &data);
|
|
} else
|
|
sea->nexus = scb;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Transfer data in given phase using polled I/O.
|
|
*/
|
|
int
|
|
sea_transfer_pio(sea, phase, count, data)
|
|
struct sea_softc *sea;
|
|
u_char *phase;
|
|
int *count;
|
|
u_char **data;
|
|
{
|
|
u_char p = *phase, tmp;
|
|
int c = *count;
|
|
u_char *d = *data;
|
|
int timeout;
|
|
|
|
do {
|
|
/*
|
|
* Wait for assertion of REQ, after which the phase bits will
|
|
* be valid.
|
|
*/
|
|
for (timeout = 0; timeout < 50000; timeout++)
|
|
if ((tmp = STATUS) & STAT_REQ)
|
|
break;
|
|
if (!(tmp & STAT_REQ)) {
|
|
printf("%s: timeout waiting for STAT_REQ\n",
|
|
sea->sc_dev.dv_xname);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Check for phase mismatch. Reached if the target decides
|
|
* that it has finished the transfer.
|
|
*/
|
|
if (sea->type == FDOMAIN840)
|
|
tmp = ((tmp & 0x08) >> 2) |
|
|
((tmp & 0x02) << 2) |
|
|
(tmp & 0xf5);
|
|
if ((tmp & PH_MASK) != p)
|
|
break;
|
|
|
|
/* Do actual transfer from SCSI bus to/from memory. */
|
|
if (!(p & STAT_IO))
|
|
DATA = *d;
|
|
else
|
|
*d = DATA;
|
|
++d;
|
|
|
|
/*
|
|
* The SCSI standard suggests that in MSGOUT phase, the
|
|
* initiator should drop ATN on the last byte of the message
|
|
* phase after REQ has been asserted for the handshake but
|
|
* before the initiator raises ACK.
|
|
* Don't know how to accomplish this on the ST01/02.
|
|
*/
|
|
|
|
#if 0
|
|
/*
|
|
* XXX
|
|
* The st01 code doesn't wait for STAT_REQ to be deasserted.
|
|
* Is this ok?
|
|
*/
|
|
for (timeout = 0; timeout < 200000L; timeout++)
|
|
if (!(STATUS & STAT_REQ))
|
|
break;
|
|
if (STATUS & STAT_REQ)
|
|
printf("%s: timeout on wait for !STAT_REQ",
|
|
sea->sc_dev.dv_xname);
|
|
#endif
|
|
} while (--c);
|
|
|
|
*count = c;
|
|
*data = d;
|
|
tmp = STATUS;
|
|
if (tmp & STAT_REQ)
|
|
*phase = tmp & PH_MASK;
|
|
else
|
|
*phase = PH_INVALID;
|
|
|
|
if (c && (*phase != p))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Establish I_T_L or I_T_L_Q nexus for new or existing command including
|
|
* ARBITRATION, SELECTION, and initial message out for IDENTIFY and queue
|
|
* messages. Return -1 if selection could not execute for some reason, 0 if
|
|
* selection succeded or failed because the target did not respond.
|
|
*/
|
|
int
|
|
sea_select(sea, scb)
|
|
struct sea_softc *sea;
|
|
struct sea_scb *scb;
|
|
{
|
|
u_char msg[3], phase;
|
|
u_char *data;
|
|
int len;
|
|
int timeout;
|
|
|
|
CONTROL = BASE_CMD;
|
|
DATA = sea->our_id_mask;
|
|
CONTROL = (BASE_CMD & ~CMD_INTR) | CMD_START_ARB;
|
|
|
|
/* wait for arbitration to complete */
|
|
for (timeout = 0; timeout < 3000000L; timeout++)
|
|
if (STATUS & STAT_ARB_CMPL)
|
|
break;
|
|
if (!(STATUS & STAT_ARB_CMPL)) {
|
|
if (STATUS & STAT_SEL) {
|
|
printf("%s: arbitration lost\n", sea->sc_dev.dv_xname);
|
|
scb->flags |= SCB_ERROR;
|
|
} else {
|
|
printf("%s: arbitration timeout\n",
|
|
sea->sc_dev.dv_xname);
|
|
scb->flags |= SCB_TIMEOUT;
|
|
}
|
|
CONTROL = BASE_CMD;
|
|
return -1;
|
|
}
|
|
|
|
delay(2);
|
|
DATA = (u_char)((1 << scb->xs->xs_periph->periph_target) |
|
|
sea->our_id_mask);
|
|
CONTROL =
|
|
#ifdef SEA_NOMSGS
|
|
(BASE_CMD & ~CMD_INTR) | CMD_DRVR_ENABLE | CMD_SEL;
|
|
#else
|
|
(BASE_CMD & ~CMD_INTR) | CMD_DRVR_ENABLE | CMD_SEL | CMD_ATTN;
|
|
#endif
|
|
delay(1);
|
|
|
|
/* wait for a bsy from target */
|
|
for (timeout = 0; timeout < 2000000L; timeout++)
|
|
if (STATUS & STAT_BSY)
|
|
break;
|
|
if (!(STATUS & STAT_BSY)) {
|
|
/* should return some error to the higher level driver */
|
|
CONTROL = BASE_CMD;
|
|
scb->flags |= SCB_TIMEOUT;
|
|
return 0;
|
|
}
|
|
|
|
/* Try to make the target to take a message from us */
|
|
#ifdef SEA_NOMSGS
|
|
CONTROL = (BASE_CMD & ~CMD_INTR) | CMD_DRVR_ENABLE;
|
|
#else
|
|
CONTROL = (BASE_CMD & ~CMD_INTR) | CMD_DRVR_ENABLE | CMD_ATTN;
|
|
#endif
|
|
delay(1);
|
|
|
|
/* should start a msg_out phase */
|
|
for (timeout = 0; timeout < 2000000L; timeout++)
|
|
if (STATUS & STAT_REQ)
|
|
break;
|
|
/* Remove ATN. */
|
|
CONTROL = BASE_CMD | CMD_DRVR_ENABLE;
|
|
if (!(STATUS & STAT_REQ)) {
|
|
/*
|
|
* This should not be taken as an error, but more like an
|
|
* unsupported feature! Should set a flag indicating that the
|
|
* target don't support messages, and continue without failure.
|
|
* (THIS IS NOT AN ERROR!)
|
|
*/
|
|
} else {
|
|
msg[0] = MSG_IDENTIFY(scb->xs->xs_periph->periph_lun, 1);
|
|
len = 1;
|
|
data = msg;
|
|
phase = PH_MSGOUT;
|
|
/* Should do test on result of sea_transfer_pio(). */
|
|
sea_transfer_pio(sea, &phase, &len, &data);
|
|
}
|
|
if (!(STATUS & STAT_BSY))
|
|
printf("%s: after successful arbitrate: no STAT_BSY!\n",
|
|
sea->sc_dev.dv_xname);
|
|
|
|
sea->nexus = scb;
|
|
sea->busy[scb->xs->xs_periph->periph_target] |=
|
|
1 << scb->xs->xs_periph->periph_lun;
|
|
/* This assignment should depend on possibility to send a message to target. */
|
|
CONTROL = BASE_CMD | CMD_DRVR_ENABLE;
|
|
/* XXX Reset pointer in command? */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Send an abort to the target. Return 1 success, 0 on failure.
|
|
*/
|
|
int
|
|
sea_abort(sea, scb)
|
|
struct sea_softc *sea;
|
|
struct sea_scb *scb;
|
|
{
|
|
struct sea_scb *tmp;
|
|
u_char msg, phase, *msgptr;
|
|
int len;
|
|
|
|
/*
|
|
* If the command hasn't been issued yet, we simply remove it from the
|
|
* issue queue
|
|
* XXX Could avoid this loop.
|
|
*/
|
|
for (tmp = sea->ready_list.tqh_first; tmp; tmp = tmp->chain.tqe_next)
|
|
if (scb == tmp) {
|
|
TAILQ_REMOVE(&sea->ready_list, scb, chain);
|
|
/* XXX Set some type of error result for operation. */
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* If any commands are connected, we're going to fail the abort and let
|
|
* the high level SCSI driver retry at a later time or issue a reset.
|
|
*/
|
|
if (sea->nexus)
|
|
return 0;
|
|
|
|
/*
|
|
* If the command is currently disconnected from the bus, and there are
|
|
* no connected commands, we reconnect the I_T_L or I_T_L_Q nexus
|
|
* associated with it, go into message out, and send an abort message.
|
|
*/
|
|
for (tmp = sea->nexus_list.tqh_first; tmp;
|
|
tmp = tmp->chain.tqe_next)
|
|
if (scb == tmp) {
|
|
if (sea_select(sea, scb))
|
|
return 0;
|
|
|
|
msg = MSG_ABORT;
|
|
msgptr = &msg;
|
|
len = 1;
|
|
phase = PH_MSGOUT;
|
|
CONTROL = BASE_CMD | CMD_ATTN;
|
|
sea_transfer_pio(sea, &phase, &len, &msgptr);
|
|
|
|
for (tmp = sea->nexus_list.tqh_first; tmp;
|
|
tmp = tmp->chain.tqe_next)
|
|
if (scb == tmp) {
|
|
TAILQ_REMOVE(&sea->nexus_list,
|
|
scb, chain);
|
|
/* XXX Set some type of error result
|
|
for the operation. */
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Command not found in any queue; race condition? */
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
sea_done(sea, scb)
|
|
struct sea_softc *sea;
|
|
struct sea_scb *scb;
|
|
{
|
|
struct scsipi_xfer *xs = scb->xs;
|
|
|
|
callout_stop(&scb->xs->xs_callout);
|
|
|
|
xs->resid = scb->datalen;
|
|
|
|
/* XXXX need to get status */
|
|
if (scb->flags == SCB_ACTIVE) {
|
|
xs->resid = 0;
|
|
} else {
|
|
if (scb->flags & (SCB_TIMEOUT | SCB_ABORTED))
|
|
xs->error = XS_TIMEOUT;
|
|
if (scb->flags & SCB_ERROR)
|
|
xs->error = XS_DRIVER_STUFFUP;
|
|
}
|
|
sea_free_scb(sea, scb, xs->xs_control);
|
|
scsipi_done(xs);
|
|
}
|
|
|
|
/*
|
|
* Wait for completion of command in polled mode.
|
|
*/
|
|
int
|
|
sea_poll(sea, xs, count)
|
|
struct sea_softc *sea;
|
|
struct scsipi_xfer *xs;
|
|
int count;
|
|
{
|
|
int s;
|
|
|
|
while (count) {
|
|
/* try to do something */
|
|
s = splbio();
|
|
if (!main_running)
|
|
sea_main();
|
|
splx(s);
|
|
if (xs->xs_status & XS_STS_DONE)
|
|
return 0;
|
|
delay(1000);
|
|
count--;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Do the transfer. We know we are connected. Update the flags, and call
|
|
* sea_done() when task accomplished. Dialog controlled by the target.
|
|
*/
|
|
void
|
|
sea_information_transfer(sea)
|
|
struct sea_softc *sea;
|
|
{
|
|
int timeout;
|
|
u_char msgout = MSG_NOOP;
|
|
int len;
|
|
int s;
|
|
u_char *data;
|
|
u_char phase, tmp, old_phase = PH_INVALID;
|
|
struct sea_scb *scb = sea->nexus;
|
|
int loop;
|
|
|
|
for (timeout = 0; timeout < 10000000L; timeout++) {
|
|
tmp = STATUS;
|
|
if (tmp & STAT_PARITY)
|
|
printf("%s: parity error detected\n",
|
|
sea->sc_dev.dv_xname);
|
|
if (!(tmp & STAT_BSY)) {
|
|
for (loop = 0; loop < 20; loop++)
|
|
if ((tmp = STATUS) & STAT_BSY)
|
|
break;
|
|
if (!(tmp & STAT_BSY)) {
|
|
printf("%s: !STAT_BSY unit in data transfer!\n",
|
|
sea->sc_dev.dv_xname);
|
|
s = splbio();
|
|
sea->nexus = NULL;
|
|
scb->flags = SCB_ERROR;
|
|
splx(s);
|
|
sea_done(sea, scb);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* we only have a valid SCSI phase when REQ is asserted */
|
|
if (!(tmp & STAT_REQ))
|
|
continue;
|
|
|
|
if (sea->type == FDOMAIN840)
|
|
tmp = ((tmp & 0x08) >> 2) |
|
|
((tmp & 0x02) << 2) |
|
|
(tmp & 0xf5);
|
|
phase = tmp & PH_MASK;
|
|
if (phase != old_phase)
|
|
old_phase = phase;
|
|
|
|
switch (phase) {
|
|
case PH_DATAOUT:
|
|
#ifdef SEA_NODATAOUT
|
|
printf("%s: SEA_NODATAOUT set, attempted DATAOUT aborted\n",
|
|
sea->sc_dev.dv_xname);
|
|
msgout = MSG_ABORT;
|
|
CONTROL = BASE_CMD | CMD_ATTN;
|
|
break;
|
|
#endif
|
|
case PH_DATAIN:
|
|
if (!scb->data)
|
|
printf("no data address!\n");
|
|
#ifdef SEA_BLINDTRANSFER
|
|
if (scb->datalen && !(scb->datalen % BLOCK_SIZE)) {
|
|
while (scb->datalen) {
|
|
for (loop = 0; loop < 50000; loop++)
|
|
if ((tmp = STATUS) & STAT_REQ)
|
|
break;
|
|
if (!(tmp & STAT_REQ)) {
|
|
printf("%s: timeout waiting for STAT_REQ\n",
|
|
sea->sc_dev.dv_xname);
|
|
/* XXX Do something? */
|
|
}
|
|
if (sea->type == FDOMAIN840)
|
|
tmp = ((tmp & 0x08) >> 2) |
|
|
((tmp & 0x02) << 2) |
|
|
(tmp & 0xf5);
|
|
if ((tmp & PH_MASK) != phase)
|
|
break;
|
|
if (!(phase & STAT_IO)) {
|
|
#ifdef SEA_ASSEMBLER
|
|
caddr_t junk;
|
|
asm("cld\n\t\
|
|
rep\n\t\
|
|
movsl" :
|
|
"=S" (scb->data),
|
|
"=c" (len),
|
|
"=D" (junk) :
|
|
"0" (scb->data),
|
|
"1" (BLOCK_SIZE >> 2),
|
|
"2" (sea->maddr_dr));
|
|
#else
|
|
for (len = BLOCK_SIZE;
|
|
len; len--)
|
|
DATA = *(scb->data++);
|
|
#endif
|
|
} else {
|
|
#ifdef SEA_ASSEMBLER
|
|
caddr_t junk;
|
|
asm("cld\n\t\
|
|
rep\n\t\
|
|
movsl" :
|
|
"=D" (scb->data),
|
|
"=c" (len),
|
|
"=S" (junk) :
|
|
"0" (scb->data),
|
|
"1" (BLOCK_SIZE >> 2),
|
|
"2" (sea->maddr_dr));
|
|
#else
|
|
for (len = BLOCK_SIZE;
|
|
len; len--)
|
|
*(scb->data++) = DATA;
|
|
#endif
|
|
}
|
|
scb->datalen -= BLOCK_SIZE;
|
|
}
|
|
}
|
|
#endif
|
|
if (scb->datalen)
|
|
sea_transfer_pio(sea, &phase, &scb->datalen,
|
|
&scb->data);
|
|
break;
|
|
case PH_MSGIN:
|
|
/* Multibyte messages should not be present here. */
|
|
len = 1;
|
|
data = &tmp;
|
|
sea_transfer_pio(sea, &phase, &len, &data);
|
|
/* scb->MessageIn = tmp; */
|
|
|
|
switch (tmp) {
|
|
case MSG_ABORT:
|
|
scb->flags = SCB_ABORTED;
|
|
printf("sea: command aborted by target\n");
|
|
CONTROL = BASE_CMD;
|
|
sea_done(sea, scb);
|
|
return;
|
|
case MSG_CMDCOMPLETE:
|
|
s = splbio();
|
|
sea->nexus = NULL;
|
|
splx(s);
|
|
sea->busy[scb->xs->xs_periph->periph_target] &=
|
|
~(1 << scb->xs->xs_periph->periph_lun);
|
|
CONTROL = BASE_CMD;
|
|
sea_done(sea, scb);
|
|
return;
|
|
case MSG_MESSAGE_REJECT:
|
|
printf("%s: message_reject received\n",
|
|
sea->sc_dev.dv_xname);
|
|
break;
|
|
case MSG_DISCONNECT:
|
|
s = splbio();
|
|
TAILQ_INSERT_TAIL(&sea->nexus_list,
|
|
scb, chain);
|
|
sea->nexus = NULL;
|
|
CONTROL = BASE_CMD;
|
|
splx(s);
|
|
return;
|
|
case MSG_SAVEDATAPOINTER:
|
|
case MSG_RESTOREPOINTERS:
|
|
/* save/restore of pointers are ignored */
|
|
break;
|
|
default:
|
|
/*
|
|
* This should be handled in the pio data
|
|
* transfer phase, as the ATN should be raised
|
|
* before ACK goes false when rejecting a
|
|
* message.
|
|
*/
|
|
printf("%s: unknown message in: %x\n",
|
|
sea->sc_dev.dv_xname, tmp);
|
|
break;
|
|
} /* switch (tmp) */
|
|
break;
|
|
case PH_MSGOUT:
|
|
len = 1;
|
|
data = &msgout;
|
|
/* sea->last_message = msgout; */
|
|
sea_transfer_pio(sea, &phase, &len, &data);
|
|
if (msgout == MSG_ABORT) {
|
|
printf("%s: sent message abort to target\n",
|
|
sea->sc_dev.dv_xname);
|
|
s = splbio();
|
|
sea->busy[scb->xs->xs_periph->periph_target] &=
|
|
~(1 << scb->xs->xs_periph->periph_lun);
|
|
sea->nexus = NULL;
|
|
scb->flags = SCB_ABORTED;
|
|
splx(s);
|
|
/* enable interrupt from scsi */
|
|
sea_done(sea, scb);
|
|
return;
|
|
}
|
|
msgout = MSG_NOOP;
|
|
break;
|
|
case PH_CMD:
|
|
len = scb->xs->cmdlen;
|
|
data = (char *) scb->xs->cmd;
|
|
sea_transfer_pio(sea, &phase, &len, &data);
|
|
break;
|
|
case PH_STAT:
|
|
len = 1;
|
|
data = &tmp;
|
|
sea_transfer_pio(sea, &phase, &len, &data);
|
|
scb->xs->status = tmp;
|
|
break;
|
|
default:
|
|
printf("sea: unknown phase\n");
|
|
} /* switch (phase) */
|
|
} /* for (...) */
|
|
|
|
/* If we get here we have got a timeout! */
|
|
printf("%s: timeout in data transfer\n", sea->sc_dev.dv_xname);
|
|
scb->flags = SCB_TIMEOUT;
|
|
/* XXX Should I clear scsi-bus state? */
|
|
sea_done(sea, scb);
|
|
}
|