| /* |
| * 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 $ |
| */ |
| |
| #ifdef __linux__ |
| #include "aic79xx_osm.h" |
| #include "aic79xx_inline.h" |
| #include "aicasm/aicasm_insformat.h" |
| #else |
| #include <dev/aic7xxx/aic79xx_osm.h> |
| #include <dev/aic7xxx/aic79xx_inline.h> |
| #include <dev/aic7xxx/aicasm/aicasm_insformat.h> |
| #endif |
| |
| |
| /***************************** Lookup Tables **********************************/ |
| static char *ahd_chip_names[] = |
| { |
| "NONE", |
| "aic7901", |
| "aic7902", |
| "aic7901A" |
| }; |
| static const u_int num_chip_names = ARRAY_SIZE(ahd_chip_names); |
| |
| /* |
| * Hardware error codes. |
| */ |
| struct ahd_hard_error_entry { |
| uint8_t errno; |
| char *errmesg; |
| }; |
| |
| static 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 struct ahd_phase_table_entry ahd_phase_table[] = |
| { |
| { P_DATAOUT, MSG_NOOP, "in Data-out phase" }, |
| { P_DATAIN, MSG_INITIATOR_DET_ERR, "in Data-in phase" }, |
| { P_DATAOUT_DT, MSG_NOOP, "in DT Data-out phase" }, |
| { P_DATAIN_DT, MSG_INITIATOR_DET_ERR, "in DT Data-in phase" }, |
| { P_COMMAND, MSG_NOOP, "in Command phase" }, |
| { P_MESGOUT, MSG_NOOP, "in Message-out phase" }, |
| { P_STATUS, MSG_INITIATOR_DET_ERR, "in Status phase" }, |
| { P_MESGIN, MSG_PARITY_ERROR, "in Message-in phase" }, |
| { P_BUSFREE, MSG_NOOP, "while idle" }, |
| { 0, MSG_NOOP, "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 ahd_callback_t ahd_stat_timer; |
| #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, |
| 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 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); |
| |
| /******************************** 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 __inline 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, MSG_NOOP); /* 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) |
| printf("%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) { |
| printf("%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 if 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) { |
| printf("%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) { |
| printf("%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) { |
| printf("%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. |
| */ |
| 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) { |
| printf("%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 *********************************/ |
| 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) |
| printf("%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; |
| uint32_t len; |
| |
| addr = ahd_le64toh(sg_list[i].addr); |
| len = ahd_le32toh(sg_list[i].len); |
| printf("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); |
| printf("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 */ |
| |
| 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) |
| printf("%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) |
| printf("%s: Assuming LQIPHASE_NLQ with " |
| "P0 assertion\n", ahd_name(ahd)); |
| #endif |
| } |
| #ifdef AHD_DEBUG |
| if ((ahd_debug & AHD_SHOW_RECOVERY) != 0) |
| printf("%s: Entering NONPACK\n", ahd_name(ahd)); |
| #endif |
| break; |
| } |
| case INVALID_SEQINT: |
| printf("%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 |
| printf("%s: ", ahd_name(ahd)); |
| printf("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); |
| printf("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; |
| printf("%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); |
| printf("%s: Issued Bus Reset.\n", ahd_name(ahd)); |
| break; |
| case P_COMMAND: |
| { |
| struct ahd_devinfo devinfo; |
| struct scb *scb; |
| struct ahd_initiator_tinfo *targ_info; |
| struct ahd_tmode_tstate *tstate; |
| struct ahd_transinfo *tinfo; |
| 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) { |
| printf("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); |
| targ_info = ahd_fetch_transinfo(ahd, |
| devinfo.channel, |
| devinfo.our_scsiid, |
| devinfo.target, |
| &tstate); |
| tinfo = &targ_info->curr; |
| 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); |
| printf("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) { |
| printf("%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] = MSG_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) { |
| printf("%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 |
| * transfered 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) { |
| printf("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); |
| |
| printf("%s:%c:%d: no active SCB for reconnecting " |
| "target - issuing BUS DEVICE RESET\n", |
| ahd_name(ahd), 'A', ahd_inb(ahd, SELID) >> 4); |
| printf("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)); |
| printf("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)); |
| printf("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)); |
| printf("SCSIBUS[0] == 0x%x, SCSISIGI == 0x%x\n", |
| ahd_inb(ahd, SCSIBUS), ahd_inb(ahd, SCSISIGI)); |
| printf("SXFRCTL0 == 0x%x\n", ahd_inb(ahd, SXFRCTL0)); |
| printf("SEQCTL0 == 0x%x\n", ahd_inb(ahd, SEQCTL0)); |
| ahd_dump_card_state(ahd); |
| ahd->msgout_buf[0] = MSG_BUS_DEV_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); |
| printf("%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); |
| printf("%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); |
| printf("data overrun detected %s. Tag == 0x%x.\n", |
| ahd_lookup_phase_entry(lastphase)->phasemsg, |
| SCB_GET_TAG(scb)); |
| ahd_print_path(ahd, scb); |
| printf("%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); |
| printf("%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); |
| printf("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); |
| 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; |
| 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); |
| printf("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: |
| printf("%s: Tracepoint %d\n", ahd_name(ahd), |
| seqintcode - TRACEPOINT0); |
| break; |
| case NO_SEQINT: |
| break; |
| case SAW_HWERR: |
| ahd_handle_hwerrint(ahd); |
| break; |
| default: |
| printf("%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); |
| } |
| |
| 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; |
| printf("%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) { |
| |
| printf("%s: SCSI offset overrun detected. Resetting bus.\n", |
| ahd_name(ahd)); |
| ahd_reset_channel(ahd, 'A', /*Initiate Reset*/TRUE); |
| } else if ((status & SCSIRSTI) != 0) { |
| |
| printf("%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) { |
| |
| printf("%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) { |
| u_int scbid; |
| |
| /* 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 sucessfull |
| * 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) { |
| printf("%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); |
| printf("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) { |
| printf("%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: |
| { |
| u_int scbid; |
| struct scb *scb; |
| |
| 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) { |
| printf("%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) |
| printf("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 { |
| printf("%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 lqistat2; |
| 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); |
| lqistat2 = 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) { |
| printf("%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 = MSG_INITIATOR_DET_ERR; |
| 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) { |
| printf("%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); |
| printf("\n"); |
| ahd_dump_card_state(ahd); |
| } |
| |
| if ((lqistat1 & (LQIOVERI_LQ|LQIOVERI_NLQ)) != 0) { |
| if (silent == FALSE) { |
| printf("%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); |
| printf("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) |
| printf("LQICRC_NLQ\n"); |
| if (scb == NULL) { |
| printf("%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) { |
| printf("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) |
| printf("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 MSG_NOP. |
| */ |
| ahd->send_msg_perror = msg_out; |
| if (scb != NULL && msg_out == MSG_INITIATOR_DET_ERR) |
| 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) { |
| printf("LQIRETRY for LQIPHASE_LQ\n"); |
| ahd_outb(ahd, LQCTL2, LQIRETRY); |
| } else if ((lqistat1 & LQIPHASE_NLQ) != 0) { |
| printf("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 { |
| printf("Reseting 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); |
| printf("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) |
| printf("%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); |
| printf("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); |
| } |
| printf("%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, MSG_ABORT_TAG, TRUE) |
| || ahd_sent_msg(ahd, AHDMSG_1B, MSG_ABORT, TRUE)) { |
| int found; |
| int sent_msg; |
| |
| if (scb == NULL) { |
| ahd_print_devinfo(ahd, &devinfo); |
| printf("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); |
| printf("SCB %d - Abort%s Completed.\n", |
| SCB_GET_TAG(scb), |
| sent_msg == MSG_ABORT_TAG ? "" : " Tag"); |
| |
| if (sent_msg == MSG_ABORT_TAG) |
| 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); |
| printf("found == 0x%x\n", found); |
| printerror = 0; |
| } else if (ahd_sent_msg(ahd, AHDMSG_1B, |
| MSG_BUS_DEV_RESET, TRUE)) { |
| #ifdef __FreeBSD__ |
| /* |
| * Don't mark the user's request for this BDR |
| * as completing with CAM_BDR_SENT. CAM3 |
| * specifies CAM_REQ_CMP. |
| */ |
| if (scb != NULL |
| && scb->io_ctx->ccb_h.func_code== XPT_RESET_DEV |
| && ahd_match_scb(ahd, scb, target, 'A', |
| CAM_LUN_WILDCARD, SCB_LIST_NULL, |
| ROLE_INITIATOR)) |
| ahd_set_transaction_status(scb, CAM_REQ_CMP); |
| #endif |
| 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, MSG_EXT_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) |
| printf("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; |
| /* |
| * 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, MSG_EXT_WDTR, FALSE) |
| && ppr_busfree == 0) { |
| /* |
| * Negotiation Rejected. Go-narrow and |
| * retry command. |
| */ |
| #ifdef AHD_DEBUG |
| if ((ahd_debug & AHD_SHOW_MESSAGES) != 0) |
| printf("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); |
| /* |
| * 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, MSG_EXT_SDTR, FALSE) |
| && ppr_busfree == 0) { |
| /* |
| * Negotiation Rejected. Go-async and |
| * retry command. |
| */ |
| #ifdef AHD_DEBUG |
| if ((ahd_debug & AHD_SHOW_MESSAGES) != 0) |
| printf("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); |
| /* |
| * 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, |
| MSG_INITIATOR_DET_ERR, TRUE)) { |
| |
| #ifdef AHD_DEBUG |
| if ((ahd_debug & AHD_SHOW_MESSAGES) != 0) |
| printf("Expected IDE Busfree\n"); |
| #endif |
| printerror = 0; |
| } else if ((ahd->msg_flags & MSG_FLAG_EXPECT_QASREJ_BUSFREE) |
| && ahd_sent_msg(ahd, AHDMSG_1B, |
| MSG_MESSAGE_REJECT, TRUE)) { |
| |
| #ifdef AHD_DEBUG |
| if ((ahd_debug & AHD_SHOW_MESSAGES) != 0) |
| printf("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 (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) |
| printf("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. |
| */ |
| printf("%s: ", ahd_name(ahd)); |
| } |
| printf("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); |
| printf("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); |
| printf("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); |
| printf("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); |
| printf("Completed command without status.\n"); |
| } else { |
| ahd_print_path(ahd, scb); |
| printf("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); |
| printf("%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] = MSG_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; |
| } |
| printf("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); |
| printf("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; |
| lqomode1 = 0; |
| saved_modes = ahd_save_modes(ahd); |
| for (;;) { |
| struct cs *cs; |
| u_int seqaddr; |
| u_int i; |
| |
| ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI); |
| seqaddr = ahd_inw(ahd, CURADDR); |
| |
| cs = ahd->critical_sections; |
| for (i = 0; i < ahd->num_critical_sections; i++, cs++) { |
| |
| if (cs->begin < seqaddr && cs->end >= seqaddr) |
| break; |
| } |
| |
| if (i == ahd->num_critical_sections) |
| break; |
| |
| if (steps > AHD_MAX_STEPS) { |
| printf("%s: Infinite loop in critical section\n" |
| "%s: First Instruction 0x%x now 0x%x\n", |
| ahd_name(ahd), ahd_name(ahd), first_instr, |
| seqaddr); |
| ahd_dump_card_state(ahd); |
| panic("critical section loop"); |
| } |
| |
| steps++; |
| #ifdef AHD_DEBUG |
| if ((ahd_debug & AHD_SHOW_MISC) != 0) |
| printf("%s: Single stepping at 0x%x\n", ahd_name(ahd), |
| seqaddr); |
| #endif |
| if (stepping == FALSE) { |
| |
| first_instr = seqaddr; |
| ahd_set_modes(ahd, AHD_MODE_CFG, AHD_MODE_CFG); |
| simode0 = ahd_inb(ahd, SIMODE0); |
| simode3 = ahd_inb(ahd, SIMODE3); |
| lqimode0 = ahd_inb(ahd, LQIMODE0); |
| lqimode1 = ahd_inb(ahd, LQIMODE1); |
| lqomode0 = ahd_inb(ahd, LQOMODE0); |
| lqomode1 = ahd_inb(ahd, LQOMODE1); |
| ahd_outb(ahd, SIMODE0, 0); |
| ahd_outb(ahd, SIMODE3, 0); |
| ahd_outb(ahd, LQIMODE0, 0); |
| ahd_outb(ahd, LQIMODE1, 0); |
| ahd_outb(ahd, LQOMODE0, 0); |
| ahd_outb(ahd, LQOMODE1, 0); |
| ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI); |
| simode1 = ahd_inb(ahd, SIMODE1); |
| /* |
| * We don't clear ENBUSFREE. Unfortunately |
| * we cannot re-enable busfree detection within |
| * the current connection, so we must leave it |
| * on while single stepping. |
| */ |
| ahd_outb(ahd, SIMODE1, simode1 & ENBUSFREE); |
| ahd_outb(ahd, SEQCTL0, ahd_inb(ahd, SEQCTL0) | STEP); |
| stepping = TRUE; |
| } |
| ahd_outb(ahd, CLRSINT1, CLRBUSFREE); |
| ahd_outb(ahd, CLRINT, CLRSCSIINT); |
| ahd_set_modes(ahd, ahd->saved_src_mode, ahd->saved_dst_mode); |
| ahd_outb(ahd, HCNTRL, ahd->unpause); |
| while (!ahd_is_paused(ahd)) |
| ahd_delay(200); |
| ahd_update_modes(ahd); |
| } |
| if (stepping) { |
| ahd_set_modes(ahd, AHD_MODE_CFG, AHD_MODE_CFG); |
| ahd_outb(ahd, SIMODE0, simode0); |
| ahd_outb(ahd, SIMODE3, simode3); |
| ahd_outb(ahd, LQIMODE0, lqimode0); |
| ahd_outb(ahd, LQIMODE1, lqimode1); |
| ahd_outb(ahd, LQOMODE0, lqomode0); |
| ahd_outb(ahd, LQOMODE1, lqomode1); |
| ahd_set_modes(ahd, AHD_MODE_SCSI, AHD_MODE_SCSI); |
| ahd_outb(ahd, SEQCTL0, ahd_inb(ahd, SEQCTL0) & ~STEP); |
| ahd_outb(ahd, SIMODE1, simode1); |
| /* |
| * SCSIINT seems to glitch occassionally when |
| * the interrupt masks are restored. Clear SCSIINT |
| * one more time so that only persistent errors |
| * are seen as a real interrupt. |
| */ |
| ahd_outb(ahd, CLRINT, CLRSCSIINT); |
| } |
| ahd_restore_modes(ahd, saved_modes); |
| } |
| |
| /* |
| * Clear any pending interrupt status. |
| */ |
| static void |
| ahd_clear_intstat(struct ahd_softc *ahd) |
| { |
| AHD_ASSERT_MODES(ahd, ~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK), |
| ~(AHD_MODE_UNKNOWN_MSK|AHD_MODE_CFG_MSK)); |
| /* Clear any interrupt conditions this may have caused */ |
| ahd_outb(ahd, CLRLQIINT0, CLRLQIATNQAS|CLRLQICRCT1|CLRLQICRCT2 |
| |CLRLQIBADLQT|CLRLQIATNLQ|CLRLQIATNCMD); |
| ahd_outb(ahd, CLRLQIINT1, CLRLQIPHASE_LQ|CLRLQIPHASE_NLQ|CLRLIQABORT |
| |CLRLQICRCI_LQ|CLRLQICRCI_NLQ|CLRLQIBADLQI |
| |CLRLQIOVERI_LQ|CLRLQIOVERI_NLQ|CLRNONPACKREQ); |
| ahd_outb(ahd, CLRLQOINT0, CLRLQOTARGSCBPERR|CLRLQOSTOPT2|CLRLQOATNLQ |
| |CLRLQOATNPKT|CLRLQOTCRC); |
| ahd_outb(ahd, CLRLQOINT1, CLRLQOINITSCBPERR|CLRLQOSTOPI2|CLRLQOBADQAS |
| |CLRLQOBUSFREE|CLRLQOPHACHGINPKT); |
| if ((ahd->bugs & AHD_CLRLQO_AUTOCLR_BUG) != 0) { |
| ahd_outb(ahd, CLRLQOINT0, 0); |
| ahd_outb(ahd, CLRLQOINT1, 0); |
| } |
| ahd_outb(ahd, CLRSINT3, CLRNTRAMPERR|CLROSRAMPERR); |
| ahd_outb(ahd, CLRSINT1, CLRSELTIMEO|CLRATNO|CLRSCSIRSTI |
| |CLRBUSFREE|CLRSCSIPERR|CLRREQINIT); |
| ahd_outb(ahd, CLRSINT0, CLRSELDO|CLRSELDI|CLRSELINGO |
| |CLRIOERR|CLROVERRUN); |
| ahd_outb(ahd, CLRINT, CLRSCSIINT); |
| } |
| |
| /**************************** Debugging Routines ******************************/ |
| #ifdef AHD_DEBUG |
| uint32_t ahd_debug = AHD_DEBUG_OPTS; |
| #endif |
| |
| #if 0 |
| void |
| ahd_print_scb(struct scb *scb) |
| { |
| struct hardware_scb *hscb; |
| int i; |
| |
| hscb = scb->hscb; |
| printf("scb:%p control:0x%x scsiid:0x%x lun:%d cdb_len:%d\n", |
| (void *)scb, |
| hscb->control, |
| hscb->scsiid, |
| hscb->lun, |
| hscb->cdb_len); |
| printf("Shared Data: "); |
| for (i = 0; i < sizeof(hscb->shared_data.idata.cdb); i++) |
| printf("%#02x", hscb->shared_data.idata.cdb[i]); |
| printf(" dataptr:%#x%x datacnt:%#x sgptr:%#x tag:%#x\n", |
| (uint32_t)((ahd_le64toh(hscb->dataptr) >> 32) & 0xFFFFFFFF), |
| (uint32_t)(ahd_le64toh(hscb->dataptr) & 0xFFFFFFFF), |
| ahd_le32toh(hscb->datacnt), |
| ahd_le32toh(hscb->sgptr), |
| SCB_GET_TAG(scb)); |
| ahd_dump_sglist(scb); |
| } |
| #endif /* 0 */ |
| |
| /************************* Transfer Negotiation *******************************/ |
| /* |
| * Allocate per target mode instance (ID we respond to as a target) |
| * transfer negotiation data structures. |
| */ |
| static struct ahd_tmode_tstate * |
| ahd_alloc_tstate(struct ahd_softc *ahd, u_int scsi_id, char channel) |
| { |
| struct ahd_tmode_tstate *master_tstate; |
| struct ahd_tmode_tstate *tstate; |
| int i; |
| |
| master_tstate = ahd->enabled_targets[ahd->our_id]; |
| if (ahd->enabled_targets[scsi_id] != NULL |
| && ahd->enabled_targets[scsi_id] != master_tstate) |
| panic("%s: ahd_alloc_tstate - Target already allocated", |
| ahd_name(ahd)); |
| tstate = malloc(sizeof(*tstate), M_DEVBUF, M_NOWAIT); |
| if (tstate == NULL) |
| return (NULL); |
| |
| /* |
| * If we have allocated a master tstate, copy user settings from |
| * the master tstate (taken from SRAM or the EEPROM) for this |
| * channel, but reset our current and goal settings to async/narrow |
| * until an initiator talks to us. |
| */ |
| if (master_tstate != NULL) { |
| memcpy(tstate, master_tstate, sizeof(*tstate)); |
| memset(tstate->enabled_luns, 0, sizeof(tstate->enabled_luns)); |
| for (i = 0; i < 16; i++) { |
| memset(&tstate->transinfo[i].curr, 0, |
| sizeof(tstate->transinfo[i].curr)); |
| memset(&tstate->transinfo[i].goal, 0, |
| sizeof(tstate->transinfo[i].goal)); |
| } |
| } else |
| memset(tstate, 0, sizeof(*tstate)); |
| ahd->enabled_targets[scsi_id] = tstate; |
| return (tstate); |
| } |
| |
| #ifdef AHD_TARGET_MODE |
| /* |
| * Free per target mode instance (ID we respond to as a target) |
| * transfer negotiation data structures. |
| */ |
| static void |
| ahd_free_tstate(struct ahd_softc *ahd, u_int scsi_id, char channel, int force) |
| { |
| struct ahd_tmode_tstate *tstate; |
| |
| /* |
| * Don't clean up our "master" tstate. |
| * It has our default user settings. |
| */ |
| if (scsi_id == ahd->our_id |
| && force == FALSE) |
| return; |
| |
| tstate = ahd->enabled_targets[scsi_id]; |
| if (tstate != NULL) |
| free(tstate, M_DEVBUF); |
| ahd->enabled_targets[scsi_id] = NULL; |
| } |
| #endif |
| |
| /* |
| * Called when we have an active connection to a target on the bus, |
| * this function finds the nearest period to the input period limited |
| * by the capabilities of the bus connectivity of and sync settings for |
| * the target. |
| */ |
| void |
| ahd_devlimited_syncrate(struct ahd_softc *ahd, |
| struct ahd_initiator_tinfo *tinfo, |
| u_int *period, u_int *ppr_options, role_t role) |
| { |
| struct ahd_transinfo *transinfo; |
| u_int maxsync; |
| |
| if ((ahd_inb(ahd, SBLKCTL) & ENAB40) != 0 |
| && (ahd_inb(ahd, SSTAT2) & EXP_ACTIVE) == 0) { |
| maxsync = AHD_SYNCRATE_PACED; |
| } else { |
| maxsync = AHD_SYNCRATE_ULTRA; |
| /* Can't do DT related options on an SE bus */ |
| *ppr_options &= MSG_EXT_PPR_QAS_REQ; |
| } |
| /* |
| * Never allow a value higher than our current goal |
| * period otherwise we may allow a target initiated |
| * negotiation to go above the limit as set by the |
| * user. In the case of an initiator initiated |
| * sync negotiation, we limit based on the user |
| * setting. This allows the system to still accept |
| * incoming negotiations even if target initiated |
| * negotiation is not performed. |
| */ |
| if (role == ROLE_TARGET) |
| transinfo = &tinfo->user; |
| else |
| transinfo = &tinfo->goal; |
| *ppr_options &= (transinfo->ppr_options|MSG_EXT_PPR_PCOMP_EN); |
| if (transinfo->width == MSG_EXT_WDTR_BUS_8_BIT) { |
| maxsync = max(maxsync, (u_int)AHD_SYNCRATE_ULTRA2); |
| *ppr_options &= ~MSG_EXT_PPR_DT_REQ; |
| } |
| if (transinfo->period == 0) { |
| *period = 0; |
| *ppr_options = 0; |
| } else { |
| *period = max(*period, (u_int)transinfo->period); |
| ahd_find_syncrate(ahd, period, ppr_options, maxsync); |
| } |
| } |
| |
| /* |
| * Look up the valid period to SCSIRATE conversion in our table. |
| * Return the period and offset that should be sent to the target |
| * if this was the beginning of an SDTR. |
| */ |
| void |
| ahd_find_syncrate(struct ahd_softc *ahd, u_int *period, |
| u_int *ppr_options, u_int maxsync) |
| { |
| if (*period < maxsync) |
| *period = maxsync; |
| |
| if ((*ppr_options & MSG_EXT_PPR_DT_REQ) != 0 |
| && *period > AHD_SYNCRATE_MIN_DT) |
| *ppr_options &= ~MSG_EXT_PPR_DT_REQ; |
| |
| if (*period > AHD_SYNCRATE_MIN) |
| *period = 0; |
| |
| /* Honor PPR option conformance rules. */ |
| if (*period > AHD_SYNCRATE_PACED) |
| *ppr_options &= ~MSG_EXT_PPR_RTI; |
| |
| if ((*ppr_options & MSG_EXT_PPR_IU_REQ) == 0) |
| *ppr_options &= (MSG_EXT_PPR_DT_REQ|MSG_EXT_PPR_QAS_REQ); |
| |
| if ((*ppr_options & MSG_EXT_PPR_DT_REQ) == 0) |
| *ppr_options &= MSG_EXT_PPR_QAS_REQ; |
| |
| /* Skip all PACED only entries if IU is not available */ |
| if ((*ppr_options & MSG_EXT_PPR_IU_REQ) == 0 |
| && *period < AHD_SYNCRATE_DT) |
| *period = AHD_SYNCRATE_DT; |
| |
| /* Skip all DT only entries if DT is not available */ |
| if ((*ppr_options & MSG_EXT_PPR_DT_REQ) == 0 |
| && *period < AHD_SYNCRATE_ULTRA2) |
| *period = AHD_SYNCRATE_ULTRA2; |
| } |
| |
| /* |
| * Truncate the given synchronous offset to a value the |
| * current adapter type and syncrate are capable of. |
| */ |
| static void |
| ahd_validate_offset(struct ahd_softc *ahd, |
| struct ahd_initiator_tinfo *tinfo, |
| u_int period, u_int *offset, int wide, |
| role_t role) |
| { |
| u_int maxoffset; |
| |
| /* Limit offset to what we can do */ |
| if (period == 0) |
| maxoffset = 0; |
| else if (period <= AHD_SYNCRATE_PACED) { |
| if ((ahd->bugs & AHD_PACED_NEGTABLE_BUG) != 0) |
| maxoffset = MAX_OFFSET_PACED_BUG; |
| else |
| maxoffset = MAX_OFFSET_PACED; |
| } else |
| maxoffset = MAX_OFFSET_NON_PACED; |
| *offset = min(*offset, maxoffset); |
| if (tinfo != NULL) { |
| if (role == ROLE_TARGET) |
| *offset = min(*offset, (u_int)tinfo->user.offset); |
| else |
| *offset = min(*offset, (u_int)tinfo->goal.offset); |
| } |
| } |
| |
| /* |
| * Truncate the given transfer width parameter to a value the |
| * current adapter type is capable of. |
| */ |
| static void |
| ahd_validate_width(struct ahd_softc *ahd, struct ahd_initiator_tinfo *tinfo, |
| u_int *bus_width, role_t role) |
| { |
| switch (*bus_width) { |
| default: |
| if (ahd->features & AHD_WIDE) { |
| /* Respond Wide */ |
| *bus_width = MSG_EXT_WDTR_BUS_16_BIT; |
| break; |
| } |
| /* FALLTHROUGH */ |
| case MSG_EXT_WDTR_BUS_8_BIT: |
| *bus_width = MSG_EXT_WDTR_BUS_8_BIT; |
| break; |
| } |
| if (tinfo != NULL) { |
| if (role == ROLE_TARGET) |
| *bus_width = min((u_int)tinfo->user.width, *bus_width); |
| else |
| *bus_width = min((u_int)tinfo->goal.width, *bus_width); |
| } |
| } |
| |
| /* |
| * Update the bitmask of targets for which the controller should |
| * negotiate with at the next convenient oportunity. This currently |
| * means the next time we send the initial identify messages for |
| * a new transaction. |
| */ |
| int |
| ahd_update_neg_request(struct ahd_softc *ahd, struct ahd_devinfo *devinfo, |
| struct ahd_tmode_tstate *tstate, |
| struct ahd_initiator_tinfo *tinfo, ahd_neg_type neg_type) |
| { |
| u_int auto_negotiate_orig; |
| |
| auto_negotiate_orig = tstate->auto_negotiate; |
| if (neg_type == AHD_NEG_ALWAYS) { |
| /* |
| * Force our "current" settings to be |
| * unknown so that unless a bus reset |
| * occurs the need to renegotiate is |
| * recorded persistently. |
| */ |
| if ((ahd->features & AHD_WIDE) != 0) |
| tinfo->curr.width = AHD_WIDTH_UNKNOWN; |
| tinfo->curr.period = AHD_PERIOD_UNKNOWN; |
| tinfo->curr.offset = AHD_OFFSET_UNKNOWN; |
| } |
| if (tinfo->curr.period != tinfo->goal.period |
| || tinfo->curr.width != tinfo->goal.width |
| || tinfo->curr.offset != tinfo->goal.offset |
| || tinfo->curr.ppr_options != tinfo->goal. |