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