/*
* Inline routines shareable across OS platforms.
*
* Copyright (c) 1994-2001 Justin T. Gibbs.
* Copyright (c) 2000-2003 Adaptec Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
* substantially similar to the "NO WARRANTY" disclaimer below
* ("Disclaimer") and any redistribution must be conditioned upon
* including a substantially similar Disclaimer requirement for further
* binary redistribution.
* 3. Neither the names of the above-listed copyright holders nor the names
* of any contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* NO WARRANTY
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* Id: //depot/aic7xxx/aic7xxx/aic79xx_inline.h#51 $
*
* $FreeBSD: src/sys/dev/aic7xxx/aic79xx_inline.h,v 1.12 2003/06/28 04:43:19 gibbs Exp $
*/
/*
* Ported from FreeBSD by Pascal Renauld, Network Storage Solutions, Inc. - April 2003
*/
/*
* Determine whether the sequencer has halted code execution.
* Returns non-zero status if the sequencer is stopped.
*/
static __inline int
ahd_is_paused(struct ahd_softc *ahd)
{
return ((ahd_inb(ahd, HCNTRL) & PAUSE) != 0);
}
/*
* Request that the sequencer stop and wait, indefinitely, for it
* to stop. The sequencer will only acknowledge that it is paused
* once it has reached an instruction boundary and PAUSEDIS is
* cleared in the SEQCTL register. The sequencer may use PAUSEDIS
* for critical sections.
*/
static __inline void
ahd_pause(struct ahd_softc *ahd)
{
ahd_outb(ahd, HCNTRL, ahd->pause);
/*
* Since the sequencer can disable pausing in a critical section, we
* must loop until it actually stops.
*/
while (ahd_is_paused(ahd) == 0)
;
}
/*
* Allow the sequencer to continue program execution.
* We check here to ensure that no additional interrupt
* sources that would cause the sequencer to halt have been
* asserted. If, for example, a SCSI bus reset is detected
* while we are fielding a different, pausing, interrupt type,
* we don't want to release the sequencer before going back
* into our interrupt handler and dealing with this new
* condition.
*/
static __inline void
ahd_unpause(struct ahd_softc *ahd)
{
/*
* Automatically restore our modes to those saved
* prior to the first change of the mode.
*/
if (ahd->saved_src_mode != AHD_MODE_UNKNOWN
&& ahd->saved_dst_mode != AHD_MODE_UNKNOWN) {
if ((ahd->flags & AHD_UPDATE_PEND_CMDS) != 0)
ahd_reset_cmds_pending(ahd);
ahd_set_modes(ahd, ahd->saved_src_mode, ahd->saved_dst_mode);
}
if ((ahd_inb(ahd, INTSTAT) & ~CMDCMPLT) == 0)
ahd_outb(ahd, HCNTRL, ahd->unpause);
high_addr = ahd_le32toh(sg->len) & 0x7F000000;
scb->hscb->dataptr |= ahd_htole64(high_addr << 8);
}
scb->hscb->datacnt = sg->len;
}
/*
* Note where to find the SG entries in bus space.
* We also set the full residual flag which the
* sequencer will clear as soon as a data transfer
* occurs.
*/
scb->hscb->sgptr = ahd_htole32(scb->sg_list_busaddr|SG_FULL_RESID);
}
/*
* Return pointers to the transfer negotiation information
* for the specified our_id/remote_id pair.
*/
static __inline struct ahd_initiator_tinfo *
ahd_fetch_transinfo(struct ahd_softc *ahd, char channel, u_int our_id,
u_int remote_id, struct ahd_tmode_tstate **tstate)
{
/*
* Transfer data structures are stored from the perspective
* of the target role. Since the parameters for a connection
* in the initiator role to a given target are the same as
* when the roles are reversed, we pretend we are the target.
*/
if (channel == 'B')
our_id += 8;
*tstate = ahd->enabled_targets[our_id];
return (&(*tstate)->transinfo[remote_id]);
}
#define AHD_COPY_COL_IDX(dst, src) \
do { \
dst->hscb->scsiid = src->hscb->scsiid; \
dst->hscb->lun = src->hscb->lun; \
} while (0)
/*
* Workaround PCI-X Rev A. hardware bug.
* After a host read of SCB memory, the chip
* may become confused into thinking prefetch
* was required. This starts the discard timer
* running and can cause an unexpected discard
* timer interrupt. The work around is to read
* a normal register prior to the exhaustion of
* the discard timer. The mode pointer register
* has no side effects and so serves well for
* this purpose.
*
* Razor #528
*/
value = ahd_inb(ahd, offset);
if ((ahd->flags & AHD_PCIX_SCBRAM_RD_BUG) != 0)
ahd_inb(ahd, MODE_PTR);
return (value);
}
/*
* Our queuing method is a bit tricky. The card
* knows in advance which HSCB (by address) to download,
* and we can't disappoint it. To achieve this, the next
* HSCB to download is saved off in ahd->next_queued_hscb.
* When we are called to queue "an arbitrary scb",
* we copy the contents of the incoming HSCB to the one
* the sequencer knows about, swap HSCB pointers and
* finally assign the SCB to the tag indexed location
* in the scb_array. This makes sure that we can still
* locate the correct SCB by SCB_TAG.
*/
q_hscb = ahd->next_queued_hscb;
q_hscb_map = ahd->next_queued_hscb_map;
saved_hscb_busaddr = q_hscb->hscb_busaddr;
memcpy(q_hscb, scb->hscb, sizeof(*scb->hscb));
q_hscb->hscb_busaddr = saved_hscb_busaddr;
q_hscb->next_hscb_busaddr = scb->hscb->hscb_busaddr;
/* Now define the mapping from tag to SCB in the scbindex */
ahd->scb_data.scbindex[SCB_GET_TAG(scb)] = scb;
}
/*
* Tell the sequencer about a new transaction to execute.
*/
static __inline void
ahd_queue_scb(struct ahd_softc *ahd, struct scb *scb)
{
ahd_swap_with_next_hscb(ahd, scb);
if (SCBID_IS_NULL(SCB_GET_TAG(scb)))
panic("Attempt to queue invalid SCB tag %x\n",
SCB_GET_TAG(scb));
/*
* Keep a history of SCBs we've downloaded in the qinfifo.
*/
ahd->qinfifo[AHD_QIN_WRAP(ahd->qinfifonext)] = SCB_GET_TAG(scb);
ahd->qinfifonext++;
/*
* Catch an interrupt from the adapter
*/
static __inline int
ahd_intr(void *arg)
{
struct ahd_softc *ahd = arg;
u_int intstat;
if ((ahd->pause & INTEN) == 0) {
/*
* Our interrupt is not enabled on the chip
* and may be disabled for re-entrancy reasons,
* so just return. This is likely just a shared
* interrupt.
*/
return (0);
}
/*
* Instead of directly reading the interrupt status register,
* infer the cause of the interrupt by checking our in-core
* completion queues. This avoids a costly PCI bus read in
* most cases.
*/
if ((ahd->flags & AHD_ALL_INTERRUPTS) == 0
&& (ahd_check_cmdcmpltqueues(ahd) != 0))
intstat = CMDCMPLT;
else
intstat = ahd_inb(ahd, INTSTAT);
if ((intstat & INT_PEND) == 0)
return (0);
if (intstat & CMDCMPLT) {
ahd_outb(ahd, CLRINT, CLRCMDINT);
/*
* Ensure that the chip sees that we've cleared
* this interrupt before we walk the output fifo.
* Otherwise, we may, due to posted bus writes,
* clear the interrupt after we finish the scan,
* and after the sequencer has added new entries
* and asserted the interrupt again.
*/
if ((ahd->bugs & AHD_INTCOLLISION_BUG) != 0) {
if (ahd_is_paused(ahd)) {
/*
* Potentially lost SEQINT.
* If SEQINTCODE is non-zero,
* simulate the SEQINT.
*/
if (ahd_inb(ahd, SEQINTCODE) != NO_SEQINT)
intstat |= SEQINT;
}
} else {
ahd_flush_device_writes(ahd);
}
scsipi_channel_freeze(&ahd->sc_channel, 1);
ahd_run_qoutfifo(ahd);
scsipi_channel_thaw(&ahd->sc_channel, 1);
ahd->cmdcmplt_counts[ahd->cmdcmplt_bucket]++;
ahd->cmdcmplt_total++;
#ifdef AHD_TARGET_MODE
if ((ahd->flags & AHD_TARGETROLE) != 0)
ahd_run_tqinfifo(ahd, /*paused*/FALSE);
#endif
if (intstat == CMDCMPLT)
return 1;
}
/*
* Handle statuses that may invalidate our cached
* copy of INTSTAT separately.
*/
if (intstat == 0xFF && (ahd->features & AHD_REMOVABLE) != 0) {
/* Hot eject. Do nothing */
} else if (intstat & HWERRINT) {
ahd_handle_hwerrint(ahd);
} else if ((intstat & (PCIINT|SPLTINT)) != 0) {
ahd->bus_intr(ahd);
} else {
if ((intstat & SEQINT) != 0)
ahd_handle_seqint(ahd, intstat);
if ((intstat & SCSIINT) != 0)
ahd_handle_scsiint(ahd, intstat);
}
return (1);
}
static __inline void
ahd_minphys(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, hence the "page per segment" limit
* enforced here.
*/
if (bp->b_bcount > AHD_MAXTRANSFER_SIZE) {
bp->b_bcount = AHD_MAXTRANSFER_SIZE;
}
minphys(bp);
}