10725 lines
290 KiB
C
10725 lines
290 KiB
C
/*
|
|
* Core routines and tables shareable across OS platforms.
|
|
*
|
|
* Copyright (c) 1994-2002 Justin T. Gibbs.
|
|
* Copyright (c) 2000-2003 Adaptec Inc.
|
|
* 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,
|
|
* without modification.
|
|
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
|
|
* substantially similar to the "NO WARRANTY" disclaimer below
|
|
* ("Disclaimer") and any redistribution must be conditioned upon
|
|
* including a substantially similar Disclaimer requirement for further
|
|
* binary redistribution.
|
|
* 3. Neither the names of the above-listed copyright holders nor the names
|
|
* of any contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* Alternatively, this software may be distributed under the terms of the
|
|
* GNU General Public License ("GPL") version 2 as published by the Free
|
|
* Software Foundation.
|
|
*
|
|
* NO WARRANTY
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
|
|
*
|
|
* $Id: //depot/aic7xxx/aic7xxx/aic79xx.c#250 $
|
|
*/
|
|
|
|
#include "aic79xx_osm.h"
|
|
#include "aic79xx_inline.h"
|
|
#include "aicasm/aicasm_insformat.h"
|
|
|
|
/***************************** Lookup Tables **********************************/
|
|
static const char *const ahd_chip_names[] =
|
|
{
|
|
"NONE",
|
|
"aic7901",
|
|
"aic7902",
|
|
"aic7901A"
|
|
};
|
|
|
|
/*
|
|
* Hardware error codes.
|
|
*/
|
|
struct ahd_hard_error_entry {
|
|
uint8_t errno;
|
|
const char *errmesg;
|
|
};
|
|
|
|
static const struct ahd_hard_error_entry ahd_hard_errors[] = {
|
|
{ DSCTMOUT, "Discard Timer has timed out" },
|
|
{ ILLOPCODE, "Illegal Opcode in sequencer program" },
|
|
{ SQPARERR, "Sequencer Parity Error" },
|
|
{ DPARERR, "Data-path Parity Error" },
|
|
{ MPARERR, "Scratch or SCB Memory Parity Error" },
|
|
{ CIOPARERR, "CIOBUS Parity Error" },
|
|
};
|
|
static const u_int num_errors = ARRAY_SIZE(ahd_hard_errors);
|
|
|
|
static const struct ahd_phase_table_entry ahd_phase_table[] =
|
|
{
|
|
{ P_DATAOUT, NOP, "in Data-out phase" },
|
|
{ P_DATAIN, INITIATOR_ERROR, "in Data-in phase" },
|
|
{ P_DATAOUT_DT, NOP, "in DT Data-out phase" },
|
|
{ P_DATAIN_DT, INITIATOR_ERROR, "in DT Data-in phase" },
|
|
{ P_COMMAND, NOP, "in Command phase" },
|
|
{ P_MESGOUT, NOP, "in Message-out phase" },
|
|
{ P_STATUS, INITIATOR_ERROR, "in Status phase" },
|
|
{ P_MESGIN, MSG_PARITY_ERROR, "in Message-in phase" },
|
|
{ P_BUSFREE, NOP, "while idle" },
|
|
{ 0, NOP, "in unknown phase" }
|
|
};
|
|
|
|
/*
|
|
* In most cases we only wish to itterate over real phases, so
|
|
* exclude the last element from the count.
|
|
*/
|
|
static const u_int num_phases = ARRAY_SIZE(ahd_phase_table) - 1;
|
|
|
|
/* Our Sequencer Program */
|
|
#include "aic79xx_seq.h"
|
|
|
|
/**************************** Function Declarations ***************************/
|
|
static void ahd_handle_transmission_error(struct ahd_softc *ahd);
|
|
static void ahd_handle_lqiphase_error(struct ahd_softc *ahd,
|
|
u_int lqistat1);
|
|
static int ahd_handle_pkt_busfree(struct ahd_softc *ahd,
|
|
u_int busfreetime);
|
|
static int ahd_handle_nonpkt_busfree(struct ahd_softc *ahd);
|
|
static void ahd_handle_proto_violation(struct ahd_softc *ahd);
|
|
static void ahd_force_renegotiation(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo);
|
|
|
|
static struct ahd_tmode_tstate*
|
|
ahd_alloc_tstate(struct ahd_softc *ahd,
|
|
u_int scsi_id, char channel);
|
|
#ifdef AHD_TARGET_MODE
|
|
static void ahd_free_tstate(struct ahd_softc *ahd,
|
|
u_int scsi_id, char channel, int force);
|
|
#endif
|
|
static void ahd_devlimited_syncrate(struct ahd_softc *ahd,
|
|
struct ahd_initiator_tinfo *,
|
|
u_int *period,
|
|
u_int *ppr_options,
|
|
role_t role);
|
|
static void ahd_update_neg_table(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo,
|
|
struct ahd_transinfo *tinfo);
|
|
static void ahd_update_pending_scbs(struct ahd_softc *ahd);
|
|
static void ahd_fetch_devinfo(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo);
|
|
static void ahd_scb_devinfo(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo,
|
|
struct scb *scb);
|
|
static void ahd_setup_initiator_msgout(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo,
|
|
struct scb *scb);
|
|
static void ahd_build_transfer_msg(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo);
|
|
static void ahd_construct_sdtr(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo,
|
|
u_int period, u_int offset);
|
|
static void ahd_construct_wdtr(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo,
|
|
u_int bus_width);
|
|
static void ahd_construct_ppr(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo,
|
|
u_int period, u_int offset,
|
|
u_int bus_width, u_int ppr_options);
|
|
static void ahd_clear_msg_state(struct ahd_softc *ahd);
|
|
static void ahd_handle_message_phase(struct ahd_softc *ahd);
|
|
typedef enum {
|
|
AHDMSG_1B,
|
|
AHDMSG_2B,
|
|
AHDMSG_EXT
|
|
} ahd_msgtype;
|
|
static int ahd_sent_msg(struct ahd_softc *ahd, ahd_msgtype type,
|
|
u_int msgval, int full);
|
|
static int ahd_parse_msg(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo);
|
|
static int ahd_handle_msg_reject(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo);
|
|
static void ahd_handle_ign_wide_residue(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo);
|
|
static void ahd_reinitialize_dataptrs(struct ahd_softc *ahd);
|
|
static void ahd_handle_devreset(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo,
|
|
u_int lun, cam_status status,
|
|
char *message, int verbose_level);
|
|
#ifdef AHD_TARGET_MODE
|
|
static void ahd_setup_target_msgin(struct ahd_softc *ahd,
|
|
struct ahd_devinfo *devinfo,
|
|
struct scb *scb);
|
|
#endif
|
|
|
|
static u_int ahd_sglist_size(struct ahd_softc *ahd);
|
|
static u_int ahd_sglist_allocsize(struct ahd_softc *ahd);
|
|
static bus_dmamap_callback_t
|
|
ahd_dmamap_cb;
|
|
static void ahd_initialize_hscbs(struct ahd_softc *ahd);
|
|
static int ahd_init_scbdata(struct ahd_softc *ahd);
|
|
static void ahd_fini_scbdata(struct ahd_softc *ahd);
|
|
static void ahd_setup_iocell_workaround(struct ahd_softc *ahd);
|
|
static void ahd_iocell_first_selection(struct ahd_softc *ahd);
|
|
static void ahd_add_col_list(struct ahd_softc *ahd,
|
|
struct scb *scb, u_int col_idx);
|
|
static void ahd_rem_col_list(struct ahd_softc *ahd,
|
|
struct scb *scb);
|
|
static void ahd_chip_init(struct ahd_softc *ahd);
|
|
static void ahd_qinfifo_requeue(struct ahd_softc *ahd,
|
|
struct scb *prev_scb,
|
|
struct scb *scb);
|
|
static int ahd_qinfifo_count(struct ahd_softc *ahd);
|
|
static int ahd_search_scb_list(struct ahd_softc *ahd, int target,
|
|
char channel, int lun, u_int tag,
|
|
role_t role, uint32_t status,
|
|
ahd_search_action action,
|
|
u_int *list_head, u_int *list_tail,
|
|
u_int tid);
|
|
static void ahd_stitch_tid_list(struct ahd_softc *ahd,
|
|
u_int tid_prev, u_int tid_cur,
|
|
u_int tid_next);
|
|
static void ahd_add_scb_to_free_list(struct ahd_softc *ahd,
|
|
u_int scbid);
|
|
static u_int ahd_rem_wscb(struct ahd_softc *ahd, u_int scbid,
|
|
u_int prev, u_int next, u_int tid);
|
|
static void ahd_reset_current_bus(struct ahd_softc *ahd);
|
|
static void ahd_stat_timer(struct timer_list *t);
|
|
#ifdef AHD_DUMP_SEQ
|
|
static void ahd_dumpseq(struct ahd_softc *ahd);
|
|
#endif
|
|
static void ahd_loadseq(struct ahd_softc *ahd);
|
|
static int ahd_check_patch(struct ahd_softc *ahd,
|
|
const struct patch **start_patch,
|
|
u_int start_instr, u_int *skip_addr);
|
|
static u_int ahd_resolve_seqaddr(struct ahd_softc *ahd,
|
|
u_int address);
|
|
static void ahd_download_instr(struct ahd_softc *ahd,
|
|
u_int instrptr, uint8_t *dconsts);
|
|
static int ahd_probe_stack_size(struct ahd_softc *ahd);
|
|
static int ahd_scb_active_in_fifo(struct ahd_softc *ahd,
|
|
struct scb *scb);
|
|
static void ahd_run_data_fifo(struct ahd_softc *ahd,
|
|
struct scb *scb);
|
|
|
|
#ifdef AHD_TARGET_MODE
|
|
static void ahd_queue_lstate_event(struct ahd_softc *ahd,
|
|
struct ahd_tmode_lstate *lstate,
|
|
u_int initiator_id,
|
|
u_int event_type,
|
|
u_int event_arg);
|
|
static void ahd_update_scsiid(struct ahd_softc *ahd,
|
|
u_int targid_mask);
|
|
static int ahd_handle_target_cmd(struct ahd_softc *ahd,
|
|
struct target_cmd *cmd);
|
|
#endif
|
|
|
|
static int ahd_abort_scbs(struct ahd_softc *ahd, int target,
|
|
char channel, int lun, u_int tag,
|
|
role_t role, uint32_t status);
|
|
static void ahd_alloc_scbs(struct ahd_softc *ahd);
|
|
static void ahd_busy_tcl(struct ahd_softc *ahd, u_int tcl,
|
|
u_int scbid);
|
|
static void ahd_calc_residual(struct ahd_softc *ahd,
|
|
struct scb *scb);
|
|
static void ahd_clear_critical_section(struct ahd_softc *ahd);
|
|
static void ahd_clear_intstat(struct ahd_softc *ahd);
|
|
static void ahd_enable_coalescing(struct ahd_softc *ahd,
|
|
int enable);
|
|
static u_int ahd_find_busy_tcl(struct ahd_softc *ahd, u_int tcl);
|
|
static void ahd_freeze_devq(struct ahd_softc *ahd,
|
|
struct scb *scb);
|
|
static void ahd_handle_scb_status(struct ahd_softc *ahd,
|
|
struct scb *scb);
|
|
static const struct ahd_phase_table_entry* ahd_lookup_phase_entry(int phase);
|
|
static void ahd_shutdown(void *arg);
|
|
static void ahd_update_coalescing_values(struct ahd_softc *ahd,
|
|
u_int timer,
|
|
u_int maxcmds,
|
|
u_int mincmds);
|
|
static int ahd_verify_vpd_cksum(struct vpd_config *vpd);
|
|
static int ahd_wait_seeprom(struct ahd_softc *ahd);
|
|
static int ahd_match_scb(struct ahd_softc *ahd, struct scb *scb,
|
|
int target, char channel, int lun,
|
|
u_int tag, role_t role);
|
|
|
|
static void ahd_reset_cmds_pending(struct ahd_softc *ahd);
|
|
|
|
/*************************** Interrupt Services *******************************/
|
|
static void ahd_run_qoutfifo(struct ahd_softc *ahd);
|
|
#ifdef AHD_TARGET_MODE
|
|
static void ahd_run_tqinfifo(struct ahd_softc *ahd, int paused);
|
|
#endif
|
|
static void ahd_handle_hwerrint(struct ahd_softc *ahd);
|
|
static void ahd_handle_seqint(struct ahd_softc *ahd, u_int intstat);
|
|
static void ahd_handle_scsiint(struct ahd_softc *ahd,
|
|
u_int intstat);
|
|
|
|
/************************ Sequencer Execution Control *************************/
|
|
void
|
|
ahd_set_modes(struct ahd_softc *ahd, ahd_mode src, ahd_mode dst)
|
|
{
|
|
if (ahd->src_mode == src && ahd->dst_mode == dst)
|
|
return;
|
|
#ifdef AHD_DEBUG
|
|
if (ahd->src_mode == AHD_MODE_UNKNOWN
|
|
|| ahd->dst_mode == AHD_MODE_UNKNOWN)
|
|
panic("Setting mode prior to saving it.\n");
|
|
if ((ahd_debug & AHD_SHOW_MODEPTR) != 0)
|
|
printk("%s: Setting mode 0x%x\n", ahd_name(ahd),
|
|
ahd_build_mode_state(ahd, src, dst));
|
|
#endif
|
|
ahd_outb(ahd, MODE_PTR, ahd_build_mode_state(ahd, src, dst));
|
|
ahd->src_mode = src;
|
|
ahd->dst_mode = dst;
|
|
}
|
|
|
|
static void
|
|
ahd_update_modes(struct ahd_softc *ahd)
|
|
{
|
|
ahd_mode_state mode_ptr;
|
|
ahd_mode src;
|
|
ahd_mode dst;
|
|
|
|
mode_ptr = ahd_inb(ahd, MODE_PTR);
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MODEPTR) != 0)
|
|
printk("Reading mode 0x%x\n", mode_ptr);
|
|
#endif
|
|
ahd_extract_mode_state(ahd, mode_ptr, &src, &dst);
|
|
ahd_known_modes(ahd, src, dst);
|
|
}
|
|
|
|
static void
|
|
ahd_assert_modes(struct ahd_softc *ahd, ahd_mode srcmode,
|
|
ahd_mode dstmode, const char *file, int line)
|
|
{
|
|
#ifdef AHD_DEBUG
|
|
if ((srcmode & AHD_MK_MSK(ahd->src_mode)) == 0
|
|
|| (dstmode & AHD_MK_MSK(ahd->dst_mode)) == 0) {
|
|
panic("%s:%s:%d: Mode assertion failed.\n",
|
|
ahd_name(ahd), file, line);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#define AHD_ASSERT_MODES(ahd, source, dest) \
|
|
ahd_assert_modes(ahd, source, dest, __FILE__, __LINE__);
|
|
|
|
ahd_mode_state
|
|
ahd_save_modes(struct ahd_softc *ahd)
|
|
{
|
|
if (ahd->src_mode == AHD_MODE_UNKNOWN
|
|
|| ahd->dst_mode == AHD_MODE_UNKNOWN)
|
|
ahd_update_modes(ahd);
|
|
|
|
return (ahd_build_mode_state(ahd, ahd->src_mode, ahd->dst_mode));
|
|
}
|
|
|
|
void
|
|
ahd_restore_modes(struct ahd_softc *ahd, ahd_mode_state state)
|
|
{
|
|
ahd_mode src;
|
|
ahd_mode dst;
|
|
|
|
ahd_extract_mode_state(ahd, state, &src, &dst);
|
|
ahd_set_modes(ahd, src, dst);
|
|
}
|
|
|
|
/*
|
|
* Determine whether the sequencer has halted code execution.
|
|
* Returns non-zero status if the sequencer is stopped.
|
|
*/
|
|
int
|
|
ahd_is_paused(struct ahd_softc *ahd)
|
|
{
|
|
return ((ahd_inb(ahd, HCNTRL) & PAUSE) != 0);
|
|
}
|
|
|
|
/*
|
|
* Request that the sequencer stop and wait, indefinitely, for it
|
|
* to stop. The sequencer will only acknowledge that it is paused
|
|
* once it has reached an instruction boundary and PAUSEDIS is
|
|
* cleared in the SEQCTL register. The sequencer may use PAUSEDIS
|
|
* for critical sections.
|
|
*/
|
|
void
|
|
ahd_pause(struct ahd_softc *ahd)
|
|
{
|
|
ahd_outb(ahd, HCNTRL, ahd->pause);
|
|
|
|
/*
|
|
* Since the sequencer can disable pausing in a critical section, we
|
|
* must loop until it actually stops.
|
|
*/
|
|
while (ahd_is_paused(ahd) == 0)
|
|
;
|
|
}
|
|
|
|
/*
|
|
* Allow the sequencer to continue program execution.
|
|
* We check here to ensure that no additional interrupt
|
|
* sources that would cause the sequencer to halt have been
|
|
* asserted. If, for example, a SCSI bus reset is detected
|
|
* while we are fielding a different, pausing, interrupt type,
|
|
* we don't want to release the sequencer before going back
|
|
* into our interrupt handler and dealing with this new
|
|
* condition.
|
|
*/
|
|
void
|
|
ahd_unpause(struct ahd_softc *ahd)
|
|
{
|
|
/*
|
|
* Automatically restore our modes to those saved
|
|
* prior to the first change of the mode.
|
|
*/
|
|
if (ahd->saved_src_mode != AHD_MODE_UNKNOWN
|
|
&& ahd->saved_dst_mode != AHD_MODE_UNKNOWN) {
|
|
if ((ahd->flags & AHD_UPDATE_PEND_CMDS) != 0)
|
|
ahd_reset_cmds_pending(ahd);
|
|
ahd_set_modes(ahd, ahd->saved_src_mode, ahd->saved_dst_mode);
|
|
}
|
|
|
|
if ((ahd_inb(ahd, INTSTAT) & ~CMDCMPLT) == 0)
|
|
ahd_outb(ahd, HCNTRL, ahd->unpause);
|
|
|
|
ahd_known_modes(ahd, AHD_MODE_UNKNOWN, AHD_MODE_UNKNOWN);
|
|
}
|
|
|
|
/*********************** Scatter Gather List Handling *************************/
|
|
void *
|
|
ahd_sg_setup(struct ahd_softc *ahd, struct scb *scb,
|
|
void *sgptr, dma_addr_t addr, bus_size_t len, int last)
|
|
{
|
|
scb->sg_count++;
|
|
if (sizeof(dma_addr_t) > 4
|
|
&& (ahd->flags & AHD_64BIT_ADDRESSING) != 0) {
|
|
struct ahd_dma64_seg *sg;
|
|
|
|
sg = (struct ahd_dma64_seg *)sgptr;
|
|
sg->addr = ahd_htole64(addr);
|
|
sg->len = ahd_htole32(len | (last ? AHD_DMA_LAST_SEG : 0));
|
|
return (sg + 1);
|
|
} else {
|
|
struct ahd_dma_seg *sg;
|
|
|
|
sg = (struct ahd_dma_seg *)sgptr;
|
|
sg->addr = ahd_htole32(addr & 0xFFFFFFFF);
|
|
sg->len = ahd_htole32(len | ((addr >> 8) & 0x7F000000)
|
|
| (last ? AHD_DMA_LAST_SEG : 0));
|
|
return (sg + 1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ahd_setup_scb_common(struct ahd_softc *ahd, struct scb *scb)
|
|
{
|
|
/* XXX Handle target mode SCBs. */
|
|
scb->crc_retry_count = 0;
|
|
if ((scb->flags & SCB_PACKETIZED) != 0) {
|
|
/* XXX what about ACA?? It is type 4, but TAG_TYPE == 0x3. */
|
|
scb->hscb->task_attribute = scb->hscb->control & SCB_TAG_TYPE;
|
|
} else {
|
|
if (ahd_get_transfer_length(scb) & 0x01)
|
|
scb->hscb->task_attribute = SCB_XFERLEN_ODD;
|
|
else
|
|
scb->hscb->task_attribute = 0;
|
|
}
|
|
|
|
if (scb->hscb->cdb_len <= MAX_CDB_LEN_WITH_SENSE_ADDR
|
|
|| (scb->hscb->cdb_len & SCB_CDB_LEN_PTR) != 0)
|
|
scb->hscb->shared_data.idata.cdb_plus_saddr.sense_addr =
|
|
ahd_htole32(scb->sense_busaddr);
|
|
}
|
|
|
|
static void
|
|
ahd_setup_data_scb(struct ahd_softc *ahd, struct scb *scb)
|
|
{
|
|
/*
|
|
* Copy the first SG into the "current" data ponter area.
|
|
*/
|
|
if ((ahd->flags & AHD_64BIT_ADDRESSING) != 0) {
|
|
struct ahd_dma64_seg *sg;
|
|
|
|
sg = (struct ahd_dma64_seg *)scb->sg_list;
|
|
scb->hscb->dataptr = sg->addr;
|
|
scb->hscb->datacnt = sg->len;
|
|
} else {
|
|
struct ahd_dma_seg *sg;
|
|
uint32_t *dataptr_words;
|
|
|
|
sg = (struct ahd_dma_seg *)scb->sg_list;
|
|
dataptr_words = (uint32_t*)&scb->hscb->dataptr;
|
|
dataptr_words[0] = sg->addr;
|
|
dataptr_words[1] = 0;
|
|
if ((ahd->flags & AHD_39BIT_ADDRESSING) != 0) {
|
|
uint64_t high_addr;
|
|
|
|
high_addr = ahd_le32toh(sg->len) & 0x7F000000;
|
|
scb->hscb->dataptr |= ahd_htole64(high_addr << 8);
|
|
}
|
|
scb->hscb->datacnt = sg->len;
|
|
}
|
|
/*
|
|
* Note where to find the SG entries in bus space.
|
|
* We also set the full residual flag which the
|
|
* sequencer will clear as soon as a data transfer
|
|
* occurs.
|
|
*/
|
|
scb->hscb->sgptr = ahd_htole32(scb->sg_list_busaddr|SG_FULL_RESID);
|
|
}
|
|
|
|
static void
|
|
ahd_setup_noxfer_scb(struct ahd_softc *ahd, struct scb *scb)
|
|
{
|
|
scb->hscb->sgptr = ahd_htole32(SG_LIST_NULL);
|
|
scb->hscb->dataptr = 0;
|
|
scb->hscb->datacnt = 0;
|
|
}
|
|
|
|
/************************** Memory mapping routines ***************************/
|
|
static void *
|
|
ahd_sg_bus_to_virt(struct ahd_softc *ahd, struct scb *scb, uint32_t sg_busaddr)
|
|
{
|
|
dma_addr_t sg_offset;
|
|
|
|
/* sg_list_phys points to entry 1, not 0 */
|
|
sg_offset = sg_busaddr - (scb->sg_list_busaddr - ahd_sg_size(ahd));
|
|
return ((uint8_t *)scb->sg_list + sg_offset);
|
|
}
|
|
|
|
static uint32_t
|
|
ahd_sg_virt_to_bus(struct ahd_softc *ahd, struct scb *scb, void *sg)
|
|
{
|
|
dma_addr_t sg_offset;
|
|
|
|
/* sg_list_phys points to entry 1, not 0 */
|
|
sg_offset = ((uint8_t *)sg - (uint8_t *)scb->sg_list)
|
|
- ahd_sg_size(ahd);
|
|
|
|
return (scb->sg_list_busaddr + sg_offset);
|
|
}
|
|
|
|
static void
|
|
ahd_sync_scb(struct ahd_softc *ahd, struct scb *scb, int op)
|
|
{
|
|
ahd_dmamap_sync(ahd, ahd->scb_data.hscb_dmat,
|
|
scb->hscb_map->dmamap,
|
|
/*offset*/(uint8_t*)scb->hscb - scb->hscb_map->vaddr,
|
|
/*len*/sizeof(*scb->hscb), op);
|
|
}
|
|
|
|
void
|
|
ahd_sync_sglist(struct ahd_softc *ahd, struct scb *scb, int op)
|
|
{
|
|
if (scb->sg_count == 0)
|
|
return;
|
|
|
|
ahd_dmamap_sync(ahd, ahd->scb_data.sg_dmat,
|
|
scb->sg_map->dmamap,
|
|
/*offset*/scb->sg_list_busaddr - ahd_sg_size(ahd),
|
|
/*len*/ahd_sg_size(ahd) * scb->sg_count, op);
|
|
}
|
|
|
|
static void
|
|
ahd_sync_sense(struct ahd_softc *ahd, struct scb *scb, int op)
|
|
{
|
|
ahd_dmamap_sync(ahd, ahd->scb_data.sense_dmat,
|
|
scb->sense_map->dmamap,
|
|
/*offset*/scb->sense_busaddr,
|
|
/*len*/AHD_SENSE_BUFSIZE, op);
|
|
}
|
|
|
|
#ifdef AHD_TARGET_MODE
|
|
static uint32_t
|
|
ahd_targetcmd_offset(struct ahd_softc *ahd, u_int index)
|
|
{
|
|
return (((uint8_t *)&ahd->targetcmds[index])
|
|
- (uint8_t *)ahd->qoutfifo);
|
|
}
|
|
#endif
|
|
|
|
/*********************** Miscellaneous Support Functions ***********************/
|
|
/*
|
|
* Return pointers to the transfer negotiation information
|
|
* for the specified our_id/remote_id pair.
|
|
*/
|
|
struct ahd_initiator_tinfo *
|
|
ahd_fetch_transinfo(struct ahd_softc *ahd, char channel, u_int our_id,
|
|
u_int remote_id, struct ahd_tmode_tstate **tstate)
|
|
{
|
|
/*
|
|
* Transfer data structures are stored from the perspective
|
|
* of the target role. Since the parameters for a connection
|
|
* in the initiator role to a given target are the same as
|
|
* when the roles are reversed, we pretend we are the target.
|
|
*/
|
|
if (channel == 'B')
|
|
our_id += 8;
|
|
*tstate = ahd->enabled_targets[our_id];
|
|
return (&(*tstate)->transinfo[remote_id]);
|
|
}
|
|
|
|
uint16_t
|
|
ahd_inw(struct ahd_softc *ahd, u_int port)
|
|
{
|
|
/*
|
|
* Read high byte first as some registers increment
|
|
* or have other side effects when the low byte is
|
|
* read.
|
|
*/
|
|
uint16_t r = ahd_inb(ahd, port+1) << 8;
|
|
return r | ahd_inb(ahd, port);
|
|
}
|
|
|
|
void
|
|
ahd_outw(struct ahd_softc *ahd, u_int port, u_int value)
|
|
{
|
|
/*
|
|
* Write low byte first to accommodate registers
|
|
* such as PRGMCNT where the order maters.
|
|
*/
|
|
ahd_outb(ahd, port, value & 0xFF);
|
|
ahd_outb(ahd, port+1, (value >> 8) & 0xFF);
|
|
}
|
|
|
|
uint32_t
|
|
ahd_inl(struct ahd_softc *ahd, u_int port)
|
|
{
|
|
return ((ahd_inb(ahd, port))
|
|
| (ahd_inb(ahd, port+1) << 8)
|
|
| (ahd_inb(ahd, port+2) << 16)
|
|
| (ahd_inb(ahd, port+3) << 24));
|
|
}
|
|
|
|
void
|
|
ahd_outl(struct ahd_softc *ahd, u_int port, uint32_t value)
|
|
{
|
|
ahd_outb(ahd, port, (value) & 0xFF);
|
|
ahd_outb(ahd, port+1, ((value) >> 8) & 0xFF);
|
|
ahd_outb(ahd, port+2, ((value) >> 16) & 0xFF);
|
|
ahd_outb(ahd, port+3, ((value) >> 24) & 0xFF);
|
|
}
|
|
|
|
uint64_t
|
|
ahd_inq(struct ahd_softc *ahd, u_int port)
|
|
{
|
|
return ((ahd_inb(ahd, port))
|
|
| (ahd_inb(ahd, port+1) << 8)
|
|
| (ahd_inb(ahd, port+2) << 16)
|
|
| (ahd_inb(ahd, port+3) << 24)
|
|
| (((uint64_t)ahd_inb(ahd, port+4)) << 32)
|
|
| (((uint64_t)ahd_inb(ahd, port+5)) << 40)
|
|
| (((uint64_t)ahd_inb(ahd, port+6)) << 48)
|
|
| (((uint64_t)ahd_inb(ahd, port+7)) << 56));
|
|
}
|
|
|
|
void
|
|
ahd_outq(struct ahd_softc *ahd, u_int port, uint64_t value)
|
|
{
|
|
ahd_outb(ahd, port, value & 0xFF);
|
|
ahd_outb(ahd, port+1, (value >> 8) & 0xFF);
|
|
ahd_outb(ahd, port+2, (value >> 16) & 0xFF);
|
|
ahd_outb(ahd, port+3, (value >> 24) & 0xFF);
|
|
ahd_outb(ahd, port+4, (value >> 32) & 0xFF);
|
|
ahd_outb(ahd, port+5, (value >> 40) & 0xFF);
|
|
ahd_outb(ahd, port+6, (value >> 48) & 0xFF);
|
|
ahd_outb(ahd, port+7, (value >> 56) & 0xFF);
|
|
}
|
|
|
|
u_int
|
|
ahd_get_scbptr(struct ahd_softc *ahd)
|
|
{
|
|
AHD_ASSERT_MODES(ahd, ~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK),
|
|
~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK));
|
|
return (ahd_inb(ahd, SCBPTR) | (ahd_inb(ahd, SCBPTR + 1) << 8));
|
|
}
|
|
|
|
void
|
|
ahd_set_scbptr(struct ahd_softc *ahd, u_int scbptr)
|
|
{
|
|
AHD_ASSERT_MODES(ahd, ~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK),
|
|
~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK));
|
|
ahd_outb(ahd, SCBPTR, scbptr & 0xFF);
|
|
ahd_outb(ahd, SCBPTR+1, (scbptr >> 8) & 0xFF);
|
|
}
|
|
|
|
#if 0 /* unused */
|
|
static u_int
|
|
ahd_get_hnscb_qoff(struct ahd_softc *ahd)
|
|
{
|
|
return (ahd_inw_atomic(ahd, HNSCB_QOFF));
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
ahd_set_hnscb_qoff(struct ahd_softc *ahd, u_int value)
|
|
{
|
|
ahd_outw_atomic(ahd, HNSCB_QOFF, value);
|
|
}
|
|
|
|
#if 0 /* unused */
|
|
static u_int
|
|
ahd_get_hescb_qoff(struct ahd_softc *ahd)
|
|
{
|
|
return (ahd_inb(ahd, HESCB_QOFF));
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
ahd_set_hescb_qoff(struct ahd_softc *ahd, u_int value)
|
|
{
|
|
ahd_outb(ahd, HESCB_QOFF, value);
|
|
}
|
|
|
|
static u_int
|
|
ahd_get_snscb_qoff(struct ahd_softc *ahd)
|
|
{
|
|
u_int oldvalue;
|
|
|
|
AHD_ASSERT_MODES(ahd, AHD_MODE_CCHAN_MSK, AHD_MODE_CCHAN_MSK);
|
|
oldvalue = ahd_inw(ahd, SNSCB_QOFF);
|
|
ahd_outw(ahd, SNSCB_QOFF, oldvalue);
|
|
return (oldvalue);
|
|
}
|
|
|
|
static void
|
|
ahd_set_snscb_qoff(struct ahd_softc *ahd, u_int value)
|
|
{
|
|
AHD_ASSERT_MODES(ahd, AHD_MODE_CCHAN_MSK, AHD_MODE_CCHAN_MSK);
|
|
ahd_outw(ahd, SNSCB_QOFF, value);
|
|
}
|
|
|
|
#if 0 /* unused */
|
|
static u_int
|
|
ahd_get_sescb_qoff(struct ahd_softc *ahd)
|
|
{
|
|
AHD_ASSERT_MODES(ahd, AHD_MODE_CCHAN_MSK, AHD_MODE_CCHAN_MSK);
|
|
return (ahd_inb(ahd, SESCB_QOFF));
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
ahd_set_sescb_qoff(struct ahd_softc *ahd, u_int value)
|
|
{
|
|
AHD_ASSERT_MODES(ahd, AHD_MODE_CCHAN_MSK, AHD_MODE_CCHAN_MSK);
|
|
ahd_outb(ahd, SESCB_QOFF, value);
|
|
}
|
|
|
|
#if 0 /* unused */
|
|
static u_int
|
|
ahd_get_sdscb_qoff(struct ahd_softc *ahd)
|
|
{
|
|
AHD_ASSERT_MODES(ahd, AHD_MODE_CCHAN_MSK, AHD_MODE_CCHAN_MSK);
|
|
return (ahd_inb(ahd, SDSCB_QOFF) | (ahd_inb(ahd, SDSCB_QOFF + 1) << 8));
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
ahd_set_sdscb_qoff(struct ahd_softc *ahd, u_int value)
|
|
{
|
|
AHD_ASSERT_MODES(ahd, AHD_MODE_CCHAN_MSK, AHD_MODE_CCHAN_MSK);
|
|
ahd_outb(ahd, SDSCB_QOFF, value & 0xFF);
|
|
ahd_outb(ahd, SDSCB_QOFF+1, (value >> 8) & 0xFF);
|
|
}
|
|
|
|
u_int
|
|
ahd_inb_scbram(struct ahd_softc *ahd, u_int offset)
|
|
{
|
|
u_int value;
|
|
|
|
/*
|
|
* Workaround PCI-X Rev A. hardware bug.
|
|
* After a host read of SCB memory, the chip
|
|
* may become confused into thinking prefetch
|
|
* was required. This starts the discard timer
|
|
* running and can cause an unexpected discard
|
|
* timer interrupt. The work around is to read
|
|
* a normal register prior to the exhaustion of
|
|
* the discard timer. The mode pointer register
|
|
* has no side effects and so serves well for
|
|
* this purpose.
|
|
*
|
|
* Razor #528
|
|
*/
|
|
value = ahd_inb(ahd, offset);
|
|
if ((ahd->bugs & AHD_PCIX_SCBRAM_RD_BUG) != 0)
|
|
ahd_inb(ahd, MODE_PTR);
|
|
return (value);
|
|
}
|
|
|
|
u_int
|
|
ahd_inw_scbram(struct ahd_softc *ahd, u_int offset)
|
|
{
|
|
return (ahd_inb_scbram(ahd, offset)
|
|
| (ahd_inb_scbram(ahd, offset+1) << 8));
|
|
}
|
|
|
|
static uint32_t
|
|
ahd_inl_scbram(struct ahd_softc *ahd, u_int offset)
|
|
{
|
|
return (ahd_inw_scbram(ahd, offset)
|
|
| (ahd_inw_scbram(ahd, offset+2) << 16));
|
|
}
|
|
|
|
static uint64_t
|
|
ahd_inq_scbram(struct ahd_softc *ahd, u_int offset)
|
|
{
|
|
return (ahd_inl_scbram(ahd, offset)
|
|
| ((uint64_t)ahd_inl_scbram(ahd, offset+4)) << 32);
|
|
}
|
|
|
|
struct scb *
|
|
ahd_lookup_scb(struct ahd_softc *ahd, u_int tag)
|
|
{
|
|
struct scb* scb;
|
|
|
|
if (tag >= AHD_SCB_MAX)
|
|
return (NULL);
|
|
scb = ahd->scb_data.scbindex[tag];
|
|
if (scb != NULL)
|
|
ahd_sync_scb(ahd, scb,
|
|
BUS_DMASYNC_POSTREAD|BUS_DMASYNC_POSTWRITE);
|
|
return (scb);
|
|
}
|
|
|
|
static void
|
|
ahd_swap_with_next_hscb(struct ahd_softc *ahd, struct scb *scb)
|
|
{
|
|
struct hardware_scb *q_hscb;
|
|
struct map_node *q_hscb_map;
|
|
uint32_t saved_hscb_busaddr;
|
|
|
|
/*
|
|
* Our queuing method is a bit tricky. The card
|
|
* knows in advance which HSCB (by address) to download,
|
|
* and we can't disappoint it. To achieve this, the next
|
|
* HSCB to download is saved off in ahd->next_queued_hscb.
|
|
* When we are called to queue "an arbitrary scb",
|
|
* we copy the contents of the incoming HSCB to the one
|
|
* the sequencer knows about, swap HSCB pointers and
|
|
* finally assign the SCB to the tag indexed location
|
|
* in the scb_array. This makes sure that we can still
|
|
* locate the correct SCB by SCB_TAG.
|
|
*/
|
|
q_hscb = ahd->next_queued_hscb;
|
|
q_hscb_map = ahd->next_queued_hscb_map;
|
|
saved_hscb_busaddr = q_hscb->hscb_busaddr;
|
|
memcpy(q_hscb, scb->hscb, sizeof(*scb->hscb));
|
|
q_hscb->hscb_busaddr = saved_hscb_busaddr;
|
|
q_hscb->next_hscb_busaddr = scb->hscb->hscb_busaddr;
|
|
|
|
/* Now swap HSCB pointers. */
|
|
ahd->next_queued_hscb = scb->hscb;
|
|
ahd->next_queued_hscb_map = scb->hscb_map;
|
|
scb->hscb = q_hscb;
|
|
scb->hscb_map = q_hscb_map;
|
|
|
|
/* Now define the mapping from tag to SCB in the scbindex */
|
|
ahd->scb_data.scbindex[SCB_GET_TAG(scb)] = scb;
|
|
}
|
|
|
|
/*
|
|
* Tell the sequencer about a new transaction to execute.
|
|
*/
|
|
void
|
|
ahd_queue_scb(struct ahd_softc *ahd, struct scb *scb)
|
|
{
|
|
ahd_swap_with_next_hscb(ahd, scb);
|
|
|
|
if (SCBID_IS_NULL(SCB_GET_TAG(scb)))
|
|
panic("Attempt to queue invalid SCB tag %x\n",
|
|
SCB_GET_TAG(scb));
|
|
|
|
/*
|
|
* Keep a history of SCBs we've downloaded in the qinfifo.
|
|
*/
|
|
ahd->qinfifo[AHD_QIN_WRAP(ahd->qinfifonext)] = SCB_GET_TAG(scb);
|
|
ahd->qinfifonext++;
|
|
|
|
if (scb->sg_count != 0)
|
|
ahd_setup_data_scb(ahd, scb);
|
|
else
|
|
ahd_setup_noxfer_scb(ahd, scb);
|
|
ahd_setup_scb_common(ahd, scb);
|
|
|
|
/*
|
|
* Make sure our data is consistent from the
|
|
* perspective of the adapter.
|
|
*/
|
|
ahd_sync_scb(ahd, scb, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
|
|
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_QUEUE) != 0) {
|
|
uint64_t host_dataptr;
|
|
|
|
host_dataptr = ahd_le64toh(scb->hscb->dataptr);
|
|
printk("%s: Queueing SCB %d:0x%x bus addr 0x%x - 0x%x%x/0x%x\n",
|
|
ahd_name(ahd),
|
|
SCB_GET_TAG(scb), scb->hscb->scsiid,
|
|
ahd_le32toh(scb->hscb->hscb_busaddr),
|
|
(u_int)((host_dataptr >> 32) & 0xFFFFFFFF),
|
|
(u_int)(host_dataptr & 0xFFFFFFFF),
|
|
ahd_le32toh(scb->hscb->datacnt));
|
|
}
|
|
#endif
|
|
/* Tell the adapter about the newly queued SCB */
|
|
ahd_set_hnscb_qoff(ahd, ahd->qinfifonext);
|
|
}
|
|
|
|
/************************** Interrupt Processing ******************************/
|
|
static void
|
|
ahd_sync_qoutfifo(struct ahd_softc *ahd, int op)
|
|
{
|
|
ahd_dmamap_sync(ahd, ahd->shared_data_dmat, ahd->shared_data_map.dmamap,
|
|
/*offset*/0,
|
|
/*len*/AHD_SCB_MAX * sizeof(struct ahd_completion), op);
|
|
}
|
|
|
|
static void
|
|
ahd_sync_tqinfifo(struct ahd_softc *ahd, int op)
|
|
{
|
|
#ifdef AHD_TARGET_MODE
|
|
if ((ahd->flags & AHD_TARGETROLE) != 0) {
|
|
ahd_dmamap_sync(ahd, ahd->shared_data_dmat,
|
|
ahd->shared_data_map.dmamap,
|
|
ahd_targetcmd_offset(ahd, 0),
|
|
sizeof(struct target_cmd) * AHD_TMODE_CMDS,
|
|
op);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* See if the firmware has posted any completed commands
|
|
* into our in-core command complete fifos.
|
|
*/
|
|
#define AHD_RUN_QOUTFIFO 0x1
|
|
#define AHD_RUN_TQINFIFO 0x2
|
|
static u_int
|
|
ahd_check_cmdcmpltqueues(struct ahd_softc *ahd)
|
|
{
|
|
u_int retval;
|
|
|
|
retval = 0;
|
|
ahd_dmamap_sync(ahd, ahd->shared_data_dmat, ahd->shared_data_map.dmamap,
|
|
/*offset*/ahd->qoutfifonext * sizeof(*ahd->qoutfifo),
|
|
/*len*/sizeof(*ahd->qoutfifo), BUS_DMASYNC_POSTREAD);
|
|
if (ahd->qoutfifo[ahd->qoutfifonext].valid_tag
|
|
== ahd->qoutfifonext_valid_tag)
|
|
retval |= AHD_RUN_QOUTFIFO;
|
|
#ifdef AHD_TARGET_MODE
|
|
if ((ahd->flags & AHD_TARGETROLE) != 0
|
|
&& (ahd->flags & AHD_TQINFIFO_BLOCKED) == 0) {
|
|
ahd_dmamap_sync(ahd, ahd->shared_data_dmat,
|
|
ahd->shared_data_map.dmamap,
|
|
ahd_targetcmd_offset(ahd, ahd->tqinfifofnext),
|
|
/*len*/sizeof(struct target_cmd),
|
|
BUS_DMASYNC_POSTREAD);
|
|
if (ahd->targetcmds[ahd->tqinfifonext].cmd_valid != 0)
|
|
retval |= AHD_RUN_TQINFIFO;
|
|
}
|
|
#endif
|
|
return (retval);
|
|
}
|
|
|
|
/*
|
|
* Catch an interrupt from the adapter
|
|
*/
|
|
int
|
|
ahd_intr(struct ahd_softc *ahd)
|
|
{
|
|
u_int intstat;
|
|
|
|
if ((ahd->pause & INTEN) == 0) {
|
|
/*
|
|
* Our interrupt is not enabled on the chip
|
|
* and may be disabled for re-entrancy reasons,
|
|
* so just return. This is likely just a shared
|
|
* interrupt.
|
|
*/
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Instead of directly reading the interrupt status register,
|
|
* infer the cause of the interrupt by checking our in-core
|
|
* completion queues. This avoids a costly PCI bus read in
|
|
* most cases.
|
|
*/
|
|
if ((ahd->flags & AHD_ALL_INTERRUPTS) == 0
|
|
&& (ahd_check_cmdcmpltqueues(ahd) != 0))
|
|
intstat = CMDCMPLT;
|
|
else
|
|
intstat = ahd_inb(ahd, INTSTAT);
|
|
|
|
if ((intstat & INT_PEND) == 0)
|
|
return (0);
|
|
|
|
if (intstat & CMDCMPLT) {
|
|
ahd_outb(ahd, CLRINT, CLRCMDINT);
|
|
|
|
/*
|
|
* Ensure that the chip sees that we've cleared
|
|
* this interrupt before we walk the output fifo.
|
|
* Otherwise, we may, due to posted bus writes,
|
|
* clear the interrupt after we finish the scan,
|
|
* and after the sequencer has added new entries
|
|
* and asserted the interrupt again.
|
|
*/
|
|
if ((ahd->bugs & AHD_INTCOLLISION_BUG) != 0) {
|
|
if (ahd_is_paused(ahd)) {
|
|
/*
|
|
* Potentially lost SEQINT.
|
|
* If SEQINTCODE is non-zero,
|
|
* simulate the SEQINT.
|
|
*/
|
|
if (ahd_inb(ahd, SEQINTCODE) != NO_SEQINT)
|
|
intstat |= SEQINT;
|
|
}
|
|
} else {
|
|
ahd_flush_device_writes(ahd);
|
|
}
|
|
ahd_run_qoutfifo(ahd);
|
|
ahd->cmdcmplt_counts[ahd->cmdcmplt_bucket]++;
|
|
ahd->cmdcmplt_total++;
|
|
#ifdef AHD_TARGET_MODE
|
|
if ((ahd->flags & AHD_TARGETROLE) != 0)
|
|
ahd_run_tqinfifo(ahd, /*paused*/FALSE);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Handle statuses that may invalidate our cached
|
|
* copy of INTSTAT separately.
|
|
*/
|
|
if (intstat == 0xFF && (ahd->features & AHD_REMOVABLE) != 0) {
|
|
/* Hot eject. Do nothing */
|
|
} else if (intstat & HWERRINT) {
|
|
ahd_handle_hwerrint(ahd);
|
|
} else if ((intstat & (PCIINT|SPLTINT)) != 0) {
|
|
ahd->bus_intr(ahd);
|
|
} else {
|
|
|
|
if ((intstat & SEQINT) != 0)
|
|
ahd_handle_seqint(ahd, intstat);
|
|
|
|
if ((intstat & SCSIINT) != 0)
|
|
ahd_handle_scsiint(ahd, intstat);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/******************************** Private Inlines *****************************/
|
|
static inline void
|
|
ahd_assert_atn(struct ahd_softc *ahd)
|
|
{
|
|
ahd_outb(ahd, SCSISIGO, ATNO);
|
|
}
|
|
|
|
/*
|
|
* Determine if the current connection has a packetized
|
|
* agreement. This does not necessarily mean that we
|
|
* are currently in a packetized transfer. We could
|
|
* just as easily be sending or receiving a message.
|
|
*/
|
|
static int
|
|
ahd_currently_packetized(struct ahd_softc *ahd)
|
|
{
|
|
ahd_mode_state saved_modes;
|
|
int packetized;
|
|
|
|
saved_modes = ahd_save_modes(ahd);
|
|
if ((ahd->bugs & AHD_PKTIZED_STATUS_BUG) != 0) {
|
|
/*
|
|
* The packetized bit refers to the last
|
|
* connection, not the current one. Check
|
|
* for non-zero LQISTATE instead.
|
|
*/
|
|
ahd_set_modes(ahd, AHD_MODE_CFG, AHD_MODE_CFG);
|
|
packetized = ahd_inb(ahd, LQISTATE) != 0;
|
|
} else {
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
packetized = ahd_inb(ahd, LQISTAT2) & PACKETIZED;
|
|
}
|
|
ahd_restore_modes(ahd, saved_modes);
|
|
return (packetized);
|
|
}
|
|
|
|
static inline int
|
|
ahd_set_active_fifo(struct ahd_softc *ahd)
|
|
{
|
|
u_int active_fifo;
|
|
|
|
AHD_ASSERT_MODES(ahd, AHD_MODE_SCSI_MSK, AHD_MODE_SCSI_MSK);
|
|
active_fifo = ahd_inb(ahd, DFFSTAT) & CURRFIFO;
|
|
switch (active_fifo) {
|
|
case 0:
|
|
case 1:
|
|
ahd_set_modes(ahd, active_fifo, active_fifo);
|
|
return (1);
|
|
default:
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
ahd_unbusy_tcl(struct ahd_softc *ahd, u_int tcl)
|
|
{
|
|
ahd_busy_tcl(ahd, tcl, SCB_LIST_NULL);
|
|
}
|
|
|
|
/*
|
|
* Determine whether the sequencer reported a residual
|
|
* for this SCB/transaction.
|
|
*/
|
|
static inline void
|
|
ahd_update_residual(struct ahd_softc *ahd, struct scb *scb)
|
|
{
|
|
uint32_t sgptr;
|
|
|
|
sgptr = ahd_le32toh(scb->hscb->sgptr);
|
|
if ((sgptr & SG_STATUS_VALID) != 0)
|
|
ahd_calc_residual(ahd, scb);
|
|
}
|
|
|
|
static inline void
|
|
ahd_complete_scb(struct ahd_softc *ahd, struct scb *scb)
|
|
{
|
|
uint32_t sgptr;
|
|
|
|
sgptr = ahd_le32toh(scb->hscb->sgptr);
|
|
if ((sgptr & SG_STATUS_VALID) != 0)
|
|
ahd_handle_scb_status(ahd, scb);
|
|
else
|
|
ahd_done(ahd, scb);
|
|
}
|
|
|
|
|
|
/************************* Sequencer Execution Control ************************/
|
|
/*
|
|
* Restart the sequencer program from address zero
|
|
*/
|
|
static void
|
|
ahd_restart(struct ahd_softc *ahd)
|
|
{
|
|
|
|
ahd_pause(ahd);
|
|
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
|
|
/* No more pending messages */
|
|
ahd_clear_msg_state(ahd);
|
|
ahd_outb(ahd, SCSISIGO, 0); /* De-assert BSY */
|
|
ahd_outb(ahd, MSG_OUT, NOP); /* No message to send */
|
|
ahd_outb(ahd, SXFRCTL1, ahd_inb(ahd, SXFRCTL1) & ~BITBUCKET);
|
|
ahd_outb(ahd, SEQINTCTL, 0);
|
|
ahd_outb(ahd, LASTPHASE, P_BUSFREE);
|
|
ahd_outb(ahd, SEQ_FLAGS, 0);
|
|
ahd_outb(ahd, SAVED_SCSIID, 0xFF);
|
|
ahd_outb(ahd, SAVED_LUN, 0xFF);
|
|
|
|
/*
|
|
* Ensure that the sequencer's idea of TQINPOS
|
|
* matches our own. The sequencer increments TQINPOS
|
|
* only after it sees a DMA complete and a reset could
|
|
* occur before the increment leaving the kernel to believe
|
|
* the command arrived but the sequencer to not.
|
|
*/
|
|
ahd_outb(ahd, TQINPOS, ahd->tqinfifonext);
|
|
|
|
/* Always allow reselection */
|
|
ahd_outb(ahd, SCSISEQ1,
|
|
ahd_inb(ahd, SCSISEQ_TEMPLATE) & (ENSELI|ENRSELI|ENAUTOATNP));
|
|
ahd_set_modes(ahd, AHD_MODE_CCHAN, AHD_MODE_CCHAN);
|
|
|
|
/*
|
|
* Clear any pending sequencer interrupt. It is no
|
|
* longer relevant since we're resetting the Program
|
|
* Counter.
|
|
*/
|
|
ahd_outb(ahd, CLRINT, CLRSEQINT);
|
|
|
|
ahd_outb(ahd, SEQCTL0, FASTMODE|SEQRESET);
|
|
ahd_unpause(ahd);
|
|
}
|
|
|
|
static void
|
|
ahd_clear_fifo(struct ahd_softc *ahd, u_int fifo)
|
|
{
|
|
ahd_mode_state saved_modes;
|
|
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_FIFOS) != 0)
|
|
printk("%s: Clearing FIFO %d\n", ahd_name(ahd), fifo);
|
|
#endif
|
|
saved_modes = ahd_save_modes(ahd);
|
|
ahd_set_modes(ahd, fifo, fifo);
|
|
ahd_outb(ahd, DFFSXFRCTL, RSTCHN|CLRSHCNT);
|
|
if ((ahd_inb(ahd, SG_STATE) & FETCH_INPROG) != 0)
|
|
ahd_outb(ahd, CCSGCTL, CCSGRESET);
|
|
ahd_outb(ahd, LONGJMP_ADDR + 1, INVALID_ADDR);
|
|
ahd_outb(ahd, SG_STATE, 0);
|
|
ahd_restore_modes(ahd, saved_modes);
|
|
}
|
|
|
|
/************************* Input/Output Queues ********************************/
|
|
/*
|
|
* Flush and completed commands that are sitting in the command
|
|
* complete queues down on the chip but have yet to be dma'ed back up.
|
|
*/
|
|
static void
|
|
ahd_flush_qoutfifo(struct ahd_softc *ahd)
|
|
{
|
|
struct scb *scb;
|
|
ahd_mode_state saved_modes;
|
|
u_int saved_scbptr;
|
|
u_int ccscbctl;
|
|
u_int scbid;
|
|
u_int next_scbid;
|
|
|
|
saved_modes = ahd_save_modes(ahd);
|
|
|
|
/*
|
|
* Flush the good status FIFO for completed packetized commands.
|
|
*/
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
saved_scbptr = ahd_get_scbptr(ahd);
|
|
while ((ahd_inb(ahd, LQISTAT2) & LQIGSAVAIL) != 0) {
|
|
u_int fifo_mode;
|
|
u_int i;
|
|
|
|
scbid = ahd_inw(ahd, GSFIFO);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL) {
|
|
printk("%s: Warning - GSFIFO SCB %d invalid\n",
|
|
ahd_name(ahd), scbid);
|
|
continue;
|
|
}
|
|
/*
|
|
* Determine if this transaction is still active in
|
|
* any FIFO. If it is, we must flush that FIFO to
|
|
* the host before completing the command.
|
|
*/
|
|
fifo_mode = 0;
|
|
rescan_fifos:
|
|
for (i = 0; i < 2; i++) {
|
|
/* Toggle to the other mode. */
|
|
fifo_mode ^= 1;
|
|
ahd_set_modes(ahd, fifo_mode, fifo_mode);
|
|
|
|
if (ahd_scb_active_in_fifo(ahd, scb) == 0)
|
|
continue;
|
|
|
|
ahd_run_data_fifo(ahd, scb);
|
|
|
|
/*
|
|
* Running this FIFO may cause a CFG4DATA for
|
|
* this same transaction to assert in the other
|
|
* FIFO or a new snapshot SAVEPTRS interrupt
|
|
* in this FIFO. Even running a FIFO may not
|
|
* clear the transaction if we are still waiting
|
|
* for data to drain to the host. We must loop
|
|
* until the transaction is not active in either
|
|
* FIFO just to be sure. Reset our loop counter
|
|
* so we will visit both FIFOs again before
|
|
* declaring this transaction finished. We
|
|
* also delay a bit so that status has a chance
|
|
* to change before we look at this FIFO again.
|
|
*/
|
|
ahd_delay(200);
|
|
goto rescan_fifos;
|
|
}
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
ahd_set_scbptr(ahd, scbid);
|
|
if ((ahd_inb_scbram(ahd, SCB_SGPTR) & SG_LIST_NULL) == 0
|
|
&& ((ahd_inb_scbram(ahd, SCB_SGPTR) & SG_FULL_RESID) != 0
|
|
|| (ahd_inb_scbram(ahd, SCB_RESIDUAL_SGPTR)
|
|
& SG_LIST_NULL) != 0)) {
|
|
u_int comp_head;
|
|
|
|
/*
|
|
* The transfer completed with a residual.
|
|
* Place this SCB on the complete DMA list
|
|
* so that we update our in-core copy of the
|
|
* SCB before completing the command.
|
|
*/
|
|
ahd_outb(ahd, SCB_SCSI_STATUS, 0);
|
|
ahd_outb(ahd, SCB_SGPTR,
|
|
ahd_inb_scbram(ahd, SCB_SGPTR)
|
|
| SG_STATUS_VALID);
|
|
ahd_outw(ahd, SCB_TAG, scbid);
|
|
ahd_outw(ahd, SCB_NEXT_COMPLETE, SCB_LIST_NULL);
|
|
comp_head = ahd_inw(ahd, COMPLETE_DMA_SCB_HEAD);
|
|
if (SCBID_IS_NULL(comp_head)) {
|
|
ahd_outw(ahd, COMPLETE_DMA_SCB_HEAD, scbid);
|
|
ahd_outw(ahd, COMPLETE_DMA_SCB_TAIL, scbid);
|
|
} else {
|
|
u_int tail;
|
|
|
|
tail = ahd_inw(ahd, COMPLETE_DMA_SCB_TAIL);
|
|
ahd_set_scbptr(ahd, tail);
|
|
ahd_outw(ahd, SCB_NEXT_COMPLETE, scbid);
|
|
ahd_outw(ahd, COMPLETE_DMA_SCB_TAIL, scbid);
|
|
ahd_set_scbptr(ahd, scbid);
|
|
}
|
|
} else
|
|
ahd_complete_scb(ahd, scb);
|
|
}
|
|
ahd_set_scbptr(ahd, saved_scbptr);
|
|
|
|
/*
|
|
* Setup for command channel portion of flush.
|
|
*/
|
|
ahd_set_modes(ahd, AHD_MODE_CCHAN, AHD_MODE_CCHAN);
|
|
|
|
/*
|
|
* Wait for any inprogress DMA to complete and clear DMA state
|
|
* if this is for an SCB in the qinfifo.
|
|
*/
|
|
while (((ccscbctl = ahd_inb(ahd, CCSCBCTL)) & (CCARREN|CCSCBEN)) != 0) {
|
|
|
|
if ((ccscbctl & (CCSCBDIR|CCARREN)) == (CCSCBDIR|CCARREN)) {
|
|
if ((ccscbctl & ARRDONE) != 0)
|
|
break;
|
|
} else if ((ccscbctl & CCSCBDONE) != 0)
|
|
break;
|
|
ahd_delay(200);
|
|
}
|
|
/*
|
|
* We leave the sequencer to cleanup in the case of DMA's to
|
|
* update the qoutfifo. In all other cases (DMA's to the
|
|
* chip or a push of an SCB from the COMPLETE_DMA_SCB list),
|
|
* we disable the DMA engine so that the sequencer will not
|
|
* attempt to handle the DMA completion.
|
|
*/
|
|
if ((ccscbctl & CCSCBDIR) != 0 || (ccscbctl & ARRDONE) != 0)
|
|
ahd_outb(ahd, CCSCBCTL, ccscbctl & ~(CCARREN|CCSCBEN));
|
|
|
|
/*
|
|
* Complete any SCBs that just finished
|
|
* being DMA'ed into the qoutfifo.
|
|
*/
|
|
ahd_run_qoutfifo(ahd);
|
|
|
|
saved_scbptr = ahd_get_scbptr(ahd);
|
|
/*
|
|
* Manually update/complete any completed SCBs that are waiting to be
|
|
* DMA'ed back up to the host.
|
|
*/
|
|
scbid = ahd_inw(ahd, COMPLETE_DMA_SCB_HEAD);
|
|
while (!SCBID_IS_NULL(scbid)) {
|
|
uint8_t *hscb_ptr;
|
|
u_int i;
|
|
|
|
ahd_set_scbptr(ahd, scbid);
|
|
next_scbid = ahd_inw_scbram(ahd, SCB_NEXT_COMPLETE);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL) {
|
|
printk("%s: Warning - DMA-up and complete "
|
|
"SCB %d invalid\n", ahd_name(ahd), scbid);
|
|
continue;
|
|
}
|
|
hscb_ptr = (uint8_t *)scb->hscb;
|
|
for (i = 0; i < sizeof(struct hardware_scb); i++)
|
|
*hscb_ptr++ = ahd_inb_scbram(ahd, SCB_BASE + i);
|
|
|
|
ahd_complete_scb(ahd, scb);
|
|
scbid = next_scbid;
|
|
}
|
|
ahd_outw(ahd, COMPLETE_DMA_SCB_HEAD, SCB_LIST_NULL);
|
|
ahd_outw(ahd, COMPLETE_DMA_SCB_TAIL, SCB_LIST_NULL);
|
|
|
|
scbid = ahd_inw(ahd, COMPLETE_ON_QFREEZE_HEAD);
|
|
while (!SCBID_IS_NULL(scbid)) {
|
|
|
|
ahd_set_scbptr(ahd, scbid);
|
|
next_scbid = ahd_inw_scbram(ahd, SCB_NEXT_COMPLETE);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL) {
|
|
printk("%s: Warning - Complete Qfrz SCB %d invalid\n",
|
|
ahd_name(ahd), scbid);
|
|
continue;
|
|
}
|
|
|
|
ahd_complete_scb(ahd, scb);
|
|
scbid = next_scbid;
|
|
}
|
|
ahd_outw(ahd, COMPLETE_ON_QFREEZE_HEAD, SCB_LIST_NULL);
|
|
|
|
scbid = ahd_inw(ahd, COMPLETE_SCB_HEAD);
|
|
while (!SCBID_IS_NULL(scbid)) {
|
|
|
|
ahd_set_scbptr(ahd, scbid);
|
|
next_scbid = ahd_inw_scbram(ahd, SCB_NEXT_COMPLETE);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL) {
|
|
printk("%s: Warning - Complete SCB %d invalid\n",
|
|
ahd_name(ahd), scbid);
|
|
continue;
|
|
}
|
|
|
|
ahd_complete_scb(ahd, scb);
|
|
scbid = next_scbid;
|
|
}
|
|
ahd_outw(ahd, COMPLETE_SCB_HEAD, SCB_LIST_NULL);
|
|
|
|
/*
|
|
* Restore state.
|
|
*/
|
|
ahd_set_scbptr(ahd, saved_scbptr);
|
|
ahd_restore_modes(ahd, saved_modes);
|
|
ahd->flags |= AHD_UPDATE_PEND_CMDS;
|
|
}
|
|
|
|
/*
|
|
* Determine if an SCB for a packetized transaction
|
|
* is active in a FIFO.
|
|
*/
|
|
static int
|
|
ahd_scb_active_in_fifo(struct ahd_softc *ahd, struct scb *scb)
|
|
{
|
|
|
|
/*
|
|
* The FIFO is only active for our transaction if
|
|
* the SCBPTR matches the SCB's ID and the firmware
|
|
* has installed a handler for the FIFO or we have
|
|
* a pending SAVEPTRS or CFG4DATA interrupt.
|
|
*/
|
|
if (ahd_get_scbptr(ahd) != SCB_GET_TAG(scb)
|
|
|| ((ahd_inb(ahd, LONGJMP_ADDR+1) & INVALID_ADDR) != 0
|
|
&& (ahd_inb(ahd, SEQINTSRC) & (CFG4DATA|SAVEPTRS)) == 0))
|
|
return (0);
|
|
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Run a data fifo to completion for a transaction we know
|
|
* has completed across the SCSI bus (good status has been
|
|
* received). We are already set to the correct FIFO mode
|
|
* on entry to this routine.
|
|
*
|
|
* This function attempts to operate exactly as the firmware
|
|
* would when running this FIFO. Care must be taken to update
|
|
* this routine any time the firmware's FIFO algorithm is
|
|
* changed.
|
|
*/
|
|
static void
|
|
ahd_run_data_fifo(struct ahd_softc *ahd, struct scb *scb)
|
|
{
|
|
u_int seqintsrc;
|
|
|
|
seqintsrc = ahd_inb(ahd, SEQINTSRC);
|
|
if ((seqintsrc & CFG4DATA) != 0) {
|
|
uint32_t datacnt;
|
|
uint32_t sgptr;
|
|
|
|
/*
|
|
* Clear full residual flag.
|
|
*/
|
|
sgptr = ahd_inl_scbram(ahd, SCB_SGPTR) & ~SG_FULL_RESID;
|
|
ahd_outb(ahd, SCB_SGPTR, sgptr);
|
|
|
|
/*
|
|
* Load datacnt and address.
|
|
*/
|
|
datacnt = ahd_inl_scbram(ahd, SCB_DATACNT);
|
|
if ((datacnt & AHD_DMA_LAST_SEG) != 0) {
|
|
sgptr |= LAST_SEG;
|
|
ahd_outb(ahd, SG_STATE, 0);
|
|
} else
|
|
ahd_outb(ahd, SG_STATE, LOADING_NEEDED);
|
|
ahd_outq(ahd, HADDR, ahd_inq_scbram(ahd, SCB_DATAPTR));
|
|
ahd_outl(ahd, HCNT, datacnt & AHD_SG_LEN_MASK);
|
|
ahd_outb(ahd, SG_CACHE_PRE, sgptr);
|
|
ahd_outb(ahd, DFCNTRL, PRELOADEN|SCSIEN|HDMAEN);
|
|
|
|
/*
|
|
* Initialize Residual Fields.
|
|
*/
|
|
ahd_outb(ahd, SCB_RESIDUAL_DATACNT+3, datacnt >> 24);
|
|
ahd_outl(ahd, SCB_RESIDUAL_SGPTR, sgptr & SG_PTR_MASK);
|
|
|
|
/*
|
|
* Mark the SCB as having a FIFO in use.
|
|
*/
|
|
ahd_outb(ahd, SCB_FIFO_USE_COUNT,
|
|
ahd_inb_scbram(ahd, SCB_FIFO_USE_COUNT) + 1);
|
|
|
|
/*
|
|
* Install a "fake" handler for this FIFO.
|
|
*/
|
|
ahd_outw(ahd, LONGJMP_ADDR, 0);
|
|
|
|
/*
|
|
* Notify the hardware that we have satisfied
|
|
* this sequencer interrupt.
|
|
*/
|
|
ahd_outb(ahd, CLRSEQINTSRC, CLRCFG4DATA);
|
|
} else if ((seqintsrc & SAVEPTRS) != 0) {
|
|
uint32_t sgptr;
|
|
uint32_t resid;
|
|
|
|
if ((ahd_inb(ahd, LONGJMP_ADDR+1)&INVALID_ADDR) != 0) {
|
|
/*
|
|
* Snapshot Save Pointers. All that
|
|
* is necessary to clear the snapshot
|
|
* is a CLRCHN.
|
|
*/
|
|
goto clrchn;
|
|
}
|
|
|
|
/*
|
|
* Disable S/G fetch so the DMA engine
|
|
* is available to future users.
|
|
*/
|
|
if ((ahd_inb(ahd, SG_STATE) & FETCH_INPROG) != 0)
|
|
ahd_outb(ahd, CCSGCTL, 0);
|
|
ahd_outb(ahd, SG_STATE, 0);
|
|
|
|
/*
|
|
* Flush the data FIFO. Strickly only
|
|
* necessary for Rev A parts.
|
|
*/
|
|
ahd_outb(ahd, DFCNTRL, ahd_inb(ahd, DFCNTRL) | FIFOFLUSH);
|
|
|
|
/*
|
|
* Calculate residual.
|
|
*/
|
|
sgptr = ahd_inl_scbram(ahd, SCB_RESIDUAL_SGPTR);
|
|
resid = ahd_inl(ahd, SHCNT);
|
|
resid |= ahd_inb_scbram(ahd, SCB_RESIDUAL_DATACNT+3) << 24;
|
|
ahd_outl(ahd, SCB_RESIDUAL_DATACNT, resid);
|
|
if ((ahd_inb(ahd, SG_CACHE_SHADOW) & LAST_SEG) == 0) {
|
|
/*
|
|
* Must back up to the correct S/G element.
|
|
* Typically this just means resetting our
|
|
* low byte to the offset in the SG_CACHE,
|
|
* but if we wrapped, we have to correct
|
|
* the other bytes of the sgptr too.
|
|
*/
|
|
if ((ahd_inb(ahd, SG_CACHE_SHADOW) & 0x80) != 0
|
|
&& (sgptr & 0x80) == 0)
|
|
sgptr -= 0x100;
|
|
sgptr &= ~0xFF;
|
|
sgptr |= ahd_inb(ahd, SG_CACHE_SHADOW)
|
|
& SG_ADDR_MASK;
|
|
ahd_outl(ahd, SCB_RESIDUAL_SGPTR, sgptr);
|
|
ahd_outb(ahd, SCB_RESIDUAL_DATACNT + 3, 0);
|
|
} else if ((resid & AHD_SG_LEN_MASK) == 0) {
|
|
ahd_outb(ahd, SCB_RESIDUAL_SGPTR,
|
|
sgptr | SG_LIST_NULL);
|
|
}
|
|
/*
|
|
* Save Pointers.
|
|
*/
|
|
ahd_outq(ahd, SCB_DATAPTR, ahd_inq(ahd, SHADDR));
|
|
ahd_outl(ahd, SCB_DATACNT, resid);
|
|
ahd_outl(ahd, SCB_SGPTR, sgptr);
|
|
ahd_outb(ahd, CLRSEQINTSRC, CLRSAVEPTRS);
|
|
ahd_outb(ahd, SEQIMODE,
|
|
ahd_inb(ahd, SEQIMODE) | ENSAVEPTRS);
|
|
/*
|
|
* If the data is to the SCSI bus, we are
|
|
* done, otherwise wait for FIFOEMP.
|
|
*/
|
|
if ((ahd_inb(ahd, DFCNTRL) & DIRECTION) != 0)
|
|
goto clrchn;
|
|
} else if ((ahd_inb(ahd, SG_STATE) & LOADING_NEEDED) != 0) {
|
|
uint32_t sgptr;
|
|
uint64_t data_addr;
|
|
uint32_t data_len;
|
|
u_int dfcntrl;
|
|
|
|
/*
|
|
* Disable S/G fetch so the DMA engine
|
|
* is available to future users. We won't
|
|
* be using the DMA engine to load segments.
|
|
*/
|
|
if ((ahd_inb(ahd, SG_STATE) & FETCH_INPROG) != 0) {
|
|
ahd_outb(ahd, CCSGCTL, 0);
|
|
ahd_outb(ahd, SG_STATE, LOADING_NEEDED);
|
|
}
|
|
|
|
/*
|
|
* Wait for the DMA engine to notice that the
|
|
* host transfer is enabled and that there is
|
|
* space in the S/G FIFO for new segments before
|
|
* loading more segments.
|
|
*/
|
|
if ((ahd_inb(ahd, DFSTATUS) & PRELOAD_AVAIL) != 0
|
|
&& (ahd_inb(ahd, DFCNTRL) & HDMAENACK) != 0) {
|
|
|
|
/*
|
|
* Determine the offset of the next S/G
|
|
* element to load.
|
|
*/
|
|
sgptr = ahd_inl_scbram(ahd, SCB_RESIDUAL_SGPTR);
|
|
sgptr &= SG_PTR_MASK;
|
|
if ((ahd->flags & AHD_64BIT_ADDRESSING) != 0) {
|
|
struct ahd_dma64_seg *sg;
|
|
|
|
sg = ahd_sg_bus_to_virt(ahd, scb, sgptr);
|
|
data_addr = sg->addr;
|
|
data_len = sg->len;
|
|
sgptr += sizeof(*sg);
|
|
} else {
|
|
struct ahd_dma_seg *sg;
|
|
|
|
sg = ahd_sg_bus_to_virt(ahd, scb, sgptr);
|
|
data_addr = sg->len & AHD_SG_HIGH_ADDR_MASK;
|
|
data_addr <<= 8;
|
|
data_addr |= sg->addr;
|
|
data_len = sg->len;
|
|
sgptr += sizeof(*sg);
|
|
}
|
|
|
|
/*
|
|
* Update residual information.
|
|
*/
|
|
ahd_outb(ahd, SCB_RESIDUAL_DATACNT+3, data_len >> 24);
|
|
ahd_outl(ahd, SCB_RESIDUAL_SGPTR, sgptr);
|
|
|
|
/*
|
|
* Load the S/G.
|
|
*/
|
|
if (data_len & AHD_DMA_LAST_SEG) {
|
|
sgptr |= LAST_SEG;
|
|
ahd_outb(ahd, SG_STATE, 0);
|
|
}
|
|
ahd_outq(ahd, HADDR, data_addr);
|
|
ahd_outl(ahd, HCNT, data_len & AHD_SG_LEN_MASK);
|
|
ahd_outb(ahd, SG_CACHE_PRE, sgptr & 0xFF);
|
|
|
|
/*
|
|
* Advertise the segment to the hardware.
|
|
*/
|
|
dfcntrl = ahd_inb(ahd, DFCNTRL)|PRELOADEN|HDMAEN;
|
|
if ((ahd->features & AHD_NEW_DFCNTRL_OPTS) != 0) {
|
|
/*
|
|
* Use SCSIENWRDIS so that SCSIEN
|
|
* is never modified by this
|
|
* operation.
|
|
*/
|
|
dfcntrl |= SCSIENWRDIS;
|
|
}
|
|
ahd_outb(ahd, DFCNTRL, dfcntrl);
|
|
}
|
|
} else if ((ahd_inb(ahd, SG_CACHE_SHADOW) & LAST_SEG_DONE) != 0) {
|
|
|
|
/*
|
|
* Transfer completed to the end of SG list
|
|
* and has flushed to the host.
|
|
*/
|
|
ahd_outb(ahd, SCB_SGPTR,
|
|
ahd_inb_scbram(ahd, SCB_SGPTR) | SG_LIST_NULL);
|
|
goto clrchn;
|
|
} else if ((ahd_inb(ahd, DFSTATUS) & FIFOEMP) != 0) {
|
|
clrchn:
|
|
/*
|
|
* Clear any handler for this FIFO, decrement
|
|
* the FIFO use count for the SCB, and release
|
|
* the FIFO.
|
|
*/
|
|
ahd_outb(ahd, LONGJMP_ADDR + 1, INVALID_ADDR);
|
|
ahd_outb(ahd, SCB_FIFO_USE_COUNT,
|
|
ahd_inb_scbram(ahd, SCB_FIFO_USE_COUNT) - 1);
|
|
ahd_outb(ahd, DFFSXFRCTL, CLRCHN);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look for entries in the QoutFIFO that have completed.
|
|
* The valid_tag completion field indicates the validity
|
|
* of the entry - the valid value toggles each time through
|
|
* the queue. We use the sg_status field in the completion
|
|
* entry to avoid referencing the hscb if the completion
|
|
* occurred with no errors and no residual. sg_status is
|
|
* a copy of the first byte (little endian) of the sgptr
|
|
* hscb field.
|
|
*/
|
|
static void
|
|
ahd_run_qoutfifo(struct ahd_softc *ahd)
|
|
{
|
|
struct ahd_completion *completion;
|
|
struct scb *scb;
|
|
u_int scb_index;
|
|
|
|
if ((ahd->flags & AHD_RUNNING_QOUTFIFO) != 0)
|
|
panic("ahd_run_qoutfifo recursion");
|
|
ahd->flags |= AHD_RUNNING_QOUTFIFO;
|
|
ahd_sync_qoutfifo(ahd, BUS_DMASYNC_POSTREAD);
|
|
for (;;) {
|
|
completion = &ahd->qoutfifo[ahd->qoutfifonext];
|
|
|
|
if (completion->valid_tag != ahd->qoutfifonext_valid_tag)
|
|
break;
|
|
|
|
scb_index = ahd_le16toh(completion->tag);
|
|
scb = ahd_lookup_scb(ahd, scb_index);
|
|
if (scb == NULL) {
|
|
printk("%s: WARNING no command for scb %d "
|
|
"(cmdcmplt)\nQOUTPOS = %d\n",
|
|
ahd_name(ahd), scb_index,
|
|
ahd->qoutfifonext);
|
|
ahd_dump_card_state(ahd);
|
|
} else if ((completion->sg_status & SG_STATUS_VALID) != 0) {
|
|
ahd_handle_scb_status(ahd, scb);
|
|
} else {
|
|
ahd_done(ahd, scb);
|
|
}
|
|
|
|
ahd->qoutfifonext = (ahd->qoutfifonext+1) & (AHD_QOUT_SIZE-1);
|
|
if (ahd->qoutfifonext == 0)
|
|
ahd->qoutfifonext_valid_tag ^= QOUTFIFO_ENTRY_VALID;
|
|
}
|
|
ahd->flags &= ~AHD_RUNNING_QOUTFIFO;
|
|
}
|
|
|
|
/************************* Interrupt Handling *********************************/
|
|
static void
|
|
ahd_handle_hwerrint(struct ahd_softc *ahd)
|
|
{
|
|
/*
|
|
* Some catastrophic hardware error has occurred.
|
|
* Print it for the user and disable the controller.
|
|
*/
|
|
int i;
|
|
int error;
|
|
|
|
error = ahd_inb(ahd, ERROR);
|
|
for (i = 0; i < num_errors; i++) {
|
|
if ((error & ahd_hard_errors[i].errno) != 0)
|
|
printk("%s: hwerrint, %s\n",
|
|
ahd_name(ahd), ahd_hard_errors[i].errmesg);
|
|
}
|
|
|
|
ahd_dump_card_state(ahd);
|
|
panic("BRKADRINT");
|
|
|
|
/* Tell everyone that this HBA is no longer available */
|
|
ahd_abort_scbs(ahd, CAM_TARGET_WILDCARD, ALL_CHANNELS,
|
|
CAM_LUN_WILDCARD, SCB_LIST_NULL, ROLE_UNKNOWN,
|
|
CAM_NO_HBA);
|
|
|
|
/* Tell the system that this controller has gone away. */
|
|
ahd_free(ahd);
|
|
}
|
|
|
|
#ifdef AHD_DEBUG
|
|
static void
|
|
ahd_dump_sglist(struct scb *scb)
|
|
{
|
|
int i;
|
|
|
|
if (scb->sg_count > 0) {
|
|
if ((scb->ahd_softc->flags & AHD_64BIT_ADDRESSING) != 0) {
|
|
struct ahd_dma64_seg *sg_list;
|
|
|
|
sg_list = (struct ahd_dma64_seg*)scb->sg_list;
|
|
for (i = 0; i < scb->sg_count; i++) {
|
|
uint64_t addr;
|
|
|
|
addr = ahd_le64toh(sg_list[i].addr);
|
|
printk("sg[%d] - Addr 0x%x%x : Length %d%s\n",
|
|
i,
|
|
(uint32_t)((addr >> 32) & 0xFFFFFFFF),
|
|
(uint32_t)(addr & 0xFFFFFFFF),
|
|
sg_list[i].len & AHD_SG_LEN_MASK,
|
|
(sg_list[i].len & AHD_DMA_LAST_SEG)
|
|
? " Last" : "");
|
|
}
|
|
} else {
|
|
struct ahd_dma_seg *sg_list;
|
|
|
|
sg_list = (struct ahd_dma_seg*)scb->sg_list;
|
|
for (i = 0; i < scb->sg_count; i++) {
|
|
uint32_t len;
|
|
|
|
len = ahd_le32toh(sg_list[i].len);
|
|
printk("sg[%d] - Addr 0x%x%x : Length %d%s\n",
|
|
i,
|
|
(len & AHD_SG_HIGH_ADDR_MASK) >> 24,
|
|
ahd_le32toh(sg_list[i].addr),
|
|
len & AHD_SG_LEN_MASK,
|
|
len & AHD_DMA_LAST_SEG ? " Last" : "");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* AHD_DEBUG */
|
|
|
|
static void
|
|
ahd_handle_seqint(struct ahd_softc *ahd, u_int intstat)
|
|
{
|
|
u_int seqintcode;
|
|
|
|
/*
|
|
* Save the sequencer interrupt code and clear the SEQINT
|
|
* bit. We will unpause the sequencer, if appropriate,
|
|
* after servicing the request.
|
|
*/
|
|
seqintcode = ahd_inb(ahd, SEQINTCODE);
|
|
ahd_outb(ahd, CLRINT, CLRSEQINT);
|
|
if ((ahd->bugs & AHD_INTCOLLISION_BUG) != 0) {
|
|
/*
|
|
* Unpause the sequencer and let it clear
|
|
* SEQINT by writing NO_SEQINT to it. This
|
|
* will cause the sequencer to be paused again,
|
|
* which is the expected state of this routine.
|
|
*/
|
|
ahd_unpause(ahd);
|
|
while (!ahd_is_paused(ahd))
|
|
;
|
|
ahd_outb(ahd, CLRINT, CLRSEQINT);
|
|
}
|
|
ahd_update_modes(ahd);
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MISC) != 0)
|
|
printk("%s: Handle Seqint Called for code %d\n",
|
|
ahd_name(ahd), seqintcode);
|
|
#endif
|
|
switch (seqintcode) {
|
|
case ENTERING_NONPACK:
|
|
{
|
|
struct scb *scb;
|
|
u_int scbid;
|
|
|
|
AHD_ASSERT_MODES(ahd, ~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK),
|
|
~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK));
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL) {
|
|
/*
|
|
* Somehow need to know if this
|
|
* is from a selection or reselection.
|
|
* From that, we can determine target
|
|
* ID so we at least have an I_T nexus.
|
|
*/
|
|
} else {
|
|
ahd_outb(ahd, SAVED_SCSIID, scb->hscb->scsiid);
|
|
ahd_outb(ahd, SAVED_LUN, scb->hscb->lun);
|
|
ahd_outb(ahd, SEQ_FLAGS, 0x0);
|
|
}
|
|
if ((ahd_inb(ahd, LQISTAT2) & LQIPHASE_OUTPKT) != 0
|
|
&& (ahd_inb(ahd, SCSISIGO) & ATNO) != 0) {
|
|
/*
|
|
* Phase change after read stream with
|
|
* CRC error with P0 asserted on last
|
|
* packet.
|
|
*/
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_RECOVERY) != 0)
|
|
printk("%s: Assuming LQIPHASE_NLQ with "
|
|
"P0 assertion\n", ahd_name(ahd));
|
|
#endif
|
|
}
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_RECOVERY) != 0)
|
|
printk("%s: Entering NONPACK\n", ahd_name(ahd));
|
|
#endif
|
|
break;
|
|
}
|
|
case INVALID_SEQINT:
|
|
printk("%s: Invalid Sequencer interrupt occurred, "
|
|
"resetting channel.\n",
|
|
ahd_name(ahd));
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_RECOVERY) != 0)
|
|
ahd_dump_card_state(ahd);
|
|
#endif
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE);
|
|
break;
|
|
case STATUS_OVERRUN:
|
|
{
|
|
struct scb *scb;
|
|
u_int scbid;
|
|
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb != NULL)
|
|
ahd_print_path(ahd, scb);
|
|
else
|
|
printk("%s: ", ahd_name(ahd));
|
|
printk("SCB %d Packetized Status Overrun", scbid);
|
|
ahd_dump_card_state(ahd);
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE);
|
|
break;
|
|
}
|
|
case CFG4ISTAT_INTR:
|
|
{
|
|
struct scb *scb;
|
|
u_int scbid;
|
|
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL) {
|
|
ahd_dump_card_state(ahd);
|
|
printk("CFG4ISTAT: Free SCB %d referenced", scbid);
|
|
panic("For safety");
|
|
}
|
|
ahd_outq(ahd, HADDR, scb->sense_busaddr);
|
|
ahd_outw(ahd, HCNT, AHD_SENSE_BUFSIZE);
|
|
ahd_outb(ahd, HCNT + 2, 0);
|
|
ahd_outb(ahd, SG_CACHE_PRE, SG_LAST_SEG);
|
|
ahd_outb(ahd, DFCNTRL, PRELOADEN|SCSIEN|HDMAEN);
|
|
break;
|
|
}
|
|
case ILLEGAL_PHASE:
|
|
{
|
|
u_int bus_phase;
|
|
|
|
bus_phase = ahd_inb(ahd, SCSISIGI) & PHASE_MASK;
|
|
printk("%s: ILLEGAL_PHASE 0x%x\n",
|
|
ahd_name(ahd), bus_phase);
|
|
|
|
switch (bus_phase) {
|
|
case P_DATAOUT:
|
|
case P_DATAIN:
|
|
case P_DATAOUT_DT:
|
|
case P_DATAIN_DT:
|
|
case P_MESGOUT:
|
|
case P_STATUS:
|
|
case P_MESGIN:
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE);
|
|
printk("%s: Issued Bus Reset.\n", ahd_name(ahd));
|
|
break;
|
|
case P_COMMAND:
|
|
{
|
|
struct ahd_devinfo devinfo;
|
|
struct scb *scb;
|
|
u_int scbid;
|
|
|
|
/*
|
|
* If a target takes us into the command phase
|
|
* assume that it has been externally reset and
|
|
* has thus lost our previous packetized negotiation
|
|
* agreement. Since we have not sent an identify
|
|
* message and may not have fully qualified the
|
|
* connection, we change our command to TUR, assert
|
|
* ATN and ABORT the task when we go to message in
|
|
* phase. The OSM will see the REQUEUE_REQUEST
|
|
* status and retry the command.
|
|
*/
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL) {
|
|
printk("Invalid phase with no valid SCB. "
|
|
"Resetting bus.\n");
|
|
ahd_reset_channel(ahd, 'A',
|
|
/*Initiate Reset*/TRUE);
|
|
break;
|
|
}
|
|
ahd_compile_devinfo(&devinfo, SCB_GET_OUR_ID(scb),
|
|
SCB_GET_TARGET(ahd, scb),
|
|
SCB_GET_LUN(scb),
|
|
SCB_GET_CHANNEL(ahd, scb),
|
|
ROLE_INITIATOR);
|
|
ahd_set_width(ahd, &devinfo, MSG_EXT_WDTR_BUS_8_BIT,
|
|
AHD_TRANS_ACTIVE, /*paused*/TRUE);
|
|
ahd_set_syncrate(ahd, &devinfo, /*period*/0,
|
|
/*offset*/0, /*ppr_options*/0,
|
|
AHD_TRANS_ACTIVE, /*paused*/TRUE);
|
|
/* Hand-craft TUR command */
|
|
ahd_outb(ahd, SCB_CDB_STORE, 0);
|
|
ahd_outb(ahd, SCB_CDB_STORE+1, 0);
|
|
ahd_outb(ahd, SCB_CDB_STORE+2, 0);
|
|
ahd_outb(ahd, SCB_CDB_STORE+3, 0);
|
|
ahd_outb(ahd, SCB_CDB_STORE+4, 0);
|
|
ahd_outb(ahd, SCB_CDB_STORE+5, 0);
|
|
ahd_outb(ahd, SCB_CDB_LEN, 6);
|
|
scb->hscb->control &= ~(TAG_ENB|SCB_TAG_TYPE);
|
|
scb->hscb->control |= MK_MESSAGE;
|
|
ahd_outb(ahd, SCB_CONTROL, scb->hscb->control);
|
|
ahd_outb(ahd, MSG_OUT, HOST_MSG);
|
|
ahd_outb(ahd, SAVED_SCSIID, scb->hscb->scsiid);
|
|
/*
|
|
* The lun is 0, regardless of the SCB's lun
|
|
* as we have not sent an identify message.
|
|
*/
|
|
ahd_outb(ahd, SAVED_LUN, 0);
|
|
ahd_outb(ahd, SEQ_FLAGS, 0);
|
|
ahd_assert_atn(ahd);
|
|
scb->flags &= ~SCB_PACKETIZED;
|
|
scb->flags |= SCB_ABORT|SCB_EXTERNAL_RESET;
|
|
ahd_freeze_devq(ahd, scb);
|
|
ahd_set_transaction_status(scb, CAM_REQUEUE_REQ);
|
|
ahd_freeze_scb(scb);
|
|
|
|
/* Notify XPT */
|
|
ahd_send_async(ahd, devinfo.channel, devinfo.target,
|
|
CAM_LUN_WILDCARD, AC_SENT_BDR);
|
|
|
|
/*
|
|
* Allow the sequencer to continue with
|
|
* non-pack processing.
|
|
*/
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
ahd_outb(ahd, CLRLQOINT1, CLRLQOPHACHGINPKT);
|
|
if ((ahd->bugs & AHD_CLRLQO_AUTOCLR_BUG) != 0) {
|
|
ahd_outb(ahd, CLRLQOINT1, 0);
|
|
}
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_RECOVERY) != 0) {
|
|
ahd_print_path(ahd, scb);
|
|
printk("Unexpected command phase from "
|
|
"packetized target\n");
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case CFG4OVERRUN:
|
|
{
|
|
struct scb *scb;
|
|
u_int scb_index;
|
|
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_RECOVERY) != 0) {
|
|
printk("%s: CFG4OVERRUN mode = %x\n", ahd_name(ahd),
|
|
ahd_inb(ahd, MODE_PTR));
|
|
}
|
|
#endif
|
|
scb_index = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scb_index);
|
|
if (scb == NULL) {
|
|
/*
|
|
* Attempt to transfer to an SCB that is
|
|
* not outstanding.
|
|
*/
|
|
ahd_assert_atn(ahd);
|
|
ahd_outb(ahd, MSG_OUT, HOST_MSG);
|
|
ahd->msgout_buf[0] = ABORT_TASK;
|
|
ahd->msgout_len = 1;
|
|
ahd->msgout_index = 0;
|
|
ahd->msg_type = MSG_TYPE_INITIATOR_MSGOUT;
|
|
/*
|
|
* Clear status received flag to prevent any
|
|
* attempt to complete this bogus SCB.
|
|
*/
|
|
ahd_outb(ahd, SCB_CONTROL,
|
|
ahd_inb_scbram(ahd, SCB_CONTROL)
|
|
& ~STATUS_RCVD);
|
|
}
|
|
break;
|
|
}
|
|
case DUMP_CARD_STATE:
|
|
{
|
|
ahd_dump_card_state(ahd);
|
|
break;
|
|
}
|
|
case PDATA_REINIT:
|
|
{
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_RECOVERY) != 0) {
|
|
printk("%s: PDATA_REINIT - DFCNTRL = 0x%x "
|
|
"SG_CACHE_SHADOW = 0x%x\n",
|
|
ahd_name(ahd), ahd_inb(ahd, DFCNTRL),
|
|
ahd_inb(ahd, SG_CACHE_SHADOW));
|
|
}
|
|
#endif
|
|
ahd_reinitialize_dataptrs(ahd);
|
|
break;
|
|
}
|
|
case HOST_MSG_LOOP:
|
|
{
|
|
struct ahd_devinfo devinfo;
|
|
|
|
/*
|
|
* The sequencer has encountered a message phase
|
|
* that requires host assistance for completion.
|
|
* While handling the message phase(s), we will be
|
|
* notified by the sequencer after each byte is
|
|
* transferred so we can track bus phase changes.
|
|
*
|
|
* If this is the first time we've seen a HOST_MSG_LOOP
|
|
* interrupt, initialize the state of the host message
|
|
* loop.
|
|
*/
|
|
ahd_fetch_devinfo(ahd, &devinfo);
|
|
if (ahd->msg_type == MSG_TYPE_NONE) {
|
|
struct scb *scb;
|
|
u_int scb_index;
|
|
u_int bus_phase;
|
|
|
|
bus_phase = ahd_inb(ahd, SCSISIGI) & PHASE_MASK;
|
|
if (bus_phase != P_MESGIN
|
|
&& bus_phase != P_MESGOUT) {
|
|
printk("ahd_intr: HOST_MSG_LOOP bad "
|
|
"phase 0x%x\n", bus_phase);
|
|
/*
|
|
* Probably transitioned to bus free before
|
|
* we got here. Just punt the message.
|
|
*/
|
|
ahd_dump_card_state(ahd);
|
|
ahd_clear_intstat(ahd);
|
|
ahd_restart(ahd);
|
|
return;
|
|
}
|
|
|
|
scb_index = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scb_index);
|
|
if (devinfo.role == ROLE_INITIATOR) {
|
|
if (bus_phase == P_MESGOUT)
|
|
ahd_setup_initiator_msgout(ahd,
|
|
&devinfo,
|
|
scb);
|
|
else {
|
|
ahd->msg_type =
|
|
MSG_TYPE_INITIATOR_MSGIN;
|
|
ahd->msgin_index = 0;
|
|
}
|
|
}
|
|
#ifdef AHD_TARGET_MODE
|
|
else {
|
|
if (bus_phase == P_MESGOUT) {
|
|
ahd->msg_type =
|
|
MSG_TYPE_TARGET_MSGOUT;
|
|
ahd->msgin_index = 0;
|
|
} else
|
|
ahd_setup_target_msgin(ahd,
|
|
&devinfo,
|
|
scb);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
ahd_handle_message_phase(ahd);
|
|
break;
|
|
}
|
|
case NO_MATCH:
|
|
{
|
|
/* Ensure we don't leave the selection hardware on */
|
|
AHD_ASSERT_MODES(ahd, AHD_MODE_SCSI_MSK, AHD_MODE_SCSI_MSK);
|
|
ahd_outb(ahd, SCSISEQ0, ahd_inb(ahd, SCSISEQ0) & ~ENSELO);
|
|
|
|
printk("%s:%c:%d: no active SCB for reconnecting "
|
|
"target - issuing BUS DEVICE RESET\n",
|
|
ahd_name(ahd), 'A', ahd_inb(ahd, SELID) >> 4);
|
|
printk("SAVED_SCSIID == 0x%x, SAVED_LUN == 0x%x, "
|
|
"REG0 == 0x%x ACCUM = 0x%x\n",
|
|
ahd_inb(ahd, SAVED_SCSIID), ahd_inb(ahd, SAVED_LUN),
|
|
ahd_inw(ahd, REG0), ahd_inb(ahd, ACCUM));
|
|
printk("SEQ_FLAGS == 0x%x, SCBPTR == 0x%x, BTT == 0x%x, "
|
|
"SINDEX == 0x%x\n",
|
|
ahd_inb(ahd, SEQ_FLAGS), ahd_get_scbptr(ahd),
|
|
ahd_find_busy_tcl(ahd,
|
|
BUILD_TCL(ahd_inb(ahd, SAVED_SCSIID),
|
|
ahd_inb(ahd, SAVED_LUN))),
|
|
ahd_inw(ahd, SINDEX));
|
|
printk("SELID == 0x%x, SCB_SCSIID == 0x%x, SCB_LUN == 0x%x, "
|
|
"SCB_CONTROL == 0x%x\n",
|
|
ahd_inb(ahd, SELID), ahd_inb_scbram(ahd, SCB_SCSIID),
|
|
ahd_inb_scbram(ahd, SCB_LUN),
|
|
ahd_inb_scbram(ahd, SCB_CONTROL));
|
|
printk("SCSIBUS[0] == 0x%x, SCSISIGI == 0x%x\n",
|
|
ahd_inb(ahd, SCSIBUS), ahd_inb(ahd, SCSISIGI));
|
|
printk("SXFRCTL0 == 0x%x\n", ahd_inb(ahd, SXFRCTL0));
|
|
printk("SEQCTL0 == 0x%x\n", ahd_inb(ahd, SEQCTL0));
|
|
ahd_dump_card_state(ahd);
|
|
ahd->msgout_buf[0] = TARGET_RESET;
|
|
ahd->msgout_len = 1;
|
|
ahd->msgout_index = 0;
|
|
ahd->msg_type = MSG_TYPE_INITIATOR_MSGOUT;
|
|
ahd_outb(ahd, MSG_OUT, HOST_MSG);
|
|
ahd_assert_atn(ahd);
|
|
break;
|
|
}
|
|
case PROTO_VIOLATION:
|
|
{
|
|
ahd_handle_proto_violation(ahd);
|
|
break;
|
|
}
|
|
case IGN_WIDE_RES:
|
|
{
|
|
struct ahd_devinfo devinfo;
|
|
|
|
ahd_fetch_devinfo(ahd, &devinfo);
|
|
ahd_handle_ign_wide_residue(ahd, &devinfo);
|
|
break;
|
|
}
|
|
case BAD_PHASE:
|
|
{
|
|
u_int lastphase;
|
|
|
|
lastphase = ahd_inb(ahd, LASTPHASE);
|
|
printk("%s:%c:%d: unknown scsi bus phase %x, "
|
|
"lastphase = 0x%x. Attempting to continue\n",
|
|
ahd_name(ahd), 'A',
|
|
SCSIID_TARGET(ahd, ahd_inb(ahd, SAVED_SCSIID)),
|
|
lastphase, ahd_inb(ahd, SCSISIGI));
|
|
break;
|
|
}
|
|
case MISSED_BUSFREE:
|
|
{
|
|
u_int lastphase;
|
|
|
|
lastphase = ahd_inb(ahd, LASTPHASE);
|
|
printk("%s:%c:%d: Missed busfree. "
|
|
"Lastphase = 0x%x, Curphase = 0x%x\n",
|
|
ahd_name(ahd), 'A',
|
|
SCSIID_TARGET(ahd, ahd_inb(ahd, SAVED_SCSIID)),
|
|
lastphase, ahd_inb(ahd, SCSISIGI));
|
|
ahd_restart(ahd);
|
|
return;
|
|
}
|
|
case DATA_OVERRUN:
|
|
{
|
|
/*
|
|
* When the sequencer detects an overrun, it
|
|
* places the controller in "BITBUCKET" mode
|
|
* and allows the target to complete its transfer.
|
|
* Unfortunately, none of the counters get updated
|
|
* when the controller is in this mode, so we have
|
|
* no way of knowing how large the overrun was.
|
|
*/
|
|
struct scb *scb;
|
|
u_int scbindex;
|
|
#ifdef AHD_DEBUG
|
|
u_int lastphase;
|
|
#endif
|
|
|
|
scbindex = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbindex);
|
|
#ifdef AHD_DEBUG
|
|
lastphase = ahd_inb(ahd, LASTPHASE);
|
|
if ((ahd_debug & AHD_SHOW_RECOVERY) != 0) {
|
|
ahd_print_path(ahd, scb);
|
|
printk("data overrun detected %s. Tag == 0x%x.\n",
|
|
ahd_lookup_phase_entry(lastphase)->phasemsg,
|
|
SCB_GET_TAG(scb));
|
|
ahd_print_path(ahd, scb);
|
|
printk("%s seen Data Phase. Length = %ld. "
|
|
"NumSGs = %d.\n",
|
|
ahd_inb(ahd, SEQ_FLAGS) & DPHASE
|
|
? "Have" : "Haven't",
|
|
ahd_get_transfer_length(scb), scb->sg_count);
|
|
ahd_dump_sglist(scb);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Set this and it will take effect when the
|
|
* target does a command complete.
|
|
*/
|
|
ahd_freeze_devq(ahd, scb);
|
|
ahd_set_transaction_status(scb, CAM_DATA_RUN_ERR);
|
|
ahd_freeze_scb(scb);
|
|
break;
|
|
}
|
|
case MKMSG_FAILED:
|
|
{
|
|
struct ahd_devinfo devinfo;
|
|
struct scb *scb;
|
|
u_int scbid;
|
|
|
|
ahd_fetch_devinfo(ahd, &devinfo);
|
|
printk("%s:%c:%d:%d: Attempt to issue message failed\n",
|
|
ahd_name(ahd), devinfo.channel, devinfo.target,
|
|
devinfo.lun);
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb != NULL
|
|
&& (scb->flags & SCB_RECOVERY_SCB) != 0)
|
|
/*
|
|
* Ensure that we didn't put a second instance of this
|
|
* SCB into the QINFIFO.
|
|
*/
|
|
ahd_search_qinfifo(ahd, SCB_GET_TARGET(ahd, scb),
|
|
SCB_GET_CHANNEL(ahd, scb),
|
|
SCB_GET_LUN(scb), SCB_GET_TAG(scb),
|
|
ROLE_INITIATOR, /*status*/0,
|
|
SEARCH_REMOVE);
|
|
ahd_outb(ahd, SCB_CONTROL,
|
|
ahd_inb_scbram(ahd, SCB_CONTROL) & ~MK_MESSAGE);
|
|
break;
|
|
}
|
|
case TASKMGMT_FUNC_COMPLETE:
|
|
{
|
|
u_int scbid;
|
|
struct scb *scb;
|
|
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb != NULL) {
|
|
u_int lun;
|
|
u_int tag;
|
|
cam_status error;
|
|
|
|
ahd_print_path(ahd, scb);
|
|
printk("Task Management Func 0x%x Complete\n",
|
|
scb->hscb->task_management);
|
|
lun = CAM_LUN_WILDCARD;
|
|
tag = SCB_LIST_NULL;
|
|
|
|
switch (scb->hscb->task_management) {
|
|
case SIU_TASKMGMT_ABORT_TASK:
|
|
tag = SCB_GET_TAG(scb);
|
|
fallthrough;
|
|
case SIU_TASKMGMT_ABORT_TASK_SET:
|
|
case SIU_TASKMGMT_CLEAR_TASK_SET:
|
|
lun = scb->hscb->lun;
|
|
error = CAM_REQ_ABORTED;
|
|
ahd_abort_scbs(ahd, SCB_GET_TARGET(ahd, scb),
|
|
'A', lun, tag, ROLE_INITIATOR,
|
|
error);
|
|
break;
|
|
case SIU_TASKMGMT_LUN_RESET:
|
|
lun = scb->hscb->lun;
|
|
fallthrough;
|
|
case SIU_TASKMGMT_TARGET_RESET:
|
|
{
|
|
struct ahd_devinfo devinfo;
|
|
|
|
ahd_scb_devinfo(ahd, &devinfo, scb);
|
|
error = CAM_BDR_SENT;
|
|
ahd_handle_devreset(ahd, &devinfo, lun,
|
|
CAM_BDR_SENT,
|
|
lun != CAM_LUN_WILDCARD
|
|
? "Lun Reset"
|
|
: "Target Reset",
|
|
/*verbose_level*/0);
|
|
break;
|
|
}
|
|
default:
|
|
panic("Unexpected TaskMgmt Func\n");
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case TASKMGMT_CMD_CMPLT_OKAY:
|
|
{
|
|
u_int scbid;
|
|
struct scb *scb;
|
|
|
|
/*
|
|
* An ABORT TASK TMF failed to be delivered before
|
|
* the targeted command completed normally.
|
|
*/
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb != NULL) {
|
|
/*
|
|
* Remove the second instance of this SCB from
|
|
* the QINFIFO if it is still there.
|
|
*/
|
|
ahd_print_path(ahd, scb);
|
|
printk("SCB completes before TMF\n");
|
|
/*
|
|
* Handle losing the race. Wait until any
|
|
* current selection completes. We will then
|
|
* set the TMF back to zero in this SCB so that
|
|
* the sequencer doesn't bother to issue another
|
|
* sequencer interrupt for its completion.
|
|
*/
|
|
while ((ahd_inb(ahd, SCSISEQ0) & ENSELO) != 0
|
|
&& (ahd_inb(ahd, SSTAT0) & SELDO) == 0
|
|
&& (ahd_inb(ahd, SSTAT1) & SELTO) == 0)
|
|
;
|
|
ahd_outb(ahd, SCB_TASK_MANAGEMENT, 0);
|
|
ahd_search_qinfifo(ahd, SCB_GET_TARGET(ahd, scb),
|
|
SCB_GET_CHANNEL(ahd, scb),
|
|
SCB_GET_LUN(scb), SCB_GET_TAG(scb),
|
|
ROLE_INITIATOR, /*status*/0,
|
|
SEARCH_REMOVE);
|
|
}
|
|
break;
|
|
}
|
|
case TRACEPOINT0:
|
|
case TRACEPOINT1:
|
|
case TRACEPOINT2:
|
|
case TRACEPOINT3:
|
|
printk("%s: Tracepoint %d\n", ahd_name(ahd),
|
|
seqintcode - TRACEPOINT0);
|
|
break;
|
|
case NO_SEQINT:
|
|
break;
|
|
case SAW_HWERR:
|
|
ahd_handle_hwerrint(ahd);
|
|
break;
|
|
default:
|
|
printk("%s: Unexpected SEQINTCODE %d\n", ahd_name(ahd),
|
|
seqintcode);
|
|
break;
|
|
}
|
|
/*
|
|
* The sequencer is paused immediately on
|
|
* a SEQINT, so we should restart it when
|
|
* we're done.
|
|
*/
|
|
ahd_unpause(ahd);
|
|
}
|
|
|
|
static void
|
|
ahd_handle_scsiint(struct ahd_softc *ahd, u_int intstat)
|
|
{
|
|
struct scb *scb;
|
|
u_int status0;
|
|
u_int status3;
|
|
u_int status;
|
|
u_int lqistat1;
|
|
u_int lqostat0;
|
|
u_int scbid;
|
|
u_int busfreetime;
|
|
|
|
ahd_update_modes(ahd);
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
|
|
status3 = ahd_inb(ahd, SSTAT3) & (NTRAMPERR|OSRAMPERR);
|
|
status0 = ahd_inb(ahd, SSTAT0) & (IOERR|OVERRUN|SELDI|SELDO);
|
|
status = ahd_inb(ahd, SSTAT1) & (SELTO|SCSIRSTI|BUSFREE|SCSIPERR);
|
|
lqistat1 = ahd_inb(ahd, LQISTAT1);
|
|
lqostat0 = ahd_inb(ahd, LQOSTAT0);
|
|
busfreetime = ahd_inb(ahd, SSTAT2) & BUSFREETIME;
|
|
|
|
/*
|
|
* Ignore external resets after a bus reset.
|
|
*/
|
|
if (((status & SCSIRSTI) != 0) && (ahd->flags & AHD_BUS_RESET_ACTIVE)) {
|
|
ahd_outb(ahd, CLRSINT1, CLRSCSIRSTI);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Clear bus reset flag
|
|
*/
|
|
ahd->flags &= ~AHD_BUS_RESET_ACTIVE;
|
|
|
|
if ((status0 & (SELDI|SELDO)) != 0) {
|
|
u_int simode0;
|
|
|
|
ahd_set_modes(ahd, AHD_MODE_CFG, AHD_MODE_CFG);
|
|
simode0 = ahd_inb(ahd, SIMODE0);
|
|
status0 &= simode0 & (IOERR|OVERRUN|SELDI|SELDO);
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
}
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb != NULL
|
|
&& (ahd_inb(ahd, SEQ_FLAGS) & NOT_IDENTIFIED) != 0)
|
|
scb = NULL;
|
|
|
|
if ((status0 & IOERR) != 0) {
|
|
u_int now_lvd;
|
|
|
|
now_lvd = ahd_inb(ahd, SBLKCTL) & ENAB40;
|
|
printk("%s: Transceiver State Has Changed to %s mode\n",
|
|
ahd_name(ahd), now_lvd ? "LVD" : "SE");
|
|
ahd_outb(ahd, CLRSINT0, CLRIOERR);
|
|
/*
|
|
* A change in I/O mode is equivalent to a bus reset.
|
|
*/
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE);
|
|
ahd_pause(ahd);
|
|
ahd_setup_iocell_workaround(ahd);
|
|
ahd_unpause(ahd);
|
|
} else if ((status0 & OVERRUN) != 0) {
|
|
|
|
printk("%s: SCSI offset overrun detected. Resetting bus.\n",
|
|
ahd_name(ahd));
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE);
|
|
} else if ((status & SCSIRSTI) != 0) {
|
|
|
|
printk("%s: Someone reset channel A\n", ahd_name(ahd));
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/FALSE);
|
|
} else if ((status & SCSIPERR) != 0) {
|
|
|
|
/* Make sure the sequencer is in a safe location. */
|
|
ahd_clear_critical_section(ahd);
|
|
|
|
ahd_handle_transmission_error(ahd);
|
|
} else if (lqostat0 != 0) {
|
|
|
|
printk("%s: lqostat0 == 0x%x!\n", ahd_name(ahd), lqostat0);
|
|
ahd_outb(ahd, CLRLQOINT0, lqostat0);
|
|
if ((ahd->bugs & AHD_CLRLQO_AUTOCLR_BUG) != 0)
|
|
ahd_outb(ahd, CLRLQOINT1, 0);
|
|
} else if ((status & SELTO) != 0) {
|
|
/* Stop the selection */
|
|
ahd_outb(ahd, SCSISEQ0, 0);
|
|
|
|
/* Make sure the sequencer is in a safe location. */
|
|
ahd_clear_critical_section(ahd);
|
|
|
|
/* No more pending messages */
|
|
ahd_clear_msg_state(ahd);
|
|
|
|
/* Clear interrupt state */
|
|
ahd_outb(ahd, CLRSINT1, CLRSELTIMEO|CLRBUSFREE|CLRSCSIPERR);
|
|
|
|
/*
|
|
* Although the driver does not care about the
|
|
* 'Selection in Progress' status bit, the busy
|
|
* LED does. SELINGO is only cleared by a successful
|
|
* selection, so we must manually clear it to insure
|
|
* the LED turns off just incase no future successful
|
|
* selections occur (e.g. no devices on the bus).
|
|
*/
|
|
ahd_outb(ahd, CLRSINT0, CLRSELINGO);
|
|
|
|
scbid = ahd_inw(ahd, WAITING_TID_HEAD);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL) {
|
|
printk("%s: ahd_intr - referenced scb not "
|
|
"valid during SELTO scb(0x%x)\n",
|
|
ahd_name(ahd), scbid);
|
|
ahd_dump_card_state(ahd);
|
|
} else {
|
|
struct ahd_devinfo devinfo;
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_SELTO) != 0) {
|
|
ahd_print_path(ahd, scb);
|
|
printk("Saw Selection Timeout for SCB 0x%x\n",
|
|
scbid);
|
|
}
|
|
#endif
|
|
ahd_scb_devinfo(ahd, &devinfo, scb);
|
|
ahd_set_transaction_status(scb, CAM_SEL_TIMEOUT);
|
|
ahd_freeze_devq(ahd, scb);
|
|
|
|
/*
|
|
* Cancel any pending transactions on the device
|
|
* now that it seems to be missing. This will
|
|
* also revert us to async/narrow transfers until
|
|
* we can renegotiate with the device.
|
|
*/
|
|
ahd_handle_devreset(ahd, &devinfo,
|
|
CAM_LUN_WILDCARD,
|
|
CAM_SEL_TIMEOUT,
|
|
"Selection Timeout",
|
|
/*verbose_level*/1);
|
|
}
|
|
ahd_outb(ahd, CLRINT, CLRSCSIINT);
|
|
ahd_iocell_first_selection(ahd);
|
|
ahd_unpause(ahd);
|
|
} else if ((status0 & (SELDI|SELDO)) != 0) {
|
|
|
|
ahd_iocell_first_selection(ahd);
|
|
ahd_unpause(ahd);
|
|
} else if (status3 != 0) {
|
|
printk("%s: SCSI Cell parity error SSTAT3 == 0x%x\n",
|
|
ahd_name(ahd), status3);
|
|
ahd_outb(ahd, CLRSINT3, status3);
|
|
} else if ((lqistat1 & (LQIPHASE_LQ|LQIPHASE_NLQ)) != 0) {
|
|
|
|
/* Make sure the sequencer is in a safe location. */
|
|
ahd_clear_critical_section(ahd);
|
|
|
|
ahd_handle_lqiphase_error(ahd, lqistat1);
|
|
} else if ((lqistat1 & LQICRCI_NLQ) != 0) {
|
|
/*
|
|
* This status can be delayed during some
|
|
* streaming operations. The SCSIPHASE
|
|
* handler has already dealt with this case
|
|
* so just clear the error.
|
|
*/
|
|
ahd_outb(ahd, CLRLQIINT1, CLRLQICRCI_NLQ);
|
|
} else if ((status & BUSFREE) != 0
|
|
|| (lqistat1 & LQOBUSFREE) != 0) {
|
|
u_int lqostat1;
|
|
int restart;
|
|
int clear_fifo;
|
|
int packetized;
|
|
u_int mode;
|
|
|
|
/*
|
|
* Clear our selection hardware as soon as possible.
|
|
* We may have an entry in the waiting Q for this target,
|
|
* that is affected by this busfree and we don't want to
|
|
* go about selecting the target while we handle the event.
|
|
*/
|
|
ahd_outb(ahd, SCSISEQ0, 0);
|
|
|
|
/* Make sure the sequencer is in a safe location. */
|
|
ahd_clear_critical_section(ahd);
|
|
|
|
/*
|
|
* Determine what we were up to at the time of
|
|
* the busfree.
|
|
*/
|
|
mode = AHD_MODE_SCSI;
|
|
busfreetime = ahd_inb(ahd, SSTAT2) & BUSFREETIME;
|
|
lqostat1 = ahd_inb(ahd, LQOSTAT1);
|
|
switch (busfreetime) {
|
|
case BUSFREE_DFF0:
|
|
case BUSFREE_DFF1:
|
|
{
|
|
mode = busfreetime == BUSFREE_DFF0
|
|
? AHD_MODE_DFF0 : AHD_MODE_DFF1;
|
|
ahd_set_modes(ahd, mode, mode);
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL) {
|
|
printk("%s: Invalid SCB %d in DFF%d "
|
|
"during unexpected busfree\n",
|
|
ahd_name(ahd), scbid, mode);
|
|
packetized = 0;
|
|
} else
|
|
packetized = (scb->flags & SCB_PACKETIZED) != 0;
|
|
clear_fifo = 1;
|
|
break;
|
|
}
|
|
case BUSFREE_LQO:
|
|
clear_fifo = 0;
|
|
packetized = 1;
|
|
break;
|
|
default:
|
|
clear_fifo = 0;
|
|
packetized = (lqostat1 & LQOBUSFREE) != 0;
|
|
if (!packetized
|
|
&& ahd_inb(ahd, LASTPHASE) == P_BUSFREE
|
|
&& (ahd_inb(ahd, SSTAT0) & SELDI) == 0
|
|
&& ((ahd_inb(ahd, SSTAT0) & SELDO) == 0
|
|
|| (ahd_inb(ahd, SCSISEQ0) & ENSELO) == 0))
|
|
/*
|
|
* Assume packetized if we are not
|
|
* on the bus in a non-packetized
|
|
* capacity and any pending selection
|
|
* was a packetized selection.
|
|
*/
|
|
packetized = 1;
|
|
break;
|
|
}
|
|
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MISC) != 0)
|
|
printk("Saw Busfree. Busfreetime = 0x%x.\n",
|
|
busfreetime);
|
|
#endif
|
|
/*
|
|
* Busfrees that occur in non-packetized phases are
|
|
* handled by the nonpkt_busfree handler.
|
|
*/
|
|
if (packetized && ahd_inb(ahd, LASTPHASE) == P_BUSFREE) {
|
|
restart = ahd_handle_pkt_busfree(ahd, busfreetime);
|
|
} else {
|
|
packetized = 0;
|
|
restart = ahd_handle_nonpkt_busfree(ahd);
|
|
}
|
|
/*
|
|
* Clear the busfree interrupt status. The setting of
|
|
* the interrupt is a pulse, so in a perfect world, we
|
|
* would not need to muck with the ENBUSFREE logic. This
|
|
* would ensure that if the bus moves on to another
|
|
* connection, busfree protection is still in force. If
|
|
* BUSFREEREV is broken, however, we must manually clear
|
|
* the ENBUSFREE if the busfree occurred during a non-pack
|
|
* connection so that we don't get false positives during
|
|
* future, packetized, connections.
|
|
*/
|
|
ahd_outb(ahd, CLRSINT1, CLRBUSFREE);
|
|
if (packetized == 0
|
|
&& (ahd->bugs & AHD_BUSFREEREV_BUG) != 0)
|
|
ahd_outb(ahd, SIMODE1,
|
|
ahd_inb(ahd, SIMODE1) & ~ENBUSFREE);
|
|
|
|
if (clear_fifo)
|
|
ahd_clear_fifo(ahd, mode);
|
|
|
|
ahd_clear_msg_state(ahd);
|
|
ahd_outb(ahd, CLRINT, CLRSCSIINT);
|
|
if (restart) {
|
|
ahd_restart(ahd);
|
|
} else {
|
|
ahd_unpause(ahd);
|
|
}
|
|
} else {
|
|
printk("%s: Missing case in ahd_handle_scsiint. status = %x\n",
|
|
ahd_name(ahd), status);
|
|
ahd_dump_card_state(ahd);
|
|
ahd_clear_intstat(ahd);
|
|
ahd_unpause(ahd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ahd_handle_transmission_error(struct ahd_softc *ahd)
|
|
{
|
|
struct scb *scb;
|
|
u_int scbid;
|
|
u_int lqistat1;
|
|
u_int msg_out;
|
|
u_int curphase;
|
|
u_int lastphase;
|
|
u_int perrdiag;
|
|
u_int cur_col;
|
|
int silent;
|
|
|
|
scb = NULL;
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
lqistat1 = ahd_inb(ahd, LQISTAT1) & ~(LQIPHASE_LQ|LQIPHASE_NLQ);
|
|
ahd_inb(ahd, LQISTAT2);
|
|
if ((lqistat1 & (LQICRCI_NLQ|LQICRCI_LQ)) == 0
|
|
&& (ahd->bugs & AHD_NLQICRC_DELAYED_BUG) != 0) {
|
|
u_int lqistate;
|
|
|
|
ahd_set_modes(ahd, AHD_MODE_CFG, AHD_MODE_CFG);
|
|
lqistate = ahd_inb(ahd, LQISTATE);
|
|
if ((lqistate >= 0x1E && lqistate <= 0x24)
|
|
|| (lqistate == 0x29)) {
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_RECOVERY) != 0) {
|
|
printk("%s: NLQCRC found via LQISTATE\n",
|
|
ahd_name(ahd));
|
|
}
|
|
#endif
|
|
lqistat1 |= LQICRCI_NLQ;
|
|
}
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
}
|
|
|
|
ahd_outb(ahd, CLRLQIINT1, lqistat1);
|
|
lastphase = ahd_inb(ahd, LASTPHASE);
|
|
curphase = ahd_inb(ahd, SCSISIGI) & PHASE_MASK;
|
|
perrdiag = ahd_inb(ahd, PERRDIAG);
|
|
msg_out = INITIATOR_ERROR;
|
|
ahd_outb(ahd, CLRSINT1, CLRSCSIPERR);
|
|
|
|
/*
|
|
* Try to find the SCB associated with this error.
|
|
*/
|
|
silent = FALSE;
|
|
if (lqistat1 == 0
|
|
|| (lqistat1 & LQICRCI_NLQ) != 0) {
|
|
if ((lqistat1 & (LQICRCI_NLQ|LQIOVERI_NLQ)) != 0)
|
|
ahd_set_active_fifo(ahd);
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb != NULL && SCB_IS_SILENT(scb))
|
|
silent = TRUE;
|
|
}
|
|
|
|
cur_col = 0;
|
|
if (silent == FALSE) {
|
|
printk("%s: Transmission error detected\n", ahd_name(ahd));
|
|
ahd_lqistat1_print(lqistat1, &cur_col, 50);
|
|
ahd_lastphase_print(lastphase, &cur_col, 50);
|
|
ahd_scsisigi_print(curphase, &cur_col, 50);
|
|
ahd_perrdiag_print(perrdiag, &cur_col, 50);
|
|
printk("\n");
|
|
ahd_dump_card_state(ahd);
|
|
}
|
|
|
|
if ((lqistat1 & (LQIOVERI_LQ|LQIOVERI_NLQ)) != 0) {
|
|
if (silent == FALSE) {
|
|
printk("%s: Gross protocol error during incoming "
|
|
"packet. lqistat1 == 0x%x. Resetting bus.\n",
|
|
ahd_name(ahd), lqistat1);
|
|
}
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE);
|
|
return;
|
|
} else if ((lqistat1 & LQICRCI_LQ) != 0) {
|
|
/*
|
|
* A CRC error has been detected on an incoming LQ.
|
|
* The bus is currently hung on the last ACK.
|
|
* Hit LQIRETRY to release the last ack, and
|
|
* wait for the sequencer to determine that ATNO
|
|
* is asserted while in message out to take us
|
|
* to our host message loop. No NONPACKREQ or
|
|
* LQIPHASE type errors will occur in this
|
|
* scenario. After this first LQIRETRY, the LQI
|
|
* manager will be in ISELO where it will
|
|
* happily sit until another packet phase begins.
|
|
* Unexpected bus free detection is enabled
|
|
* through any phases that occur after we release
|
|
* this last ack until the LQI manager sees a
|
|
* packet phase. This implies we may have to
|
|
* ignore a perfectly valid "unexected busfree"
|
|
* after our "initiator detected error" message is
|
|
* sent. A busfree is the expected response after
|
|
* we tell the target that it's L_Q was corrupted.
|
|
* (SPI4R09 10.7.3.3.3)
|
|
*/
|
|
ahd_outb(ahd, LQCTL2, LQIRETRY);
|
|
printk("LQIRetry for LQICRCI_LQ to release ACK\n");
|
|
} else if ((lqistat1 & LQICRCI_NLQ) != 0) {
|
|
/*
|
|
* We detected a CRC error in a NON-LQ packet.
|
|
* The hardware has varying behavior in this situation
|
|
* depending on whether this packet was part of a
|
|
* stream or not.
|
|
*
|
|
* PKT by PKT mode:
|
|
* The hardware has already acked the complete packet.
|
|
* If the target honors our outstanding ATN condition,
|
|
* we should be (or soon will be) in MSGOUT phase.
|
|
* This will trigger the LQIPHASE_LQ status bit as the
|
|
* hardware was expecting another LQ. Unexpected
|
|
* busfree detection is enabled. Once LQIPHASE_LQ is
|
|
* true (first entry into host message loop is much
|
|
* the same), we must clear LQIPHASE_LQ and hit
|
|
* LQIRETRY so the hardware is ready to handle
|
|
* a future LQ. NONPACKREQ will not be asserted again
|
|
* once we hit LQIRETRY until another packet is
|
|
* processed. The target may either go busfree
|
|
* or start another packet in response to our message.
|
|
*
|
|
* Read Streaming P0 asserted:
|
|
* If we raise ATN and the target completes the entire
|
|
* stream (P0 asserted during the last packet), the
|
|
* hardware will ack all data and return to the ISTART
|
|
* state. When the target reponds to our ATN condition,
|
|
* LQIPHASE_LQ will be asserted. We should respond to
|
|
* this with an LQIRETRY to prepare for any future
|
|
* packets. NONPACKREQ will not be asserted again
|
|
* once we hit LQIRETRY until another packet is
|
|
* processed. The target may either go busfree or
|
|
* start another packet in response to our message.
|
|
* Busfree detection is enabled.
|
|
*
|
|
* Read Streaming P0 not asserted:
|
|
* If we raise ATN and the target transitions to
|
|
* MSGOUT in or after a packet where P0 is not
|
|
* asserted, the hardware will assert LQIPHASE_NLQ.
|
|
* We should respond to the LQIPHASE_NLQ with an
|
|
* LQIRETRY. Should the target stay in a non-pkt
|
|
* phase after we send our message, the hardware
|
|
* will assert LQIPHASE_LQ. Recovery is then just as
|
|
* listed above for the read streaming with P0 asserted.
|
|
* Busfree detection is enabled.
|
|
*/
|
|
if (silent == FALSE)
|
|
printk("LQICRC_NLQ\n");
|
|
if (scb == NULL) {
|
|
printk("%s: No SCB valid for LQICRC_NLQ. "
|
|
"Resetting bus\n", ahd_name(ahd));
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE);
|
|
return;
|
|
}
|
|
} else if ((lqistat1 & LQIBADLQI) != 0) {
|
|
printk("Need to handle BADLQI!\n");
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE);
|
|
return;
|
|
} else if ((perrdiag & (PARITYERR|PREVPHASE)) == PARITYERR) {
|
|
if ((curphase & ~P_DATAIN_DT) != 0) {
|
|
/* Ack the byte. So we can continue. */
|
|
if (silent == FALSE)
|
|
printk("Acking %s to clear perror\n",
|
|
ahd_lookup_phase_entry(curphase)->phasemsg);
|
|
ahd_inb(ahd, SCSIDAT);
|
|
}
|
|
|
|
if (curphase == P_MESGIN)
|
|
msg_out = MSG_PARITY_ERROR;
|
|
}
|
|
|
|
/*
|
|
* We've set the hardware to assert ATN if we
|
|
* get a parity error on "in" phases, so all we
|
|
* need to do is stuff the message buffer with
|
|
* the appropriate message. "In" phases have set
|
|
* mesg_out to something other than NOP.
|
|
*/
|
|
ahd->send_msg_perror = msg_out;
|
|
if (scb != NULL && msg_out == INITIATOR_ERROR)
|
|
scb->flags |= SCB_TRANSMISSION_ERROR;
|
|
ahd_outb(ahd, MSG_OUT, HOST_MSG);
|
|
ahd_outb(ahd, CLRINT, CLRSCSIINT);
|
|
ahd_unpause(ahd);
|
|
}
|
|
|
|
static void
|
|
ahd_handle_lqiphase_error(struct ahd_softc *ahd, u_int lqistat1)
|
|
{
|
|
/*
|
|
* Clear the sources of the interrupts.
|
|
*/
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
ahd_outb(ahd, CLRLQIINT1, lqistat1);
|
|
|
|
/*
|
|
* If the "illegal" phase changes were in response
|
|
* to our ATN to flag a CRC error, AND we ended up
|
|
* on packet boundaries, clear the error, restart the
|
|
* LQI manager as appropriate, and go on our merry
|
|
* way toward sending the message. Otherwise, reset
|
|
* the bus to clear the error.
|
|
*/
|
|
ahd_set_active_fifo(ahd);
|
|
if ((ahd_inb(ahd, SCSISIGO) & ATNO) != 0
|
|
&& (ahd_inb(ahd, MDFFSTAT) & DLZERO) != 0) {
|
|
if ((lqistat1 & LQIPHASE_LQ) != 0) {
|
|
printk("LQIRETRY for LQIPHASE_LQ\n");
|
|
ahd_outb(ahd, LQCTL2, LQIRETRY);
|
|
} else if ((lqistat1 & LQIPHASE_NLQ) != 0) {
|
|
printk("LQIRETRY for LQIPHASE_NLQ\n");
|
|
ahd_outb(ahd, LQCTL2, LQIRETRY);
|
|
} else
|
|
panic("ahd_handle_lqiphase_error: No phase errors\n");
|
|
ahd_dump_card_state(ahd);
|
|
ahd_outb(ahd, CLRINT, CLRSCSIINT);
|
|
ahd_unpause(ahd);
|
|
} else {
|
|
printk("Resetting Channel for LQI Phase error\n");
|
|
ahd_dump_card_state(ahd);
|
|
ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Packetized unexpected or expected busfree.
|
|
* Entered in mode based on busfreetime.
|
|
*/
|
|
static int
|
|
ahd_handle_pkt_busfree(struct ahd_softc *ahd, u_int busfreetime)
|
|
{
|
|
u_int lqostat1;
|
|
|
|
AHD_ASSERT_MODES(ahd, ~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK),
|
|
~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK));
|
|
lqostat1 = ahd_inb(ahd, LQOSTAT1);
|
|
if ((lqostat1 & LQOBUSFREE) != 0) {
|
|
struct scb *scb;
|
|
u_int scbid;
|
|
u_int saved_scbptr;
|
|
u_int waiting_h;
|
|
u_int waiting_t;
|
|
u_int next;
|
|
|
|
/*
|
|
* The LQO manager detected an unexpected busfree
|
|
* either:
|
|
*
|
|
* 1) During an outgoing LQ.
|
|
* 2) After an outgoing LQ but before the first
|
|
* REQ of the command packet.
|
|
* 3) During an outgoing command packet.
|
|
*
|
|
* In all cases, CURRSCB is pointing to the
|
|
* SCB that encountered the failure. Clean
|
|
* up the queue, clear SELDO and LQOBUSFREE,
|
|
* and allow the sequencer to restart the select
|
|
* out at its lesure.
|
|
*/
|
|
ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI);
|
|
scbid = ahd_inw(ahd, CURRSCB);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb == NULL)
|
|
panic("SCB not valid during LQOBUSFREE");
|
|
/*
|
|
* Clear the status.
|
|
*/
|
|
ahd_outb(ahd, CLRLQOINT1, CLRLQOBUSFREE);
|
|
if ((ahd->bugs & AHD_CLRLQO_AUTOCLR_BUG) != 0)
|
|
ahd_outb(ahd, CLRLQOINT1, 0);
|
|
ahd_outb(ahd, SCSISEQ0, ahd_inb(ahd, SCSISEQ0) & ~ENSELO);
|
|
ahd_flush_device_writes(ahd);
|
|
ahd_outb(ahd, CLRSINT0, CLRSELDO);
|
|
|
|
/*
|
|
* Return the LQO manager to its idle loop. It will
|
|
* not do this automatically if the busfree occurs
|
|
* after the first REQ of either the LQ or command
|
|
* packet or between the LQ and command packet.
|
|
*/
|
|
ahd_outb(ahd, LQCTL2, ahd_inb(ahd, LQCTL2) | LQOTOIDLE);
|
|
|
|
/*
|
|
* Update the waiting for selection queue so
|
|
* we restart on the correct SCB.
|
|
*/
|
|
waiting_h = ahd_inw(ahd, WAITING_TID_HEAD);
|
|
saved_scbptr = ahd_get_scbptr(ahd);
|
|
if (waiting_h != scbid) {
|
|
|
|
ahd_outw(ahd, WAITING_TID_HEAD, scbid);
|
|
waiting_t = ahd_inw(ahd, WAITING_TID_TAIL);
|
|
if (waiting_t == waiting_h) {
|
|
ahd_outw(ahd, WAITING_TID_TAIL, scbid);
|
|
next = SCB_LIST_NULL;
|
|
} else {
|
|
ahd_set_scbptr(ahd, waiting_h);
|
|
next = ahd_inw_scbram(ahd, SCB_NEXT2);
|
|
}
|
|
ahd_set_scbptr(ahd, scbid);
|
|
ahd_outw(ahd, SCB_NEXT2, next);
|
|
}
|
|
ahd_set_scbptr(ahd, saved_scbptr);
|
|
if (scb->crc_retry_count < AHD_MAX_LQ_CRC_ERRORS) {
|
|
if (SCB_IS_SILENT(scb) == FALSE) {
|
|
ahd_print_path(ahd, scb);
|
|
printk("Probable outgoing LQ CRC error. "
|
|
"Retrying command\n");
|
|
}
|
|
scb->crc_retry_count++;
|
|
} else {
|
|
ahd_set_transaction_status(scb, CAM_UNCOR_PARITY);
|
|
ahd_freeze_scb(scb);
|
|
ahd_freeze_devq(ahd, scb);
|
|
}
|
|
/* Return unpausing the sequencer. */
|
|
return (0);
|
|
} else if ((ahd_inb(ahd, PERRDIAG) & PARITYERR) != 0) {
|
|
/*
|
|
* Ignore what are really parity errors that
|
|
* occur on the last REQ of a free running
|
|
* clock prior to going busfree. Some drives
|
|
* do not properly active negate just before
|
|
* going busfree resulting in a parity glitch.
|
|
*/
|
|
ahd_outb(ahd, CLRSINT1, CLRSCSIPERR|CLRBUSFREE);
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MASKED_ERRORS) != 0)
|
|
printk("%s: Parity on last REQ detected "
|
|
"during busfree phase.\n",
|
|
ahd_name(ahd));
|
|
#endif
|
|
/* Return unpausing the sequencer. */
|
|
return (0);
|
|
}
|
|
if (ahd->src_mode != AHD_MODE_SCSI) {
|
|
u_int scbid;
|
|
struct scb *scb;
|
|
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
ahd_print_path(ahd, scb);
|
|
printk("Unexpected PKT busfree condition\n");
|
|
ahd_dump_card_state(ahd);
|
|
ahd_abort_scbs(ahd, SCB_GET_TARGET(ahd, scb), 'A',
|
|
SCB_GET_LUN(scb), SCB_GET_TAG(scb),
|
|
ROLE_INITIATOR, CAM_UNEXP_BUSFREE);
|
|
|
|
/* Return restarting the sequencer. */
|
|
return (1);
|
|
}
|
|
printk("%s: Unexpected PKT busfree condition\n", ahd_name(ahd));
|
|
ahd_dump_card_state(ahd);
|
|
/* Restart the sequencer. */
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Non-packetized unexpected or expected busfree.
|
|
*/
|
|
static int
|
|
ahd_handle_nonpkt_busfree(struct ahd_softc *ahd)
|
|
{
|
|
struct ahd_devinfo devinfo;
|
|
struct scb *scb;
|
|
u_int lastphase;
|
|
u_int saved_scsiid;
|
|
u_int saved_lun;
|
|
u_int target;
|
|
u_int initiator_role_id;
|
|
u_int scbid;
|
|
u_int ppr_busfree;
|
|
int printerror;
|
|
|
|
/*
|
|
* Look at what phase we were last in. If its message out,
|
|
* chances are pretty good that the busfree was in response
|
|
* to one of our abort requests.
|
|
*/
|
|
lastphase = ahd_inb(ahd, LASTPHASE);
|
|
saved_scsiid = ahd_inb(ahd, SAVED_SCSIID);
|
|
saved_lun = ahd_inb(ahd, SAVED_LUN);
|
|
target = SCSIID_TARGET(ahd, saved_scsiid);
|
|
initiator_role_id = SCSIID_OUR_ID(saved_scsiid);
|
|
ahd_compile_devinfo(&devinfo, initiator_role_id,
|
|
target, saved_lun, 'A', ROLE_INITIATOR);
|
|
printerror = 1;
|
|
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
if (scb != NULL
|
|
&& (ahd_inb(ahd, SEQ_FLAGS) & NOT_IDENTIFIED) != 0)
|
|
scb = NULL;
|
|
|
|
ppr_busfree = (ahd->msg_flags & MSG_FLAG_EXPECT_PPR_BUSFREE) != 0;
|
|
if (lastphase == P_MESGOUT) {
|
|
u_int tag;
|
|
|
|
tag = SCB_LIST_NULL;
|
|
if (ahd_sent_msg(ahd, AHDMSG_1B, ABORT_TASK, TRUE)
|
|
|| ahd_sent_msg(ahd, AHDMSG_1B, ABORT_TASK_SET, TRUE)) {
|
|
int found;
|
|
int sent_msg;
|
|
|
|
if (scb == NULL) {
|
|
ahd_print_devinfo(ahd, &devinfo);
|
|
printk("Abort for unidentified "
|
|
"connection completed.\n");
|
|
/* restart the sequencer. */
|
|
return (1);
|
|
}
|
|
sent_msg = ahd->msgout_buf[ahd->msgout_index - 1];
|
|
ahd_print_path(ahd, scb);
|
|
printk("SCB %d - Abort%s Completed.\n",
|
|
SCB_GET_TAG(scb),
|
|
sent_msg == ABORT_TASK ? "" : " Tag");
|
|
|
|
if (sent_msg == ABORT_TASK)
|
|
tag = SCB_GET_TAG(scb);
|
|
|
|
if ((scb->flags & SCB_EXTERNAL_RESET) != 0) {
|
|
/*
|
|
* This abort is in response to an
|
|
* unexpected switch to command phase
|
|
* for a packetized connection. Since
|
|
* the identify message was never sent,
|
|
* "saved lun" is 0. We really want to
|
|
* abort only the SCB that encountered
|
|
* this error, which could have a different
|
|
* lun. The SCB will be retried so the OS
|
|
* will see the UA after renegotiating to
|
|
* packetized.
|
|
*/
|
|
tag = SCB_GET_TAG(scb);
|
|
saved_lun = scb->hscb->lun;
|
|
}
|
|
found = ahd_abort_scbs(ahd, target, 'A', saved_lun,
|
|
tag, ROLE_INITIATOR,
|
|
CAM_REQ_ABORTED);
|
|
printk("found == 0x%x\n", found);
|
|
printerror = 0;
|
|
} else if (ahd_sent_msg(ahd, AHDMSG_1B,
|
|
TARGET_RESET, TRUE)) {
|
|
ahd_handle_devreset(ahd, &devinfo, CAM_LUN_WILDCARD,
|
|
CAM_BDR_SENT, "Bus Device Reset",
|
|
/*verbose_level*/0);
|
|
printerror = 0;
|
|
} else if (ahd_sent_msg(ahd, AHDMSG_EXT, EXTENDED_PPR, FALSE)
|
|
&& ppr_busfree == 0) {
|
|
struct ahd_initiator_tinfo *tinfo;
|
|
struct ahd_tmode_tstate *tstate;
|
|
|
|
/*
|
|
* PPR Rejected.
|
|
*
|
|
* If the previous negotiation was packetized,
|
|
* this could be because the device has been
|
|
* reset without our knowledge. Force our
|
|
* current negotiation to async and retry the
|
|
* negotiation. Otherwise retry the command
|
|
* with non-ppr negotiation.
|
|
*/
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MESSAGES) != 0)
|
|
printk("PPR negotiation rejected busfree.\n");
|
|
#endif
|
|
tinfo = ahd_fetch_transinfo(ahd, devinfo.channel,
|
|
devinfo.our_scsiid,
|
|
devinfo.target, &tstate);
|
|
if ((tinfo->curr.ppr_options & MSG_EXT_PPR_IU_REQ)!=0) {
|
|
ahd_set_width(ahd, &devinfo,
|
|
MSG_EXT_WDTR_BUS_8_BIT,
|
|
AHD_TRANS_CUR,
|
|
/*paused*/TRUE);
|
|
ahd_set_syncrate(ahd, &devinfo,
|
|
/*period*/0, /*offset*/0,
|
|
/*ppr_options*/0,
|
|
AHD_TRANS_CUR,
|
|
/*paused*/TRUE);
|
|
/*
|
|
* The expect PPR busfree handler below
|
|
* will effect the retry and necessary
|
|
* abort.
|
|
*/
|
|
} else {
|
|
tinfo->curr.transport_version = 2;
|
|
tinfo->goal.transport_version = 2;
|
|
tinfo->goal.ppr_options = 0;
|
|
if (scb != NULL) {
|
|
/*
|
|
* Remove any SCBs in the waiting
|
|
* for selection queue that may
|
|
* also be for this target so that
|
|
* command ordering is preserved.
|
|
*/
|
|
ahd_freeze_devq(ahd, scb);
|
|
ahd_qinfifo_requeue_tail(ahd, scb);
|
|
}
|
|
printerror = 0;
|
|
}
|
|
} else if (ahd_sent_msg(ahd, AHDMSG_EXT, EXTENDED_WDTR, FALSE)
|
|
&& ppr_busfree == 0) {
|
|
/*
|
|
* Negotiation Rejected. Go-narrow and
|
|
* retry command.
|
|
*/
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MESSAGES) != 0)
|
|
printk("WDTR negotiation rejected busfree.\n");
|
|
#endif
|
|
ahd_set_width(ahd, &devinfo,
|
|
MSG_EXT_WDTR_BUS_8_BIT,
|
|
AHD_TRANS_CUR|AHD_TRANS_GOAL,
|
|
/*paused*/TRUE);
|
|
if (scb != NULL) {
|
|
/*
|
|
* Remove any SCBs in the waiting for
|
|
* selection queue that may also be for
|
|
* this target so that command ordering
|
|
* is preserved.
|
|
*/
|
|
ahd_freeze_devq(ahd, scb);
|
|
ahd_qinfifo_requeue_tail(ahd, scb);
|
|
}
|
|
printerror = 0;
|
|
} else if (ahd_sent_msg(ahd, AHDMSG_EXT, EXTENDED_SDTR, FALSE)
|
|
&& ppr_busfree == 0) {
|
|
/*
|
|
* Negotiation Rejected. Go-async and
|
|
* retry command.
|
|
*/
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MESSAGES) != 0)
|
|
printk("SDTR negotiation rejected busfree.\n");
|
|
#endif
|
|
ahd_set_syncrate(ahd, &devinfo,
|
|
/*period*/0, /*offset*/0,
|
|
/*ppr_options*/0,
|
|
AHD_TRANS_CUR|AHD_TRANS_GOAL,
|
|
/*paused*/TRUE);
|
|
if (scb != NULL) {
|
|
/*
|
|
* Remove any SCBs in the waiting for
|
|
* selection queue that may also be for
|
|
* this target so that command ordering
|
|
* is preserved.
|
|
*/
|
|
ahd_freeze_devq(ahd, scb);
|
|
ahd_qinfifo_requeue_tail(ahd, scb);
|
|
}
|
|
printerror = 0;
|
|
} else if ((ahd->msg_flags & MSG_FLAG_EXPECT_IDE_BUSFREE) != 0
|
|
&& ahd_sent_msg(ahd, AHDMSG_1B,
|
|
INITIATOR_ERROR, TRUE)) {
|
|
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MESSAGES) != 0)
|
|
printk("Expected IDE Busfree\n");
|
|
#endif
|
|
printerror = 0;
|
|
} else if ((ahd->msg_flags & MSG_FLAG_EXPECT_QASREJ_BUSFREE)
|
|
&& ahd_sent_msg(ahd, AHDMSG_1B,
|
|
MESSAGE_REJECT, TRUE)) {
|
|
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MESSAGES) != 0)
|
|
printk("Expected QAS Reject Busfree\n");
|
|
#endif
|
|
printerror = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The busfree required flag is honored at the end of
|
|
* the message phases. We check it last in case we
|
|
* had to send some other message that caused a busfree.
|
|
*/
|
|
if (scb != NULL && printerror != 0
|
|
&& (lastphase == P_MESGIN || lastphase == P_MESGOUT)
|
|
&& ((ahd->msg_flags & MSG_FLAG_EXPECT_PPR_BUSFREE) != 0)) {
|
|
|
|
ahd_freeze_devq(ahd, scb);
|
|
ahd_set_transaction_status(scb, CAM_REQUEUE_REQ);
|
|
ahd_freeze_scb(scb);
|
|
if ((ahd->msg_flags & MSG_FLAG_IU_REQ_CHANGED) != 0) {
|
|
ahd_abort_scbs(ahd, SCB_GET_TARGET(ahd, scb),
|
|
SCB_GET_CHANNEL(ahd, scb),
|
|
SCB_GET_LUN(scb), SCB_LIST_NULL,
|
|
ROLE_INITIATOR, CAM_REQ_ABORTED);
|
|
} else {
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MESSAGES) != 0)
|
|
printk("PPR Negotiation Busfree.\n");
|
|
#endif
|
|
ahd_done(ahd, scb);
|
|
}
|
|
printerror = 0;
|
|
}
|
|
if (printerror != 0) {
|
|
int aborted;
|
|
|
|
aborted = 0;
|
|
if (scb != NULL) {
|
|
u_int tag;
|
|
|
|
if ((scb->hscb->control & TAG_ENB) != 0)
|
|
tag = SCB_GET_TAG(scb);
|
|
else
|
|
tag = SCB_LIST_NULL;
|
|
ahd_print_path(ahd, scb);
|
|
aborted = ahd_abort_scbs(ahd, target, 'A',
|
|
SCB_GET_LUN(scb), tag,
|
|
ROLE_INITIATOR,
|
|
CAM_UNEXP_BUSFREE);
|
|
} else {
|
|
/*
|
|
* We had not fully identified this connection,
|
|
* so we cannot abort anything.
|
|
*/
|
|
printk("%s: ", ahd_name(ahd));
|
|
}
|
|
printk("Unexpected busfree %s, %d SCBs aborted, "
|
|
"PRGMCNT == 0x%x\n",
|
|
ahd_lookup_phase_entry(lastphase)->phasemsg,
|
|
aborted,
|
|
ahd_inw(ahd, PRGMCNT));
|
|
ahd_dump_card_state(ahd);
|
|
if (lastphase != P_BUSFREE)
|
|
ahd_force_renegotiation(ahd, &devinfo);
|
|
}
|
|
/* Always restart the sequencer. */
|
|
return (1);
|
|
}
|
|
|
|
static void
|
|
ahd_handle_proto_violation(struct ahd_softc *ahd)
|
|
{
|
|
struct ahd_devinfo devinfo;
|
|
struct scb *scb;
|
|
u_int scbid;
|
|
u_int seq_flags;
|
|
u_int curphase;
|
|
u_int lastphase;
|
|
int found;
|
|
|
|
ahd_fetch_devinfo(ahd, &devinfo);
|
|
scbid = ahd_get_scbptr(ahd);
|
|
scb = ahd_lookup_scb(ahd, scbid);
|
|
seq_flags = ahd_inb(ahd, SEQ_FLAGS);
|
|
curphase = ahd_inb(ahd, SCSISIGI) & PHASE_MASK;
|
|
lastphase = ahd_inb(ahd, LASTPHASE);
|
|
if ((seq_flags & NOT_IDENTIFIED) != 0) {
|
|
|
|
/*
|
|
* The reconnecting target either did not send an
|
|
* identify message, or did, but we didn't find an SCB
|
|
* to match.
|
|
*/
|
|
ahd_print_devinfo(ahd, &devinfo);
|
|
printk("Target did not send an IDENTIFY message. "
|
|
"LASTPHASE = 0x%x.\n", lastphase);
|
|
scb = NULL;
|
|
} else if (scb == NULL) {
|
|
/*
|
|
* We don't seem to have an SCB active for this
|
|
* transaction. Print an error and reset the bus.
|
|
*/
|
|
ahd_print_devinfo(ahd, &devinfo);
|
|
printk("No SCB found during protocol violation\n");
|
|
goto proto_violation_reset;
|
|
} else {
|
|
ahd_set_transaction_status(scb, CAM_SEQUENCE_FAIL);
|
|
if ((seq_flags & NO_CDB_SENT) != 0) {
|
|
ahd_print_path(ahd, scb);
|
|
printk("No or incomplete CDB sent to device.\n");
|
|
} else if ((ahd_inb_scbram(ahd, SCB_CONTROL)
|
|
& STATUS_RCVD) == 0) {
|
|
/*
|
|
* The target never bothered to provide status to
|
|
* us prior to completing the command. Since we don't
|
|
* know the disposition of this command, we must attempt
|
|
* to abort it. Assert ATN and prepare to send an abort
|
|
* message.
|
|
*/
|
|
ahd_print_path(ahd, scb);
|
|
printk("Completed command without status.\n");
|
|
} else {
|
|
ahd_print_path(ahd, scb);
|
|
printk("Unknown protocol violation.\n");
|
|
ahd_dump_card_state(ahd);
|
|
}
|
|
}
|
|
if ((lastphase & ~P_DATAIN_DT) == 0
|
|
|| lastphase == P_COMMAND) {
|
|
proto_violation_reset:
|
|
/*
|
|
* Target either went directly to data
|
|
* phase or didn't respond to our ATN.
|
|
* The only safe thing to do is to blow
|
|
* it away with a bus reset.
|
|
*/
|
|
found = ahd_reset_channel(ahd, 'A', TRUE);
|
|
printk("%s: Issued Channel %c Bus Reset. "
|
|
"%d SCBs aborted\n", ahd_name(ahd), 'A', found);
|
|
} else {
|
|
/*
|
|
* Leave the selection hardware off in case
|
|
* this abort attempt will affect yet to
|
|
* be sent commands.
|
|
*/
|
|
ahd_outb(ahd, SCSISEQ0,
|
|
ahd_inb(ahd, SCSISEQ0) & ~ENSELO);
|
|
ahd_assert_atn(ahd);
|
|
ahd_outb(ahd, MSG_OUT, HOST_MSG);
|
|
if (scb == NULL) {
|
|
ahd_print_devinfo(ahd, &devinfo);
|
|
ahd->msgout_buf[0] = ABORT_TASK;
|
|
ahd->msgout_len = 1;
|
|
ahd->msgout_index = 0;
|
|
ahd->msg_type = MSG_TYPE_INITIATOR_MSGOUT;
|
|
} else {
|
|
ahd_print_path(ahd, scb);
|
|
scb->flags |= SCB_ABORT;
|
|
}
|
|
printk("Protocol violation %s. Attempting to abort.\n",
|
|
ahd_lookup_phase_entry(curphase)->phasemsg);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Force renegotiation to occur the next time we initiate
|
|
* a command to the current device.
|
|
*/
|
|
static void
|
|
ahd_force_renegotiation(struct ahd_softc *ahd, struct ahd_devinfo *devinfo)
|
|
{
|
|
struct ahd_initiator_tinfo *targ_info;
|
|
struct ahd_tmode_tstate *tstate;
|
|
|
|
#ifdef AHD_DEBUG
|
|
if ((ahd_debug & AHD_SHOW_MESSAGES) != 0) {
|
|
ahd_print_devinfo(ahd, devinfo);
|
|
printk("Forcing renegotiation\n");
|
|
}
|
|
#endif
|
|
targ_info = ahd_fetch_transinfo(ahd,
|
|
devinfo->channel,
|
|
devinfo->our_scsiid,
|
|
devinfo->target,
|
|
&tstate);
|
|
ahd_update_neg_request(ahd, devinfo, tstate,
|
|
targ_info, AHD_NEG_IF_NON_ASYNC);
|
|
}
|
|
|
|
#define AHD_MAX_STEPS 2000
|
|
static void
|
|
ahd_clear_critical_section(struct ahd_softc *ahd)
|
|
{
|
|
ahd_mode_state saved_modes;
|
|
int stepping;
|
|
int steps;
|
|
int first_instr;
|
|
u_int simode0;
|
|
u_int simode1;
|
|
u_int simode3;
|
|
u_int lqimode0;
|
|
u_int lqimode1;
|
|
u_int lqomode0;
|
|
u_int lqomode1;
|
|
|
|
if (ahd->num_critical_sections == 0)
|
|
return;
|
|
|
|
stepping = FALSE;
|
|
steps = 0;
|
|
first_instr = 0;
|
|
simode0 = 0;
|
|
simode1 = 0;
|
|
simode3 = 0;
|
|
lqimode0 = 0;
|
|
lqimode1 = 0;
|
|
lqomode0 = 0;
|
|