* Copyright (c) 1992 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* %sccs.include.redist.c%
* @(#)asc.c 7.1 (Berkeley) %G%
* Copyright (c) 1991,1990,1989 Carnegie Mellon University
* Permission to use, copy, modify and distribute this software and its
* documentation is hereby granted, provided that both the copyright
* notice and this permission notice appear in all copies of the
* software, derivative works or modified versions, and any portions
* thereof, and that both notices appear in supporting documentation.
* CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS
* CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
* ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
* Carnegie Mellon requests users of this software to return to
* Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
* School of Computer Science
* Carnegie Mellon University
* Pittsburgh PA 15213-3890
* any improvements or extensions that they make and grant Carnegie the
* rights to redistribute these changes.
* $Log: scsi_53C94_hdw.c,v $
* Revision 2.5 91/02/05 17:45:07 mrt
* [91/02/04 11:18:43 mrt]
* Changed to use new Mach copyright
* [91/02/02 12:17:20 mrt]
* Revision 2.4 91/01/08 15:48:24 rpd
* Added continuation argument to thread_block.
* Revision 2.3 90/12/05 23:34:48 af
* Recovered from pmax merge.. and from the destruction of a disk.
* Revision 2.1.1.1 90/11/01 03:39:09 af
* Created, from the DEC specs:
* "PMAZ-AA TURBOchannel SCSI Module Functional Specification"
* Workstation Systems Engineering, Palo Alto, CA. Aug 27, 1990.
* And from the NCR data sheets
* "NCR 53C94, 53C95, 53C96 Advances SCSI Controller"
* Author: Alessandro Forin, Carnegie Mellon University
* Bottom layer of the SCSI driver: chip-dependent functions
* This file contains the code that is specific to the NCR 53C94
* SCSI chip (Host Bus Adapter in SCSI parlance): probing, start
* operation, and interrupt routine.
* This layer works based on small simple 'scripts' that are installed
* at the start of the command and drive the chip to completion.
* The idea comes from the specs of the NCR 53C700 'script' processor.
* There are various reasons for this, mainly
* - Performance: identify the common (successful) path, and follow it;
* at interrupt time no code is needed to find the current status
* - Code size: it should be easy to compact common operations
* - Adaptability: the code skeleton should adapt to different chips without
* terrible complications.
* - Error handling: and it is easy to modify the actions performed
* by the scripts to cope with strange but well identified sequences
#define ASC_OFFSET_53C94 0x0 /* from module base */
#define ASC_OFFSET_DMAR 0x40000 /* DMA Address Register */
#define ASC_OFFSET_RAM 0x80000 /* SRAM Buffer */
#define ASC_OFFSET_ROM 0xc0000 /* Diagnostic ROM */
#define ASC_RAM_SIZE 0x20000 /* 128k (32k*32) */
#define ASC_DMAR_MASK 0x1ffff /* 17 bits, 128k */
#define ASC_DMAR_WRITE 0x80000000 /* DMA direction bit */
#define ASC_DMA_ADDR(x) ((unsigned)(x)) & ASC_DMAR_MASK
* Synch xfer parameters, and timing conversions
#define SCSI_MIN_PERIOD 50 /* in 4 nsecs units */
#define ASC_MIN_PERIOD 5 /* in CLKS/BYTE, 1 CLK = 40nsecs */
#define ASC_MAX_PERIOD 35 /* in CLKS/BYTE, 1 CLK = 40nsecs */
#define ASC_MAX_OFFSET 15 /* pure number */
int asc_to_scsi_period
[] = {
* Internal forward declarations.
static void asc_startcmd();
} asc_log
[NLOG
], *asc_logp
= asc_log
;
#define PACK(unit, status, ss, ir) \
((unit << 24) | (status << 16) | (ss << 8) | ir)
* Scripts are entries in a state machine table.
* A script has four parts: a pre-condition, an action, a command to the chip,
* and an index into asc_scripts for the next state. The first triggers error
* handling if not satisfied and in our case it is formed by the
* values of the interrupt register and status register, this
* basically captures the phase of the bus and the TC and BS
* bits. The action part is just a function pointer, and the
* command is what the 53C94 should be told to do at the end
* of the action processing. This command is only issued and the
* script proceeds if the action routine returns TRUE.
* See asc_intr() for how and where this is all done.
int condition
; /* expected state at interrupt time */
int (*action
)(); /* extra operations */
int command
; /* command to the chip */
struct script
*next
; /* index into asc_scripts for next state */
/* Matching on the condition value */
#define SCRIPT_MATCH(ir, csr) ((ir) | (ASC_PHASE(csr) << 8))
/* forward decls of script actions */
int script_nop(); /* when nothing needed */
int asc_end(); /* all come to an end */
int asc_get_status(); /* get status from target */
int asc_dma_in(); /* start reading data from target */
int asc_last_dma_in(); /* cleanup after all data is read */
int asc_resume_dma_in(); /* resume DMA after a disconnect */
int asc_dma_out(); /* send data to target via dma */
int asc_last_dma_out(); /* cleanup after all data is written */
int asc_resume_dma_out(); /* resume DMA after a disconnect */
int asc_sendsync(); /* negotiate sync xfer */
int asc_replysync(); /* negotiate sync xfer */
int asc_recvmsg(); /* process a message byte */
/* Define the index into asc_scripts for various state transitions */
#define SCRIPT_DATA_OUT 2
#define SCRIPT_GET_STATUS 5
#define SCRIPT_REPLY_SYNC 9
#define SCRIPT_RESUME_IN 10
#define SCRIPT_RESUME_OUT 11
#define SCRIPT_TRY_SYNC 12
#define SCRIPT_RESUME_DMA_IN 16
#define SCRIPT_RESUME_DMA_OUT 17
script_t asc_scripts
[] = {
{SCRIPT_MATCH(ASC_INT_FC
| ASC_INT_BS
, ASC_PHASE_DATAI
), /* 0 */
asc_dma_in
, ASC_CMD_XFER_INFO
| ASC_CMD_DMA
,
&asc_scripts
[SCRIPT_DATA_IN
+ 1] },
{SCRIPT_MATCH(ASC_INT_BS
, ASC_PHASE_STATUS
), /* 1 */
asc_last_dma_in
, ASC_CMD_I_COMPLETE
,
&asc_scripts
[SCRIPT_GET_STATUS
] },
{SCRIPT_MATCH(ASC_INT_FC
| ASC_INT_BS
, ASC_PHASE_DATAO
), /* 2 */
asc_dma_out
, ASC_CMD_XFER_INFO
| ASC_CMD_DMA
,
&asc_scripts
[SCRIPT_DATA_OUT
+ 1] },
{SCRIPT_MATCH(ASC_INT_BS
, ASC_PHASE_STATUS
), /* 3 */
asc_last_dma_out
, ASC_CMD_I_COMPLETE
,
&asc_scripts
[SCRIPT_GET_STATUS
] },
/* simple command with no data transfer */
{SCRIPT_MATCH(ASC_INT_FC
| ASC_INT_BS
, ASC_PHASE_STATUS
), /* 4 */
script_nop
, ASC_CMD_I_COMPLETE
,
&asc_scripts
[SCRIPT_GET_STATUS
] },
/* get status and finish command */
{SCRIPT_MATCH(ASC_INT_FC
, ASC_PHASE_MSG_IN
), /* 5 */
asc_get_status
, ASC_CMD_MSG_ACPT
,
&asc_scripts
[SCRIPT_GET_STATUS
+ 1] },
{SCRIPT_MATCH(ASC_INT_DISC
, 0), /* 6 */
&asc_scripts
[SCRIPT_GET_STATUS
+ 1] },
{SCRIPT_MATCH(ASC_INT_FC
, ASC_PHASE_MSG_IN
), /* 7 */
asc_recvmsg
, ASC_CMD_MSG_ACPT
,
&asc_scripts
[SCRIPT_MSG_IN
+ 1] },
{SCRIPT_MATCH(ASC_INT_BS
, ASC_PHASE_MSG_IN
), /* 8 */
script_nop
, ASC_CMD_XFER_INFO
,
&asc_scripts
[SCRIPT_MSG_IN
] },
/* send synchonous negotiation reply */
{SCRIPT_MATCH(ASC_INT_BS
, ASC_PHASE_MSG_OUT
), /* 9 */
asc_replysync
, ASC_CMD_XFER_INFO
,
&asc_scripts
[SCRIPT_REPLY_SYNC
] },
/* resume data in after a message */
{SCRIPT_MATCH(ASC_INT_BS
, ASC_PHASE_DATAI
), /* 10 */
asc_dma_in
, ASC_CMD_XFER_INFO
| ASC_CMD_DMA
,
&asc_scripts
[SCRIPT_DATA_IN
+ 1] },
/* resume data out after a message */
{SCRIPT_MATCH(ASC_INT_BS
, ASC_PHASE_DATAO
), /* 11 */
asc_dma_out
, ASC_CMD_XFER_INFO
| ASC_CMD_DMA
,
&asc_scripts
[SCRIPT_DATA_OUT
+ 1] },
/* try to negotiate synchonous transfer parameters */
{SCRIPT_MATCH(ASC_INT_FC
| ASC_INT_BS
, ASC_PHASE_MSG_OUT
), /* 12 */
asc_sendsync
, ASC_CMD_XFER_INFO
,
&asc_scripts
[SCRIPT_GET_STATUS
] },
{SCRIPT_MATCH(ASC_INT_FC
, ASC_PHASE_MSG_IN
), /* 13 */
script_nop
, ASC_CMD_XFER_INFO
,
&asc_scripts
[SCRIPT_GET_STATUS
] },
{SCRIPT_MATCH(ASC_INT_FC
, ASC_PHASE_MSG_IN
), /* 14 */
asc_recvmsg
, ASC_CMD_MSG_ACPT
,
&asc_scripts
[SCRIPT_GET_STATUS
] },
/* reselect sequence: this is just a placeholder so match fails */
{SCRIPT_MATCH(0, ASC_PHASE_MSG_IN
), /* 15 */
script_nop
, ASC_CMD_MSG_ACPT
,
&asc_scripts
[SCRIPT_RESEL
] },
/* resume data in after a disconnect */
{SCRIPT_MATCH(ASC_INT_BS
, ASC_PHASE_DATAI
), /* 16 */
asc_resume_dma_in
, ASC_CMD_XFER_INFO
| ASC_CMD_DMA
,
&asc_scripts
[SCRIPT_DATA_IN
+ 1] },
/* resume data out after a disconnect */
{SCRIPT_MATCH(ASC_INT_BS
, ASC_PHASE_DATAO
), /* 17 */
asc_resume_dma_out
, ASC_CMD_XFER_INFO
| ASC_CMD_DMA
,
&asc_scripts
[SCRIPT_DATA_OUT
+ 1] },
* State kept for each active SCSI device.
typedef struct scsi_state
{
script_t
*script
; /* saved script while processing error */
int statusByte
; /* status byte returned during STATUS_PHASE */
int error
; /* errno to pass back to device driver */
u_char
*dmaBufAddr
; /* DMA buffer address */
u_int dmaBufSize
; /* DMA buffer size */
int dmalen
; /* amount to transfer in this chunk */
int dmaresid
; /* amount not transfered if chunk suspended */
int buflen
; /* total remaining amount of data to transfer */
char *buf
; /* current pointer within scsicmd->buf */
int flags
; /* see below */
int msglen
; /* number of message bytes to read */
int msgcnt
; /* number of message bytes received */
u_char sync_period
; /* DMA synchronous period */
u_char sync_offset
; /* DMA synchronous xfer offset or 0 if async */
u_char msg_out
; /* next MSG_OUT byte to send */
u_char msg_in
[16]; /* buffer for multibyte messages */
#define DISCONN 0x01 /* true if currently disconnected from bus */
#define FIRST_DMA 0x02 /* true if no data DMA started yet */
#define DMA_IN 0x04 /* true if reading from SCSI device */
#define DMA_OUT 0x10 /* true if writing to SCSI device */
#define DID_SYNC 0x20 /* true if synchronous offset was negotiated */
#define TRY_SYNC 0x40 /* true if try neg. synchronous offset */
* State kept for each active SCSI host interface (53C94).
asc_regmap_t
*regs
; /* chip address */
volatile int *dmar
; /* DMA address register address */
volatile u_char
*buff
; /* RAM buffer address */
int myid
; /* SCSI ID of this interface */
int myidmask
; /* ~(1 << myid) */
int state
; /* current SCSI connection state */
int target
; /* target SCSI ID if busy */
script_t
*script
; /* next expected interrupt & action */
ScsiCmd
*cmd
[ASC_NCMD
]; /* active command indexed by SCSI ID */
State st
[ASC_NCMD
]; /* state info for each active command */
#define ASC_STATE_IDLE 0 /* idle state */
#define ASC_STATE_BUSY 1 /* selecting or currently connected */
#define ASC_STATE_TARGET 2 /* currently selected as target */
#define ASC_STATE_RESEL 3 /* currently waiting for reselect */
typedef struct asc_softc
*asc_softc_t
;
* Definition of the controller for the auto-configuration program.
struct driver ascdriver
= {
"asc", asc_probe
, asc_start
, 0, asc_intr
,
* Test to see if device is present.
* Return true if found and initialized ok.
register struct pmax_ctlr
*cp
;
register asc_softc_t asc
;
register asc_regmap_t
*regs
;
if ((unit
= cp
->pmax_unit
) >= NASC
)
if (badaddr(cp
->pmax_addr
+ ASC_OFFSET_53C94
, 1))
* Initialize hw descriptor, cache some pointers
asc
->regs
= (asc_regmap_t
*)(cp
->pmax_addr
+ ASC_OFFSET_53C94
);
asc
->dmar
= (volatile int *)(cp
->pmax_addr
+ ASC_OFFSET_DMAR
);
asc
->buff
= (volatile u_char
*)(cp
->pmax_addr
+ ASC_OFFSET_RAM
);
asc
->state
= ASC_STATE_IDLE
;
* Reset chip, fully. Note that interrupts are already enabled.
/* preserve our ID for now */
asc
->myid
= regs
->asc_cnfg1
& ASC_CNFG1_MY_BUS_ID
;
asc
->myidmask
= ~(1 << asc
->myid
);
* Our SCSI id on the bus.
* The user can set this via the prom on 3maxen/pmaxen.
* If this changes it is easy to fix: make a default that
* can be changed as boot arg.
regs
->asc_cnfg1
= (regs
->asc_cnfg1
& ~ASC_CNFG1_MY_BUS_ID
) |
(scsi_initiator_id
[unit
] & 0x7);
id
= regs
->asc_cnfg1
& ASC_CNFG1_MY_BUS_ID
;
* Statically partition the DMA buffer between targets.
* This way we will eventually be able to attach/detach
* drives on-fly. And 18k/target is plenty for normal use.
#define PER_TGT_DMA_SIZE ((ASC_RAM_SIZE/7) & ~(sizeof(int)-1))
* Give each target its own DMA buffer region.
* We may want to try ping ponging buffers later.
for (i
= 0; i
< ASC_NCMD
; i
++) {
asc
->st
[i
].dmaBufAddr
= asc
->buff
+ PER_TGT_DMA_SIZE
* i
;
asc
->st
[i
].dmaBufSize
= PER_TGT_DMA_SIZE
;
printf("asc%d at nexus0 csr 0x%x priority %d SCSI id %d\n",
unit
, cp
->pmax_addr
, cp
->pmax_pri
, id
);
* Start activity on a SCSI device.
* We maintain information on each device separately since devices can
* connect/disconnect during an operation.
register ScsiCmd
*scsicmd
; /* command to start */
register struct scsi_device
*sdp
= scsicmd
->sd
;
register asc_softc_t asc
= &asc_softc
[sdp
->sd_ctlr
];
* Check if another command is already in progress.
* We may have to change this if we allow SCSI devices with
if (asc
->cmd
[sdp
->sd_drive
]) {
printf("asc%d: device %s busy at start\n", sdp
->sd_ctlr
,
(*sdp
->sd_driver
->d_done
)(scsicmd
->unit
, EBUSY
,
asc
->cmd
[sdp
->sd_drive
] = scsicmd
;
asc_startcmd(asc
, sdp
->sd_drive
);
* Reset chip and wait till done
regs
->asc_cmd
= ASC_CMD_RESET
;
MachEmptyWriteBuffer(); DELAY(25);
/* spec says this is needed after reset */
regs
->asc_cmd
= ASC_CMD_NOP
;
MachEmptyWriteBuffer(); DELAY(25);
* Set up various chip parameters
regs
->asc_ccf
= ASC_CCF_25MHz
; /* 25 MHz clock */
MachEmptyWriteBuffer(); DELAY(25);
regs
->asc_sel_timo
= ASC_TIMEOUT_250
;
regs
->asc_cnfg1
= asc
->myid
| ASC_CNFG1_P_CHECK
;
regs
->asc_cnfg2
= /* ASC_CNFG2_RFB | */ ASC_CNFG2_EPL
;
regs
->asc_sel_timo
= ASC_TIMEOUT_250
;
regs
->asc_syn_p
= ASC_MIN_PERIOD
;
regs
->asc_syn_o
= 0; /* async for now */
* Start a SCSI command on a target.
asc_startcmd(asc
, target
)
register asc_regmap_t
*regs
;
register ScsiCmd
*scsicmd
;
* See if another target is currently selected on this SCSI bus.
* Check to see if a reselection is in progress and if so,
* try to cancel it or respond to the reselection if it won.
if (asc
->state
== ASC_STATE_RESEL
) {
regs
->asc_cmd
= ASC_CMD_DISABLE_SEL
;
while (!(regs
->asc_status
& ASC_CSR_INT
))
asc_intr(asc
- asc_softc
);
/* we will be busy if a reselecting device won */
if (asc
->state
== ASC_STATE_BUSY
)
asc
->state
= ASC_STATE_BUSY
;
/* cache some pointers */
scsicmd
= asc
->cmd
[target
];
state
= &asc
->st
[target
];
printf("asc_startcmd: %s target %d cmd %x len %d\n",
scsicmd
->sd
->sd_driver
->d_name
, target
,
scsicmd
->cmd
[0], scsicmd
->buflen
);
asc_debug_cmd
= scsicmd
->cmd
[0];
if (scsicmd
->cmd
[0] == SCSI_READ_EXT
) {
asc_debug_bn
= (scsicmd
->cmd
[2] << 24) |
(scsicmd
->cmd
[3] << 16) |
asc_debug_sz
= (scsicmd
->cmd
[7] << 8) | scsicmd
->cmd
[8];
asc_logp
->status
= PACK(asc
- asc_softc
, 0, 0, 0);
asc_logp
->target
= asc
->target
;
if (++asc_logp
>= &asc_log
[NLOG
])
* Init the chip and target state.
regs
->asc_cmd
= ASC_CMD_FLUSH
;
state
->flags
= FIRST_DMA
| (state
->flags
& DID_SYNC
);
state
->script
= (script_t
*)0;
state
->msg_out
= SCSI_NO_OP
;
* Copy command data to the DMA buffer.
bcopy(scsicmd
->cmd
, state
->dmaBufAddr
, len
);
/* check for simple SCSI command with no data transfer */
if ((state
->buflen
= scsicmd
->buflen
) == 0) {
/* check for sync negotiation */
if ((scsicmd
->flags
& SCSICMD_USE_SYNC
) &&
!(state
->flags
& DID_SYNC
)) {
asc
->script
= &asc_scripts
[SCRIPT_TRY_SYNC
];
state
->flags
|= TRY_SYNC
;
asc
->script
= &asc_scripts
[SCRIPT_SIMPLE
];
} else if (scsicmd
->flags
& SCSICMD_DATA_TO_DEVICE
) {
asc
->script
= &asc_scripts
[SCRIPT_DATA_OUT
];
/* setup to write first chunk */
state
->buf
= scsicmd
->buf
;
cnt
= state
->dmaBufSize
- len
;
else printf("can't write in one chunk cnt %d buflen %d\n",
cnt
, state
->buflen
); /* XXX */
bcopy(state
->buf
, state
->dmaBufAddr
+ len
, cnt
);
asc
->script
= &asc_scripts
[SCRIPT_DATA_IN
];
state
->buf
= scsicmd
->buf
;
/* preload the FIFO with the message to be sent */
regs
->asc_fifo
= /* SCSI_IDENTIFY */ SCSI_DIS_REC_IDENTIFY
;
*asc
->dmar
= ASC_DMAR_WRITE
| ASC_DMA_ADDR(state
->dmaBufAddr
);
regs
->asc_dbus_id
= target
;
regs
->asc_syn_p
= state
->sync_period
;
regs
->asc_syn_o
= state
->sync_offset
;
if (state
->flags
& TRY_SYNC
)
regs
->asc_cmd
= ASC_CMD_SEL_ATN_STOP
| ASC_CMD_DMA
;
regs
->asc_cmd
= ASC_CMD_SEL_ATN
| ASC_CMD_DMA
;
* Take interrupts from the chip
* Move along the current command's script if
* all is well, invoke error handler if not.
register asc_softc_t asc
= &asc_softc
[unit
];
register asc_regmap_t
*regs
= asc
->regs
;
register int ss
, ir
, status
;
/* collect ephemeral information */
status
= regs
->asc_status
;
ir
= regs
->asc_intr
; /* this resets the previous two */
asc_logp
->status
= PACK(unit
, status
, ss
, ir
);
asc_logp
->target
= (asc
->state
== ASC_STATE_BUSY
) ? asc
->target
: -1;
asc_logp
->state
= scpt
- asc_scripts
;
if (++asc_logp
>= &asc_log
[NLOG
])
printf("asc_intr: status %x ss %x ir %x cond %d:%x\n",
status
, ss
, ir
, scpt
- asc_scripts
, scpt
->condition
);
/* check the expected state */
if (SCRIPT_MATCH(ir
, status
) == scpt
->condition
) {
* Perform the appropriate operation, then proceed.
if ((*scpt
->action
)(asc
, status
, ss
, ir
)) {
regs
->asc_cmd
= scpt
->command
;
asc
->script
= scpt
->next
;
/* check for message in or out */
if ((ir
& ~ASC_INT_FC
) == ASC_INT_BS
) {
state
= &asc
->st
[asc
->target
];
switch (ASC_PHASE(status
)) {
regs
->asc_fifo
= state
->msg_out
;
state
->msg_out
= SCSI_NO_OP
;
regs
->asc_cmd
= ASC_CMD_XFER_INFO
;
/* probably an error in the SCSI command */
asc
->script
= &asc_scripts
[SCRIPT_GET_STATUS
];
regs
->asc_cmd
= ASC_CMD_I_COMPLETE
;
/* check for DMA in progress */
fifo
= regs
->asc_flags
& ASC_FLAGS_FIFO_CNT
;
/* flush any data in the FIFO */
printf("asc_intr: suspend flags %x dmalen %d len %d fifo %d\n",
state
->flags
, state
->dmalen
,
regs
->asc_cmd
= ASC_CMD_FLUSH
;
/* save number of bytes still to be sent or received */
/* setup state to resume to */
if (state
->flags
& DMA_IN
)
&asc_scripts
[SCRIPT_RESUME_DMA_IN
];
else if (state
->flags
& DMA_OUT
)
&asc_scripts
[SCRIPT_RESUME_DMA_OUT
];
state
->script
= asc
->script
;
/* setup state to resume to */
if (state
->flags
& DMA_IN
)
state
->script
= &asc_scripts
[SCRIPT_RESUME_IN
];
else if (state
->flags
& DMA_OUT
)
state
->script
= &asc_scripts
[SCRIPT_RESUME_OUT
];
state
->script
= asc
->script
;
/* setup to receive a message */
asc
->script
= &asc_scripts
[SCRIPT_MSG_IN
];
regs
->asc_cmd
= ASC_CMD_XFER_INFO
;
/* check for SCSI bus reset */
if (ir
& ASC_INT_RESET
) {
printf("asc%d: SCSI bus reset!!\n", asc
- asc_softc
);
/* need to flush any pending commands */
for (i
= 0; i
< ASC_NCMD
; i
++) {
/* rearbitrate synchronous offset */
for (i
= 0; i
< ASC_NCMD
; i
++) {
asc
->st
[i
].sync_offset
= 0;
/* check for command errors */
/* check for disconnect */
state
= &asc
->st
[asc
->target
];
if (state
->flags
& DISCONN
) {
state
->script
= asc
->script
;
asc
->state
= ASC_STATE_RESEL
;
asc
->script
= &asc_scripts
[SCRIPT_RESEL
];
regs
->asc_cmd
= ASC_CMD_ENABLE_SEL
;
case 0: /* device did not respond */
asc_end(asc
, status
, ss
, ir
);
if (ir
& ASC_INT_RESEL
) {
fifo
= regs
->asc_flags
& ASC_FLAGS_FIFO_CNT
;
/* read unencoded SCSI ID and convert to binary */
msg
= regs
->asc_fifo
& asc
->myidmask
;
for (id
= 0; (msg
& 1) == 0; id
++)
/* read identify message */
asc_log
[NLOG
- 1].msg
= msg
;
if (asc
->state
!= ASC_STATE_RESEL
)
asc
->state
= ASC_STATE_BUSY
;
asc
->script
= state
->script
;
state
->script
= (script_t
*)0;
if (!(state
->flags
& DISCONN
))
state
->flags
&= ~DISCONN
;
regs
->asc_cmd
= ASC_CMD_MSG_ACPT
;
/* check if we are being selected as a target */
if (ir
& (ASC_INT_SEL
| ASC_INT_SEL_ATN
))
/* must be just a ASC_INT_FC */
if (regs
->asc_status
& ASC_CSR_INT
)
* All the many little things that the interrupt
* routine might switch to.
script_nop(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
asc_get_status(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
* Get the last two bytes in the FIFO.
if ((data
= regs
->asc_flags
& ASC_FLAGS_FIFO_CNT
) != 2) {
printf("asc_get_status: fifo cnt %d\n", data
); /* XXX */
asc
->regs
->asc_cmd
= ASC_CMD_MSG_ACPT
;
} while ((regs
->asc_flags
& ASC_FLAGS_FIFO_CNT
) > 2);
/* save the status byte */
asc
->st
[asc
->target
].statusByte
= data
= regs
->asc_fifo
;
asc_log
[NLOG
- 1].msg
= data
;
/* get the (presumed) command_complete message */
if ((data
= regs
->asc_fifo
) == SCSI_COMMAND_COMPLETE
)
printf("asc_get_status: status %x cmd %x\n",
asc
->st
[asc
->target
].statusByte
, data
);
asc_DumpLog("asc_get_status");
asc_end(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register ScsiCmd
*scsicmd
;
asc
->state
= ASC_STATE_IDLE
;
scsicmd
= asc
->cmd
[target
];
asc
->cmd
[target
] = (ScsiCmd
*)0;
state
= &asc
->st
[target
];
printf("asc_end: %s target %d cmd %x err %d resid %d\n",
scsicmd
->sd
->sd_driver
->d_name
, target
,
scsicmd
->cmd
[0], state
->error
, state
->buflen
);
if (target
< 0 || !scsicmd
)
/* look for disconnected devices */
for (i
= 0; i
< ASC_NCMD
; i
++) {
if (!asc
->cmd
[i
] || !(asc
->st
[i
].flags
& DISCONN
))
asc
->regs
->asc_cmd
= ASC_CMD_ENABLE_SEL
;
asc
->state
= ASC_STATE_RESEL
;
asc
->script
= &asc_scripts
[SCRIPT_RESEL
];
/* look for another device that is ready */
for (i
= 0; i
< ASC_NCMD
; i
++) {
/* don't restart a disconnected command */
if (!asc
->cmd
[i
] || (asc
->st
[i
].flags
& DISCONN
))
/* signal device driver that the command is done */
(*scsicmd
->sd
->sd_driver
->d_done
)(scsicmd
->unit
, state
->error
,
state
->buflen
, state
->statusByte
);
asc_dma_in(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
register State
*state
= &asc
->st
[asc
->target
];
/* check for previous chunk in buffer */
if (!(state
->flags
& FIRST_DMA
)) {
* Only count bytes that have been copied to memory.
* There may be some bytes in the FIFO if synchonous transfers
len
= state
->dmalen
- len
;
bcopy(state
->dmaBufAddr
, state
->buf
, len
);
state
->flags
&= ~FIRST_DMA
;
/* setup to start reading next chunk */
if (len
> state
->dmaBufSize
)
*asc
->dmar
= ASC_DMA_ADDR(state
->dmaBufAddr
);
if (len
!= state
->buflen
) {
regs
->asc_cmd
= ASC_CMD_XFER_INFO
| ASC_CMD_DMA
;
asc
->script
= &asc_scripts
[SCRIPT_RESUME_IN
];
asc_last_dma_in(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
register State
*state
= &asc
->st
[asc
->target
];
/* copy data from buffer to main memory */
fifo
= regs
->asc_flags
& ASC_FLAGS_FIFO_CNT
;
if (asc_debug
> 2 || len
|| fifo
) /* XXX */
printf("asc_last_dma_in: buflen %d dmalen %d tc %d fifo %d\n",
state
->buflen
, state
->dmalen
, len
, fifo
);
regs
->asc_cmd
= ASC_CMD_FLUSH
;
len
= state
->dmalen
- len
;
bcopy(state
->dmaBufAddr
, state
->buf
, len
);
asc_resume_dma_in(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
register State
*state
= &asc
->st
[asc
->target
];
/* setup to finish reading the current chunk */
off
= state
->dmalen
- len
;
if ((off
& 1) && state
->sync_offset
) {
printf("asc_resume_dma_in: odd xfer dmalen %d len %d off %d\n",
state
->dmalen
, len
, off
); /* XXX */
regs
->asc_res_fifo
= state
->dmaBufAddr
[off
];
*asc
->dmar
= ASC_DMA_ADDR(state
->dmaBufAddr
+ off
);
if (state
->dmalen
!= state
->buflen
) {
regs
->asc_cmd
= ASC_CMD_XFER_INFO
| ASC_CMD_DMA
;
asc
->script
= &asc_scripts
[SCRIPT_RESUME_IN
];
asc_dma_out(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
register State
*state
= &asc
->st
[asc
->target
];
if (!(state
->flags
& FIRST_DMA
)) {
/* check to be sure previous chunk was finished */
fifo
= regs
->asc_flags
& ASC_FLAGS_FIFO_CNT
;
printf("asc_dma_out: buflen %d dmalen %d tc %d fifo %d\n",
state
->buflen
, state
->dmalen
, len
, fifo
); /* XXX */
len
= state
->dmalen
- len
;
/* setup for this chunck */
if (len
> state
->dmaBufSize
)
bcopy(state
->buf
, state
->dmaBufAddr
, len
);
*asc
->dmar
= ASC_DMAR_WRITE
| ASC_DMA_ADDR(state
->dmaBufAddr
);
state
->flags
&= ~FIRST_DMA
;
printf("asc_dma_out: dmalen %d fifo %d\n",
regs
->asc_flags
& ASC_FLAGS_FIFO_CNT
);
/* check for next chunk */
if (len
!= state
->buflen
) {
regs
->asc_cmd
= ASC_CMD_XFER_INFO
| ASC_CMD_DMA
;
asc
->script
= &asc_scripts
[SCRIPT_RESUME_OUT
];
asc_last_dma_out(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
register State
*state
= &asc
->st
[asc
->target
];
fifo
= regs
->asc_flags
& ASC_FLAGS_FIFO_CNT
;
printf("asc_last_dma_out: dmalen %d tc %d fifo %d\n",
state
->dmalen
, len
, fifo
);
printf("asc_last_dma_out: buflen %d dmalen %d tc %d fifo %d\n",
state
->buflen
, state
->dmalen
, len
, fifo
); /* XXX */
len
= state
->dmalen
- len
;
asc_resume_dma_out(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
register State
*state
= &asc
->st
[asc
->target
];
/* setup to finish writing this chunk */
off
= state
->dmalen
- len
;
printf("asc_resume_dma_out: odd xfer dmalen %d len %d off %d\n",
state
->dmalen
, len
, off
); /* XXX */
regs
->asc_fifo
= state
->dmaBufAddr
[off
];
*asc
->dmar
= ASC_DMAR_WRITE
| ASC_DMA_ADDR(state
->dmaBufAddr
+ off
);
if (state
->dmalen
!= state
->buflen
) {
regs
->asc_cmd
= ASC_CMD_XFER_INFO
| ASC_CMD_DMA
;
asc
->script
= &asc_scripts
[SCRIPT_RESUME_OUT
];
asc_sendsync(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
* Try sync negotiation, unless prohibited
regs
->asc_fifo
= SCSI_EXTENDED_MSG
;
regs
->asc_fifo
= SCSI_SYNCHRONOUS_XFER
;
regs
->asc_fifo
= SCSI_MIN_PERIOD
;
regs
->asc_fifo
= ASC_MAX_OFFSET
;
asc_replysync(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
register State
*state
= &asc
->st
[asc
->target
];
printf("asc_replysync: %x %x\n",
asc_to_scsi_period
[state
->sync_period
],
/* send synchronous transfer in response to a request */
regs
->asc_fifo
= SCSI_EXTENDED_MSG
;
regs
->asc_fifo
= SCSI_SYNCHRONOUS_XFER
;
regs
->asc_fifo
= asc_to_scsi_period
[state
->sync_period
];
regs
->asc_fifo
= state
->sync_offset
;
regs
->asc_cmd
= ASC_CMD_XFER_INFO
;
/* return to the appropriate script */
asc_DumpLog("asc_replsync");
asc
->script
= state
->script
;
state
->script
= (script_t
*)0;
asc_recvmsg(asc
, status
, ss
, ir
)
register asc_softc_t asc
;
register int status
, ss
, ir
;
register asc_regmap_t
*regs
= asc
->regs
;
register State
*state
= &asc
->st
[asc
->target
];
/* read one message byte */
asc_log
[NLOG
- 1].msg
= msg
;
/* check for multi-byte message */
if (state
->msglen
!= 0) {
/* first byte is the message length */
if (state
->msgcnt
>= state
->msglen
)
state
->msg_in
[state
->msgcnt
++] = msg
;
/* did we just read the last byte of the message? */
if (state
->msgcnt
!= state
->msglen
)
/* process an extended message */
printf("asc_recvmsg: msg %x %x %x\n",
switch (state
->msg_in
[0]) {
case SCSI_SYNCHRONOUS_XFER
:
state
->flags
|= DID_SYNC
;
state
->sync_offset
= state
->msg_in
[2];
/* convert SCSI period to ASC period */
i
= state
->msg_in
[1] / 10;
else if (i
>= ASC_MAX_PERIOD
) {
/* can't do sync transfer, period too long */
printf("asc%d: SCSI device %d: sync xfer period too long (%d)\n",
asc
- asc_softc
, asc
->target
, i
);
if ((i
* 10) != state
->msg_in
[1])
state
->sync_period
= i
& 0x1F;
* If this is a request, check minimums and
* send back an acknowledge.
if (!(state
->flags
& TRY_SYNC
)) {
regs
->asc_cmd
= ASC_CMD_SET_ATN
;
if (state
->sync_period
< ASC_MIN_PERIOD
)
if (state
->sync_offset
> ASC_MAX_OFFSET
)
asc
->script
= &asc_scripts
[SCRIPT_REPLY_SYNC
];
regs
->asc_syn_p
= state
->sync_period
;
regs
->asc_syn_o
= state
->sync_offset
;
regs
->asc_cmd
= ASC_CMD_MSG_ACPT
;
regs
->asc_syn_p
= state
->sync_period
;
regs
->asc_syn_o
= state
->sync_offset
;
printf("asc%d: SCSI device %d: rejecting extended message 0x%x\n",
asc
- asc_softc
, asc
->target
,
/* process first byte of a message */
printf("asc_recvmsg: msg %x\n", msg
);
case SCSI_MESSAGE_REJECT
:
printf(" did not like SYNCH xfer "); /* XXX */
state
->flags
|= DID_SYNC
;
regs
->asc_cmd
= ASC_CMD_MSG_ACPT
;
status
= asc_wait(regs
, ASC_CSR_INT
);
/* some just break out here, some dont */
if (ASC_PHASE(status
) == ASC_PHASE_MSG_OUT
) {
regs
->asc_fifo
= SCSI_ABORT
;
regs
->asc_cmd
= ASC_CMD_XFER_INFO
;
status
= asc_wait(regs
, ASC_CSR_INT
);
asc_end(asc
, status
, 0, ir
);
case SCSI_EXTENDED_MSG
: /* read an extended message */
/* setup to read message length next */
case SCSI_SAVE_DATA_POINTER
:
/* expect another message */
case SCSI_RESTORE_POINTERS
:
* Need to do the following if resuming synchonous data in
* on an odd byte boundary.
regs->asc_cnfg2 |= ASC_CNFG2_RFB;
if (state
->flags
& DISCONN
)
printf("asc%d: SCSI device %d: rejecting message 0x%x\n",
asc
- asc_softc
, asc
->target
, msg
);
/* request a message out before acknowledging this message */
state
->msg_out
= SCSI_MESSAGE_REJECT
;
regs
->asc_cmd
= ASC_CMD_SET_ATN
;
/* return to original script */
regs
->asc_cmd
= ASC_CMD_MSG_ACPT
;
asc_DumpLog("asc_recvmsg");
asc
->script
= state
->script
;
state
->script
= (script_t
*)0;
register struct asc_log
*lp
;
printf("asc: %s: cmd %x bn %d cnt %d\n", str
, asc_debug_cmd
,
asc_debug_bn
, asc_debug_sz
);
printf("asc%d tgt %d status %x ss %x ir %x cond %d:%x msg %x\n",
asc_scripts
[lp
->state
].condition
,
if (++lp
>= &asc_log
[NLOG
])