aic79xx_core.c

来自「LINUX 2.6.17.4的源码」· C语言 代码 · 共 2,213 行 · 第 1/5 页

C
2,213
字号
			 */			ahd_outb(ahd, SCB_SCSI_STATUS, 0);			ahd_outb(ahd, SCB_SGPTR,				 ahd_inb_scbram(ahd, SCB_SGPTR)				 | SG_STATUS_VALID);			ahd_outw(ahd, SCB_TAG, scbid);			ahd_outw(ahd, SCB_NEXT_COMPLETE, SCB_LIST_NULL);			comp_head = ahd_inw(ahd, COMPLETE_DMA_SCB_HEAD);			if (SCBID_IS_NULL(comp_head)) {				ahd_outw(ahd, COMPLETE_DMA_SCB_HEAD, scbid);				ahd_outw(ahd, COMPLETE_DMA_SCB_TAIL, scbid);			} else {				u_int tail;				tail = ahd_inw(ahd, COMPLETE_DMA_SCB_TAIL);				ahd_set_scbptr(ahd, tail);				ahd_outw(ahd, SCB_NEXT_COMPLETE, scbid);				ahd_outw(ahd, COMPLETE_DMA_SCB_TAIL, scbid);				ahd_set_scbptr(ahd, scbid);			}		} else			ahd_complete_scb(ahd, scb);	}	ahd_set_scbptr(ahd, saved_scbptr);	/*	 * Setup for command channel portion of flush.	 */	ahd_set_modes(ahd, AHD_MODE_CCHAN, AHD_MODE_CCHAN);	/*	 * Wait for any inprogress DMA to complete and clear DMA state	 * if this if for an SCB in the qinfifo.	 */	while (((ccscbctl = ahd_inb(ahd, CCSCBCTL)) & (CCARREN|CCSCBEN)) != 0) {		if ((ccscbctl & (CCSCBDIR|CCARREN)) == (CCSCBDIR|CCARREN)) {			if ((ccscbctl & ARRDONE) != 0)				break;		} else if ((ccscbctl & CCSCBDONE) != 0)			break;		ahd_delay(200);	}	/*	 * We leave the sequencer to cleanup in the case of DMA's to	 * update the qoutfifo.  In all other cases (DMA's to the	 * chip or a push of an SCB from the COMPLETE_DMA_SCB list),	 * we disable the DMA engine so that the sequencer will not	 * attempt to handle the DMA completion.	 */	if ((ccscbctl & CCSCBDIR) != 0 || (ccscbctl & ARRDONE) != 0)		ahd_outb(ahd, CCSCBCTL, ccscbctl & ~(CCARREN|CCSCBEN));	/*	 * Complete any SCBs that just finished	 * being DMA'ed into the qoutfifo.	 */	ahd_run_qoutfifo(ahd);	saved_scbptr = ahd_get_scbptr(ahd);	/*	 * Manually update/complete any completed SCBs that are waiting to be	 * DMA'ed back up to the host.	 */	scbid = ahd_inw(ahd, COMPLETE_DMA_SCB_HEAD);	while (!SCBID_IS_NULL(scbid)) {		uint8_t *hscb_ptr;		u_int	 i;				ahd_set_scbptr(ahd, scbid);		next_scbid = ahd_inw_scbram(ahd, SCB_NEXT_COMPLETE);		scb = ahd_lookup_scb(ahd, scbid);		if (scb == NULL) {			printf("%s: Warning - DMA-up and complete "			       "SCB %d invalid\n", ahd_name(ahd), scbid);			continue;		}		hscb_ptr = (uint8_t *)scb->hscb;		for (i = 0; i < sizeof(struct hardware_scb); i++)			*hscb_ptr++ = ahd_inb_scbram(ahd, SCB_BASE + i);		ahd_complete_scb(ahd, scb);		scbid = next_scbid;	}	ahd_outw(ahd, COMPLETE_DMA_SCB_HEAD, SCB_LIST_NULL);	ahd_outw(ahd, COMPLETE_DMA_SCB_TAIL, SCB_LIST_NULL);	scbid = ahd_inw(ahd, COMPLETE_ON_QFREEZE_HEAD);	while (!SCBID_IS_NULL(scbid)) {		ahd_set_scbptr(ahd, scbid);		next_scbid = ahd_inw_scbram(ahd, SCB_NEXT_COMPLETE);		scb = ahd_lookup_scb(ahd, scbid);		if (scb == NULL) {			printf("%s: Warning - Complete Qfrz SCB %d invalid\n",			       ahd_name(ahd), scbid);			continue;		}		ahd_complete_scb(ahd, scb);		scbid = next_scbid;	}	ahd_outw(ahd, COMPLETE_ON_QFREEZE_HEAD, SCB_LIST_NULL);	scbid = ahd_inw(ahd, COMPLETE_SCB_HEAD);	while (!SCBID_IS_NULL(scbid)) {		ahd_set_scbptr(ahd, scbid);		next_scbid = ahd_inw_scbram(ahd, SCB_NEXT_COMPLETE);		scb = ahd_lookup_scb(ahd, scbid);		if (scb == NULL) {			printf("%s: Warning - Complete SCB %d invalid\n",			       ahd_name(ahd), scbid);			continue;		}		ahd_complete_scb(ahd, scb);		scbid = next_scbid;	}	ahd_outw(ahd, COMPLETE_SCB_HEAD, SCB_LIST_NULL);	/*	 * Restore state.	 */	ahd_set_scbptr(ahd, saved_scbptr);	ahd_restore_modes(ahd, saved_modes);	ahd->flags |= AHD_UPDATE_PEND_CMDS;}/* * Determine if an SCB for a packetized transaction * is active in a FIFO. */static intahd_scb_active_in_fifo(struct ahd_softc *ahd, struct scb *scb){	/*	 * The FIFO is only active for our transaction if	 * the SCBPTR matches the SCB's ID and the firmware	 * has installed a handler for the FIFO or we have	 * a pending SAVEPTRS or CFG4DATA interrupt.	 */	if (ahd_get_scbptr(ahd) != SCB_GET_TAG(scb)	 || ((ahd_inb(ahd, LONGJMP_ADDR+1) & INVALID_ADDR) != 0	  && (ahd_inb(ahd, SEQINTSRC) & (CFG4DATA|SAVEPTRS)) == 0))		return (0);	return (1);}/* * Run a data fifo to completion for a transaction we know * has completed across the SCSI bus (good status has been * received).  We are already set to the correct FIFO mode * on entry to this routine. * * This function attempts to operate exactly as the firmware * would when running this FIFO.  Care must be taken to update * this routine any time the firmware's FIFO algorithm is * changed. */static voidahd_run_data_fifo(struct ahd_softc *ahd, struct scb *scb){	u_int seqintsrc;	seqintsrc = ahd_inb(ahd, SEQINTSRC);	if ((seqintsrc & CFG4DATA) != 0) {		uint32_t datacnt;		uint32_t sgptr;		/*		 * Clear full residual flag.		 */		sgptr = ahd_inl_scbram(ahd, SCB_SGPTR) & ~SG_FULL_RESID;		ahd_outb(ahd, SCB_SGPTR, sgptr);		/*		 * Load datacnt and address.		 */		datacnt = ahd_inl_scbram(ahd, SCB_DATACNT);		if ((datacnt & AHD_DMA_LAST_SEG) != 0) {			sgptr |= LAST_SEG;			ahd_outb(ahd, SG_STATE, 0);		} else			ahd_outb(ahd, SG_STATE, LOADING_NEEDED);		ahd_outq(ahd, HADDR, ahd_inq_scbram(ahd, SCB_DATAPTR));		ahd_outl(ahd, HCNT, datacnt & AHD_SG_LEN_MASK);		ahd_outb(ahd, SG_CACHE_PRE, sgptr);		ahd_outb(ahd, DFCNTRL, PRELOADEN|SCSIEN|HDMAEN);		/*		 * Initialize Residual Fields.		 */		ahd_outb(ahd, SCB_RESIDUAL_DATACNT+3, datacnt >> 24);		ahd_outl(ahd, SCB_RESIDUAL_SGPTR, sgptr & SG_PTR_MASK);		/*		 * Mark the SCB as having a FIFO in use.		 */		ahd_outb(ahd, SCB_FIFO_USE_COUNT,			 ahd_inb_scbram(ahd, SCB_FIFO_USE_COUNT) + 1);		/*		 * Install a "fake" handler for this FIFO.		 */		ahd_outw(ahd, LONGJMP_ADDR, 0);		/*		 * Notify the hardware that we have satisfied		 * this sequencer interrupt.		 */		ahd_outb(ahd, CLRSEQINTSRC, CLRCFG4DATA);	} else if ((seqintsrc & SAVEPTRS) != 0) {		uint32_t sgptr;		uint32_t resid;		if ((ahd_inb(ahd, LONGJMP_ADDR+1)&INVALID_ADDR) != 0) {			/*			 * Snapshot Save Pointers.  All that			 * is necessary to clear the snapshot			 * is a CLRCHN.			 */			goto clrchn;		}		/*		 * Disable S/G fetch so the DMA engine		 * is available to future users.		 */		if ((ahd_inb(ahd, SG_STATE) & FETCH_INPROG) != 0)			ahd_outb(ahd, CCSGCTL, 0);		ahd_outb(ahd, SG_STATE, 0);		/*		 * Flush the data FIFO.  Strickly only		 * necessary for Rev A parts.		 */		ahd_outb(ahd, DFCNTRL, ahd_inb(ahd, DFCNTRL) | FIFOFLUSH);		/*		 * Calculate residual.		 */		sgptr = ahd_inl_scbram(ahd, SCB_RESIDUAL_SGPTR);		resid = ahd_inl(ahd, SHCNT);		resid |= ahd_inb_scbram(ahd, SCB_RESIDUAL_DATACNT+3) << 24;		ahd_outl(ahd, SCB_RESIDUAL_DATACNT, resid);		if ((ahd_inb(ahd, SG_CACHE_SHADOW) & LAST_SEG) == 0) {			/*			 * Must back up to the correct S/G element.			 * Typically this just means resetting our			 * low byte to the offset in the SG_CACHE,			 * but if we wrapped, we have to correct			 * the other bytes of the sgptr too.			 */			if ((ahd_inb(ahd, SG_CACHE_SHADOW) & 0x80) != 0			 && (sgptr & 0x80) == 0)				sgptr -= 0x100;			sgptr &= ~0xFF;			sgptr |= ahd_inb(ahd, SG_CACHE_SHADOW)			       & SG_ADDR_MASK;			ahd_outl(ahd, SCB_RESIDUAL_SGPTR, sgptr);			ahd_outb(ahd, SCB_RESIDUAL_DATACNT + 3, 0);		} else if ((resid & AHD_SG_LEN_MASK) == 0) {			ahd_outb(ahd, SCB_RESIDUAL_SGPTR,				 sgptr | SG_LIST_NULL);		}		/*		 * Save Pointers.		 */		ahd_outq(ahd, SCB_DATAPTR, ahd_inq(ahd, SHADDR));		ahd_outl(ahd, SCB_DATACNT, resid);		ahd_outl(ahd, SCB_SGPTR, sgptr);		ahd_outb(ahd, CLRSEQINTSRC, CLRSAVEPTRS);		ahd_outb(ahd, SEQIMODE,			 ahd_inb(ahd, SEQIMODE) | ENSAVEPTRS);		/*		 * If the data is to the SCSI bus, we are		 * done, otherwise wait for FIFOEMP.		 */		if ((ahd_inb(ahd, DFCNTRL) & DIRECTION) != 0)			goto clrchn;	} else if ((ahd_inb(ahd, SG_STATE) & LOADING_NEEDED) != 0) {		uint32_t sgptr;		uint64_t data_addr;		uint32_t data_len;		u_int	 dfcntrl;		/*		 * Disable S/G fetch so the DMA engine		 * is available to future users.  We won't		 * be using the DMA engine to load segments.		 */		if ((ahd_inb(ahd, SG_STATE) & FETCH_INPROG) != 0) {			ahd_outb(ahd, CCSGCTL, 0);			ahd_outb(ahd, SG_STATE, LOADING_NEEDED);		}		/*		 * Wait for the DMA engine to notice that the		 * host transfer is enabled and that there is		 * space in the S/G FIFO for new segments before		 * loading more segments.		 */		if ((ahd_inb(ahd, DFSTATUS) & PRELOAD_AVAIL) != 0		 && (ahd_inb(ahd, DFCNTRL) & HDMAENACK) != 0) {			/*			 * Determine the offset of the next S/G			 * element to load.			 */			sgptr = ahd_inl_scbram(ahd, SCB_RESIDUAL_SGPTR);			sgptr &= SG_PTR_MASK;			if ((ahd->flags & AHD_64BIT_ADDRESSING) != 0) {				struct ahd_dma64_seg *sg;				sg = ahd_sg_bus_to_virt(ahd, scb, sgptr);				data_addr = sg->addr;				data_len = sg->len;				sgptr += sizeof(*sg);			} else {				struct	ahd_dma_seg *sg;				sg = ahd_sg_bus_to_virt(ahd, scb, sgptr);				data_addr = sg->len & AHD_SG_HIGH_ADDR_MASK;				data_addr <<= 8;				data_addr |= sg->addr;				data_len = sg->len;				sgptr += sizeof(*sg);			}			/*			 * Update residual information.			 */			ahd_outb(ahd, SCB_RESIDUAL_DATACNT+3, data_len >> 24);			ahd_outl(ahd, SCB_RESIDUAL_SGPTR, sgptr);			/*			 * Load the S/G.			 */			if (data_len & AHD_DMA_LAST_SEG) {				sgptr |= LAST_SEG;				ahd_outb(ahd, SG_STATE, 0);			}			ahd_outq(ahd, HADDR, data_addr);			ahd_outl(ahd, HCNT, data_len & AHD_SG_LEN_MASK);			ahd_outb(ahd, SG_CACHE_PRE, sgptr & 0xFF);			/*			 * Advertise the segment to the hardware.			 */			dfcntrl = ahd_inb(ahd, DFCNTRL)|PRELOADEN|HDMAEN;			if ((ahd->features & AHD_NEW_DFCNTRL_OPTS) != 0) {				/*				 * Use SCSIENWRDIS so that SCSIEN				 * is never modified by this				 * operation.				 */				dfcntrl |= SCSIENWRDIS;			}			ahd_outb(ahd, DFCNTRL, dfcntrl);		}	} else if ((ahd_inb(ahd, SG_CACHE_SHADOW) & LAST_SEG_DONE) != 0) {		/*		 * Transfer completed to the end of SG list		 * and has flushed to the host.		 */		ahd_outb(ahd, SCB_SGPTR,			 ahd_inb_scbram(ahd, SCB_SGPTR) | SG_LIST_NULL);		goto clrchn;	} else if ((ahd_inb(ahd, DFSTATUS) & FIFOEMP) != 0) {clrchn:		/*		 * Clear any handler for this FIFO, decrement		 * the FIFO use count for the SCB, and release		 * the FIFO.		 */		ahd_outb(ahd, LONGJMP_ADDR + 1, INVALID_ADDR);		ahd_outb(ahd, SCB_FIFO_USE_COUNT,			 ahd_inb_scbram(ahd, SCB_FIFO_USE_COUNT) - 1);		ahd_outb(ahd, DFFSXFRCTL, CLRCHN);	}}/* * Look for entries in the QoutFIFO that have completed. * The valid_tag completion field indicates the validity * of the entry - the valid value toggles each time through * the queue. We use the sg_status field in the completion * entry to avoid referencing the hscb if the completion * occurred with no errors and no residual.  sg_status is * a copy of the first byte (little endian) of the sgptr * hscb field. */voidahd_run_qoutfifo(struct ahd_softc *ahd){	struct ahd_completion *completion;	struct scb *scb;	u_int  scb_index;	if ((ahd->flags & AHD_RUNNING_QOUTFIFO) != 0)		panic("ahd_run_qoutfifo recursion");	ahd->flags |= AHD_RUNNING_QOUTFIFO;	ahd_sync_qoutfifo(ahd, BUS_DMASYNC_POSTREAD);	for (;;) {		completion = &ahd->qoutfifo[ahd->qoutfifonext];		if (completion->valid_tag != ahd->qoutfifonext_valid_tag)			break;		scb_index = ahd_le16toh(completion->tag);		scb = ahd_lookup_scb(ahd, scb_index);		if (scb == NULL) {			printf("%s: WARNING no command for scb %d "			       "(cmdcmplt)\nQOUTPOS = %d\n",			       ahd_name(ahd), scb_index,			       ahd->qoutfifonext);			ahd_dump_card_state(ahd);		} else if ((completion->sg_status & SG_STATUS_VALID) != 0) {			ahd_handle_scb_status(ahd, scb);		} else {			ahd_done(ahd, scb);		}		ahd->qoutfifonext = (ahd->qoutfifonext+1) & (AHD_QOUT_SIZE-1);		if (ahd->qoutfifonext == 0)			ahd->qoutfifonext_valid_tag ^= QOUTFIFO_ENTRY_VALID;	}	ahd->flags &= ~AHD_RUNNING_QOUTFIFO;}/************************* Interrupt Handling *********************************/voidahd_handle_hwerrint(struct ahd_softc *ahd){	/*	 * Some catastrophic hardware error has occurred.	 * Print it for the user and disable the controller.	 */	int i;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?