diff options
Diffstat (limited to 'sys/dev/ic/aic7xxx.c')
-rw-r--r-- | sys/dev/ic/aic7xxx.c | 7603 |
1 files changed, 4918 insertions, 2685 deletions
diff --git a/sys/dev/ic/aic7xxx.c b/sys/dev/ic/aic7xxx.c index 33ef8990af7..782a7a2e61d 100644 --- a/sys/dev/ic/aic7xxx.c +++ b/sys/dev/ic/aic7xxx.c @@ -1,27 +1,25 @@ -/* $OpenBSD: aic7xxx.c,v 1.18 1999/01/11 05:12:15 millert Exp $ */ -/* $NetBSD: aic7xxx.c,v 1.17 1996/10/21 22:34:04 thorpej Exp $ */ - /* * Generic driver for the aic7xxx based adaptec SCSI controllers * Product specific probe and attach routines can be found in: - * i386/eisa/aic7770.c 27/284X and aic7770 motherboard controllers - * pci/aic7870.c 3940, 2940, aic7880, aic7870 and aic7850 controllers + * i386/eisa/ahc_eisa.c 27/284X and aic7770 motherboard controllers + * pci/ahc_pci.c 3985, 3980, 3940, 2940, aic7895, aic7890, + * aic7880, aic7870, aic7860, and aic7850 controllers * - * Copyright (c) 1994, 1995, 1996 Justin T. Gibbs. + * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000 Justin T. Gibbs. * 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 immediately at the beginning of the file, without modification, - * this list of conditions, and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not 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 + * the GNU Public License ("GPL"). + * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE @@ -34,95 +32,68 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * from Id: aic7xxx.c,v 1.75 1996/06/23 20:02:37 gibbs Exp + * $FreeBSD: src/sys/dev/aic7xxx/aic7xxx.c,v 1.40 2000/01/07 23:08:17 gibbs Exp $ + * $OpenBSD: aic7xxx.c,v 1.19 2000/03/22 02:48:47 smurph Exp $ */ /* - * TODO: - * Implement Target Mode - * - * A few notes on how SCB paging works... + * A few notes on features of the driver. * * SCB paging takes advantage of the fact that devices stay disconnected * from the bus a relatively long time and that while they're disconnected, - * having the SCBs for that device down on the host adapter is of little use. - * Instead we copy the SCB back up into kernel memory and reuse the SCB slot - * on the card to schedule another transaction. This can be a real payoff - * when doing random I/O to tagged queueing devices since there are more - * transactions active at once for the device to sort for optimal seek - * reduction. The algorithm goes like this... - * - * At the sequencer level: - * 1) Disconnected SCBs are threaded onto a doubly linked list, headed by - * DISCONNECTED_SCBH using the SCB_NEXT and SCB_PREV fields. The most - * recently disconnected device is always at the head. - * - * 2) The SCB has an added field SCB_TAG that corresponds to the kernel - * SCB number (ie 0-254). - * - * 3) When a command is queued, the hardware index of the SCB it was downloaded - * into is placed into the QINFIFO for easy indexing by the sequencer. - * - * 4) The tag field is used as the tag for tagged-queueing, for determining - * the related kernel SCB, and is the value put into the QOUTFIFO - * so the kernel doesn't have to upload the SCB to determine the kernel SCB - * that completed on command completes. + * having the SCBs for these transactions down on the host adapter is of + * little use. Instead of leaving this idle SCB down on the card we copy + * it back up into kernel memory and reuse the SCB slot on the card to + * schedule another transaction. This can be a real payoff when doing random + * I/O to tagged queueing devices since there are more transactions active at + * once for the device to sort for optimal seek reduction. The algorithm goes + * like this... * - * 5) When a reconnect occurs, the sequencer must scan the SCB array (even - * in the tag case) looking for the appropriate SCB and if it can't find - * it, it interrupts the kernel so it can page the SCB in. + * The sequencer maintains two lists of its hardware SCBs. The first is the + * singly linked free list which tracks all SCBs that are not currently in + * use. The second is the doubly linked disconnected list which holds the + * SCBs of transactions that are in the disconnected state sorted most + * recently disconnected first. When the kernel queues a transaction to + * the card, a hardware SCB to "house" this transaction is retrieved from + * either of these two lists. If the SCB came from the disconnected list, + * a check is made to see if any data transfer or SCB linking (more on linking + * in a bit) information has been changed since it was copied from the host + * and if so, DMAs the SCB back up before it can be used. Once a hardware + * SCB has been obtained, the SCB is DMAed from the host. Before any work + * can begin on this SCB, the sequencer must ensure that either the SCB is + * for a tagged transaction or the target is not already working on another + * non-tagged transaction. If a conflict arises in the non-tagged case, the + * sequencer finds the SCB for the active transactions and sets the SCB_LINKED + * field in that SCB to this next SCB to execute. To facilitate finding + * active non-tagged SCBs, the last four bytes of up to the first four hardware + * SCBs serve as a storage area for the currently active SCB ID for each + * target. * - * 6) If the sequencer is successful in finding the SCB, it removes it from - * the doubly linked list of disconnected SCBS. + * When a device reconnects, a search is made of the hardware SCBs to find + * the SCB for this transaction. If the search fails, a hardware SCB is + * pulled from either the free or disconnected SCB list and the proper + * SCB is DMAed from the host. If the MK_MESSAGE control bit is set + * in the control byte of the SCB while it was disconnected, the sequencer + * will assert ATN and attempt to issue a message to the host. * - * At the kernel level: - * 1) There are four queues that a kernel SCB may reside on: - * free_scbs - SCBs that are not in use and have a hardware slot assigned - * to them. - * page_scbs - SCBs that are not in use and need to have a hardware slot - * assigned to them (i.e. they will most likely cause a page - * out event). - * waiting_scbs - SCBs that are active, don't have an assigned hardware - * slot assigned to them and are waiting for either a - * disconnection or a command complete to free up a slot. - * assigned_scbs - SCBs that were in the waiting_scbs queue, but were - * assigned a slot by ahc_free_scb. + * When a command completes, a check for non-zero status and residuals is + * made. If either of these conditions exists, the SCB is DMAed back up to + * the host so that it can interpret this information. Additionally, in the + * case of bad status, the sequencer generates a special interrupt and pauses + * itself. This allows the host to setup a request sense command if it + * chooses for this target synchronously with the error so that sense + * information isn't lost. * - * 2) When a new request comes in, an SCB is allocated from the free_scbs or - * page_scbs queue with preference to SCBs on the free_scbs queue. - * - * 3) If there are no free slots (we retrieved the SCB off of the page_scbs - * queue), the SCB is inserted onto the tail of the waiting_scbs list and - * we attempt to run this queue down. - * - * 4) ahc_run_waiting_queues() looks at both the assigned_scbs and waiting_scbs - * queues. In the case of the assigned_scbs, the commands are immediately - * downloaded and started. For waiting_scbs, we page in all that we can - * ensuring we don't create a resource deadlock (see comments in - * ahc_run_waiting_queues()). - * - * 5) After we handle a bunch of command completes, we also try running the - * queues since many SCBs may have disconnected since the last command - * was started and we have at least one free slot on the card. - * - * 6) ahc_free_scb looks at the waiting_scbs queue for a transaction - * requiring a slot and moves it to the assigned_scbs queue if it - * finds one. Otherwise it puts the current SCB onto the free_scbs - * queue for later use. - * - * 7) The driver handles page-in requests from the sequencer in response to - * the NO_MATCH sequencer interrupt. For tagged commands, the appropriate - * SCB is easily found since the tag is a direct index into our kernel SCB - * array. For non-tagged commands, we keep a separate array of 16 pointers - * that point to the single possible SCB that was paged out for that target. */ #include <sys/param.h> #include <sys/systm.h> -#if defined(__NetBSD__) || defined(__OpenBSD__) #include <sys/device.h> #include <machine/bus.h> #include <machine/intr.h> -#endif /* defined(__NetBSD__) || defined(__OpenBSD__) */ + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcidevs.h> #include <sys/malloc.h> #include <sys/buf.h> @@ -130,495 +101,1372 @@ #include <scsi/scsi_all.h> #include <scsi/scsi_message.h> -#if defined(__NetBSD__) || defined(__OpenBSD__) #include <scsi/scsi_debug.h> -#endif #include <scsi/scsiconf.h> -#if defined(__FreeBSD__) -#include <machine/clock.h> -#endif - #include <vm/vm.h> #include <vm/vm_param.h> #include <vm/pmap.h> -#if defined(__FreeBSD__) -#include <i386/scsi/aic7xxx.h> - -#include <dev/aic7xxx/aic7xxx_reg.h> -#endif /* defined(__FreeBSD__) */ - -#if defined(__NetBSD__) || defined(__OpenBSD__) #include <dev/ic/aic7xxxreg.h> #include <dev/ic/aic7xxxvar.h> +#include <dev/microcode/aic7xxx/aic7xxx_seq.h> +#include <dev/microcode/aic7xxx/sequencer.h> +#include "pci.h" -#define bootverbose 1 +/* + * Some ISA devices (e.g. on a VLB) can perform 32-bit DMA. This + * flag is passed to bus_dmamap_create() to indicate that fact. + */ +#ifndef ISABUS_DMA_32BIT +#define ISABUS_DMA_32BIT BUS_DMA_BUS1 +#endif -#define DEBUGTARG DEBUGTARGET -#if DEBUGTARG < 0 /* Negative numbers for disabling cause warnings */ -#undef DEBUGTARG -#define DEBUGTARG 17 +#ifndef AHC_TMODE_ENABLE +#define AHC_TMODE_ENABLE 0 #endif -#ifdef alpha /* XXX */ -/* XXX XXX NEED REAL DMA MAPPING SUPPORT XXX XXX */ -extern vm_offset_t alpha_XXX_dmamap(vm_offset_t); -#undef vtophys -#define vtophys(va) alpha_XXX_dmamap((vm_offset_t) va) -#endif /* alpha */ -#endif /* defined(__NetBSD__) || defined(__OpenBSD__) */ - #include <sys/kernel.h> -#define KVTOPHYS(x) vtophys(x) +#define offsetof(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) -#define MIN(a,b) ((a < b) ? a : b) -#define ALL_TARGETS -1 +#define IS_SCSIBUS_B(ahc, sc_link) \ + ((sc_link)->scsibus == (ahc)->sc_link_b.scsibus) +#define ALL_CHANNELS '\0' +#define ALL_TARGETS_MASK 0xFFFF +#define INITIATOR_WILDCARD (~0) -#if defined(__FreeBSD__) -u_long ahc_unit = 0; +#define SIM_IS_SCSIBUS_B(ahc, sc_link) \ + ((sc_link)->scsibus == (ahc)->sc_link_b.scsibus) +#define SIM_CHANNEL(ahc, sc_link) \ + (SIM_IS_SCSIBUS_B(ahc, sc_link) ? 'B' : 'A') +#define SIM_SCSI_ID(ahc, sc_link) \ + (SIM_IS_SCSIBUS_B(ahc, sc_link) ? ahc->our_id_b : ahc->our_id) +#define SCB_IS_SCSIBUS_B(scb) \ + (((scb)->hscb->tcl & SELBUSB) != 0) +#define SCB_TARGET(scb) \ + (((scb)->hscb->tcl & TID) >> 4) +#define SCB_CHANNEL(scb) \ + (SCB_IS_SCSIBUS_B(scb) ? 'B' : 'A') +#define SCB_LUN(scb) \ + ((scb)->hscb->tcl & LID) +#define SCB_TARGET_OFFSET(scb) \ + (SCB_TARGET(scb) + (SCB_IS_SCSIBUS_B(scb) ? 8 : 0)) +#define SCB_TARGET_MASK(scb) \ + (0x01 << (SCB_TARGET_OFFSET(scb))) +#define TCL_CHANNEL(ahc, tcl) \ + ((((ahc)->features & AHC_TWIN) && ((tcl) & SELBUSB)) ? 'B' : 'A') +#define TCL_SCSI_ID(ahc, tcl) \ + (TCL_CHANNEL((ahc), (tcl)) == 'B' ? (ahc)->our_id_b : (ahc)->our_id) +#define TCL_TARGET(tcl) (((tcl) & TID) >> TCL_TARGET_SHIFT) +#define TCL_LUN(tcl) ((tcl) & LID) + +#define XS_TCL(ahc, xs) \ + ((((xs)->sc_link->target << 4) & 0xF0) \ + | (SIM_IS_SCSIBUS_B((ahc), (xs)->sc_link) ? SELBUSB : 0) \ + | ((xs)->sc_link->lun & 0x07)) + +/* + * Under normal circumstances, these messages are unnecessary + * and not terribly cosmetic. + */ +#ifdef DEBUG +#define bootverbose 1 +#define STATIC +#define INLINE +#else +#define bootverbose 0 +#define STATIC static +#define INLINE __inline #endif +typedef enum { + ROLE_UNKNOWN, + ROLE_INITIATOR, + ROLE_TARGET +} role_t; + +struct ahc_devinfo { + int our_scsiid; + int target_offset; + u_int16_t target_mask; + u_int8_t target; + u_int8_t lun; + char channel; + role_t role; /* + * Only guaranteed to be correct if not + * in the busfree state. + */ +}; + +typedef enum { + SEARCH_COMPLETE, + SEARCH_COUNT, + SEARCH_REMOVE +} ahc_search_action; + #ifdef AHC_DEBUG static int ahc_debug = AHC_DEBUG; #endif -#ifdef AHC_BROKEN_CACHE -int ahc_broken_cache = 1; - -/* - * "wbinvd" cause writing back whole cache (both CPU internal & external) - * to memory, so that the instruction takes a lot of time. - * This makes machine slow. - */ -#define INVALIDATE_CACHE() __asm __volatile("wbinvd") +#if NPCI > 0 +void ahc_pci_intr(struct ahc_softc *ahc); #endif -/**** bit definitions for SCSIDEF ****/ -#define HSCSIID 0x07 /* our SCSI ID */ -#define HWSCSIID 0x0f /* our SCSI ID if Wide Bus */ +STATIC int ahcinitscbdata(struct ahc_softc *ahc); +STATIC void ahcfiniscbdata(struct ahc_softc *ahc); + +STATIC int ahc_poll __P((struct ahc_softc *ahc, int wait)); +STATIC void ahc_shutdown __P((void *arg)); +STATIC int ahc_execute_scb __P((void *arg, bus_dma_segment_t *dm_segs, + int nsegments)); +STATIC int ahc_setup_data __P((struct ahc_softc *ahc, + struct scsi_xfer *xs, struct scb *scb)); +STATIC void ahc_freeze_devq __P((struct ahc_softc *ahc, + struct scsi_link *sc_link)); +STATIC void ahcallocscbs __P((struct ahc_softc *ahc)); +STATIC void ahc_fetch_devinfo __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo)); +STATIC void ahc_compile_devinfo __P((struct ahc_devinfo *devinfo, + u_int our_id, u_int target, + u_int lun, char channel, + role_t role)); +STATIC u_int ahc_abort_wscb __P((struct ahc_softc *ahc, + u_int scbpos, u_int prev)); +STATIC void ahc_done __P((struct ahc_softc *ahc, struct scb *scbp)); +STATIC struct tmode_tstate * + ahc_alloc_tstate __P((struct ahc_softc *ahc, u_int scsi_id, + char channel)); +STATIC void ahc_handle_seqint __P((struct ahc_softc *ahc, u_int intstat)); +STATIC void ahc_handle_scsiint __P((struct ahc_softc *ahc, u_int intstat)); +STATIC void ahc_build_transfer_msg __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo)); +STATIC void ahc_setup_initiator_msgout __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo, + struct scb *scb)); +STATIC void ahc_setup_target_msgin __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo)); +STATIC int ahc_handle_msg_reject __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo)); +STATIC void ahc_clear_msg_state __P((struct ahc_softc *ahc)); +STATIC void ahc_handle_message_phase __P((struct ahc_softc *ahc, + struct scsi_link *sc_link)); +STATIC int ahc_sent_msg __P((struct ahc_softc *ahc, u_int msgtype, + int full)); + +typedef enum { + MSGLOOP_IN_PROG, + MSGLOOP_MSGCOMPLETE, + MSGLOOP_TERMINATED +} msg_loop_stat; + +STATIC int ahc_parse_msg __P((struct ahc_softc *ahc, + struct scsi_link *sc_link, + struct ahc_devinfo *devinfo)); +STATIC void ahc_handle_ign_wide_residue __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo)); +STATIC void ahc_handle_devreset __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo, + int status, char *message, + int verbose_level)); +#ifdef AHC_DUMP_SEQ +STATIC void ahc_dumpseq __P((struct ahc_softc *ahc)); +#endif +STATIC void ahc_loadseq __P((struct ahc_softc *ahc)); +STATIC int ahc_check_patch __P((struct ahc_softc *ahc, + struct patch **start_patch, + int start_instr, int *skip_addr)); +STATIC void ahc_download_instr __P((struct ahc_softc *ahc, + int instrptr, u_int8_t *dconsts)); +STATIC int ahc_match_scb __P((struct scb *scb, int target, char channel, + int lun, u_int tag, role_t role)); +#ifdef AHC_DEBUG +STATIC void ahc_print_scb __P((struct scb *scb)); +#endif +STATIC int ahc_search_qinfifo __P((struct ahc_softc *ahc, int target, + char channel, int lun, u_int tag, + role_t role, u_int32_t status, + ahc_search_action action)); +STATIC int ahc_reset_channel __P((struct ahc_softc *ahc, char channel, + int initiate_reset)); +STATIC int ahc_abort_scbs __P((struct ahc_softc *ahc, int target, + char channel, int lun, u_int tag, + role_t role, u_int32_t status)); +STATIC int ahc_search_disc_list __P((struct ahc_softc *ahc, int target, + char channel, int lun, u_int tag, + int stop_on_first, int remove, + int save_state)); +STATIC u_int ahc_rem_scb_from_disc_list __P((struct ahc_softc *ahc, + u_int prev, u_int scbptr)); +STATIC void ahc_add_curscb_to_free_list __P((struct ahc_softc *ahc)); +STATIC void ahc_clear_intstat __P((struct ahc_softc *ahc)); +STATIC void ahc_reset_current_bus __P((struct ahc_softc *ahc)); +STATIC struct ahc_syncrate * + ahc_devlimited_syncrate __P((struct ahc_softc *ahc, u_int *period)); +STATIC struct ahc_syncrate * + ahc_find_syncrate __P((struct ahc_softc *ahc, u_int *period, + u_int maxsync)); +STATIC u_int ahc_find_period __P((struct ahc_softc *ahc, u_int scsirate, + u_int maxsync)); +STATIC void ahc_validate_offset __P((struct ahc_softc *ahc, + struct ahc_syncrate *syncrate, + u_int *offset, int wide)); +STATIC void ahc_update_target_msg_request __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo, + struct ahc_initiator_tinfo *tinfo, + int force, int paused)); +STATIC void ahc_set_syncrate __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo, + struct ahc_syncrate *syncrate, + u_int period, u_int offset, + u_int type, int paused, int done)); +STATIC void ahc_set_width __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo, + u_int width, u_int type, int paused, int done)); +STATIC void ahc_set_tags __P((struct ahc_softc *ahc, + struct ahc_devinfo *devinfo,int enable)); +STATIC void ahc_construct_sdtr __P((struct ahc_softc *ahc, + u_int period, u_int offset)); +STATIC void ahc_construct_wdtr __P((struct ahc_softc *ahc, u_int bus_width)); + +STATIC void ahc_calc_residual __P((struct scb *scb)); + +STATIC void ahc_update_pending_syncrates __P((struct ahc_softc *ahc)); + +STATIC void ahc_set_recoveryscb __P((struct ahc_softc *ahc, + struct scb *scb)); +STATIC void ahc_timeout __P((void *)); + +static __inline int sequencer_paused __P((struct ahc_softc *ahc)); +static __inline void pause_sequencer __P((struct ahc_softc *ahc)); +static __inline void unpause_sequencer __P((struct ahc_softc *ahc)); +STATIC void restart_sequencer __P((struct ahc_softc *ahc)); +static __inline u_int ahc_index_busy_tcl __P((struct ahc_softc *ahc, + u_int tcl, int unbusy)); + +static __inline void ahc_busy_tcl __P((struct ahc_softc *ahc, + struct scb *scb)); +static __inline int ahc_isbusy_tcl __P((struct ahc_softc *ahc, + struct scb *scb)); +static __inline void ahc_freeze_ccb __P((struct scb* scb)); +static __inline void ahcsetccbstatus __P((struct scsi_xfer *xs, int status)); +STATIC void ahc_run_qoutfifo __P((struct ahc_softc *ahc)); + +static __inline struct ahc_initiator_tinfo * + ahc_fetch_transinfo __P((struct ahc_softc *ahc, char channel, + u_int our_id, u_int target, + struct tmode_tstate **tstate)); +STATIC void ahcfreescb __P((struct ahc_softc *ahc, struct scb *scb)); +static __inline struct scb *ahcgetscb __P((struct ahc_softc *ahc)); +int ahc_createdmamem __P((struct ahc_softc *ahc, int size, + bus_dmamap_t *mapp, caddr_t *vaddr, + bus_addr_t *baddr, bus_dma_segment_t *segs, + int *nseg, const char *what)); +STATIC void ahc_freedmamem __P((bus_dma_tag_t tag, int size, + bus_dmamap_t map, caddr_t vaddr, + bus_dma_segment_t *seg, int nseg)); +STATIC void ahcminphys __P((struct buf *bp)); + +STATIC INLINE struct scsi_xfer *ahc_first_xs __P((struct ahc_softc *)); +STATIC INLINE void ahc_list_insert_before __P((struct ahc_softc *ahc, + struct scsi_xfer *xs, + struct scsi_xfer *next_xs)); +STATIC INLINE void ahc_list_insert_head __P((struct ahc_softc *ahc, + struct scsi_xfer *xs)); +STATIC INLINE void ahc_list_insert_tail __P((struct ahc_softc *ahc, + struct scsi_xfer *xs)); +STATIC INLINE void ahc_list_remove __P((struct ahc_softc *ahc, + struct scsi_xfer *xs)); +STATIC INLINE struct scsi_xfer *ahc_list_next __P((struct ahc_softc *ahc, + struct scsi_xfer *xs)); + +STATIC int32_t ahc_scsi_cmd __P((struct scsi_xfer *xs)); -static void ahcminphys __P((struct buf *bp)); -static int32_t ahc_scsi_cmd __P((struct scsi_xfer *xs)); -static inline void pause_sequencer __P((struct ahc_data *ahc)); -static inline void unpause_sequencer __P((struct ahc_data *ahc, - int unpause_always)); -static inline void restart_sequencer __P((struct ahc_data *ahc)); +struct cfdriver ahc_cd = { + NULL, "ahc", DV_DULL +}; static struct scsi_adapter ahc_switch = { - ahc_scsi_cmd, - ahcminphys, - 0, - 0, -#if defined(__FreeBSD__) - 0, - "ahc", - { 0, 0 } -#endif + ahc_scsi_cmd, + ahcminphys, + 0, + 0, }; /* the below structure is so we have a default dev struct for our link struct */ static struct scsi_device ahc_dev = { - NULL, /* Use default error handler */ - NULL, /* have a queue, served by this */ - NULL, /* have no async handler */ - NULL, /* Use default 'done' routine */ -#if defined(__FreeBSD__) - "ahc", - 0, - { 0, 0 } -#endif + NULL, /* Use default error handler */ + NULL, /* have a queue, served by this */ + NULL, /* have no async handler */ + NULL, /* Use default 'done' routine */ }; -static inline void +STATIC void +ahcminphys(bp) + struct buf *bp; +{ +/* + * Even though the card can transfer up to 16megs per command + * we are limited by the number of segments in the dma segment + * list that we can hold. The worst case is that all pages are + * discontinuous physically, hense the "page per segment" limit + * enforced here. + */ + if (bp->b_bcount > ((AHC_NSEG - 1) * PAGE_SIZE)) { + bp->b_bcount = ((AHC_NSEG - 1) * PAGE_SIZE); + } + minphys(bp); +} + + +static __inline u_int32_t +ahc_hscb_busaddr(struct ahc_softc *ahc, u_int index) +{ + return (ahc->scb_data->hscb_busaddr + + (sizeof(struct hardware_scb) * index)); +} + +#define AHC_BUSRESET_DELAY 25 /* Reset delay in us */ + +static __inline int +sequencer_paused(ahc) + struct ahc_softc *ahc; +{ + return ((ahc_inb(ahc, HCNTRL) & PAUSE) != 0); +} + +static __inline void pause_sequencer(ahc) - struct ahc_data *ahc; + struct ahc_softc *ahc; { - AHC_OUTB(ahc, HCNTRL, ahc->pause); + ahc_outb(ahc, HCNTRL, ahc->pause); /* * Since the sequencer can disable pausing in a critical section, we * must loop until it actually stops. */ - while ((AHC_INB(ahc, HCNTRL) & PAUSE) == 0) + while (sequencer_paused(ahc) == 0) ; } -static inline void -unpause_sequencer(ahc, unpause_always) - struct ahc_data *ahc; - int unpause_always; +static __inline void +unpause_sequencer(ahc) + struct ahc_softc *ahc; { - if (unpause_always - ||(AHC_INB(ahc, INTSTAT) & (SCSIINT | SEQINT | BRKADRINT)) == 0) - AHC_OUTB(ahc, HCNTRL, ahc->unpause); + if ((ahc_inb(ahc, INTSTAT) & (SCSIINT | SEQINT | BRKADRINT)) == 0) + ahc_outb(ahc, HCNTRL, ahc->unpause); } /* * Restart the sequencer program from address zero */ -static inline void +STATIC void restart_sequencer(ahc) - struct ahc_data *ahc; + struct ahc_softc *ahc; { - do { - AHC_OUTB(ahc, SEQCTL, SEQRESET|FASTMODE); - } while((AHC_INB(ahc, SEQADDR0) != 0) - || (AHC_INB(ahc, SEQADDR1) != 0)); + u_int i; + + pause_sequencer(ahc); + + /* + * Everytime we restart the sequencer, there + * is the possiblitity that we have restarted + * within a three instruction window where an + * SCB has been marked free but has not made it + * onto the free list. Since SCSI events(bus reset, + * unexpected bus free) will always freeze the + * sequencer, we cannot close this window. To + * avoid losing an SCB, we reconsitute the free + * list every time we restart the sequencer. + */ + ahc_outb(ahc, FREE_SCBH, SCB_LIST_NULL); + for (i = 0; i < ahc->scb_data->maxhscbs; i++) { + + ahc_outb(ahc, SCBPTR, i); + if (ahc_inb(ahc, SCB_TAG) == SCB_LIST_NULL) + ahc_add_curscb_to_free_list(ahc); + } + ahc_outb(ahc, SEQCTL, FASTMODE|SEQRESET); + unpause_sequencer(ahc); +} + +static __inline u_int +ahc_index_busy_tcl(ahc, tcl, unbusy) + struct ahc_softc *ahc; + u_int tcl; + int unbusy; +{ + u_int scbid; + + scbid = ahc->untagged_scbs[tcl]; + if (unbusy) { + ahc->untagged_scbs[tcl] = SCB_LIST_NULL; + bus_dmamap_sync(ahc->sc_dmat, ahc->shared_data_dmamap, + BUS_DMASYNC_PREWRITE); + } - unpause_sequencer(ahc, /*unpause_always*/TRUE); + return (scbid); +} + +static __inline void +ahc_busy_tcl(ahc, scb) + struct ahc_softc *ahc; + struct scb *scb; +{ + ahc->untagged_scbs[scb->hscb->tcl] = scb->hscb->tag; + bus_dmamap_sync(ahc->sc_dmat, ahc->shared_data_dmamap, + BUS_DMASYNC_PREWRITE); } -#if defined(__NetBSD__) || defined(__OpenBSD__) +static __inline int +ahc_isbusy_tcl(ahc, scb) + struct ahc_softc *ahc; + struct scb *scb; +{ + return ahc->untagged_scbs[scb->hscb->tcl] != SCB_LIST_NULL; +} + +static __inline void +ahc_freeze_ccb(scb) + struct scb *scb; +{ + struct scsi_xfer *xs = scb->xs; + struct ahc_softc *ahc = (struct ahc_softc *)xs->sc_link->adapter_softc; + int target; + + target = xs->sc_link->target; + if (!(scb->flags & SCB_FREEZE_QUEUE)) { + ahc->devqueue_blocked[target]++; + scb->flags |= SCB_FREEZE_QUEUE; + } +} + +static __inline void +ahcsetccbstatus(xs, status) + struct scsi_xfer *xs; + int status; +{ + xs->error = status; +} + +static __inline struct ahc_initiator_tinfo * +ahc_fetch_transinfo(ahc, channel, our_id, remote_id, tstate) + struct ahc_softc *ahc; + char channel; + u_int our_id; + u_int remote_id; + struct 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 = ahc->enabled_targets[our_id]; + return (&(*tstate)->transinfo[remote_id]); +} + +STATIC void +ahc_run_qoutfifo(ahc) + struct ahc_softc *ahc; +{ + struct scb *scb; + u_int scb_index; + + bus_dmamap_sync(ahc->sc_dmat, ahc->shared_data_dmamap, + BUS_DMASYNC_POSTREAD); + + while (ahc->qoutfifo[ahc->qoutfifonext] != SCB_LIST_NULL) { + scb_index = ahc->qoutfifo[ahc->qoutfifonext]; + ahc->qoutfifo[ahc->qoutfifonext++] = SCB_LIST_NULL; + + scb = &ahc->scb_data->scbarray[scb_index]; + if (scb_index >= ahc->scb_data->numscbs + || (scb->flags & SCB_ACTIVE) == 0) { + printf("%s: WARNING no command for scb %d " + "(cmdcmplt)\nQOUTPOS = %d\n", + ahc_name(ahc), scb_index, + ahc->qoutfifonext - 1); + continue; + } + + /* + * Save off the residual + * if there is one. + */ + if (scb->hscb->residual_SG_count != 0) + ahc_calc_residual(scb); + else + scb->xs->resid = 0; + ahc_done(ahc, scb); + } +} + + /* - * Is device which is pointed by sc_link connected on second scsi bus ? + * An scb (and hence an scb entry on the board) is put onto the + * free list. */ -#define IS_SCSIBUS_B(ahc, sc_link) \ - ((sc_link)->scsibus == (ahc)->sc_link_b.scsibus) +STATIC void +ahcfreescb(ahc, scb) + struct ahc_softc *ahc; + struct scb *scb; +{ + struct hardware_scb *hscb; + int opri; + + hscb = scb->hscb; + opri = splbio(); + + if ((ahc->flags & AHC_RESOURCE_SHORTAGE) != 0 || + (scb->flags & SCB_RECOVERY_SCB) != 0) { + ahc->flags &= ~AHC_RESOURCE_SHORTAGE; + ahc->queue_blocked = 0; + } + + /* Clean up for the next user */ + scb->flags = SCB_FREE; + hscb->control = 0; + hscb->status = 0; + + SLIST_INSERT_HEAD(&ahc->scb_data->free_scbs, scb, links); + splx(opri); +} /* - * convert FreeBSD's SCSI symbols to Net- & OpenBSD's + * Get a free scb, either one already assigned to a hardware slot + * on the adapter or one that will require an SCB to be paged out before + * use. If there are none, see if we can allocate a new SCB. Otherwise + * either return an error or sleep. */ -#define SCSI_NOMASK SCSI_POLL -#define opennings openings -#endif +static __inline struct scb * +ahcgetscb(ahc) + struct ahc_softc *ahc; +{ + struct scb *scbp; + int opri; -static u_char ahc_abort_wscb __P((struct ahc_data *ahc, struct scb *scbp, - u_char prev, - u_char timedout_scb, u_int32_t xs_error)); -static void ahc_add_waiting_scb __P((struct ahc_data *ahc, - struct scb *scb)); -static void ahc_done __P((struct ahc_data *ahc, struct scb *scbp)); -static void ahc_free_scb __P((struct ahc_data *ahc, struct scb *scb, - int flags)); -static inline void ahc_send_scb __P((struct ahc_data *ahc, struct scb *scb)); -static inline void ahc_fetch_scb __P((struct ahc_data *ahc, struct scb *scb)); -static inline void ahc_page_scb __P((struct ahc_data *ahc, struct scb *out_scb, - struct scb *in_scb)); -static inline void ahc_run_waiting_queues __P((struct ahc_data *ahc)); -static void ahc_handle_seqint __P((struct ahc_data *ahc, u_int8_t intstat)); -static struct scb * - ahc_get_scb __P((struct ahc_data *ahc, int flags)); -static void ahc_loadseq __P((struct ahc_data *ahc)); -static int ahc_match_scb __P((struct scb *scb, int target, char channel)); -static int ahc_poll __P((struct ahc_data *ahc, int wait)); -#ifdef AHC_DEBUG -static void ahc_print_scb __P((struct scb *scb)); -#endif -static int ahc_reset_channel __P((struct ahc_data *ahc, char channel, - u_char timedout_scb, u_int32_t xs_error, - u_char initiate_reset)); -static int ahc_reset_device __P((struct ahc_data *ahc, int target, - char channel, u_char timedout_scb, - u_int32_t xs_error)); -static void ahc_reset_current_bus __P((struct ahc_data *ahc)); -static void ahc_run_done_queue __P((struct ahc_data *ahc)); -static void ahc_scsirate __P((struct ahc_data* ahc, u_int8_t *scsirate, - u_int8_t *period, u_int8_t *offset, - char channel, int target)); -#if defined(__FreeBSD__) -static timeout_t - ahc_timeout; -#elif defined(__NetBSD__) || defined(__OpenBSD__) -static void ahc_timeout __P((void *)); -#endif -static void ahc_busy_target __P((struct ahc_data *ahc, - int target, char channel)); -static void ahc_unbusy_target __P((struct ahc_data *ahc, - int target, char channel)); -static void ahc_construct_sdtr __P((struct ahc_data *ahc, int start_byte, - u_int8_t period, u_int8_t offset)); -static void ahc_construct_wdtr __P((struct ahc_data *ahc, int start_byte, - u_int8_t bus_width)); - -#if defined(__NetBSD__) || defined(__OpenBSD__) /* XXX */ -static void ahc_xxx_enqueue __P((struct ahc_data *ahc, - struct scsi_xfer *xs, int infront)); -static struct scsi_xfer *ahc_xxx_dequeue __P((struct ahc_data *ahc)); -#endif + opri = splbio(); + if ((scbp = SLIST_FIRST(&ahc->scb_data->free_scbs))) { + SLIST_REMOVE_HEAD(&ahc->scb_data->free_scbs, links); + } else { + ahcallocscbs(ahc); + scbp = SLIST_FIRST(&ahc->scb_data->free_scbs); + if (scbp != NULL) + SLIST_REMOVE_HEAD(&ahc->scb_data->free_scbs, links); + } -#if defined(__FreeBSD__) + splx(opri); + + return (scbp); +} -char *ahc_name(ahc) - struct ahc_data *ahc; +int +ahc_createdmamem(ahc, size, mapp, vaddr, baddr, seg, nseg, what) + struct ahc_softc *ahc; + int size; + bus_dmamap_t *mapp; + caddr_t *vaddr; + bus_addr_t *baddr; + bus_dma_segment_t *seg; + int *nseg; + const char *what; { - static char name[10]; + int error, rseg, level = 0; + int dma_flags = BUS_DMA_NOWAIT; + bus_dma_tag_t tag = ahc->sc_dmat; + const char *myname = ahc_name(ahc); + if ((ahc->chip & AHC_VL) !=0) + dma_flags |= ISABUS_DMA_32BIT; + + if ((error = bus_dmamem_alloc(tag, size, NBPG, 0, + seg, 1, nseg, BUS_DMA_NOWAIT)) != 0) { + printf("%s: failed to allocate DMA mem for %s, error = %d\n", + myname, what, error); + goto out; + } + level++; + + if ((error = bus_dmamem_map(tag, seg, *nseg, size, vaddr, + BUS_DMA_NOWAIT|BUS_DMA_COHERENT)) != 0) { + printf("%s: failed to map DMA mem for %s, error = %d\n", + myname, what, error); + goto out; + } + level++; + + if ((error = bus_dmamap_create(tag, size, 1, size, 0, + dma_flags, mapp)) != 0) { + printf("%s: failed to create DMA map for %s, error = %d\n", + myname, what, error); + goto out; + } + level++; + + if ((error = bus_dmamap_load(tag, *mapp, *vaddr, size, NULL, + BUS_DMA_NOWAIT)) != 0) { + printf("%s: failed to load DMA map for %s, error = %d\n", + myname, what, error); + goto out; + } + + *baddr = seg[0].ds_addr; + + if (bootverbose) + printf("%s: dmamem for %s at phys %lx virt %lx nseg %d size %d\n", + myname, what, (unsigned long)*baddr, + (unsigned long)*vaddr, *nseg, size); + return 0; +out: + switch (level) { + case 3: + bus_dmamap_destroy(tag, *mapp); + /* FALLTHROUGH */ + case 2: + bus_dmamem_unmap(tag, *vaddr, size); + /* FALLTHROUGH */ + case 1: + bus_dmamem_free(tag, seg, rseg); + break; + default: + break; + } - sprintf(name, "ahc%d", ahc->unit); - return (name); + return error; } -#elif defined(__NetBSD__) || defined(__OpenBSD__) -struct cfdriver ahc_cd = { - NULL, "ahc", DV_DULL -}; -#endif +STATIC void +ahc_freedmamem(tag, size, map, vaddr, seg, nseg) + bus_dma_tag_t tag; + int size; + bus_dmamap_t map; + caddr_t vaddr; + bus_dma_segment_t *seg; + int nseg; +{ + + bus_dmamap_unload(tag, map); + bus_dmamap_destroy(tag, map); + bus_dmamem_unmap(tag, vaddr, size); + bus_dmamem_free(tag, seg, nseg); +} #ifdef AHC_DEBUG -static void +STATIC void ahc_print_scb(scb) - struct scb *scb; -{ - printf("scb:%p control:0x%x tcl:0x%x cmdlen:%d cmdpointer:0x%lx\n" - ,scb - ,scb->control - ,scb->tcl - ,scb->cmdlen - ,scb->cmdpointer ); - printf(" datlen:%d data:0x%lx segs:0x%x segp:0x%lx\n" - ,scb->datalen - ,scb->data - ,scb->SG_segment_count - ,scb->SG_list_pointer); - printf(" sg_addr:%lx sg_len:%ld\n" - ,scb->ahc_dma[0].addr - ,scb->ahc_dma[0].len); + struct scb *scb; +{ + struct hardware_scb *hscb = scb->hscb; + + printf("scb:%p control:0x%x tcl:0x%x cmdlen:%d cmdpointer:0x%lx\n", + scb, + hscb->control, + hscb->tcl, + hscb->cmdlen, + hscb->cmdpointer ); + printf(" datlen:%d data:0x%lx segs:0x%x segp:0x%lx\n", + hscb->datalen, + hscb->data, + hscb->SG_count, + hscb->SG_pointer); + printf(" sg_addr:%lx sg_len:%ld\n", + scb->sg_list[0].addr, + scb->sg_list[0].len); + printf(" cdb:%x %x %x %x %x %x %x %x %x %x %x %x\n", + hscb->cmdstore[0], hscb->cmdstore[1], hscb->cmdstore[2], + hscb->cmdstore[3], hscb->cmdstore[4], hscb->cmdstore[5], + hscb->cmdstore[6], hscb->cmdstore[7], hscb->cmdstore[8], + hscb->cmdstore[9], hscb->cmdstore[10], hscb->cmdstore[11]); } - #endif static struct { - u_char errno; + u_int8_t errno; char *errmesg; } hard_error[] = { - { ILLHADDR, "Illegal Host Access" }, - { ILLSADDR, "Illegal Sequencer Address referenced" }, - { ILLOPCODE, "Illegal Opcode in sequencer program" }, - { PARERR, "Sequencer Ram Parity Error" } + { ILLHADDR, "Illegal Host Access" }, + { ILLSADDR, "Illegal Sequencer Address referrenced" }, + { ILLOPCODE, "Illegal Opcode in sequencer program" }, + { SQPARERR, "Sequencer Parity Error" }, + { DPARERR, "Data-path Parity Error" }, + { MPARERR, "Scratch or SCB Memory Parity Error" }, + { PCIERRSTAT, "PCI Error detected" }, + { CIOPARERR, "CIOBUS Parity Error" }, }; +static const int num_errors = sizeof(hard_error)/sizeof(hard_error[0]); +static struct { + u_int8_t phase; + u_int8_t mesg_out; /* Message response to parity errors */ + char *phasemsg; +} phase_table[] = { + { P_DATAOUT, MSG_NOOP, "in Data-out phase" }, + { P_DATAIN, MSG_INITIATOR_DET_ERR, "in Data-in phase" }, + { P_COMMAND, MSG_NOOP, "in Command phase" }, + { P_MESGOUT, MSG_NOOP, "in Message-out phase" }, + { P_STATUS, MSG_INITIATOR_DET_ERR, "in Status phase" }, + { P_MESGIN, MSG_PARITY_ERROR, "in Message-in phase" }, + { P_BUSFREE, MSG_NOOP, "while idle" }, + { 0, MSG_NOOP, "in unknown phase" } +}; +static const int num_phases = (sizeof(phase_table)/sizeof(phase_table[0])) - 1; /* * Valid SCSIRATE values. (p. 3-17) * Provides a mapping of tranfer periods in ns to the proper value to * stick in the scsiscfr reg to use that transfer rate. */ -static struct { - short sxfr; - /* Rates in Ultra mode have bit 8 of sxfr set */ -#define ULTRA_SXFR 0x100 - int period; /* in ns/4 */ - char *rate; -} ahc_syncrates[] = { - { 0x100, 12, "20.0" }, - { 0x110, 15, "16.0" }, - { 0x120, 18, "13.4" }, - { 0x000, 25, "10.0" }, - { 0x010, 31, "8.0" }, - { 0x020, 37, "6.67" }, - { 0x030, 43, "5.7" }, - { 0x040, 50, "5.0" }, - { 0x050, 56, "4.4" }, - { 0x060, 62, "4.0" }, - { 0x070, 68, "3.6" } +#define AHC_SYNCRATE_DT 0 +#define AHC_SYNCRATE_ULTRA2 1 +#define AHC_SYNCRATE_ULTRA 2 +#define AHC_SYNCRATE_FAST 5 +static struct ahc_syncrate ahc_syncrates[] = { + /* ultra2 fast/ultra period rate */ + { 0x42, 0x000, 9, "80.0" }, + { 0x03, 0x000, 10, "40.0" }, + { 0x04, 0x000, 11, "33.0" }, + { 0x05, 0x100, 12, "20.0" }, + { 0x06, 0x110, 15, "16.0" }, + { 0x07, 0x120, 18, "13.4" }, + { 0x08, 0x000, 25, "10.0" }, + { 0x19, 0x010, 31, "8.0" }, + { 0x1a, 0x020, 37, "6.67" }, + { 0x1b, 0x030, 43, "5.7" }, + { 0x1c, 0x040, 50, "5.0" }, + { 0x00, 0x050, 56, "4.4" }, + { 0x00, 0x060, 62, "4.0" }, + { 0x00, 0x070, 68, "3.6" }, + { 0x00, 0x000, 0, NULL } }; -static int ahc_num_syncrates = - sizeof(ahc_syncrates) / sizeof(ahc_syncrates[0]); - /* * Allocate a controller structure for a new device and initialize it. * ahc_reset should be called before now since we assume that the card * is paused. */ -#if defined(__FreeBSD__) -struct ahc_data * -ahc_alloc(unit, iobase, type, flags) - int unit; - u_long iobase; -#elif defined(__NetBSD__) || defined(__OpenBSD__) void -ahc_construct(ahc, iot, ioh, type, flags) - struct ahc_data *ahc; +ahc_construct(ahc, iot, ioh, chip, flags, features, channel) + struct ahc_softc *ahc; bus_space_tag_t iot; bus_space_handle_t ioh; -#endif - ahc_type type; + ahc_chip chip; ahc_flag flags; + ahc_feature features; + u_char channel; { - /* * find unit and check we have that many defined */ - -#if defined(__FreeBSD__) - struct ahc_data *ahc; - - /* - * Allocate a storage area for us - */ - - ahc = malloc(sizeof(struct ahc_data), M_TEMP, M_NOWAIT); - if (!ahc) { - printf("ahc%d: cannot malloc!\n", unit); - return NULL; - } - bzero(ahc, sizeof(struct ahc_data)); -#endif - STAILQ_INIT(&ahc->free_scbs); - STAILQ_INIT(&ahc->page_scbs); - STAILQ_INIT(&ahc->waiting_scbs); - STAILQ_INIT(&ahc->assigned_scbs); -#if defined(__FreeBSD__) - ahc->unit = unit; -#endif -#if defined(__FreeBSD__) - ahc->baseport = iobase; -#elif defined(__NetBSD__) || defined(__OpenBSD__) + LIST_INIT(&ahc->pending_scbs); ahc->sc_iot = iot; ahc->sc_ioh = ioh; -#endif - ahc->type = type; + ahc->chip = chip; ahc->flags = flags; - ahc->unpause = (AHC_INB(ahc, HCNTRL) & IRQMS) | INTEN; + ahc->features = features; + ahc->channel = channel; + ahc->scb_data = NULL; + ahc->pci_intr_func = NULL; + + ahc->unpause = (ahc_inb(ahc, HCNTRL) & IRQMS) | INTEN; + /* The IRQMS bit is only valid on VL and EISA chips */ + if ((ahc->chip & AHC_PCI) != 0) + ahc->unpause &= ~IRQMS; ahc->pause = ahc->unpause | PAUSE; - -#if defined(__FreeBSD__) - return (ahc); -#endif } void ahc_free(ahc) - struct ahc_data *ahc; + struct ahc_softc *ahc; { -#if defined(__FreeBSD__) - free(ahc, M_DEVBUF); + ahcfiniscbdata(ahc); + if (ahc->init_level != 0) + ahc_freedmamem(ahc->sc_dmat, ahc->shared_data_size, + ahc->shared_data_dmamap, ahc->qoutfifo, + &ahc->shared_data_seg, ahc->shared_data_nseg); + + if (ahc->scb_data != NULL) + free(ahc->scb_data, M_DEVBUF); + if (ahc->pci_data != NULL) + free(ahc->pci_data, M_DEVBUF); return; -#endif +} + +STATIC int +ahcinitscbdata(ahc) + struct ahc_softc *ahc; +{ + struct scb_data *scb_data; + int i; + + scb_data = ahc->scb_data; + SLIST_INIT(&scb_data->free_scbs); + SLIST_INIT(&scb_data->sg_maps); + + /* Allocate SCB resources */ + scb_data->scbarray = + (struct scb *)malloc(sizeof(struct scb) * AHC_SCB_MAX, + M_DEVBUF, M_NOWAIT); + if (scb_data->scbarray == NULL) + return (ENOMEM); + bzero(scb_data->scbarray, sizeof(struct scb) * AHC_SCB_MAX); + + /* Determine the number of hardware SCBs and initialize them */ + + scb_data->maxhscbs = ahc_probe_scbs(ahc); + /* SCB 0 heads the free list */ + ahc_outb(ahc, FREE_SCBH, 0); + for (i = 0; i < ahc->scb_data->maxhscbs; i++) { + ahc_outb(ahc, SCBPTR, i); + + /* Clear the control byte. */ + ahc_outb(ahc, SCB_CONTROL, 0); + + /* Set the next pointer */ + ahc_outb(ahc, SCB_NEXT, i+1); + + /* Make the tag number invalid */ + ahc_outb(ahc, SCB_TAG, SCB_LIST_NULL); + } + + /* Make sure that the last SCB terminates the free list */ + ahc_outb(ahc, SCBPTR, i-1); + ahc_outb(ahc, SCB_NEXT, SCB_LIST_NULL); + + /* Ensure we clear the 0 SCB's control byte. */ + ahc_outb(ahc, SCBPTR, 0); + ahc_outb(ahc, SCB_CONTROL, 0); + + scb_data->maxhscbs = i; + + if (ahc->scb_data->maxhscbs == 0) + panic("%s: No SCB space found", ahc_name(ahc)); + + /* + * Create our DMA tags. These tags define the kinds of device + * accessable memory allocations and memory mappings we will + * need to perform during normal operation. + * + * Unless we need to further restrict the allocation, we rely + * on the restrictions of the parent dmat, hence the common + * use of MAXADDR and MAXSIZE. + */ + + if (ahc_createdmamem(ahc, + AHC_SCB_MAX * sizeof(struct hardware_scb), + &scb_data->hscb_dmamap, (caddr_t *)&scb_data->hscbs, + &scb_data->hscb_busaddr, &scb_data->hscb_seg, + &scb_data->hscb_nseg, "hardware SCB structures") < 0) + goto error_exit; + + scb_data->init_level++; + + if (ahc_createdmamem(ahc, + AHC_SCB_MAX * sizeof(struct scsi_sense_data), + &scb_data->sense_dmamap, (caddr_t *)&scb_data->sense, + &scb_data->sense_busaddr, &scb_data->sense_seg, + &scb_data->sense_nseg, "sense buffers") < 0) + goto error_exit; + + scb_data->init_level++; + + /* Perform initial CCB allocation */ + bzero(scb_data->hscbs, AHC_SCB_MAX * sizeof(struct hardware_scb)); + ahcallocscbs(ahc); + + if (scb_data->numscbs == 0) { + printf("%s: ahc_init_scb_data - " + "Unable to allocate initial scbs\n", + ahc_name(ahc)); + goto error_exit; + } + + scb_data->init_level++; + + /* + * Note that we were successfull + */ + return 0; + +error_exit: + + return ENOMEM; +} + +STATIC void +ahcfiniscbdata(ahc) + struct ahc_softc *ahc; +{ + struct scb_data *scb_data; + + scb_data = ahc->scb_data; + + switch (scb_data->init_level) { + default: + case 3: + { + struct sg_map_node *sg_map; + + while ((sg_map = SLIST_FIRST(&scb_data->sg_maps))!= NULL) { + SLIST_REMOVE_HEAD(&scb_data->sg_maps, links); + ahc_freedmamem(ahc->sc_dmat, PAGE_SIZE, + sg_map->sg_dmamap, (caddr_t)sg_map->sg_vaddr, + &sg_map->sg_dmasegs, sg_map->sg_nseg); + free(sg_map, M_DEVBUF); + } + } + /*FALLTHROUGH*/ + case 2: + ahc_freedmamem(ahc->sc_dmat, + AHC_SCB_MAX * sizeof(struct scsi_sense_data), + scb_data->sense_dmamap, (caddr_t)scb_data->sense, + &scb_data->sense_seg, scb_data->sense_nseg); + /*FALLTHROUGH*/ + case 1: + ahc_freedmamem(ahc->sc_dmat, + AHC_SCB_MAX * sizeof(struct hardware_scb), + scb_data->hscb_dmamap, (caddr_t)scb_data->hscbs, + &scb_data->hscb_seg, scb_data->hscb_nseg); + /*FALLTHROUGH*/ + } + if (scb_data->scbarray != NULL) + free(scb_data->scbarray, M_DEVBUF); } void -#if defined(__FreeBSD__) -ahc_reset(iobase) - u_long iobase; -#elif defined(__NetBSD__) || defined(__OpenBSD__) -ahc_reset(devname, iot, ioh) +ahc_xxx_reset(devname, iot, ioh) char *devname; bus_space_tag_t iot; bus_space_handle_t ioh; -#endif { - u_char hcntrl; + u_char hcntrl; int wait; +#ifdef AHC_DUMP_SEQ + ahc_dumpseq(ahc); +#endif /* Retain the IRQ type accross the chip reset */ -#if defined(__FreeBSD__) - hcntrl = (inb(HCNTRL + iobase) & IRQMS) | INTEN; - - outb(HCNTRL + iobase, CHIPRST | PAUSE); -#elif defined(__NetBSD__) || defined(__OpenBSD__) hcntrl = (bus_space_read_1(iot, ioh, HCNTRL) & IRQMS) | INTEN; bus_space_write_1(iot, ioh, HCNTRL, CHIPRST | PAUSE); -#endif /* * Ensure that the reset has finished */ wait = 1000; -#if defined(__FreeBSD__) - while (--wait && !(inb(HCNTRL + iobase) & CHIPRSTACK)) -#elif defined(__NetBSD__) || defined(__OpenBSD__) while (--wait && !(bus_space_read_1(iot, ioh, HCNTRL) & CHIPRSTACK)) -#endif DELAY(1000); - if(wait == 0) { -#if defined(__FreeBSD__) - printf("ahc at 0x%lx: WARNING - Failed chip reset! " - "Trying to initialize anyway.\n", iobase); -#elif defined(__NetBSD__) || defined(__OpenBSD__) + if (wait == 0) { printf("%s: WARNING - Failed chip reset! " - "Trying to initialize anyway.\n", devname); -#endif + "Trying to initialize anyway.\n", devname); } -#if defined(__FreeBSD__) - outb(HCNTRL + iobase, hcntrl | PAUSE); -#elif defined(__NetBSD__) || defined(__OpenBSD__) bus_space_write_1(iot, ioh, HCNTRL, hcntrl | PAUSE); +} + +int +ahc_reset(ahc) + struct ahc_softc *ahc; +{ + u_int sblkctl; + int wait; + +#ifdef AHC_DUMP_SEQ + if (ahc->init_level == 0) + ahc_dumpseq(ahc); #endif + ahc_outb(ahc, HCNTRL, CHIPRST | ahc->pause); + /* + * Ensure that the reset has finished + */ + wait = 1000; + do { + DELAY(1000); + } while (--wait && !(ahc_inb(ahc, HCNTRL) & CHIPRSTACK)); + + if (wait == 0) { + printf("%s: WARNING - Failed chip reset! " + "Trying to initialize anyway.\n", ahc_name(ahc)); + } + ahc_outb(ahc, HCNTRL, ahc->pause); + + /* Determine channel configuration */ + sblkctl = ahc_inb(ahc, SBLKCTL) & (SELBUSB|SELWIDE); + /* No Twin Channel PCI cards */ + if ((ahc->chip & AHC_PCI) != 0) + sblkctl &= ~SELBUSB; + switch (sblkctl) { + case 0: + /* Single Narrow Channel */ + break; + case 2: + /* Wide Channel */ + ahc->features |= AHC_WIDE; + break; + case 8: + /* Twin Channel */ + ahc->features |= AHC_TWIN; + break; + default: + printf(" Unsupported adapter type. Ignoring\n"); + return(-1); + } + return (0); +} + +/* + * Called when we have an active connection to a target on the bus, + * this function finds the nearest syncrate to the input period limited + * by the capabilities of the bus connectivity of the target. + */ +STATIC struct ahc_syncrate * +ahc_devlimited_syncrate(ahc, period) + struct ahc_softc *ahc; + u_int *period; +{ + u_int maxsync; + + if ((ahc->features & AHC_ULTRA2) != 0) { + if ((ahc_inb(ahc, SBLKCTL) & ENAB40) != 0 + && (ahc_inb(ahc, SSTAT2) & EXP_ACTIVE) == 0) { + maxsync = AHC_SYNCRATE_ULTRA2; + } else { + maxsync = AHC_SYNCRATE_ULTRA; + } + } else if ((ahc->features & AHC_ULTRA) != 0) { + maxsync = AHC_SYNCRATE_ULTRA; + } else { + maxsync = AHC_SYNCRATE_FAST; + } + return (ahc_find_syncrate(ahc, period, maxsync)); } /* * Look up the valid period to SCSIRATE conversion in our table. + * Return the period and offset that should be sent to the target + * if this was the beginning of an SDTR. */ -static void -ahc_scsirate(ahc, scsirate, period, offset, channel, target ) - struct ahc_data *ahc; - u_int8_t *scsirate; - u_int8_t *period; - u_int8_t *offset; - char channel; - int target; +STATIC struct ahc_syncrate * +ahc_find_syncrate(ahc, period, maxsync) + struct ahc_softc *ahc; + u_int *period; + u_int maxsync; { - int i; - u_int32_t ultra_enb_addr; - u_int8_t sxfrctl0; - u_int8_t ultra_enb; + struct ahc_syncrate *syncrate; - i = ahc_num_syncrates; /* Default to async */ - - if (*period >= ahc_syncrates[0].period && *offset != 0) { - for (i = 0; i < ahc_num_syncrates; i++) { + syncrate = &ahc_syncrates[maxsync]; + while ((syncrate->rate != NULL) + && ((ahc->features & AHC_ULTRA2) == 0 + || (syncrate->sxfr_u2 != 0))) { - if (*period <= ahc_syncrates[i].period) { - /* - * Watch out for Ultra speeds when ultra is not - * enabled and vice-versa. - */ - if(!(ahc->type & AHC_ULTRA) - && (ahc_syncrates[i].sxfr & ULTRA_SXFR)) { - /* - * This should only happen if the - * drive is the first to negotiate - * and chooses a high rate. We'll - * just move down the table util - * we hit a non ultra speed. - */ - continue; - } - *scsirate = (ahc_syncrates[i].sxfr & 0xF0) - | (*offset & 0x0f); - *period = ahc_syncrates[i].period; - - if(bootverbose) { - printf("%s: target %d synchronous at %sMHz," - " offset = 0x%x\n", - ahc_name(ahc), target, - ahc_syncrates[i].rate, *offset ); - } - break; - } + if (*period <= syncrate->period) { + /* + * When responding to a target that requests + * sync, the requested rate may fall between + * two rates that we can output, but still be + * a rate that we can receive. Because of this, + * we want to respond to the target with + * the same rate that it sent to us even + * if the period we use to send data to it + * is lower. Only lower the response period + * if we must. + */ + if (syncrate == &ahc_syncrates[maxsync]) + *period = syncrate->period; + break; } + syncrate++; } - if (i >= ahc_num_syncrates) { + + if ((*period == 0) + || (syncrate->rate == NULL) + || ((ahc->features & AHC_ULTRA2) != 0 + && (syncrate->sxfr_u2 == 0))) { /* Use asynchronous transfers. */ - *scsirate = 0; *period = 0; - *offset = 0; - if (bootverbose) - printf("%s: target %d using asynchronous transfers\n", - ahc_name(ahc), target ); + syncrate = NULL; + } + return (syncrate); +} + +STATIC u_int +ahc_find_period(ahc, scsirate, maxsync) + struct ahc_softc *ahc; + u_int scsirate; + u_int maxsync; +{ + struct ahc_syncrate *syncrate; + + if ((ahc->features & AHC_ULTRA2) != 0) + scsirate &= SXFR_ULTRA2; + else + scsirate &= SXFR; + + syncrate = &ahc_syncrates[maxsync]; + while (syncrate->rate != NULL) { + + if ((ahc->features & AHC_ULTRA2) != 0) { + if (syncrate->sxfr_u2 == 0) + break; + else if (scsirate == (syncrate->sxfr_u2 & SXFR_ULTRA2)) + return (syncrate->period); + } else if (scsirate == (syncrate->sxfr & SXFR)) { + return (syncrate->period); + } + syncrate++; + } + return (0); /* async */ +} + +STATIC void +ahc_validate_offset(ahc, syncrate, offset, wide) + struct ahc_softc *ahc; + struct ahc_syncrate *syncrate; + u_int *offset; + int wide; +{ + u_int maxoffset; + + /* Limit offset to what we can do */ + if (syncrate == NULL) { + maxoffset = 0; + } else if ((ahc->features & AHC_ULTRA2) != 0) { + maxoffset = MAX_OFFSET_ULTRA2; + } else { + if (wide) + maxoffset = MAX_OFFSET_16BIT; + else + maxoffset = MAX_OFFSET_8BIT; } + *offset = MIN(*offset, maxoffset); +} + +STATIC void +ahc_update_target_msg_request(ahc, devinfo, tinfo, force, paused) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; + struct ahc_initiator_tinfo *tinfo; + int force; + int paused; +{ + u_int targ_msg_req_orig; + + targ_msg_req_orig = ahc->targ_msg_req; + if (tinfo->current.period != tinfo->goal.period + || tinfo->current.width != tinfo->goal.width + || tinfo->current.offset != tinfo->goal.offset + || (force && (tinfo->goal.period != 0 + || tinfo->goal.width != MSG_EXT_WDTR_BUS_8_BIT))) + ahc->targ_msg_req |= devinfo->target_mask; + else + ahc->targ_msg_req &= ~devinfo->target_mask; + + if (ahc->targ_msg_req != targ_msg_req_orig) { + /* Update the message request bit for this target */ + if ((ahc->features & AHC_HS_MAILBOX) != 0) { + if (paused) { + ahc_outb(ahc, TARGET_MSG_REQUEST, + ahc->targ_msg_req & 0xFF); + ahc_outb(ahc, TARGET_MSG_REQUEST + 1, + (ahc->targ_msg_req >> 8) & 0xFF); + } else { + ahc_outb(ahc, HS_MAILBOX, + 0x01 << HOST_MAILBOX_SHIFT); + } + } else { + if (!paused) + pause_sequencer(ahc); + + ahc_outb(ahc, TARGET_MSG_REQUEST, + ahc->targ_msg_req & 0xFF); + ahc_outb(ahc, TARGET_MSG_REQUEST + 1, + (ahc->targ_msg_req >> 8) & 0xFF); + + if (!paused) + unpause_sequencer(ahc); + } + } +} + +STATIC void +ahc_set_syncrate(ahc, devinfo, syncrate, period, offset, type, paused, done) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; + struct ahc_syncrate *syncrate; + u_int period; + u_int offset; + u_int type; + int paused; + int done; +{ + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + u_int old_period; + u_int old_offset; + int active = (type & AHC_TRANS_ACTIVE) == AHC_TRANS_ACTIVE; + + if (syncrate == NULL) { + period = 0; + offset = 0; + } + + tinfo = ahc_fetch_transinfo(ahc, devinfo->channel, + devinfo->our_scsiid, + devinfo->target, &tstate); + old_period = tinfo->current.period; + old_offset = tinfo->current.offset; + + if ((type & AHC_TRANS_CUR) != 0 + && (old_period != period || old_offset != offset)) { + u_int scsirate; + + scsirate = tinfo->scsirate; + if ((ahc->features & AHC_ULTRA2) != 0) { + + /* XXX */ + /* Force single edge until DT is fully implemented */ + scsirate &= ~(SXFR_ULTRA2|SINGLE_EDGE|ENABLE_CRC); + if (syncrate != NULL) + scsirate |= syncrate->sxfr_u2|SINGLE_EDGE; + + if (active) + ahc_outb(ahc, SCSIOFFSET, offset); + } else { + + scsirate &= ~(SXFR|SOFS); + /* + * Ensure Ultra mode is set properly for + * this target. + */ + tstate->ultraenb &= ~devinfo->target_mask; + if (syncrate != NULL) { + if (syncrate->sxfr & ULTRA_SXFR) { + tstate->ultraenb |= + devinfo->target_mask; + } + scsirate |= syncrate->sxfr & SXFR; + scsirate |= offset & SOFS; + } + if (active) { + u_int sxfrctl0; + + sxfrctl0 = ahc_inb(ahc, SXFRCTL0); + sxfrctl0 &= ~FAST20; + if (tstate->ultraenb & devinfo->target_mask) + sxfrctl0 |= FAST20; + ahc_outb(ahc, SXFRCTL0, sxfrctl0); + } + } + if (active) + ahc_outb(ahc, SCSIRATE, scsirate); + + tinfo->scsirate = scsirate; + tinfo->current.period = period; + tinfo->current.offset = offset; + + /* Update the syncrates in any pending scbs */ + ahc_update_pending_syncrates(ahc); + } + /* - * Ensure Ultra mode is set properly for - * this target. + * Print messages if we're verbose and at the end of a negotiation + * cycle. */ - ultra_enb_addr = ULTRA_ENB; - if(channel == 'B' || target > 7) - ultra_enb_addr++; - ultra_enb = AHC_INB(ahc, ultra_enb_addr); - sxfrctl0 = AHC_INB(ahc, SXFRCTL0); - if (*scsirate != 0 && ahc_syncrates[i].sxfr & ULTRA_SXFR) { - ultra_enb |= 0x01 << (target & 0x07); - sxfrctl0 |= ULTRAEN; + if (done && bootverbose) { + if (offset != 0) { + printf("%s: target %d synchronous at %sMHz, " + "offset = 0x%x\n", ahc_name(ahc), + devinfo->target, syncrate->rate, offset); + } else { + printf("%s: target %d using " + "asynchronous transfers\n", + ahc_name(ahc), devinfo->target); + } + } + + if ((type & AHC_TRANS_GOAL) != 0) { + tinfo->goal.period = period; + tinfo->goal.offset = offset; + } + + if ((type & AHC_TRANS_USER) != 0) { + tinfo->user.period = period; + tinfo->user.offset = offset; + } + + ahc_update_target_msg_request(ahc, devinfo, tinfo, + /*force*/FALSE, + paused); +} + +STATIC void +ahc_set_width(ahc, devinfo, width, type, paused, done) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; + u_int width; + u_int type; + int paused; + int done; +{ + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + u_int oldwidth; + int active = (type & AHC_TRANS_ACTIVE) == AHC_TRANS_ACTIVE; + + tinfo = ahc_fetch_transinfo(ahc, devinfo->channel, + devinfo->our_scsiid, + devinfo->target, + &tstate); + oldwidth = tinfo->current.width; + + if ((type & AHC_TRANS_CUR) != 0 && oldwidth != width) { + u_int scsirate; + + scsirate = tinfo->scsirate; + scsirate &= ~WIDEXFER; + if (width == MSG_EXT_WDTR_BUS_16_BIT) + scsirate |= WIDEXFER; + + tinfo->scsirate = scsirate; + + if (active) + ahc_outb(ahc, SCSIRATE, scsirate); + + tinfo->current.width = width; } - else { - ultra_enb &= ~(0x01 << (target & 0x07)); - sxfrctl0 &= ~ULTRAEN; + + if (done) { + printf("%s: target %d using %dbit transfers\n", + ahc_name(ahc), devinfo->target, + 8 * (0x01 << width)); } - AHC_OUTB(ahc, ultra_enb_addr, ultra_enb); - AHC_OUTB(ahc, SXFRCTL0, sxfrctl0); + + if ((type & AHC_TRANS_GOAL) != 0) + tinfo->goal.width = width; + if ((type & AHC_TRANS_USER) != 0) + tinfo->user.width = width; + + ahc_update_target_msg_request(ahc, devinfo, tinfo, + /*force*/FALSE, paused); +} + +STATIC void +ahc_set_tags(ahc, devinfo, enable) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; + int enable; +{ + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + + tinfo = ahc_fetch_transinfo(ahc, devinfo->channel, + devinfo->our_scsiid, + devinfo->target, + &tstate); + if (enable) + tstate->tagenable |= devinfo->target_mask; + else + tstate->tagenable &= ~devinfo->target_mask; } /* @@ -626,18 +1474,12 @@ ahc_scsirate(ahc, scsirate, period, offset, channel, target ) */ int ahc_attach(ahc) - struct ahc_data *ahc; + struct ahc_softc *ahc; { -#if defined(__FreeBSD__) - struct scsibus_data *scbus; -#endif - -#if defined(__NetBSD__) || defined(__OpenBSD__) /* XXX */ /* * Initialize the software queue. */ LIST_INIT(&ahc->sc_xxxq); -#endif #ifdef AHC_BROKEN_CACHE if (cpu_class == CPUCLASS_386) /* doesn't have "wbinvd" instruction */ @@ -646,88 +1488,24 @@ ahc_attach(ahc) /* * fill in the prototype scsi_links. */ -#if defined(__FreeBSD__) - ahc->sc_link.adapter_unit = ahc->unit; - ahc->sc_link.adapter_targ = ahc->our_id; - ahc->sc_link.fordriver = 0; -#elif defined(__NetBSD__) || defined(__OpenBSD__) ahc->sc_link.adapter_target = ahc->our_id; -#ifdef __OpenBSD__ - if(ahc->type & AHC_WIDE) + if (ahc->features & AHC_WIDE) ahc->sc_link.adapter_buswidth = 16; -#endif -#ifndef __OpenBSD__ - ahc->sc_link.channel = 0; -#endif -#endif ahc->sc_link.adapter_softc = ahc; ahc->sc_link.adapter = &ahc_switch; - ahc->sc_link.opennings = 2; + ahc->sc_link.openings = 2; ahc->sc_link.device = &ahc_dev; -#ifdef __OpenBSD__ ahc->sc_link.flags = SCSIDEBUG_LEVEL; -#else - ahc->sc_link.flags = DEBUGLEVEL; -#endif - - if(ahc->type & AHC_TWIN) { + + if (ahc->features & AHC_TWIN) { /* Configure the second scsi bus */ ahc->sc_link_b = ahc->sc_link; -#if defined(__FreeBSD__) - ahc->sc_link_b.adapter_targ = ahc->our_id_b; - ahc->sc_link_b.adapter_bus = 1; - ahc->sc_link_b.fordriver = (void *)SELBUSB; -#elif defined(__NetBSD__) || defined(__OpenBSD__) ahc->sc_link_b.adapter_target = ahc->our_id_b; -#ifdef __OpenBSD__ - if(ahc->type & AHC_WIDE) + if (ahc->features & AHC_WIDE) ahc->sc_link.adapter_buswidth = 16; -#endif -#ifndef __OpenBSD__ - ahc->sc_link_b.channel = 1; -#endif -#endif } - -#if defined(__FreeBSD__) - /* - * Prepare the scsibus_data area for the upperlevel - * scsi code. - */ - scbus = scsi_alloc_bus(); - if(!scbus) - return 0; - scbus->adapter_link = (ahc->flags & AHC_CHANNEL_B_PRIMARY) ? - &ahc->sc_link_b : &ahc->sc_link; - if(ahc->type & AHC_WIDE) - scbus->maxtarg = 15; - - /* - * ask the adapter what subunits are present - */ - if(bootverbose) - printf("ahc%d: Probing channel %c\n", ahc->unit, - (ahc->flags & AHC_CHANNEL_B_PRIMARY) ? 'B' : 'A'); - scsi_attachdevs(scbus); - scbus = NULL; /* Upper-level SCSI code owns this now */ - - if(ahc->type & AHC_TWIN) { - scbus = scsi_alloc_bus(); - if(!scbus) - return 0; - scbus->adapter_link = (ahc->flags & AHC_CHANNEL_B_PRIMARY) ? - &ahc->sc_link : &ahc->sc_link_b; - if(ahc->type & AHC_WIDE) - scbus->maxtarg = 15; - if(bootverbose) - printf("ahc%d: Probing Channel %c\n", ahc->unit, - (ahc->flags & AHC_CHANNEL_B_PRIMARY) ? 'A': 'B'); - scsi_attachdevs(scbus); - scbus = NULL; /* Upper-level SCSI code owns this now */ - } -#elif defined(__NetBSD__) || defined (__OpenBSD__) - /* +/* * ask the adapter what subunits are present */ if ((ahc->flags & AHC_CHANNEL_B_PRIMARY) == 0) { @@ -735,7 +1513,7 @@ ahc_attach(ahc) ahc->sc_link_b.scsibus = 0xff; config_found((void *)ahc, &ahc->sc_link, scsiprint); - if (ahc->type & AHC_TWIN) + if (ahc->features & AHC_TWIN) config_found((void *)ahc, &ahc->sc_link_b, scsiprint); } else { /* @@ -744,1283 +1522,1997 @@ ahc_attach(ahc) * is needed, here. */ - /* assert(ahc->type & AHC_TWIN); */ + /* assert(ahc->features & AHC_TWIN); */ config_found((void *)ahc, &ahc->sc_link_b, scsiprint); config_found((void *)ahc, &ahc->sc_link, scsiprint); } -#endif return 1; } -/* - * Send an SCB down to the card via PIO. - * We assume that the proper SCB is already selected in SCBPTR. - */ -static inline void -ahc_send_scb(ahc, scb) - struct ahc_data *ahc; - struct scb *scb; -{ - AHC_OUTB(ahc, SCBCNT, SCBAUTO); - if( ahc->type == AHC_284 ) - /* Can only do 8bit PIO */ - AHC_OUTSB(ahc, SCBARRAY, scb, SCB_PIO_TRANSFER_SIZE); +STATIC void +ahc_fetch_devinfo(ahc, devinfo) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; +{ + u_int saved_tcl; + role_t role; + int our_id; + + if (ahc_inb(ahc, SSTAT0) & TARGET) + role = ROLE_TARGET; + else + role = ROLE_INITIATOR; + + if (role == ROLE_TARGET + && (ahc->features & AHC_MULTI_TID) != 0 + && (ahc_inb(ahc, SEQ_FLAGS) & CMDPHASE_PENDING) != 0) { + /* We were selected, so pull our id from TARGIDIN */ + our_id = ahc_inb(ahc, TARGIDIN) & OID; + } else if ((ahc->features & AHC_ULTRA2) != 0) + our_id = ahc_inb(ahc, SCSIID_ULTRA2) & OID; else - AHC_OUTSL(ahc, SCBARRAY, scb, - (SCB_PIO_TRANSFER_SIZE + 3) / 4); - AHC_OUTB(ahc, SCBCNT, 0); + our_id = ahc_inb(ahc, SCSIID) & OID; + + saved_tcl = ahc_inb(ahc, SAVED_TCL); + ahc_compile_devinfo(devinfo, our_id, TCL_TARGET(saved_tcl), + TCL_LUN(saved_tcl), TCL_CHANNEL(ahc, saved_tcl), + role); +} + +STATIC void +ahc_compile_devinfo(devinfo, our_id, target, lun, channel, role) + struct ahc_devinfo *devinfo; + u_int our_id; + u_int target; + u_int lun; + char channel; + role_t role; +{ + devinfo->our_scsiid = our_id; + devinfo->target = target; + devinfo->lun = lun; + devinfo->target_offset = target; + devinfo->channel = channel; + devinfo->role = role; + if (channel == 'B') + devinfo->target_offset += 8; + devinfo->target_mask = (0x01 << devinfo->target_offset); } /* - * Retrieve an SCB from the card via PIO. - * We assume that the proper SCB is already selected in SCBPTR. + * Catch an interrupt from the adapter */ -static inline void -ahc_fetch_scb(ahc, scb) - struct ahc_data *ahc; - struct scb *scb; +int +ahc_intr(void *arg) { - AHC_OUTB(ahc, SCBCNT, 0x80); /* SCBAUTO */ + struct ahc_softc *ahc; + u_int intstat; - /* Can only do 8bit PIO for reads */ - AHC_INSB(ahc, SCBARRAY, scb, SCB_PIO_TRANSFER_SIZE); + ahc = (struct ahc_softc *)arg; - AHC_OUTB(ahc, SCBCNT, 0); -} + intstat = ahc_inb(ahc, INTSTAT); -/* - * Swap in_scbp for out_scbp down in the cards SCB array. - * We assume that the SCB for out_scbp is already selected in SCBPTR. - */ -static inline void -ahc_page_scb(ahc, out_scbp, in_scbp) - struct ahc_data *ahc; - struct scb *out_scbp; - struct scb *in_scbp; -{ - /* Page-out */ - ahc_fetch_scb(ahc, out_scbp); - out_scbp->flags |= SCB_PAGED_OUT; - if(!(out_scbp->control & TAG_ENB)) - { - /* Stick in non-tagged array */ - int index = (out_scbp->tcl >> 4) - | (out_scbp->tcl & SELBUSB); - ahc->pagedout_ntscbs[index] = out_scbp; + /* + * Any interrupts to process? + */ + if ((intstat & INT_PEND) == 0) { + if (ahc->pci_intr_func && ahc->pci_intr_func(ahc)) { +#ifdef AHC_DEBUG + printf("%s: bus intr: CCHADDR %x HADDR %x SEQADDR %x\n", + ahc_name(ahc), + ahc_inb(ahc, CCHADDR) | + (ahc_inb(ahc, CCHADDR+1) << 8) + | (ahc_inb(ahc, CCHADDR+2) << 16) + | (ahc_inb(ahc, CCHADDR+3) << 24), + ahc_inb(ahc, HADDR) | (ahc_inb(ahc, HADDR+1) << 8) + | (ahc_inb(ahc, HADDR+2) << 16) + | (ahc_inb(ahc, HADDR+3) << 24), + ahc_inb(ahc, SEQADDR0) | + (ahc_inb(ahc, SEQADDR1) << 8)); +#endif + return 1; + } + return 0; + } + + if (intstat & CMDCMPLT) { + ahc_outb(ahc, CLRINT, CLRCMDINT); + ahc_run_qoutfifo(ahc); } + if (intstat & BRKADRINT) { + /* + * We upset the sequencer :-( + * Lookup the error message + */ + int i, error, num_errors; - /* Page-in */ - in_scbp->position = out_scbp->position; - out_scbp->position = SCB_LIST_NULL; - ahc_send_scb(ahc, in_scbp); - in_scbp->flags &= ~SCB_PAGED_OUT; + error = ahc_inb(ahc, ERROR); + num_errors = sizeof(hard_error)/sizeof(hard_error[0]); + for (i = 0; error != 1 && i < num_errors; i++) + error >>= 1; + panic("%s: brkadrint, %s at seqaddr = 0x%x\n", + ahc_name(ahc), hard_error[i].errmesg, + ahc_inb(ahc, SEQADDR0) | + (ahc_inb(ahc, SEQADDR1) << 8)); + + /* Tell everyone that this HBA is no longer availible */ + ahc_abort_scbs(ahc, ALL_TARGETS, ALL_CHANNELS, + ALL_LUNS, SCB_LIST_NULL, ROLE_UNKNOWN, + XS_DRIVER_STUFFUP); + } + if (intstat & SEQINT) + ahc_handle_seqint(ahc, intstat); + + if (intstat & SCSIINT) + ahc_handle_scsiint(ahc, intstat); + return(1); } -static inline void -ahc_run_waiting_queues(ahc) - struct ahc_data *ahc; +STATIC struct tmode_tstate * +ahc_alloc_tstate(ahc, scsi_id, channel) + struct ahc_softc *ahc; + u_int scsi_id; + char channel; { - struct scb* scb; - u_char cur_scb; + struct tmode_tstate *master_tstate; + struct tmode_tstate *tstate; + int i, s; + + master_tstate = ahc->enabled_targets[ahc->our_id]; + if (channel == 'B') { + scsi_id += 8; + master_tstate = ahc->enabled_targets[ahc->our_id_b + 8]; + } + if (ahc->enabled_targets[scsi_id] != NULL + && ahc->enabled_targets[scsi_id] != master_tstate) + panic("%s: ahc_alloc_tstate - Target already allocated", + ahc_name(ahc)); + tstate = malloc(sizeof(*tstate), M_DEVBUF, M_NOWAIT); + if (tstate == NULL) + return (NULL); - if(!(ahc->assigned_scbs.stqh_first || ahc->waiting_scbs.stqh_first)) - return; + /* + * If we have allocated a master tstate, copy user settings from + * the master tstate (taken from SRAM or the EEPROM) for this + * channel, but reset our current and goal settings to async/narrow + * until an initiator talks to us. + */ + if (master_tstate != NULL) { + bcopy(master_tstate, tstate, sizeof(*tstate)); + tstate->ultraenb = 0; + for (i = 0; i < 16; i++) { + bzero(&tstate->transinfo[i].current, + sizeof(tstate->transinfo[i].current)); + bzero(&tstate->transinfo[i].goal, + sizeof(tstate->transinfo[i].goal)); + } + } else + bzero(tstate, sizeof(*tstate)); + s = splbio(); + ahc->enabled_targets[scsi_id] = tstate; + splx(s); + return (tstate); +} - pause_sequencer(ahc); - cur_scb = AHC_INB(ahc, SCBPTR); +STATIC void +ahc_handle_seqint(ahc, intstat) + struct ahc_softc *ahc; + u_int intstat; +{ + struct scb *scb; + struct ahc_devinfo devinfo; + + ahc_fetch_devinfo(ahc, &devinfo); /* - * First handle SCBs that are waiting but have been - * assigned a slot. + * Clear the upper byte that holds SEQINT status + * codes and clear the SEQINT bit. We will unpause + * the sequencer, if appropriate, after servicing + * the request. */ - while((scb = ahc->assigned_scbs.stqh_first) != NULL) { - STAILQ_REMOVE_HEAD(&ahc->assigned_scbs, links); - AHC_OUTB(ahc, SCBPTR, scb->position); - ahc_send_scb(ahc, scb); - - /* Mark this as an active command */ - scb->flags ^= SCB_ASSIGNEDQ|SCB_ACTIVE; + ahc_outb(ahc, CLRINT, CLRSEQINT); + switch (intstat & SEQINT_MASK) { + case NO_MATCH: + { + /* Ensure we don't leave the selection hardware on */ + ahc_outb(ahc, SCSISEQ, + ahc_inb(ahc, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP)); + + printf("%s:%c:%d: no active SCB for reconnecting " + "target - issuing BUS DEVICE RESET\n", + ahc_name(ahc), devinfo.channel, devinfo.target); + printf("SAVED_TCL == 0x%x, ARG_1 == 0x%x, SEQ_FLAGS == 0x%x\n", + ahc_inb(ahc, SAVED_TCL), ahc_inb(ahc, ARG_1), + ahc_inb(ahc, SEQ_FLAGS)); + ahc->msgout_buf[0] = MSG_BUS_DEV_RESET; + ahc->msgout_len = 1; + ahc->msgout_index = 0; + ahc->msg_type = MSG_TYPE_INITIATOR_MSGOUT; + ahc_outb(ahc, MSG_OUT, HOST_MSG); + ahc_outb(ahc, SCSISIGO, ahc_inb(ahc, LASTPHASE) | ATNO); + break; + } + case UPDATE_TMSG_REQ: + ahc_outb(ahc, TARGET_MSG_REQUEST, ahc->targ_msg_req & 0xFF); + ahc_outb(ahc, TARGET_MSG_REQUEST + 1, + (ahc->targ_msg_req >> 8) & 0xFF); + ahc_outb(ahc, HS_MAILBOX, 0); + break; + case SEND_REJECT: + { + u_int rejbyte = ahc_inb(ahc, ACCUM); + printf("%s:%c:%d: Warning - unknown message received from " + "target (0x%x). Rejecting\n", + ahc_name(ahc), devinfo.channel, devinfo.target, rejbyte); + break; + } + case NO_IDENT: + { + /* + * The reconnecting target either did not send an identify + * message, or did, but we didn't find and SCB to match and + * before it could respond to our ATN/abort, it hit a dataphase. + * The only safe thing to do is to blow it away with a bus + * reset. + */ + int found; + + printf("%s:%c:%d: Target did not send an IDENTIFY message. " + "LASTPHASE = 0x%x, SAVED_TCL == 0x%x\n", + ahc_name(ahc), devinfo.channel, devinfo.target, + ahc_inb(ahc, LASTPHASE), ahc_inb(ahc, SAVED_TCL)); + found = ahc_reset_channel(ahc, devinfo.channel, + /*initiate reset*/TRUE); + printf("%s: Issued Channel %c Bus Reset. " + "%d SCBs aborted\n", ahc_name(ahc), devinfo.channel, + found); + return; + } + case BAD_PHASE: + { + u_int lastphase; - AHC_OUTB(ahc, QINFIFO, scb->position); - if (!(scb->xs->flags & SCSI_NOMASK)) { - timeout(ahc_timeout, (caddr_t)scb, - (scb->xs->timeout * hz) / 1000); + lastphase = ahc_inb(ahc, LASTPHASE); + if (lastphase == P_BUSFREE) { + printf("%s:%c:%d: Missed busfree. Curphase = 0x%x\n", + ahc_name(ahc), devinfo.channel, devinfo.target, + ahc_inb(ahc, SCSISIGI)); + restart_sequencer(ahc); + return; + } else { + printf("%s:%c:%d: unknown scsi bus phase %x. " + "Attempting to continue\n", + ahc_name(ahc), devinfo.channel, devinfo.target, + ahc_inb(ahc, SCSISIGI)); } - SC_DEBUG(scb->xs->sc_link, SDEV_DB3, ("cmd_sent\n")); + break; } - /* Now deal with SCBs that require paging */ - if((scb = ahc->waiting_scbs.stqh_first) != NULL) { - u_char disc_scb = AHC_INB(ahc, DISCONNECTED_SCBH); - u_char active = AHC_INB(ahc, FLAGS) & (SELECTED|IDENTIFY_SEEN); - int count = 0; + case BAD_STATUS: + { + u_int scb_index; + struct hardware_scb *hscb; + struct scsi_xfer *xs; + /* + * The sequencer will notify us when a command + * has an error that would be of interest to + * the kernel. This allows us to leave the sequencer + * running in the common case of command completes + * without error. The sequencer will already have + * dma'd the SCB back up to us, so we can reference + * the in kernel copy directly. + */ + scb_index = ahc_inb(ahc, SCB_TAG); + scb = &ahc->scb_data->scbarray[scb_index]; - do { - u_char next_scb; + /* + * Set the default return value to 0 (don't + * send sense). The sense code will change + * this if needed. + */ + ahc_outb(ahc, RETURN_1, 0); + if (!(scb_index < ahc->scb_data->numscbs + && (scb->flags & SCB_ACTIVE) != 0)) { + printf("%s:%c:%d: ahc_intr - referenced scb " + "not valid during seqint 0x%x scb(%d)\n", + ahc_name(ahc), devinfo.channel, + devinfo.target, intstat, scb_index); + goto unpause; + } - /* Attempt to page this SCB in */ - if(disc_scb == SCB_LIST_NULL) - break; + hscb = scb->hscb; + xs = scb->xs; + /* Don't want to clobber the original sense code */ + if ((scb->flags & SCB_SENSE) != 0) { /* - * Check the next SCB on in the list. + * Clear the SCB_SENSE Flag and have + * the sequencer do a normal command + * complete. */ - AHC_OUTB(ahc, SCBPTR, disc_scb); - next_scb = AHC_INB(ahc, SCB_NEXT); + scb->flags &= ~SCB_SENSE; + ahcsetccbstatus(xs, XS_DRIVER_STUFFUP); + break; + } + /* Freeze the queue unit the client sees the error. */ + ahc_freeze_devq(ahc, xs->sc_link); + ahc_freeze_ccb(scb); + xs->status = hscb->status; + switch (hscb->status) { + case SCSI_OK: + printf("%s: Interrupted for staus of 0???\n", + ahc_name(ahc)); + break; + case SCSI_CHECK: +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWSENSE) { + sc_print_addr(scb->xs->sc_link); + printf("SCB %d: requests Check Status\n", + scb->hscb->tag); + } +#endif + + if (xs->error == XS_NOERROR && + !(scb->flags & SCB_SENSE)) { + struct ahc_dma_seg *sg; + struct scsi_sense *sc; + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + + sg = scb->sg_list; + sc = (struct scsi_sense *)(&hscb->cmdstore); + /* + * Save off the residual if there is one. + */ + if (hscb->residual_SG_count != 0) + ahc_calc_residual(scb); + else + xs->resid = 0; - /* - * We have to be careful about when we allow - * an SCB to be paged out. There must always - * be at least one slot available for a - * reconnecting target in case it references - * an SCB that has been paged out. Our - * heuristic is that either the disconnected - * list has at least two entries in it or - * there is one entry and the sequencer is - * actively working on an SCB which implies that - * it will either complete or disconnect before - * another reconnection can occur. - */ - if((next_scb != SCB_LIST_NULL) || active) - { - u_char out_scbi; - struct scb* out_scbp; +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWSENSE) { + sc_print_addr(scb->xs->sc_link); + printf("Sending Sense\n"); + } +#endif + sg->addr = ahc->scb_data->sense_busaddr + + (hscb->tag*sizeof(struct scsi_sense_data)); + + sg->len = sizeof(struct scsi_sense_data); - STAILQ_REMOVE_HEAD(&ahc->waiting_scbs, links); + sc->opcode = REQUEST_SENSE; + sc->byte2 = SCB_LUN(scb) << 5; + sc->unused[0] = 0; + sc->unused[1] = 0; + sc->length = sg->len; + sc->control = 0; /* - * Find the in-core SCB for the one - * we're paging out. + * Would be nice to preserve DISCENB here, + * but due to the way we page SCBs, we can't. */ - out_scbi = AHC_INB(ahc, SCB_TAG); - out_scbp = ahc->scbarray[out_scbi]; + hscb->control = 0; - /* Do the page out */ - ahc_page_scb(ahc, out_scbp, scb); - - /* Mark this as an active command */ - scb->flags ^= SCB_WAITINGQ|SCB_ACTIVE; + /* + * This request sense could be because the + * the device lost power or in some other + * way has lost our transfer negotiations. + * Renegotiate if appropriate. + */ + ahc_calc_residual(scb); +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWSENSE) { + sc_print_addr(xs->sc_link); + printf("Sense: datalen %d resid %d" + "chan %d id %d targ %d\n", + xs->datalen, xs->resid, + devinfo.channel, + devinfo.our_scsiid, + devinfo.target); + } +#endif + if (xs->datalen > 0 && + xs->resid == xs->datalen) { + tinfo = ahc_fetch_transinfo(ahc, + devinfo.channel, + devinfo.our_scsiid, + devinfo.target, + &tstate); + ahc_update_target_msg_request(ahc, + &devinfo, + tinfo, + /*force*/TRUE, + /*paused*/TRUE); + } + hscb->status = 0; + hscb->SG_count = 1; + hscb->SG_pointer = scb->sg_list_phys; + hscb->data = sg->addr; + hscb->datalen = sg->len; + hscb->cmdpointer = hscb->cmdstore_busaddr; + hscb->cmdlen = sizeof(*sc); + scb->sg_count = hscb->SG_count; + scb->flags |= SCB_SENSE; + /* + * Ensure the target is busy since this + * will be an untagged request. + */ + ahc_busy_tcl(ahc, scb); + ahc_outb(ahc, RETURN_1, SEND_SENSE); - /* Queue the command */ - AHC_OUTB(ahc, QINFIFO, scb->position); - if (!(scb->xs->flags & SCSI_NOMASK)) { + /* + * Ensure we have enough time to actually + * retrieve the sense. + */ + if (!(scb->xs->flags & SCSI_POLL)) { + untimeout(ahc_timeout, (caddr_t)scb); timeout(ahc_timeout, (caddr_t)scb, - (scb->xs->timeout * hz) / 1000); + 5 * hz); } - SC_DEBUG(scb->xs->sc_link, SDEV_DB3, - ("cmd_paged-in\n")); - count++; + } + break; + case SCSI_BUSY: + /* + * Requeue any transactions that haven't been + * sent yet. + */ + ahc_freeze_devq(ahc, xs->sc_link); + ahc_freeze_ccb(scb); + break; + } + break; + } + case TRACE_POINT: + { + printf("SSTAT2 = 0x%x DFCNTRL = 0x%x\n", ahc_inb(ahc, SSTAT2), + ahc_inb(ahc, DFCNTRL)); + printf("SSTAT3 = 0x%x DSTATUS = 0x%x\n", ahc_inb(ahc, SSTAT3), + ahc_inb(ahc, DFSTATUS)); + printf("SSTAT0 = 0x%x, SCB_DATACNT = 0x%x\n", + ahc_inb(ahc, SSTAT0), + ahc_inb(ahc, SCB_DATACNT)); + break; + } + case HOST_MSG_LOOP: + { + /* + * The sequencer has encountered a message phase + * that requires host assistance for completion. + * While handling the message phase(s), we will be + * notified by the sequencer after each byte is + * transfered so we can track bus phases. + * + * If this is the first time we've seen a HOST_MSG_LOOP, + * initialize the state of the host message loop. + */ + if (ahc->msg_type == MSG_TYPE_NONE) { + u_int bus_phase; + + bus_phase = ahc_inb(ahc, SCSISIGI) & PHASE_MASK; + if (bus_phase != P_MESGIN + && bus_phase != P_MESGOUT) { + printf("ahc_intr: HOST_MSG_LOOP bad " + "phase 0x%x\n", + bus_phase); + /* + * Probably transitioned to bus free before + * we got here. Just punt the message. + */ + ahc_clear_intstat(ahc); + restart_sequencer(ahc); + } - /* Advance to the next disconnected SCB */ - disc_scb = next_scb; + if (devinfo.role == ROLE_INITIATOR) { + struct scb *scb; + u_int scb_index; + + scb_index = ahc_inb(ahc, SCB_TAG); + scb = &ahc->scb_data->scbarray[scb_index]; + + if (bus_phase == P_MESGOUT) + ahc_setup_initiator_msgout(ahc, + &devinfo, + scb); + else { + ahc->msg_type = + MSG_TYPE_INITIATOR_MSGIN; + ahc->msgin_index = 0; + } + } else { + if (bus_phase == P_MESGOUT) { + ahc->msg_type = + MSG_TYPE_TARGET_MSGOUT; + ahc->msgin_index = 0; + } else + /* XXX Ever executed??? */ + ahc_setup_target_msgin(ahc, &devinfo); + } } + + /* Pass a NULL path so that handlers generate their own */ + ahc_handle_message_phase(ahc, /*path*/NULL); + break; + } + case PERR_DETECTED: + { + /* + * If we've cleared the parity error interrupt + * but the sequencer still believes that SCSIPERR + * is true, it must be that the parity error is + * for the currently presented byte on the bus, + * and we are not in a phase (data-in) where we will + * eventually ack this byte. Ack the byte and + * throw it away in the hope that the target will + * take us to message out to deliver the appropriate + * error message. + */ + if ((intstat & SCSIINT) == 0 + && (ahc_inb(ahc, SSTAT1) & SCSIPERR) != 0) { + u_int curphase; + + /* + * The hardware will only let you ack bytes + * if the expected phase in SCSISIGO matches + * the current phase. Make sure this is + * currently the case. + */ + curphase = ahc_inb(ahc, SCSISIGI) & PHASE_MASK; + ahc_outb(ahc, LASTPHASE, curphase); + ahc_outb(ahc, SCSISIGO, curphase); + ahc_inb(ahc, SCSIDATL); + } + break; + } + 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. + */ + u_int scbindex = ahc_inb(ahc, SCB_TAG); + u_int lastphase = ahc_inb(ahc, LASTPHASE); + int i; + + scb = &ahc->scb_data->scbarray[scbindex]; + for (i = 0; i < num_phases; i++) { + if (lastphase == phase_table[i].phase) + break; + } + sc_print_addr(scb->xs->sc_link); + printf("data overrun detected %s." + " Tag == 0x%x.\n", + phase_table[i].phasemsg, + scb->hscb->tag); + sc_print_addr(scb->xs->sc_link); + printf("%s seen Data Phase. Length = %d. NumSGs = %d.\n", + ahc_inb(ahc, SEQ_FLAGS) & DPHASE ? "Have" : "Haven't", + scb->xs->datalen, scb->sg_count); + if (scb->sg_count > 0) { + for (i = 0; i < scb->sg_count; i++) { + printf("sg[%d] - Addr 0x%x : Length %d\n", + i, + scb->sg_list[i].addr, + scb->sg_list[i].len); + } + } + /* + * Set this and it will take affect when the + * target does a command complete. + */ + ahc_freeze_devq(ahc, scb->xs->sc_link); + ahcsetccbstatus(scb->xs, XS_DRIVER_STUFFUP); + ahc_freeze_ccb(scb); + break; + } + case TRACEPOINT: + { + printf("TRACEPOINT: RETURN_2 = %d\n", ahc_inb(ahc, RETURN_2)); +#if 0 + printf("SSTAT1 == 0x%x\n", ahc_inb(ahc, SSTAT1)); + printf("SSTAT0 == 0x%x\n", ahc_inb(ahc, SSTAT0)); + printf(", SCSISIGI == 0x%x\n", ahc_inb(ahc, SCSISIGI)); + printf("TRACEPOINT: CCHCNT = %d, SG_COUNT = %d\n", + ahc_inb(ahc, CCHCNT), ahc_inb(ahc, SG_COUNT)); + printf("TRACEPOINT: SCB_TAG = %d\n", ahc_inb(ahc, SCB_TAG)); + printf("TRACEPOINT1: CCHADDR = %d, CCHCNT = %d, SCBPTR = %d\n", + ahc_inb(ahc, CCHADDR) + | (ahc_inb(ahc, CCHADDR+1) << 8) + | (ahc_inb(ahc, CCHADDR+2) << 16) + | (ahc_inb(ahc, CCHADDR+3) << 24), + ahc_inb(ahc, CCHCNT) + | (ahc_inb(ahc, CCHCNT+1) << 8) + | (ahc_inb(ahc, CCHCNT+2) << 16), + ahc_inb(ahc, SCBPTR)); + printf("TRACEPOINT: WAITING_SCBH = %d\n", + ahc_inb(ahc, WAITING_SCBH)); + printf("TRACEPOINT: SCB_TAG = %d\n", ahc_inb(ahc, SCB_TAG)); +#endif + break; + } +#if NOT_YET + /* XXX Fill these in later */ + case MESG_BUFFER_BUSY: + break; + case MSGIN_PHASEMIS: + break; +#endif + default: + printf("ahc_intr: seqint, " + "intstat == 0x%x, scsisigi = 0x%x\n", + intstat, ahc_inb(ahc, SCSISIGI)); + break; + } + +unpause: + /* + * The sequencer is paused immediately on + * a SEQINT, so we should restart it when + * we're done. + */ + unpause_sequencer(ahc); +} + +STATIC void +ahc_handle_scsiint(ahc, intstat) + struct ahc_softc *ahc; + u_int intstat; +{ + u_int scb_index; + u_int status; + struct scb *scb; + char cur_channel; + char intr_channel; + + if ((ahc->features & AHC_TWIN) != 0 + && ((ahc_inb(ahc, SBLKCTL) & SELBUSB) != 0)) + cur_channel = 'B'; + else + cur_channel = 'A'; + intr_channel = cur_channel; + + status = ahc_inb(ahc, SSTAT1); + if (status == 0) { + if ((ahc->features & AHC_TWIN) != 0) { + /* Try the other channel */ + ahc_outb(ahc, SBLKCTL, ahc_inb(ahc, SBLKCTL) ^ SELBUSB); + status = ahc_inb(ahc, SSTAT1); + ahc_outb(ahc, SBLKCTL, ahc_inb(ahc, SBLKCTL) ^ SELBUSB); + intr_channel = (cur_channel == 'A') ? 'B' : 'A'; + } + if (status == 0) { + printf("%s: Spurious SCSI interrupt\n", ahc_name(ahc)); + return; + } + } + + scb_index = ahc_inb(ahc, SCB_TAG); + if (scb_index < ahc->scb_data->numscbs) { + scb = &ahc->scb_data->scbarray[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0 + || (ahc_inb(ahc, SEQ_FLAGS) & IDENTIFY_SEEN) == 0) + scb = NULL; + } else + scb = NULL; + + if ((status & SCSIRSTI) != 0) { + printf("%s: Someone reset channel %c\n", + ahc_name(ahc), intr_channel); + ahc_reset_channel(ahc, intr_channel, /* Initiate Reset */FALSE); + } else if ((status & SCSIPERR) != 0) { + /* + * Determine the bus phase and queue an appropriate message. + * SCSIPERR is latched true as soon as a parity error + * occurs. If the sequencer acked the transfer that + * caused the parity error and the currently presented + * transfer on the bus has correct parity, SCSIPERR will + * be cleared by CLRSCSIPERR. Use this to determine if + * we should look at the last phase the sequencer recorded, + * or the current phase presented on the bus. + */ + u_int mesg_out; + u_int curphase; + u_int errorphase; + u_int lastphase; + int i; + + lastphase = ahc_inb(ahc, LASTPHASE); + curphase = ahc_inb(ahc, SCSISIGI) & PHASE_MASK; + ahc_outb(ahc, CLRSINT1, CLRSCSIPERR); + /* + * For all phases save DATA, the sequencer won't + * automatically ack a byte that has a parity error + * in it. So the only way that the current phase + * could be 'data-in' is if the parity error is for + * an already acked byte in the data phase. During + * synchronous data-in transfers, we may actually + * ack bytes before latching the current phase in + * LASTPHASE, leading to the discrepancy between + * curphase and lastphase. + */ + if ((ahc_inb(ahc, SSTAT1) & SCSIPERR) != 0 + || curphase == P_DATAIN) + errorphase = curphase; + else + errorphase = lastphase; + + for (i = 0; i < num_phases; i++) { + if (errorphase == phase_table[i].phase) + break; + } + mesg_out = phase_table[i].mesg_out; + if (scb != NULL) + sc_print_addr(scb->xs->sc_link); + else + printf("%s:%c:%d: ", ahc_name(ahc), + intr_channel, + TCL_TARGET(ahc_inb(ahc, SAVED_TCL))); + + printf("parity error detected %s. " + "SEQADDR(0x%x) SCSIRATE(0x%x)\n", + phase_table[i].phasemsg, + ahc_inb(ahc, SEQADDR0) | (ahc_inb(ahc, SEQADDR1) << 8), + ahc_inb(ahc, SCSIRATE)); + + /* + * We've set the hardware to assert ATN if we + * get a parity error on "in" phases, so all we + * need to do is stuff the message buffer with + * the appropriate message. "In" phases have set + * mesg_out to something other than MSG_NOP. + */ + if (mesg_out != MSG_NOOP) { + if (ahc->msg_type != MSG_TYPE_NONE) + ahc->send_msg_perror = TRUE; else + ahc_outb(ahc, MSG_OUT, mesg_out); + } + ahc_outb(ahc, CLRINT, CLRSCSIINT); + unpause_sequencer(ahc); + } else if ((status & BUSFREE) != 0 + && (ahc_inb(ahc, SIMODE1) & ENBUSFREE) != 0) { + /* + * First 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. + */ + u_int lastphase = ahc_inb(ahc, LASTPHASE); + u_int saved_tcl = ahc_inb(ahc, SAVED_TCL); + u_int target = TCL_TARGET(saved_tcl); + u_int initiator_role_id = TCL_SCSI_ID(ahc, saved_tcl); + char channel = TCL_CHANNEL(ahc, saved_tcl); + int printerror = 1; + + ahc_outb(ahc, SCSISEQ, + ahc_inb(ahc, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP)); + if (lastphase == P_MESGOUT) { + u_int message; + u_int tag; + + message = ahc->msgout_buf[ahc->msgout_index - 1]; + tag = SCB_LIST_NULL; + switch (message) { + case MSG_ABORT_TAG: + tag = scb->hscb->tag; + /* FALLTRHOUGH */ + case MSG_ABORT: + sc_print_addr(scb->xs->sc_link); + printf("SCB %d - Abort %s Completed.\n", + scb->hscb->tag, tag == SCB_LIST_NULL ? + "" : "Tag"); + ahc_abort_scbs(ahc, target, channel, + TCL_LUN(saved_tcl), tag, + ROLE_INITIATOR, + XS_DRIVER_STUFFUP); + printerror = 0; + break; + case MSG_BUS_DEV_RESET: + { + struct ahc_devinfo devinfo; + + if (scb != NULL && + (scb->xs->flags & SCSI_RESET) + && ahc_match_scb(scb, target, channel, + TCL_LUN(saved_tcl), + SCB_LIST_NULL, + ROLE_INITIATOR)) { + ahcsetccbstatus(scb->xs, XS_NOERROR); + } + ahc_compile_devinfo(&devinfo, + initiator_role_id, + target, + TCL_LUN(saved_tcl), + channel, + ROLE_INITIATOR); + ahc_handle_devreset(ahc, &devinfo, + XS_RESET, + "Bus Device Reset", + /*verbose_level*/0); + printerror = 0; + break; + } + default: break; - } while((scb = ahc->waiting_scbs.stqh_first) != NULL); + } + } + if (printerror != 0) { + int i; - if(count) { - /* - * Update the head of the disconnected list. - */ - AHC_OUTB(ahc, DISCONNECTED_SCBH, disc_scb); - if(disc_scb != SCB_LIST_NULL) { - AHC_OUTB(ahc, SCBPTR, disc_scb); - AHC_OUTB(ahc, SCB_PREV, SCB_LIST_NULL); + if (scb != NULL) { + u_int tag; + + if ((scb->hscb->control & TAG_ENB) != 0) + tag = scb->hscb->tag; + else + tag = SCB_LIST_NULL; + ahc_abort_scbs(ahc, target, channel, + SCB_LUN(scb), tag, + ROLE_INITIATOR, + XS_DRIVER_STUFFUP); + } else { + /* + * We had not fully identified this connection, + * so we cannot abort anything. + */ + printf("%s: ", ahc_name(ahc)); + } + for (i = 0; i < num_phases; i++) { + if (lastphase == phase_table[i].phase) + break; } + printf("Unexpected busfree %s\n" + "SEQADDR == 0x%x\n", + phase_table[i].phasemsg, ahc_inb(ahc, SEQADDR0) + | (ahc_inb(ahc, SEQADDR1) << 8)); } + ahc_clear_msg_state(ahc); + ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) & ~ENBUSFREE); + ahc_outb(ahc, CLRSINT1, CLRBUSFREE|CLRSCSIPERR); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + restart_sequencer(ahc); + } else if ((status & SELTO) != 0) { + u_int scbptr; + + scbptr = ahc_inb(ahc, WAITING_SCBH); + ahc_outb(ahc, SCBPTR, scbptr); + scb_index = ahc_inb(ahc, SCB_TAG); + + if (scb_index < ahc->scb_data->numscbs) { + scb = &ahc->scb_data->scbarray[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0) + scb = NULL; + } else + scb = NULL; + + if (scb == NULL) { + printf("%s: ahc_intr - referenced scb not " + "valid during SELTO scb(%d, %d)\n", + ahc_name(ahc), scbptr, scb_index); + } else { + u_int tag; + + tag = SCB_LIST_NULL; + if ((scb->hscb->control & MSG_SIMPLE_Q_TAG) != 0) + tag = scb->hscb->tag; + + ahc_abort_scbs(ahc, SCB_TARGET(scb), SCB_CHANNEL(scb), + SCB_LUN(scb), tag, + ROLE_INITIATOR, XS_SELTIMEOUT); + } + /* Stop the selection */ + ahc_outb(ahc, SCSISEQ, 0); + + /* No more pending messages */ + ahc_clear_msg_state(ahc); + + /* + * Although the driver does not care about the + * 'Selection in Progress' status bit, the busy + * LED does. SELINGO is only cleared by a sucessful + * selection, so we must manually clear it to ensure + * the LED turns off just incase no future successful + * selections occur (e.g. no devices on the bus). + */ + ahc_outb(ahc, CLRSINT0, CLRSELINGO); + + /* Clear interrupt state */ + ahc_outb(ahc, CLRSINT1, CLRSELTIMEO|CLRBUSFREE|CLRSCSIPERR); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + restart_sequencer(ahc); + } else { + sc_print_addr(scb->xs->sc_link); + printf("Unknown SCSIINT. Status = 0x%x\n", status); + ahc_outb(ahc, CLRSINT1, status); + ahc_outb(ahc, CLRINT, CLRSCSIINT); + unpause_sequencer(ahc); } - /* Restore old position */ - AHC_OUTB(ahc, SCBPTR, cur_scb); - unpause_sequencer(ahc, /*unpause_always*/FALSE); } -/* - * Add this SCB to the head of the "waiting for selection" list. - */ -static -void ahc_add_waiting_scb(ahc, scb) - struct ahc_data *ahc; - struct scb *scb; +STATIC void +ahc_build_transfer_msg(ahc, devinfo) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; { - u_char next; - u_char curscb; + /* + * We need to initiate transfer negotiations. + * If our current and goal settings are identical, + * we want to renegotiate due to a check condition. + */ + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + int dowide; + int dosync; + + tinfo = ahc_fetch_transinfo(ahc, devinfo->channel, + devinfo->our_scsiid, + devinfo->target, &tstate); + dowide = tinfo->current.width != tinfo->goal.width; + dosync = tinfo->current.period != tinfo->goal.period; + + if (!dowide && !dosync) { + dowide = tinfo->goal.width != MSG_EXT_WDTR_BUS_8_BIT; + dosync = tinfo->goal.period != 0; + } - curscb = AHC_INB(ahc, SCBPTR); - next = AHC_INB(ahc, WAITING_SCBH); + if (dowide) { + ahc_construct_wdtr(ahc, tinfo->goal.width); + } else if (dosync) { + struct ahc_syncrate *rate; + u_int period; + u_int offset; + + period = tinfo->goal.period; + rate = ahc_devlimited_syncrate(ahc, &period); + offset = tinfo->goal.offset; + ahc_validate_offset(ahc, rate, &offset, + tinfo->current.width); + ahc_construct_sdtr(ahc, period, offset); + } else { + panic("ahc_intr: AWAITING_MSG for negotiation, " + "but no negotiation needed\n"); + } +} - AHC_OUTB(ahc, SCBPTR, scb->position); - AHC_OUTB(ahc, SCB_NEXT, next); - AHC_OUTB(ahc, WAITING_SCBH, scb->position); +STATIC void +ahc_setup_initiator_msgout(ahc, devinfo, scb) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; + struct scb *scb; +{ + /* + * To facilitate adding multiple messages together, + * each routine should increment the index and len + * variables instead of setting them explicitly. + */ + ahc->msgout_index = 0; + ahc->msgout_len = 0; + + if ((scb->flags & SCB_DEVICE_RESET) == 0 + && ahc_inb(ahc, MSG_OUT) == MSG_IDENTIFYFLAG) { + u_int identify_msg; + + identify_msg = MSG_IDENTIFYFLAG | SCB_LUN(scb); + if ((scb->hscb->control & DISCENB) != 0) + identify_msg |= MSG_IDENTIFY_DISCFLAG; + ahc->msgout_buf[ahc->msgout_index++] = identify_msg; + ahc->msgout_len++; + + if ((scb->hscb->control & TAG_ENB) != 0) { + /* XXX fvdl FreeBSD has tag action passed down */ + ahc->msgout_buf[ahc->msgout_index++] = MSG_SIMPLE_Q_TAG; + ahc->msgout_buf[ahc->msgout_index++] = scb->hscb->tag; + ahc->msgout_len += 2; + } + } + + if (scb->flags & SCB_DEVICE_RESET) { + ahc->msgout_buf[ahc->msgout_index++] = MSG_BUS_DEV_RESET; + ahc->msgout_len++; + + sc_print_addr(scb->xs->sc_link); + printf("Bus Device Reset Message Sent\n"); + } else if (scb->flags & SCB_ABORT) { + if ((scb->hscb->control & TAG_ENB) != 0) + ahc->msgout_buf[ahc->msgout_index++] = MSG_ABORT_TAG; + else + ahc->msgout_buf[ahc->msgout_index++] = MSG_ABORT; + ahc->msgout_len++; + sc_print_addr(scb->xs->sc_link); + printf("Abort Message Sent\n"); + } else if ((ahc->targ_msg_req & devinfo->target_mask) != 0) { + ahc_build_transfer_msg(ahc, devinfo); + } else { + printf("ahc_intr: AWAITING_MSG for an SCB that " + "does not have a waiting message"); + panic("SCB = %d, SCB Control = %x, MSG_OUT = %x " + "SCB flags = %x", scb->hscb->tag, scb->hscb->control, + ahc_inb(ahc, MSG_OUT), scb->flags); + } - AHC_OUTB(ahc, SCBPTR, curscb); + /* + * Clear the MK_MESSAGE flag from the SCB so we aren't + * asked to send this message again. + */ + ahc_outb(ahc, SCB_CONTROL, ahc_inb(ahc, SCB_CONTROL) & ~MK_MESSAGE); + ahc->msgout_index = 0; + ahc->msg_type = MSG_TYPE_INITIATOR_MSGOUT; } -/* - * Catch an interrupt from the adapter - */ -#if defined(__FreeBSD__) -void -#elif defined (__NetBSD__) || defined (__OpenBSD__) -int -#endif -ahc_intr(arg) - void *arg; +STATIC void +ahc_setup_target_msgin(ahc, devinfo) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; { - int intstat; - u_char status; - struct scb *scb; - struct scsi_xfer *xs; - struct ahc_data *ahc = (struct ahc_data *)arg; + /* + * To facilitate adding multiple messages together, + * each routine should increment the index and len + * variables instead of setting them explicitly. + */ + ahc->msgout_index = 0; + ahc->msgout_len = 0; + + if ((ahc->targ_msg_req & devinfo->target_mask) != 0) + ahc_build_transfer_msg(ahc, devinfo); + else + panic("ahc_intr: AWAITING target message with no message"); - intstat = AHC_INB(ahc, INTSTAT); + ahc->msgout_index = 0; + ahc->msg_type = MSG_TYPE_TARGET_MSGIN; +} + +STATIC int +ahc_handle_msg_reject(ahc, devinfo) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; +{ /* - * Is this interrupt for me? or for - * someone who is sharing my interrupt? + * What we care about here is if we had an + * outstanding SDTR or WDTR message for this + * target. If we did, this is a signal that + * the target is refusing negotiation. */ - if (!(intstat & INT_PEND)) -#if defined(__FreeBSD__) - return; -#elif defined(__NetBSD__) || defined(__OpenBSD__) - return 0; -#endif + struct scb *scb; + u_int scb_index; + u_int last_msg; + int response = 0; + + scb_index = ahc_inb(ahc, SCB_TAG); + scb = &ahc->scb_data->scbarray[scb_index]; + + /* Might be necessary */ + last_msg = ahc_inb(ahc, LAST_MSG); + + if (ahc_sent_msg(ahc, MSG_EXT_WDTR, /*full*/FALSE)) { + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + + /* note 8bit xfers */ + printf("%s:%c:%d: refuses WIDE negotiation. Using " + "8bit transfers\n", ahc_name(ahc), + devinfo->channel, devinfo->target); + ahc_set_width(ahc, devinfo, + MSG_EXT_WDTR_BUS_8_BIT, + AHC_TRANS_ACTIVE|AHC_TRANS_GOAL, + /*paused*/TRUE, /*done*/TRUE); + /* + * No need to clear the sync rate. If the target + * did not accept the command, our syncrate is + * unaffected. If the target started the negotiation, + * but rejected our response, we already cleared the + * sync rate before sending our WDTR. + */ + tinfo = ahc_fetch_transinfo(ahc, devinfo->channel, + devinfo->our_scsiid, + devinfo->target, &tstate); + if (tinfo->goal.period) { + u_int period; + + /* Start the sync negotiation */ + period = tinfo->goal.period; + ahc_devlimited_syncrate(ahc, &period); + ahc->msgout_index = 0; + ahc->msgout_len = 0; + ahc_construct_sdtr(ahc, period, tinfo->goal.offset); + ahc->msgout_index = 0; + response = 1; + } + } else if (ahc_sent_msg(ahc, MSG_EXT_SDTR, /*full*/FALSE)) { + /* note asynch xfers and clear flag */ + ahc_set_syncrate(ahc, devinfo, /*syncrate*/NULL, /*period*/0, + /*offset*/0, + AHC_TRANS_ACTIVE|AHC_TRANS_GOAL, + /*paused*/TRUE, + /*done*/TRUE); + printf("%s:%c:%d: refuses synchronous negotiation. " + "Using asynchronous transfers\n", + ahc_name(ahc), + devinfo->channel, devinfo->target); + } else if ((scb->hscb->control & MSG_SIMPLE_Q_TAG) != 0) { + printf("%s:%c:%d: refuses tagged commands. Performing " + "non-tagged I/O\n", ahc_name(ahc), + devinfo->channel, devinfo->target); + + ahc_set_tags(ahc, devinfo, FALSE); - if (intstat & BRKADRINT) { - /* We upset the sequencer :-( */ + /* + * Resend the identify for this CCB as the target + * may believe that the selection is invalid otherwise. + */ + ahc_outb(ahc, SCB_CONTROL, ahc_inb(ahc, SCB_CONTROL) + & ~MSG_SIMPLE_Q_TAG); + scb->hscb->control &= ~MSG_SIMPLE_Q_TAG; + ahc_outb(ahc, MSG_OUT, MSG_IDENTIFYFLAG); + ahc_outb(ahc, SCSISIGO, ahc_inb(ahc, SCSISIGO) | ATNO); - /* Lookup the error message */ - int i, error = AHC_INB(ahc, ERROR); - int num_errors = sizeof(hard_error)/sizeof(hard_error[0]); - for(i = 0; error != 1 && i < num_errors; i++) - error >>= 1; - panic("%s: brkadrint, %s at seqaddr = 0x%x", - ahc_name(ahc), hard_error[i].errmesg, - (AHC_INB(ahc, SEQADDR1) << 8) | - AHC_INB(ahc, SEQADDR0)); - } - if (intstat & SEQINT) - ahc_handle_seqint(ahc, intstat); + /* + * Requeue all tagged commands for this target + * currently in our posession so they can be + * converted to untagged commands. + */ + ahc_search_qinfifo(ahc, SCB_TARGET(scb), SCB_CHANNEL(scb), + SCB_LUN(scb), /*tag*/SCB_LIST_NULL, + ROLE_INITIATOR, SCB_REQUEUE, + SEARCH_COMPLETE); + } else { + /* + * Otherwise, we ignore it. + */ + printf("%s:%c:%d: Message reject for %x -- ignored\n", + ahc_name(ahc), devinfo->channel, devinfo->target, + last_msg); + } + return (response); +} - if (intstat & SCSIINT) { - - int scb_index = AHC_INB(ahc, SCB_TAG); - status = AHC_INB(ahc, SSTAT1); - scb = ahc->scbarray[scb_index]; - - if (status & SCSIRSTI) { - char channel; - channel = AHC_INB(ahc, SBLKCTL); - channel = channel & SELBUSB ? 'B' : 'A'; - printf("%s: Someone reset channel %c\n", - ahc_name(ahc), channel); - ahc_reset_channel(ahc, - channel, - SCB_LIST_NULL, - XS_BUSY, - /* Initiate Reset */FALSE); - scb = NULL; +STATIC void +ahc_clear_msg_state(ahc) + struct ahc_softc *ahc; +{ + ahc->msgout_len = 0; + ahc->msgin_index = 0; + ahc->msg_type = MSG_TYPE_NONE; + ahc_outb(ahc, MSG_OUT, MSG_NOOP); +} + +STATIC void +ahc_handle_message_phase(ahc, sc_link) + struct ahc_softc *ahc; + struct scsi_link *sc_link; +{ + struct ahc_devinfo devinfo; + u_int bus_phase; + int end_session; + + ahc_fetch_devinfo(ahc, &devinfo); + end_session = FALSE; + bus_phase = ahc_inb(ahc, SCSISIGI) & PHASE_MASK; + +reswitch: + switch (ahc->msg_type) { + case MSG_TYPE_INITIATOR_MSGOUT: + { + int lastbyte; + int phasemis; + int msgdone; + + if (ahc->msgout_len == 0) + panic("REQINIT interrupt with no active message"); + + phasemis = bus_phase != P_MESGOUT; + if (phasemis) { + if (bus_phase == P_MESGIN) { + /* + * Change gears and see if + * this messages is of interest to + * us or should be passed back to + * the sequencer. + */ + ahc_outb(ahc, CLRSINT1, CLRATNO); + ahc->send_msg_perror = FALSE; + ahc->msg_type = MSG_TYPE_INITIATOR_MSGIN; + ahc->msgin_index = 0; + goto reswitch; + } + end_session = TRUE; + break; } - else if (!(scb && (scb->flags & SCB_ACTIVE))){ - printf("%s: ahc_intr - referenced scb not " - "valid during scsiint 0x%x scb(%d)\n", - ahc_name(ahc), status, scb_index); - AHC_OUTB(ahc, CLRSINT1, status); - unpause_sequencer(ahc, /*unpause_always*/TRUE); - AHC_OUTB(ahc, CLRINT, CLRSCSIINT); - scb = NULL; + + if (ahc->send_msg_perror) { + ahc_outb(ahc, CLRSINT1, CLRATNO); + ahc_outb(ahc, CLRSINT1, CLRREQINIT); + ahc_outb(ahc, SCSIDATL, MSG_PARITY_ERROR); + break; } - else if (status & SCSIPERR) { + + msgdone = ahc->msgout_index == ahc->msgout_len; + if (msgdone) { /* - * Determine the bus phase and - * queue an appropriate message + * The target has requested a retry. + * Re-assert ATN, reset our message index to + * 0, and try again. */ - char *phase; - u_char mesg_out = MSG_NOOP; - u_char lastphase = AHC_INB(ahc, LASTPHASE); + ahc->msgout_index = 0; + ahc_outb(ahc, SCSISIGO, ahc_inb(ahc, SCSISIGO) | ATNO); + } - xs = scb->xs; - sc_print_addr(xs->sc_link); + lastbyte = ahc->msgout_index == (ahc->msgout_len - 1); + if (lastbyte) { + /* Last byte is signified by dropping ATN */ + ahc_outb(ahc, CLRSINT1, CLRATNO); + } - switch(lastphase) { - case P_DATAOUT: - phase = "Data-Out"; - break; - case P_DATAIN: - phase = "Data-In"; - mesg_out = MSG_INITIATOR_DET_ERR; - break; - case P_COMMAND: - phase = "Command"; - break; - case P_MESGOUT: - phase = "Message-Out"; - break; - case P_STATUS: - phase = "Status"; - mesg_out = MSG_INITIATOR_DET_ERR; - break; - case P_MESGIN: - phase = "Message-In"; - mesg_out = MSG_PARITY_ERROR; - break; - default: - phase = "unknown"; - break; + /* + * Clear our interrupt status and present + * the next byte on the bus. + */ + ahc_outb(ahc, CLRSINT1, CLRREQINIT); + ahc_outb(ahc, SCSIDATL, ahc->msgout_buf[ahc->msgout_index++]); + break; + } + case MSG_TYPE_INITIATOR_MSGIN: + { + int phasemis; + int message_done; + + phasemis = bus_phase != P_MESGIN; + + if (phasemis) { + ahc->msgin_index = 0; + if (bus_phase == P_MESGOUT + && (ahc->send_msg_perror == TRUE + || (ahc->msgout_len != 0 + && ahc->msgout_index == 0))) { + ahc->msg_type = MSG_TYPE_INITIATOR_MSGOUT; + goto reswitch; } - printf("parity error during %s phase.\n", phase); + end_session = TRUE; + break; + } + + /* Pull the byte in without acking it */ + ahc->msgin_buf[ahc->msgin_index] = ahc_inb(ahc, SCSIBUSL); + + message_done = ahc_parse_msg(ahc, sc_link, &devinfo); + if (message_done) { /* - * We've set the hardware to assert ATN if we - * get a parity error on "in" phases, so all we - * need to do is stuff the message buffer with - * the appropriate message. "In" phases have set - * mesg_out to something other than MSG_NOP. + * Clear our incoming message buffer in case there + * is another message following this one. */ - if(mesg_out != MSG_NOOP) { - AHC_OUTB(ahc, MSG0, mesg_out); - AHC_OUTB(ahc, MSG_LEN, 1); - } - else - /* - * Should we allow the target to make - * this decision for us? - */ - xs->error = XS_DRIVER_STUFFUP; - } - else if (status & SELTO) { - u_char waiting; - u_char flags; + ahc->msgin_index = 0; - xs = scb->xs; - xs->error = XS_SELTIMEOUT; /* - * Clear any pending messages for the timed out - * target, and mark the target as free + * If this message illicited a response, + * assert ATN so the target takes us to the + * message out phase. */ - flags = AHC_INB(ahc, FLAGS); - AHC_OUTB(ahc, MSG_LEN, 0); - ahc_unbusy_target(ahc, xs->sc_link->target, -#if defined(__FreeBSD__) - ((long)xs->sc_link->fordriver & SELBUSB) -#elif defined(__NetBSD__) || defined(__OpenBSD__) - IS_SCSIBUS_B(ahc, xs->sc_link) -#endif - ? 'B' : 'A'); - /* Stop the selection */ - AHC_OUTB(ahc, SCSISEQ, 0); - - AHC_OUTB(ahc, SCB_CONTROL, 0); + if (ahc->msgout_len != 0) + ahc_outb(ahc, SCSISIGO, + ahc_inb(ahc, SCSISIGO) | ATNO); + } else + ahc->msgin_index++; + + /* Ack the byte */ + ahc_outb(ahc, CLRSINT1, CLRREQINIT); + ahc_inb(ahc, SCSIDATL); + break; + } + case MSG_TYPE_TARGET_MSGIN: + { + int msgdone; + int msgout_request; - AHC_OUTB(ahc, CLRSINT1, CLRSELTIMEO); + if (ahc->msgout_len == 0) + panic("Target MSGIN with no active message"); - AHC_OUTB(ahc, CLRINT, CLRSCSIINT); + /* + * If we interrupted a mesgout session, the initiator + * will not know this until our first REQ. So, we + * only honor mesgout requests after we've sent our + * first byte. + */ + if ((ahc_inb(ahc, SCSISIGI) & ATNI) != 0 + && ahc->msgout_index > 0) + msgout_request = TRUE; + else + msgout_request = FALSE; - /* Shift the waiting for selection queue forward */ - waiting = AHC_INB(ahc, WAITING_SCBH); - AHC_OUTB(ahc, SCBPTR, waiting); - waiting = AHC_INB(ahc, SCB_NEXT); - AHC_OUTB(ahc, WAITING_SCBH, waiting); + if (msgout_request) { - restart_sequencer(ahc); - } - else if (!(status & BUSFREE)) { - sc_print_addr(scb->xs->sc_link); - printf("Unknown SCSIINT. Status = 0x%x\n", status); - AHC_OUTB(ahc, CLRSINT1, status); - unpause_sequencer(ahc, /*unpause_always*/TRUE); - AHC_OUTB(ahc, CLRINT, CLRSCSIINT); - scb = NULL; - } - if(scb != NULL) { - /* We want to process the command */ - untimeout(ahc_timeout, (caddr_t)scb); - ahc_done(ahc, scb); + /* + * Change gears and see if + * this messages is of interest to + * us or should be passed back to + * the sequencer. + */ + ahc->msg_type = MSG_TYPE_TARGET_MSGOUT; + ahc_outb(ahc, SCSISIGO, P_MESGOUT | BSYO); + ahc->msgin_index = 0; + /* Dummy read to REQ for first byte */ + ahc_inb(ahc, SCSIDATL); + ahc_outb(ahc, SXFRCTL0, + ahc_inb(ahc, SXFRCTL0) | SPIOEN); + break; } - } - if (intstat & CMDCMPLT) { - int scb_index; - - do { - scb_index = AHC_INB(ahc, QOUTFIFO); - scb = ahc->scbarray[scb_index]; - if (!scb || !(scb->flags & SCB_ACTIVE)) { - printf("%s: WARNING " - "no command for scb %d (cmdcmplt)\n" - "QOUTCNT == %d\n", - ahc_name(ahc), scb_index, - AHC_INB(ahc, QOUTCNT)); - AHC_OUTB(ahc, CLRINT, CLRCMDINT); - continue; - } - AHC_OUTB(ahc, CLRINT, CLRCMDINT); - untimeout(ahc_timeout, (caddr_t)scb); - ahc_done(ahc, scb); - } while (AHC_INB(ahc, QOUTCNT) & ahc->qcntmask); + msgdone = ahc->msgout_index == ahc->msgout_len; + if (msgdone) { + ahc_outb(ahc, SXFRCTL0, + ahc_inb(ahc, SXFRCTL0) & ~SPIOEN); + end_session = TRUE; + break; + } - ahc_run_waiting_queues(ahc); + /* + * Present the next byte on the bus. + */ + ahc_outb(ahc, SXFRCTL0, ahc_inb(ahc, SXFRCTL0) | SPIOEN); + ahc_outb(ahc, SCSIDATL, ahc->msgout_buf[ahc->msgout_index++]); + break; } -#if defined(__NetBSD__) || defined(__OpenBSD__) - return 1; -#endif -} - -static void -ahc_handle_seqint(ahc, intstat) - struct ahc_data *ahc; - u_int8_t intstat; -{ - struct scb *scb; - u_short targ_mask; - u_char target = (AHC_INB(ahc, SCSIID) >> 4) & 0x0f; - u_char scratch_offset = target; - char channel = AHC_INB(ahc, SBLKCTL) & SELBUSB ? 'B': 'A'; - - if (channel == 'B') - scratch_offset += 8; - targ_mask = (0x01 << scratch_offset); + case MSG_TYPE_TARGET_MSGOUT: + { + int lastbyte; + int msgdone; - switch (intstat & SEQINT_MASK) { - case NO_MATCH: - if (ahc->flags & AHC_PAGESCBS) { - /* SCB Page-in request */ - u_char tag; - u_char next; - u_char disc_scb; - struct scb *outscb; - u_char arg_1 = AHC_INB(ahc, ARG_1); + /* + * The initiator signals that this is + * the last byte by dropping ATN. + */ + lastbyte = (ahc_inb(ahc, SCSISIGI) & ATNI) == 0; + /* + * Read the latched byte, but turn off SPIOEN first + * so that we don't inadvertantly cause a REQ for the + * next byte. + */ + ahc_outb(ahc, SXFRCTL0, ahc_inb(ahc, SXFRCTL0) & ~SPIOEN); + ahc->msgin_buf[ahc->msgin_index] = ahc_inb(ahc, SCSIDATL); + msgdone = ahc_parse_msg(ahc, sc_link, &devinfo); + if (msgdone == MSGLOOP_TERMINATED) { /* - * We should succeed, so set this now. - * If we don't, and one of the methods - * we use to aquire an SCB calls ahc_done, - * we may wind up in our start routine - * and unpause the adapter without giving - * it the correct return value, which will - * cause a hang. + * The message is *really* done in that it caused + * us to go to bus free. The sequencer has already + * been reset at this point, so pull the ejection + * handle. */ - AHC_OUTB(ahc, RETURN_1, SCB_PAGEDIN); + return; + } + + ahc->msgin_index++; - if (arg_1 == SCB_LIST_NULL) { - /* Non-tagged command */ - int index; - - index = target|(channel == 'B' ? SELBUSB : 0); - scb = ahc->pagedout_ntscbs[index]; - } else - scb = ahc->scbarray[arg_1]; + /* + * XXX Read spec about initiator dropping ATN too soon + * and use msgdone to detect it. + */ + if (msgdone == MSGLOOP_MSGCOMPLETE) { + ahc->msgin_index = 0; - if (!(scb->flags & SCB_PAGED_OUT)) - panic("%s: Request to page in a non paged out " - "SCB.", ahc_name(ahc)); /* - * Now to pick the SCB to page out. - * Either take a free SCB, an assigned SCB, - * an SCB that just completed, the first - * one on the disconnected SCB list, or - * as a last resort a queued SCB. + * If this message illicited a response, transition + * to the Message in phase and send it. */ - if (ahc->free_scbs.stqh_first) { - outscb = ahc->free_scbs.stqh_first; - STAILQ_REMOVE_HEAD(&ahc->free_scbs, links); - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - STAILQ_INSERT_HEAD(&ahc->page_scbs, outscb, - links); - AHC_OUTB(ahc, SCBPTR, scb->position); - ahc_send_scb(ahc, scb); - scb->flags &= ~SCB_PAGED_OUT; - goto pagein_done; - } - if (intstat & CMDCMPLT) { - int scb_index; - - AHC_OUTB(ahc, CLRINT, CLRCMDINT); - scb_index = AHC_INB(ahc, QOUTFIFO); - if (!(AHC_INB(ahc, QOUTCNT) & ahc->qcntmask)) - intstat &= ~CMDCMPLT; - - outscb = ahc->scbarray[scb_index]; - if (!outscb || !(outscb->flags & SCB_ACTIVE)) { - printf("%s: WARNING no command for " - "scb %d (cmdcmplt)\n", - ahc_name(ahc), - scb_index); - /* - * Fall through in hopes of finding - * another SCB - */ - } else { - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - AHC_OUTB(ahc, SCBPTR, scb->position); - ahc_send_scb(ahc, scb); - scb->flags &= ~SCB_PAGED_OUT; - untimeout(ahc_timeout, - (caddr_t)outscb); - ahc_done(ahc, outscb); - goto pagein_done; - } + if (ahc->msgout_len != 0) { + ahc_outb(ahc, SCSISIGO, P_MESGIN | BSYO); + ahc_outb(ahc, SXFRCTL0, + ahc_inb(ahc, SXFRCTL0) | SPIOEN); + ahc->msg_type = MSG_TYPE_TARGET_MSGIN; + ahc->msgin_index = 0; + break; } - disc_scb = AHC_INB(ahc, DISCONNECTED_SCBH); - if (disc_scb != SCB_LIST_NULL) { - AHC_OUTB(ahc, SCBPTR, disc_scb); - tag = AHC_INB(ahc, SCB_TAG); - outscb = ahc->scbarray[tag]; - next = AHC_INB(ahc, SCB_NEXT); - if (next != SCB_LIST_NULL) { - AHC_OUTB(ahc, SCBPTR, next); - AHC_OUTB(ahc, SCB_PREV, - SCB_LIST_NULL); - AHC_OUTB(ahc, SCBPTR, disc_scb); - } - AHC_OUTB(ahc, DISCONNECTED_SCBH, next); - ahc_page_scb(ahc, outscb, scb); - } else if (AHC_INB(ahc, QINCNT) & ahc->qcntmask) { - /* - * Pull one of our queued commands - * as a last resort - */ - disc_scb = AHC_INB(ahc, QINFIFO); - AHC_OUTB(ahc, SCBPTR, disc_scb); - tag = AHC_INB(ahc, SCB_TAG); - outscb = ahc->scbarray[tag]; - if ((outscb->control & 0x23) != TAG_ENB) { - /* - * This is not a simple tagged command - * so its position in the queue - * matters. Take the command at the - * end of the queue instead. - */ - int i; - u_char saved_queue[AHC_SCB_MAX]; - u_char queued = AHC_INB(ahc, QINCNT) - & ahc->qcntmask; + } - /* - * Count the command we removed - * already - */ - saved_queue[0] = disc_scb; - queued++; + if (lastbyte) + end_session = TRUE; + else { + /* Ask for the next byte. */ + ahc_outb(ahc, SXFRCTL0, + ahc_inb(ahc, SXFRCTL0) | SPIOEN); + } - /* Empty the input queue */ - for (i = 1; i < queued; i++) - saved_queue[i] = AHC_INB(ahc, QINFIFO); + break; + } + default: + panic("Unknown REQINIT message type"); + } - /* - * Put everyone back but the - * last entry - */ - queued--; - for (i = 0; i < queued; i++) - AHC_OUTB(ahc, QINFIFO, - saved_queue[i]); - - AHC_OUTB(ahc, SCBPTR, - saved_queue[queued]); - tag = AHC_INB(ahc, SCB_TAG); - outscb = ahc->scbarray[tag]; - } - untimeout(ahc_timeout, (caddr_t)outscb); - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - STAILQ_INSERT_HEAD(&ahc->waiting_scbs, - outscb, links); - outscb->flags |= SCB_WAITINGQ; - ahc_send_scb(ahc, scb); - scb->flags &= ~SCB_PAGED_OUT; - } - else { - panic("Page-in request with no candidates"); - AHC_OUTB(ahc, RETURN_1, 0); + if (end_session) { + ahc_clear_msg_state(ahc); + ahc_outb(ahc, RETURN_1, EXIT_MSG_LOOP); + } else + ahc_outb(ahc, RETURN_1, CONT_MSG_LOOP); +} + +/* + * See if we sent a particular extended message to the target. + * If "full" is true, the target saw the full message. + * If "full" is false, the target saw at least the first + * byte of the message. + */ +STATIC int +ahc_sent_msg(ahc, msgtype, full) + struct ahc_softc *ahc; + u_int msgtype; + int full; +{ + int found; + int index; + + found = FALSE; + index = 0; + + while (index < ahc->msgout_len) { + if (ahc->msgout_buf[index] == MSG_EXTENDED) { + + /* Found a candidate */ + if (ahc->msgout_buf[index+2] == msgtype) { + u_int end_index; + + end_index = index + 1 + + ahc->msgout_buf[index + 1]; + if (full) { + if (ahc->msgout_index > end_index) + found = TRUE; + } else if (ahc->msgout_index > index) + found = TRUE; } - pagein_done: + break; + } else if (ahc->msgout_buf[index] >= MSG_SIMPLE_Q_TAG + && ahc->msgout_buf[index] <= MSG_IGN_WIDE_RESIDUE) { + + /* Skip tag type and tag id or residue param*/ + index += 2; } else { - printf("%s:%c:%d: no active SCB for reconnecting " - "target - issuing ABORT\n", - ahc_name(ahc), channel, target); - printf("SAVED_TCL == 0x%x\n", - AHC_INB(ahc, SAVED_TCL)); - ahc_unbusy_target(ahc, target, channel); - AHC_OUTB(ahc, SCB_CONTROL, 0); - AHC_OUTB(ahc, CLRSINT1, CLRSELTIMEO); - AHC_OUTB(ahc, RETURN_1, 0); + /* Single byte message */ + index++; } + } + return (found); +} + +STATIC int +ahc_parse_msg(ahc, sc_link, devinfo) + struct ahc_softc *ahc; + struct scsi_link *sc_link; + struct ahc_devinfo *devinfo; +{ + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + int reject; + int done; + int response; + u_int targ_scsirate; + + done = MSGLOOP_IN_PROG; + response = FALSE; + reject = FALSE; + tinfo = ahc_fetch_transinfo(ahc, devinfo->channel, devinfo->our_scsiid, + devinfo->target, &tstate); + targ_scsirate = tinfo->scsirate; + + /* + * Parse as much of the message as is availible, + * rejecting it if we don't support it. When + * the entire message is availible and has been + * handled, return MSGLOOP_MSGCOMPLETE, indicating + * that we have parsed an entire message. + * + * In the case of extended messages, we accept the length + * byte outright and perform more checking once we know the + * extended message type. + */ + switch (ahc->msgin_buf[0]) { + case MSG_MESSAGE_REJECT: + response = ahc_handle_msg_reject(ahc, devinfo); + /* FALLTHROUGH */ + case MSG_NOOP: + done = MSGLOOP_MSGCOMPLETE; break; - case SEND_REJECT: + case MSG_IGN_WIDE_RESIDUE: { - u_char rejbyte = AHC_INB(ahc, REJBYTE); - printf("%s:%c:%d: Warning - unknown message received from " - "target (0x%x). Rejecting\n", - ahc_name(ahc), channel, target, rejbyte); - break; - } - case NO_IDENT: - panic("%s:%c:%d: Target did not send an IDENTIFY message. " - "SAVED_TCL == 0x%x\n", - ahc_name(ahc), channel, target, - AHC_INB(ahc, SAVED_TCL)); + /* Wait for the whole message */ + if (ahc->msgin_index >= 1) { + if (ahc->msgin_buf[1] != 1 + || tinfo->current.width == MSG_EXT_WDTR_BUS_8_BIT) { + reject = TRUE; + done = MSGLOOP_MSGCOMPLETE; + } else + ahc_handle_ign_wide_residue(ahc, devinfo); + } break; - case BAD_PHASE: - printf("%s:%c:%d: unknown scsi bus phase. Attempting to " - "continue\n", ahc_name(ahc), channel, target); - break; - case EXTENDED_MSG: + } + case MSG_EXTENDED: { - u_int8_t message_length; - u_int8_t message_code; - - message_length = AHC_INB(ahc, MSGIN_EXT_LEN); - message_code = AHC_INB(ahc, MSGIN_EXT_OPCODE); - switch(message_code) { + /* Wait for enough of the message to begin validation */ + if (ahc->msgin_index < 2) + break; + switch (ahc->msgin_buf[2]) { case MSG_EXT_SDTR: { - u_int8_t period; - u_int8_t offset; - u_int8_t saved_offset; - u_int8_t targ_scratch; - u_int8_t maxoffset; - u_int8_t rate; + struct ahc_syncrate *syncrate; + u_int period; + u_int offset; + u_int saved_offset; - if (message_length != MSG_EXT_SDTR_LEN) { - AHC_OUTB(ahc, RETURN_1, SEND_REJ); - ahc->sdtrpending &= ~targ_mask; + if (ahc->msgin_buf[1] != MSG_EXT_SDTR_LEN) { + reject = TRUE; break; } - period = AHC_INB(ahc, MSGIN_EXT_BYTE0); - saved_offset = AHC_INB(ahc, MSGIN_EXT_BYTE1); - targ_scratch = AHC_INB(ahc, TARG_SCRATCH - + scratch_offset); - if (targ_scratch & WIDEXFER) - maxoffset = MAX_OFFSET_16BIT; - else - maxoffset = MAX_OFFSET_8BIT; - offset = MIN(saved_offset, maxoffset); - ahc_scsirate(ahc, &rate, &period, &offset, - channel, target); - /* Preserve the WideXfer flag */ - targ_scratch = rate | (targ_scratch & WIDEXFER); /* - * Update both the target scratch area and the - * current SCSIRATE. + * Wait until we have both args before validating + * and acting on this message. + * + * Add one to MSG_EXT_SDTR_LEN to account for + * the extended message preamble. */ - AHC_OUTB(ahc, TARG_SCRATCH + scratch_offset, - targ_scratch); - AHC_OUTB(ahc, SCSIRATE, targ_scratch); + if (ahc->msgin_index < (MSG_EXT_SDTR_LEN + 1)) + break; + + period = ahc->msgin_buf[3]; + saved_offset = offset = ahc->msgin_buf[4]; + syncrate = ahc_devlimited_syncrate(ahc, &period); + ahc_validate_offset(ahc, syncrate, &offset, + targ_scsirate & WIDEXFER); + ahc_set_syncrate(ahc, devinfo, + syncrate, period, offset, + AHC_TRANS_ACTIVE|AHC_TRANS_GOAL, + /*paused*/TRUE, /*done*/TRUE); /* * See if we initiated Sync Negotiation * and didn't have to fall down to async * transfers. */ - if ((ahc->sdtrpending & targ_mask) != 0 - && (saved_offset == offset)) { - /* - * Don't send an SDTR back to - * the target - */ - AHC_OUTB(ahc, RETURN_1, 0); - ahc->needsdtr &= ~targ_mask; - ahc->sdtrpending &= ~targ_mask; + if (ahc_sent_msg(ahc, MSG_EXT_SDTR, /*full*/TRUE)) { + /* We started it */ + if (saved_offset != offset) { + /* Went too low - force async */ + reject = TRUE; + } } else { /* * Send our own SDTR in reply */ -#ifdef AHC_DEBUG - if(ahc_debug & AHC_SHOWMISC) - printf("Sending SDTR!!\n"); -#endif - ahc_construct_sdtr(ahc, /*start_byte*/0, - period, offset); - AHC_OUTB(ahc, RETURN_1, SEND_MSG); - - /* - * If we aren't starting a re-negotiation - * because we had to go async in response - * to a "too low" response from the target - * clear the needsdtr flag for this target. - */ - if ((ahc->sdtrpending & targ_mask) == 0) - ahc->needsdtr &= ~targ_mask; - else - ahc->sdtrpending |= targ_mask; + if (bootverbose) + printf("Sending SDTR!\n"); + ahc->msgout_index = 0; + ahc->msgout_len = 0; + ahc_construct_sdtr(ahc, period, offset); + ahc->msgout_index = 0; + response = TRUE; } + done = MSGLOOP_MSGCOMPLETE; break; } case MSG_EXT_WDTR: { - u_int8_t scratch, bus_width; + u_int bus_width; + u_int sending_reply; - if (message_length != MSG_EXT_WDTR_LEN) { - AHC_OUTB(ahc, RETURN_1, SEND_REJ); - ahc->wdtrpending &= ~targ_mask; + sending_reply = FALSE; + if (ahc->msgin_buf[1] != MSG_EXT_WDTR_LEN) { + reject = TRUE; break; } - bus_width = AHC_INB(ahc, MSGIN_EXT_BYTE0); - scratch = AHC_INB(ahc, TARG_SCRATCH - + scratch_offset); + /* + * Wait until we have our arg before validating + * and acting on this message. + * + * Add one to MSG_EXT_WDTR_LEN to account for + * the extended message preamble. + */ + if (ahc->msgin_index < (MSG_EXT_WDTR_LEN + 1)) + break; - if (ahc->wdtrpending & targ_mask) { + bus_width = ahc->msgin_buf[3]; + if (ahc_sent_msg(ahc, MSG_EXT_WDTR, /*full*/TRUE)) { /* * Don't send a WDTR back to the * target, since we asked first. */ - AHC_OUTB(ahc, RETURN_1, 0); - switch(bus_width){ - case BUS_8_BIT: - scratch &= 0x7f; - break; - case BUS_16_BIT: - if(bootverbose) - printf("%s: target %d using " - "16Bit transfers\n", - ahc_name(ahc), target); - scratch |= WIDEXFER; - break; - case BUS_32_BIT: + switch (bus_width){ + default: /* - * How can we do 32bit transfers - * on a 16bit bus? + * How can we do anything greater + * than 16bit transfers on a 16bit + * bus? */ - AHC_OUTB(ahc, RETURN_1, SEND_REJ); - printf("%s: target %d requested 32Bit " + reject = TRUE; + printf("%s: target %d requested %dBit " "transfers. Rejecting...\n", - ahc_name(ahc), target); + ahc_name(ahc), devinfo->target, + 8 * (0x01 << bus_width)); + /* FALLTHROUGH */ + case MSG_EXT_WDTR_BUS_8_BIT: + bus_width = MSG_EXT_WDTR_BUS_8_BIT; break; - default: + case MSG_EXT_WDTR_BUS_16_BIT: break; } } else { /* * Send our own WDTR in reply */ - switch(bus_width) { - case BUS_8_BIT: - scratch &= 0x7f; - break; - case BUS_32_BIT: - case BUS_16_BIT: - if(ahc->type & AHC_WIDE) { - /* Negotiate 16_BITS */ - bus_width = BUS_16_BIT; - if(bootverbose) - printf("%s: target %d " - "using 16Bit " - "transfers\n", - ahc_name(ahc), - target); - scratch |= WIDEXFER; - } else - bus_width = BUS_8_BIT; - break; + if (bootverbose) + printf("Sending WDTR!\n"); + switch (bus_width) { default: + if (ahc->features & AHC_WIDE) { + /* Respond Wide */ + bus_width = + MSG_EXT_WDTR_BUS_16_BIT; + break; + } + /* FALLTHROUGH */ + case MSG_EXT_WDTR_BUS_8_BIT: + bus_width = MSG_EXT_WDTR_BUS_8_BIT; break; } - ahc_construct_wdtr(ahc, /*start_byte*/0, - bus_width); - AHC_OUTB(ahc, RETURN_1, SEND_MSG); + ahc->msgout_index = 0; + ahc->msgout_len = 0; + ahc_construct_wdtr(ahc, bus_width); + ahc->msgout_index = 0; + response = TRUE; + sending_reply = TRUE; } - - ahc->needwdtr &= ~targ_mask; - ahc->wdtrpending &= ~targ_mask; - AHC_OUTB(ahc, TARG_SCRATCH + scratch_offset, scratch); - AHC_OUTB(ahc, SCSIRATE, scratch); + ahc_set_width(ahc, devinfo, bus_width, + AHC_TRANS_ACTIVE|AHC_TRANS_GOAL, + /*paused*/TRUE, /*done*/TRUE); + + /* After a wide message, we are async */ + ahc_set_syncrate(ahc, devinfo, + /*syncrate*/NULL, /*period*/0, + /*offset*/0, AHC_TRANS_ACTIVE, + /*paused*/TRUE, /*done*/FALSE); + if (sending_reply == FALSE && reject == FALSE) { + + if (tinfo->goal.period) { + struct ahc_syncrate *rate; + u_int period; + u_int offset; + + /* Start the sync negotiation */ + period = tinfo->goal.period; + rate = ahc_devlimited_syncrate(ahc, + &period); + offset = tinfo->goal.offset; + ahc_validate_offset(ahc, rate, &offset, + tinfo->current.width); + ahc->msgout_index = 0; + ahc->msgout_len = 0; + ahc_construct_sdtr(ahc, period, offset); + ahc->msgout_index = 0; + response = TRUE; + } + } + done = MSGLOOP_MSGCOMPLETE; break; } default: /* Unknown extended message. Reject it. */ - AHC_OUTB(ahc, RETURN_1, SEND_REJ); + reject = TRUE; + break; } + break; } - case REJECT_MSG: - { - /* - * What we care about here is if we had an - * outstanding SDTR or WDTR message for this - * target. If we did, this is a signal that - * the target is refusing negotiation. - */ - - u_char targ_scratch; - - targ_scratch = AHC_INB(ahc, TARG_SCRATCH - + scratch_offset); - - if (ahc->wdtrpending & targ_mask){ - /* note 8bit xfers and clear flag */ - targ_scratch &= 0x7f; - ahc->needwdtr &= ~targ_mask; - ahc->wdtrpending &= ~targ_mask; -#if !(defined(__NetBSD__) || defined(__OpenBSD__)) || defined(DEBUG) - printf("%s:%c:%d: refuses WIDE negotiation. Using " - "8bit transfers\n", ahc_name(ahc), - channel, target); -#endif - } else if(ahc->sdtrpending & targ_mask){ - /* note asynch xfers and clear flag */ - targ_scratch &= 0xf0; - ahc->needsdtr &= ~targ_mask; - ahc->sdtrpending &= ~targ_mask; -#if !(defined(__NetBSD__) || defined(__OpenBSD__)) || defined(DEBUG) - printf("%s:%c:%d: refuses synchronous negotiation. " - "Using asynchronous transfers\n", - ahc_name(ahc), - channel, target); -#endif - } else { - /* - * Otherwise, we ignore it. - */ -#ifdef AHC_DEBUG - if(ahc_debug & AHC_SHOWMISC) - printf("%s:%c:%d: Message reject -- ignored\n", - ahc_name(ahc), channel, target); -#endif + case MSG_BUS_DEV_RESET: + ahc_handle_devreset(ahc, devinfo, + XS_RESET, "Bus Device Reset Received", + /*verbose_level*/0); + restart_sequencer(ahc); + done = MSGLOOP_TERMINATED; + break; + case MSG_ABORT_TAG: + case MSG_ABORT: + case MSG_CLEAR_QUEUE: + /* Target mode messages */ + if (devinfo->role != ROLE_TARGET) { + reject = TRUE; break; } - AHC_OUTB(ahc, TARG_SCRATCH + scratch_offset, targ_scratch); - AHC_OUTB(ahc, SCSIRATE, targ_scratch); +#if AHC_TARGET_MODE + ahc_abort_scbs(ahc, devinfo->target, devinfo->channel, + devinfo->lun, + ahc->msgin_buf[0] == MSG_ABORT_TAG ? SCB_LIST_NULL + : ahc_inb(ahc, INITIATOR_TAG), + ROLE_TARGET, XS_DRIVER_STUFFUP); + + tstate = ahc->enabled_targets[devinfo->our_scsiid]; + if (tstate != NULL) { + struct tmode_lstate* lstate; + + lstate = tstate->enabled_luns[devinfo->lun]; + if (lstate != NULL) { + ahc_queue_lstate_event(ahc, lstate, + devinfo->our_scsiid, + ahc->msgin_buf[0], + /*arg*/0); + ahc_send_lstate_events(ahc, lstate); + } + } + done = MSGLOOP_MSGCOMPLETE; +#else + panic("ahc: got target mode message"); +#endif + break; + case MSG_TERM_IO_PROC: + default: + reject = TRUE; break; } - case BAD_STATUS: - { - int scb_index; - struct scsi_xfer *xs; - /* The sequencer will notify us when a command - * has an error that would be of interest to - * the kernel. This allows us to leave the sequencer - * running in the common case of command completes - * without error. + if (reject) { + /* + * Setup to reject the message. */ + ahc->msgout_index = 0; + ahc->msgout_len = 1; + ahc->msgout_buf[0] = MSG_MESSAGE_REJECT; + done = MSGLOOP_MSGCOMPLETE; + response = TRUE; + } - scb_index = AHC_INB(ahc, SCB_TAG); - scb = ahc->scbarray[scb_index]; + if (done != MSGLOOP_IN_PROG && !response) + /* Clear the outgoing message buffer */ + ahc->msgout_len = 0; + return (done); +} + +STATIC void +ahc_handle_ign_wide_residue(ahc, devinfo) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; +{ + u_int scb_index; + struct scb *scb; + + scb_index = ahc_inb(ahc, SCB_TAG); + scb = &ahc->scb_data->scbarray[scb_index]; + if ((ahc_inb(ahc, SEQ_FLAGS) & DPHASE) == 0 + || !(scb->xs->flags & SCSI_DATA_IN)) { /* - * Set the default return value to 0 (don't - * send sense). The sense code will change - * this if needed and this reduces code - * duplication. + * Ignore the message if we haven't + * seen an appropriate data phase yet. */ - AHC_OUTB(ahc, RETURN_1, 0); - if (!(scb && (scb->flags & SCB_ACTIVE))) { - printf("%s:%c:%d: ahc_intr - referenced scb " - "not valid during seqint 0x%x scb(%d)\n", - ahc_name(ahc), - channel, target, intstat, - scb_index); - goto clear; - } + } else { + /* + * If the residual occurred on the last + * transfer and the transfer request was + * expected to end on an odd count, do + * nothing. Otherwise, subtract a byte + * and update the residual count accordingly. + */ + u_int resid_sgcnt; - xs = scb->xs; + resid_sgcnt = ahc_inb(ahc, SCB_RESID_SGCNT); + if (resid_sgcnt == 0 + && ahc_inb(ahc, DATA_COUNT_ODD) == 1) { + /* + * If the residual occurred on the last + * transfer and the transfer request was + * expected to end on an odd count, do + * nothing. + */ + } else { + u_int data_cnt; + u_int data_addr; + u_int sg_index; - scb->status = AHC_INB(ahc, SCB_TARGET_STATUS); + data_cnt = (ahc_inb(ahc, SCB_RESID_DCNT + 2) << 16) + | (ahc_inb(ahc, SCB_RESID_DCNT + 1) << 8) + | (ahc_inb(ahc, SCB_RESID_DCNT)); -#ifdef AHC_DEBUG - if((ahc_debug & AHC_SHOWSCBS) - && xs->sc_link->target == DEBUGTARGET) - ahc_print_scb(scb); -#endif - xs->status = scb->status; - switch(scb->status){ - case SCSI_OK: - printf("%s: Interrupted for staus of" - " 0???\n", ahc_name(ahc)); - break; - case SCSI_CHECK: -#ifdef AHC_DEBUG - if(ahc_debug & AHC_SHOWSENSE) - { + data_addr = (ahc_inb(ahc, SHADDR + 3) << 24) + | (ahc_inb(ahc, SHADDR + 2) << 16) + | (ahc_inb(ahc, SHADDR + 1) << 8) + | (ahc_inb(ahc, SHADDR)); - sc_print_addr(xs->sc_link); - printf("requests Check Status\n"); - } -#endif + data_cnt += 1; + data_addr -= 1; - if ((xs->error == XS_NOERROR) - && !(scb->flags & SCB_SENSE)) { - struct ahc_dma_seg *sg = scb->ahc_dma; - struct scsi_sense *sc = &(scb->sense_cmd); -#ifdef AHC_DEBUG - if (ahc_debug & AHC_SHOWSENSE) - { - sc_print_addr(xs->sc_link); - printf("Sending Sense\n"); - } -#endif -#if defined(__FreeBSD__) - sc->op_code = REQUEST_SENSE; -#elif defined(__NetBSD__) || defined(__OpenBSD__) - sc->opcode = REQUEST_SENSE; -#endif - sc->byte2 = xs->sc_link->lun << 5; - sc->length = sizeof(struct scsi_sense_data); - sc->control = 0; - sg->addr = KVTOPHYS(&xs->sense); - sg->len = sizeof(struct scsi_sense_data); + sg_index = scb->sg_count - resid_sgcnt; - scb->control &= DISCENB; - scb->status = 0; - scb->SG_segment_count = 1; - scb->SG_list_pointer = KVTOPHYS(sg); - scb->data = sg->addr; - scb->datalen = sg->len; -#ifdef AHC_BROKEN_CACHE - if (ahc_broken_cache) - INVALIDATE_CACHE(); -#endif - scb->cmdpointer = KVTOPHYS(sc); - scb->cmdlen = sizeof(*sc); - - scb->flags |= SCB_SENSE; - ahc_send_scb(ahc, scb); - /* - * Ensure that the target is "BUSY" - * so we don't get overlapping - * commands if we happen to be doing - * tagged I/O. - */ - ahc_busy_target(ahc, target, channel); + if (sg_index != 0 + && (scb->sg_list[sg_index].len < data_cnt)) { + u_int sg_addr; + sg_index--; + data_cnt = 1; + data_addr = scb->sg_list[sg_index].addr + + scb->sg_list[sg_index].len - 1; + /* - * Make us the next command to run + * The physical address base points to the + * second entry as it is always used for + * calculating the "next S/G pointer". */ - ahc_add_waiting_scb(ahc, scb); - AHC_OUTB(ahc, RETURN_1, SEND_SENSE); - break; + sg_addr = scb->sg_list_phys + + (sg_index* sizeof(*scb->sg_list)); + ahc_outb(ahc, SG_NEXT + 3, sg_addr >> 24); + ahc_outb(ahc, SG_NEXT + 2, sg_addr >> 16); + ahc_outb(ahc, SG_NEXT + 1, sg_addr >> 8); + ahc_outb(ahc, SG_NEXT, sg_addr); } - /* - * Clear the SCB_SENSE Flag and have - * the sequencer do a normal command - * complete with either a "DRIVER_STUFFUP" - * error or whatever other error condition - * we already had. - */ - scb->flags &= ~SCB_SENSE; - if (xs->error == XS_NOERROR) - xs->error = XS_DRIVER_STUFFUP; - break; - case SCSI_BUSY: - xs->error = XS_BUSY; - sc_print_addr(xs->sc_link); - printf("Target Busy\n"); - break; - case SCSI_QUEUE_FULL: - /* - * The upper level SCSI code will someday - * handle this properly. - */ - sc_print_addr(xs->sc_link); - printf("Queue Full\n"); - scb->flags |= SCB_ASSIGNEDQ; - STAILQ_INSERT_TAIL(&ahc->assigned_scbs,scb, links); - AHC_OUTB(ahc, RETURN_1, SEND_SENSE); - break; - default: - sc_print_addr(xs->sc_link); - printf("unexpected targ_status: %x\n", scb->status); - xs->error = XS_DRIVER_STUFFUP; - break; - } - break; - } - case RESIDUAL: - { - int scb_index; - struct scsi_xfer *xs; - scb_index = AHC_INB(ahc, SCB_TAG); - scb = ahc->scbarray[scb_index]; - xs = scb->xs; - /* - * Don't clobber valid resid info with - * a resid coming from a check sense - * operation. - */ - if (!(scb->flags & SCB_SENSE)) { - int resid_sgs; - - /* - * Remainder of the SG where the transfer - * stopped. - */ - xs->resid = (AHC_INB(ahc, SCB_RESID_DCNT2)<<16) | - (AHC_INB(ahc, SCB_RESID_DCNT1)<<8) | - AHC_INB(ahc, SCB_RESID_DCNT0); - - /* - * Add up the contents of all residual - * SG segments that are after the SG where - * the transfer stopped. - */ - resid_sgs = AHC_INB(ahc, SCB_RESID_SGCNT) - 1; - while (resid_sgs > 0) { - int sg; - - sg = scb->SG_segment_count - resid_sgs; - xs->resid += scb->ahc_dma[sg].len; - resid_sgs--; - } + ahc_outb(ahc, SCB_RESID_DCNT + 2, data_cnt >> 16); + ahc_outb(ahc, SCB_RESID_DCNT + 1, data_cnt >> 8); + ahc_outb(ahc, SCB_RESID_DCNT, data_cnt); -#if defined(__FreeBSD__) - xs->flags |= SCSI_RESID_VALID; -#elif defined(__NetBSD__) || defined(__OpenBSD__) - /* XXX - Update to do this right */ -#endif -#ifdef AHC_DEBUG - if (ahc_debug & AHC_SHOWMISC) { - sc_print_addr(xs->sc_link); - printf("Handled Residual of %ld bytes\n" - ,xs->resid); - } -#endif + ahc_outb(ahc, SHADDR + 3, data_addr >> 24); + ahc_outb(ahc, SHADDR + 2, data_addr >> 16); + ahc_outb(ahc, SHADDR + 1, data_addr >> 8); + ahc_outb(ahc, SHADDR, data_addr); } - break; } - case ABORT_TAG: - { - int scb_index; - struct scsi_xfer *xs; +} - scb_index = AHC_INB(ahc, SCB_TAG); - scb = ahc->scbarray[scb_index]; - xs = scb->xs; - /* - * We didn't recieve a valid tag back from - * the target on a reconnect. - */ - sc_print_addr(xs->sc_link); - printf("invalid tag received -- sending ABORT_TAG\n"); - xs->error = XS_DRIVER_STUFFUP; - untimeout(ahc_timeout, (caddr_t)scb); - ahc_done(ahc, scb); - break; - } - case AWAITING_MSG: - { - int scb_index; - scb_index = AHC_INB(ahc, SCB_TAG); - scb = ahc->scbarray[scb_index]; - /* - * This SCB had a zero length command, informing - * the sequencer that we wanted to send a special - * message to this target. We only do this for - * BUS_DEVICE_RESET messages currently. - */ - if (scb->flags & SCB_DEVICE_RESET) { - AHC_OUTB(ahc, MSG0, - MSG_BUS_DEV_RESET); - AHC_OUTB(ahc, MSG_LEN, 1); - printf("Bus Device Reset Message Sent\n"); - } else if (scb->flags & SCB_MSGOUT_WDTR) { - ahc_construct_wdtr(ahc, AHC_INB(ahc, MSG_LEN), - BUS_16_BIT); - } else if (scb->flags & SCB_MSGOUT_SDTR) { - u_int8_t target_scratch; - u_int8_t ultraenable; - int sxfr; - int i; +STATIC void +ahc_handle_devreset(ahc, devinfo, status, message, verbose_level) + struct ahc_softc *ahc; + struct ahc_devinfo *devinfo; + int status; + char *message; + int verbose_level; +{ + int found; - /* Pull the user defined setting */ - target_scratch = AHC_INB(ahc, TARG_SCRATCH - + scratch_offset); - - sxfr = target_scratch & SXFR; - if (scratch_offset < 8) - ultraenable = AHC_INB(ahc, ULTRA_ENB); - else - ultraenable = AHC_INB(ahc, ULTRA_ENB + 1); - - if (ultraenable & targ_mask) - /* Want an ultra speed in the table */ - sxfr |= 0x100; - - for (i = 0; i < ahc_num_syncrates; i++) - if (sxfr == ahc_syncrates[i].sxfr) - break; - - ahc_construct_sdtr(ahc, AHC_INB(ahc, MSG_LEN), - ahc_syncrates[i].period, - target_scratch & WIDEXFER ? - MAX_OFFSET_16BIT : MAX_OFFSET_8BIT); - } else - panic("ahc_intr: AWAITING_MSG for an SCB that " - "does not have a waiting message"); - break; - } - case IMMEDDONE: - { - /* - * Take care of device reset messages - */ - u_char scbindex = AHC_INB(ahc, SCB_TAG); - scb = ahc->scbarray[scbindex]; - if (scb->flags & SCB_DEVICE_RESET) { - u_char targ_scratch; - int found; - /* - * Go back to async/narrow transfers and - * renegotiate. - */ - ahc_unbusy_target(ahc, target, channel); - ahc->needsdtr |= ahc->needsdtr_orig & targ_mask; - ahc->needwdtr |= ahc->needwdtr_orig & targ_mask; - ahc->sdtrpending &= ~targ_mask; - ahc->wdtrpending &= ~targ_mask; - targ_scratch = AHC_INB(ahc, TARG_SCRATCH - + scratch_offset); - targ_scratch &= SXFR; - AHC_OUTB(ahc, TARG_SCRATCH + scratch_offset, - targ_scratch); - found = ahc_reset_device(ahc, target, - channel, SCB_LIST_NULL, - XS_NOERROR); - sc_print_addr(scb->xs->sc_link); - printf("Bus Device Reset delivered. " - "%d SCBs aborted\n", found); - ahc->in_timeout = FALSE; - ahc_run_done_queue(ahc); - } else - panic("ahc_intr: Immediate complete for " - "unknown operation."); - break; - } - case DATA_OVERRUN: - { - /* - * When the sequencer detects an overrun, it - * sets STCNT to 0x00ffffff and allows the - * target to complete its transfer in - * BITBUCKET mode. - */ - u_char scbindex = AHC_INB(ahc, SCB_TAG); - u_int32_t overrun; - scb = ahc->scbarray[scbindex]; - overrun = AHC_INB(ahc, STCNT0) - | (AHC_INB(ahc, STCNT1) << 8) - | (AHC_INB(ahc, STCNT2) << 16); - overrun = 0x00ffffff - overrun; - sc_print_addr(scb->xs->sc_link); - printf("data overrun of %d bytes detected." - " Forcing a retry.\n", overrun); - /* - * Set this and it will take affect when the - * target does a command complete. - */ - scb->xs->error = XS_DRIVER_STUFFUP; - break; - } -#if NOT_YET - /* XXX Fill these in later */ - case MESG_BUFFER_BUSY: - break; - case MSGIN_PHASEMIS: - break; -#endif - default: - printf("ahc_intr: seqint, " - "intstat == 0x%x, scsisigi = 0x%x\n", - intstat, AHC_INB(ahc, SCSISIGI)); - break; - } + found = ahc_abort_scbs(ahc, devinfo->target, devinfo->channel, + ALL_LUNS, SCB_LIST_NULL, devinfo->role, + status); -clear: /* - * Clear the upper byte that holds SEQINT status - * codes and clear the SEQINT bit. + * Go back to async/narrow transfers and renegotiate. + * ahc_set_width and ahc_set_syncrate can cope with NULL + * paths. */ - AHC_OUTB(ahc, CLRINT, CLRSEQINT); - - /* - * The sequencer is paused immediately on - * a SEQINT, so we should restart it when - * we're done. - */ - unpause_sequencer(ahc, /*unpause_always*/TRUE); + ahc_set_width(ahc, devinfo, MSG_EXT_WDTR_BUS_8_BIT, + AHC_TRANS_CUR, /*paused*/TRUE, /*done*/FALSE); + ahc_set_syncrate(ahc, devinfo, /*syncrate*/NULL, + /*period*/0, /*offset*/0, AHC_TRANS_CUR, + /*paused*/TRUE, /*done*/FALSE); + + if (message != NULL + && (verbose_level <= bootverbose)) + printf("%s: %s on %c:%d. %d SCBs aborted\n", ahc_name(ahc), + message, devinfo->channel, devinfo->target, found); } /* - * We have a scb which has been processed by the + * We have an scb which has been processed by the * adaptor, now we look to see how the operation * went. */ -static void +STATIC void ahc_done(ahc, scb) - struct ahc_data *ahc; + struct ahc_softc *ahc; struct scb *scb; { struct scsi_xfer *xs = scb->xs; + struct scsi_link *sc_link = xs->sc_link; + int requeue = 0; + int target; SC_DEBUG(xs->sc_link, SDEV_DB2, ("ahc_done\n")); + + LIST_REMOVE(scb, pend_links); + + untimeout(ahc_timeout, (caddr_t)scb); + +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWCMDS) { + sc_print_addr(sc_link); + printf("ahc_done opcode %d tag %x\n", xs->cmdstore.opcode, + scb->hscb->tag); + } +#endif + + target = sc_link->target; + + if (xs->datalen) { + int op; + + if ((xs->flags & SCSI_DATA_IN) != 0) + op = BUS_DMASYNC_POSTREAD; + else + op = BUS_DMASYNC_POSTWRITE; + bus_dmamap_sync(ahc->sc_dmat, scb->dmamap, op); + bus_dmamap_unload(ahc->sc_dmat, scb->dmamap); + } + /* - * Put the results of the operation - * into the xfer and call whoever started it + * Unbusy this target/channel/lun. + * XXX if we are holding two commands per lun, + * send the next command. */ -#if defined(__NetBSD__) || defined(__OpenBSD__) - if (xs->error != XS_NOERROR) { - /* Don't override the error value. */ - } else if (scb->flags & SCB_ABORTED) { - xs->error = XS_DRIVER_STUFFUP; - } else -#endif - if(scb->flags & SCB_SENSE) - xs->error = XS_SENSE; - if(scb->flags & SCB_SENTORDEREDTAG) - ahc->in_timeout = FALSE; -#if defined(__FreeBSD__) - if ((xs->flags & SCSI_ERR_OK) && !(xs->error == XS_SENSE)) { - /* All went correctly OR errors expected */ - xs->error = XS_NOERROR; - } -#elif defined(__NetBSD__) || defined(__OpenBSD__) + ahc_index_busy_tcl(ahc, scb->hscb->tcl, /*unbusy*/TRUE); + /* - * Since NetBSD nor OpenBSD doesn't have error ignoring operation mode - * (SCSI_ERR_OK in FreeBSD), we don't have to care this case. + * If the recovery SCB completes, we have to be + * out of our timeout. */ -#endif - xs->flags |= ITSDONE; -#ifdef AHC_TAGENABLE - if(xs->cmd->opcode == INQUIRY && xs->error == XS_NOERROR) - { - struct scsi_inquiry_data *inq_data; - u_short mask = 0x01 << (xs->sc_link->target | - (scb->tcl & 0x08)); + if ((scb->flags & SCB_RECOVERY_SCB) != 0) { + + struct scb *scbp; + /* - * Sneak a look at the results of the SCSI Inquiry - * command and see if we can do Tagged queing. This - * should really be done by the higher level drivers. + * We were able to complete the command successfully, + * so reinstate the timeouts for all other pending + * commands. */ - inq_data = (struct scsi_inquiry_data *)xs->data; - if((inq_data->flags & SID_CmdQue) && !(ahc->tagenable & mask)) - { - printf("%s: target %d Tagged Queuing Device\n", - ahc_name(ahc), xs->sc_link->target); - ahc->tagenable |= mask; - if(ahc->maxhscbs >= 16 || (ahc->flags & AHC_PAGESCBS)) { - /* Default to 8 tags */ - xs->sc_link->opennings += 6; - } - else - { - /* - * Default to 4 tags on whimpy - * cards that don't have much SCB - * space and can't page. This prevents - * a single device from hogging all - * slots. We should really have a better - * way of providing fairness. - */ - xs->sc_link->opennings += 2; + scbp = ahc->pending_scbs.lh_first; + while (scbp != NULL) { + struct scsi_xfer *txs = scbp->xs; + + if (!(txs->flags & SCSI_POLL)) { + timeout(ahc_timeout, scbp, + (scbp->xs->timeout * hz)/1000); } + scbp = LIST_NEXT(scbp, pend_links); } + + /* + * Ensure that we didn't put a second instance of this + * SCB into the QINFIFO. + */ + ahc_search_qinfifo(ahc, SCB_TARGET(scb), SCB_CHANNEL(scb), + SCB_LUN(scb), scb->hscb->tag, + ROLE_INITIATOR, /*status*/0, + SEARCH_REMOVE); + if (xs->error != XS_NOERROR) + ahcsetccbstatus(xs, XS_TIMEOUT); + sc_print_addr(xs->sc_link); + printf("no longer in timeout, status = %x\n", xs->status); + } + + if (xs->error != XS_NOERROR) { + /* Don't clobber any existing error state */ + } else if ((scb->flags & SCB_SENSE) != 0) { + /* + * We performed autosense retrieval. + * + * bzero the sense data before having + * the drive fill it. The SCSI spec mandates + * that any untransfered data should be + * assumed to be zero. Complete the 'bounce' + * of sense information through buffers accessible + * via bus-space by copying it into the clients + * csio. + */ + bzero(&xs->sense, sizeof(struct scsi_sense)); + bcopy(&ahc->scb_data->sense[scb->hscb->tag], + &xs->sense, scb->sg_list->len); + xs->error = XS_SENSE; + } + if (scb->flags & SCB_FREEZE_QUEUE) { + ahc->devqueue_blocked[target]--; + scb->flags &= ~SCB_FREEZE_QUEUE; + } + + requeue = scb->flags & SCB_REQUEUE; + ahcfreescb(ahc, scb); + + if (requeue) { + /* + * Re-insert at the front of the private queue to + * preserve order. + */ + int s; + + s = splbio(); + /* TAILQ_INSERT_HEAD(&ahc->sc_q, xs, adapter_q); */ + ahc_list_insert_head(ahc, xs); + splx(s); + } else { + xs->flags |= ITSDONE; + scsi_done(xs); } -#endif - ahc_free_scb(ahc, scb, xs->flags); - scsi_done(xs); -#if defined(__NetBSD__) || defined(__OpenBSD__) /* XXX */ /* * If there are entries in the software queue, try to * run the first one. We should be more or less guaranteed @@ -2029,9 +3521,30 @@ ahc_done(ahc, scb) * NOTE: ahc_scsi_cmd() relies on our calling it with * the first entry in the queue. */ - if (ahc->sc_xxxq.lh_first != NULL) - (void) ahc_scsi_cmd(ahc->sc_xxxq.lh_first); -#endif /* __NetBSD__ || __OpenBSD__ */ + if ((xs = ahc->sc_xxxq.lh_first) != NULL) + (void) ahc_scsi_cmd(xs); +} + +/* + * Determine the number of SCBs available on the controller + */ +int +ahc_probe_scbs(ahc) + struct ahc_softc *ahc; +{ + int i; + + for (i = 0; i < AHC_SCB_MAX; i++) { + ahc_outb(ahc, SCBPTR, i); + ahc_outb(ahc, SCB_CONTROL, i); + if (ahc_inb(ahc, SCB_CONTROL) != i) + break; + ahc_outb(ahc, SCBPTR, 0); + if (ahc_inb(ahc, SCB_CONTROL) != 0) + break; + } + + return (i); } /* @@ -2039,435 +3552,555 @@ ahc_done(ahc, scb) */ int ahc_init(ahc) - struct ahc_data *ahc; + struct ahc_softc *ahc; { - u_int8_t scsi_conf, sblkctl, i; - u_int16_t ultraenable = 0; int max_targ = 15; + int i; + int term; + u_int scsi_conf; + u_int scsiseq_template; + u_int ultraenb; + u_int discenable; + u_int tagenable; + size_t driver_data_size; + u_int32_t physaddr; + struct scb_data *scb_data = NULL; + +#ifdef AHC_PRINT_SRAM + printf("Scratch Ram:"); + for (i = 0x20; i < 0x5f; i++) { + if (((i % 8) == 0) && (i != 0)) { + printf ("\n "); + } + printf (" 0x%x", ahc_inb(ahc, i)); + } + if ((ahc->features & AHC_MORE_SRAM) != 0) { + for (i = 0x70; i < 0x7f; i++) { + if (((i % 8) == 0) && (i != 0)) { + printf ("\n "); + } + printf (" 0x%x", ahc_inb(ahc, i)); + } + } + printf ("\n"); +#endif + + if (ahc->scb_data == NULL) { + scb_data = malloc(sizeof (struct scb_data), M_DEVBUF, M_NOWAIT); + if (scb_data == NULL) { + printf("%s: cannot malloc scb_data!\n", ahc_name(ahc)); + return (ENOMEM); + } + bzero(scb_data, sizeof(struct scb_data)); + ahc->scb_data = scb_data; + } /* * Assume we have a board at this stage and it has been reset. */ + if ((ahc->flags & AHC_USEDEFAULTS) != 0) + ahc->our_id = ahc->our_id_b = 7; + + /* + * Default to allowing initiator operations. + */ + ahc->flags |= AHC_INITIATORMODE; + + /* + * DMA tag for our command fifos and other data in system memory + * the card's sequencer must be able to access. For initiator + * roles, we need to allocate space for the qinfifo, qoutfifo, + * and untagged_scb arrays each of which are composed of 256 + * 1 byte elements. When providing for the target mode role, + * we additionally must provide space for the incoming target + * command fifo. + */ + driver_data_size = 3 * 256 * sizeof(u_int8_t); - /* Handle the SCBPAGING option */ -#ifndef AHC_SCBPAGING_ENABLE - ahc->flags &= ~AHC_PAGESCBS; -#endif + if (ahc_createdmamem(ahc, driver_data_size, + &ahc->shared_data_dmamap, (caddr_t *)&ahc->qoutfifo, + &ahc->shared_data_busaddr, &ahc->shared_data_seg, + &ahc->shared_data_nseg, "shared data") < 0) + return (ENOMEM); - /* Determine channel configuration and who we are on the scsi bus. */ - switch ( (sblkctl = AHC_INB(ahc, SBLKCTL) & 0x0a) ) { - case 0: - ahc->our_id = (AHC_INB(ahc, SCSICONF) & HSCSIID); - ahc->flags &= ~AHC_CHANNEL_B_PRIMARY; - if(ahc->type == AHC_394) - printf("Channel %c, SCSI Id=%d, ", - ahc->flags & AHC_CHNLB ? 'B' : 'A', - ahc->our_id); - else - printf("Single Channel, SCSI Id=%d, ", ahc->our_id); - AHC_OUTB(ahc, FLAGS, SINGLE_BUS | (ahc->flags & AHC_PAGESCBS)); - break; - case 2: - ahc->our_id = (AHC_INB(ahc, SCSICONF + 1) & HWSCSIID); - ahc->flags &= ~AHC_CHANNEL_B_PRIMARY; - if(ahc->type == AHC_394) - printf("Wide Channel %c, SCSI Id=%d, ", - ahc->flags & AHC_CHNLB ? 'B' : 'A', - ahc->our_id); - else - printf("Wide Channel, SCSI Id=%d, ", ahc->our_id); - ahc->type |= AHC_WIDE; - AHC_OUTB(ahc, FLAGS, WIDE_BUS | (ahc->flags & AHC_PAGESCBS)); - break; - case 8: - ahc->our_id = (AHC_INB(ahc, SCSICONF) & HSCSIID); - ahc->our_id_b = (AHC_INB(ahc, SCSICONF + 1) & HSCSIID); - printf("Twin Channel, A SCSI Id=%d, B SCSI Id=%d, ", - ahc->our_id, ahc->our_id_b); - ahc->type |= AHC_TWIN; - AHC_OUTB(ahc, FLAGS, TWIN_BUS | (ahc->flags & AHC_PAGESCBS)); - break; - default: - printf(" Unsupported adapter type. Ignoring\n"); - return(-1); - } + ahc->init_level++; - /* Determine the number of SCBs */ + /* Allocate SCB data now that sc_dmat is initialized */ + if (ahc->scb_data->maxhscbs == 0) + if (ahcinitscbdata(ahc) != 0) + return (ENOMEM); - { - AHC_OUTB(ahc, SCBPTR, 0); - AHC_OUTB(ahc, SCB_CONTROL, 0); - for(i = 1; i < AHC_SCB_MAX; i++) { - AHC_OUTB(ahc, SCBPTR, i); - AHC_OUTB(ahc, SCB_CONTROL, i); - if(AHC_INB(ahc, SCB_CONTROL) != i) - break; - AHC_OUTB(ahc, SCBPTR, 0); - if(AHC_INB(ahc, SCB_CONTROL) != 0) - break; - /* Clear the control byte. */ - AHC_OUTB(ahc, SCBPTR, i); - AHC_OUTB(ahc, SCB_CONTROL, 0); + ahc->qinfifo = &ahc->qoutfifo[256]; + ahc->untagged_scbs = &ahc->qinfifo[256]; + /* There are no untagged SCBs active yet. */ + for (i = 0; i < 256; i++) + ahc->untagged_scbs[i] = SCB_LIST_NULL; - ahc->qcntmask |= i; /* Update the count mask. */ - } + /* All of our queues are empty */ + for (i = 0; i < 256; i++) + ahc->qoutfifo[i] = SCB_LIST_NULL; - /* Ensure we clear the 0 SCB's control byte. */ - AHC_OUTB(ahc, SCBPTR, 0); - AHC_OUTB(ahc, SCB_CONTROL, 0); + /* + * Allocate a tstate to house information for our + * initiator presence on the bus as well as the user + * data for any target mode initiator. + */ + if (ahc_alloc_tstate(ahc, ahc->our_id, 'A') == NULL) { + printf("%s: unable to allocate tmode_tstate. " + "Failing attach\n", ahc_name(ahc)); + return (-1); + } - ahc->qcntmask |= i; - ahc->maxhscbs = i; + if ((ahc->features & AHC_TWIN) != 0) { + if (ahc_alloc_tstate(ahc, ahc->our_id_b, 'B') == NULL) { + printf("%s: unable to allocate tmode_tstate. " + "Failing attach\n", ahc_name(ahc)); + return (-1); + } + printf("Twin Channel, A SCSI Id=%d, B SCSI Id=%d, primary %c, ", + ahc->our_id, ahc->our_id_b, + ahc->flags & AHC_CHANNEL_B_PRIMARY? 'B': 'A'); + } else { + if ((ahc->features & AHC_WIDE) != 0) { + printf("Wide "); + } else { + printf("Single "); + } + printf("Channel %c, SCSI Id=%d, ", ahc->channel, ahc->our_id); } - if((ahc->maxhscbs < AHC_SCB_MAX) && (ahc->flags & AHC_PAGESCBS)) - ahc->maxscbs = AHC_SCB_MAX; - else { - ahc->maxscbs = ahc->maxhscbs; + ahc_outb(ahc, SEQ_FLAGS, 0); + + if (ahc->scb_data->maxhscbs < AHC_SCB_MAX) { + ahc->flags |= AHC_PAGESCBS; + printf("%d/%d SCBs\n", ahc->scb_data->maxhscbs, AHC_SCB_MAX); + } else { ahc->flags &= ~AHC_PAGESCBS; + printf("%d SCBs\n", ahc->scb_data->maxhscbs); } - printf("%d SCBs\n", ahc->maxhscbs); - #ifdef AHC_DEBUG - if(ahc_debug & AHC_SHOWMISC) { - struct scb test; - printf("%s: hardware scb %ld bytes; kernel scb; " + if (ahc_debug & AHC_SHOWMISC) { + printf("%s: hardware scb %d bytes; kernel scb %d bytes; " "ahc_dma %d bytes\n", ahc_name(ahc), - (u_long)&(test.next) - (u_long)(&test), - sizeof(test), + sizeof(struct hardware_scb), + sizeof(struct scb), sizeof(struct ahc_dma_seg)); } #endif /* AHC_DEBUG */ - /* Set the SCSI Id, SXFRCTL0, SXFRCTL1, and SIMODE1, for both channels*/ - if(ahc->type & AHC_TWIN) - { + /* Set the SCSI Id,SXFRCTL0,SXFRCTL1, and SIMODE1, for both channels*/ + if (ahc->features & AHC_TWIN) { + /* * The device is gated to channel B after a chip reset, * so set those values first */ - AHC_OUTB(ahc, SCSIID, ahc->our_id_b); - scsi_conf = AHC_INB(ahc, SCSICONF + 1); - AHC_OUTB(ahc, SXFRCTL1, (scsi_conf & (ENSPCHK|STIMESEL)) - | ENSTIMER|ACTNEGEN|STPWEN); - AHC_OUTB(ahc, SIMODE1, ENSELTIMO|ENSCSIRST|ENSCSIPERR); - if(ahc->type & AHC_ULTRA) - AHC_OUTB(ahc, SXFRCTL0, DFON|SPIOEN|ULTRAEN); + term = (ahc->flags & AHC_TERM_ENB_B) != 0 ? STPWEN : 0; + if ((ahc->features & AHC_ULTRA2) != 0) + ahc_outb(ahc, SCSIID_ULTRA2, ahc->our_id_b); else - AHC_OUTB(ahc, SXFRCTL0, DFON|SPIOEN); + ahc_outb(ahc, SCSIID, ahc->our_id_b); + scsi_conf = ahc_inb(ahc, SCSICONF + 1); + ahc_outb(ahc, SXFRCTL1, (scsi_conf & (ENSPCHK|STIMESEL)) + |term|ENSTIMER|ACTNEGEN); + ahc_outb(ahc, SIMODE1, ENSELTIMO|ENSCSIRST|ENSCSIPERR); + ahc_outb(ahc, SXFRCTL0, DFON|SPIOEN); - if(scsi_conf & RESET_SCSI) { - /* Reset the bus */ -#if (!defined(__NetBSD__) && !defined(__OpenBSD__)) || defined(DEBUG) - if(bootverbose) - printf("%s: Resetting Channel B\n", - ahc_name(ahc)); -#endif - AHC_OUTB(ahc, SCSISEQ, SCSIRSTO); - DELAY(1000); - AHC_OUTB(ahc, SCSISEQ, 0); - - /* Ensure we don't get a RSTI interrupt from this */ - AHC_OUTB(ahc, CLRSINT1, CLRSCSIRSTI); - AHC_OUTB(ahc, CLRINT, CLRSCSIINT); - } + if ((scsi_conf & RESET_SCSI) != 0 + && (ahc->flags & AHC_INITIATORMODE) != 0) + ahc->flags |= AHC_RESET_BUS_B; /* Select Channel A */ - AHC_OUTB(ahc, SBLKCTL, 0); - } - AHC_OUTB(ahc, SCSIID, ahc->our_id); - scsi_conf = AHC_INB(ahc, SCSICONF); - AHC_OUTB(ahc, SXFRCTL1, (scsi_conf & (ENSPCHK|STIMESEL)) - | ENSTIMER|ACTNEGEN|STPWEN); - AHC_OUTB(ahc, SIMODE1, ENSELTIMO|ENSCSIRST|ENSCSIPERR); - if(ahc->type & AHC_ULTRA) - AHC_OUTB(ahc, SXFRCTL0, DFON|SPIOEN|ULTRAEN); - else - AHC_OUTB(ahc, SXFRCTL0, DFON|SPIOEN); - - if(scsi_conf & RESET_SCSI) { - /* Reset the bus */ -#if (!defined(__NetBSD__) && !defined(__OpenBSD__)) || defined(DEBUG) - if(bootverbose) - printf("%s: Resetting Channel A\n", ahc_name(ahc)); -#endif - - AHC_OUTB(ahc, SCSISEQ, SCSIRSTO); - DELAY(1000); - AHC_OUTB(ahc, SCSISEQ, 0); - - /* Ensure we don't get a RSTI interrupt from this */ - AHC_OUTB(ahc, CLRSINT1, CLRSCSIRSTI); - AHC_OUTB(ahc, CLRINT, CLRSCSIINT); + ahc_outb(ahc, SBLKCTL, ahc_inb(ahc, SBLKCTL) & ~SELBUSB); } + term = (ahc->flags & AHC_TERM_ENB_A) != 0 ? STPWEN : 0; + if ((ahc->features & AHC_ULTRA2) != 0) + ahc_outb(ahc, SCSIID_ULTRA2, ahc->our_id); + else + ahc_outb(ahc, SCSIID, ahc->our_id); + scsi_conf = ahc_inb(ahc, SCSICONF); + ahc_outb(ahc, SXFRCTL1, (scsi_conf & (ENSPCHK|STIMESEL)) + |term + |ENSTIMER|ACTNEGEN); + ahc_outb(ahc, SIMODE1, ENSELTIMO|ENSCSIRST|ENSCSIPERR); + ahc_outb(ahc, SXFRCTL0, DFON|SPIOEN); + + if ((scsi_conf & RESET_SCSI) != 0 + && (ahc->flags & AHC_INITIATORMODE) != 0) + ahc->flags |= AHC_RESET_BUS_A; /* * Look at the information that board initialization or - * the board bios has left us. In the lower four bits of each - * target's scratch space any value other than 0 indicates - * that we should initiate synchronous transfers. If it's zero, - * the user or the BIOS has decided to disable synchronous - * negotiation to that target so we don't activate the needsdtr - * flag. + * the board bios has left us. */ - ahc->needsdtr_orig = 0; - ahc->needwdtr_orig = 0; + ultraenb = 0; + tagenable = ALL_TARGETS_MASK; /* Grab the disconnection disable table and invert it for our needs */ - if(ahc->flags & AHC_USEDEFAULTS) { + if (ahc->flags & AHC_USEDEFAULTS) { printf("%s: Host Adapter Bios disabled. Using default SCSI " "device parameters\n", ahc_name(ahc)); - ahc->discenable = 0xff; + ahc->flags |= AHC_EXTENDED_TRANS_A|AHC_EXTENDED_TRANS_B| + AHC_TERM_ENB_A|AHC_TERM_ENB_B; + discenable = ALL_TARGETS_MASK; + if ((ahc->features & AHC_ULTRA) != 0) + ultraenb = ALL_TARGETS_MASK; + } else { + discenable = ~((ahc_inb(ahc, DISC_DSB + 1) << 8) + | ahc_inb(ahc, DISC_DSB)); + if ((ahc->features & (AHC_ULTRA|AHC_ULTRA2)) != 0) + ultraenb = (ahc_inb(ahc, ULTRA_ENB + 1) << 8) + | ahc_inb(ahc, ULTRA_ENB); } - else - ahc->discenable = ~((AHC_INB(ahc, DISC_DSB + 1) << 8) - | AHC_INB(ahc, DISC_DSB)); - if(!(ahc->type & (AHC_WIDE|AHC_TWIN))) + if ((ahc->features & (AHC_WIDE|AHC_TWIN)) == 0) max_targ = 7; - for(i = 0; i <= max_targ; i++){ - u_char target_settings; - if (ahc->flags & AHC_USEDEFAULTS) { - target_settings = 0; /* 10MHz */ - ahc->needsdtr_orig |= (0x01 << i); - ahc->needwdtr_orig |= (0x01 << i); + for (i = 0; i <= max_targ; i++) { + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + u_int our_id; + u_int target_id; + char channel; + + channel = 'A'; + our_id = ahc->our_id; + target_id = i; + if (i > 7 && (ahc->features & AHC_TWIN) != 0) { + channel = 'B'; + our_id = ahc->our_id_b; + target_id = i % 8; } - else { + tinfo = ahc_fetch_transinfo(ahc, channel, our_id, + target_id, &tstate); + /* Default to async narrow across the board */ + bzero(tinfo, sizeof(*tinfo)); + if (ahc->flags & AHC_USEDEFAULTS) { + if ((ahc->features & AHC_WIDE) != 0) + tinfo->user.width = MSG_EXT_WDTR_BUS_16_BIT; + + /* + * These will be truncated when we determine the + * connection type we have with the target. + */ + tinfo->user.period = ahc_syncrates->period; + tinfo->user.offset = ~0; + } else { + u_int scsirate; + u_int16_t mask; + /* Take the settings leftover in scratch RAM. */ - target_settings = AHC_INB(ahc, TARG_SCRATCH + i); + scsirate = ahc_inb(ahc, TARG_SCSIRATE + i); + mask = (0x01 << i); + if ((ahc->features & AHC_ULTRA2) != 0) { + u_int offset; + u_int maxsync; - if(target_settings & 0x0f){ - ahc->needsdtr_orig |= (0x01 << i); - /*Default to asynchronous transfers(0 offset)*/ - target_settings &= 0xf0; - } - if(target_settings & 0x80){ - ahc->needwdtr_orig |= (0x01 << i); - /* - * We'll set the Wide flag when we - * are successful with Wide negotiation. - * Turn it off for now so we aren't - * confused. - */ - target_settings &= 0x7f; - } - if(ahc->type & AHC_ULTRA) { - /* - * Enable Ultra for any target that - * has a valid ultra syncrate setting. - */ - u_char rate = target_settings & 0x70; - if(rate == 0x00 || rate == 0x10 || - rate == 0x20 || rate == 0x40) { - if(rate == 0x40) { - /* Treat 10MHz specially */ - target_settings &= ~0x70; - } - else - ultraenable |= (0x01 << i); - } + if ((scsirate & SOFS) == 0x0F) { + /* + * Haven't negotiated yet, + * so the format is different. + */ + scsirate = (scsirate & SXFR) >> 4 + | (ultraenb & mask) + ? 0x08 : 0x0 + | (scsirate & WIDEXFER); + offset = MAX_OFFSET_ULTRA2; + } else + offset = ahc_inb(ahc, TARG_OFFSET + i); + maxsync = AHC_SYNCRATE_ULTRA2; + if ((ahc->features & AHC_DT) != 0) + maxsync = AHC_SYNCRATE_DT; + tinfo->user.period = + ahc_find_period(ahc, scsirate, maxsync); + if (offset == 0) + tinfo->user.period = 0; + else + tinfo->user.offset = ~0; + } else if ((scsirate & SOFS) != 0) { + tinfo->user.period = + ahc_find_period(ahc, scsirate, + (ultraenb & mask) + ? AHC_SYNCRATE_ULTRA + : AHC_SYNCRATE_FAST); + if (tinfo->user.period != 0) + tinfo->user.offset = ~0; } + if ((scsirate & WIDEXFER) != 0 + && (ahc->features & AHC_WIDE) != 0) + tinfo->user.width = MSG_EXT_WDTR_BUS_16_BIT; } - AHC_OUTB(ahc, TARG_SCRATCH+i,target_settings); + tinfo->goal = tinfo->user; /* force negotiation */ + tstate->ultraenb = ultraenb; + tstate->discenable = discenable; + tstate->tagenable = 0; /* Wait until the XPT says its okay */ } + ahc->user_discenable = discenable; + ahc->user_tagenable = tagenable; + + /* + * Tell the sequencer where it can find our arrays in memory. + */ + physaddr = ahc->scb_data->hscb_busaddr; + ahc_outb(ahc, HSCB_ADDR, physaddr & 0xFF); + ahc_outb(ahc, HSCB_ADDR + 1, (physaddr >> 8) & 0xFF); + ahc_outb(ahc, HSCB_ADDR + 2, (physaddr >> 16) & 0xFF); + ahc_outb(ahc, HSCB_ADDR + 3, (physaddr >> 24) & 0xFF); + + physaddr = ahc->shared_data_busaddr; + ahc_outb(ahc, SCBID_ADDR, physaddr & 0xFF); + ahc_outb(ahc, SCBID_ADDR + 1, (physaddr >> 8) & 0xFF); + ahc_outb(ahc, SCBID_ADDR + 2, (physaddr >> 16) & 0xFF); + ahc_outb(ahc, SCBID_ADDR + 3, (physaddr >> 24) & 0xFF); + + /* Target mode incomding command fifo */ + physaddr += 3 * 256 * sizeof(u_int8_t); + ahc_outb(ahc, TMODE_CMDADDR, physaddr & 0xFF); + ahc_outb(ahc, TMODE_CMDADDR + 1, (physaddr >> 8) & 0xFF); + ahc_outb(ahc, TMODE_CMDADDR + 2, (physaddr >> 16) & 0xFF); + ahc_outb(ahc, TMODE_CMDADDR + 3, (physaddr >> 24) & 0xFF); + /* - * If we are not a WIDE device, forget WDTR. This - * makes the driver work on some cards that don't - * leave these fields cleared when the BIOS is not - * installed. + * Initialize the group code to command length table. + * This overrides the values in TARG_SCSIRATE, so only + * setup the table after we have processed that information. */ - if(!(ahc->type & AHC_WIDE)) - ahc->needwdtr_orig = 0; - ahc->needsdtr = ahc->needsdtr_orig; - ahc->needwdtr = ahc->needwdtr_orig; - ahc->sdtrpending = 0; - ahc->wdtrpending = 0; - ahc->tagenable = 0; - ahc->orderedtag = 0; - - AHC_OUTB(ahc, ULTRA_ENB, ultraenable & 0xff); - AHC_OUTB(ahc, ULTRA_ENB + 1, (ultraenable >> 8) & 0xff); + ahc_outb(ahc, CMDSIZE_TABLE, 5); + ahc_outb(ahc, CMDSIZE_TABLE + 1, 9); + ahc_outb(ahc, CMDSIZE_TABLE + 2, 9); + ahc_outb(ahc, CMDSIZE_TABLE + 3, 0); + ahc_outb(ahc, CMDSIZE_TABLE + 4, 15); + ahc_outb(ahc, CMDSIZE_TABLE + 5, 11); + ahc_outb(ahc, CMDSIZE_TABLE + 6, 0); + ahc_outb(ahc, CMDSIZE_TABLE + 7, 0); + + /* Tell the sequencer of our initial queue positions */ + ahc_outb(ahc, KERNEL_QINPOS, 0); + ahc_outb(ahc, QINPOS, 0); + ahc_outb(ahc, QOUTPOS, 0); #ifdef AHC_DEBUG - /* How did we do? */ - if(ahc_debug & AHC_SHOWMISC) + if (ahc_debug & AHC_SHOWMISC) printf("NEEDSDTR == 0x%x\nNEEDWDTR == 0x%x\n" - "DISCENABLE == 0x%x\n", ahc->needsdtr, - ahc->needwdtr, ahc->discenable); + "DISCENABLE == 0x%x\nULTRAENB == 0x%x\n", + ahc->needsdtr_orig, ahc->needwdtr_orig, + discenable, ultraenb); #endif - /* - * Set the number of available SCBs - */ - AHC_OUTB(ahc, SCBCOUNT, ahc->maxhscbs); - /* - * 2's compliment of maximum tag value - */ - i = ahc->maxscbs; - AHC_OUTB(ahc, COMP_SCBCOUNT, -i & 0xff); + /* Don't have any special messages to send to targets */ + ahc_outb(ahc, TARGET_MSG_REQUEST, 0); + ahc_outb(ahc, TARGET_MSG_REQUEST + 1, 0); /* - * QCount mask to deal with broken aic7850s that - * sporadically get garbage in the upper bits of - * their QCount registers. + * Use the built in queue management registers + * if they are available. */ - AHC_OUTB(ahc, QCNTMASK, ahc->qcntmask); + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + ahc_outb(ahc, QOFF_CTLSTA, SCB_QSIZE_256); + ahc_outb(ahc, SDSCB_QOFF, 0); + ahc_outb(ahc, SNSCB_QOFF, 0); + ahc_outb(ahc, HNSCB_QOFF, 0); + } - /* We don't have any busy targets right now */ - AHC_OUTB(ahc, ACTIVE_A, 0); - AHC_OUTB(ahc, ACTIVE_B, 0); /* We don't have any waiting selections */ - AHC_OUTB(ahc, WAITING_SCBH, SCB_LIST_NULL); + ahc_outb(ahc, WAITING_SCBH, SCB_LIST_NULL); /* Our disconnection list is empty too */ - AHC_OUTB(ahc, DISCONNECTED_SCBH, SCB_LIST_NULL); + ahc_outb(ahc, DISCONNECTED_SCBH, SCB_LIST_NULL); /* Message out buffer starts empty */ - AHC_OUTB(ahc, MSG_LEN, 0x00); + ahc_outb(ahc, MSG_OUT, MSG_NOOP); + + /* + * Setup the allowed SCSI Sequences based on operational mode. + * If we are a target, we'll enalbe select in operations once + * we've had a lun enabled. + */ + scsiseq_template = ENSELO|ENAUTOATNO|ENAUTOATNP; + if ((ahc->flags & AHC_INITIATORMODE) != 0) + scsiseq_template |= ENRSELI; + ahc_outb(ahc, SCSISEQ_TEMPLATE, scsiseq_template); /* * Load the Sequencer program and Enable the adapter * in "fast" mode. */ -#if (!defined(__NetBSD__) && !defined(__OpenBSD__)) || defined(DEBUG) - if(bootverbose) + if (bootverbose) printf("%s: Downloading Sequencer Program...", ahc_name(ahc)); -#endif ahc_loadseq(ahc); -#if (!defined(__NetBSD__) && !defined(__OpenBSD__)) || defined(DEBUG) - if(bootverbose) - printf("Done\n"); -#endif - - AHC_OUTB(ahc, SEQCTL, FASTMODE); - - unpause_sequencer(ahc, /*unpause_always*/TRUE); - - /* - * Note that we are going and return (to probe) - */ - ahc->flags |= AHC_INIT; + /* We have to wait until after any system dumps... */ + shutdownhook_establish(ahc_shutdown, ahc); return (0); } -static void -ahcminphys(bp) - struct buf *bp; -{ /* - * Even though the card can transfer up to 16megs per command - * we are limited by the number of segments in the dma segment - * list that we can hold. The worst case is that all pages are - * discontinuous physically, hense the "page per segment" limit - * enforced here. + * Routines to manage a scsi_xfer into the software queue. + * We overload xs->free_list to to ensure we don't run into a queue + * resource shortage, and keep a pointer to the last entry around + * to make insertion O(C). */ - if (bp->b_bcount > ((AHC_NSEG - 1) * PAGE_SIZE)) { - bp->b_bcount = ((AHC_NSEG - 1) * PAGE_SIZE); - } -#if defined(__NetBSD__) || defined(__OpenBSD__) - minphys(bp); -#endif +STATIC INLINE void +ahc_list_insert_before(ahc, xs, next_xs) + struct ahc_softc *ahc; + struct scsi_xfer *xs; + struct scsi_xfer *next_xs; +{ + LIST_INSERT_BEFORE(xs, next_xs, free_list); + } -#if defined(__NetBSD__) || defined(__OpenBSD__) /* XXX */ -/* - * Insert a scsi_xfer into the software queue. We overload xs->free_list - * to to ensure we don't run into a queue resource shortage, and keep - * a pointer to the last entry around to make insertion O(C). - */ -static void -ahc_xxx_enqueue(ahc, xs, infront) - struct ahc_data *ahc; +STATIC INLINE void +ahc_list_insert_head(ahc, xs) + struct ahc_softc *ahc; struct scsi_xfer *xs; - int infront; { + if (ahc->sc_xxxq.lh_first == NULL) + ahc->sc_xxxqlast = xs; + LIST_INSERT_HEAD(&ahc->sc_xxxq, xs, free_list); + return; +} - if (infront || ahc->sc_xxxq.lh_first == NULL) { - if (ahc->sc_xxxq.lh_first == NULL) - ahc->sc_xxxqlast = xs; +STATIC INLINE void +ahc_list_insert_tail(ahc, xs) + struct ahc_softc *ahc; + struct scsi_xfer *xs; +{ + if (ahc->sc_xxxq.lh_first == NULL){ + ahc->sc_xxxqlast = xs; LIST_INSERT_HEAD(&ahc->sc_xxxq, xs, free_list); return; } - LIST_INSERT_AFTER(ahc->sc_xxxqlast, xs, free_list); ahc->sc_xxxqlast = xs; } -/* - * Pull a scsi_xfer off the front of the software queue. When we - * pull the last one off, we need to clear the pointer to the last - * entry. - */ -static struct scsi_xfer * -ahc_xxx_dequeue(ahc) - struct ahc_data *ahc; -{ +STATIC INLINE void +ahc_list_remove(ahc, xs) + struct ahc_softc *ahc; struct scsi_xfer *xs; - - xs = ahc->sc_xxxq.lh_first; +{ + struct scsi_xfer *lxs; + if (xs == ahc->sc_xxxqlast) { + lxs = ahc->sc_xxxq.lh_first; + while (lxs != NULL) { + if (LIST_NEXT(lxs, free_list) == ahc->sc_xxxqlast) { + ahc->sc_xxxqlast = lxs; + break; + } + lxs = LIST_NEXT(xs, free_list); + } + } + LIST_REMOVE(xs, free_list); - if (ahc->sc_xxxq.lh_first == NULL) ahc->sc_xxxqlast = NULL; +} - return (xs); +STATIC INLINE struct scsi_xfer * +ahc_list_next(ahc, xs) + struct ahc_softc *ahc; + struct scsi_xfer *xs; +{ + return(LIST_NEXT(xs, free_list)); } -#endif /* - * start a scsi operation given the command and - * the data address, target, and lun all of which - * are stored in the scsi_xfer struct + * Pick the first xs for a non-blocked target. */ -static int32_t -ahc_scsi_cmd(xs) - struct scsi_xfer *xs; +STATIC INLINE struct scsi_xfer * +ahc_first_xs(struct ahc_softc *ahc) { - struct scb *scb; - struct ahc_dma_seg *sg; - int seg; /* scatter gather seg being worked on */ - unsigned long thiskv, nextkv; - physaddr thisphys, nextphys; - int bytes_this_seg, bytes_this_page, datalen, flags; - struct ahc_data *ahc; - u_short mask; - int s; -#if defined(__NetBSD__) || defined(__OpenBSD__) /* XXX */ - int dontqueue = 0, fromqueue = 0; -#endif + int target; + struct scsi_xfer *xs = ahc->sc_xxxq.lh_first; - ahc = (struct ahc_data *)xs->sc_link->adapter_softc; - mask = (0x01 << (xs->sc_link->target -#if defined(__FreeBSD__) - | ((u_long)xs->sc_link->fordriver & 0x08))); -#elif defined(__NetBSD__) || defined(__OpenBSD__) - | (IS_SCSIBUS_B(ahc, xs->sc_link) ? SELBUSB : 0) )); -#endif + if (ahc->queue_blocked) + return NULL; - SC_DEBUG(xs->sc_link, SDEV_DB2, ("ahc_scsi_cmd\n")); + while (xs != NULL) { + target = xs->sc_link->target; + if (ahc->devqueue_blocked[target] == 0 && + ahc_index_busy_tcl(ahc, XS_TCL(ahc, xs), FALSE) == + SCB_LIST_NULL) + break; + xs = LIST_NEXT(xs, free_list); + } + + return xs; +} + +STATIC int32_t +ahc_scsi_cmd(xs) + struct scsi_xfer *xs; +{ + struct scsi_xfer *first_xs, *next_xs = NULL; + struct ahc_softc *ahc; + struct scb *scb; + struct hardware_scb *hscb; + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + u_int target_id; + u_int our_id; + int s, tcl; + u_int16_t mask; + int dontqueue = 0, fromqueue = 0; + + SC_DEBUG(xs->sc_link, SDEV_DB3, ("ahc_scsi_cmd\n")); + ahc = (struct ahc_softc *)xs->sc_link->adapter_softc; -#if defined(__NetBSD__) || (__OpenBSD__) /* XXX */ /* must protect the queue */ s = splbio(); - /* - * If we're running the queue from ahc_done(), we're called - * with the first entry in the queue as our argument. - * Pull it off; if we can't run the job, it will get placed - * back at the front. - */ if (xs == ahc->sc_xxxq.lh_first) { - xs = ahc_xxx_dequeue(ahc); + /* + * Called from ahc_done. Calling with the first entry in + * the queue is really just a way of seeing where we're + * called from. Now, find the first eligible SCB to send, + * e.g. one which will be accepted immediately. + */ + + if (ahc->queue_blocked) { + splx(s); + return (TRY_AGAIN_LATER); + } + + xs = ahc_first_xs(ahc); + if (xs == NULL) { + splx(s); + return (TRY_AGAIN_LATER); + } + + next_xs = ahc_list_next(ahc, xs); + ahc_list_remove(ahc, xs); fromqueue = 1; goto get_scb; } + /* + * If no new requests are accepted, just insert into the + * private queue to wait for our turn. + */ + tcl = XS_TCL(ahc, xs); + + if (ahc->queue_blocked || + ahc->devqueue_blocked[xs->sc_link->target] || + ahc_index_busy_tcl(ahc, tcl, FALSE) != SCB_LIST_NULL) { + if (dontqueue) { + splx(s); + xs->error = XS_DRIVER_STUFFUP; + return TRY_AGAIN_LATER; + } + ahc_list_insert_tail(ahc, xs); + splx(s); + return SUCCESSFULLY_QUEUED; + } + + first_xs = ahc_first_xs(ahc); + /* determine safety of software queueing */ dontqueue = xs->flags & SCSI_POLL; @@ -2475,7 +4108,7 @@ ahc_scsi_cmd(xs) * Handle situations where there's already entries in the * queue. */ - if (ahc->sc_xxxq.lh_first != NULL) { + if (first_xs != NULL) { /* * If we can't queue, we have to abort, since * we have to preserve order. @@ -2489,32 +4122,24 @@ ahc_scsi_cmd(xs) /* * Swap with the first queue entry. */ - ahc_xxx_enqueue(ahc, xs, 0); - xs = ahc_xxx_dequeue(ahc); + ahc_list_insert_tail(ahc, xs); + xs = first_xs; + next_xs = ahc_list_next(ahc, xs); + ahc_list_remove(ahc, xs); fromqueue = 1; + } - get_scb: -#endif /* __NetBSD__ || __OpenBSD__ */ +get_scb: + + target_id = xs->sc_link->target; + our_id = SIM_SCSI_ID(ahc, xs->sc_link); + /* - * get an scb to use. If the transfer - * is from a buf (possibly from interrupt time) - * then we can't allow it to sleep + * get an scb to use. */ - flags = xs->flags; - if (flags & ITSDONE) { - printf("%s: Already done?", ahc_name(ahc)); - xs->flags &= ~ITSDONE; - } - if (!(flags & INUSE)) { - printf("%s: Not in use?", ahc_name(ahc)); - xs->flags |= INUSE; - } - if (!(scb = ahc_get_scb(ahc, flags))) { -#if defined(__NetBSD__) || defined(__OpenBSD__) /* XXX */ - /* - * If we can't queue, we lose. - */ + if ((scb = ahcgetscb(ahc)) == NULL) { + if (dontqueue) { splx(s); xs->error = XS_DRIVER_STUFFUP; @@ -2523,182 +4148,189 @@ ahc_scsi_cmd(xs) /* * If we were pulled off the queue, put ourselves - * back in the front, otherwise tack ourselves onto - * the end. + * back to where we came from, otherwise tack ourselves + * onto the end. */ - ahc_xxx_enqueue(ahc, xs, fromqueue); + if (fromqueue && next_xs != NULL) + ahc_list_insert_before(ahc, xs, next_xs); + else + ahc_list_insert_tail(ahc, xs); splx(s); return (SUCCESSFULLY_QUEUED); -#else - xs->error = XS_DRIVER_STUFFUP; - return (TRY_AGAIN_LATER); -#endif /* __NetBSD__ || __OpenBSD__ */ } -#if defined(__NetBSD__) || defined(__OpenBSD__) - /* we're done playing with the queue */ - splx(s); -#endif + tcl = XS_TCL(ahc, xs); - SC_DEBUG(xs->sc_link, SDEV_DB3, ("start scb(%p)\n", scb)); +#ifdef DIAGNOSTIC + if (ahc_index_busy_tcl(ahc, tcl, FALSE) != SCB_LIST_NULL) + panic("ahc: queuing for busy target"); +#endif + scb->xs = xs; - if (flags & SCSI_RESET) { - scb->flags |= SCB_DEVICE_RESET|SCB_IMMED; - scb->control |= MK_MESSAGE; - } + hscb = scb->hscb; + hscb->tcl = tcl; + + ahc_busy_tcl(ahc, scb); + + splx(s); + /* * Put all the arguments for the xfer in the scb */ - if(ahc->tagenable & mask) { - scb->control |= TAG_ENB; - if(ahc->orderedtag & mask) { - printf("Ordered Tag sent\n"); - scb->control |= 0x02; - ahc->orderedtag &= ~mask; - } + mask = SCB_TARGET_MASK(scb); + tinfo = ahc_fetch_transinfo(ahc, SIM_CHANNEL(ahc, xs->sc_link), our_id, + target_id, &tstate); + if (ahc->inited_targets[target_id] == 0) { + struct ahc_devinfo devinfo; + + s = splbio(); + ahc_compile_devinfo(&devinfo, our_id, target_id, + xs->sc_link->lun, SIM_CHANNEL(ahc, xs->sc_link), + ROLE_INITIATOR); + ahc_update_target_msg_request(ahc, &devinfo, tinfo, TRUE, + FALSE); + ahc->inited_targets[target_id] = 1; + splx(s); } - if(ahc->discenable & mask) - scb->control |= DISCENB; - if((ahc->needwdtr & mask) && !(ahc->wdtrpending & mask)) - { - scb->control |= MK_MESSAGE; - scb->flags |= SCB_MSGOUT_WDTR; - ahc->wdtrpending |= mask; + + hscb->scsirate = tinfo->scsirate; + hscb->scsioffset = tinfo->current.offset; + if ((tstate->ultraenb & mask) != 0) + hscb->control |= ULTRAENB; + + if ((tstate->discenable & mask) != 0) + hscb->control |= DISCENB; + + if (xs->flags & SCSI_RESET) { + hscb->cmdpointer = NULL; + scb->flags |= SCB_DEVICE_RESET; + hscb->control |= MK_MESSAGE; + return ahc_execute_scb(scb, NULL, 0); } - else if((ahc->needsdtr & mask) && !(ahc->sdtrpending & mask)) - { - scb->control |= MK_MESSAGE; - scb->flags |= SCB_MSGOUT_SDTR; - ahc->sdtrpending |= mask; - } - scb->tcl = ((xs->sc_link->target << 4) & 0xF0) | -#if defined(__FreeBSD__) - ((u_long)xs->sc_link->fordriver & 0x08) | -#elif defined(__NetBSD__) || defined(__OpenBSD__) - (IS_SCSIBUS_B(ahc,xs->sc_link)? SELBUSB : 0)| -#endif - (xs->sc_link->lun & 0x07); - scb->cmdlen = xs->cmdlen; - scb->cmdpointer = KVTOPHYS(xs->cmd); - xs->resid = 0; - xs->status = 0; - if (xs->datalen) { /* should use S/G only if not zero length */ - scb->SG_list_pointer = KVTOPHYS(scb->ahc_dma); - sg = scb->ahc_dma; - seg = 0; - /* - * Set up the scatter gather block - */ - SC_DEBUG(xs->sc_link, SDEV_DB4, - ("%ld @%p:- ", xs->datalen, xs->data)); - datalen = xs->datalen; - thiskv = (unsigned long) xs->data; - thisphys = KVTOPHYS(thiskv); - while ((datalen) && (seg < AHC_NSEG)) { - bytes_this_seg = 0; + return ahc_setup_data(ahc, xs, scb); +} - /* put in the base address */ - sg->addr = thisphys; +STATIC int +ahc_execute_scb(arg, dm_segs, nsegments) + void *arg; + bus_dma_segment_t *dm_segs; + int nsegments; +{ + struct scb *scb; + struct scsi_xfer *xs; + struct ahc_softc *ahc; + int s; - SC_DEBUGN(xs->sc_link, SDEV_DB4, ("0x%lx", thisphys)); + scb = (struct scb *)arg; + xs = scb->xs; + ahc = (struct ahc_softc *)xs->sc_link->adapter_softc; - /* do it at least once */ - nextphys = thisphys; - while ((datalen) && (thisphys == nextphys)) { - /* - * This page is contiguous (physically) - * with the the last, just extend the - * length - */ - /* how far to the end of the page */ - nextphys = (thisphys & (~(PAGE_SIZE- 1))) - + PAGE_SIZE; - bytes_this_page = nextphys - thisphys; - /**** or the data ****/ - bytes_this_page = min(bytes_this_page, datalen); - bytes_this_seg += bytes_this_page; - datalen -= bytes_this_page; - - /* get more ready for the next page */ - nextkv = thiskv; - nextkv &= ~((unsigned long) PAGE_SIZE - 1); - nextkv += PAGE_SIZE; - if (datalen) - thisphys = KVTOPHYS(nextkv); - thiskv = nextkv; - } - /* - * next page isn't contiguous, finish the seg - */ - SC_DEBUGN(xs->sc_link, SDEV_DB4, - ("(0x%x)", bytes_this_seg)); - sg->len = bytes_this_seg; - sg++; - seg++; - } - scb->SG_segment_count = seg; + + if (nsegments != 0) { + struct ahc_dma_seg *sg; + bus_dma_segment_t *end_seg; + bus_dmasync_op_t op; + + end_seg = dm_segs + nsegments; /* Copy the first SG into the data pointer area */ - scb->data = scb->ahc_dma->addr; - scb->datalen = scb->ahc_dma->len; - SC_DEBUGN(xs->sc_link, SDEV_DB4, ("\n")); - if (datalen) { - /* there's still data, must have run out of segs! */ - printf("%s: ahc_scsi_cmd: more than %d DMA segs\n", - ahc_name(ahc), AHC_NSEG); - xs->error = XS_DRIVER_STUFFUP; - ahc_free_scb(ahc, scb, flags); - return (COMPLETE); + scb->hscb->data = dm_segs->ds_addr; + scb->hscb->datalen = dm_segs->ds_len; + + /* Copy the segments into our SG list */ + sg = scb->sg_list; + while (dm_segs < end_seg) { + sg->addr = dm_segs->ds_addr; + sg->len = dm_segs->ds_len; + sg++; + dm_segs++; } -#ifdef AHC_BROKEN_CACHE - if (ahc_broken_cache) - INVALIDATE_CACHE(); -#endif + + /* Note where to find the SG entries in bus space */ + scb->hscb->SG_pointer = scb->sg_list_phys; + if ((scb->xs->flags & SCSI_DATA_IN) != 0) + op = BUS_DMASYNC_PREREAD; + else + op = BUS_DMASYNC_PREWRITE; + bus_dmamap_sync(ahc->sc_dmat, scb->dmamap, op); + } else { + scb->hscb->SG_pointer = 0; + scb->hscb->data = 0; + scb->hscb->datalen = 0; } - else { - /* - * No data xfer, use non S/G values - */ - scb->SG_segment_count = 0; - scb->SG_list_pointer = 0; - scb->data = 0; - scb->datalen = 0; + + scb->sg_count = scb->hscb->SG_count = nsegments; + + s = splbio(); + + /* + * Last time we need to check if this SCB needs to + * be aborted. + */ + if (xs->flags & ITSDONE) { + ahc_index_busy_tcl(ahc, scb->hscb->tcl, TRUE); + if (nsegments != 0) + bus_dmamap_unload(ahc->sc_dmat, scb->dmamap); + ahcfreescb(ahc, scb); + splx(s); + return (COMPLETE); } -#ifdef AHC_DEBUG - if((ahc_debug & AHC_SHOWSCBS) && (xs->sc_link->target == DEBUGTARG)) - ahc_print_scb(scb); +#ifdef DIAGNOSTIC + if (scb->sg_count > 255) + panic("ahc bad sg_count"); #endif - s = splbio(); + + LIST_INSERT_HEAD(&ahc->pending_scbs, scb, pend_links); - if( scb->position != SCB_LIST_NULL ) - { - /* We already have a valid slot */ - u_char curscb; + scb->flags |= SCB_ACTIVE; + + if (!(xs->flags & SCSI_POLL)) + timeout(ahc_timeout, (caddr_t)scb, + (xs->timeout * hz) / 1000); + if ((scb->flags & SCB_TARGET_IMMEDIATE) != 0) { +#if 0 + printf("Continueing Immediate Command %d:%d\n", + xs->sc_link->target, + xs->sc_link->lun); +#endif pause_sequencer(ahc); - curscb = AHC_INB(ahc, SCBPTR); - AHC_OUTB(ahc, SCBPTR, scb->position); - ahc_send_scb(ahc, scb); - AHC_OUTB(ahc, SCBPTR, curscb); - AHC_OUTB(ahc, QINFIFO, scb->position); - unpause_sequencer(ahc, /*unpause_always*/FALSE); - scb->flags |= SCB_ACTIVE; - if (!(flags & SCSI_NOMASK)) { - timeout(ahc_timeout, (caddr_t)scb, - (xs->timeout * hz) / 1000); - } - SC_DEBUG(xs->sc_link, SDEV_DB3, ("cmd_sent\n")); - } - else { - scb->flags |= SCB_WAITINGQ; - STAILQ_INSERT_TAIL(&ahc->waiting_scbs, scb, links); - ahc_run_waiting_queues(ahc); - } - if (!(flags & SCSI_NOMASK)) { + if ((ahc->flags & AHC_PAGESCBS) == 0) + ahc_outb(ahc, SCBPTR, scb->hscb->tag); + ahc_outb(ahc, SCB_TAG, scb->hscb->tag); + ahc_outb(ahc, RETURN_1, CONT_MSG_LOOP); + unpause_sequencer(ahc); + } else { + + ahc->qinfifo[ahc->qinfifonext++] = scb->hscb->tag; + + bus_dmamap_sync(ahc->sc_dmat, ahc->shared_data_dmamap, + BUS_DMASYNC_PREWRITE); + + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + ahc_outb(ahc, HNSCB_QOFF, ahc->qinfifonext); + } else { + pause_sequencer(ahc); + ahc_outb(ahc, KERNEL_QINPOS, ahc->qinfifonext); + unpause_sequencer(ahc); + } + } + +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWCMDS) { + printf("opcode %d tag %x len %d flags %x control %x fpos %u" + " rate %x\n", + xs->cmdstore.opcode, scb->hscb->tag, scb->hscb->datalen, + scb->flags, scb->hscb->control, ahc->qinfifonext, + scb->hscb->scsirate); + } +#endif + + if (!(xs->flags & SCSI_POLL)) { splx(s); return (SUCCESSFULLY_QUEUED); } @@ -2713,797 +4345,1398 @@ ahc_scsi_cmd(xs) ahc_timeout(scb); break; } - } while (!(xs->flags & ITSDONE)); /* a non command complete intr */ - splx(s); + } while (!(xs->flags & ITSDONE)); + splx(s); return (COMPLETE); } +STATIC int +ahc_poll(ahc, wait) + struct ahc_softc *ahc; + int wait; /* in msec */ +{ + while (--wait) { + DELAY(1000); + if (ahc_inb(ahc, INTSTAT) & INT_PEND) + break; + } -/* - * A scb (and hence an scb entry on the board) is put onto the - * free list. - */ -static void -ahc_free_scb(ahc, scb, flags) - struct ahc_data *ahc; - int flags; - struct scb *scb; + if (wait == 0) { + printf("%s: board is not responding\n", ahc_name(ahc)); + return (EIO); + } + + ahc_intr((void *)ahc); + return (0); +} + +STATIC int +ahc_setup_data(ahc, xs, scb) + struct ahc_softc *ahc; + struct scsi_xfer *xs; + struct scb *scb; +{ + struct hardware_scb *hscb; + + hscb = scb->hscb; + xs->resid = xs->status = 0; + + hscb->cmdlen = xs->cmdlen; + bcopy(xs->cmd, hscb->cmdstore, xs->cmdlen); + hscb->cmdpointer = hscb->cmdstore_busaddr; + + /* Only use S/G if there is a transfer */ + if (xs->datalen) { + int error; + + error = bus_dmamap_load(ahc->sc_dmat, + scb->dmamap, xs->data, + xs->datalen, NULL, + (xs->flags & SCSI_NOSLEEP) ? + BUS_DMA_NOWAIT : BUS_DMA_WAITOK); + if (error) { + ahc_index_busy_tcl(ahc, hscb->tcl, TRUE); + return (TRY_AGAIN_LATER); /* XXX fvdl */ + } + error = ahc_execute_scb(scb, + scb->dmamap->dm_segs, + scb->dmamap->dm_nsegs); + return error; + } else { + return ahc_execute_scb(scb, NULL, 0); + } +} + +STATIC void +ahc_freeze_devq(ahc, sc_link) + struct ahc_softc *ahc; + struct scsi_link *sc_link; { - struct scb *wscb; - unsigned int opri; + int target; + char channel; + int lun; - opri = splbio(); + target = sc_link->target; + lun = sc_link->lun; + channel = SIM_CHANNEL(ahc, sc_link); + + ahc_search_qinfifo(ahc, target, channel, lun, + /*tag*/SCB_LIST_NULL, ROLE_UNKNOWN, + SCB_REQUEUE, SEARCH_COMPLETE); +} - /* Clean up for the next user */ - scb->flags = SCB_FREE; - scb->control = 0; - scb->status = 0; +STATIC void +ahcallocscbs(ahc) + struct ahc_softc *ahc; +{ + struct scb_data *scb_data; + struct scb *next_scb; + struct sg_map_node *sg_map; + bus_addr_t physaddr; + struct ahc_dma_seg *segs; + int newcount; + int i; + int dma_flags = 0; - if(scb->position == SCB_LIST_NULL) { - STAILQ_INSERT_HEAD(&ahc->page_scbs, scb, links); - if(!scb->links.stqe_next && !ahc->free_scbs.stqh_first) - /* - * If there were no SCBs available, wake anybody waiting - * for one to come free. - */ - wakeup((caddr_t)&ahc->free_scbs); + scb_data = ahc->scb_data; + if (scb_data->numscbs >= AHC_SCB_MAX) + /* Can't allocate any more */ + return; + + next_scb = &scb_data->scbarray[scb_data->numscbs]; + + sg_map = malloc(sizeof(*sg_map), M_DEVBUF, M_NOWAIT); + + if (sg_map == NULL) + return; + bzero(sg_map, sizeof(struct sg_map_node)); + + if (ahc_createdmamem(ahc, PAGE_SIZE, &sg_map->sg_dmamap, + (caddr_t *)&sg_map->sg_vaddr, &sg_map->sg_physaddr, + &sg_map->sg_dmasegs, &sg_map->sg_nseg, "SG space") < 0) { + free(sg_map, M_DEVBUF); + return; } - /* - * If there are any SCBS on the waiting queue, - * assign the slot of this "freed" SCB to the first - * one. We'll run the waiting queues after all command - * completes for a particular interrupt are completed - * or when we start another command. - */ - else if((wscb = ahc->waiting_scbs.stqh_first) != NULL) { - STAILQ_REMOVE_HEAD(&ahc->waiting_scbs, links); - wscb->position = scb->position; - STAILQ_INSERT_TAIL(&ahc->assigned_scbs, wscb, links); - wscb->flags ^= SCB_WAITINGQ|SCB_ASSIGNEDQ; - - /* - * The "freed" SCB will need to be assigned a slot - * before being used, so put it in the page_scbs - * queue. + + SLIST_INSERT_HEAD(&scb_data->sg_maps, sg_map, links); + + segs = sg_map->sg_vaddr; + physaddr = sg_map->sg_physaddr; + + newcount = (PAGE_SIZE / (AHC_NSEG * sizeof(struct ahc_dma_seg))); + + for (i = 0; scb_data->numscbs < AHC_SCB_MAX && i < newcount; i++) { + int error; + + next_scb->sg_list = segs; + /* + * The sequencer always starts with the second entry. + * The first entry is embedded in the scb. */ - scb->position = SCB_LIST_NULL; - STAILQ_INSERT_HEAD(&ahc->page_scbs, scb, links); - if(!scb->links.stqe_next && !ahc->free_scbs.stqh_first) + next_scb->sg_list_phys = physaddr + sizeof(struct ahc_dma_seg); + next_scb->flags = SCB_FREE; + + /* set up AHA-284x right. */ + dma_flags = ((ahc->chip & AHC_VL) !=0) ? + BUS_DMA_NOWAIT|ISABUS_DMA_32BIT : + BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW; + + error = bus_dmamap_create(ahc->sc_dmat, + AHC_MAXTRANSFER_SIZE, AHC_NSEG, MAXBSIZE, 0, + dma_flags, &next_scb->dmamap); + if (error !=0) + break; + + next_scb->hscb = &scb_data->hscbs[scb_data->numscbs]; + next_scb->hscb->tag = ahc->scb_data->numscbs; + next_scb->hscb->cmdstore_busaddr = + ahc_hscb_busaddr(ahc, next_scb->hscb->tag) + + offsetof(struct hardware_scb, cmdstore); + SLIST_INSERT_HEAD(&ahc->scb_data->free_scbs, next_scb, links); + segs += AHC_NSEG; + physaddr += (AHC_NSEG * sizeof(struct ahc_dma_seg)); + next_scb++; + ahc->scb_data->numscbs++; + } +} + +#ifdef AHC_DUMP_SEQ +STATIC void +ahc_dumpseq(ahc) + struct ahc_softc* ahc; +{ + int i; + int max_prog; + + if ((ahc->chip & AHC_BUS_MASK) < AHC_PCI) + max_prog = 448; + else if ((ahc->features & AHC_ULTRA2) != 0) + max_prog = 768; + else + max_prog = 512; + + ahc_outb(ahc, SEQCTL, PERRORDIS|FAILDIS|FASTMODE|LOADRAM); + ahc_outb(ahc, SEQADDR0, 0); + ahc_outb(ahc, SEQADDR1, 0); + for (i = 0; i < max_prog; i++) { + u_int8_t ins_bytes[4]; + + ahc_insb(ahc, SEQRAM, ins_bytes, 4); + printf("0x%08x\n", ins_bytes[0] << 24 + | ins_bytes[1] << 16 + | ins_bytes[2] << 8 + | ins_bytes[3]); + } +} +#endif + +STATIC void +ahc_loadseq(ahc) + struct ahc_softc* ahc; +{ + struct patch *cur_patch; + int i; + int downloaded; + int skip_addr; + u_int8_t download_consts[4]; + + /* Setup downloadable constant table */ +#if 0 + /* No downloaded constants are currently defined. */ + download_consts[TMODE_NUMCMDS] = ahc->num_targetcmds; +#endif + + cur_patch = patches; + downloaded = 0; + skip_addr = 0; + ahc_outb(ahc, SEQCTL, PERRORDIS|FAILDIS|FASTMODE|LOADRAM); + ahc_outb(ahc, SEQADDR0, 0); + ahc_outb(ahc, SEQADDR1, 0); + + for (i = 0; i < sizeof(seqprog)/4; i++) { + if (ahc_check_patch(ahc, &cur_patch, i, &skip_addr) == 0) { /* - * If there were no SCBs available, wake anybody waiting - * for one to come free. + * Don't download this instruction as it + * is in a patch that was removed. */ - wakeup((caddr_t)&ahc->free_scbs); + continue; + } + ahc_download_instr(ahc, i, download_consts); + downloaded++; } - else { - STAILQ_INSERT_HEAD(&ahc->free_scbs, scb, links); - if(!scb->links.stqe_next && !ahc->page_scbs.stqh_first) - /* - * If there were no SCBs available, wake anybody waiting - * for one to come free. + ahc_outb(ahc, SEQCTL, PERRORDIS|FAILDIS|FASTMODE); + restart_sequencer(ahc); + + if (bootverbose) + printf(" %d instructions downloaded\n", downloaded); +} + +STATIC int +ahc_check_patch(ahc, start_patch, start_instr,skip_addr) + struct ahc_softc *ahc; + struct patch **start_patch; + int start_instr; + int *skip_addr; +{ + struct patch *cur_patch; + struct patch *last_patch; + int num_patches; + + num_patches = sizeof(patches)/sizeof(struct patch); + last_patch = &patches[num_patches]; + cur_patch = *start_patch; + + while (cur_patch < last_patch && start_instr == cur_patch->begin) { + + if (cur_patch->patch_func(ahc) == 0) { + + /* Start rejecting code */ + *skip_addr = start_instr + cur_patch->skip_instr; + cur_patch += cur_patch->skip_patch; + } else { + /* Accepted this patch. Advance to the next + * one and wait for our intruction pointer to + * hit this point. */ - wakeup((caddr_t)&ahc->free_scbs); + cur_patch++; + } } -#ifdef AHC_DEBUG - ahc->activescbs--; -#endif - splx(opri); + + *start_patch = cur_patch; + if (start_instr < *skip_addr) + /* Still skipping */ + return (0); + + return (1); } -/* - * Get a free scb, either one already assigned to a hardware slot - * on the adapter or one that will require an SCB to be paged out before - * use. If there are none, see if we can allocate a new SCB. Otherwise - * either return an error or sleep. - */ -static struct scb * -ahc_get_scb(ahc, flags) - struct ahc_data *ahc; - int flags; +STATIC void +ahc_download_instr(ahc, instrptr, dconsts) + struct ahc_softc *ahc; + int instrptr; + u_int8_t *dconsts; { - unsigned opri; - struct scb *scbp; + union ins_formats instr; + struct ins_format1 *fmt1_ins; + struct ins_format3 *fmt3_ins; + u_int opcode; + + /* Structure copy */ + instr = *(union ins_formats*)&seqprog[instrptr * 4]; + + fmt1_ins = &instr.format1; + fmt3_ins = NULL; + + /* Pull the opcode */ + opcode = instr.format1.opcode; + switch (opcode) { + case AIC_OP_JMP: + case AIC_OP_JC: + case AIC_OP_JNC: + case AIC_OP_CALL: + case AIC_OP_JNE: + case AIC_OP_JNZ: + case AIC_OP_JE: + case AIC_OP_JZ: + { + struct patch *cur_patch; + int address_offset; + u_int address; + int skip_addr; + int i; - opri = splbio(); - /* - * If we can and have to, sleep waiting for one to come free - * but only if we can't allocate a new one. - */ - while (1) { - if((scbp = ahc->free_scbs.stqh_first)) { - STAILQ_REMOVE_HEAD(&ahc->free_scbs, links); - } - else if((scbp = ahc->page_scbs.stqh_first)) { - STAILQ_REMOVE_HEAD(&ahc->page_scbs, links); - } - else if(ahc->numscbs < ahc->maxscbs) { - scbp = (struct scb *) malloc(sizeof(struct scb), - M_TEMP, M_NOWAIT); - if (scbp) { - bzero(scbp, sizeof(struct scb)); - scbp->tag = ahc->numscbs; - if( ahc->numscbs < ahc->maxhscbs ) - scbp->position = ahc->numscbs; - else - scbp->position = SCB_LIST_NULL; - ahc->numscbs++; - /* - * Place in the scbarray - * Never is removed. - */ - ahc->scbarray[scbp->tag] = scbp; - } - else { - printf("%s: Can't malloc SCB\n", - ahc_name(ahc)); + fmt3_ins = &instr.format3; + address_offset = 0; + address = fmt3_ins->address; + cur_patch = patches; + skip_addr = 0; + + for (i = 0; i < address;) { + + ahc_check_patch(ahc, &cur_patch, i, &skip_addr); + + if (skip_addr > i) { + int end_addr; + + end_addr = MIN(address, skip_addr); + address_offset += end_addr - i; + i = skip_addr; + } else { + i++; } } - else { - if (!(flags & SCSI_NOSLEEP)) { - tsleep((caddr_t)&ahc->free_scbs, PRIBIO, - "ahcscb", 0); - continue; + address -= address_offset; + fmt3_ins->address = address; + /* FALLTHROUGH */ + } + case AIC_OP_OR: + case AIC_OP_AND: + case AIC_OP_XOR: + case AIC_OP_ADD: + case AIC_OP_ADC: + case AIC_OP_BMOV: + if (fmt1_ins->parity != 0) { + fmt1_ins->immediate = dconsts[fmt1_ins->immediate]; + } + fmt1_ins->parity = 0; + /* FALLTHROUGH */ + case AIC_OP_ROL: + if ((ahc->features & AHC_ULTRA2) != 0) { + int i, count; + + /* Calculate odd parity for the instruction */ + for (i = 0, count = 0; i < 31; i++) { + u_int32_t mask; + + mask = 0x01 << i; + if ((instr.integer & mask) != 0) + count++; + } + if ((count & 0x01) == 0) + instr.format1.parity = 1; + } else { + /* Compress the instruction for older sequencers */ + if (fmt3_ins != NULL) { + instr.integer = + fmt3_ins->immediate + | (fmt3_ins->source << 8) + | (fmt3_ins->address << 16) + | (fmt3_ins->opcode << 25); + } else { + instr.integer = + fmt1_ins->immediate + | (fmt1_ins->source << 8) + | (fmt1_ins->destination << 16) + | (fmt1_ins->ret << 24) + | (fmt1_ins->opcode << 25); } } + ahc_outsb(ahc, SEQRAM, instr.bytes, 4); + break; + default: + panic("Unknown opcode encountered in seq program"); break; } - -#ifdef AHC_DEBUG - if (scbp) { - ahc->activescbs++; - if((ahc_debug & AHC_SHOWSCBCNT) - && (ahc->activescbs == ahc->maxhscbs)) - printf("%s: Max SCBs active\n", ahc_name(ahc)); - } -#endif - - splx(opri); - - return (scbp); } -static void ahc_loadseq(ahc) - struct ahc_data *ahc; +STATIC void +ahc_set_recoveryscb(ahc, scb) + struct ahc_softc *ahc; + struct scb *scb; { - static u_char seqprog[] = { -#if defined(__FreeBSD__) -# include "aic7xxx_seq.h" -#endif -#if defined(__NetBSD__) || defined(__OpenBSD__) -# include <dev/microcode/aic7xxx/aic7xxx_seq.h> -#endif - }; - AHC_OUTB(ahc, SEQCTL, PERRORDIS|SEQRESET|LOADRAM); + if ((scb->flags & SCB_RECOVERY_SCB) == 0) { + struct scb *scbp; - AHC_OUTSB(ahc, SEQRAM, seqprog, sizeof(seqprog)); + scb->flags |= SCB_RECOVERY_SCB; - do { - AHC_OUTB(ahc, SEQCTL, SEQRESET|FASTMODE); - } while((AHC_INB(ahc, SEQADDR0) != 0) - || (AHC_INB(ahc, SEQADDR1) != 0)); -} + /* + * Take all queued, but not sent SCBs out of the equation. + * Also ensure that no new CCBs are queued to us while we + * try to fix this problem. + */ + ahc->queue_blocked = 1; -/* - * Function to poll for command completion when - * interrupts are disabled (crash dumps) - */ -static int -ahc_poll(ahc, wait) - struct ahc_data *ahc; - int wait; /* in msec */ -{ - while (--wait) { - DELAY(1000); - if (AHC_INB(ahc, INTSTAT) & INT_PEND) - break; - } if (wait == 0) { - printf("%s: board is not responding\n", ahc_name(ahc)); - return (EIO); + /* + * Go through all of our pending SCBs and remove + * any scheduled timeouts for them. We will reschedule + * them after we've successfully fixed this problem. + */ + scbp = ahc->pending_scbs.lh_first; + while (scbp != NULL) { + untimeout(ahc_timeout, scbp); + scbp = scbp->pend_links.le_next; + } } - ahc_intr((void *)ahc); - return (0); } -static void -ahc_timeout(arg) - void *arg; +STATIC void +ahc_timeout(void *arg) { - struct scb *scb = (struct scb *)arg; - struct ahc_data *ahc; + struct scb *scb; + struct ahc_softc *ahc; int s, found; - u_char bus_state; + u_int last_phase; + int target; + int lun; + int i; char channel; + scb = (struct scb *)arg; + ahc = (struct ahc_softc *)scb->xs->sc_link->adapter_softc; + s = splbio(); - if (!(scb->flags & SCB_ACTIVE)) { + /* + * Ensure that the card doesn't do anything + * behind our back. Also make sure that we + * didn't "just" miss an interrupt that would + * affect this timeout. + */ + do { + ahc_intr(ahc); + pause_sequencer(ahc); + } while (ahc_inb(ahc, INTSTAT) & INT_PEND); + + if ((scb->flags & SCB_ACTIVE) == 0) { /* Previous timeout took care of me already */ + printf("Timedout SCB handled by another timeout\n"); + unpause_sequencer(ahc); splx(s); return; } - ahc = (struct ahc_data *)scb->xs->sc_link->adapter_softc; - - if (ahc->in_timeout) { - /* - * Some other SCB has started a recovery operation - * and is still working on cleaning things up. - */ - if (scb->flags & SCB_TIMEDOUT) { - /* - * This SCB has been here before and is not the - * recovery SCB. Cut our losses and panic. Its - * better to do this than trash a filesystem. - */ - panic("%s: Timed-out command times out " - "again\n", ahc_name(ahc)); - } - else if (!(scb->flags & SCB_ABORTED)) - { - /* - * This is not the SCB that started this timeout - * processing. Give this scb another lifetime so - * that it can continue once we deal with the - * timeout. - */ - scb->flags |= SCB_TIMEDOUT; - timeout(ahc_timeout, (caddr_t)scb, - (scb->xs->timeout * hz) / 1000); - splx(s); - return; - } - } - ahc->in_timeout = TRUE; - - /* - * Ensure that the card doesn't do anything - * behind our back. - */ - pause_sequencer(ahc); + target = SCB_TARGET(scb); + channel = SCB_CHANNEL(scb); + lun = SCB_LUN(scb); sc_print_addr(scb->xs->sc_link); - printf("timed out "); + printf("SCB 0x%x - timed out ", scb->hscb->tag); /* * Take a snapshot of the bus state and print out * some information so we can track down driver bugs. */ - bus_state = AHC_INB(ahc, LASTPHASE); + last_phase = ahc_inb(ahc, LASTPHASE); - switch(bus_state & PHASE_MASK) - { - case P_DATAOUT: - printf("in dataout phase"); - break; - case P_DATAIN: - printf("in datain phase"); - break; - case P_COMMAND: - printf("in command phase"); - break; - case P_MESGOUT: - printf("in message out phase"); - break; - case P_STATUS: - printf("in status phase"); - break; - case P_MESGIN: - printf("in message in phase"); - break; - default: - printf("while idle, LASTPHASE == 0x%x", - bus_state); - /* - * We aren't in a valid phase, so assume we're - * idle. - */ - bus_state = 0; + for (i = 0; i < num_phases; i++) { + if (last_phase == phase_table[i].phase) break; } - - printf(", SCSISIGI == 0x%x\n", AHC_INB(ahc, SCSISIGI)); - - /* Decide our course of action */ - - if(scb->flags & SCB_ABORTED) - { + printf("%s", phase_table[i].phasemsg); + + printf(", SEQADDR == 0x%x\n", + ahc_inb(ahc, SEQADDR0) | (ahc_inb(ahc, SEQADDR1) << 8)); +#if 0 + printf("SSTAT1 == 0x%x\n", ahc_inb(ahc, SSTAT1)); + printf("SSTAT3 == 0x%x\n", ahc_inb(ahc, SSTAT3)); + printf("SCSIPHASE == 0x%x\n", ahc_inb(ahc, SCSIPHASE)); + printf("SCSIRATE == 0x%x\n", ahc_inb(ahc, SCSIRATE)); + printf("SCSIOFFSET == 0x%x\n", ahc_inb(ahc, SCSIOFFSET)); + printf("SEQ_FLAGS == 0x%x\n", ahc_inb(ahc, SEQ_FLAGS)); + printf("SCB_DATAPTR == 0x%x\n", ahc_inb(ahc, SCB_DATAPTR) + | ahc_inb(ahc, SCB_DATAPTR + 1) << 8 + | ahc_inb(ahc, SCB_DATAPTR + 2) << 16 + | ahc_inb(ahc, SCB_DATAPTR + 3) << 24); + printf("SCB_DATACNT == 0x%x\n", ahc_inb(ahc, SCB_DATACNT) + | ahc_inb(ahc, SCB_DATACNT + 1) << 8 + | ahc_inb(ahc, SCB_DATACNT + 2) << 16); + printf("SCB_SGCOUNT == 0x%x\n", ahc_inb(ahc, SCB_SGCOUNT)); + printf("CCSCBCTL == 0x%x\n", ahc_inb(ahc, CCSCBCTL)); + printf("CCSCBCNT == 0x%x\n", ahc_inb(ahc, CCSCBCNT)); + printf("DFCNTRL == 0x%x\n", ahc_inb(ahc, DFCNTRL)); + printf("DFSTATUS == 0x%x\n", ahc_inb(ahc, DFSTATUS)); + printf("CCHCNT == 0x%x\n", ahc_inb(ahc, CCHCNT)); + if (scb->sg_count > 0) { + for (i = 0; i < scb->sg_count; i++) { + printf("sg[%d] - Addr 0x%x : Length %d\n", + i, + scb->sg_list[i].addr, + scb->sg_list[i].len); + } + } +#endif + if (scb->flags & (SCB_DEVICE_RESET|SCB_ABORT)) { /* * Been down this road before. * Do a full bus reset. */ - char channel = (scb->tcl & SELBUSB) - ? 'B': 'A'; - found = ahc_reset_channel(ahc, channel, scb->tag, - XS_TIMEOUT, /*Initiate Reset*/TRUE); - printf("%s: Issued Channel %c Bus Reset #1. " +bus_reset: + ahcsetccbstatus(scb->xs, XS_TIMEOUT); + found = ahc_reset_channel(ahc, channel, /*Initiate Reset*/TRUE); + printf("%s: Issued Channel %c Bus Reset. " "%d SCBs aborted\n", ahc_name(ahc), channel, found); - ahc->in_timeout = FALSE; - } - else if(scb->control & TAG_ENB) { - /* - * We could be starving this command - * try sending an ordered tag command - * to the target we come from. - */ - scb->flags |= SCB_ABORTED|SCB_SENTORDEREDTAG; - ahc->orderedtag |= 0xFF; - timeout(ahc_timeout, (caddr_t)scb, (5 * hz)); - unpause_sequencer(ahc, /*unpause_always*/FALSE); - printf("Ordered Tag queued\n"); - goto done; - } - else { + } else { /* - * Send a Bus Device Reset Message: - * The target that is holding up the bus may not + * If we are a target, transition to bus free and report + * the timeout. + * + * The target/initiator that is holding up the bus may not * be the same as the one that triggered this timeout * (different commands have different timeout lengths). - * It is also impossible to get a message to a target - * if we are in a "frozen" data transfer phase. Our - * strategy here is to queue a bus device reset message - * to the timed out target if it is disconnected. - * Otherwise, if we have an active target we stuff the - * message buffer with a bus device reset message and - * assert ATN in the hopes that the target will let go - * of the bus and finally disconnect. If this fails, - * we'll get another timeout 2 seconds later which will - * cause a bus reset. + * If the bus is idle and we are actiing as the initiator + * for this request, queue a BDR message to the timed out + * target. Otherwise, if the timed out transaction is + * active: + * Initiator transaction: + * Stuff the message buffer with a BDR message and assert + * ATN in the hopes that the target will let go of the bus + * and go to the mesgout phase. If this fails, we'll + * get another timeout 2 seconds later which will attempt + * a bus reset. * - * XXX If the SCB is paged out, we simply reset the - * bus. We should probably queue a new command - * instead. + * Target transaction: + * Transition to BUS FREE and report the error. + * It's good to be the target! */ + u_int active_scb_index; - /* Test to see if scb is disconnected */ - if( !(scb->flags & SCB_PAGED_OUT ) ){ - u_char active_scb; - struct scb *active_scbp; + active_scb_index = ahc_inb(ahc, SCB_TAG); - active_scb = AHC_INB(ahc, SCBPTR); - active_scbp = ahc->scbarray[AHC_INB(ahc, SCB_TAG)]; - AHC_OUTB(ahc, SCBPTR, scb->position); + if (last_phase != P_BUSFREE + && (active_scb_index < ahc->scb_data->numscbs)) { + struct scb *active_scb; + + /* + * If the active SCB is not from our device, + * assume that another device is hogging the bus + * and wait for it's timeout to expire before + * taking additional action. + */ + active_scb = &ahc->scb_data->scbarray[active_scb_index]; + if (active_scb->hscb->tcl != scb->hscb->tcl) { + u_int newtimeout; - if(AHC_INB(ahc, SCB_CONTROL) & DISCONNECTED) { - if(ahc->flags & AHC_PAGESCBS) { - /* - * Pull this SCB out of the - * disconnected list. - */ - u_char prev = AHC_INB(ahc, SCB_PREV); - u_char next = AHC_INB(ahc, SCB_NEXT); - if(prev == SCB_LIST_NULL) { - /* At the head */ - AHC_OUTB(ahc, DISCONNECTED_SCBH, - next ); - } - else { - AHC_OUTB(ahc, SCBPTR, prev); - AHC_OUTB(ahc, SCB_NEXT, next); - if(next != SCB_LIST_NULL) { - AHC_OUTB(ahc, SCBPTR, - next); - AHC_OUTB(ahc, SCB_PREV, - prev); - } - AHC_OUTB(ahc, SCBPTR, - scb->position); - } - } - scb->flags |= SCB_DEVICE_RESET|SCB_ABORTED; - scb->control &= DISCENB; - scb->control |= MK_MESSAGE; - scb->cmdlen = 0; - scb->SG_segment_count = 0; - scb->SG_list_pointer = 0; - scb->data = 0; - scb->datalen = 0; - ahc_send_scb(ahc, scb); - ahc_add_waiting_scb(ahc, scb); - timeout(ahc_timeout, (caddr_t)scb, (2 * hz)); sc_print_addr(scb->xs->sc_link); - printf("BUS DEVICE RESET message queued.\n"); - AHC_OUTB(ahc, SCBPTR, active_scb); - unpause_sequencer(ahc, /*unpause_always*/FALSE); - goto done; + printf("Other SCB Timeout%s", + (scb->flags & SCB_OTHERTCL_TIMEOUT) != 0 + ? " again\n" : "\n"); + scb->flags |= SCB_OTHERTCL_TIMEOUT; + newtimeout = MAX(active_scb->xs->timeout, + scb->xs->timeout); + timeout(ahc_timeout, scb, + (newtimeout * hz) / 1000); + splx(s); + return; + } + + /* It's us */ + if ((scb->hscb->control & TARGET_SCB) != 0) { + + /* + * Send back any queued up transactions + * and properly record the error condition. + */ + ahc_freeze_devq(ahc, scb->xs->sc_link); + ahcsetccbstatus(scb->xs, XS_TIMEOUT); + ahc_freeze_ccb(scb); + ahc_done(ahc, scb); + + /* Will clear us from the bus */ + restart_sequencer(ahc); + return; + } + + ahc_set_recoveryscb(ahc, active_scb); + ahc_outb(ahc, MSG_OUT, MSG_BUS_DEV_RESET); + ahc_outb(ahc, SCSISIGO, last_phase|ATNO); + sc_print_addr(active_scb->xs->sc_link); + printf("BDR message in message buffer\n"); + active_scb->flags |= SCB_DEVICE_RESET; + timeout(ahc_timeout, (caddr_t)active_scb, 2 * hz); + } else { + int disconnected; + + /* XXX Shouldn't panic. Just punt instead */ + if ((scb->hscb->control & TARGET_SCB) != 0) + panic("Timed-out target SCB but bus idle"); + + if (last_phase != P_BUSFREE + && (ahc_inb(ahc, SSTAT0) & TARGET) != 0) { + /* XXX What happened to the SCB? */ + /* Hung target selection. Goto busfree */ + printf("%s: Hung target selection\n", + ahc_name(ahc)); + restart_sequencer(ahc); + return; } - /* Is the active SCB really active? */ - else if((active_scbp->flags & SCB_ACTIVE) && bus_state){ - AHC_OUTB(ahc, MSG_LEN, 1); - AHC_OUTB(ahc, MSG0, MSG_BUS_DEV_RESET); - AHC_OUTB(ahc, SCSISIGO, bus_state|ATNO); - sc_print_addr(active_scbp->xs->sc_link); - printf("asserted ATN - device reset in " - "message buffer\n"); - active_scbp->flags |= SCB_DEVICE_RESET - | SCB_ABORTED; - if(active_scbp != scb) { - untimeout(ahc_timeout, - (caddr_t)active_scbp); - /* Give scb a new lease on life */ - timeout(ahc_timeout, (caddr_t)scb, - (scb->xs->timeout * hz) / 1000); + + if (ahc_search_qinfifo(ahc, target, channel, lun, + scb->hscb->tag, ROLE_INITIATOR, + /*status*/0, SEARCH_COUNT) > 0) { + disconnected = FALSE; + } else { + disconnected = TRUE; + } + + if (disconnected) { + u_int active_scb; + + ahc_set_recoveryscb(ahc, scb); + /* + * Simply set the MK_MESSAGE control bit. + */ + scb->hscb->control |= MK_MESSAGE; + scb->flags |= SCB_QUEUED_MSG + | SCB_DEVICE_RESET; + + /* + * Mark the cached copy of this SCB in the + * disconnected list too, so that a reconnect + * at this point causes a BDR or abort. + */ + active_scb = ahc_inb(ahc, SCBPTR); + if (ahc_search_disc_list(ahc, target, + channel, lun, + scb->hscb->tag, + /*stop_on_first*/TRUE, + /*remove*/FALSE, + /*save_state*/FALSE)) { + u_int scb_control; + + scb_control = ahc_inb(ahc, SCB_CONTROL); + scb_control |= MK_MESSAGE; + ahc_outb(ahc, SCB_CONTROL, scb_control); + } + ahc_outb(ahc, SCBPTR, active_scb); + ahc_index_busy_tcl(ahc, scb->hscb->tcl, + /*unbusy*/TRUE); + + /* + * Actually re-queue this SCB in case we can + * select the device before it reconnects. + * Clear out any entries in the QINFIFO first + * so we are the next SCB for this target + * to run. + */ + ahc_search_qinfifo(ahc, SCB_TARGET(scb), + channel, SCB_LUN(scb), + SCB_LIST_NULL, + ROLE_INITIATOR, + SCB_REQUEUE, + SEARCH_COMPLETE); + sc_print_addr(scb->xs->sc_link); + printf("Queuing a BDR SCB\n"); + ahc->qinfifo[ahc->qinfifonext++] = + scb->hscb->tag; + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + ahc_outb(ahc, HNSCB_QOFF, + ahc->qinfifonext); + } else { + ahc_outb(ahc, KERNEL_QINPOS, + ahc->qinfifonext); } - timeout(ahc_timeout, (caddr_t)active_scbp, - (2 * hz)); - AHC_OUTB(ahc, SCBPTR, active_scb); - unpause_sequencer(ahc, /*unpause_always*/FALSE); - goto done; + timeout(ahc_timeout, (caddr_t)scb, 2 * hz); + unpause_sequencer(ahc); + } else { + /* Go "immediatly" to the bus reset */ + /* This shouldn't happen */ + ahc_set_recoveryscb(ahc, scb); + sc_print_addr(scb->xs->sc_link); + printf("SCB %d: Immediate reset. " + "Flags = 0x%x\n", scb->hscb->tag, + scb->flags); + goto bus_reset; } } - /* - * No active target or a paged out SCB. - * Try resetting the bus. - */ - channel = (scb->tcl & SELBUSB) ? 'B': 'A'; - found = ahc_reset_channel(ahc, channel, scb->tag, - XS_TIMEOUT, - /*Initiate Reset*/TRUE); - printf("%s: Issued Channel %c Bus Reset #2. " - "%d SCBs aborted\n", ahc_name(ahc), channel, - found); - ahc->in_timeout = FALSE; - } -done: + } splx(s); } - -/* - * The device at the given target/channel has been reset. Abort - * all active and queued scbs for that target/channel. - */ -static int -ahc_reset_device(ahc, target, channel, timedout_scb, xs_error) - struct ahc_data *ahc; +STATIC int +ahc_search_qinfifo(ahc, target, channel, lun, tag, role, status, action) + struct ahc_softc *ahc; int target; char channel; - u_char timedout_scb; - u_int32_t xs_error; + int lun; + u_int tag; + role_t role; + u_int32_t status; + ahc_search_action action; { - struct scb *scbp; - u_char active_scb; - int i = 0; - int found = 0; + struct scb *scbp; + u_int8_t qinpos; + u_int8_t qintail; + int found; - /* restore this when we're done */ - active_scb = AHC_INB(ahc, SCBPTR); + qinpos = ahc_inb(ahc, QINPOS); + qintail = ahc->qinfifonext; + found = 0; /* - * Search the QINFIFO. + * Start with an empty queue. Entries that are not chosen + * for removal will be re-added to the queue as we go. */ - { - u_char saved_queue[AHC_SCB_MAX]; - u_char queued = AHC_INB(ahc, QINCNT) & ahc->qcntmask; - - for (i = 0; i < (queued - found); i++) { - saved_queue[i] = AHC_INB(ahc, QINFIFO); - AHC_OUTB(ahc, SCBPTR, saved_queue[i]); - scbp = ahc->scbarray[AHC_INB(ahc, SCB_TAG)]; - if (ahc_match_scb (scbp, target, channel)){ - /* - * We found an scb that needs to be aborted. - */ - scbp->flags = SCB_ABORTED|SCB_QUEUED_FOR_DONE; - scbp->xs->error |= xs_error; - if(scbp->position != timedout_scb) - untimeout(ahc_timeout, (caddr_t)scbp); - AHC_OUTB(ahc, SCB_CONTROL, 0); - i--; - found++; + ahc->qinfifonext = qinpos; + bus_dmamap_sync(ahc->sc_dmat, ahc->shared_data_dmamap, + BUS_DMASYNC_POSTREAD); + + while (qinpos != qintail) { + scbp = &ahc->scb_data->scbarray[ahc->qinfifo[qinpos]]; + if (ahc_match_scb(scbp, target, channel, lun, tag, role)) { + /* + * We found an scb that needs to be removed. + */ + switch (action) { + case SEARCH_COMPLETE: + if (!(scbp->xs->flags & ITSDONE)) { + scbp->flags |= status; + scbp->xs->error = XS_NOERROR; + } + ahc_freeze_ccb(scbp); + ahc_done(ahc, scbp); + break; + case SEARCH_COUNT: + ahc->qinfifo[ahc->qinfifonext++] = + scbp->hscb->tag; + break; + case SEARCH_REMOVE: + break; } + found++; + } else { + ahc->qinfifo[ahc->qinfifonext++] = scbp->hscb->tag; } - /* Now put the saved scbs back. */ - for (queued = 0; queued < i; queued++) { - AHC_OUTB(ahc, QINFIFO, saved_queue[queued]); - } + qinpos++; } + bus_dmamap_sync(ahc->sc_dmat, ahc->shared_data_dmamap, + BUS_DMASYNC_PREWRITE); + + if ((ahc->features & AHC_QUEUE_REGS) != 0) { + ahc_outb(ahc, HNSCB_QOFF, ahc->qinfifonext); + } else { + ahc_outb(ahc, KERNEL_QINPOS, ahc->qinfifonext); + } + + return (found); +} + +/* + * Abort all SCBs that match the given description (target/channel/lun/tag), + * setting their status to the passed in status if the status has not already + * been modified from CAM_REQ_INPROG. This routine assumes that the sequencer + * is paused before it is called. + */ +STATIC int +ahc_abort_scbs(ahc, target, channel, lun, tag, role, status) + struct ahc_softc *ahc; + int target; + char channel; + int lun; + u_int tag; + role_t role; + u_int32_t status; +{ + struct scb *scbp; + u_int active_scb; + int i; + int found; + + /* restore this when we're done */ + active_scb = ahc_inb(ahc, SCBPTR); + + found = ahc_search_qinfifo(ahc, target, channel, lun, SCB_LIST_NULL, + role, SCB_REQUEUE, SEARCH_COMPLETE); /* * Search waiting for selection list. */ { - u_char next, prev; - - next = AHC_INB(ahc, WAITING_SCBH); /* Start at head of list. */ + u_int8_t next, prev; + /* Start at head of list. */ + next = ahc_inb(ahc, WAITING_SCBH); prev = SCB_LIST_NULL; while (next != SCB_LIST_NULL) { - AHC_OUTB(ahc, SCBPTR, next); - scbp = ahc->scbarray[AHC_INB(ahc, SCB_TAG)]; - /* - * Select the SCB. - */ - if (ahc_match_scb(scbp, target, channel)) { - next = ahc_abort_wscb(ahc, scbp, prev, - timedout_scb, xs_error); - found++; + u_int8_t scb_index; + + ahc_outb(ahc, SCBPTR, next); + scb_index = ahc_inb(ahc, SCB_TAG); + if (scb_index >= ahc->scb_data->numscbs) { + panic("Waiting List inconsistency. " + "SCB index == %d, yet numscbs == %d.", + scb_index, ahc->scb_data->numscbs); } - else { + scbp = &ahc->scb_data->scbarray[scb_index]; + if (ahc_match_scb(scbp, target, channel, + lun, SCB_LIST_NULL, role)) { + + next = ahc_abort_wscb(ahc, next, prev); + } else { + prev = next; - next = AHC_INB(ahc, SCB_NEXT); + next = ahc_inb(ahc, SCB_NEXT); } } } /* - * Go through the entire SCB array now and look for - * commands for this target that are active. These - * are other (most likely tagged) commands that - * were disconnected when the reset occured. + * Go through the disconnected list and remove any entries we + * have queued for completion, 0'ing their control byte too. + * We save the active SCB and restore it ourselves, so there + * is no reason for this search to restore it too. */ - for(i = 0; i < ahc->numscbs; i++) { - scbp = ahc->scbarray[i]; - if((scbp->flags & SCB_ACTIVE) - && ahc_match_scb(scbp, target, channel)) { - /* Ensure the target is "free" */ - ahc_unbusy_target(ahc, target, channel); - if( !(scbp->flags & SCB_PAGED_OUT) ) - { - AHC_OUTB(ahc, SCBPTR, scbp->position); - AHC_OUTB(ahc, SCB_CONTROL, 0); + ahc_search_disc_list(ahc, target, channel, lun, tag, + /*stop_on_first*/FALSE, /*remove*/TRUE, + /*save_state*/FALSE); + + /* + * Go through the hardware SCB array looking for commands that + * were active but not on any list. + */ + for(i = 0; i < ahc->scb_data->maxhscbs; i++) { + u_int scbid; + + ahc_outb(ahc, SCBPTR, i); + scbid = ahc_inb(ahc, SCB_TAG); + scbp = &ahc->scb_data->scbarray[scbid]; + if (scbid < ahc->scb_data->numscbs && + ahc_match_scb(scbp, target, channel, lun, tag, role)) + ahc_add_curscb_to_free_list(ahc); + } + + /* + * Go through the pending CCB list and look for + * commands for this target that are still active. + * These are other tagged commands that were + * disconnected when the reset occured. + */ + { + struct scb *scb; + + scb = ahc->pending_scbs.lh_first; + while (scb != NULL) { + scbp = scb; + scb = scb->pend_links.le_next; + if (ahc_match_scb(scbp, target, channel, + lun, tag, role)) { + if (!(scbp->xs->flags & ITSDONE)) + ahcsetccbstatus(scbp->xs, status); + ahc_freeze_ccb(scbp); + ahc_done(ahc, scbp); + found++; } - scbp->flags = SCB_ABORTED|SCB_QUEUED_FOR_DONE; - scbp->xs->error |= xs_error; - if(scbp->tag != timedout_scb) - untimeout(ahc_timeout, (caddr_t)scbp); - found++; } - } - AHC_OUTB(ahc, SCBPTR, active_scb); + } + ahc_outb(ahc, SCBPTR, active_scb); return found; } +STATIC int +ahc_search_disc_list(ahc, target, channel, lun, tag, stop_on_first, + remove, save_state) + struct ahc_softc *ahc; + int target; + char channel; + int lun; + u_int tag; + int stop_on_first; + int remove; + int save_state; +{ + struct scb *scbp; + u_int next; + u_int prev; + u_int count; + u_int active_scb; + + count = 0; + next = ahc_inb(ahc, DISCONNECTED_SCBH); + prev = SCB_LIST_NULL; + + if (save_state) { + /* restore this when we're done */ + active_scb = ahc_inb(ahc, SCBPTR); + } else + /* Silence compiler */ + active_scb = SCB_LIST_NULL; + + while (next != SCB_LIST_NULL) { + u_int scb_index; + + ahc_outb(ahc, SCBPTR, next); + scb_index = ahc_inb(ahc, SCB_TAG); + if (scb_index >= ahc->scb_data->numscbs) { + panic("Disconnected List inconsistency. " + "SCB index == %d, yet numscbs == %d.", + scb_index, ahc->scb_data->numscbs); + } + scbp = &ahc->scb_data->scbarray[scb_index]; + if (ahc_match_scb(scbp, target, channel, lun, + tag, ROLE_INITIATOR)) { + count++; + if (remove) { + next = + ahc_rem_scb_from_disc_list(ahc, prev, next); + } else { + prev = next; + next = ahc_inb(ahc, SCB_NEXT); + } + if (stop_on_first) + break; + } else { + prev = next; + next = ahc_inb(ahc, SCB_NEXT); + } + } + if (save_state) + ahc_outb(ahc, SCBPTR, active_scb); + return (count); +} + +STATIC u_int +ahc_rem_scb_from_disc_list(ahc, prev, scbptr) + struct ahc_softc *ahc; + u_int prev; + u_int scbptr; +{ + u_int next; + + ahc_outb(ahc, SCBPTR, scbptr); + next = ahc_inb(ahc, SCB_NEXT); + + ahc_outb(ahc, SCB_CONTROL, 0); + + ahc_add_curscb_to_free_list(ahc); + + if (prev != SCB_LIST_NULL) { + ahc_outb(ahc, SCBPTR, prev); + ahc_outb(ahc, SCB_NEXT, next); + } else + ahc_outb(ahc, DISCONNECTED_SCBH, next); + + return (next); +} + +STATIC void +ahc_add_curscb_to_free_list(ahc) + struct ahc_softc *ahc; +{ + /* Invalidate the tag so that ahc_find_scb doesn't think it's active */ + ahc_outb(ahc, SCB_TAG, SCB_LIST_NULL); + + ahc_outb(ahc, SCB_NEXT, ahc_inb(ahc, FREE_SCBH)); + ahc_outb(ahc, FREE_SCBH, ahc_inb(ahc, SCBPTR)); +} + /* * Manipulate the waiting for selection list and return the * scb that follows the one that we remove. */ -static u_char -ahc_abort_wscb (ahc, scbp, prev, timedout_scb, xs_error) - struct ahc_data *ahc; - struct scb *scbp; - u_char prev; - u_char timedout_scb; - u_int32_t xs_error; +STATIC u_int +ahc_abort_wscb(ahc, scbpos, prev) + struct ahc_softc *ahc; + u_int scbpos; + u_int prev; { - u_char curscbp, next; - int target = ((scbp->tcl >> 4) & 0x0f); - char channel = (scbp->tcl & SELBUSB) ? 'B' : 'A'; + u_int curscb, next; + /* * Select the SCB we want to abort and * pull the next pointer out of it. */ - curscbp = AHC_INB(ahc, SCBPTR); - AHC_OUTB(ahc, SCBPTR, scbp->position); - next = AHC_INB(ahc, SCB_NEXT); + curscb = ahc_inb(ahc, SCBPTR); + ahc_outb(ahc, SCBPTR, scbpos); + next = ahc_inb(ahc, SCB_NEXT); /* Clear the necessary fields */ - AHC_OUTB(ahc, SCB_CONTROL, 0); - AHC_OUTB(ahc, SCB_NEXT, SCB_LIST_NULL); - ahc_unbusy_target(ahc, target, channel); + ahc_outb(ahc, SCB_CONTROL, 0); + + ahc_add_curscb_to_free_list(ahc); /* update the waiting list */ - if( prev == SCB_LIST_NULL ) + if (prev == SCB_LIST_NULL) { /* First in the list */ - AHC_OUTB(ahc, WAITING_SCBH, next); - else { + ahc_outb(ahc, WAITING_SCBH, next); + + /* + * Ensure we aren't attempting to perform + * selection for this entry. + */ + ahc_outb(ahc, SCSISEQ, (ahc_inb(ahc, SCSISEQ) & ~ENSELO)); + } else { /* * Select the scb that pointed to us * and update its next pointer. */ - AHC_OUTB(ahc, SCBPTR, prev); - AHC_OUTB(ahc, SCB_NEXT, next); + ahc_outb(ahc, SCBPTR, prev); + ahc_outb(ahc, SCB_NEXT, next); } + /* - * Point us back at the original scb position - * and inform the SCSI system that the command - * has been aborted. + * Point us back at the original scb position. */ - AHC_OUTB(ahc, SCBPTR, curscbp); - scbp->flags = SCB_ABORTED|SCB_QUEUED_FOR_DONE; - scbp->xs->error |= xs_error; - if(scbp->tag != timedout_scb) - untimeout(ahc_timeout, (caddr_t)scbp); + ahc_outb(ahc, SCBPTR, curscb); return next; } -static void -ahc_busy_target(ahc, target, channel) - struct ahc_data *ahc; - u_char target; - char channel; +STATIC void +ahc_clear_intstat(ahc) + struct ahc_softc *ahc; { - u_char active; - u_long active_port = ACTIVE_A; - - if(target > 0x07 || channel == 'B') { - /* - * targets on the Second channel or - * above id 7 store info in byte two - * of HA_ACTIVE - */ - active_port++; - } - active = AHC_INB(ahc, active_port); - active |= (0x01 << (target & 0x07)); - AHC_OUTB(ahc, active_port, active); + /* Clear any interrupt conditions this may have caused */ + ahc_outb(ahc, CLRSINT0, CLRSELDO|CLRSELDI|CLRSELINGO); + ahc_outb(ahc, CLRSINT1, CLRSELTIMEO|CLRATNO|CLRSCSIRSTI + |CLRBUSFREE|CLRSCSIPERR|CLRPHASECHG| + CLRREQINIT); + ahc_outb(ahc, CLRINT, CLRSCSIINT); } -static void -ahc_unbusy_target(ahc, target, channel) - struct ahc_data *ahc; - u_char target; - char channel; +STATIC void +ahc_reset_current_bus(ahc) + struct ahc_softc *ahc; { - u_char active; - u_long active_port = ACTIVE_A; + u_int8_t scsiseq; - if(target > 0x07 || channel == 'B') { - /* - * targets on the Second channel or - * above id 7 store info in byte two - * of HA_ACTIVE - */ - active_port++; - } - active = AHC_INB(ahc, active_port); - active &= ~(0x01 << (target & 0x07)); - AHC_OUTB(ahc, active_port, active); -} + ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) & ~ENSCSIRST); + scsiseq = ahc_inb(ahc, SCSISEQ); + ahc_outb(ahc, SCSISEQ, scsiseq | SCSIRSTO); + DELAY(AHC_BUSRESET_DELAY); + /* Turn off the bus reset */ + ahc_outb(ahc, SCSISEQ, scsiseq & ~SCSIRSTO); -static void -ahc_reset_current_bus(ahc) - struct ahc_data *ahc; -{ - AHC_OUTB(ahc, SCSISEQ, SCSIRSTO); - DELAY(1000); - AHC_OUTB(ahc, SCSISEQ, 0); + ahc_clear_intstat(ahc); + + /* Re-enable reset interrupts */ + ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) | ENSCSIRST); } -static int -ahc_reset_channel(ahc, channel, timedout_scb, xs_error, initiate_reset) - struct ahc_data *ahc; - char channel; - u_char timedout_scb; - u_int32_t xs_error; - u_char initiate_reset; +STATIC int +ahc_reset_channel(ahc, channel, initiate_reset) + struct ahc_softc *ahc; + char channel; + int initiate_reset; { - u_char sblkctl; - char cur_channel; - u_long offset, offset_max; - int found; + u_int initiator, target, max_scsiid; + u_int sblkctl; + u_int our_id; + int found; + int restart_needed; + char cur_channel; + + ahc->pending_device = NULL; + + pause_sequencer(ahc); /* - * Clean up all the state information for the - * pending transactions on this bus. + * Run our command complete fifos to ensure that we perform + * completion processing on any commands that 'completed' + * before the reset occurred. + */ + ahc_run_qoutfifo(ahc); + + /* + * Reset the bus if we are initiating this reset */ - found = ahc_reset_device(ahc, ALL_TARGETS, channel, - timedout_scb, xs_error); - if(channel == 'B'){ - ahc->needsdtr |= (ahc->needsdtr_orig & 0xff00); - ahc->sdtrpending &= 0x00ff; - AHC_OUTB(ahc, ACTIVE_B, 0); - offset = TARG_SCRATCH + 8; - offset_max = TARG_SCRATCH + 16; - } - else if (ahc->type & AHC_WIDE){ - ahc->needsdtr = ahc->needsdtr_orig; - ahc->needwdtr = ahc->needwdtr_orig; - ahc->sdtrpending = 0; - ahc->wdtrpending = 0; - AHC_OUTB(ahc, ACTIVE_A, 0); - AHC_OUTB(ahc, ACTIVE_B, 0); - offset = TARG_SCRATCH; - offset_max = TARG_SCRATCH + 16; - } - else{ - ahc->needsdtr |= (ahc->needsdtr_orig & 0x00ff); - ahc->sdtrpending &= 0xff00; - AHC_OUTB(ahc, ACTIVE_A, 0); - offset = TARG_SCRATCH; - offset_max = TARG_SCRATCH + 8; - } - for(;offset < offset_max;offset++) { + sblkctl = ahc_inb(ahc, SBLKCTL); + cur_channel = 'A'; + if ((ahc->features & AHC_TWIN) != 0 + && ((sblkctl & SELBUSB) != 0)) + cur_channel = 'B'; + if (cur_channel != channel) { + /* Case 1: Command for another bus is active + * Stealthily reset the other bus without + * upsetting the current bus. + */ + ahc_outb(ahc, SBLKCTL, sblkctl ^ SELBUSB); + ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) & ~ENBUSFREE); + ahc_outb(ahc, SCSISEQ, + ahc_inb(ahc, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP)); + if (initiate_reset) + ahc_reset_current_bus(ahc); + ahc_clear_intstat(ahc); + ahc_outb(ahc, SBLKCTL, sblkctl); + restart_needed = FALSE; + } else { + /* Case 2: A command from this bus is active or we're idle */ + ahc_clear_msg_state(ahc); + ahc_outb(ahc, SIMODE1, ahc_inb(ahc, SIMODE1) & ~ENBUSFREE); + ahc_outb(ahc, SCSISEQ, + ahc_inb(ahc, SCSISEQ) & (ENSELI|ENRSELI|ENAUTOATNP)); + if (initiate_reset) + ahc_reset_current_bus(ahc); + ahc_clear_intstat(ahc); + /* - * Revert to async/narrow transfers - * until we renegotiate. + * Since we are going to restart the sequencer, avoid + * a race in the sequencer that could cause corruption + * of our Q pointers by starting over from index 0. */ - u_char targ_scratch; + ahc->qoutfifonext = 0; + if ((ahc->features & AHC_QUEUE_REGS) != 0) + ahc_outb(ahc, SDSCB_QOFF, 0); + else + ahc_outb(ahc, QOUTPOS, 0); + restart_needed = TRUE; + } - targ_scratch = AHC_INB(ahc, offset); - targ_scratch &= SXFR; - AHC_OUTB(ahc, offset, targ_scratch); + /* + * Clean up all the state information for the + * pending transactions on this bus. + */ + found = ahc_abort_scbs(ahc, ALL_TARGETS, channel, + ALL_LUNS, SCB_LIST_NULL, + ROLE_UNKNOWN, XS_RESET); + if (channel == 'B') { + our_id = ahc->our_id_b; + } else { + our_id = ahc->our_id; } + max_scsiid = (ahc->features & AHC_WIDE) ? 15 : 7; + /* - * Reset the bus if we are initiating this reset and - * restart/unpause the sequencer + * Revert to async/narrow transfers until we renegotiate. */ - /* Case 1: Command for another bus is active */ - sblkctl = AHC_INB(ahc, SBLKCTL); - cur_channel = (sblkctl & SELBUSB) ? 'B' : 'A'; - if(cur_channel != channel) - { - /* - * Stealthily reset the other bus - * without upsetting the current bus - */ - AHC_OUTB(ahc, SBLKCTL, sblkctl ^ SELBUSB); - if( initiate_reset ) - { - ahc_reset_current_bus(ahc); + for (target = 0; target <= max_scsiid; target++) { + + if (ahc->enabled_targets[target] == NULL) + continue; + for (initiator = 0; initiator <= max_scsiid; initiator++) { + struct ahc_devinfo devinfo; + + ahc_compile_devinfo(&devinfo, target, initiator, + ALL_LUNS, + channel, ROLE_UNKNOWN); + ahc_set_width(ahc, &devinfo, + MSG_EXT_WDTR_BUS_8_BIT, + AHC_TRANS_CUR, + /*paused*/TRUE, + /*done*/FALSE); + ahc_set_syncrate(ahc, &devinfo, + /*syncrate*/NULL, /*period*/0, + /*offset*/0, AHC_TRANS_CUR, + /*paused*/TRUE, + /*done*/FALSE); } - AHC_OUTB(ahc, CLRSINT1, CLRSCSIRSTI|CLRSELTIMEO); - AHC_OUTB(ahc, CLRINT, CLRSCSIINT); - AHC_OUTB(ahc, SBLKCTL, sblkctl); - unpause_sequencer(ahc, /*unpause_always*/TRUE); } - /* Case 2: A command from this bus is active or we're idle */ - else { - if( initiate_reset ) - { - ahc_reset_current_bus(ahc); - } - AHC_OUTB(ahc, CLRSINT1, CLRSCSIRSTI|CLRSELTIMEO); - AHC_OUTB(ahc, CLRINT, CLRSCSIINT); + + if (restart_needed) restart_sequencer(ahc); - } - ahc_run_done_queue(ahc); + else + unpause_sequencer(ahc); return found; } -void -ahc_run_done_queue(ahc) - struct ahc_data *ahc; +STATIC int +ahc_match_scb(scb, target, channel, lun, role, tag) + struct scb *scb; + int target; + char channel; + int lun; + role_t role; + u_int tag; { - int i; - struct scb *scbp; - - for(i = 0; i < ahc->numscbs; i++) { - scbp = ahc->scbarray[i]; - if(scbp->flags & SCB_QUEUED_FOR_DONE) - ahc_done(ahc, scbp); - } + int targ = SCB_TARGET(scb); + char chan = SCB_CHANNEL(scb); + int slun = SCB_LUN(scb); + int match; + + match = ((chan == channel) || (channel == ALL_CHANNELS)); + if (match != 0) + match = ((targ == target) || (target == ALL_TARGETS)); + if (match != 0) + match = ((lun == slun) || (lun == ALL_LUNS)); + return match; } - -static int -ahc_match_scb (scb, target, channel) - struct scb *scb; - int target; - char channel; + +STATIC void +ahc_construct_sdtr(ahc, period, offset) + struct ahc_softc *ahc; + u_int period; + u_int offset; { - int targ = (scb->tcl >> 4) & 0x0f; - char chan = (scb->tcl & SELBUSB) ? 'B' : 'A'; + ahc->msgout_buf[ahc->msgout_index++] = MSG_EXTENDED; + ahc->msgout_buf[ahc->msgout_index++] = MSG_EXT_SDTR_LEN; + ahc->msgout_buf[ahc->msgout_index++] = MSG_EXT_SDTR; + ahc->msgout_buf[ahc->msgout_index++] = period; + ahc->msgout_buf[ahc->msgout_index++] = offset; + ahc->msgout_len += 5; +} - if (target == ALL_TARGETS) - return (chan == channel); - else - return ((chan == channel) && (targ == target)); +STATIC void +ahc_construct_wdtr(ahc, bus_width) + struct ahc_softc *ahc; + u_int bus_width; +{ + ahc->msgout_buf[ahc->msgout_index++] = MSG_EXTENDED; + ahc->msgout_buf[ahc->msgout_index++] = MSG_EXT_WDTR_LEN; + ahc->msgout_buf[ahc->msgout_index++] = MSG_EXT_WDTR; + ahc->msgout_buf[ahc->msgout_index++] = bus_width; + ahc->msgout_len += 4; } +STATIC void +ahc_calc_residual(scb) + struct scb *scb; +{ + struct hardware_scb *hscb; + + hscb = scb->hscb; + + /* + * If the disconnected flag is still set, this is bogus + * residual information left over from a sequencer + * pagin/pageout, so ignore this case. + */ + if ((scb->hscb->control & DISCONNECTED) == 0) { + u_int32_t resid; + int resid_sgs; + int sg; + + /* + * Remainder of the SG where the transfer + * stopped. + */ + resid = (hscb->residual_data_count[2] << 16) + | (hscb->residual_data_count[1] <<8) + | (hscb->residual_data_count[0]); + + /* + * Add up the contents of all residual + * SG segments that are after the SG where + * the transfer stopped. + */ + resid_sgs = scb->hscb->residual_SG_count - 1/*current*/; + sg = scb->sg_count - resid_sgs; + while (resid_sgs > 0) { + + resid += scb->sg_list[sg].len; + sg++; + resid_sgs--; + } + scb->xs->resid = resid; + } + + /* + * Clean out the residual information in this SCB for its + * next consumer. + */ + hscb->residual_SG_count = 0; + +#ifdef AHC_DEBUG + if (ahc_debug & AHC_SHOWMISC) { + sc_print_addr(scb->xs->sc_link); + printf("Handled Residual of %ld bytes\n" ,scb->xs->resid); + } +#endif +} -static void -ahc_construct_sdtr(ahc, start_byte, period, offset) - struct ahc_data *ahc; - int start_byte; - u_int8_t period; - u_int8_t offset; +STATIC void +ahc_update_pending_syncrates(ahc) + struct ahc_softc *ahc; { - AHC_OUTB(ahc, MSG0 + start_byte, MSG_EXTENDED); - AHC_OUTB(ahc, MSG1 + start_byte, MSG_EXT_SDTR_LEN); - AHC_OUTB(ahc, MSG2 + start_byte, MSG_EXT_SDTR); - AHC_OUTB(ahc, MSG3 + start_byte, period); - AHC_OUTB(ahc, MSG4 + start_byte, offset); - AHC_OUTB(ahc, MSG_LEN, start_byte + 5); + struct scb *scb; + int pending_scb_count; + int i; + u_int saved_scbptr; + + /* + * Traverse the pending SCB list and ensure that all of the + * SCBs there have the proper settings. + */ + scb = LIST_FIRST(&ahc->pending_scbs); + pending_scb_count = 0; + while (scb != NULL) { + struct ahc_devinfo devinfo; + struct scsi_xfer *xs; + struct scb *pending_scb; + struct hardware_scb *pending_hscb; + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + u_int our_id, remote_id; + + xs = scb->xs; + pending_scb = scb; + pending_hscb = pending_scb->hscb; + our_id = SCB_IS_SCSIBUS_B(pending_scb) + ? ahc->our_id_b : ahc->our_id; + remote_id = xs->sc_link->target; + ahc_compile_devinfo(&devinfo, our_id, remote_id, + SCB_LUN(pending_scb), + SCB_CHANNEL(pending_scb), + ROLE_UNKNOWN); + tinfo = ahc_fetch_transinfo(ahc, devinfo.channel, + our_id, remote_id, &tstate); + pending_hscb->control &= ~ULTRAENB; + if ((tstate->ultraenb & devinfo.target_mask) != 0) + pending_hscb->control |= ULTRAENB; + pending_hscb->scsirate = tinfo->scsirate; + pending_hscb->scsioffset = tinfo->current.offset; + pending_scb_count++; + scb = LIST_NEXT(scb, pend_links); + } + + if (pending_scb_count == 0) + return; + + saved_scbptr = ahc_inb(ahc, SCBPTR); + /* Ensure that the hscbs down on the card match the new information */ + for (i = 0; i < ahc->scb_data->maxhscbs; i++) { + u_int scb_tag; + + ahc_outb(ahc, SCBPTR, i); + scb_tag = ahc_inb(ahc, SCB_TAG); + if (scb_tag != SCB_LIST_NULL) { + struct ahc_devinfo devinfo; + struct scb *pending_scb; + struct scsi_xfer *xs; + struct hardware_scb *pending_hscb; + struct ahc_initiator_tinfo *tinfo; + struct tmode_tstate *tstate; + u_int our_id, remote_id; + u_int control; + + pending_scb = &ahc->scb_data->scbarray[scb_tag]; + if (pending_scb->flags == SCB_FREE) + continue; + pending_hscb = pending_scb->hscb; + xs = pending_scb->xs; + our_id = SCB_IS_SCSIBUS_B(pending_scb) + ? ahc->our_id_b : ahc->our_id; + remote_id = xs->sc_link->target; + ahc_compile_devinfo(&devinfo, our_id, remote_id, + SCB_LUN(pending_scb), + SCB_CHANNEL(pending_scb), + ROLE_UNKNOWN); + tinfo = ahc_fetch_transinfo(ahc, devinfo.channel, + our_id, remote_id, &tstate); + control = ahc_inb(ahc, SCB_CONTROL); + control &= ~ULTRAENB; + if ((tstate->ultraenb & devinfo.target_mask) != 0) + control |= ULTRAENB; + ahc_outb(ahc, SCB_CONTROL, control); + ahc_outb(ahc, SCB_SCSIRATE, tinfo->scsirate); + ahc_outb(ahc, SCB_SCSIOFFSET, tinfo->current.offset); + } + } + ahc_outb(ahc, SCBPTR, saved_scbptr); } -static void -ahc_construct_wdtr(ahc, start_byte, bus_width) - struct ahc_data *ahc; - int start_byte; - u_int8_t bus_width; +STATIC void +ahc_shutdown(void *arg) { - AHC_OUTB(ahc, MSG0 + start_byte, MSG_EXTENDED); - AHC_OUTB(ahc, MSG1 + start_byte, MSG_EXT_WDTR_LEN); - AHC_OUTB(ahc, MSG2 + start_byte, MSG_EXT_WDTR); - AHC_OUTB(ahc, MSG3 + start_byte, bus_width); - AHC_OUTB(ahc, MSG_LEN, start_byte + 4); + struct ahc_softc *ahc; + int i; + u_int sxfrctl1_a, sxfrctl1_b; + + ahc = (struct ahc_softc *)arg; + + pause_sequencer(ahc); + + /* + * Preserve the value of the SXFRCTL1 register for all channels. + * It contains settings that affect termination and we don't want + * to disturb the integrity of the bus during shutdown in case + * we are in a multi-initiator setup. + */ + sxfrctl1_b = 0; + if ((ahc->features & AHC_TWIN) != 0) { + u_int sblkctl; + + sblkctl = ahc_inb(ahc, SBLKCTL); + ahc_outb(ahc, SBLKCTL, sblkctl | SELBUSB); + sxfrctl1_b = ahc_inb(ahc, SXFRCTL1); + ahc_outb(ahc, SBLKCTL, sblkctl & ~SELBUSB); + } + + sxfrctl1_a = ahc_inb(ahc, SXFRCTL1); + + /* This will reset most registers to 0, but not all */ + ahc_reset(ahc); + + if ((ahc->features & AHC_TWIN) != 0) { + u_int sblkctl; + + sblkctl = ahc_inb(ahc, SBLKCTL); + ahc_outb(ahc, SBLKCTL, sblkctl | SELBUSB); + ahc_outb(ahc, SXFRCTL1, sxfrctl1_b); + ahc_outb(ahc, SBLKCTL, sblkctl & ~SELBUSB); + } + ahc_outb(ahc, SXFRCTL1, sxfrctl1_a); + + ahc_outb(ahc, SCSISEQ, 0); + ahc_outb(ahc, SXFRCTL0, 0); + ahc_outb(ahc, DSPCISTATUS, 0); + + for (i = TARG_SCSIRATE; i < HA_274_BIOSCTRL; i++) + ahc_outb(ahc, i, 0); } |