URL
https://opencores.org/ocsvn/or1k/or1k/trunk
Subversion Repositories or1k
[/] [or1k/] [trunk/] [linux/] [linux-2.4/] [drivers/] [scsi/] [cpqfcTSworker.c] - Rev 1275
Go to most recent revision | Compare with Previous | Blame | View Log
/* Copyright(c) 2000, Compaq Computer Corporation * Fibre Channel Host Bus Adapter * 64-bit, 66MHz PCI * Originally developed and tested on: * (front): [chip] Tachyon TS HPFC-5166A/1.2 L2C1090 ... * SP# P225CXCBFIEL6T, Rev XC * SP# 161290-001, Rev XD * (back): Board No. 010008-001 A/W Rev X5, FAB REV X5 * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * Written by Don Zimmerman */ #include <linux/sched.h> #include <linux/timer.h> #include <linux/string.h> #include <linux/slab.h> #include <linux/ioport.h> #include <linux/kernel.h> #include <linux/stat.h> #include <linux/blk.h> #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/smp_lock.h> #define __KERNEL_SYSCALLS__ #define SHUTDOWN_SIGS (sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM)) #include <linux/unistd.h> #include <asm/system.h> #include <asm/irq.h> #include <asm/dma.h> #include "sd.h" #include "hosts.h" // struct Scsi_Host definition for T handler #include "cpqfcTSchip.h" #include "cpqfcTSstructs.h" #include "cpqfcTStrigger.h" //#define LOGIN_DBG 1 // REMARKS: // Since Tachyon chips may be permitted to wait from 500ms up to 2 sec // to empty an outgoing frame from its FIFO to the Fibre Channel stream, // we cannot do everything we need to in the interrupt handler. Specifically, // every time a link re-init (e.g. LIP) takes place, all SCSI I/O has to be // suspended until the login sequences have been completed. Login commands // are frames just like SCSI commands are frames; they are subject to the same // timeout issues and delays. Also, various specs provide up to 2 seconds for // devices to log back in (i.e. respond with ACC to a login frame), so I/O to // that device has to be suspended. // A serious problem here occurs on highly loaded FC-AL systems. If our FC port // has a low priority (e.g. high arbitrated loop physical address, alpa), and // some other device is hogging bandwidth (permissible under FC-AL), we might // time out thinking the link is hung, when it's simply busy. Many such // considerations complicate the design. Although Tachyon assumes control // (in silicon) for many link-specific issues, the Linux driver is left with the // rest, which turns out to be a difficult, time critical chore. // These "worker" functions will handle things like FC Logins; all // processes with I/O to our device must wait for the Login to complete // and (if successful) I/O to resume. In the event of a malfunctioning or // very busy loop, it may take hundreds of millisecs or even seconds to complete // a frame send. We don't want to hang up the entire server (and all // processes which don't depend on Fibre) during this wait. // The Tachyon chip can have around 30,000 I/O operations ("exchanges") // open at one time. However, each exchange must be initiated // synchronously (i.e. each of the 30k I/O had to be started one at a // time by sending a starting frame via Tachyon's outbound que). // To accomodate kernel "module" build, this driver limits the exchanges // to 256, because of the contiguous physical memory limitation of 128M. // Typical FC Exchanges are opened presuming the FC frames start without errors, // while Exchange completion is handled in the interrupt handler. This // optimizes performance for the "everything's working" case. // However, when we have FC related errors or hot plugging of FC ports, we pause // I/O and handle FC-specific tasks in the worker thread. These FC-specific // functions will handle things like FC Logins and Aborts. As the Login sequence // completes to each and every target, I/O can resume to that target. // Our kernel "worker thread" must share the HBA with threads calling // "queuecommand". We define a "BoardLock" semaphore which indicates // to "queuecommand" that the HBA is unavailable, and Cmnds are added to a // board lock Q. When the worker thread finishes with the board, the board // lock Q commands are completed with status causing immediate retry. // Typically, the board is locked while Logins are in progress after an // FC Link Down condition. When Cmnds are re-queued after board lock, the // particular Scsi channel/target may or may not have logged back in. When // the device is waiting for login, the "prli" flag is clear, in which case // commands are passed to a Link Down Q. Whenever the login finally completes, // the LinkDown Q is completed, again with status causing immediate retry. // When FC devices are logged in, we build and start FC commands to the // devices. // NOTE!! As of May 2000, kernel 2.2.14, the error recovery logic for devices // that never log back in (e.g. physically removed) is NOT completely // understood. I've still seen instances of system hangs on failed Write // commands (possibly from the ext2 layer?) on device removal. Such special // cases need to be evaluated from a system/application view - e.g., how // exactly does the system want me to complete commands when the device is // physically removed?? // local functions static void SetLoginFields(PFC_LOGGEDIN_PORT pLoggedInPort, TachFCHDR_GCMND * fchs, u8 PDisc, u8 Originator); static void AnalyzeIncomingFrame(CPQFCHBA * dev, u32 QNdx); static void SendLogins(CPQFCHBA * dev, __u32 * FabricPortIds); static int verify_PLOGI(PTACHYON fcChip, TachFCHDR_GCMND * fchs, u32 * reject_explain); static int verify_PRLI(TachFCHDR_GCMND * fchs, u32 * reject_explain); static void LoadWWN(PTACHYON fcChip, u8 * dest, u8 type); static void BuildLinkServicePayload(PTACHYON fcChip, u32 type, void *payload); static void UnblockScsiDevice(struct Scsi_Host *HostAdapter, PFC_LOGGEDIN_PORT pLoggedInPort); static void cpqfcTSCheckandSnoopFCP(PTACHYON fcChip, u32 x_ID); static void CompleteBoardLockCmnd(CPQFCHBA * dev); static void RevalidateSEST(struct Scsi_Host *HostAdapter, PFC_LOGGEDIN_PORT pLoggedInPort); static void IssueReportLunsCommand(CPQFCHBA * dev, TachFCHDR_GCMND * fchs); // (see scsi_error.c comments on kernel task creation) void cpqfcTSWorkerThread(void *host) { struct Scsi_Host *shpnt = (struct Scsi_Host *) host; CPQFCHBA *dev = (CPQFCHBA *) shpnt->hostdata; #ifdef PCI_KERNEL_TRACE PTACHYON fcChip = &dev->fcChip; #endif struct fs_struct *fs; DECLARE_MUTEX_LOCKED(fcQueReady); DECLARE_MUTEX_LOCKED(fcTYOBcomplete); DECLARE_MUTEX_LOCKED(TachFrozen); DECLARE_MUTEX_LOCKED(BoardLock); ENTER("WorkerThread"); lock_kernel(); /* * If we were started as result of loading a module, close all of the * user space pages. We don't need them, and if we didn't close them * they would be locked into memory. * * FIXME: should use daemonize! */ exit_mm(current); current->session = 1; current->pgrp = 1; /* Become as one with the init task */ exit_fs(current); /* current->fs->count--; */ fs = init_task.fs; current->fs = fs; atomic_inc(&fs->count); siginitsetinv(¤t->blocked, SHUTDOWN_SIGS); /* * Set the name of this process. */ sprintf(current->comm, "cpqfcTS_wt_%d", shpnt->host_no); dev->fcQueReady = &fcQueReady; // primary wait point dev->TYOBcomplete = &fcTYOBcomplete; dev->TachFrozen = &TachFrozen; dev->worker_thread = current; unlock_kernel(); if (dev->notify_wt != NULL) up(dev->notify_wt); // OK to continue while (1) { unsigned long flags; down_interruptible(&fcQueReady); // wait for something to do if (signal_pending(current)) break; PCI_TRACE(0x90) // first, take the IO lock so the SCSI upper layers can't call // into our _quecommand function (this also disables INTs) spin_lock_irqsave(&io_request_lock, flags); // STOP _que function PCI_TRACE(0x90) CPQ_SPINLOCK_HBA(dev) // next, set this pointer to indicate to the _quecommand function // that the board is in use, so it should que the command and // immediately return (we don't actually require the semaphore function // in this driver rev) dev->BoardLock = &BoardLock; PCI_TRACE(0x90) // release the IO lock (and re-enable interrupts) spin_unlock_irqrestore(&io_request_lock, flags); // disable OUR HBA interrupt (keep them off as much as possible // during error recovery) disable_irq(dev->HostAdapter->irq); // OK, let's process the Fibre Channel Link Q and do the work cpqfcTS_WorkTask(shpnt); // hopefully, no more "work" to do; // re-enable our INTs for "normal" completion processing enable_irq(dev->HostAdapter->irq); dev->BoardLock = NULL; // allow commands to be queued CPQ_SPINUNLOCK_HBA(dev) // Now, complete any Cmnd we Q'd up while BoardLock was held CompleteBoardLockCmnd(dev); } // hopefully, the signal was for our module exit... if (dev->notify_wt != NULL) up(dev->notify_wt); // yep, we're outta here } // Freeze Tachyon routine. // If Tachyon is already frozen, return 0 // If Tachyon is not frozen, call freeze function, return 1 // static u8 FreezeTach(CPQFCHBA * dev) { PTACHYON fcChip = &dev->fcChip; u8 FrozeTach = 0; // It's possible that the chip is already frozen; if so, // "Freezing" again will NOT! generate another Freeze // Completion Message. if ((fcChip->Registers.TYstatus.value & 0x70000) != 0x70000) { // (need to freeze...) fcChip->FreezeTachyon(fcChip, 2); // both ERQ and FCP assists // 2. Get Tach freeze confirmation // (synchronize SEST manipulation with Freeze Completion Message) // we need INTs on so semaphore can be set. enable_irq(dev->HostAdapter->irq); // only way to get Semaphore down_interruptible(dev->TachFrozen); // wait for INT handler sem. // can we TIMEOUT semaphore wait?? TBD disable_irq(dev->HostAdapter->irq); FrozeTach = 1; } // (else, already frozen) return FrozeTach; } // This is the kernel worker thread task, which processes FC // tasks which were queued by the Interrupt handler or by // other WorkTask functions. #define DBG 1 //#undef DBG void cpqfcTS_WorkTask(struct Scsi_Host *shpnt) { CPQFCHBA *dev = (CPQFCHBA *) shpnt->hostdata; PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; u32 QconsumerNdx; s32 ExchangeID; u32 ulStatus = 0; TachFCHDR_GCMND fchs; PFC_LINK_QUE fcLQ = dev->fcLQ; ENTER("WorkTask"); // copy current index to work on QconsumerNdx = fcLQ->consumer; PCI_TRACEO(fcLQ->Qitem[QconsumerNdx].Type, 0x90) // NOTE: when this switch completes, we will "consume" the Que item // printk("Que type %Xh\n", fcLQ->Qitem[QconsumerNdx].Type); switch (fcLQ->Qitem[QconsumerNdx].Type) { // incoming frame - link service (ACC, UNSOL REQ, etc.) // or FCP-SCSI command case SFQ_UNKNOWN: AnalyzeIncomingFrame(dev, QconsumerNdx); break; case EXCHANGE_QUEUED: // an Exchange (i.e. FCP-SCSI) was previously // Queued because the link was down. The // heartbeat timer detected it and Queued it here. // We attempt to start it again, and if // successful we clear the EXCHANGE_Q flag. // If the link doesn't come up, the Exchange // will eventually time-out. ExchangeID = (s32) fcLQ->Qitem[QconsumerNdx].ulBuff[0]; // x_ID copied from DPC timeout function // It's possible that a Q'd exchange could have already // been started by other logic (e.g. ABTS process) // Don't start if already started (Q'd flag clear) if (Exchanges->fcExchange[ExchangeID].status & EXCHANGE_QUEUED) { // printk(" *Start Q'd x_ID %Xh: type %Xh ", // ExchangeID, Exchanges->fcExchange[ExchangeID].type); ulStatus = cpqfcTSStartExchange(dev, ExchangeID); if (!ulStatus) { // printk("success* "); } else { #ifdef DBG if (ulStatus == EXCHANGE_QUEUED) printk("Queued* "); else printk("failed* "); #endif } } break; case LINKDOWN: // (lots of things already done in INT handler) future here? break; case LINKACTIVE: // Tachyon set the Lup bit in FM status // NOTE: some misbehaving FC ports (like Tach2.1) // can re-LIP immediately after a LIP completes. // if "initiator", need to verify LOGs with ports // printk("\n*LNKUP* "); if (fcChip->Options.initiator) SendLogins(dev, NULL); // PLOGI or PDISC, based on fcPort data // if SendLogins successfully completes, PortDiscDone // will be set. // If SendLogins was successful, then we expect to get incoming // ACCepts or REJECTs, which are handled below. break; // LinkService and Fabric request/reply processing case ELS_FDISC: // need to send Fabric Discovery (Login) case ELS_FLOGI: // need to send Fabric Login case ELS_SCR: // need to send State Change Registration case FCS_NSR: // need to send Name Service Request case ELS_PLOGI: // need to send PLOGI case ELS_ACC: // send generic ACCept case ELS_PLOGI_ACC: // need to send ELS ACCept frame to recv'd PLOGI case ELS_PRLI_ACC: // need to send ELS ACCept frame to recv'd PRLI case ELS_LOGO: // need to send ELS LOGO (logout) case ELS_LOGO_ACC: // need to send ELS ACCept frame to recv'd PLOGI case ELS_RJT: // ReJecT reply case ELS_PRLI: // need to send ELS PRLI // printk(" *ELS %Xh* ", fcLQ->Qitem[QconsumerNdx].Type); // if PortDiscDone is not set, it means the SendLogins routine // failed to complete -- assume that LDn occurred, so login frames // are invalid if (!dev->PortDiscDone) // cleared by LDn { printk("Discard Q'd ELS login frame\n"); break; } ulStatus = cpqfcTSBuildExchange(dev, fcLQ->Qitem[QconsumerNdx].Type, // e.g. PLOGI (TachFCHDR_GCMND *) fcLQ->Qitem[QconsumerNdx].ulBuff, // incoming fchs NULL, // no data (no scatter/gather list) &ExchangeID); // fcController->fcExchanges index, -1 if failed if (!ulStatus) // Exchange setup? { ulStatus = cpqfcTSStartExchange(dev, ExchangeID); if (!ulStatus) { // submitted to Tach's Outbound Que (ERQ PI incremented) // waited for completion for ELS type (Login frames issued // synchronously) } else // check reason for Exchange not being started - we might // want to Queue and start later, or fail with error { } } else // Xchange setup failed... printk(" cpqfcTSBuildExchange failed: %Xh\n", ulStatus); break; case SCSI_REPORT_LUNS: // pass the incoming frame (actually, it's a PRLI frame) // so we can send REPORT_LUNS, in order to determine VSA/PDU // FCP-SCSI Lun address mode IssueReportLunsCommand(dev, (TachFCHDR_GCMND *) fcLQ->Qitem[QconsumerNdx].ulBuff); break; case BLS_ABTS: // need to ABORT one or more exchanges { s32 x_ID = fcLQ->Qitem[QconsumerNdx].ulBuff[0]; u8 FrozeTach = 0; if (x_ID > TACH_SEST_LEN) // (in)sanity check { // printk( " cpqfcTS ERROR! BOGUS x_ID %Xh", x_ID); break; } if (Exchanges->fcExchange[x_ID].Cmnd == NULL) // should be RARE { // printk(" ABTS %Xh Scsi Cmnd null! ", x_ID); break; // nothing to abort! } //#define ABTS_DBG #ifdef ABTS_DBG printk("INV SEST[%X] ", x_ID); if (Exchanges->fcExchange[x_ID].status & FC2_TIMEOUT) { printk("FC2TO"); } if (Exchanges->fcExchange[x_ID].status & INITIATOR_ABORT) { printk("IA"); } if (Exchanges->fcExchange[x_ID].status & PORTID_CHANGED) { printk("PORTID"); } if (Exchanges->fcExchange[x_ID].status & DEVICE_REMOVED) { printk("DEVRM"); } if (Exchanges->fcExchange[x_ID].status & LINKFAIL_TX) { printk("LKF"); } if (Exchanges->fcExchange[x_ID].status & FRAME_TO) { printk("FRMTO"); } if (Exchanges->fcExchange[x_ID].status & ABORTSEQ_NOTIFY) { printk("ABSQ"); } if (Exchanges->fcExchange[x_ID].status & SFQ_FRAME) { printk("SFQFR"); } if (Exchanges->fcExchange[x_ID].type == 0x2000) printk(" WR"); else if (Exchanges->fcExchange[x_ID].type == 0x3000) printk(" RD"); else if (Exchanges->fcExchange[x_ID].type == 0x10) printk(" ABTS"); else printk(" %Xh", Exchanges->fcExchange[x_ID].type); if (!(Exchanges->fcExchange[x_ID].status & INITIATOR_ABORT)) { printk(" Cmd %p, ", Exchanges->fcExchange[x_ID].Cmnd); printk(" brd/chn/trg/lun %d/%d/%d/%d port_id %06X\n", dev->HBAnum, Exchanges->fcExchange[x_ID].Cmnd->channel, Exchanges->fcExchange[x_ID].Cmnd->target, Exchanges->fcExchange[x_ID].Cmnd->lun, Exchanges->fcExchange[x_ID].fchs.d_id & 0xFFFFFF); } else // assume that Cmnd ptr is invalid on _abort() { printk(" Cmd ptr invalid\n"); } #endif // Steps to ABORT a SEST exchange: // 1. Freeze TL SCSI assists & ERQ (everything) // 2. Receive FROZEN inbound CM (must succeed!) // 3. Invalidate x_ID SEST entry // 4. Resume TL SCSI assists & ERQ (everything) // 5. Build/start on exchange - change "type" to BLS_ABTS, // timeout to X sec (RA_TOV from PLDA is actually 0) // 6. Set Exchange Q'd status if ABTS cannot be started, // or simply complete Exchange in "Terminate" condition PCI_TRACEO(x_ID, 0xB4) // 1 & 2 . Freeze Tach & get confirmation of freeze FrozeTach = FreezeTach(dev); // 3. OK, Tachyon is frozen, so we can invalidate SEST exchange. // FC2_TIMEOUT means we are originating the abort, while // TARGET_ABORT means we are ACCepting an abort. // LINKFAIL_TX, ABORTSEQ_NOFITY, INV_ENTRY or FRAME_TO are // all from Tachyon: // Exchange was corrupted by LDn or other FC physical failure // INITIATOR_ABORT means the upper layer driver/application // requested the abort. // clear bit 31 (VALid), to invalidate & take control from TL fcChip->SEST->u[x_ID].IWE.Hdr_Len &= 0x7FFFFFFF; // examine and Tach's "Linked List" for IWEs that // received (nearly) simultaneous transfer ready (XRDY) // repair linked list if necessary (TBD!) // (If we ignore the "Linked List", we will time out // WRITE commands where we received the FCP-SCSI XFRDY // frame (because Tachyon didn't processes it). Linked List // management should be done as an optimization. // readl( fcChip->Registers.ReMapMemBase+TL_MEM_SEST_LINKED_LIST )); // 4. Resume all Tachlite functions (for other open Exchanges) // as quickly as possible to allow other exchanges to other ports // to resume. Freezing Tachyon may cause cascading errors, because // any received SEST frame cannot be processed by the SEST. // Don't "unfreeze" unless Link is operational if (FrozeTach) // did we just freeze it (above)? fcChip->UnFreezeTachyon(fcChip, 2); // both ERQ and FCP assists PCI_TRACEO(x_ID, 0xB4) // Note there is no confirmation that the chip is "unfrozen". Also, // if the Link is down when unfreeze is called, it has no effect. // Chip will unfreeze when the Link is back up. // 5. Now send out Abort commands if possible // Some Aborts can't be "sent" (Port_id changed or gone); // if the device is gone, there is no port_id to send the ABTS to. if (!(Exchanges->fcExchange[x_ID].status & PORTID_CHANGED) && !(Exchanges->fcExchange[x_ID].status & DEVICE_REMOVED)) { Exchanges->fcExchange[x_ID].type = BLS_ABTS; fchs.s_id = Exchanges->fcExchange[x_ID].fchs.d_id; ulStatus = cpqfcTSBuildExchange(dev, BLS_ABTS, &fchs, // (uses only s_id) NULL, // (no scatter/gather list for ABTS) &x_ID); // ABTS on this Exchange ID if (!ulStatus) // Exchange setup build OK? { // ABTS may be needed because an Exchange was corrupted // by a Link disruption. If the Link is UP, we can // presume that this ABTS can start immediately; otherwise, // set Que'd status so the Login functions // can restart it when the FC physical Link is restored if (((fcChip->Registers.FMstatus.value & 0xF0) & 0x80)) // loop init? { // printk(" *set Q status x_ID %Xh on LDn* ", x_ID); Exchanges->fcExchange[x_ID].status |= EXCHANGE_QUEUED; } else // what FC device (port_id) does the Cmd belong to? { PFC_LOGGEDIN_PORT pLoggedInPort = Exchanges->fcExchange[x_ID].pLoggedInPort; // if Port is logged in, we might start the abort. if ((pLoggedInPort != NULL) && (pLoggedInPort->prli == 1)) { // it's possible that an Exchange has already been Queued // to start after Login completes. Check and don't // start it (again) here if Q'd status set // printk(" ABTS xchg %Xh ", x_ID); if (Exchanges->fcExchange[x_ID].status & EXCHANGE_QUEUED) { // printk("already Q'd "); } else { // printk("starting "); fcChip->fcStats.FC2aborted++; ulStatus = cpqfcTSStartExchange(dev, x_ID); if (!ulStatus) { // OK // submitted to Tach's Outbound Que (ERQ PI incremented) } else { // printk("ABTS exchange start failed -status %Xh, x_ID %Xh ", ulStatus, x_ID); } } } } } else // what the #@! { // how do we fail to build an Exchange for ABTS?? printk("ABTS exchange build failed -status %Xh, x_ID %Xh\n", ulStatus, x_ID); } } else // abort without ABTS -- just complete exchange/Cmnd to Linux { // printk(" *Terminating x_ID %Xh on %Xh* ", // x_ID, Exchanges->fcExchange[x_ID].status); cpqfcTSCompleteExchange(dev->PciDev, fcChip, x_ID); } } // end of ABTS case break; case BLS_ABTS_ACC: // need to ACCept one ABTS // (NOTE! this code not updated for Linux yet..) printk(" *ABTS_ACC* "); // 1. Freeze TL fcChip->FreezeTachyon(fcChip, 2); // both ERQ and FCP assists memcpy( // copy the incoming ABTS frame &fchs, fcLQ->Qitem[QconsumerNdx].ulBuff, // incoming fchs sizeof(fchs)); // 3. OK, Tachyon is frozen so we can invalidate SEST entry // (if necessary) // Status FC2_TIMEOUT means we are originating the abort, while // TARGET_ABORT means we are ACCepting an abort ExchangeID = fchs.ox_rx_id & 0x7FFF; // RX_ID for exchange // printk("ABTS ACC for Target ExchangeID %Xh\n", ExchangeID); // sanity check on received ExchangeID if (Exchanges->fcExchange[ExchangeID].status == TARGET_ABORT) { // clear bit 31 (VALid), to invalidate & take control from TL // printk("Invalidating SEST exchange %Xh\n", ExchangeID); fcChip->SEST->u[ExchangeID].IWE.Hdr_Len &= 0x7FFFFFFF; } // 4. Resume all Tachlite functions (for other open Exchanges) // as quickly as possible to allow other exchanges to other ports // to resume. Freezing Tachyon for too long may royally screw // up everything! fcChip->UnFreezeTachyon(fcChip, 2); // both ERQ and FCP assists // Note there is no confirmation that the chip is "unfrozen". Also, // if the Link is down when unfreeze is called, it has no effect. // Chip will unfreeze when the Link is back up. // 5. Now send out Abort ACC reply for this exchange Exchanges->fcExchange[ExchangeID].type = BLS_ABTS_ACC; fchs.s_id = Exchanges->fcExchange[ExchangeID].fchs.d_id; ulStatus = cpqfcTSBuildExchange(dev, BLS_ABTS_ACC, &fchs, NULL, // no data (no scatter/gather list) &ExchangeID); // fcController->fcExchanges index, -1 if failed if (!ulStatus) // Exchange setup? { ulStatus = cpqfcTSStartExchange(dev, ExchangeID); if (!ulStatus) { // submitted to Tach's Outbound Que (ERQ PI incremented) // waited for completion for ELS type (Login frames issued // synchronously) } else // check reason for Exchange not being started - we might // want to Queue and start later, or fail with error { } } break; case BLS_ABTS_RJT: // need to ReJecT one ABTS; reject implies the // exchange doesn't exist in the TARGET context. // ExchangeID has to come from LinkService space. printk(" *ABTS_RJT* "); ulStatus = cpqfcTSBuildExchange(dev, BLS_ABTS_RJT, (TachFCHDR_GCMND *) fcLQ->Qitem[QconsumerNdx].ulBuff, // incoming fchs NULL, // no data (no scatter/gather list) &ExchangeID); // fcController->fcExchanges index, -1 if failed if (!ulStatus) // Exchange setup OK? { ulStatus = cpqfcTSStartExchange(dev, ExchangeID); // If it fails, we aren't required to retry. } if (ulStatus) { printk("Failed to send BLS_RJT for ABTS, X_ID %Xh\n", ExchangeID); } else { printk("Sent BLS_RJT for ABTS, X_ID %Xh\n", ExchangeID); } break; default: break; } // end switch // done with this item - now set the NEXT index if (QconsumerNdx + 1 >= FC_LINKQ_DEPTH) // rollover test fcLQ->consumer = 0; else fcLQ->consumer++; PCI_TRACEO(fcLQ->Qitem[QconsumerNdx].Type, 0x94) LEAVE("WorkTask"); return; } // When Tachyon reports link down, bad al_pa, or Link Service (e.g. Login) // commands come in, post to the LinkQ so that action can be taken outside the // interrupt handler. // This circular Q works like Tachyon's que - the producer points to the next // (unused) entry. Called by Interrupt handler, WorkerThread, Timer // sputlinkq void cpqfcTSPutLinkQue(CPQFCHBA * dev, int Type, void *QueContent) { PTACHYON fcChip = &dev->fcChip; // FC_EXCHANGES *Exchanges = fcChip->Exchanges; PFC_LINK_QUE fcLQ = dev->fcLQ; u32 ndx; ENTER("cpqfcTSPutLinkQ"); ndx = fcLQ->producer; ndx += 1; // test for Que full if (ndx >= FC_LINKQ_DEPTH) // rollover test ndx = 0; if (ndx == fcLQ->consumer) // QUE full test { // QUE was full! lost LK command (fatal to logic) fcChip->fcStats.lnkQueFull++; printk("*LinkQ Full!*"); TriggerHBA(fcChip->Registers.ReMapMemBase, 1); /* { int i; printk("LinkQ PI %d, CI %d\n", fcLQ->producer, fcLQ->consumer); for( i=0; i< FC_LINKQ_DEPTH; ) { printk(" [%d]%Xh ", i, fcLQ->Qitem[i].Type); if( (++i %8) == 0) printk("\n"); } } */ printk("cpqfcTS: WARNING!! PutLinkQue - FULL!\n"); // we're hung } else // QUE next element { // Prevent certain multiple (back-to-back) requests. // This is important in that we don't want to issue multiple // ABTS for the same Exchange, or do multiple FM inits, etc. // We can never be sure of the timing of events reported to // us by Tach's IMQ, which can depend on system/bus speeds, // FC physical link circumstances, etc. if ((fcLQ->producer != fcLQ->consumer) && (Type == FMINIT)) { s32 lastNdx; // compute previous producer index if (fcLQ->producer) lastNdx = fcLQ->producer - 1; else lastNdx = FC_LINKQ_DEPTH - 1; if (fcLQ->Qitem[lastNdx].Type == FMINIT) { // printk(" *skip FMINIT Q post* "); // goto DoneWithPutQ; } } // OK, add the Q'd item... fcLQ->Qitem[fcLQ->producer].Type = Type; memcpy(fcLQ->Qitem[fcLQ->producer].ulBuff, QueContent, sizeof(fcLQ->Qitem[fcLQ->producer].ulBuff)); fcLQ->producer = ndx; // increment Que producer // set semaphore to wake up Kernel (worker) thread up(dev->fcQueReady); } //DoneWithPutQ: LEAVE("cpqfcTSPutLinkQ"); } // reset device ext FC link Q void cpqfcTSLinkQReset(CPQFCHBA * dev) { PFC_LINK_QUE fcLQ = dev->fcLQ; fcLQ->producer = 0; fcLQ->consumer = 0; } // When Tachyon gets an unassisted FCP-SCSI frame, post here so // an arbitrary context thread (e.g. IOCTL loopback test function) // can process it. // (NOTE: Not revised for Linux) // This Q works like Tachyon's que - the producer points to the next // (unused) entry. void cpqfcTSPutScsiQue(CPQFCHBA * dev, int Type, void *QueContent) { // CPQFCHBA *dev = (CPQFCHBA *)shpnt->hostdata; // PTACHYON fcChip = &dev->fcChip; // u32 ndx; // u32 *pExchangeID; // s32 ExchangeID; /* KeAcquireSpinLockAtDpcLevel( &pDevExt->fcScsiQueLock); ndx = pDevExt->fcScsiQue.producer + 1; // test for Que full if( ndx >= FC_SCSIQ_DEPTH ) // rollover test ndx = 0; if( ndx == pDevExt->fcScsiQue.consumer ) // QUE full test { // QUE was full! lost LK command (fatal to logic) fcChip->fcStats.ScsiQueFull++; #ifdef DBG printk( "fcPutScsiQue - FULL!\n"); #endif } else // QUE next element { pDevExt->fcScsiQue.Qitem[pDevExt->fcScsiQue.producer].Type = Type; if( Type == FCP_RSP ) { // this TL inbound message type means that a TL SEST exchange has // copied an FCP response frame into a buffer pointed to by the SEST // entry. That buffer is allocated in the SEST structure at ->RspHDR. // Copy the RspHDR for use by the Que handler. pExchangeID = (u32 *)QueContent; memcpy( pDevExt->fcScsiQue.Qitem[pDevExt->fcScsiQue.producer].ulBuff, &fcChip->SEST->RspHDR[ *pExchangeID ], sizeof(pDevExt->fcScsiQue.Qitem[0].ulBuff)); // (any element for size) } else { memcpy( pDevExt->fcScsiQue.Qitem[pDevExt->fcScsiQue.producer].ulBuff, QueContent, sizeof(pDevExt->fcScsiQue.Qitem[pDevExt->fcScsiQue.producer].ulBuff)); } pDevExt->fcScsiQue.producer = ndx; // increment Que KeSetEvent( &pDevExt->TYIBscsi, // signal any waiting thread 0, // no priority boost 0 ); // no waiting later for this event } KeReleaseSpinLockFromDpcLevel( &pDevExt->fcScsiQueLock); */ } static void ProcessELS_Request(CPQFCHBA *, TachFCHDR_GCMND *); static void ProcessELS_Reply(CPQFCHBA *, TachFCHDR_GCMND *); static void ProcessFCS_Reply(CPQFCHBA *, TachFCHDR_GCMND *); void cpqfcTSImplicitLogout(CPQFCHBA * dev, PFC_LOGGEDIN_PORT pFcPort) { PTACHYON fcChip = &dev->fcChip; if (pFcPort->port_id != 0xFFFC01) // don't care about Fabric { fcChip->fcStats.logouts++; printk("cpqfcTS: Implicit logout of WWN %08X%08X, port_id %06X\n", (u32) pFcPort->u.liWWN, (u32) (pFcPort->u.liWWN >> 32), pFcPort->port_id); // Terminate I/O with this (Linux) Scsi target cpqfcTSTerminateExchange(dev, &pFcPort->ScsiNexus, DEVICE_REMOVED); } // Do an "implicit logout" - we can't really Logout the device // (i.e. with LOGOut Request) because of port_id confusion // (i.e. the Other port has no port_id). // A new login for that WWN will have to re-write port_id (0 invalid) pFcPort->port_id = 0; // invalid! pFcPort->pdisc = 0; pFcPort->prli = 0; pFcPort->plogi = 0; pFcPort->flogi = 0; pFcPort->LOGO_timer = 0; pFcPort->device_blocked = 1; // block Scsi Requests pFcPort->ScsiNexus.VolumeSetAddressing = 0; } // On FC-AL, there is a chance that a previously known device can // be quietly removed (e.g. with non-managed hub), // while a NEW device (with different WWN) took the same alpa or // even 24-bit port_id. This chance is unlikely but we must always // check for it. static void TestDuplicatePortId(CPQFCHBA * dev, PFC_LOGGEDIN_PORT pLoggedInPort) { PTACHYON fcChip = &dev->fcChip; // set "other port" at beginning of fcPorts list PFC_LOGGEDIN_PORT pOtherPortWithPortId = fcChip->fcPorts.pNextPort; while (pOtherPortWithPortId) { if ((pOtherPortWithPortId->port_id == pLoggedInPort->port_id) && (pOtherPortWithPortId != pLoggedInPort)) { // trouble! (Implicitly) Log the other guy out printk(" *port_id %Xh is duplicated!* ", pOtherPortWithPortId->port_id); cpqfcTSImplicitLogout(dev, pOtherPortWithPortId); } pOtherPortWithPortId = pOtherPortWithPortId->pNextPort; } } // Dynamic Memory Allocation for newly discovered FC Ports. // For simplicity, maintain fcPorts structs for ALL // for discovered devices, including those we never do I/O with // (e.g. Fabric addresses) static PFC_LOGGEDIN_PORT CreateFcPort(CPQFCHBA * dev, PFC_LOGGEDIN_PORT pLastLoggedInPort, TachFCHDR_GCMND * fchs, LOGIN_PAYLOAD * plogi) { PTACHYON fcChip = &dev->fcChip; PFC_LOGGEDIN_PORT pNextLoggedInPort = NULL; int i; printk("cpqfcTS: New FC port %06Xh WWN: ", fchs->s_id); for (i = 3; i >= 0; i--) // copy the LOGIN port's WWN printk("%02X", plogi->port_name[i]); for (i = 7; i > 3; i--) // copy the LOGIN port's WWN printk("%02X", plogi->port_name[i]); // allocate mem for new port // (these are small and rare allocations...) pNextLoggedInPort = kmalloc(sizeof(FC_LOGGEDIN_PORT), GFP_ATOMIC); // allocation succeeded? Fill out NEW PORT if (pNextLoggedInPort) { // clear out any garbage (sometimes exists) memset(pNextLoggedInPort, 0, sizeof(FC_LOGGEDIN_PORT)); // If we login to a Fabric, we don't want to treat it // as a SCSI device... if ((fchs->s_id & 0xFFF000) != 0xFFF000) { int i; // create a unique "virtual" SCSI Nexus (for now, just a // new target ID) -- we will update channel/target on REPORT_LUNS // special case for very first SCSI target... if (dev->HostAdapter->max_id == 0) { pNextLoggedInPort->ScsiNexus.target = 0; fcChip->fcPorts.ScsiNexus.target = -1; // don't use "stub" } else { pNextLoggedInPort->ScsiNexus.target = dev->HostAdapter->max_id; } // initialize the lun[] Nexus struct for lun masking for (i = 0; i < CPQFCTS_MAX_LUN; i++) pNextLoggedInPort->ScsiNexus.lun[i] = 0xFF; // init to NOT USED pNextLoggedInPort->ScsiNexus.channel = 0; // cpqfcTS has 1 FC port printk(" SCSI Chan/Trgt %d/%d", pNextLoggedInPort->ScsiNexus.channel, pNextLoggedInPort->ScsiNexus.target); // tell Scsi layers about the new target... dev->HostAdapter->max_id++; // printk("HostAdapter->max_id = %d\n", // dev->HostAdapter->max_id); } else { // device is NOT SCSI (in case of Fabric) pNextLoggedInPort->ScsiNexus.target = -1; // invalid } // create forward link to new port pLastLoggedInPort->pNextPort = pNextLoggedInPort; printk("\n"); } return pNextLoggedInPort; // NULL on allocation failure } // end NEW PORT (WWN) logic // For certain cases, we want to terminate exchanges without // sending ABTS to the device. Examples include when an FC // device changed it's port_id after Loop re-init, or when // the device sent us a logout. In the case of changed port_id, // we want to complete the command and return SOFT_ERROR to // force a re-try. In the case of LOGOut, we might return // BAD_TARGET if the device is really gone. // Since we must ensure that Tachyon is not operating on the // exchange, we have to freeze the chip // sterminateex void cpqfcTSTerminateExchange(CPQFCHBA * dev, SCSI_NEXUS * ScsiNexus, int TerminateStatus) { PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; u32 x_ID; if (ScsiNexus) { // printk("TerminateExchange: ScsiNexus chan/target %d/%d\n", // ScsiNexus->channel, ScsiNexus->target); } for (x_ID = 0; x_ID < TACH_SEST_LEN; x_ID++) { if (Exchanges->fcExchange[x_ID].type) // in use? { if (ScsiNexus == NULL) // our HBA changed - term. all { Exchanges->fcExchange[x_ID].status = TerminateStatus; cpqfcTSPutLinkQue(dev, BLS_ABTS, &x_ID); } else { // If a device, according to WWN, has been removed, it's // port_id may be used by another working device, so we // have to terminate by SCSI target, NOT port_id. if (Exchanges->fcExchange[x_ID].Cmnd) // Cmnd in progress? { if ((Exchanges->fcExchange[x_ID].Cmnd->target == ScsiNexus->target) && (Exchanges->fcExchange[x_ID].Cmnd->channel == ScsiNexus->channel)) { Exchanges->fcExchange[x_ID].status = TerminateStatus; cpqfcTSPutLinkQue(dev, BLS_ABTS, &x_ID); // timed-out } } // (in case we ever need it...) // all SEST structures have a remote node ID at SEST DWORD 2 // if( (fcChip->SEST->u[ x_ID ].TWE.Remote_Node_ID >> 8) // == port_id) } } } } static void ProcessELS_Request(CPQFCHBA * dev, TachFCHDR_GCMND * fchs) { PTACHYON fcChip = &dev->fcChip; // FC_EXCHANGES *Exchanges = fcChip->Exchanges; // u32 ox_id = (fchs->ox_rx_id >>16); PFC_LOGGEDIN_PORT pLoggedInPort = NULL, pLastLoggedInPort; u8 NeedReject = 0; u32 ls_reject_code = 0; // default don'n know?? // Check the incoming frame for a supported ELS type switch (fchs->pl[0] & 0xFFFF) { case 0x0050: // PDISC? // Payload for PLOGI and PDISC is identical (request & reply) if (!verify_PLOGI(fcChip, fchs, &ls_reject_code)) // valid payload? { LOGIN_PAYLOAD logi; // FC-PH Port Login // PDISC payload OK. If critical login fields // (e.g. WWN) matches last login for this port_id, // we may resume any prior exchanges // with the other port BigEndianSwap((u8 *) & fchs->pl[0], (u8 *) & logi, sizeof(logi)); pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // don't search Scsi Nexus 0, // don't search linked list for port_id &logi.port_name[0], // search linked list for WWN &pLastLoggedInPort); // must return non-NULL; when a port_id // is not found, this pointer marks the // end of the singly linked list if (pLoggedInPort != NULL) // WWN found (prior login OK) { if ((fchs->s_id & 0xFFFFFF) == pLoggedInPort->port_id) { // Yes. We were expecting PDISC? if (pLoggedInPort->pdisc) { // Yes; set fields accordingly. (PDISC, not Originator) SetLoginFields(pLoggedInPort, fchs, 1, 0); // send 'ACC' reply cpqfcTSPutLinkQue(dev, ELS_PLOGI_ACC, // (PDISC same as PLOGI ACC) fchs); // OK to resume I/O... } else { printk("Not expecting PDISC (pdisc=0)\n"); NeedReject = 1; // set reject reason code ls_reject_code = LS_RJT_REASON(PROTOCOL_ERROR, INITIATOR_CTL_ERROR); } } else { if (pLoggedInPort->port_id != 0) { printk("PDISC PortID change: old %Xh, new %Xh\n", pLoggedInPort->port_id, fchs->s_id & 0xFFFFFF); } NeedReject = 1; // set reject reason code ls_reject_code = LS_RJT_REASON(PROTOCOL_ERROR, INITIATOR_CTL_ERROR); } } else { printk("PDISC Request from unknown WWN\n"); NeedReject = 1; // set reject reason code ls_reject_code = LS_RJT_REASON(LOGICAL_ERROR, INVALID_PORT_NAME); } } else // Payload unacceptable { printk("payload unacceptable\n"); NeedReject = 1; // reject code already set } if (NeedReject) { u32 port_id; // The PDISC failed. Set login struct flags accordingly, // terminate any I/O to this port, and Q a PLOGI if (pLoggedInPort) { pLoggedInPort->pdisc = 0; pLoggedInPort->prli = 0; pLoggedInPort->plogi = 0; cpqfcTSTerminateExchange(dev, &pLoggedInPort->ScsiNexus, PORTID_CHANGED); port_id = pLoggedInPort->port_id; } else { port_id = fchs->s_id & 0xFFFFFF; } fchs->reserved = ls_reject_code; // borrow this (unused) field cpqfcTSPutLinkQue(dev, ELS_RJT, fchs); } break; case 0x0003: // PLOGI? // Payload for PLOGI and PDISC is identical (request & reply) if (!verify_PLOGI(fcChip, fchs, &ls_reject_code)) // valid payload? { LOGIN_PAYLOAD logi; // FC-PH Port Login u8 NeedReject = 0; // PDISC payload OK. If critical login fields // (e.g. WWN) matches last login for this port_id, // we may resume any prior exchanges // with the other port BigEndianSwap((u8 *) & fchs->pl[0], (u8 *) & logi, sizeof(logi)); pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // don't search Scsi Nexus 0, // don't search linked list for port_id &logi.port_name[0], // search linked list for WWN &pLastLoggedInPort); // must return non-NULL; when a port_id // is not found, this pointer marks the // end of the singly linked list if (pLoggedInPort == NULL) // WWN not found -New Port { pLoggedInPort = CreateFcPort(dev, pLastLoggedInPort, fchs, &logi); if (pLoggedInPort == NULL) { printk(" cpqfcTS: New port allocation failed - lost FC device!\n"); // Now Q a LOGOut Request, since we won't be talking to that device NeedReject = 1; // set reject reason code ls_reject_code = LS_RJT_REASON(LOGICAL_ERROR, NO_LOGIN_RESOURCES); } } if (!NeedReject) { // OK - we have valid fcPort ptr; set fields accordingly. // (not PDISC, not Originator) SetLoginFields(pLoggedInPort, fchs, 0, 0); // send 'ACC' reply cpqfcTSPutLinkQue(dev, ELS_PLOGI_ACC, // (PDISC same as PLOGI ACC) fchs); } } else // Payload unacceptable { printk("payload unacceptable\n"); NeedReject = 1; // reject code already set } if (NeedReject) { // The PDISC failed. Set login struct flags accordingly, // terminate any I/O to this port, and Q a PLOGI pLoggedInPort->pdisc = 0; pLoggedInPort->prli = 0; pLoggedInPort->plogi = 0; fchs->reserved = ls_reject_code; // borrow this (unused) field // send 'RJT' reply cpqfcTSPutLinkQue(dev, ELS_RJT, fchs); } // terminate any exchanges with this device... if (pLoggedInPort) { cpqfcTSTerminateExchange(dev, &pLoggedInPort->ScsiNexus, PORTID_CHANGED); } break; case 0x1020: // PRLI? { u8 NeedReject = 1; pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // don't search Scsi Nexus (fchs->s_id & 0xFFFFFF), // search linked list for port_id NULL, // DON'T search linked list for WWN NULL); // don't care if (pLoggedInPort == NULL) { // huh? printk(" Unexpected PRLI Request -not logged in!\n"); // set reject reason code ls_reject_code = LS_RJT_REASON(PROTOCOL_ERROR, INITIATOR_CTL_ERROR); // Q a LOGOut here? } else { // verify the PRLI ACC payload if (!verify_PRLI(fchs, &ls_reject_code)) { // PRLI Reply is acceptable; were we expecting it? if (pLoggedInPort->plogi) { // yes, we expected the PRLI ACC (not PDISC; not Originator) SetLoginFields(pLoggedInPort, fchs, 0, 0); // Q an ACCept Reply cpqfcTSPutLinkQue(dev, ELS_PRLI_ACC, fchs); NeedReject = 0; } else { // huh? printk(" (unexpected) PRLI REQEST with plogi 0\n"); // set reject reason code ls_reject_code = LS_RJT_REASON(PROTOCOL_ERROR, INITIATOR_CTL_ERROR); // Q a LOGOut here? } } else { printk(" PRLI REQUEST payload failed verify\n"); // (reject code set by "verify") // Q a LOGOut here? } } if (NeedReject) { // Q a ReJecT Reply with reason code fchs->reserved = ls_reject_code; cpqfcTSPutLinkQue(dev, ELS_RJT, // Q Type fchs); } } break; case 0x0005: // LOGOut? { // was this LOGOUT because we sent a ELS_PDISC to an FC device // with changed (or new) port_id, or does the port refuse // to communicate to us? // We maintain a logout counter - if we get 3 consecutive LOGOuts, // give up! LOGOUT_PAYLOAD logo; u8 GiveUpOnDevice = 0; u32 ls_reject_code = 0; BigEndianSwap((u8 *) & fchs->pl[0], (u8 *) & logo, sizeof(logo)); pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // don't search Scsi Nexus 0, // don't search linked list for port_id &logo.port_name[0], // search linked list for WWN NULL); // don't care about end of list if (pLoggedInPort) // found the device? { // Q an ACC reply cpqfcTSPutLinkQue(dev, ELS_LOGO_ACC, // Q Type fchs); // device to respond to // set login struct fields (LOGO_counter increment) SetLoginFields(pLoggedInPort, fchs, 0, 0); // are we an Initiator? if (fcChip->Options.initiator) { // we're an Initiator, so check if we should // try (another?) login // Fabrics routinely log out from us after // getting device info - don't try to log them // back in. if ((fchs->s_id & 0xFFF000) == 0xFFF000) { ; // do nothing } else if (pLoggedInPort->LOGO_counter <= 3) { // try (another) login (PLOGI request) cpqfcTSPutLinkQue(dev, ELS_PLOGI, // Q Type fchs); // Terminate I/O with "retry" potential cpqfcTSTerminateExchange(dev, &pLoggedInPort->ScsiNexus, PORTID_CHANGED); } else { printk(" Got 3 LOGOuts - terminating comm. with port_id %Xh\n", fchs->s_id && 0xFFFFFF); GiveUpOnDevice = 1; } } else { GiveUpOnDevice = 1; } if (GiveUpOnDevice == 1) { cpqfcTSTerminateExchange(dev, &pLoggedInPort->ScsiNexus, DEVICE_REMOVED); } } else // we don't know this WWN! { // Q a ReJecT Reply with reason code fchs->reserved = ls_reject_code; cpqfcTSPutLinkQue(dev, ELS_RJT, // Q Type fchs); } } break; // FABRIC only case case 0x0461: // ELS RSCN (Registered State Change Notification)? { int Ports; int i; __u32 Buff; // Typically, one or more devices have been added to or dropped // from the Fabric. // The format of this frame is defined in FC-FLA (Rev 2.7, Aug 1997) // The first 32-bit word has a 2-byte Payload Length, which // includes the 4 bytes of the first word. Consequently, // this PL len must never be less than 4, must be a multiple of 4, // and has a specified max value 256. // (Endianess!) Ports = ((fchs->pl[0] >> 24) - 4) / 4; Ports = Ports > 63 ? 63 : Ports; printk(" RSCN ports: %d\n", Ports); if (Ports <= 0) // huh? { // ReJecT the command fchs->reserved = LS_RJT_REASON(UNABLE_TO_PERFORM, 0); cpqfcTSPutLinkQue(dev, ELS_RJT, // Q Type fchs); break; } else // Accept the command { cpqfcTSPutLinkQue(dev, ELS_ACC, // Q Type fchs); } // Check the "address format" to determine action. // We have 3 cases: // 0 = Port Address; 24-bit address of affected device // 1 = Area Address; MS 16 bits valid // 2 = Domain Address; MS 8 bits valid for (i = 0; i < Ports; i++) { BigEndianSwap((u8 *) & fchs->pl[i + 1], (u8 *) & Buff, 4); switch (Buff & 0xFF000000) { case 0: // Port Address? case 0x01000000: // Area Domain? case 0x02000000: // Domain Address // For example, "port_id" 0x201300 // OK, let's try a Name Service Request (Query) fchs->s_id = 0xFFFFFC; // Name Server Address cpqfcTSPutLinkQue(dev, FCS_NSR, fchs); break; default: // huh? new value on version change? break; } } } break; default: // don't support this request (yet) // set reject reason code fchs->reserved = LS_RJT_REASON(UNABLE_TO_PERFORM, REQUEST_NOT_SUPPORTED); cpqfcTSPutLinkQue(dev, ELS_RJT, fchs); // Q Type break; } } static void ProcessELS_Reply(CPQFCHBA * dev, TachFCHDR_GCMND * fchs) { PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; u32 ox_id = (fchs->ox_rx_id >> 16); u32 ls_reject_code; PFC_LOGGEDIN_PORT pLoggedInPort, pLastLoggedInPort; // If this is a valid reply, then we MUST have sent a request. // Verify that we can find a valid request OX_ID corresponding to // this reply if (Exchanges->fcExchange[(fchs->ox_rx_id >> 16)].type == 0) { printk(" *Discarding ACC/RJT frame, xID %04X/%04X* ", ox_id, fchs->ox_rx_id & 0xffff); goto Quit; // exit this routine } // Is the reply a RJT (reject)? if ((fchs->pl[0] & 0xFFFFL) == 0x01) // Reject reply? { // ****** REJECT REPLY ******** switch (Exchanges->fcExchange[ox_id].type) { case ELS_FDISC: // we sent out Fabric Discovery case ELS_FLOGI: // we sent out FLOGI printk("RJT received on Fabric Login from %Xh, reason %Xh\n", fchs->s_id, fchs->pl[1]); break; default: break; } goto Done; } // OK, we have an ACCept... // What's the ACC type? (according to what we sent) switch (Exchanges->fcExchange[ox_id].type) { case ELS_PLOGI: // we sent out PLOGI if (!verify_PLOGI(fcChip, fchs, &ls_reject_code)) { LOGIN_PAYLOAD logi; // FC-PH Port Login // login ACC payload acceptable; search for WWN in our list // of fcPorts BigEndianSwap((u8 *) & fchs->pl[0], (u8 *) & logi, sizeof(logi)); pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // don't search Scsi Nexus 0, // don't search linked list for port_id &logi.port_name[0], // search linked list for WWN &pLastLoggedInPort); // must return non-NULL; when a port_id // is not found, this pointer marks the // end of the singly linked list if (pLoggedInPort == NULL) // WWN not found - new port { pLoggedInPort = CreateFcPort(dev, pLastLoggedInPort, fchs, &logi); if (pLoggedInPort == NULL) { printk(" cpqfcTS: New port allocation failed - lost FC device!\n"); // Now Q a LOGOut Request, since we won't be talking to that device goto Done; // exit with error! dropped login frame } } else // WWN was already known. Ensure that any open // exchanges for this WWN are terminated. // NOTE: It's possible that a device can change its // 24-bit port_id after a Link init or Fabric change // (e.g. LIP or Fabric RSCN). In that case, the old // 24-bit port_id may be duplicated, or no longer exist. { cpqfcTSTerminateExchange(dev, &pLoggedInPort->ScsiNexus, PORTID_CHANGED); } // We have an fcPort struct - set fields accordingly // not PDISC, originator SetLoginFields(pLoggedInPort, fchs, 0, 1); // We just set a "port_id"; is it duplicated? TestDuplicatePortId(dev, pLoggedInPort); // For Fabric operation, we issued PLOGI to 0xFFFFFC // so we can send SCR (State Change Registration) // Check for this special case... if (fchs->s_id == 0xFFFFFC) { // PLOGI ACC was a Fabric response... issue SCR fchs->s_id = 0xFFFFFD; // address for SCR cpqfcTSPutLinkQue(dev, ELS_SCR, fchs); } else { // Now we need a PRLI to enable FCP-SCSI operation // set flags and Q up a ELS_PRLI cpqfcTSPutLinkQue(dev, ELS_PRLI, fchs); } } else { // login payload unacceptable - reason in ls_reject_code // Q up a Logout Request printk("Login Payload unacceptable\n"); } break; // PDISC logic very similar to PLOGI, except we never want // to allocate mem for "new" port, and we set flags differently // (might combine later with PLOGI logic for efficiency) case ELS_PDISC: // we sent out PDISC if (!verify_PLOGI(fcChip, fchs, &ls_reject_code)) { LOGIN_PAYLOAD logi; // FC-PH Port Login u8 NeedLogin = 0; // login payload acceptable; search for WWN in our list // of (previously seen) fcPorts BigEndianSwap((u8 *) & fchs->pl[0], (u8 *) & logi, sizeof(logi)); pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // don't search Scsi Nexus 0, // don't search linked list for port_id &logi.port_name[0], // search linked list for WWN &pLastLoggedInPort); // must return non-NULL; when a port_id // is not found, this pointer marks the // end of the singly linked list if (pLoggedInPort != NULL) // WWN found? { // WWN has same port_id as last login? (Of course, a properly // working FC device should NEVER ACCept a PDISC if it's // port_id changed, but check just in case...) if ((fchs->s_id & 0xFFFFFF) == pLoggedInPort->port_id) { // Yes. We were expecting PDISC? if (pLoggedInPort->pdisc) { int i; // PDISC expected -- set fields. (PDISC, Originator) SetLoginFields(pLoggedInPort, fchs, 1, 1); // We are ready to resume FCP-SCSI to this device... // Do we need to start anything that was Queued? for (i = 0; i < TACH_SEST_LEN; i++) { // see if any exchange for this PDISC'd port was queued if (((fchs->s_id & 0xFFFFFF) == (Exchanges->fcExchange[i].fchs.d_id & 0xFFFFFF)) && (Exchanges->fcExchange[i].status & EXCHANGE_QUEUED)) { fchs->reserved = i; // copy ExchangeID // printk(" *Q x_ID %Xh after PDISC* ",i); cpqfcTSPutLinkQue(dev, EXCHANGE_QUEUED, fchs); } } // Complete commands Q'd while we were waiting for Login UnblockScsiDevice(dev->HostAdapter, pLoggedInPort); } else { printk("Not expecting PDISC (pdisc=0)\n"); NeedLogin = 1; } } else { printk("PDISC PortID change: old %Xh, new %Xh\n", pLoggedInPort->port_id, fchs->s_id & 0xFFFFFF); NeedLogin = 1; } } else { printk("PDISC ACC from unknown WWN\n"); NeedLogin = 1; } if (NeedLogin) { // The PDISC failed. Set login struct flags accordingly, // terminate any I/O to this port, and Q a PLOGI if (pLoggedInPort) // FC device previously known? { cpqfcTSPutLinkQue(dev, ELS_LOGO, fchs); // Qtype has port_id to send to // There are a variety of error scenarios which can result // in PDISC failure, so as a catchall, add the check for // duplicate port_id. TestDuplicatePortId(dev, pLoggedInPort); // TriggerHBA( fcChip->Registers.ReMapMemBase, 0); pLoggedInPort->pdisc = 0; pLoggedInPort->prli = 0; pLoggedInPort->plogi = 0; cpqfcTSTerminateExchange(dev, &pLoggedInPort->ScsiNexus, PORTID_CHANGED); } cpqfcTSPutLinkQue(dev, ELS_PLOGI, fchs); } } else { // login payload unacceptable - reason in ls_reject_code // Q up a Logout Request printk("ERROR: Login Payload unacceptable!\n"); } break; case ELS_PRLI: // we sent out PRLI pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // don't search Scsi Nexus (fchs->s_id & 0xFFFFFF), // search linked list for port_id NULL, // DON'T search linked list for WWN NULL); // don't care if (pLoggedInPort == NULL) { // huh? printk(" Unexpected PRLI ACCept frame!\n"); // Q a LOGOut here? goto Done; } // verify the PRLI ACC payload if (!verify_PRLI(fchs, &ls_reject_code)) { // PRLI Reply is acceptable; were we expecting it? if (pLoggedInPort->plogi) { // yes, we expected the PRLI ACC (not PDISC; Originator) SetLoginFields(pLoggedInPort, fchs, 0, 1); // OK, let's send a REPORT_LUNS command to determine // whether VSA or PDA FCP-LUN addressing is used. cpqfcTSPutLinkQue(dev, SCSI_REPORT_LUNS, fchs); // It's possible that a device we were talking to changed // port_id, and has logged back in. This function ensures // that I/O will resume. UnblockScsiDevice(dev->HostAdapter, pLoggedInPort); } else { // huh? printk(" (unexpected) PRLI ACCept with plogi 0\n"); // Q a LOGOut here? goto Done; } } else { printk(" PRLI ACCept payload failed verify\n"); // Q a LOGOut here? } break; case ELS_FLOGI: // we sent out FLOGI (Fabric Login) // update the upper 16 bits of our port_id in Tachyon // the switch adds those upper 16 bits when responding // to us (i.e. we are the destination_id) fcChip->Registers.my_al_pa = (fchs->d_id & 0xFFFFFF); writel(fcChip->Registers.my_al_pa, fcChip->Registers.ReMapMemBase + TL_MEM_TACH_My_ID); // now send out a PLOGI to the well known port_id 0xFFFFFC fchs->s_id = 0xFFFFFC; cpqfcTSPutLinkQue(dev, ELS_PLOGI, fchs); break; case ELS_FDISC: // we sent out FDISC (Fabric Discovery (Login)) printk(" ELS_FDISC success "); break; case ELS_SCR: // we sent out State Change Registration // now we can issue Name Service Request to find any // Fabric-connected devices we might want to login to. fchs->s_id = 0xFFFFFC; // Name Server Address cpqfcTSPutLinkQue(dev, FCS_NSR, fchs); break; default: printk(" *Discarding unknown ACC frame, xID %04X/%04X* ", ox_id, fchs->ox_rx_id & 0xffff); break; } Done: // Regardless of whether the Reply is valid or not, the // the exchange is done - complete cpqfcTSCompleteExchange(dev->PciDev, fcChip, (fchs->ox_rx_id >> 16)); Quit: return; } // **************** Fibre Channel Services ************** // This is where we process the Directory (Name) Service Reply // to know which devices are on the Fabric static void ProcessFCS_Reply(CPQFCHBA * dev, TachFCHDR_GCMND * fchs) { PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; u32 ox_id = (fchs->ox_rx_id >> 16); // u32 ls_reject_code; // PFC_LOGGEDIN_PORT pLoggedInPort, pLastLoggedInPort; // If this is a valid reply, then we MUST have sent a request. // Verify that we can find a valid request OX_ID corresponding to // this reply if (Exchanges->fcExchange[(fchs->ox_rx_id >> 16)].type == 0) { printk(" *Discarding Reply frame, xID %04X/%04X* ", ox_id, fchs->ox_rx_id & 0xffff); goto Quit; // exit this routine } // OK, we were expecting it. Now check to see if it's a // "Name Service" Reply, and if so force a re-validation of // Fabric device logins (i.e. Start the login timeout and // send PDISC or PLOGI) // (Endianess Byte Swap?) if (fchs->pl[1] == 0x02FC) // Name Service { // got a new (or NULL) list of Fabric attach devices... // Invalidate current logins PFC_LOGGEDIN_PORT pLoggedInPort = &fcChip->fcPorts; while (pLoggedInPort) // for all ports which are expecting // PDISC after the next LIP, set the // logoutTimer { if ((pLoggedInPort->port_id & 0xFFFF00) // Fabric device? && (pLoggedInPort->port_id != 0xFFFFFC)) // NOT the F_Port { pLoggedInPort->LOGO_timer = 6; // what's the Fabric timeout?? // suspend any I/O in progress until // PDISC received... pLoggedInPort->prli = 0; // block FCP-SCSI commands } pLoggedInPort = pLoggedInPort->pNextPort; } if (fchs->pl[2] == 0x0280) // ACCept? { // Send PLOGI or PDISC to these Fabric devices SendLogins(dev, &fchs->pl[4]); } // As of this writing, the only reason to reject is because NO // devices are left on the Fabric. We already started // "logged out" timers; if the device(s) don't come // back, we'll do the implicit logout in the heart beat // timer routine else // ReJecT { // this just means no Fabric device is visible at this instant } } // Regardless of whether the Reply is valid or not, the // the exchange is done - complete cpqfcTSCompleteExchange(dev->PciDev, fcChip, (fchs->ox_rx_id >> 16)); Quit: return; } static void AnalyzeIncomingFrame(CPQFCHBA * dev, u32 QNdx) { PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; PFC_LINK_QUE fcLQ = dev->fcLQ; TachFCHDR_GCMND *fchs = (TachFCHDR_GCMND *) fcLQ->Qitem[QNdx].ulBuff; // u32 ls_reject_code; // reason for rejecting login s32 ExchangeID; // FC_LOGGEDIN_PORT *pLoggedInPort; u8 AbortAccept; ENTER("AnalyzeIncomingFrame"); switch (fcLQ->Qitem[QNdx].Type) // FCP or Unknown { case SFQ_UNKNOWN: // unknown frame (e.g. LIP position frame, NOP, etc.) // ********* FC-4 Device Data/ Fibre Channel Service ************* if (((fchs->d_id & 0xF0000000) == 0) // R_CTL (upper nibble) 0x0? && (fchs->f_ctl & 0x20000000)) // TYPE 20h is Fibre Channel Service { // ************** FCS Reply ********************** if ((fchs->d_id & 0xff000000L) == 0x03000000L) // (31:23 R_CTL) { ProcessFCS_Reply(dev, fchs); } // end of FCS logic } // *********** Extended Link Service ************** else if (fchs->d_id & 0x20000000 // R_CTL 0x2? && (fchs->f_ctl & 0x01000000)) // TYPE = 1 { // these frames are either a response to // something we sent (0x23) or "unsolicited" // frames (0x22). // **************Extended Link REPLY ********************** // R_CTL Solicited Control Reply if ((fchs->d_id & 0xff000000L) == 0x23000000L) // (31:23 R_CTL) { ProcessELS_Reply(dev, fchs); } // end of "R_CTL Solicited Control Reply" // **************Extended Link REQUEST ********************** // (unsolicited commands from another port or task...) // R_CTL Ext Link REQUEST else if ((fchs->d_id & 0xff000000L) == 0x22000000L && (fchs->ox_rx_id != 0xFFFFFFFFL)) // (ignore LIP frame) { ProcessELS_Request(dev, fchs); } // ************** LILP ********************** else if ((fchs->d_id & 0xff000000L) == 0x22000000L && (fchs->ox_rx_id == 0xFFFFFFFFL)) // (e.g., LIP frames) { // SANMark specifies that when available, we must use // the LILP frame to determine which ALPAs to send Port Discovery // to... if (fchs->pl[0] == 0x0711L) // ELS_PLOGI? { // u8 *ptr = (u8*)&fchs->pl[1]; // printk(" %d ALPAs found\n", *ptr); memcpy(fcChip->LILPmap, &fchs->pl[1], 32 * 4); // 32 DWORDs fcChip->Options.LILPin = 1; // our LILPmap is valid! // now post to make Port Discovery happen... cpqfcTSPutLinkQue(dev, LINKACTIVE, fchs); } } } // ***************** BASIC LINK SERVICE ***************** else if (fchs->d_id & 0x80000000 // R_CTL: && // Basic Link Service Request !(fchs->f_ctl & 0xFF000000)) // type=0 for BLS { // Check for ABTS (Abort Sequence) if ((fchs->d_id & 0x8F000000) == 0x81000000) { // look for OX_ID, S_ID pair that matches in our // fcExchanges table; if found, reply with ACCept and complete // the exchange // Per PLDA, an ABTS is sent by an initiator; therefore // assume that if we have an exhange open to the port who // sent ABTS, it will be the d_id of what we sent. for (ExchangeID = 0, AbortAccept = 0; ExchangeID < TACH_SEST_LEN; ExchangeID++) { // Valid "target" exchange 24-bit port_id matches? // NOTE: For the case of handling Intiator AND Target // functions on the same chip, we can have TWO Exchanges // with the same OX_ID -- OX_ID/FFFF for the CMND, and // OX_ID/RX_ID for the XRDY or DATA frame(s). Ideally, // we would like to support ABTS from Initiators or Targets, // but it's not clear that can be supported on Tachyon for // all cases (requires more investigation). if ((Exchanges->fcExchange[ExchangeID].type == SCSI_TWE || Exchanges->fcExchange[ExchangeID].type == SCSI_TRE) && ((Exchanges->fcExchange[ExchangeID].fchs.d_id & 0xFFFFFF) == (fchs->s_id & 0xFFFFFF))) { // target xchnge port_id matches -- how about OX_ID? if ((Exchanges->fcExchange[ExchangeID].fchs.ox_rx_id & 0xFFFF0000) == (fchs->ox_rx_id & 0xFFFF0000)) // yes! post ACCept response; will be completed by fcStart { Exchanges->fcExchange[ExchangeID].status = TARGET_ABORT; // copy (add) rx_id field for simplified ACCept reply fchs->ox_rx_id = Exchanges->fcExchange[ExchangeID].fchs.ox_rx_id; cpqfcTSPutLinkQue(dev, BLS_ABTS_ACC, // Q Type fchs); // void QueContent AbortAccept = 1; printk("ACCepting ABTS for x_ID %8.8Xh, SEST pair %8.8Xh\n", fchs->ox_rx_id, Exchanges->fcExchange[ExchangeID].fchs.ox_rx_id); break; // ABTS can affect only ONE exchange -exit loop } } } // end of FOR loop if (!AbortAccept) // can't ACCept ABTS - send Reject { printk("ReJecTing: can't find ExchangeID %8.8Xh for ABTS command\n", fchs->ox_rx_id); if (Exchanges->fcExchange[ExchangeID].type && !(fcChip->SEST->u[ExchangeID].IWE.Hdr_Len & 0x80000000)) { cpqfcTSCompleteExchange(dev->PciDev, fcChip, ExchangeID); } else { printk("Unexpected ABTS ReJecT! SEST[%X] Dword 0: %Xh\n", ExchangeID, fcChip->SEST->u[ExchangeID].IWE.Hdr_Len); } } } // Check for BLS {ABTS? (Abort Sequence)} ACCept else if ((fchs->d_id & 0x8F000000) == 0x84000000) { // target has responded with ACC for our ABTS; // complete the indicated exchange with ABORTED status // Make no checks for correct RX_ID, since // all we need to conform ABTS ACC is the OX_ID. // Verify that the d_id matches! ExchangeID = (fchs->ox_rx_id >> 16) & 0x7FFF; // x_id from ACC // printk("ABTS ACC x_ID 0x%04X 0x%04X, status %Xh\n", // fchs->ox_rx_id >> 16, fchs->ox_rx_id & 0xffff, // Exchanges->fcExchange[ExchangeID].status); if (ExchangeID < TACH_SEST_LEN) // x_ID makes sense { // Does "target" exchange 24-bit port_id match? // (See "NOTE" above for handling Intiator AND Target in // the same device driver) // First, if this is a target response, then we originated // (initiated) it with BLS_ABTS: if ((Exchanges->fcExchange[ExchangeID].type == BLS_ABTS) && // Second, does the source of this ACC match the destination // of who we originally sent it to? ((Exchanges->fcExchange[ExchangeID].fchs.d_id & 0xFFFFFF) == (fchs->s_id & 0xFFFFFF))) { cpqfcTSCompleteExchange(dev->PciDev, fcChip, ExchangeID); } } } // Check for BLS {ABTS? (Abort Sequence)} ReJecT else if ((fchs->d_id & 0x8F000000) == 0x85000000) { // target has responded with RJT for our ABTS; // complete the indicated exchange with ABORTED status // Make no checks for correct RX_ID, since // all we need to conform ABTS ACC is the OX_ID. // Verify that the d_id matches! ExchangeID = (fchs->ox_rx_id >> 16) & 0x7FFF; // x_id from ACC // printk("BLS_ABTS RJT on Exchange 0x%04X 0x%04X\n", // fchs->ox_rx_id >> 16, fchs->ox_rx_id & 0xffff); if (ExchangeID < TACH_SEST_LEN) // x_ID makes sense { // Does "target" exchange 24-bit port_id match? // (See "NOTE" above for handling Intiator AND Target in // the same device driver) // First, if this is a target response, then we originated // (initiated) it with BLS_ABTS: if ((Exchanges->fcExchange[ExchangeID].type == BLS_ABTS) && // Second, does the source of this ACC match the destination // of who we originally sent it to? ((Exchanges->fcExchange[ExchangeID].fchs.d_id & 0xFFFFFF) == (fchs->s_id & 0xFFFFFF))) { // YES! NOTE: There is a bug in CPQ's RA-4000 box // where the "reason code" isn't returned in the payload // For now, simply presume the reject is because the target // already completed the exchange... // printk("complete x_ID %Xh on ABTS RJT\n", ExchangeID); cpqfcTSCompleteExchange(dev->PciDev, fcChip, ExchangeID); } } } // end of ABTS check } // end of Basic Link Service Request break; default: printk("AnalyzeIncomingFrame: unknown type: %Xh(%d)\n", fcLQ->Qitem[QNdx].Type, fcLQ->Qitem[QNdx].Type); break; } } // Function for Port Discovery necessary after every FC // initialization (e.g. LIP). // Also may be called if from Fabric Name Service logic. static void SendLogins(CPQFCHBA * dev, __u32 * FabricPortIds) { PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; u32 ulStatus = 0; TachFCHDR_GCMND fchs; // copy fields for transmission int i; u32 loginType; s32 ExchangeID; PFC_LOGGEDIN_PORT pLoggedInPort; __u32 PortIds[number_of_al_pa]; int NumberOfPorts = 0; // We're going to presume (for now) that our limit of Fabric devices // is the same as the number of alpa on a private loop (126 devices). // (Of course this could be changed to support however many we have // memory for). memset(&PortIds[0], 0, sizeof(PortIds)); // First, check if this login is for our own Link Initialization // (e.g. LIP on FC-AL), or if we have knowledge of Fabric devices // from a switch. If we are logging into Fabric devices, we'll // have a non-NULL FabricPortId pointer if (FabricPortIds != NULL) // may need logins { int LastPort = 0; i = 0; while (!LastPort) { // port IDs From NSR payload; byte swap needed? BigEndianSwap((u8 *) FabricPortIds, (u8 *) & PortIds[i], 4); // printk("FPortId[%d] %Xh ", i, PortIds[i]); if (PortIds[i] & 0x80000000) LastPort = 1; PortIds[i] &= 0xFFFFFF; // get 24-bit port_id // some non-Fabric devices (like the Crossroads Fibre/Scsi bridge) // erroneously use ALPA 0. if (PortIds[i]) // need non-zero port_id... i++; if (i >= number_of_al_pa) // (in)sanity check break; FabricPortIds++; // next... } NumberOfPorts = i; // printk("NumberOf Fabric ports %d", NumberOfPorts); } else // need to send logins on our "local" link { // are we a loop port? If so, check for reception of LILP frame, // and if received use it (SANMark requirement) if (fcChip->Options.LILPin) { int j = 0; // sanity check on number of ALPAs from LILP frame... // For format of LILP frame, see FC-AL specs or // "Fibre Channel Bench Reference", J. Stai, 1995 (ISBN 1-879936-17-8) // First byte is number of ALPAs i = fcChip->LILPmap[0] >= (32 * 4) ? 32 * 4 : fcChip->LILPmap[0]; NumberOfPorts = i; // printk(" LILP alpa count %d ", i); while (i > 0) { PortIds[j] = fcChip->LILPmap[1 + j]; j++; i--; } } else // have to send login to everybody { int j = 0; i = number_of_al_pa; NumberOfPorts = i; while (i > 0) { PortIds[j] = valid_al_pa[j]; // all legal ALPAs j++; i--; } } } // Now we have a copy of the port_ids (and how many)... for (i = 0; i < NumberOfPorts; i++) { // 24-bit FC Port ID fchs.s_id = PortIds[i]; // note: only 8-bits used for ALPA // don't log into ourselves (Linux Scsi disk scan will stop on // no TARGET support error on us, and quit trying for rest of devices) if ((fchs.s_id & 0xFF) == (fcChip->Registers.my_al_pa & 0xFF)) continue; // fabric login needed? if ((fchs.s_id == 0) || (fcChip->Options.fabric == 1)) { fcChip->Options.flogi = 1; // fabric needs longer for login // Do we need FLOGI or FDISC? pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // don't search SCSI Nexus 0xFFFFFC, // search linked list for Fabric port_id NULL, // don't search WWN NULL); // (don't care about end of list) if (pLoggedInPort) // If found, we have prior experience with // this port -- check whether PDISC is needed { if (pLoggedInPort->flogi) { // does the switch support FDISC?? (FLOGI for now...) loginType = ELS_FLOGI; // prior FLOGI still valid } else loginType = ELS_FLOGI; // expired FLOGI } else // first FLOGI? loginType = ELS_FLOGI; fchs.s_id = 0xFFFFFE; // well known F_Port address // Fabrics are not required to support FDISC, and // it's not clear if that helps us anyway, since // we'll want a Name Service Request to re-verify // visible devices... // Consequently, we always want our upper 16 bit // port_id to be zero (we'll be rejected if we // use our prior port_id if we've been plugged into // a different switch port). // Trick Tachyon to send to ALPA 0 (see TL/TS UG, pg 87) // If our ALPA is 55h for instance, we want the FC frame // s_id to be 0x000055, while Tach's my_al_pa register // must be 0x000155, to force an OPN at ALPA 0 // (the Fabric port) fcChip->Registers.my_al_pa &= 0xFF; // only use ALPA for FLOGI writel(fcChip->Registers.my_al_pa | 0x0100, fcChip->Registers.ReMapMemBase + TL_MEM_TACH_My_ID); } else // not FLOGI... { // should we send PLOGI or PDISC? Check if any prior port_id // (e.g. alpa) completed a PLOGI/PRLI exchange by checking // the pdisc flag. pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // don't search SCSI Nexus fchs.s_id, // search linked list for al_pa NULL, // don't search WWN NULL); // (don't care about end of list) if (pLoggedInPort) // If found, we have prior experience with // this port -- check whether PDISC is needed { if (pLoggedInPort->pdisc) { loginType = ELS_PDISC; // prior PLOGI and PRLI maybe still valid } else loginType = ELS_PLOGI; // prior knowledge, but can't use PDISC } else // never talked to this port_id before loginType = ELS_PLOGI; // prior knowledge, but can't use PDISC } ulStatus = cpqfcTSBuildExchange(dev, loginType, // e.g. PLOGI &fchs, // no incoming frame (we are originator) NULL, // no data (no scatter/gather list) &ExchangeID); // fcController->fcExchanges index, -1 if failed if (!ulStatus) // Exchange setup OK? { ulStatus = cpqfcTSStartExchange(dev, ExchangeID); if (!ulStatus) { // submitted to Tach's Outbound Que (ERQ PI incremented) // waited for completion for ELS type (Login frames issued // synchronously) if (loginType == ELS_PDISC) { // now, we really shouldn't Revalidate SEST exchanges until // we get an ACC reply from our target and verify that // the target address/WWN is unchanged. However, when a fast // target gets the PDISC, they can send SEST Exchange data // before we even get around to processing the PDISC ACC. // Consequently, we lose the I/O. // To avoid this, go ahead and Revalidate when the PDISC goes // out, anticipating that the ACC will be truly acceptable // (this happens 99.9999....% of the time). // If we revalidate a SEST write, and write data goes to a // target that is NOT the one we originated the WRITE to, // that target is required (FCP-SCSI specs, etc) to discard // our WRITE data. // Re-validate SEST entries (Tachyon hardware assists) RevalidateSEST(dev->HostAdapter, pLoggedInPort); //TriggerHBA( fcChip->Registers.ReMapMemBase, 1); } } else // give up immediately on error { #ifdef LOGIN_DBG printk("SendLogins: fcStartExchange failed: %Xh\n", ulStatus); #endif break; } if (fcChip->Registers.FMstatus.value & 0x080) // LDn during Port Disc. { ulStatus = LNKDWN_OSLS; #ifdef LOGIN_DBG printk("SendLogins: PortDisc aborted (LDn) @alpa %Xh\n", fchs.s_id); #endif break; } // Check the exchange for bad status (i.e. FrameTimeOut), // and complete on bad status (most likely due to BAD_ALPA) // on LDn, DPC function may already complete (ABORT) a started // exchange, so check type first (type = 0 on complete). if (Exchanges->fcExchange[ExchangeID].status) { #ifdef LOGIN_DBG printk("completing x_ID %X on status %Xh\n", ExchangeID, Exchanges->fcExchange[ExchangeID].status); #endif cpqfcTSCompleteExchange(dev->PciDev, fcChip, ExchangeID); } } else // Xchange setup failed... { #ifdef LOGIN_DBG printk("FC: cpqfcTSBuildExchange failed: %Xh\n", ulStatus); #endif break; } } if (!ulStatus) { // set the event signifying that all ALPAs were sent out. #ifdef LOGIN_DBG printk("SendLogins: PortDiscDone\n"); #endif dev->PortDiscDone = 1; // TL/TS UG, pg. 184 // 0x0065 = 100ms for RT_TOV // 0x01f5 = 500ms for ED_TOV fcChip->Registers.ed_tov.value = 0x006501f5L; writel(fcChip->Registers.ed_tov.value, (fcChip->Registers.ed_tov.address)); // set the LP_TOV back to ED_TOV (i.e. 500 ms) writel(0x00000010, fcChip->Registers.ReMapMemBase + TL_MEM_FM_TIMEOUT2); } else { printk("SendLogins: failed at xchng %Xh, alpa %Xh, status %Xh\n", ExchangeID, fchs.s_id, ulStatus); } LEAVE("SendLogins"); } // for REPORT_LUNS documentation, see "In-Depth Exploration of Scsi", // D. Deming, 1994, pg 7-19 (ISBN 1-879936-08-9) static void ScsiReportLunsDone(Scsi_Cmnd * Cmnd) { struct Scsi_Host *shpnt = Cmnd->host; CPQFCHBA *dev = (CPQFCHBA *) shpnt->hostdata; PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; PFC_LOGGEDIN_PORT pLoggedInPort; int LunListLen = 0; int i; u32 x_ID = 0xFFFFFFFF; u8 *ucBuff = Cmnd->request_buffer; // printk("cpqfcTS: ReportLunsDone \n"); // first, we need to find the Exchange for this command, // so we can find the fcPort struct to make the indicated // changes. for (i = 0; i < TACH_SEST_LEN; i++) { if (Exchanges->fcExchange[i].type // exchange defined? && (Exchanges->fcExchange[i].Cmnd == Cmnd)) // matches? { x_ID = i; // found exchange! break; } } if (x_ID == 0xFFFFFFFF) { // printk("cpqfcTS: ReportLuns failed - no FC Exchange\n"); goto Done; // Report Luns FC Exchange gone; // exchange probably Terminated by Implicit logout } // search linked list for the port_id we sent INQUIRY to pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // DON'T search Scsi Nexus (we will set it) Exchanges->fcExchange[x_ID].fchs.d_id & 0xFFFFFF, NULL, // DON'T search linked list for FC WWN NULL); // DON'T care about end of list if (!pLoggedInPort) { // printk("cpqfcTS: ReportLuns failed - device gone\n"); goto Done; // error! can't find logged in Port } LunListLen = ucBuff[3]; LunListLen += ucBuff[2] >> 8; if (!LunListLen) // failed { // generically speaking, a soft error means we should retry... if ((Cmnd->result >> 16) == DID_SOFT_ERROR) { if (((Cmnd->sense_buffer[2] & 0xF) == 0x6) && (Cmnd->sense_buffer[12] == 0x29)) // Sense Code "reset" { TachFCHDR_GCMND *fchs = &Exchanges->fcExchange[x_ID].fchs; // did we fail because of "check condition, device reset?" // e.g. the device was reset (i.e., at every power up) // retry the Report Luns // who are we sending it to? // we know this because we have a copy of the command // frame from the original Report Lun command - // switch the d_id/s_id fields, because the Exchange Build // context is "reply to source". fchs->s_id = fchs->d_id; // (temporarily re-use the struct) cpqfcTSPutLinkQue(dev, SCSI_REPORT_LUNS, fchs); } } else // probably, the device doesn't support Report Luns pLoggedInPort->ScsiNexus.VolumeSetAddressing = 0; } else // we have LUN info - check VSA mode { // for now, assume all LUNs will have same addr mode // for VSA, payload byte 8 will be 0x40; otherwise, 0 pLoggedInPort->ScsiNexus.VolumeSetAddressing = ucBuff[8]; // Since we got a Report Luns answer, set lun masking flag pLoggedInPort->ScsiNexus.LunMasking = 1; if (LunListLen > 8 * CPQFCTS_MAX_LUN) // We expect CPQFCTS_MAX_LUN max LunListLen = 8 * CPQFCTS_MAX_LUN; /* printk("Device WWN %08X%08X Reports Luns @: ", (u32)(pLoggedInPort->u.liWWN &0xFFFFFFFF), (u32)(pLoggedInPort->u.liWWN>>32)); for( i=8; i<LunListLen+8; i+=8) { printk("%02X%02X ", ucBuff[i], ucBuff[i+1] ); } printk("\n"); */ // Since the device was kind enough to tell us where the // LUNs are, lets ensure they are contiguous for Linux's // SCSI driver scan, which expects them to start at 0. // Since Linux only supports 8 LUNs, only copy the first // eight from the report luns command // e.g., the Compaq RA4x00 f/w Rev 2.54 and above may report // LUNs 4001, 4004, etc., because other LUNs are masked from // this HBA (owned by someone else). We'll make those appear as // LUN 0, 1... to Linux { int j; int AppendLunList = 0; // Walk through the LUN list. The 'j' array number is // Linux's lun #, while the value of .lun[j] is the target's // lun #. // Once we build a LUN list, it's possible for a known device // to go offline while volumes (LUNs) are added. Later, // the device will do another PLOGI ... Report Luns command, // and we must not alter the existing Linux Lun map. // (This will be very rare). for (j = 0; j < CPQFCTS_MAX_LUN; j++) { if (pLoggedInPort->ScsiNexus.lun[j] != 0xFF) { AppendLunList = 1; break; } } if (AppendLunList) { int k; int FreeLunIndex; // printk("cpqfcTS: AppendLunList\n"); // If we get a new Report Luns, we cannot change // any existing LUN mapping! (Only additive entry) // For all LUNs in ReportLun list // if RL lun != ScsiNexus lun // if RL lun present in ScsiNexus lun[], continue // else find ScsiNexus lun[]==FF and add, continue for (i = 8, j = 0; i < LunListLen + 8 && j < CPQFCTS_MAX_LUN; i += 8, j++) { if (pLoggedInPort->ScsiNexus.lun[j] != ucBuff[i + 1]) { // something changed from the last Report Luns printk(" cpqfcTS: Report Lun change!\n"); for (k = 0, FreeLunIndex = CPQFCTS_MAX_LUN; k < CPQFCTS_MAX_LUN; k++) { if (pLoggedInPort->ScsiNexus.lun[k] == 0xFF) { FreeLunIndex = k; break; } if (pLoggedInPort->ScsiNexus.lun[k] == ucBuff[i + 1]) break; // we already masked this lun } if (k >= CPQFCTS_MAX_LUN) { printk(" no room for new LUN %d\n", ucBuff[i + 1]); } else if (k == FreeLunIndex) // need to add LUN { pLoggedInPort->ScsiNexus.lun[k] = ucBuff[i + 1]; // printk("add [%d]->%02d\n", k, pLoggedInPort->ScsiNexus.lun[k]); } else { // lun already known } break; } } // print out the new list... for (j = 0; j < CPQFCTS_MAX_LUN; j++) { if (pLoggedInPort->ScsiNexus.lun[j] == 0xFF) break; // done // printk("[%d]->%02d ", j, pLoggedInPort->ScsiNexus.lun[j]); } } else { // printk("Linux SCSI LUNs[] -> Device LUNs: "); // first time - this is easy for (i = 8, j = 0; i < LunListLen + 8 && j < CPQFCTS_MAX_LUN; i += 8, j++) { pLoggedInPort->ScsiNexus.lun[j] = ucBuff[i + 1]; // printk("[%d]->%02d ", j, pLoggedInPort->ScsiNexus.lun[j]); } // printk("\n"); } } } Done:; } static void call_scsi_done(Scsi_Cmnd * Cmnd) { // We have to reinitialize sent_command here, so the scsi-mid // layer won't re-use the scsi command leaving it set incorrectly. // (incorrectly for our purposes...it's normally unused.) if (Cmnd->SCp.sent_command != 0) { // was it a passthru? Cmnd->SCp.sent_command = 0; Cmnd->result &= 0xff00ffff; Cmnd->result |= (DID_PASSTHROUGH << 16); // prevents retry } if (Cmnd->scsi_done != NULL) (*Cmnd->scsi_done) (Cmnd); } // After successfully getting a "Process Login" (PRLI) from an // FC port, we want to Discover the LUNs so that we know the // addressing type (e.g., FCP-SCSI Volume Set Address, Peripheral // Unit Device), and whether SSP (Selective Storage Presentation or // Lun Masking) has made the LUN numbers non-zero based or // non-contiguous. To remain backward compatible with the SCSI-2 // driver model, which expects a contiguous LUNs starting at 0, // will use the ReportLuns info to map from "device" to "Linux" // LUNs. static void IssueReportLunsCommand(CPQFCHBA * dev, TachFCHDR_GCMND * fchs) { PTACHYON fcChip = &dev->fcChip; PFC_LOGGEDIN_PORT pLoggedInPort; Scsi_Cmnd *Cmnd; s32 x_ID; u32 ulStatus; u8 *ucBuff; if (!dev->PortDiscDone) // cleared by LDn { printk("Discard Q'd ReportLun command\n"); goto Done; } // find the device (from port_id) we're talking to pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // DON'T search Scsi Nexus fchs->s_id & 0xFFFFFF, NULL, // DON'T search linked list for FC WWN NULL); // DON'T care about end of list if (pLoggedInPort) // we'd BETTER find it! { if (!(pLoggedInPort->fcp_info & TARGET_FUNCTION)) goto Done; // forget it - FC device not a "target" // now use the port's Scsi Command buffer for the // Report Luns Command Cmnd = &pLoggedInPort->ScsiCmnd; ucBuff = pLoggedInPort->ReportLunsPayload; memset(Cmnd, 0, sizeof(Scsi_Cmnd)); memset(ucBuff, 0, REPORT_LUNS_PL); Cmnd->scsi_done = ScsiReportLunsDone; Cmnd->host = dev->HostAdapter; Cmnd->request_buffer = pLoggedInPort->ReportLunsPayload; Cmnd->request_bufflen = REPORT_LUNS_PL; Cmnd->cmnd[0] = 0xA0; Cmnd->cmnd[8] = REPORT_LUNS_PL >> 8; Cmnd->cmnd[9] = (u8) REPORT_LUNS_PL; Cmnd->cmd_len = 12; Cmnd->channel = pLoggedInPort->ScsiNexus.channel; Cmnd->target = pLoggedInPort->ScsiNexus.target; ulStatus = cpqfcTSBuildExchange(dev, SCSI_IRE, fchs, Cmnd, // buffer for Report Lun data &x_ID); // fcController->fcExchanges index, -1 if failed if (!ulStatus) // Exchange setup? { ulStatus = cpqfcTSStartExchange(dev, x_ID); if (!ulStatus) { // submitted to Tach's Outbound Que (ERQ PI incremented) // waited for completion for ELS type (Login frames issued // synchronously) } else // check reason for Exchange not being started - we might // want to Queue and start later, or fail with error { } } else // Xchange setup failed... printk(" cpqfcTSBuildExchange failed: %Xh\n", ulStatus); } else // like, we just got a PRLI ACC, and now the port is gone? { printk(" can't send ReportLuns - no login for port_id %Xh\n", fchs->s_id & 0xFFFFFF); } Done:; } static void CompleteBoardLockCmnd(CPQFCHBA * dev) { int i; for (i = CPQFCTS_REQ_QUEUE_LEN - 1; i >= 0; i--) { if (dev->BoardLockCmnd[i] != NULL) { Scsi_Cmnd *Cmnd = dev->BoardLockCmnd[i]; dev->BoardLockCmnd[i] = NULL; Cmnd->result = (DID_SOFT_ERROR << 16); // ask for retry // printk(" BoardLockCmnd[%d] %p Complete, chnl/target/lun %d/%d/%d\n", // i,Cmnd, Cmnd->channel, Cmnd->target, Cmnd->lun); call_scsi_done(Cmnd); } } } // runs every 1 second for FC exchange timeouts and implicit FC device logouts void cpqfcTSheartbeat(unsigned long ptr) { CPQFCHBA *dev = (CPQFCHBA *) ptr; PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; PFC_LOGGEDIN_PORT pLoggedInPort = &fcChip->fcPorts; u32 i; unsigned long flags; DECLARE_MUTEX_LOCKED(BoardLock); PCI_TRACE(0xA8) if (dev->BoardLock) // Worker Task Running? goto Skip; spin_lock_irqsave(&io_request_lock, flags); // STOP _que function PCI_TRACE(0xA8) dev->BoardLock = &BoardLock; // stop Linux SCSI command queuing // release the IO lock (and re-enable interrupts) spin_unlock_irqrestore(&io_request_lock, flags); // Ensure no contention from _quecommand or Worker process CPQ_SPINLOCK_HBA(dev) PCI_TRACE(0xA8) disable_irq(dev->HostAdapter->irq); // our IRQ // Complete the "bad target" commands (normally only used during // initialization, since we aren't supposed to call "scsi_done" // inside the queuecommand() function). (this is overly contorted, // scsi_done can be safely called from queuecommand for // this bad target case. May want to simplify this later) for (i = 0; i < CPQFCTS_MAX_TARGET_ID; i++) { if (dev->BadTargetCmnd[i]) { Scsi_Cmnd *Cmnd = dev->BadTargetCmnd[i]; dev->BadTargetCmnd[i] = NULL; Cmnd->result = (DID_BAD_TARGET << 16); call_scsi_done(Cmnd); } else break; } // logged in ports -- re-login check (ports required to verify login with // PDISC after LIP within 2 secs) // prevent contention while (pLoggedInPort) // for all ports which are expecting // PDISC after the next LIP, check to see if // time is up! { // Important: we only detect "timeout" condition on TRANSITION // from non-zero to zero if (pLoggedInPort->LOGO_timer) // time-out "armed"? { if (!(--pLoggedInPort->LOGO_timer)) // DEC from 1 to 0? { // LOGOUT time! Per PLDA, PDISC hasn't complete in 2 secs, so // issue LOGO request and destroy all I/O with other FC port(s). /* printk(" ~cpqfcTS heartbeat: LOGOut!~ "); printk("Linux SCSI Chanl/Target %d/%d (port_id %06Xh) WWN %08X%08X\n", pLoggedInPort->ScsiNexus.channel, pLoggedInPort->ScsiNexus.target, pLoggedInPort->port_id, (u32)(pLoggedInPort->u.liWWN &0xFFFFFFFF), (u32)(pLoggedInPort->u.liWWN>>32)); */ cpqfcTSImplicitLogout(dev, pLoggedInPort); } // else simply decremented - maybe next time... } pLoggedInPort = pLoggedInPort->pNextPort; } // ************ FC EXCHANGE TIMEOUT CHECK ************** for (i = 0; i < TACH_MAX_XID; i++) { if (Exchanges->fcExchange[i].type) // exchange defined? { if (!Exchanges->fcExchange[i].timeOut) // time expired { // Set Exchange timeout status Exchanges->fcExchange[i].status |= FC2_TIMEOUT; if (i >= TACH_SEST_LEN) // Link Service Exchange { cpqfcTSCompleteExchange(dev->PciDev, fcChip, i); // Don't "abort" LinkService } else // SEST Exchange TO -- may post ABTS to Worker Thread Que { // (Make sure we don't keep timing it out; let other functions // complete it or set the timeOut as needed) Exchanges->fcExchange[i].timeOut = 30000; // seconds default if (Exchanges->fcExchange[i].type & (BLS_ABTS | BLS_ABTS_ACC)) { // For BLS_ABTS*, an upper level might still have // an outstanding command waiting for low-level completion. // Also, in the case of a WRITE, we MUST get confirmation // of either ABTS ACC or RJT before re-using the Exchange. // It's possible that the RAID cache algorithm can hang // if we fail to complete a WRITE to a LBA, when a READ // comes later to that same LBA. Therefore, we must // ensure that the target verifies receipt of ABTS for // the exchange printk("~TO Q'd ABTS (x_ID %Xh)~ ", i); // TriggerHBA( fcChip->Registers.ReMapMemBase); // On timeout of a ABTS exchange, check to // see if the FC device has a current valid login. // If so, restart it. pLoggedInPort = fcFindLoggedInPort(fcChip, Exchanges->fcExchange[i].Cmnd, // find Scsi Nexus 0, // DON'T search linked list for FC port id NULL, // DON'T search linked list for FC WWN NULL); // DON'T care about end of list // device exists? if (pLoggedInPort) // device exists? { if (pLoggedInPort->prli) // logged in for FCP-SCSI? { // attempt to restart the ABTS printk(" ~restarting ABTS~ "); cpqfcTSStartExchange(dev, i); } } } else // not an ABTS { // We expect the WorkerThread to change the xchng type to // abort and set appropriate timeout. cpqfcTSPutLinkQue(dev, BLS_ABTS, &i); // timed-out } } } else // time not expired... { // decrement timeout: 1 or more seconds left --Exchanges->fcExchange[i].timeOut; } } } enable_irq(dev->HostAdapter->irq); CPQ_SPINUNLOCK_HBA(dev) dev->BoardLock = NULL; // Linux SCSI commands may be queued // Now, complete any Cmnd we Q'd up while BoardLock was held CompleteBoardLockCmnd(dev); // restart the timer to run again (1 sec later) Skip: mod_timer(&dev->cpqfcTStimer, jiffies + HZ); PCI_TRACEO(i, 0xA8) return; } // put valid FC-AL physical address in spec order static const u8 valid_al_pa[] = { 0xef, 0xe8, 0xe4, 0xe2, 0xe1, 0xE0, 0xDC, 0xDA, 0xD9, 0xD6, 0xD5, 0xD4, 0xD3, 0xD2, 0xD1, 0xCe, 0xCd, 0xCc, 0xCb, 0xCa, 0xC9, 0xC7, 0xC6, 0xC5, 0xC3, 0xBc, 0xBa, 0xB9, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xae, 0xad, 0xAc, 0xAb, 0xAa, 0xA9, 0xA7, 0xA6, 0xA5, 0xA3, 0x9f, 0x9e, 0x9d, 0x9b, 0x98, 0x97, 0x90, 0x8f, 0x88, 0x84, 0x82, 0x81, 0x80, 0x7c, 0x7a, 0x79, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x67, 0x66, 0x65, 0x63, 0x5c, 0x5a, 0x59, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a, 0x49, 0x47, 0x46, 0x45, 0x43, 0x3c, 0x3a, 0x39, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x27, 0x26, 0x25, 0x23, 0x1f, 0x1E, 0x1d, 0x1b, 0x18, 0x17, 0x10, 0x0f, 8, 4, 2, 1 }; // ALPA 0 (Fabric) is special case const int number_of_al_pa = (sizeof(valid_al_pa)); // this function looks up an al_pa from the table of valid al_pa's // we decrement from the last decimal loop ID, because soft al_pa // (our typical case) are assigned with highest priority (and high al_pa) // first. See "In-Depth FC-AL", R. Kembel pg. 38 // INPUTS: // al_pa - 24 bit port identifier (8 bit al_pa on private loop) // RETURN: // Loop ID - serves are index to array of logged in ports // -1 - invalid al_pa (not all 8 bit values are legal) #if (0) static int GetLoopID(u32 al_pa) { int i; for (i = number_of_al_pa - 1; i >= 0; i--) // dec. { if (valid_al_pa[i] == (u8) al_pa) // take lowest 8 bits return i; // success - found valid al_pa; return decimal LoopID } return -1; // failed - not found } #endif // Search the singly (forward) linked list "fcPorts" looking for // either the SCSI target (if != -1), port_id (if not NULL), // or WWN (if not null), in that specific order. // If we find a SCSI nexus (from Cmnd arg), set the SCp.phase // field according to VSA or PDU // RETURNS: // Ptr to logged in port struct if found // (NULL if not found) // pLastLoggedInPort - ptr to last struct (for adding new ones) // PFC_LOGGEDIN_PORT fcFindLoggedInPort(PTACHYON fcChip, Scsi_Cmnd * Cmnd, // search linked list for Scsi Nexus (channel/target/lun) u32 port_id, // search linked list for al_pa, or u8 wwn[8], // search linked list for WWN, or... PFC_LOGGEDIN_PORT * pLastLoggedInPort) { PFC_LOGGEDIN_PORT pLoggedInPort = &fcChip->fcPorts; u8 target_id_valid = 0; u8 port_id_valid = 0; u8 wwn_valid = 0; int i; if (Cmnd != NULL) target_id_valid = 1; else if (port_id) // note! 24-bit NULL address is illegal port_id_valid = 1; else { if (wwn) // non-null arg? (OK to pass NULL when not searching WWN) { for (i = 0; i < 8; i++) // valid WWN passed? NULL WWN invalid { if (wwn[i] != 0) wwn_valid = 1; // any non-zero byte makes (presumably) valid } } } // check other options ... // In case multiple search options are given, we use a priority // scheme: // While valid pLoggedIn Ptr // If port_id is valid // if port_id matches, return Ptr // If wwn is valid // if wwn matches, return Ptr // Next Ptr in list // // Return NULL (not found) while (pLoggedInPort) // NULL marks end of list (1st ptr always valid) { if (pLastLoggedInPort) // caller's pointer valid? *pLastLoggedInPort = pLoggedInPort; // end of linked list if (target_id_valid) { // check Linux Scsi Cmnd for channel/target Nexus match // (all luns are accessed through matching "pLoggedInPort") if ((pLoggedInPort->ScsiNexus.target == Cmnd->target) && (pLoggedInPort->ScsiNexus.channel == Cmnd->channel)) { // For "passthru" modes, the IOCTL caller is responsible // for setting the FCP-LUN addressing if (!Cmnd->SCp.sent_command) // NOT passthru? { // set the FCP-LUN addressing type Cmnd->SCp.phase = pLoggedInPort->ScsiNexus.VolumeSetAddressing; // set the Device Type we got from the snooped INQUIRY string Cmnd->SCp.Message = pLoggedInPort->ScsiNexus.InqDeviceType; // handle LUN masking; if not "default" (illegal) lun value, // the use it. These lun values are set by a successful // Report Luns command if (pLoggedInPort->ScsiNexus.LunMasking == 1) { // we KNOW all the valid LUNs... 0xFF is invalid! if (Cmnd->lun > sizeof(pLoggedInPort->ScsiNexus.lun)){ // printk("cpqfcTS FATAL: Invalid LUN index !!!!\n "); return NULL; } Cmnd->SCp.have_data_in = pLoggedInPort->ScsiNexus.lun[Cmnd->lun]; if (pLoggedInPort->ScsiNexus.lun[Cmnd->lun] == 0xFF) return NULL; // printk("xlating lun %d to 0x%02x\n", Cmnd->lun, // pLoggedInPort->ScsiNexus.lun[Cmnd->lun]); } else Cmnd->SCp.have_data_in = Cmnd->lun; // Linux & target luns match } break; // found it! } } if (port_id_valid) // look for alpa first { if (pLoggedInPort->port_id == port_id) break; // found it! } if (wwn_valid) // look for wwn second { if (!memcmp(&pLoggedInPort->u.ucWWN[0], &wwn[0], 8)) { // all 8 bytes of WWN match break; // found it! } } pLoggedInPort = pLoggedInPort->pNextPort; // try next port } return pLoggedInPort; } // // We need to examine the SEST table and re-validate // any open Exchanges for this LoggedInPort // To make Tachyon pay attention, Freeze FCP assists, // set VAL bits, Unfreeze FCP assists static void RevalidateSEST(struct Scsi_Host *shpnt, PFC_LOGGEDIN_PORT pLoggedInPort) { CPQFCHBA *dev = (CPQFCHBA *) shpnt->hostdata; PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; u32 x_ID; u8 TachFroze = 0; // re-validate any SEST exchanges that are permitted // to survive the link down (e.g., good PDISC performed) for (x_ID = 0; x_ID < TACH_SEST_LEN; x_ID++) { // If the SEST entry port_id matches the pLoggedInPort, // we need to re-validate if ((Exchanges->fcExchange[x_ID].type == SCSI_IRE) || (Exchanges->fcExchange[x_ID].type == SCSI_IWE)) { if ((Exchanges->fcExchange[x_ID].fchs.d_id & 0xFFFFFF) == pLoggedInPort->port_id) // (24-bit port ID) { // printk(" re-val xID %Xh ", x_ID); if (!TachFroze) // freeze if not already frozen TachFroze |= FreezeTach(dev); fcChip->SEST->u[x_ID].IWE.Hdr_Len |= 0x80000000; // set VAL bit } } } if (TachFroze) { fcChip->UnFreezeTachyon(fcChip, 2); // both ERQ and FCP assists } } // Complete an Linux Cmnds that we Queued because // our FC link was down (cause immediate retry) static void UnblockScsiDevice(struct Scsi_Host *shpnt, PFC_LOGGEDIN_PORT pLoggedInPort) { // Scsi_Device *sdev = shpnt->host_queue; CPQFCHBA *dev = (CPQFCHBA *) shpnt->hostdata; Scsi_Cmnd **SCptr = &dev->LinkDnCmnd[0]; Scsi_Cmnd *Cmnd; int indx; // if the device was previously "blocked", make sure // we unblock it so Linux SCSI will resume pLoggedInPort->device_blocked = 0; // clear our flag // check the Link Down command ptr buffer; // we can complete now causing immediate retry for (indx = 0; indx < CPQFCTS_REQ_QUEUE_LEN; indx++, SCptr++) { if (*SCptr != NULL) // scsi command to complete? { #ifdef DUMMYCMND_DBG printk("complete Cmnd %p in LinkDnCmnd[%d]\n", *SCptr, indx); #endif Cmnd = *SCptr; // Are there any Q'd commands for this target? if ((Cmnd->target == pLoggedInPort->ScsiNexus.target) && (Cmnd->channel == pLoggedInPort->ScsiNexus.channel)) { Cmnd->result = (DID_SOFT_ERROR << 16); // force retry if (Cmnd->scsi_done == NULL) { printk("LinkDnCmnd scsi_done ptr null, port_id %Xh\n", pLoggedInPort->port_id); Cmnd->SCp.sent_command = 0; } else call_scsi_done(Cmnd); *SCptr = NULL; // free this slot for next use } } } } //#define WWN_DBG 1 static void SetLoginFields(PFC_LOGGEDIN_PORT pLoggedInPort, TachFCHDR_GCMND * fchs, u8 PDisc, u8 Originator) { LOGIN_PAYLOAD logi; // FC-PH Port Login PRLI_REQUEST prli; // copy for BIG ENDIAN switch int i; #ifdef WWN_DBG u32 ulBuff; #endif BigEndianSwap((u8 *) & fchs->pl[0], (u8 *) & logi, sizeof(logi)); pLoggedInPort->Originator = Originator; pLoggedInPort->port_id = fchs->s_id & 0xFFFFFF; switch (fchs->pl[0] & 0xffff) { case 0x00000002: // PLOGI or PDISC ACCept? if (PDisc) // PDISC accept goto PDISC_case; case 0x00000003: // ELS_PLOGI or ELS_PLOGI_ACC // Login BB_credit typically 0 for Tachyons pLoggedInPort->BB_credit = logi.cmn_services.bb_credit; // e.g. 128, 256, 1024, 2048 per FC-PH spec // We have to use this when setting up SEST Writes, // since that determines frame size we send. pLoggedInPort->rx_data_size = logi.class3.rx_data_size; pLoggedInPort->plogi = 1; pLoggedInPort->pdisc = 0; pLoggedInPort->prli = 0; // ELS_PLOGI resets pLoggedInPort->flogi = 0; // ELS_PLOGI resets pLoggedInPort->logo = 0; // ELS_PLOGI resets pLoggedInPort->LOGO_counter = 0; // ELS_PLOGI resets pLoggedInPort->LOGO_timer = 0; // ELS_PLOGI resets // was this PLOGI to a Fabric? if (pLoggedInPort->port_id == 0xFFFFFC) // well know address pLoggedInPort->flogi = 1; for (i = 0; i < 8; i++) // copy the LOGIN port's WWN pLoggedInPort->u.ucWWN[i] = logi.port_name[i]; #ifdef WWN_DBG ulBuff = (u32) pLoggedInPort->u.liWWN; if (pLoggedInPort->Originator) printk("o"); else printk("r"); printk("PLOGI port_id %Xh, WWN %08X", pLoggedInPort->port_id, ulBuff); ulBuff = (u32) (pLoggedInPort->u.liWWN >> 32); printk("%08Xh fcPort %p\n", ulBuff, pLoggedInPort); #endif break; case 0x00000005: // ELS_LOGO (logout) pLoggedInPort->plogi = 0; pLoggedInPort->pdisc = 0; pLoggedInPort->prli = 0; // ELS_PLOGI resets pLoggedInPort->flogi = 0; // ELS_PLOGI resets pLoggedInPort->logo = 1; // ELS_PLOGI resets pLoggedInPort->LOGO_counter++; // ELS_PLOGI resets pLoggedInPort->LOGO_timer = 0; #ifdef WWN_DBG ulBuff = (u32) pLoggedInPort->u.liWWN; if (pLoggedInPort->Originator) printk("o"); else printk("r"); printk("LOGO port_id %Xh, WWN %08X", pLoggedInPort->port_id, ulBuff); ulBuff = (u32) (pLoggedInPort->u.liWWN >> 32); printk("%08Xh\n", ulBuff); #endif break; PDISC_case: case 0x00000050: // ELS_PDISC or ELS_PDISC_ACC pLoggedInPort->LOGO_timer = 0; // stop the time-out pLoggedInPort->prli = 1; // ready to accept FCP-SCSI I/O #ifdef WWN_DBG ulBuff = (u32) pLoggedInPort->u.liWWN; if (pLoggedInPort->Originator) printk("o"); else printk("r"); printk("PDISC port_id %Xh, WWN %08X", pLoggedInPort->port_id, ulBuff); ulBuff = (u32) (pLoggedInPort->u.liWWN >> 32); printk("%08Xh\n", ulBuff); #endif break; case 0x1020L: // PRLI? case 0x1002L: // PRLI ACCept? BigEndianSwap((u8 *) & fchs->pl[0], (u8 *) & prli, sizeof(prli)); pLoggedInPort->fcp_info = prli.fcp_info; // target/initiator flags pLoggedInPort->prli = 1; // PLOGI resets, PDISC doesn't pLoggedInPort->pdisc = 1; // expect to send (or receive) PDISC // next time pLoggedInPort->LOGO_timer = 0; // will be set next LinkDown #ifdef WWN_DBG ulBuff = (u32) pLoggedInPort->u.liWWN; if (pLoggedInPort->Originator) printk("o"); else printk("r"); printk("PRLI port_id %Xh, WWN %08X", pLoggedInPort->port_id, ulBuff); ulBuff = (u32) (pLoggedInPort->u.liWWN >> 32); printk("%08Xh\n", ulBuff); #endif break; } return; } static void BuildLinkServicePayload(PTACHYON fcChip, u32 type, void *payload) { LOGIN_PAYLOAD *plogi; // FC-PH Port Login LOGIN_PAYLOAD PlogiPayload; // copy for BIG ENDIAN switch PRLI_REQUEST *prli; // FCP-SCSI Process Login PRLI_REQUEST PrliPayload; // copy for BIG ENDIAN switch LOGOUT_PAYLOAD *logo; LOGOUT_PAYLOAD LogoutPayload; // PRLO_REQUEST *prlo; // PRLO_REQUEST PrloPayload; REJECT_MESSAGE rjt, *prjt; memset(&PlogiPayload, 0, sizeof(PlogiPayload)); plogi = &PlogiPayload; // load into stack buffer, // then BIG-ENDIAN switch a copy to caller switch (type) // payload type can be ELS_PLOGI, ELS_PRLI, ADISC, ... { case ELS_FDISC: case ELS_FLOGI: case ELS_PLOGI_ACC: // FC-PH PORT Login Accept case ELS_PLOGI: // FC-PH PORT Login case ELS_PDISC: // FC-PH2 Port Discovery - same payload as ELS_PLOGI plogi->login_cmd = LS_PLOGI; if (type == ELS_PDISC) plogi->login_cmd = LS_PDISC; else if (type == ELS_PLOGI_ACC) plogi->login_cmd = LS_ACC; plogi->cmn_services.bb_credit = 0x00; plogi->cmn_services.lowest_ver = fcChip->lowest_FCPH_ver; plogi->cmn_services.highest_ver = fcChip->highest_FCPH_ver; plogi->cmn_services.bb_rx_size = TACHLITE_TS_RX_SIZE; plogi->cmn_services.common_features = CONTINUOSLY_INCREASING | RANDOM_RELATIVE_OFFSET; // fill in with World Wide Name based Port Name - 8 u8s // get from Tach registers WWN hi & lo LoadWWN(fcChip, plogi->port_name, 0); // fill in with World Wide Name based Node/Fabric Name - 8 u8s // get from Tach registers WWN hi & lo LoadWWN(fcChip, plogi->node_name, 1); // For Seagate Drives. // plogi->cmn_services.common_features |= 0x800; plogi->cmn_services.rel_offset = 0xFE; plogi->cmn_services.concurrent_seq = 1; plogi->class1.service_options = 0x00; plogi->class2.service_options = 0x00; plogi->class3.service_options = CLASS_VALID; plogi->class3.initiator_control = 0x00; plogi->class3.rx_data_size = MAX_RX_PAYLOAD; plogi->class3.recipient_control = ERROR_DISCARD | ONE_CATEGORY_SEQUENCE; plogi->class3.concurrent_sequences = 1; plogi->class3.open_sequences = 1; plogi->vendor_id[0] = 'C'; plogi->vendor_id[1] = 'Q'; plogi->vendor_version[0] = 'C'; plogi->vendor_version[1] = 'Q'; plogi->vendor_version[2] = ' '; plogi->vendor_version[3] = '0'; plogi->vendor_version[4] = '0'; plogi->vendor_version[5] = '0'; // FLOGI specific fields... (see FC-FLA, Rev 2.7, Aug 1999, sec 5.1) if ((type == ELS_FLOGI) || (type == ELS_FDISC)) { if (type == ELS_FLOGI) plogi->login_cmd = LS_FLOGI; else plogi->login_cmd = LS_FDISC; plogi->cmn_services.lowest_ver = 0x20; plogi->cmn_services.common_features = 0x0800; plogi->cmn_services.rel_offset = 0; plogi->cmn_services.concurrent_seq = 0; plogi->class3.service_options = 0x8800; plogi->class3.rx_data_size = 0; plogi->class3.recipient_control = 0; plogi->class3.concurrent_sequences = 0; plogi->class3.open_sequences = 0; } // copy back to caller's buff, w/ BIG ENDIAN swap BigEndianSwap((u8 *) & PlogiPayload, payload, sizeof(PlogiPayload)); break; case ELS_ACC: // generic Extended Link Service ACCept plogi->login_cmd = LS_ACC; // copy back to caller's buff, w/ BIG ENDIAN swap BigEndianSwap((u8 *) & PlogiPayload, payload, 4); break; case ELS_SCR: // Fabric State Change Registration { SCR_PL scr; // state change registration memset(&scr, 0, sizeof(scr)); scr.command = LS_SCR; // 0x62000000 // see FC-FLA, Rev 2.7, Table A.22 (pg 82) scr.function = 3; // 1 = Events detected by Fabric // 2 = N_Port detected registration // 3 = Full registration // copy back to caller's buff, w/ BIG ENDIAN swap BigEndianSwap((u8 *) & scr, payload, sizeof(SCR_PL)); } break; case FCS_NSR: // Fabric Name Service Request { NSR_PL nsr; // Name Server Req. payload memset(&nsr, 0, sizeof(NSR_PL)); // see Brocade Fabric Programming Guide, // Rev 1.3, pg 4-44 nsr.CT_Rev = 0x01000000; nsr.FCS_Type = 0xFC020000; nsr.Command_code = 0x01710000; nsr.FCP = 8; // copy back to caller's buff, w/ BIG ENDIAN swap BigEndianSwap((u8 *) & nsr, payload, sizeof(NSR_PL)); } break; case ELS_LOGO: // FC-PH PORT LogOut logo = &LogoutPayload; // load into stack buffer, // then BIG-ENDIAN switch a copy to caller logo->cmd = LS_LOGO; // load the 3 u8s of the node name // (if private loop, upper two u8s 0) logo->reserved = 0; logo->n_port_identifier[0] = (u8) (fcChip->Registers.my_al_pa); logo->n_port_identifier[1] = (u8) (fcChip->Registers.my_al_pa >> 8); logo->n_port_identifier[2] = (u8) (fcChip->Registers.my_al_pa >> 16); // fill in with World Wide Name based Port Name - 8 u8s // get from Tach registers WWN hi & lo LoadWWN(fcChip, logo->port_name, 0); BigEndianSwap((u8 *) & LogoutPayload, payload, sizeof(LogoutPayload)); // 16 u8 struct break; case ELS_LOGO_ACC: // Logout Accept (FH-PH pg 149, table 74) logo = &LogoutPayload; // load into stack buffer, // then BIG-ENDIAN switch a copy to caller logo->cmd = LS_ACC; BigEndianSwap((u8 *) & LogoutPayload, payload, 4); // 4 u8 cmnd break; case ELS_RJT: // ELS_RJT link service reject (FH-PH pg 155) prjt = (REJECT_MESSAGE *) payload; // pick up passed data rjt.command_code = ELS_RJT; // reverse fields, because of Swap that follows... rjt.vendor = prjt->reserved; // vendor specific rjt.explain = prjt->reason; // rjt.reason = prjt->explain; // rjt.reserved = prjt->vendor; // // BIG-ENDIAN switch a copy to caller BigEndianSwap((u8 *) & rjt, payload, 8); // 8 u8 cmnd break; case ELS_PRLI_ACC: // Process Login ACCept case ELS_PRLI: // Process Login case ELS_PRLO: // Process Logout memset(&PrliPayload, 0, sizeof(PrliPayload)); prli = &PrliPayload; // load into stack buffer, if (type == ELS_PRLI) prli->cmd = 0x20; // Login else if (type == ELS_PRLO) prli->cmd = 0x21; // Logout else if (type == ELS_PRLI_ACC) { prli->cmd = 0x02; // Login ACCept prli->valid = REQUEST_EXECUTED; } prli->valid |= SCSI_FCP | ESTABLISH_PAIR; prli->fcp_info = READ_XFER_RDY; prli->page_length = 0x10; prli->payload_length = 20; // Can be initiator AND target if (fcChip->Options.initiator) prli->fcp_info |= INITIATOR_FUNCTION; if (fcChip->Options.target) prli->fcp_info |= TARGET_FUNCTION; BigEndianSwap((u8 *) & PrliPayload, payload, prli->payload_length); break; default: // no can do - programming error printk(" BuildLinkServicePayload unknown!\n"); break; } } // loads 8 u8s for PORT name or NODE name base on // controller's WWN. void LoadWWN(PTACHYON fcChip, u8 * dest, u8 type) { u8 *bPtr, i; switch (type) { case 0: // Port_Name bPtr = (u8 *) & fcChip->Registers.wwn_hi; for (i = 0; i < 4; i++) dest[i] = *bPtr++; bPtr = (u8 *) & fcChip->Registers.wwn_lo; for (i = 4; i < 8; i++) dest[i] = *bPtr++; break; case 1: // Node/Fabric _Name bPtr = (u8 *) & fcChip->Registers.wwn_hi; for (i = 0; i < 4; i++) dest[i] = *bPtr++; bPtr = (u8 *) & fcChip->Registers.wwn_lo; for (i = 4; i < 8; i++) dest[i] = *bPtr++; break; } } // We check the Port Login payload for required values. Note that // ELS_PLOGI and ELS_PDISC (Port DISCover) use the same payload. int verify_PLOGI(PTACHYON fcChip, TachFCHDR_GCMND * fchs, u32 * reject_explain) { LOGIN_PAYLOAD login; // source, dest, len (should be mult. of 4) BigEndianSwap((u8 *) & fchs->pl[0], (u8 *) & login, sizeof(login)); // check FC version // if other port's highest supported version // is less than our lowest, and // if other port's lowest if (login.cmn_services.highest_ver < fcChip->lowest_FCPH_ver || login.cmn_services.lowest_ver > fcChip->highest_FCPH_ver) { *reject_explain = LS_RJT_REASON(LOGICAL_ERROR, OPTIONS_ERROR); return LOGICAL_ERROR; } // Receive Data Field Size must be >=128 // per FC-PH if (login.cmn_services.bb_rx_size < 128) { *reject_explain = LS_RJT_REASON(LOGICAL_ERROR, DATA_FIELD_SIZE_ERROR); return LOGICAL_ERROR; } // Only check Class 3 params if (login.class3.service_options & CLASS_VALID) { if (login.class3.rx_data_size < 128) { *reject_explain = LS_RJT_REASON(LOGICAL_ERROR, INVALID_CSP); return LOGICAL_ERROR; } if (login.class3.initiator_control & XID_REQUIRED) { *reject_explain = LS_RJT_REASON(LOGICAL_ERROR, INITIATOR_CTL_ERROR); return LOGICAL_ERROR; } } return 0; // success } int verify_PRLI(TachFCHDR_GCMND * fchs, u32 * reject_explain) { PRLI_REQUEST prli; // buffer for BIG ENDIAN // source, dest, len (should be mult. of 4) BigEndianSwap((u8 *) & fchs->pl[0], (u8 *) & prli, sizeof(prli)); if (prli.fcp_info == 0) // i.e., not target or initiator? { *reject_explain = LS_RJT_REASON(LOGICAL_ERROR, OPTIONS_ERROR); return LOGICAL_ERROR; } return 0; // success } // SWAP u8s as required by Fibre Channel (i.e. BIG ENDIAN) // INPUTS: // source - ptr to LITTLE ENDIAN u32S // cnt - number of u8s to switch (should be mult. of u32) // OUTPUTS: // dest - ptr to BIG ENDIAN copy // RETURN: // none // void BigEndianSwap(u8 * source, u8 * dest, u16 cnt) { int i, j; source += 3; // start at MSB of 1st u32 for (j = 0; j < cnt; j += 4, source += 4, dest += 4) // every u32 { for (i = 0; i < 4; i++) // every u8 in u32 *(dest + i) = *(source - i); } } // Build FC Exchanges............ static void buildFCPstatus(PTACHYON fcChip, u32 ExchangeID); static s32 FindFreeExchange(PTACHYON fcChip, u32 type); static u32 build_SEST_sgList(struct pci_dev *pcidev, u32 * SESTalPairStart, Scsi_Cmnd * Cmnd, u32 * sgPairs, PSGPAGES * sgPages_head); // link list of TL Ext. S/G pages from O/S Pool static int build_FCP_payload(Scsi_Cmnd * Cmnd, u8 * payload, u32 type, u32 fcp_dl); /* IRB ERQ __________________ | | / | Req_A_SFS_Len | ____________________ |----------| / | Req_A_SFS_Addr |------->| Reserved | | IRB | / | Req_A_D_ID | | SOF EOF TimeStamp | |-----------/ | Req_A_SEST_Index |-+ | R_CTL | D_ID | | IRB | | Req_B... | | | CS_CTL| S_ID | |-----------\ | | | | TYPE | F_CTL | | IRB | \ | | | | SEQ_ID | SEQ_CNT | |----------- \ | | +-->+--| OX_ID | RX_ID | | | \ |__________________| | | RO | | | pl (payload/cmnd) | | | ..... | | |___________________| | | +-------------------------------------------+ | | | e.g. IWE | SEST __________________ for FCP_DATA | | | / | | Hdr_Len | ____________________ | |----------| / | Hdr_Addr_Addr |------->| Reserved | | | [0] | / |Remote_ID| RSP_Len| | SOF EOF TimeStamp | | |-----------/ | RSP_Addr |---+ | R_CTL | D_ID | +-> [1] | | | Buff_Off | | | CS_CTL| S_ID | |-----------\ |BuffIndex| Link | | | TYPE | F_CTL | | [2] | \ | Rsvd | RX_ID | | | SEQ_ID | SEQ_CNT | |----------- \ | Data_Len | | | OX_ID | RX_ID | | ... | \ | Exp_RO | | | RO | |----------| | Exp_Byte_Cnt | | |___________________| | SEST_LEN | +--| Len | | |__________| | | Address | | | | ... | | for FCP_RSP | |__________________| | ____________________ | +----| Reserved | | | SOF EOF TimeStamp | | | R_CTL | D_ID | | | CS_CTL| S_ID | +--- local or extended | .... | scatter/gather lists defining upper-layer data (e.g. from user's App) */ // All TachLite commands must start with a SFS (Single Frame Sequence) // command. In the simplest case (a NOP Basic Link command), // only one frame header and ERQ entry is required. The most complex // case is the SCSI assisted command, which requires an ERQ entry, // SEST entry, and several frame headers and data buffers all // logically linked together. // Inputs: // dev - controller struct // type - PLOGI, SCSI_IWE, etc. // InFCHS - Incoming Tachlite FCHS which prompted this exchange // (only s_id set if we are originating) // Data - PVOID to data struct consistent with "type" // fcExchangeIndex - pointer to OX/RD ID value of built exchange // Return: // fcExchangeIndex - OX/RD ID value if successful // 0 - success // INVALID_ARGS - NULL/ invalid passed args // BAD_ALPA - Bad source al_pa address // LNKDWN_OSLS - Link Down (according to this controller) // OUTQUE_FULL - Outbound Que full // DRIVERQ_FULL - controller's Exchange array full // SEST_FULL - SEST table full // // Remarks: // Psuedo code: // Check for NULL pointers / bad args // Build outgoing FCHS - the header/payload struct // Build IRB (for ERQ entry) // if SCSI command, build SEST entry (e.g. IWE, TRE,...) // return success //sbuildex u32 cpqfcTSBuildExchange(CPQFCHBA * dev, u32 type, // e.g. PLOGI TachFCHDR_GCMND * InFCHS, // incoming FCHS void *Data, // the CDB, scatter/gather, etc. s32 * fcExchangeIndex) // points to allocated exchange, { PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; u32 ulStatus = 0; // assume OK u16 ox_ID, rx_ID = 0xFFFF; u32 SfsLen = 0L; TachLiteIRB *pIRB; IRBflags IRB_flags; u8 *pIRB_flags = (u8 *) & IRB_flags; TachFCHDR_GCMND *CMDfchs; TachFCHDR *dataHDR; // 32 byte HEADER ONLY FCP-DATA buffer TachFCHDR_RSP *rspHDR; // 32 byte header + RSP payload Scsi_Cmnd *Cmnd = (Scsi_Cmnd *) Data; // Linux Scsi CDB, S/G, ... TachLiteIWE *pIWE; TachLiteIRE *pIRE; TachLiteTWE *pTWE; TachLiteTRE *pTRE; u32 fcp_dl; // total byte length of DATA transferred u32 fl; // frame length (FC frame size, 128, 256, 512, 1024) u32 sgPairs; // number of valid scatter/gather pairs int FCP_SCSI_command; BA_ACC_PAYLOAD *ba_acc; BA_RJT_PAYLOAD *ba_rjt; // check passed ARGS if (!fcChip->ERQ) // NULL ptr means uninitialized Tachlite chip return INVALID_ARGS; if (type == SCSI_IRE || type == SCSI_TRE || type == SCSI_IWE || type == SCSI_TWE) FCP_SCSI_command = 1; else FCP_SCSI_command = 0; // for commands that pass payload data (e.g. SCSI write) // examine command struct - verify that the // length of s/g buffers is adequate for total payload // length (end of list is NULL address) if (FCP_SCSI_command) { if (Data) // must have data descriptor (S/G list -- at least // one address with at least 1 byte of data) { // something to do (later)? } else return INVALID_ARGS; // invalid DATA ptr } // we can build an Exchange for later Queuing (on the TL chip) // if an empty slot is available in the DevExt for this controller // look for available Exchange slot... if (type != FCP_RESPONSE && type != BLS_ABTS && type != BLS_ABTS_ACC) // already have Exchange slot! *fcExchangeIndex = FindFreeExchange(fcChip, type); if (*fcExchangeIndex != -1) // Exchange is available? { // assign tmp ptr (shorthand) CMDfchs = &Exchanges->fcExchange[*fcExchangeIndex].fchs; if (Cmnd != NULL) // (necessary for ABTS cases) { Exchanges->fcExchange[*fcExchangeIndex].Cmnd = Cmnd; // Linux Scsi Exchanges->fcExchange[*fcExchangeIndex].pLoggedInPort = fcFindLoggedInPort(fcChip, Exchanges->fcExchange[*fcExchangeIndex].Cmnd, // find Scsi Nexus 0, // DON'T search linked list for FC port id NULL, // DON'T search linked list for FC WWN NULL); // DON'T care about end of list } // Build the command frame header (& data) according // to command type // fields common for all SFS frame types CMDfchs->reserved = 0L; // must clear CMDfchs->sof_eof = 0x75000000L; // SOFi3:EOFn no UAM; LCr=0, no TS // get the destination port_id from incoming FCHS // (initialized before calling if we're Originator) // Frame goes to port it was from - the source_id CMDfchs->d_id = InFCHS->s_id & 0xFFFFFF; // destination (add R_CTL later) CMDfchs->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0 // now enter command-specific fields switch (type) { case BLS_NOP: // FC defined basic link service command NO-OP // ensure unique X_IDs! (use tracking function) *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS (not SEST index) SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += 32L; // add len to LSB (header only - no payload) // TYPE[31-24] 00 Basic Link Service // f_ctl[23:0] exchg originator, 1st seq, xfer S.I. CMDfchs->d_id |= 0x80000000L; // R_CTL = 80 for NOP (Basic Link Ser.) CMDfchs->f_ctl = 0x00310000L; // xchng originator, 1st seq,.... CMDfchs->seq_cnt = 0x0L; CMDfchs->ox_rx_id = 0xFFFF; // RX_ID for now; OX_ID on start CMDfchs->ro = 0x0L; // relative offset (n/a) CMDfchs->pl[0] = 0xaabbccddL; // words 8-15 frame data payload (n/a) Exchanges->fcExchange[*fcExchangeIndex].timeOut = 1; // seconds // (NOP should complete ~instantly) break; case BLS_ABTS_ACC: // Abort Sequence ACCept *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS (not SEST index) SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += 32 + 12; // add len to LSB (header + 3 DWORD payload) CMDfchs->d_id |= 0x84000000L; // R_CTL = 84 for BASIC ACCept // TYPE[31-24] 00 Basic Link Service // f_ctl[23:0] exchg originator, not 1st seq, xfer S.I. CMDfchs->f_ctl = 0x00910000L; // xchnge responder, last seq, xfer SI // CMDfchs->seq_id & count might be set from DataHdr? CMDfchs->ro = 0x0L; // relative offset (n/a) Exchanges->fcExchange[*fcExchangeIndex].timeOut = 5; // seconds // (Timeout in case of weird error) // now set the ACCept payload... ba_acc = (BA_ACC_PAYLOAD *) & CMDfchs->pl[0]; memset(ba_acc, 0, sizeof(BA_ACC_PAYLOAD)); // Since PLDA requires (only) entire Exchange aborts, we don't need // to worry about what the last sequence was. // We expect that a "target" task is accepting the abort, so we // can use the OX/RX ID pair ba_acc->ox_rx_id = CMDfchs->ox_rx_id; // source, dest, #bytes BigEndianSwap((u8 *) & CMDfchs->ox_rx_id, (u8 *) & ba_acc->ox_rx_id, 4); ba_acc->low_seq_cnt = 0; ba_acc->high_seq_cnt = 0xFFFF; break; case BLS_ABTS_RJT: // Abort Sequence ACCept *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS (not SEST index) SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += 32 + 12; // add len to LSB (header + 3 DWORD payload) CMDfchs->d_id |= 0x85000000L; // R_CTL = 85 for BASIC ReJecT // f_ctl[23:0] exchg originator, not 1st seq, xfer S.I. // TYPE[31-24] 00 Basic Link Service CMDfchs->f_ctl = 0x00910000L; // xchnge responder, last seq, xfer SI // CMDfchs->seq_id & count might be set from DataHdr? CMDfchs->ro = 0x0L; // relative offset (n/a) Exchanges->fcExchange[*fcExchangeIndex].timeOut = 5; // seconds // (Timeout in case of weird error) CMDfchs->ox_rx_id = InFCHS->ox_rx_id; // copy from sender! // now set the ReJecT payload... ba_rjt = (BA_RJT_PAYLOAD *) & CMDfchs->pl[0]; memset(ba_rjt, 0, sizeof(BA_RJT_PAYLOAD)); // We expect that a "target" task couldn't find the Exhange in the // array of active exchanges, so we use a new LinkService X_ID. // See Reject payload description in FC-PH (Rev 4.3), pg. 140 ba_rjt->reason_code = 0x09; // "unable to perform command request" ba_rjt->reason_explain = 0x03; // invalid OX/RX ID pair break; case BLS_ABTS: // FC defined basic link service command ABTS // Abort Sequence *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS (not SEST index) SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += 32L; // add len to LSB (header only - no payload) // TYPE[31-24] 00 Basic Link Service // f_ctl[23:0] exchg originator, not 1st seq, xfer S.I. CMDfchs->d_id |= 0x81000000L; // R_CTL = 81 for ABTS CMDfchs->f_ctl = 0x00110000L; // xchnge originator, last seq, xfer SI // CMDfchs->seq_id & count might be set from DataHdr? CMDfchs->ro = 0x0L; // relative offset (n/a) Exchanges->fcExchange[*fcExchangeIndex].timeOut = 2; // seconds // (ABTS must timeout when responder is gone) break; case FCS_NSR: // Fabric Name Service Request Exchanges->fcExchange[*fcExchangeIndex].reTries = 2; Exchanges->fcExchange[*fcExchangeIndex].timeOut = 2; // seconds // OX_ID, linked to Driver Transaction ID // (fix-up at Queing time) CMDfchs->ox_rx_id = 0xFFFF; // RX_ID - Responder (target) to modify // OX_ID set at ERQueing time *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS (not SEST index) SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += (32L + sizeof(NSR_PL)); // add len (header & NSR payload) CMDfchs->d_id |= 0x02000000L; // R_CTL = 02 for - // Name Service Request: Unsolicited // TYPE[31-24] 01 Extended Link Service // f_ctl[23:0] exchg originator, 1st seq, xfer S.I. CMDfchs->f_ctl = 0x20210000L; // OX_ID will be fixed-up at Tachyon enqueing time CMDfchs->seq_cnt = 0; // seq ID, DF_ctl, seq cnt CMDfchs->ro = 0x0L; // relative offset (n/a) BuildLinkServicePayload(fcChip, type, &CMDfchs->pl[0]); break; case ELS_PLOGI: // FC-PH extended link service command Port Login // (May, 2000) // NOTE! This special case facilitates SANMark testing. The SANMark // test script for initialization-timeout.fcal.SANMark-1.fc // "eats" the OPN() primitive without issuing an R_RDY, causing // Tachyon to report LST (loop state timeout), which causes a // LIP. To avoid this, simply send out the frame (i.e. assuming a // buffer credit of 1) without waiting for R_RDY. Many FC devices // (other than Tachyon) have been doing this for years. We don't // ever want to do this for non-Link Service frames unless the // other device really did report non-zero login BB credit (i.e. // in the PLOGI ACCept frame). // CMDfchs->sof_eof |= 0x00000400L; // LCr=1 case ELS_FDISC: // Fabric Discovery (Login) case ELS_FLOGI: // Fabric Login case ELS_SCR: // Fabric State Change Registration case ELS_LOGO: // FC-PH extended link service command Port Logout case ELS_PDISC: // FC-PH extended link service cmnd Port Discovery case ELS_PRLI: // FC-PH extended link service cmnd Process Login Exchanges->fcExchange[*fcExchangeIndex].reTries = 2; Exchanges->fcExchange[*fcExchangeIndex].timeOut = 2; // seconds // OX_ID, linked to Driver Transaction ID // (fix-up at Queing time) CMDfchs->ox_rx_id = 0xFFFF; // RX_ID - Responder (target) to modify // OX_ID set at ERQueing time *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS (not SEST index) SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB if (type == ELS_LOGO) SfsLen += (32L + 16L); // add len (header & PLOGI payload) else if (type == ELS_PRLI) SfsLen += (32L + 20L); // add len (header & PRLI payload) else if (type == ELS_SCR) SfsLen += (32L + sizeof(SCR_PL)); // add len (header & SCR payload) else SfsLen += (32L + 116L); // add len (header & PLOGI payload) CMDfchs->d_id |= 0x22000000L; // R_CTL = 22 for - // Extended Link_Data: Unsolicited Control // TYPE[31-24] 01 Extended Link Service // f_ctl[23:0] exchg originator, 1st seq, xfer S.I. CMDfchs->f_ctl = 0x01210000L; // OX_ID will be fixed-up at Tachyon enqueing time CMDfchs->seq_cnt = 0; // seq ID, DF_ctl, seq cnt CMDfchs->ro = 0x0L; // relative offset (n/a) BuildLinkServicePayload(fcChip, type, &CMDfchs->pl[0]); break; case ELS_LOGO_ACC: // FC-PH extended link service logout accept case ELS_RJT: // extended link service reject (add reason) case ELS_ACC: // ext. link service generic accept case ELS_PLOGI_ACC: // ext. link service login accept (PLOGI or PDISC) case ELS_PRLI_ACC: // ext. link service process login accept Exchanges->fcExchange[*fcExchangeIndex].timeOut = 1; // assume done // ensure unique X_IDs! (use tracking function) // OX_ID from initiator cmd ox_ID = (u16) (InFCHS->ox_rx_id >> 16); rx_ID = 0xFFFF; // RX_ID, linked to Driver Exchange ID *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS (not SEST index) SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB if (type == ELS_RJT) { SfsLen += (32L + 8L); // add len (header + payload) // ELS_RJT reason codes (utilize unused "reserved" field) CMDfchs->pl[0] = 1; CMDfchs->pl[1] = InFCHS->reserved; } else if ((type == ELS_LOGO_ACC) || (type == ELS_ACC)) SfsLen += (32L + 4L); // add len (header + payload) else if (type == ELS_PLOGI_ACC) SfsLen += (32L + 116L); // add len (header + payload) else if (type == ELS_PRLI_ACC) SfsLen += (32L + 20L); // add len (header + payload) CMDfchs->d_id |= 0x23000000L; // R_CTL = 23 for - // Extended Link_Data: Control Reply // TYPE[31-24] 01 Extended Link Service // f_ctl[23:0] exchg responder, last seq, e_s, tsi CMDfchs->f_ctl = 0x01990000L; CMDfchs->seq_cnt = 0x0L; CMDfchs->ox_rx_id = 0L; // clear CMDfchs->ox_rx_id = ox_ID; // load upper 16 bits CMDfchs->ox_rx_id <<= 16; // shift them CMDfchs->ro = 0x0L; // relative offset (n/a) BuildLinkServicePayload(fcChip, type, &CMDfchs->pl[0]); break; // Fibre Channel SCSI 'originator' sequences... // (originator means 'initiator' in FCP-SCSI) case SCSI_IWE: // TachLite Initiator Write Entry { PFC_LOGGEDIN_PORT pLoggedInPort = Exchanges->fcExchange[*fcExchangeIndex].pLoggedInPort; Exchanges->fcExchange[*fcExchangeIndex].reTries = 1; Exchanges->fcExchange[*fcExchangeIndex].timeOut = 7; // FC2 timeout // first, build FCP_CMND // unique X_ID fix-ups in StartExchange *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS FCP-CMND (not SEST index) // NOTE: unlike FC LinkService login frames, normal // SCSI commands are sent without outgoing verification IRB_flags.DCM = 1; // Disable completion message for Cmnd frame SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += 64L; // add len to LSB (header & CMND payload) CMDfchs->d_id |= (0x06000000L); // R_CTL = 6 for command // TYPE[31-24] 8 for FCP SCSI // f_ctl[23:0] exchg originator, 1st seq, xfer S.I. // valid RO CMDfchs->f_ctl = 0x08210008L; CMDfchs->seq_cnt = 0x0L; CMDfchs->ox_rx_id = 0L; // clear for now (-or- in later) CMDfchs->ro = 0x0L; // relative offset (n/a) // now, fill out FCP-DATA header // (use buffer inside SEST object) dataHDR = &fcChip->SEST->DataHDR[*fcExchangeIndex]; dataHDR->reserved = 0L; // must clear dataHDR->sof_eof = 0x75002000L; // SOFi3:EOFn no UAM; no CLS, noLCr, no TS dataHDR->d_id = (InFCHS->s_id | 0x01000000L); // R_CTL= FCP_DATA dataHDR->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0 // TYPE[31-24] 8 for FCP SCSI // f_ctl[23:0] xfer S.I.| valid RO dataHDR->f_ctl = 0x08010008L; dataHDR->seq_cnt = 0x02000000L; // sequence ID: df_ctl : seqence count dataHDR->ox_rx_id = 0L; // clear; fix-up dataHDR fields later dataHDR->ro = 0x0L; // relative offset (n/a) // Now setup the SEST entry pIWE = &fcChip->SEST->u[*fcExchangeIndex].IWE; // fill out the IWE: // VALid entry:Dir outbound:DCM:enable CM:enal INT: FC frame len pIWE->Hdr_Len = 0x8e000020L; // data frame Len always 32 bytes // from login parameters with other port, what's the largest frame // we can send? if (pLoggedInPort == NULL) { ulStatus = INVALID_ARGS; // failed! give up break; } if (pLoggedInPort->rx_data_size >= 2048) fl = 0x00020000; // 2048 code (only support 1024!) else if (pLoggedInPort->rx_data_size >= 1024) fl = 0x00020000; // 1024 code else if (pLoggedInPort->rx_data_size >= 512) fl = 0x00010000; // 512 code else fl = 0; // 128 bytes -- should never happen pIWE->Hdr_Len |= fl; // add xmit FC frame len for data phase pIWE->Hdr_Addr = fcChip->SEST->base + ((unsigned long) &fcChip->SEST->DataHDR[*fcExchangeIndex] - (unsigned long) fcChip->SEST); pIWE->RSP_Len = sizeof(TachFCHDR_RSP); // hdr+data (recv'd RSP frame) pIWE->RSP_Len |= (InFCHS->s_id << 8); // MS 24 bits Remote_ID memset(&fcChip->SEST->RspHDR[*fcExchangeIndex].pl, 0, sizeof(FCP_STATUS_RESPONSE)); // clear out previous status pIWE->RSP_Addr = fcChip->SEST->base + ((unsigned long) &fcChip->SEST->RspHDR[*fcExchangeIndex] - (unsigned long) fcChip->SEST); // Do we need local or extended gather list? // depends on size - we can handle 3 len/addr pairs // locally. fcp_dl = build_SEST_sgList(dev->PciDev, &pIWE->GLen1, Cmnd, // S/G list &sgPairs, // return # of pairs in S/G list (from "Data" descriptor) &fcChip->SEST->sgPages[*fcExchangeIndex]); // (for Freeing later) if (!fcp_dl) // error building S/G list? { ulStatus = MEMPOOL_FAIL; break; // give up } // Now that we know total data length in // the passed S/G buffer, set FCP CMND frame build_FCP_payload(Cmnd, (u8 *) & CMDfchs->pl[0], type, fcp_dl); if (sgPairs > 3) // need extended s/g list pIWE->Buff_Off = 0x78000000L; // extended data | (no offset) else // local data pointers (in SEST) pIWE->Buff_Off = 0xf8000000L; // local data | (no offset) // u32 5 pIWE->Link = 0x0000ffffL; // Buff_Index | Link pIWE->RX_ID = 0x0L; // DWord 6: RX_ID set by target XFER_RDY // DWord 7 pIWE->Data_Len = 0L; // TL enters rcv'd XFER_RDY BURST_LEN pIWE->Exp_RO = 0L; // DWord 8 // DWord 9 pIWE->Exp_Byte_Cnt = fcp_dl; // sum of gather buffers } break; case SCSI_IRE: // TachLite Initiator Read Entry if (Cmnd->timeout != 0) { // printk("Cmnd->timeout %d\n", Cmnd->timeout); // per Linux Scsi Exchanges->fcExchange[*fcExchangeIndex].timeOut = Cmnd->timeout; } else // use our best guess, based on FC & device { if (Cmnd->SCp.Message == 1) // Tape device? (from INQUIRY) { // turn off our timeouts (for now...) Exchanges->fcExchange[*fcExchangeIndex].timeOut = 0xFFFFFFFF; } else { Exchanges->fcExchange[*fcExchangeIndex].reTries = 1; Exchanges->fcExchange[*fcExchangeIndex].timeOut = 7; // per SCSI req. } } // first, build FCP_CMND *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS FCP-CMND (not SEST index) // NOTE: unlike FC LinkService login frames, // normal SCSI commands are sent "open loop" IRB_flags.DCM = 1; // Disable completion message for Cmnd frame SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += 64L; // add len to LSB (header & CMND payload) CMDfchs->d_id |= (0x06000000L); // R_CTL = 6 for command // TYPE[31-24] 8 for FCP SCSI // f_ctl[23:0] exchg originator, 1st seq, xfer S.I. // valid RO CMDfchs->f_ctl = 0x08210008L; CMDfchs->seq_cnt = 0x0L; // x_ID & data direction bit set later CMDfchs->ox_rx_id = 0xFFFF; // clear CMDfchs->ro = 0x0L; // relative offset (n/a) // Now setup the SEST entry pIRE = &fcChip->SEST->u[*fcExchangeIndex].IRE; // fill out the IRE: // VALid entry:Dir outbound:enable CM:enal INT: pIRE->Seq_Accum = 0xCE000000L; // VAL,DIR inbound,DCM| INI,DAT,RSP pIRE->reserved = 0L; pIRE->RSP_Len = sizeof(TachFCHDR_RSP); // hdr+data (recv'd RSP frame) pIRE->RSP_Len |= (InFCHS->s_id << 8); // MS 24 bits Remote_ID pIRE->RSP_Addr = fcChip->SEST->base + ((unsigned long) &fcChip->SEST->RspHDR[*fcExchangeIndex] - (unsigned long) fcChip->SEST); // Do we need local or extended gather list? // depends on size - we can handle 3 len/addr pairs // locally. fcp_dl = build_SEST_sgList(dev->PciDev, &pIRE->SLen1, Cmnd, // SCSI command Data desc. with S/G list &sgPairs, // return # of pairs in S/G list (from "Data" descriptor) &fcChip->SEST->sgPages[*fcExchangeIndex]); // (for Freeing later) if (!fcp_dl) // error building S/G list? { // It is permissible to have a ZERO LENGTH Read command. // If there is the case, simply set fcp_dl (and Exp_Byte_Cnt) // to 0 and continue. if (Cmnd->request_bufflen == 0) { fcp_dl = 0; // no FC DATA frames expected } else { ulStatus = MEMPOOL_FAIL; break; // give up } } // now that we know the S/G length, build CMND payload build_FCP_payload(Cmnd, (u8 *) & CMDfchs->pl[0], type, fcp_dl); if (sgPairs > 3) // need extended s/g list pIRE->Buff_Off = 0x00000000; // DWord 4: extended s/g list, no offset else pIRE->Buff_Off = 0x80000000; // local data, no offset pIRE->Buff_Index = 0x0L; // DWord 5: Buff_Index | Reserved pIRE->Exp_RO = 0x0L; // DWord 6: Expected Rel. Offset pIRE->Byte_Count = 0; // DWord 7: filled in by TL on err pIRE->reserved_ = 0; // DWord 8: reserved // NOTE: 0 length READ is OK. pIRE->Exp_Byte_Cnt = fcp_dl; // DWord 9: sum of scatter buffers break; // Fibre Channel SCSI 'responder' sequences... // (originator means 'target' in FCP-SCSI) case SCSI_TWE: // TachLite Target Write Entry Exchanges->fcExchange[*fcExchangeIndex].timeOut = 10; // per SCSI req. // first, build FCP_CMND *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS (XFER_RDY) SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += (32L + 12L); // add SFS len (header & XFER_RDY payload) CMDfchs->d_id |= (0x05000000L); // R_CTL = 5 for XFER_RDY // TYPE[31-24] 8 for FCP SCSI // f_ctl[23:0] exchg responder, 1st seq, xfer S.I. // valid RO CMDfchs->f_ctl = 0x08810008L; CMDfchs->seq_cnt = 0x01000000; // sequence ID: df_ctl: sequence count // use originator (other port's) OX_ID CMDfchs->ox_rx_id = InFCHS->ox_rx_id; // we want upper 16 bits CMDfchs->ro = 0x0L; // relative offset (n/a) // now, fill out FCP-RSP header // (use buffer inside SEST object) rspHDR = &fcChip->SEST->RspHDR[*fcExchangeIndex]; rspHDR->reserved = 0L; // must clear rspHDR->sof_eof = 0x75000000L; // SOFi3:EOFn no UAM; no CLS, noLCr, no TS rspHDR->d_id = (InFCHS->s_id | 0x07000000L); // R_CTL= FCP_RSP rspHDR->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0 // TYPE[31-24] 8 for FCP SCSI // f_ctl[23:0] responder|last seq| xfer S.I. rspHDR->f_ctl = 0x08910000L; rspHDR->seq_cnt = 0x03000000; // sequence ID rspHDR->ox_rx_id = InFCHS->ox_rx_id; // gives us OX_ID rspHDR->ro = 0x0L; // relative offset (n/a) // Now setup the SEST entry pTWE = &fcChip->SEST->u[*fcExchangeIndex].TWE; // fill out the TWE: // VALid entry:Dir outbound:enable CM:enal INT: pTWE->Seq_Accum = 0xC4000000L; // upper word flags pTWE->reserved = 0L; pTWE->Remote_Node_ID = 0L; // no more auto RSP frame! (TL/TS change) pTWE->Remote_Node_ID |= (InFCHS->s_id << 8); // MS 24 bits Remote_ID // Do we need local or extended gather list? // depends on size - we can handle 3 len/addr pairs // locally. fcp_dl = build_SEST_sgList(dev->PciDev, &pTWE->SLen1, Cmnd, // S/G list &sgPairs, // return # of pairs in S/G list (from "Data" descriptor) &fcChip->SEST->sgPages[*fcExchangeIndex]); // (for Freeing later) if (!fcp_dl) // error building S/G list? { ulStatus = MEMPOOL_FAIL; break; // give up } // now that we know the S/G length, build CMND payload build_FCP_payload(Cmnd, (u8 *) & CMDfchs->pl[0], type, fcp_dl); if (sgPairs > 3) // need extended s/g list pTWE->Buff_Off = 0x00000000; // extended s/g list, no offset else pTWE->Buff_Off = 0x80000000; // local data, no offset pTWE->Buff_Index = 0; // Buff_Index | Link pTWE->Exp_RO = 0; pTWE->Byte_Count = 0; // filled in by TL on err pTWE->reserved_ = 0; pTWE->Exp_Byte_Cnt = fcp_dl; // sum of scatter buffers break; case SCSI_TRE: // TachLite Target Read Entry // It doesn't make much sense for us to "time-out" a READ, // but we'll use it for design consistency and internal error recovery. Exchanges->fcExchange[*fcExchangeIndex].timeOut = 10; // per SCSI req. // I/O request block settings... *pIRB_flags = 0; // clear IRB flags // check PRLI (process login) info // to see if Initiator Requires XFER_RDY // if not, don't send one! // { PRLI check...} IRB_flags.SFA = 0; // don't send XFER_RDY - start data SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += (32L + 12L); // add SFS len (header & XFER_RDY payload) // now, fill out FCP-DATA header // (use buffer inside SEST object) dataHDR = &fcChip->SEST->DataHDR[*fcExchangeIndex]; dataHDR->reserved = 0L; // must clear dataHDR->sof_eof = 0x75000000L; // SOFi3:EOFn no UAM; no CLS,noLCr,no TS dataHDR->d_id = (InFCHS->s_id | 0x01000000L); // R_CTL= FCP_DATA dataHDR->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0 // TYPE[31-24] 8 for FCP SCSI // f_ctl[23:0] exchg responder, not 1st seq, xfer S.I. // valid RO dataHDR->f_ctl = 0x08810008L; dataHDR->seq_cnt = 0x01000000; // sequence ID (no XRDY) dataHDR->ox_rx_id = InFCHS->ox_rx_id & 0xFFFF0000; // we want upper 16 bits dataHDR->ro = 0x0L; // relative offset (n/a) // now, fill out FCP-RSP header // (use buffer inside SEST object) rspHDR = &fcChip->SEST->RspHDR[*fcExchangeIndex]; rspHDR->reserved = 0L; // must clear rspHDR->sof_eof = 0x75000000L; // SOFi3:EOFn no UAM; no CLS, noLCr, no TS rspHDR->d_id = (InFCHS->s_id | 0x07000000L); // R_CTL= FCP_RSP rspHDR->s_id = fcChip->Registers.my_al_pa; // CS_CTL = 0 // TYPE[31-24] 8 for FCP SCSI // f_ctl[23:0] responder|last seq| xfer S.I. rspHDR->f_ctl = 0x08910000L; rspHDR->seq_cnt = 0x02000000; // sequence ID: df_ctl: sequence count rspHDR->ro = 0x0L; // relative offset (n/a) // Now setup the SEST entry pTRE = &fcChip->SEST->u[*fcExchangeIndex].TRE; // VALid entry:Dir outbound:enable CM:enal INT: pTRE->Hdr_Len = 0x86010020L; // data frame Len always 32 bytes pTRE->Hdr_Addr = // bus address of dataHDR; fcChip->SEST->base + ((unsigned long) &fcChip->SEST->DataHDR[*fcExchangeIndex] - (unsigned long) fcChip->SEST); pTRE->RSP_Len = 64L; // hdr+data (TL assisted RSP frame) pTRE->RSP_Len |= (InFCHS->s_id << 8); // MS 24 bits Remote_ID pTRE->RSP_Addr = // bus address of rspHDR fcChip->SEST->base + ((unsigned long) &fcChip->SEST->RspHDR[*fcExchangeIndex] - (unsigned long) fcChip->SEST); // Do we need local or extended gather list? // depends on size - we can handle 3 len/addr pairs // locally. fcp_dl = build_SEST_sgList(dev->PciDev, &pTRE->GLen1, Cmnd, // S/G list &sgPairs, // return # of pairs in S/G list (from "Data" descriptor) &fcChip->SEST->sgPages[*fcExchangeIndex]); // (for Freeing later) if (!fcp_dl) // error building S/G list? { ulStatus = MEMPOOL_FAIL; break; // give up } // no payload or command to build -- READ doesn't need XRDY if (sgPairs > 3) // need extended s/g list pTRE->Buff_Off = 0x78000000L; // extended data | (no offset) else // local data pointers (in SEST) pTRE->Buff_Off = 0xf8000000L; // local data | (no offset) // u32 5 pTRE->Buff_Index = 0L; // Buff_Index | reserved pTRE->reserved = 0x0L; // DWord 6 // DWord 7: NOTE: zero length will // hang TachLite! pTRE->Data_Len = fcp_dl; // e.g. sum of scatter buffers pTRE->reserved_ = 0L; // DWord 8 pTRE->reserved__ = 0L; // DWord 9 break; case FCP_RESPONSE: // Target response frame: this sequence uses an OX/RX ID // pair from a completed SEST exchange. We built most // of the response frame when we created the TWE/TRE. *pIRB_flags = 0; // clear IRB flags IRB_flags.SFA = 1; // send SFS (RSP) SfsLen = *pIRB_flags; SfsLen <<= 24; // shift flags to MSB SfsLen += sizeof(TachFCHDR_RSP); // add SFS len (header & RSP payload) Exchanges->fcExchange[*fcExchangeIndex].type = FCP_RESPONSE; // change Exchange type to "response" phase // take advantage of prior knowledge of OX/RX_ID pair from // previous XFER outbound frame (still in fchs of exchange) fcChip->SEST->RspHDR[*fcExchangeIndex].ox_rx_id = CMDfchs->ox_rx_id; // Check the status of the DATA phase of the exchange so we can report // status to the initiator buildFCPstatus(fcChip, *fcExchangeIndex); // set RSP payload fields memcpy(CMDfchs, // re-use same XFER fchs for Response frame &fcChip->SEST->RspHDR[*fcExchangeIndex], sizeof(TachFCHDR_RSP)); break; default: printk("cpqfcTS: don't know how to build FC type: %Xh(%d)\n", type, type); break; } if (!ulStatus) // no errors above? { // FCHS is built; now build IRB // link the just built FCHS (the "command") to the IRB entry // for this Exchange. pIRB = &Exchanges->fcExchange[*fcExchangeIndex].IRB; // len & flags according to command type above pIRB->Req_A_SFS_Len = SfsLen; // includes IRB flags & len pIRB->Req_A_SFS_Addr = // TL needs physical addr of frame to send fcChip->exch_dma_handle + (unsigned long) CMDfchs - (unsigned long) Exchanges; pIRB->Req_A_SFS_D_ID = CMDfchs->d_id << 8; // Dest_ID must be consistent! // Exchange is complete except for "fix-up" fields to be set // at Tachyon Queuing time: // IRB->Req_A_Trans_ID (OX_ID/ RX_ID): // for SEST entry, lower bits correspond to actual FC Exchange ID // fchs->OX_ID or RX_ID } else { #ifdef DBG printk("FC Error: SEST build Pool Allocation failed\n"); #endif // return resources... cpqfcTSCompleteExchange(dev->PciDev, fcChip, *fcExchangeIndex); // SEST build failed } } else // no Exchanges available { ulStatus = SEST_FULL; printk("FC Error: no fcExchanges available\n"); } return ulStatus; } // set RSP payload fields static void buildFCPstatus(PTACHYON fcChip, u32 ExchangeID) { FC_EXCHANGES *Exchanges = fcChip->Exchanges; FC_EXCHANGE *pExchange = &Exchanges->fcExchange[ExchangeID]; // shorthand PFCP_STATUS_RESPONSE pFcpStatus; memset(&fcChip->SEST->RspHDR[ExchangeID].pl, 0, sizeof(FCP_STATUS_RESPONSE)); if (pExchange->status) // something wrong? { pFcpStatus = (PFCP_STATUS_RESPONSE) // cast RSP buffer for this xchng & fcChip->SEST->RspHDR[ExchangeID].pl; if (pExchange->status & COUNT_ERROR) { // set FCP response len valid (so we can report count error) pFcpStatus->fcp_status |= FCP_RSP_LEN_VALID; pFcpStatus->fcp_rsp_len = 0x04000000; // 4 byte len (BIG Endian) pFcpStatus->fcp_rsp_info = FCP_DATA_LEN_NOT_BURST_LEN; // RSP_CODE } } } static dma_addr_t cpqfc_pci_map_sg_page(struct pci_dev *pcidev, u32 * hw_paddr, // where to put phys addr for HW use void *sgp_vaddr, // the virtual address of the sg page dma_addr_t * umap_paddr, // where to put phys addr for unmap unsigned int *maplen, // where to store sg entry length int PairCount) // number of sg pairs used in the page. { unsigned long aligned_addr = (unsigned long) sgp_vaddr; *maplen = PairCount * 8; aligned_addr += TL_EXT_SG_PAGE_BYTELEN; aligned_addr &= ~(TL_EXT_SG_PAGE_BYTELEN - 1); *umap_paddr = pci_map_single(pcidev, (void *) aligned_addr, *maplen, PCI_DMA_TODEVICE); *hw_paddr = (u32) * umap_paddr; # if BITS_PER_LONG > 32 if (*umap_paddr >> 32) { printk("cqpfcTS:Tach SG DMA addr %p>32 bits\n", (void *) umap_paddr); return 0; } # endif return *umap_paddr; } static void cpqfc_undo_SEST_mappings(struct pci_dev *pcidev, unsigned long contigaddr, int len, int dir, struct scatterlist *sgl, int use_sg, PSGPAGES * sgPages_head, int allocated_pages) { PSGPAGES i, next; if (contigaddr != (unsigned long) NULL) pci_unmap_single(pcidev, contigaddr, len, dir); if (sgl != NULL) pci_unmap_sg(pcidev, sgl, use_sg, dir); for (i = *sgPages_head; i != NULL; i = next) { pci_unmap_single(pcidev, i->busaddr, i->maplen, scsi_to_pci_dma_dir(PCI_DMA_TODEVICE)); i->busaddr = (dma_addr_t) NULL; i->maplen = 0L; next = i->next; kfree(i); } *sgPages_head = NULL; } // This routine builds scatter/gather lists into SEST entries // INPUTS: // SESTalPair - SEST address @DWordA "Local Buffer Length" // sgList - Scatter/Gather linked list of Len/Address data buffers // OUTPUT: // sgPairs - number of valid address/length pairs // Remarks: // The SEST data buffer pointers only depend on number of // length/ address pairs, NOT on the type (IWE, TRE,...) // Up to 3 pairs can be referenced in the SEST - more than 3 // require this Extended S/G list page. The page holds 4, 8, 16... // len/addr pairs, per Scatter/Gather List Page Length Reg. // TachLite allows pages to be linked to any depth. //#define DBG_SEST_SGLIST 1 // for printing out S/G pairs with Ext. pages static int ap_hi_water = TL_DANGER_SGPAGES; static u32 build_SEST_sgList(struct pci_dev *pcidev, u32 * SESTalPairStart, // the 3 len/address buffers in SEST Scsi_Cmnd * Cmnd, u32 * sgPairs, PSGPAGES * sgPages_head) // link list of TL Ext. S/G pages from O/S Pool { u32 i, AllocatedPages = 0; // Tach Ext. S/G page allocations u32 *alPair = SESTalPairStart; u32 *ext_sg_page_phys_addr_place = NULL; int PairCount; unsigned long ulBuff, contigaddr; u32 total_data_len = 0; // (in bytes) u32 bytes_to_go = Cmnd->request_bufflen; // total xfer (S/G sum) u32 thisMappingLen; struct scatterlist *sgl = NULL; // S/G list (Linux format) int sg_count, totalsgs; dma_addr_t busaddr; unsigned long thislen, offset; PSGPAGES *sgpage = sgPages_head; PSGPAGES prev_page = NULL; # define WE_HAVE_SG_LIST (sgl != (unsigned long) NULL) contigaddr = (unsigned long) NULL; if (!Cmnd->use_sg) // no S/G list? { if (bytes_to_go <= TL_MAX_SG_ELEM_LEN) { *sgPairs = 1; // use "local" S/G pair in SEST entry // (for now, ignore address bits above #31) *alPair++ = bytes_to_go; // bits 18-0, length if (bytes_to_go != 0) { contigaddr = ulBuff = pci_map_single(pcidev, Cmnd->request_buffer, Cmnd->request_bufflen, scsi_to_pci_dma_dir(Cmnd->sc_data_direction)); // printk("ms %p ", ulBuff); } else { // No data transfer, (e.g.: Test Unit Ready) // printk("btg=0 "); *sgPairs = 0; memset(alPair, 0, sizeof(*alPair)); return 0; } # if BITS_PER_LONG > 32 if (ulBuff >> 32) { printk("FATAL! Tachyon DMA address %p " "exceeds 32 bits\n", (void *) ulBuff); return 0; } # endif *alPair = (u32) ulBuff; return bytes_to_go; } else // We have a single large (too big) contiguous buffer. { // We will have to break it up. We'll use the scatter // gather code way below, but use contigaddr instead // of sg_dma_addr(). (this is a very rare case). unsigned long btg; contigaddr = pci_map_single(pcidev, Cmnd->request_buffer, Cmnd->request_bufflen, scsi_to_pci_dma_dir(Cmnd->sc_data_direction)); // printk("contigaddr = %p, len = %d\n", // (void *) contigaddr, bytes_to_go); totalsgs = 0; for (btg = bytes_to_go; btg > 0;) { btg -= (btg > TL_MAX_SG_ELEM_LEN ? TL_MAX_SG_ELEM_LEN : btg); totalsgs++; } sgl = NULL; *sgPairs = totalsgs; } } else // we do have a scatter gather list { // [TBD - update for Linux to support > 32 bits addressing] // since the format for local & extended S/G lists is different, // check if S/G pairs exceeds 3. // *sgPairs = Cmnd->use_sg; Nope, that's wrong. sgl = (struct scatterlist *) Cmnd->request_buffer; sg_count = pci_map_sg(pcidev, sgl, Cmnd->use_sg, scsi_to_pci_dma_dir(Cmnd->sc_data_direction)); // printk("sgl = %p, sg_count = %d\n", (void *) sgl, sg_count); if (sg_count <= 3) { // we need to be careful here that no individual mapping // is too large, and if any is, that breaking it up // doesn't push us over 3 sgs, or, if it does, that we // handle that case. Tachyon can take 0x7FFFF bits for length, // but sg structure uses "unsigned int", on the face of it, // up to 0xFFFFFFFF or even more. int i; unsigned long thislen; totalsgs = 0; for (i = 0; i < sg_count; i++) { thislen = sg_dma_len(&sgl[i]); while (thislen >= TL_MAX_SG_ELEM_LEN) { totalsgs++; thislen -= TL_MAX_SG_ELEM_LEN; } if (thislen > 0) totalsgs++; } *sgPairs = totalsgs; } else totalsgs = 999; // as a first estimate, definitely >3, // if (totalsgs != sg_count) // printk("totalsgs = %d, sgcount=%d\n",totalsgs,sg_count); } // printk("totalsgs = %d, sgcount=%d\n", totalsgs, sg_count); if (totalsgs <= 3) // can (must) use "local" SEST list { while (bytes_to_go) { offset = 0L; if (WE_HAVE_SG_LIST) thisMappingLen = sg_dma_len(sgl); else // or contiguous buffer? thisMappingLen = bytes_to_go; while (thisMappingLen > 0) { thislen = thisMappingLen > TL_MAX_SG_ELEM_LEN ? TL_MAX_SG_ELEM_LEN : thisMappingLen; bytes_to_go = bytes_to_go - thislen; // we have L/A pair; L = thislen, A = physicalAddress // load into SEST... total_data_len += thislen; *alPair = thislen; // bits 18-0, length alPair++; if (WE_HAVE_SG_LIST) ulBuff = sg_dma_address(sgl) + offset; else ulBuff = contigaddr + offset; offset += thislen; # if BITS_PER_LONG > 32 if (ulBuff >> 32) { printk("cqpfcTS: 2Tach DMA address %p > 32 bits\n", (void *) ulBuff); printk("%s = %p, offset = %ld\n", WE_HAVE_SG_LIST ? "ulBuff" : "contigaddr", WE_HAVE_SG_LIST ? (void *) ulBuff : (void *) contigaddr, offset); return 0; } # endif *alPair++ = (u32) ulBuff; // lower 32 bits (31-0) thisMappingLen -= thislen; } if (WE_HAVE_SG_LIST) ++sgl; // next S/G pair else if (bytes_to_go != 0) printk("BTG not zero!\n"); # ifdef DBG_SEST_SGLIST printk("L=%d ", thisMappingLen); printk("btg=%d ", bytes_to_go); # endif } // printk("i:%d\n", *sgPairs); } else // more than 3 pairs requires Extended S/G page (Pool Allocation) { // clear out SEST DWORDs (local S/G addr) C-F (A-B set in following logic) for (i = 2; i < 6; i++) alPair[i] = 0; PairCount = TL_EXT_SG_PAGE_COUNT; // forces initial page allocation totalsgs = 0; while (bytes_to_go) { // Per SEST format, we can support 524287 byte lengths per // S/G pair. Typical user buffers are 4k, and very rarely // exceed 12k due to fragmentation of physical memory pages. // However, on certain O/S system (not "user") buffers (on platforms // with huge memories), it's possible to exceed this // length in a single S/G address/len mapping, so we have to handle // that. offset = 0L; if (WE_HAVE_SG_LIST) thisMappingLen = sg_dma_len(sgl); else thisMappingLen = bytes_to_go; while (thisMappingLen > 0) { thislen = thisMappingLen > TL_MAX_SG_ELEM_LEN ? TL_MAX_SG_ELEM_LEN : thisMappingLen; // printk("%d/%d/%d\n", thislen, thisMappingLen, bytes_to_go); // should we load into "this" extended S/G page, or allocate // new page? if (PairCount >= TL_EXT_SG_PAGE_COUNT) { // Now, we have to map the previous page, (triggering buffer bounce) // The first time thru the loop, there won't be a previous page. if (prev_page != NULL) // is there a prev page? { // this code is normally kind of hard to trigger, // you have to use up more than 256 scatter gather // elements to get here. Cranking down TL_MAX_SG_ELEM_LEN // to an absurdly low value (128 bytes or so) to artificially // break i/o's into a zillion pieces is how I tested it. busaddr = cpqfc_pci_map_sg_page(pcidev, ext_sg_page_phys_addr_place, prev_page->page, &prev_page->busaddr, &prev_page->maplen, PairCount); } // Allocate the TL Extended S/G list page. We have // to allocate twice what we want to ensure required TL alignment // (Tachlite TL/TS User Man. Rev 6.0, p 168) // We store the original allocated PVOID so we can free later *sgpage = kmalloc(sizeof(SGPAGES), GFP_ATOMIC); if (!*sgpage) { printk("cpqfc: Allocation failed @ %d S/G page allocations\n", AllocatedPages); total_data_len = 0; // failure!! Ext. S/G is All-or-none affair // unmap the previous mappings, if any. cpqfc_undo_SEST_mappings(pcidev, contigaddr, Cmnd->request_bufflen, scsi_to_pci_dma_dir(Cmnd->sc_data_direction), sgl, Cmnd->use_sg, sgPages_head, AllocatedPages + 1); // FIXME: testing shows that if we get here, // it's bad news. (this has been this way for a long // time though, AFAIK. Not that that excuses it.) return 0; // give up (and probably hang the system) } // clear out memory we just allocated memset((*sgpage)->page, 0, TL_EXT_SG_PAGE_BYTELEN * 2); (*sgpage)->next = NULL; (*sgpage)->busaddr = (dma_addr_t) NULL; (*sgpage)->maplen = 0L; // align the memory - TL requires sizeof() Ext. S/G page alignment. // We doubled the actual required size so we could mask off LSBs // to get desired offset ulBuff = (unsigned long) (*sgpage)->page; ulBuff += TL_EXT_SG_PAGE_BYTELEN; ulBuff &= ~(TL_EXT_SG_PAGE_BYTELEN - 1); // set pointer, in SEST if first Ext. S/G page, or in last pair // of linked Ext. S/G pages... (Only 32-bit PVOIDs, so just // load lower 32 bits) // NOTE: the Len field must be '0' if this is the first Ext. S/G // pointer in SEST, and not 0 otherwise (we know thislen != 0). *alPair = (alPair != SESTalPairStart) ? thislen : 0; # ifdef DBG_SEST_SGLIST printk("PairCount %d @%p even %Xh, ", PairCount, alPair, *alPair); # endif // Save the place where we need to store the physical // address of this scatter gather page which we get when we map it // (and mapping we can do only after we fill it in.) alPair++; // next DWORD, will contain phys addr of the ext page ext_sg_page_phys_addr_place = alPair; // Now, set alPair = the virtual addr of the (Extended) S/G page // which will accept the Len/ PhysicalAddress pairs alPair = (u32 *) ulBuff; AllocatedPages++; if (AllocatedPages >= ap_hi_water) { // This message should rarely, if ever, come out. // Previously (cpqfc version <= 2.0.5) the driver would // just puke if more than 4 SG pages were used, and nobody // ever complained about that. This only comes out if // more than 8 pages are used. printk(KERN_WARNING "cpqfc: Possible danger. %d scatter gather pages used.\n" "cpqfc: detected seemingly extreme memory " "fragmentation or huge data transfers.\n", AllocatedPages); ap_hi_water = AllocatedPages + 1; } PairCount = 1; // starting new Ext. S/G page prev_page = (*sgpage); // remember this page, for next time thru sgpage = &((*sgpage)->next); } // end of new TL Ext. S/G page allocation *alPair = thislen; // bits 18-0, length (range check above) # ifdef DBG_SEST_SGLIST printk("PairCount %d @%p, even %Xh, ", PairCount, alPair, *alPair); # endif alPair++; // next DWORD, physical address if (WE_HAVE_SG_LIST) ulBuff = sg_dma_address(sgl) + offset; else ulBuff = contigaddr + offset; offset += thislen; # if BITS_PER_LONG > 32 if (ulBuff >> 32) { printk("cqpfcTS: 1Tach DMA address %p > 32 bits\n", (void *) ulBuff); printk("%s = %p, offset = %ld\n", WE_HAVE_SG_LIST ? "ulBuff" : "contigaddr", WE_HAVE_SG_LIST ? (void *) ulBuff : (void *) contigaddr, offset); return 0; } # endif *alPair = (u32) ulBuff; // lower 32 bits (31-0) # ifdef DBG_SEST_SGLIST printk("odd %Xh\n", *alPair); # endif alPair++; // next DWORD, next address/length pair PairCount++; // next Length/Address pair // if (PairCount > pc_hi_water) // { // printk("pc hi = %d ", PairCount); // pc_hi_water = PairCount; // } bytes_to_go -= thislen; total_data_len += thislen; thisMappingLen -= thislen; totalsgs++; } // while (thisMappingLen > 0) if (WE_HAVE_SG_LIST) sgl++; // next S/G pair } // while (bytes_to_go) // printk("Totalsgs=%d\n", totalsgs); *sgPairs = totalsgs; // PCI map (and bounce) the last (and usually only) extended SG page busaddr = cpqfc_pci_map_sg_page(pcidev, ext_sg_page_phys_addr_place, prev_page->page, &prev_page->busaddr, &prev_page->maplen, PairCount); } return total_data_len; } // The Tachlite SEST table is referenced to OX_ID (or RX_ID). To optimize // performance and debuggability, we index the Exchange structure to FC X_ID // This enables us to build exchanges for later en-queing to Tachyon, // provided we have an open X_ID slot. At Tachyon queing time, we only // need an ERQ slot; then "fix-up" references in the // IRB, FCHS, etc. as needed. // RETURNS: // 0 if successful // non-zero on error //sstartex u32 cpqfcTSStartExchange(CPQFCHBA * dev, s32 ExchangeID) { PTACHYON fcChip = &dev->fcChip; FC_EXCHANGES *Exchanges = fcChip->Exchanges; FC_EXCHANGE *pExchange = &Exchanges->fcExchange[ExchangeID]; // shorthand u16 producer, consumer; u32 ulStatus = 0; short int ErqIndex; u8 CompleteExchange = 0; // e.g. ACC replies are complete u8 SestType = 0; u32 InboundData = 0; // We will manipulate Tachlite chip registers here to successfully // start exchanges. // Check that link is not down -- we can't start an exchange on a // down link! if (fcChip->Registers.FMstatus.value & 0x80) // LPSM offline? { printk("fcStartExchange: PSM offline (%Xh), x_ID %Xh, type %Xh, port_id %Xh\n", fcChip->Registers.FMstatus.value & 0xFF, ExchangeID, pExchange->type, pExchange->fchs.d_id); if (ExchangeID >= TACH_SEST_LEN) // Link Service Outbound frame? { // Our most popular LinkService commands are port discovery types // (PLOGI/ PDISC...), which are implicitly nullified by Link Down // events, so it makes no sense to Que them. However, ABTS should // be queued, since exchange sequences are likely destroyed by // Link Down events, and we want to notify other ports of broken // sequences by aborting the corresponding exchanges. if (pExchange->type != BLS_ABTS) { ulStatus = LNKDWN_OSLS; goto Done; // don't Que most LinkServ exchanges on LINK DOWN } } printk("fcStartExchange: Que x_ID %Xh, type %Xh\n", ExchangeID, pExchange->type); pExchange->status |= EXCHANGE_QUEUED; ulStatus = EXCHANGE_QUEUED; goto Done; } // Make sure ERQ has available space. producer = (u16) fcChip->ERQ->producerIndex; // copies for logical arith. consumer = (u16) fcChip->ERQ->consumerIndex; producer++; // We are testing for full que by incrementing if (producer >= ERQ_LEN) // rollover condition? producer = 0; if (consumer != producer) // ERQ not full? { // ****************** Need Atomic access to chip registers!!******** // remember ERQ PI for copying IRB ErqIndex = (u16) fcChip->ERQ->producerIndex; fcChip->ERQ->producerIndex = producer; // this is written to Tachyon // we have an ERQ slot! If SCSI command, need SEST slot // otherwise we are done. // Note that Tachyon requires that bit 15 of the OX_ID or RX_ID be // set according to direction of data to/from Tachyon for SEST assists. // For consistency, enforce this rule for Link Service (non-SEST) // exchanges as well. // fix-up the X_ID field in IRB pExchange->IRB.Req_A_Trans_ID = ExchangeID & 0x7FFF; // 15-bit field // fix-up the X_ID field in fchs -- depends on Originator or Responder, // outgoing or incoming data? switch (pExchange->type) { // ORIGINATOR types... we're setting our OX_ID and // defaulting the responder's RX_ID to 0xFFFF case SCSI_IRE: // Requirement: set MSB of x_ID for Incoming TL data // (see "Tachyon TL/TS User's Manual", Rev 6.0, Sept.'98, pg. 50) InboundData = 0x8000; case SCSI_IWE: SestType = 1; pExchange->fchs.ox_rx_id = (ExchangeID | InboundData); pExchange->fchs.ox_rx_id <<= 16; // MSW shift pExchange->fchs.ox_rx_id |= 0xffff; // add default RX_ID // now fix-up the Data HDR OX_ID (TL automatically does rx_id) // (not necessary for IRE -- data buffer unused) if (pExchange->type == SCSI_IWE) { fcChip->SEST->DataHDR[ExchangeID].ox_rx_id = pExchange->fchs.ox_rx_id; } break; case FCS_NSR: // ext. link service Name Service Request case ELS_SCR: // ext. link service State Change Registration case ELS_FDISC: // ext. link service login case ELS_FLOGI: // ext. link service login case ELS_LOGO: // FC-PH extended link service logout case BLS_NOP: // Basic link service No OPeration case ELS_PLOGI: // ext. link service login (PLOGI) case ELS_PDISC: // ext. link service login (PDISC) case ELS_PRLI: // ext. link service process login pExchange->fchs.ox_rx_id = ExchangeID; pExchange->fchs.ox_rx_id <<= 16; // MSW shift pExchange->fchs.ox_rx_id |= 0xffff; // and RX_ID break; // RESPONDER types... we must set our RX_ID while preserving // sender's OX_ID // outgoing (or no) data case ELS_RJT: // extended link service reject case ELS_LOGO_ACC: // FC-PH extended link service logout accept case ELS_ACC: // ext. generic link service accept case ELS_PLOGI_ACC: // ext. link service login accept (PLOGI or PDISC) case ELS_PRLI_ACC: // ext. link service process login accept CompleteExchange = 1; // Reply (ACC or RJT) is end of exchange pExchange->fchs.ox_rx_id |= (ExchangeID & 0xFFFF); break; // since we are a Responder, OX_ID should already be set by // cpqfcTSBuildExchange(). We need to -OR- in RX_ID case SCSI_TWE: SestType = 1; // Requirement: set MSB of x_ID for Incoming TL data // (see "Tachyon TL/TS User's Manual", Rev 6.0, Sept.'98, pg. 50) pExchange->fchs.ox_rx_id &= 0xFFFF0000; // clear RX_ID // Requirement: set MSB of RX_ID for Incoming TL data // (see "Tachyon TL/TS User's Manual", Rev 6.0, Sept.'98, pg. 50) pExchange->fchs.ox_rx_id |= (ExchangeID | 0x8000); break; case SCSI_TRE: SestType = 1; // there is no XRDY for SEST target read; the data // header needs to be updated. Also update the RSP // exchange IDs for the status frame, in case it is sent automatically fcChip->SEST->DataHDR[ExchangeID].ox_rx_id |= ExchangeID; fcChip->SEST->RspHDR[ExchangeID].ox_rx_id = fcChip->SEST->DataHDR[ExchangeID].ox_rx_id; // for easier FCP response logic (works for TWE and TRE), // copy exchange IDs. (Not needed if TRE 'RSP' bit set) pExchange->fchs.ox_rx_id = fcChip->SEST->DataHDR[ExchangeID].ox_rx_id; break; case FCP_RESPONSE: // using existing OX_ID/ RX_ID pair, // start SFS FCP-RESPONSE frame // OX/RX_ID should already be set! (See "fcBuild" above) CompleteExchange = 1; // RSP is end of FCP-SCSI exchange break; case BLS_ABTS_RJT: // uses new RX_ID, since SEST x_ID non-existent case BLS_ABTS_ACC: // using existing OX_ID/ RX_ID pair from SEST entry CompleteExchange = 1; // ACC or RJT marks end of FCP-SCSI exchange case BLS_ABTS: // using existing OX_ID/ RX_ID pair from SEST entry break; default: printk("Error on fcStartExchange: undefined type %Xh(%d)\n", pExchange->type, pExchange->type); return INVALID_ARGS; } // X_ID fields are entered -- copy IRB to Tachyon's ERQ memcpy(&fcChip->ERQ->QEntry[ErqIndex], // dest. &pExchange->IRB, 32); // fixed (hardware) length! PCI_TRACEO(ExchangeID, 0xA0) // ACTION! May generate INT and IMQ entry writel(fcChip->ERQ->producerIndex, fcChip->Registers.ERQproducerIndex.address); if (ExchangeID >= TACH_SEST_LEN) // Link Service Outbound frame? { // wait for completion! (TDB -- timeout and chip reset) PCI_TRACEO(ExchangeID, 0xA4) enable_irq(dev->HostAdapter->irq); // only way to get Sem. down_interruptible(dev->TYOBcomplete); disable_irq(dev->HostAdapter->irq); PCI_TRACE(0xA4) // On login exchanges, BAD_ALPA (non-existent port_id) results in // FTO (Frame Time Out) on the Outbound Completion message. // If we got an FTO status, complete the exchange (free up slot) if (CompleteExchange || // flag from Reply frames pExchange->status) // typically, can get FRAME_TO { cpqfcTSCompleteExchange(dev->PciDev, fcChip, ExchangeID); } } else // SEST Exchange { ulStatus = 0; // ship & pray success (e.g. FCP-SCSI) if (CompleteExchange) // by Type of exchange (e.g. end-of-xchng) { cpqfcTSCompleteExchange(dev->PciDev, fcChip, ExchangeID); } else pExchange->status &= ~EXCHANGE_QUEUED; // clear ExchangeQueued flag } } else // ERQ 'producer' = 'consumer' and QUE is full { ulStatus = OUTQUE_FULL; // Outbound (ERQ) Que full } Done: PCI_TRACE(0xA0) return ulStatus; } // Scan fcController->fcExchanges array for a usuable index (a "free" // exchange). // Inputs: // fcChip - pointer to TachLite chip structure // Return: // index - exchange array element where exchange can be built // -1 - exchange array is full // REMARKS: // Although this is a (yuk!) linear search, we presume // that the system will complete exchanges about as quickly as // they are submitted. A full Exchange array (and hence, max linear // search time for free exchange slot) almost guarantees a Fibre problem // of some sort. // In the interest of making exchanges easier to debug, we want a LRU // (Least Recently Used) scheme. static s32 FindFreeExchange(PTACHYON fcChip, u32 type) { FC_EXCHANGES *Exchanges = fcChip->Exchanges; u32 i; u32 ulStatus = -1; // assume failure if (type == SCSI_IRE || type == SCSI_TRE || type == SCSI_IWE || type == SCSI_TWE) { // SCSI type - X_IDs should be from 0 to TACH_SEST_LEN-1 if (fcChip->fcSestExchangeLRU >= TACH_SEST_LEN) // rollover? fcChip->fcSestExchangeLRU = 0; i = fcChip->fcSestExchangeLRU; // typically it's already free! if (Exchanges->fcExchange[i].type == 0) // check for "free" element { ulStatus = 0; // success! } else { // YUK! we need to do a linear search for free element. // Fragmentation of the fcExchange array is due to excessively // long completions or timeouts. while (1) { if (++i >= TACH_SEST_LEN) // rollover check i = 0; // beginning of SEST X_IDs // printk( "looping for SCSI xchng ID: i=%d, type=%Xh\n", // i, Exchanges->fcExchange[i].type); if (Exchanges->fcExchange[i].type == 0) // "free"? { ulStatus = 0; // success! break; } if (i == fcChip->fcSestExchangeLRU) // wrapped-around array? { printk("SEST X_ID space full\n"); break; // failed - prevent inf. loop } } } fcChip->fcSestExchangeLRU = i + 1; // next! (rollover check next pass) } else // Link Service type - X_IDs should be from TACH_SEST_LEN // to TACH_MAX_XID { if (fcChip->fcLsExchangeLRU >= TACH_MAX_XID || // range check fcChip->fcLsExchangeLRU < TACH_SEST_LEN) // (e.g. startup) fcChip->fcLsExchangeLRU = TACH_SEST_LEN; i = fcChip->fcLsExchangeLRU; // typically it's already free! if (Exchanges->fcExchange[i].type == 0) // check for "free" element { ulStatus = 0; // success! } else { // YUK! we need to do a linear search for free element // Fragmentation of the fcExchange array is due to excessively // long completions or timeouts. while (1) { if (++i >= TACH_MAX_XID) // rollover check i = TACH_SEST_LEN; // beginning of Link Service X_IDs // printk( "looping for xchng ID: i=%d, type=%Xh\n", // i, Exchanges->fcExchange[i].type); if (Exchanges->fcExchange[i].type == 0) // "free"? { ulStatus = 0; // success! break; } if (i == fcChip->fcLsExchangeLRU) // wrapped-around array? { printk("LinkService X_ID space full\n"); break; // failed - prevent inf. loop } } } fcChip->fcLsExchangeLRU = i + 1; // next! (rollover check next pass) } if (!ulStatus) // success? Exchanges->fcExchange[i].type = type; // allocate it. else i = -1; // error - all exchanges "open" return i; } static void cpqfc_pci_unmap_extended_sg(struct pci_dev *pcidev, PTACHYON fcChip, u32 x_ID) { // Unmaps the memory regions used to hold the scatter gather lists PSGPAGES i; // Were there any such regions needing unmapping? if (!USES_EXTENDED_SGLIST(fcChip->SEST, x_ID)) return; // No such regions, we're outta here. // for each extended scatter gather region needing unmapping... for (i = fcChip->SEST->sgPages[x_ID]; i != NULL; i = i->next) pci_unmap_single(pcidev, i->busaddr, i->maplen, scsi_to_pci_dma_dir(PCI_DMA_TODEVICE)); } // Called also from cpqfcTScontrol.o, so can't be static void cpqfc_pci_unmap(struct pci_dev *pcidev, Scsi_Cmnd * cmd, PTACHYON fcChip, u32 x_ID) { // Undo the DMA mappings if (cmd->use_sg) { // Used scatter gather list for data buffer? cpqfc_pci_unmap_extended_sg(pcidev, fcChip, x_ID); pci_unmap_sg(pcidev, cmd->buffer, cmd->use_sg, scsi_to_pci_dma_dir(cmd->sc_data_direction)); // printk("umsg %d\n", cmd->use_sg); } else if (cmd->request_bufflen) { // printk("ums %p ", fcChip->SEST->u[ x_ID ].IWE.GAddr1); pci_unmap_single(pcidev, fcChip->SEST->u[x_ID].IWE.GAddr1, cmd->request_bufflen, scsi_to_pci_dma_dir(cmd->sc_data_direction)); } } // We call this routine to free an Exchange for any reason: // completed successfully, completed with error, aborted, etc. // returns 0 if Exchange failed and "retry" is acceptable // returns 1 if Exchange was successful, or retry is impossible // (e.g. port/device gone). //scompleteexchange void cpqfcTSCompleteExchange(struct pci_dev *pcidev, PTACHYON fcChip, u32 x_ID) { FC_EXCHANGES *Exchanges = fcChip->Exchanges; int already_unmapped = 0; if (x_ID < TACH_SEST_LEN) // SEST-based (or LinkServ for FCP exchange) { if (Exchanges->fcExchange[x_ID].Cmnd == NULL) // what#@! { // TriggerHBA( fcChip->Registers.ReMapMemBase, 0); printk(" x_ID %Xh, type %Xh, NULL ptr!\n", x_ID, Exchanges->fcExchange[x_ID].type); goto CleanUpSestResources; // this path should be very rare. } // we have Linux Scsi Cmnd ptr..., now check our Exchange status // to decide how to complete this SEST FCP exchange if (Exchanges->fcExchange[x_ID].status) // perhaps a Tach indicated problem, // or abnormal exchange completion { // set FCP Link statistics if (Exchanges->fcExchange[x_ID].status & FC2_TIMEOUT) fcChip->fcStats.timeouts++; if (Exchanges->fcExchange[x_ID].status & INITIATOR_ABORT) fcChip->fcStats.FC4aborted++; if (Exchanges->fcExchange[x_ID].status & COUNT_ERROR) fcChip->fcStats.CntErrors++; if (Exchanges->fcExchange[x_ID].status & LINKFAIL_TX) fcChip->fcStats.linkFailTX++; if (Exchanges->fcExchange[x_ID].status & LINKFAIL_RX) fcChip->fcStats.linkFailRX++; if (Exchanges->fcExchange[x_ID].status & OVERFLOW) fcChip->fcStats.CntErrors++; // First, see if the Scsi upper level initiated an ABORT on this // exchange... if (Exchanges->fcExchange[x_ID].status == INITIATOR_ABORT) { printk(" DID_ABORT, x_ID %Xh, Cmnd %p ", x_ID, Exchanges->fcExchange[x_ID].Cmnd); goto CleanUpSestResources; // (we don't expect Linux _aborts) } // Did our driver timeout the Exchange, or did Tachyon indicate // a failure during transmission? Ask for retry with "SOFT_ERROR" else if (Exchanges->fcExchange[x_ID].status & FC2_TIMEOUT) { // printk("result DID_SOFT_ERROR, x_ID %Xh, Cmnd %p\n", // x_ID, Exchanges->fcExchange[ x_ID ].Cmnd); Exchanges->fcExchange[x_ID].Cmnd->result = (DID_SOFT_ERROR << 16); } // Did frame(s) for an open exchange arrive in the SFQ, // meaning the SEST was unable to process them? else if (Exchanges->fcExchange[x_ID].status & SFQ_FRAME) { // printk("result DID_SOFT_ERROR, x_ID %Xh, Cmnd %p\n", // x_ID, Exchanges->fcExchange[ x_ID ].Cmnd); Exchanges->fcExchange[x_ID].Cmnd->result = (DID_SOFT_ERROR << 16); } // Did our driver timeout the Exchange, or did Tachyon indicate // a failure during transmission? Ask for retry with "SOFT_ERROR" else if ((Exchanges->fcExchange[x_ID].status & LINKFAIL_TX) || (Exchanges->fcExchange[x_ID].status & PORTID_CHANGED) || (Exchanges->fcExchange[x_ID].status & FRAME_TO) || (Exchanges->fcExchange[x_ID].status & INV_ENTRY) || (Exchanges->fcExchange[x_ID].status & ABORTSEQ_NOTIFY)) { // printk("result DID_SOFT_ERROR, x_ID %Xh, Cmnd %p\n", // x_ID, Exchanges->fcExchange[ x_ID ].Cmnd); Exchanges->fcExchange[x_ID].Cmnd->result = (DID_SOFT_ERROR << 16); } // e.g., a LOGOut happened, or device never logged back in. else if (Exchanges->fcExchange[x_ID].status & DEVICE_REMOVED) { // printk(" *LOGOut or timeout on login!* "); // trigger? // TriggerHBA( fcChip->Registers.ReMapMemBase, 0); Exchanges->fcExchange[x_ID].Cmnd->result = (DID_BAD_TARGET << 16); } // Did Tachyon indicate a CNT error? We need further analysis // to determine if the exchange is acceptable else if (Exchanges->fcExchange[x_ID].status == COUNT_ERROR) { u8 ScsiStatus; FCP_STATUS_RESPONSE *pFcpStatus = (PFCP_STATUS_RESPONSE) & fcChip->SEST->RspHDR[x_ID].pl; ScsiStatus = pFcpStatus->fcp_status >> 24; // If the command is a SCSI Read/Write type, we don't tolerate // count errors of any kind; assume the count error is due to // a dropped frame and ask for retry... if (((Exchanges->fcExchange[x_ID].Cmnd->cmnd[0] == 0x8) || (Exchanges->fcExchange[x_ID].Cmnd->cmnd[0] == 0x28) || (Exchanges->fcExchange[x_ID].Cmnd->cmnd[0] == 0xA) || (Exchanges->fcExchange[x_ID].Cmnd->cmnd[0] == 0x2A)) && ScsiStatus == 0) { // ask for retry // printk("COUNT_ERROR retry, x_ID %Xh, status %Xh, Cmnd %p\n", // x_ID, Exchanges->fcExchange[ x_ID ].status, // Exchanges->fcExchange[ x_ID ].Cmnd); Exchanges->fcExchange[x_ID].Cmnd->result = (DID_SOFT_ERROR << 16); } else // need more analysis { cpqfcTSCheckandSnoopFCP(fcChip, x_ID); // (will set ->result) } } // default: NOTE! We don't ever want to get here. Getting here // implies something new is happening that we've never had a test // case for. Need code maintenance! Return "ERROR" else { unsigned int stat = Exchanges->fcExchange[x_ID].status; printk("DEFAULT result %Xh, x_ID %Xh, Cmnd %p", Exchanges->fcExchange[x_ID].status, x_ID, Exchanges->fcExchange[x_ID].Cmnd); if (stat & INVALID_ARGS) printk(" INVALID_ARGS "); if (stat & LNKDWN_OSLS) printk(" LNKDWN_OSLS "); if (stat & LNKDWN_LASER) printk(" LNKDWN_LASER "); if (stat & OUTQUE_FULL) printk(" OUTQUE_FULL "); if (stat & DRIVERQ_FULL) printk(" DRIVERQ_FULL "); if (stat & SEST_FULL) printk(" SEST_FULL "); if (stat & BAD_ALPA) printk(" BAD_ALPA "); if (stat & OVERFLOW) printk(" OVERFLOW "); if (stat & COUNT_ERROR) printk(" COUNT_ERROR "); if (stat & LINKFAIL_RX) printk(" LINKFAIL_RX "); if (stat & ABORTSEQ_NOTIFY) printk(" ABORTSEQ_NOTIFY "); if (stat & LINKFAIL_TX) printk(" LINKFAIL_TX "); if (stat & HOSTPROG_ERR) printk(" HOSTPROG_ERR "); if (stat & FRAME_TO) printk(" FRAME_TO "); if (stat & INV_ENTRY) printk(" INV_ENTRY "); if (stat & SESTPROG_ERR) printk(" SESTPROG_ERR "); if (stat & OUTBOUND_TIMEOUT) printk(" OUTBOUND_TIMEOUT "); if (stat & INITIATOR_ABORT) printk(" INITIATOR_ABORT "); if (stat & MEMPOOL_FAIL) printk(" MEMPOOL_FAIL "); if (stat & FC2_TIMEOUT) printk(" FC2_TIMEOUT "); if (stat & TARGET_ABORT) printk(" TARGET_ABORT "); if (stat & EXCHANGE_QUEUED) printk(" EXCHANGE_QUEUED "); if (stat & PORTID_CHANGED) printk(" PORTID_CHANGED "); if (stat & DEVICE_REMOVED) printk(" DEVICE_REMOVED "); if (stat & SFQ_FRAME) printk(" SFQ_FRAME "); printk("\n"); Exchanges->fcExchange[x_ID].Cmnd->result = (DID_ERROR << 16); } } else // definitely no Tach problem, but perhaps an FCP problem { // set FCP Link statistic fcChip->fcStats.ok++; cpqfcTSCheckandSnoopFCP(fcChip, x_ID); // (will set ->result) } cpqfc_pci_unmap(pcidev, Exchanges->fcExchange[x_ID].Cmnd, fcChip, x_ID); // undo DMA mappings. already_unmapped = 1; // OK, we've set the Scsi "->result" field, so proceed with calling // Linux Scsi "done" (if not NULL), and free any kernel memory we // may have allocated for the exchange. PCI_TRACEO((u32) Exchanges->fcExchange[x_ID].Cmnd, 0xAC); // complete the command back to upper Scsi drivers if (Exchanges->fcExchange[x_ID].Cmnd->scsi_done != NULL) { // Calling "done" on an Linux _abort() aborted // Cmnd causes a kernel panic trying to re-free mem. // Actually, we shouldn't do anything with an _abort CMND if (Exchanges->fcExchange[x_ID].Cmnd->result != (DID_ABORT << 16)) { PCI_TRACE(0xAC) call_scsi_done(Exchanges->fcExchange[x_ID].Cmnd); } else { Exchanges->fcExchange[x_ID].Cmnd->SCp.sent_command = 0; // printk(" not calling scsi_done on x_ID %Xh, Cmnd %p\n", // x_ID, Exchanges->fcExchange[ x_ID ].Cmnd); } } else { Exchanges->fcExchange[x_ID].Cmnd->SCp.sent_command = 0; printk(" x_ID %Xh, type %Xh, Cdb0 %Xh\n", x_ID, Exchanges->fcExchange[x_ID].type, Exchanges->fcExchange[x_ID].Cmnd->cmnd[0]); printk(" cpqfcTS: Null scsi_done function pointer!\n"); } // Now, clean up non-Scsi_Cmnd items... CleanUpSestResources: if (!already_unmapped) cpqfc_pci_unmap(pcidev, Exchanges->fcExchange[x_ID].Cmnd, fcChip, x_ID); // undo DMA mappings. // Was an Extended Scatter/Gather page allocated? We know // this by checking DWORD 4, bit 31 ("LOC") of SEST entry if (!(fcChip->SEST->u[x_ID].IWE.Buff_Off & 0x80000000)) { PSGPAGES p, next; // extended S/G list was used -- Free the allocated ext. S/G pages for (p = fcChip->SEST->sgPages[x_ID]; p != NULL; p = next) { next = p->next; kfree(p); } fcChip->SEST->sgPages[x_ID] = NULL; } Exchanges->fcExchange[x_ID].Cmnd = NULL; } // Done with FCP (SEST) exchanges // the remaining logic is common to ALL Exchanges: // FCP(SEST) and LinkServ. Exchanges->fcExchange[x_ID].type = 0; // there -- FREE! Exchanges->fcExchange[x_ID].status = 0; PCI_TRACEO(x_ID, 0xAC) } // (END of CompleteExchange function) // Unfortunately, we must snoop all command completions in // order to manipulate certain return fields, and take note of // device types, etc., to facilitate the Fibre-Channel to SCSI // "mapping". // (Watch for BIG Endian confusion on some payload fields) void cpqfcTSCheckandSnoopFCP(PTACHYON fcChip, u32 x_ID) { FC_EXCHANGES *Exchanges = fcChip->Exchanges; Scsi_Cmnd *Cmnd = Exchanges->fcExchange[x_ID].Cmnd; FCP_STATUS_RESPONSE *pFcpStatus = (PFCP_STATUS_RESPONSE) & fcChip->SEST->RspHDR[x_ID].pl; u8 ScsiStatus; ScsiStatus = pFcpStatus->fcp_status >> 24; #ifdef FCP_COMPLETION_DBG printk("ScsiStatus = 0x%X\n", ScsiStatus); #endif // First, check FCP status if (pFcpStatus->fcp_status & FCP_RSP_LEN_VALID) { // check response code (RSP_CODE) -- most popular is bad len // 1st 4 bytes of rsp info -- only byte 3 interesting if (pFcpStatus->fcp_rsp_info & FCP_DATA_LEN_NOT_BURST_LEN) { // do we EVER get here? printk("cpqfcTS: FCP data len not burst len, x_ID %Xh\n", x_ID); } } // for now, go by the ScsiStatus, and manipulate certain // commands when necessary... if (ScsiStatus == 0) // SCSI status byte "good"? { Cmnd->result = 0; // everything's OK if ((Cmnd->cmnd[0] == INQUIRY)) { u8 *InquiryData = Cmnd->request_buffer; PFC_LOGGEDIN_PORT pLoggedInPort; // We need to manipulate INQUIRY // strings for COMPAQ RAID controllers to force // Linux to scan additional LUNs. Namely, set // the Inquiry string byte 2 (ANSI-approved version) // to 2. if (!memcmp(&InquiryData[8], "COMPAQ", 6)) { InquiryData[2] = 0x2; // claim SCSI-2 compliance, // so multiple LUNs may be scanned. // (no SCSI-2 problems known in CPQ) } // snoop the Inquiry to detect Disk, Tape, etc. type // (search linked list for the port_id we sent INQUIRY to) pLoggedInPort = fcFindLoggedInPort(fcChip, NULL, // DON'T search Scsi Nexus (we will set it) Exchanges->fcExchange[x_ID].fchs.d_id & 0xFFFFFF, NULL, // DON'T search linked list for FC WWN NULL); // DON'T care about end of list if (pLoggedInPort) { pLoggedInPort->ScsiNexus.InqDeviceType = InquiryData[0]; } else { printk("cpqfcTS: can't find LoggedIn FC port %06X for INQUIRY\n", Exchanges->fcExchange[x_ID].fchs.d_id & 0xFFFFFF); } } } // Scsi Status not good -- pass it back to caller else { Cmnd->result = ScsiStatus; // SCSI status byte is 1st // check for valid "sense" data if (pFcpStatus->fcp_status & FCP_SNS_LEN_VALID) { // limit Scsi Sense field length! int SenseLen = pFcpStatus->fcp_sns_len >> 24; // (BigEndian) lower byte SenseLen = SenseLen > sizeof(Cmnd->sense_buffer) ? sizeof(Cmnd->sense_buffer) : SenseLen; #ifdef FCP_COMPLETION_DBG printk("copy sense_buffer %p, len %d, result %Xh\n", Cmnd->sense_buffer, SenseLen, Cmnd->result); #endif // NOTE: There is some dispute over the FCP response // format. Most FC devices assume that FCP_RSP_INFO // is 8 bytes long, in spite of the fact that FCP_RSP_LEN // is (virtually) always 0 and the field is "invalid". // Some other devices assume that // the FCP_SNS_INFO begins after FCP_RSP_LEN bytes (i.e. 0) // when the FCP_RSP is invalid (this almost appears to be // one of those "religious" issues). // Consequently, we test the usual position of FCP_SNS_INFO // for 7Xh, since the SCSI sense format says the first // byte ("error code") should be 0x70 or 0x71. In practice, // we find that every device does in fact have 0x70 or 0x71 // in the first byte position, so this test works for all // FC devices. // (This logic is especially effective for the CPQ/DEC HSG80 // & HSG60 controllers). if ((pFcpStatus->fcp_sns_info[0] & 0x70) == 0x70) memcpy(Cmnd->sense_buffer, &pFcpStatus->fcp_sns_info[0], SenseLen); else { unsigned char *sbPtr = (unsigned char *) &pFcpStatus->fcp_sns_info[0]; sbPtr -= 8; // back up 8 bytes hoping to find the // start of the sense buffer memcpy(Cmnd->sense_buffer, sbPtr, SenseLen); } // in the special case of Device Reset, tell upper layer // to immediately retry (with SOFT_ERROR status) // look for Sense Key Unit Attention (0x6) with ASC Device // Reset (0x29) // printk("SenseLen %d, Key = 0x%X, ASC = 0x%X\n", // SenseLen, Cmnd->sense_buffer[2], // Cmnd->sense_buffer[12]); if (((Cmnd->sense_buffer[2] & 0xF) == 0x6) && (Cmnd->sense_buffer[12] == 0x29)) // Sense Code "reset" { Cmnd->result |= (DID_SOFT_ERROR << 16); // "Host" status byte 3rd } // check for SenseKey "HARDWARE ERROR", ASC InternalTargetFailure else if (((Cmnd->sense_buffer[2] & 0xF) == 0x4) && // "hardware error" (Cmnd->sense_buffer[12] == 0x44)) // Addtl. Sense Code { // printk("HARDWARE_ERROR, Channel/Target/Lun %d/%d/%d\n", // Cmnd->channel, Cmnd->target, Cmnd->lun); Cmnd->result |= (DID_ERROR << 16); // "Host" status byte 3rd } } // (end of sense len valid) // there is no sense data to help out Linux's Scsi layers... // We'll just return the Scsi status and hope he will "do the // right thing" else { // as far as we know, the Scsi status is sufficient Cmnd->result |= (DID_OK << 16); // "Host" status byte 3rd } } } //PPPPPPPPPPPPPPPPPPPPPPPPP PAYLOAD PPPPPPPPP // build data PAYLOAD; SCSI FCP_CMND I.U. // remember BIG ENDIAN payload - DWord values must be byte-reversed // (hence the affinity for byte pointer building). static int build_FCP_payload(Scsi_Cmnd * Cmnd, u8 * payload, u32 type, u32 fcp_dl) { int i; switch (type) { case SCSI_IWE: case SCSI_IRE: // 8 bytes FCP_LUN // Peripheral Device or Volume Set addressing, and LUN mapping // When the FC port was looked up, we copied address mode // and any LUN mask to the scratch pad SCp.phase & .mode *payload++ = (u8) Cmnd->SCp.phase; // Now, because of "lun masking" // (aka selective storage presentation), // the contiguous Linux Scsi lun number may not match the // device's lun number, so we may have to "map". *payload++ = (u8) Cmnd->SCp.have_data_in; // We don't know of anyone in the FC business using these // extra "levels" of addressing. In fact, confusion still exists // just using the FIRST level... ;-) *payload++ = 0; // 2nd level addressing *payload++ = 0; *payload++ = 0; // 3rd level addressing *payload++ = 0; *payload++ = 0; // 4th level addressing *payload++ = 0; // 4 bytes Control Field FCP_CNTL *payload++ = 0; // byte 0: (MSB) reserved *payload++ = 0; // byte 1: task codes // byte 2: task management flags // another "use" of the spare field to accomplish TDR // note combination needed if ((Cmnd->cmnd[0] == RELEASE) && (Cmnd->SCp.buffers_residual == FCP_TARGET_RESET)) { Cmnd->cmnd[0] = 0; // issue "Test Unit Ready" for TDR *payload++ = 0x20; // target device reset bit } else *payload++ = 0; // no TDR // byte 3: (LSB) execution management codes // bit 0 write, bit 1 read (don't set together) if (fcp_dl != 0) { if (type == SCSI_IWE) // WRITE *payload++ = 1; else // READ *payload++ = 2; } else { // On some devices, if RD or WR bits are set, // and fcp_dl is 0, they will generate an error on the command. // (i.e., if direction is specified, they insist on a length). *payload++ = 0; // no data (necessary for CPQ) } // NOTE: clean this up if/when MAX_COMMAND_SIZE is increased to 16 // FCP_CDB allows 16 byte SCSI command descriptor blk; // Linux SCSI CDB array is MAX_COMMAND_SIZE (12 at this time...) for (i = 0; (i < Cmnd->cmd_len) && i < MAX_COMMAND_SIZE; i++) *payload++ = Cmnd->cmnd[i]; if (Cmnd->cmd_len == 16) { memcpy(payload, &Cmnd->SCp.buffers_residual, 4); } payload += (16 - i); // FCP_DL is largest number of expected data bytes // per CDB (i.e. read/write command) *payload++ = (u8) (fcp_dl >> 24); // (MSB) 8 bytes data len FCP_DL *payload++ = (u8) (fcp_dl >> 16); *payload++ = (u8) (fcp_dl >> 8); *payload++ = (u8) fcp_dl; // (LSB) break; case SCSI_TWE: // need FCP_XFER_RDY *payload++ = 0; // (4 bytes) DATA_RO (MSB byte 0) *payload++ = 0; *payload++ = 0; *payload++ = 0; // LSB (byte 3) // (4 bytes) BURST_LEN // size of following FCP_DATA payload *payload++ = (u8) (fcp_dl >> 24); // (MSB) 8 bytes data len FCP_DL *payload++ = (u8) (fcp_dl >> 16); *payload++ = (u8) (fcp_dl >> 8); *payload++ = (u8) fcp_dl; // (LSB) // 4 bytes RESERVED *payload++ = 0; *payload++ = 0; *payload++ = 0; *payload++ = 0; break; default: break; } return 0; }
Go to most recent revision | Compare with Previous | Blame | View Log