| /* 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/blkdev.h> |
| #include <linux/interrupt.h> |
| #include <linux/delay.h> |
| #include <linux/smp_lock.h> |
| #include <linux/pci.h> |
| |
| #define SHUTDOWN_SIGS (sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM)) |
| |
| #include <asm/system.h> |
| #include <asm/irq.h> |
| #include <asm/dma.h> |
| |
| #include "scsi.h" |
| #include <scsi/scsi_host.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 accommodate 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, |
| BOOLEAN PDisc, |
| BOOLEAN Originator); |
| |
| static void AnalyzeIncomingFrame( |
| CPQFCHBA *cpqfcHBAdata, |
| ULONG QNdx ); |
| |
| static void SendLogins( CPQFCHBA *cpqfcHBAdata, __u32 *FabricPortIds ); |
| |
| static int verify_PLOGI( PTACHYON fcChip, |
| TachFCHDR_GCMND* fchs, ULONG* reject_explain); |
| static int verify_PRLI( TachFCHDR_GCMND* fchs, ULONG* reject_explain); |
| |
| static void LoadWWN( PTACHYON fcChip, UCHAR* dest, UCHAR type); |
| static void BuildLinkServicePayload( |
| PTACHYON fcChip, ULONG type, void* payload); |
| |
| static void UnblockScsiDevice( struct Scsi_Host *HostAdapter, |
| PFC_LOGGEDIN_PORT pLoggedInPort); |
| |
| static void cpqfcTSCheckandSnoopFCP( PTACHYON fcChip, ULONG x_ID); |
| |
| static void CompleteBoardLockCmnd( CPQFCHBA *cpqfcHBAdata); |
| |
| static void RevalidateSEST( struct Scsi_Host *HostAdapter, |
| PFC_LOGGEDIN_PORT pLoggedInPort); |
| |
| static void IssueReportLunsCommand( |
| CPQFCHBA* cpqfcHBAdata, |
| TachFCHDR_GCMND* fchs); |
| |
| // (see scsi_error.c comments on kernel task creation) |
| |
| void cpqfcTSWorkerThread( void *host) |
| { |
| struct Scsi_Host *HostAdapter = (struct Scsi_Host*)host; |
| CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata; |
| #ifdef PCI_KERNEL_TRACE |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| #endif |
| DECLARE_MUTEX_LOCKED(fcQueReady); |
| DECLARE_MUTEX_LOCKED(fcTYOBcomplete); |
| DECLARE_MUTEX_LOCKED(TachFrozen); |
| DECLARE_MUTEX_LOCKED(BoardLock); |
| |
| ENTER("WorkerThread"); |
| |
| lock_kernel(); |
| daemonize("cpqfcTS_wt_%d", HostAdapter->host_no); |
| siginitsetinv(¤t->blocked, SHUTDOWN_SIGS); |
| |
| |
| cpqfcHBAdata->fcQueReady = &fcQueReady; // primary wait point |
| cpqfcHBAdata->TYOBcomplete = &fcTYOBcomplete; |
| cpqfcHBAdata->TachFrozen = &TachFrozen; |
| |
| |
| cpqfcHBAdata->worker_thread = current; |
| |
| unlock_kernel(); |
| |
| if( cpqfcHBAdata->notify_wt != NULL ) |
| up( cpqfcHBAdata->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( HostAdapter->host_lock, flags); // STOP _que function |
| PCI_TRACE( 0x90) |
| |
| CPQ_SPINLOCK_HBA( cpqfcHBAdata) |
| // 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) |
| |
| cpqfcHBAdata->BoardLock = &BoardLock; |
| |
| PCI_TRACE( 0x90) |
| |
| // release the IO lock (and re-enable interrupts) |
| spin_unlock_irqrestore( HostAdapter->host_lock, flags); |
| |
| // disable OUR HBA interrupt (keep them off as much as possible |
| // during error recovery) |
| disable_irq( cpqfcHBAdata->HostAdapter->irq); |
| |
| // OK, let's process the Fibre Channel Link Q and do the work |
| cpqfcTS_WorkTask( HostAdapter); |
| |
| // hopefully, no more "work" to do; |
| // re-enable our INTs for "normal" completion processing |
| enable_irq( cpqfcHBAdata->HostAdapter->irq); |
| |
| |
| cpqfcHBAdata->BoardLock = NULL; // allow commands to be queued |
| CPQ_SPINUNLOCK_HBA( cpqfcHBAdata) |
| |
| |
| // Now, complete any Cmnd we Q'd up while BoardLock was held |
| |
| CompleteBoardLockCmnd( cpqfcHBAdata); |
| |
| |
| } |
| // hopefully, the signal was for our module exit... |
| if( cpqfcHBAdata->notify_wt != NULL ) |
| up( cpqfcHBAdata->notify_wt); // yep, we're outta here |
| } |
| |
| |
| // Freeze Tachyon routine. |
| // If Tachyon is already frozen, return FALSE |
| // If Tachyon is not frozen, call freeze function, return TRUE |
| // |
| static BOOLEAN FreezeTach( CPQFCHBA *cpqfcHBAdata) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| BOOLEAN FrozeTach = FALSE; |
| // 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( cpqfcHBAdata->HostAdapter->irq); // only way to get Semaphore |
| down_interruptible( cpqfcHBAdata->TachFrozen); // wait for INT handler sem. |
| // can we TIMEOUT semaphore wait?? TBD |
| disable_irq( cpqfcHBAdata->HostAdapter->irq); |
| |
| FrozeTach = TRUE; |
| } // (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 *HostAdapter) |
| { |
| CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata; |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| ULONG QconsumerNdx; |
| LONG ExchangeID; |
| ULONG ulStatus=0; |
| TachFCHDR_GCMND fchs; |
| PFC_LINK_QUE fcLQ = cpqfcHBAdata->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( cpqfcHBAdata, 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 = (LONG) // x_ID copied from DPC timeout function |
| fcLQ->Qitem[QconsumerNdx].ulBuff[0]; |
| |
| // 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( cpqfcHBAdata, 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( cpqfcHBAdata, 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( !cpqfcHBAdata->PortDiscDone) // cleared by LDn |
| { |
| printk("Discard Q'd ELS login frame\n"); |
| break; |
| } |
| |
| ulStatus = cpqfcTSBuildExchange( |
| cpqfcHBAdata, |
| 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( cpqfcHBAdata, 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( cpqfcHBAdata, (TachFCHDR_GCMND*) |
| fcLQ->Qitem[QconsumerNdx].ulBuff); |
| |
| break; |
| |
| |
| |
| |
| case BLS_ABTS: // need to ABORT one or more exchanges |
| { |
| LONG x_ID = fcLQ->Qitem[QconsumerNdx].ulBuff[0]; |
| BOOLEAN FrozeTach = FALSE; |
| |
| 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", |
| cpqfcHBAdata->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( cpqfcHBAdata); |
| |
| // 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( |
| cpqfcHBAdata, |
| 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 == TRUE) ) |
| { |
| // 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( cpqfcHBAdata, 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 |
| { |
| /* printk(" ABTS NOT starting xchg %Xh, %p ", |
| x_ID, pLoggedInPort); |
| if( pLoggedInPort ) |
| printk("prli %d ", pLoggedInPort->prli); |
| */ |
| } |
| } |
| } |
| 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( cpqfcHBAdata->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( |
| cpqfcHBAdata, |
| BLS_ABTS_ACC, |
| &fchs, |
| NULL, // no data (no scatter/gather list) |
| &ExchangeID );// fcController->fcExchanges index, -1 if failed |
| |
| if( !ulStatus ) // Exchange setup? |
| { |
| ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, 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( |
| cpqfcHBAdata, |
| 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( cpqfcHBAdata, 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 |
| //doNothing: |
| // 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 *cpqfcHBAdata, |
| int Type, |
| void *QueContent) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| // FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| PFC_LINK_QUE fcLQ = cpqfcHBAdata->fcLQ; |
| ULONG 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) ) |
| { |
| LONG 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( cpqfcHBAdata->fcQueReady ); |
| } |
| |
| //DoneWithPutQ: |
| |
| LEAVE("cpqfcTSPutLinkQ"); |
| } |
| |
| |
| |
| |
| // reset device ext FC link Q |
| void cpqfcTSLinkQReset( CPQFCHBA *cpqfcHBAdata) |
| |
| { |
| PFC_LINK_QUE fcLQ = cpqfcHBAdata->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 *cpqfcHBAdata, |
| int Type, |
| void *QueContent) |
| { |
| // CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata; |
| // PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| |
| // ULONG ndx; |
| |
| // ULONG *pExchangeID; |
| // LONG 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 = (ULONG *)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 |
| FALSE ); // 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* cpqfcHBAdata, |
| PFC_LOGGEDIN_PORT pFcPort) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->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", |
| (ULONG)pFcPort->u.liWWN, |
| (ULONG)(pFcPort->u.liWWN >>32), |
| pFcPort->port_id); |
| |
| // Terminate I/O with this (Linux) Scsi target |
| cpqfcTSTerminateExchange( cpqfcHBAdata, |
| &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 = FALSE; |
| pFcPort->prli = FALSE; |
| pFcPort->plogi = FALSE; |
| pFcPort->flogi = FALSE; |
| pFcPort->LOGO_timer = 0; |
| pFcPort->device_blocked = TRUE; // 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* cpqfcHBAdata, |
| PFC_LOGGEDIN_PORT pLoggedInPort) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->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( cpqfcHBAdata, 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* cpqfcHBAdata, |
| PFC_LOGGEDIN_PORT pLastLoggedInPort, |
| TachFCHDR_GCMND* fchs, |
| LOGIN_PAYLOAD* plogi) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->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( cpqfcHBAdata->HostAdapter->max_id == 0) |
| { |
| pNextLoggedInPort->ScsiNexus.target = 0; |
| fcChip->fcPorts.ScsiNexus.target = -1; // don't use "stub" |
| } |
| else |
| { |
| pNextLoggedInPort->ScsiNexus.target = |
| cpqfcHBAdata->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... |
| cpqfcHBAdata->HostAdapter->max_id++; |
| // printk("HostAdapter->max_id = %d\n", |
| // cpqfcHBAdata->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* cpqfcHBAdata, SCSI_NEXUS *ScsiNexus, int TerminateStatus) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| ULONG 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( cpqfcHBAdata, 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->device->id == ScsiNexus->target) |
| && |
| (Exchanges->fcExchange[x_ID].Cmnd->device->channel == ScsiNexus->channel)) |
| { |
| Exchanges->fcExchange[x_ID].status = TerminateStatus; |
| cpqfcTSPutLinkQue( cpqfcHBAdata, 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* cpqfcHBAdata, TachFCHDR_GCMND* fchs) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| // FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| // ULONG ox_id = (fchs->ox_rx_id >>16); |
| PFC_LOGGEDIN_PORT pLoggedInPort=NULL, pLastLoggedInPort; |
| BOOLEAN NeedReject = FALSE; |
| ULONG 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( (UCHAR*)&fchs->pl[0], (UCHAR*)&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, TRUE, FALSE); |
| |
| // send 'ACC' reply |
| cpqfcTSPutLinkQue( cpqfcHBAdata, |
| ELS_PLOGI_ACC, // (PDISC same as PLOGI ACC) |
| fchs ); |
| |
| // OK to resume I/O... |
| } |
| else |
| { |
| printk("Not expecting PDISC (pdisc=FALSE)\n"); |
| NeedReject = TRUE; |
| // 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 = TRUE; |
| // set reject reason code |
| ls_reject_code = |
| LS_RJT_REASON( PROTOCOL_ERROR, INITIATOR_CTL_ERROR); |
| |
| } |
| } |
| else |
| { |
| printk("PDISC Request from unknown WWN\n"); |
| NeedReject = TRUE; |
| |
| // set reject reason code |
| ls_reject_code = |
| LS_RJT_REASON( LOGICAL_ERROR, INVALID_PORT_NAME); |
| } |
| |
| } |
| else // Payload unacceptable |
| { |
| printk("payload unacceptable\n"); |
| NeedReject = TRUE; // reject code already set |
| |
| } |
| |
| if( NeedReject) |
| { |
| ULONG 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 = FALSE; |
| pLoggedInPort->prli = FALSE; |
| pLoggedInPort->plogi = FALSE; |
| |
| cpqfcTSTerminateExchange( cpqfcHBAdata, |
| &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( cpqfcHBAdata, 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 |
| BOOLEAN NeedReject = FALSE; |
| |
| // 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( (UCHAR*)&fchs->pl[0], (UCHAR*)&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( |
| cpqfcHBAdata, |
| 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 = TRUE; |
| |
| // 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, FALSE, FALSE); |
| |
| // send 'ACC' reply |
| cpqfcTSPutLinkQue( cpqfcHBAdata, |
| ELS_PLOGI_ACC, // (PDISC same as PLOGI ACC) |
| fchs ); |
| } |
| } |
| else // Payload unacceptable |
| { |
| printk("payload unacceptable\n"); |
| NeedReject = TRUE; // 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 = FALSE; |
| pLoggedInPort->prli = FALSE; |
| pLoggedInPort->plogi = FALSE; |
| |
| fchs->reserved = ls_reject_code; // borrow this (unused) field |
| |
| // send 'RJT' reply |
| cpqfcTSPutLinkQue( cpqfcHBAdata, ELS_RJT, fchs ); |
| } |
| |
| // terminate any exchanges with this device... |
| if( pLoggedInPort ) |
| { |
| cpqfcTSTerminateExchange( cpqfcHBAdata, |
| &pLoggedInPort->ScsiNexus, PORTID_CHANGED); |
| } |
| break; |
| |
| |
| |
| case 0x1020: // PRLI? |
| { |
| BOOLEAN NeedReject = TRUE; |
| 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, FALSE, FALSE); |
| |
| // Q an ACCept Reply |
| cpqfcTSPutLinkQue( cpqfcHBAdata, |
| ELS_PRLI_ACC, |
| fchs ); |
| |
| NeedReject = FALSE; |
| } |
| else |
| { |
| // huh? |
| printk(" (unexpected) PRLI REQEST with plogi FALSE\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( cpqfcHBAdata, |
| 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; |
| BOOLEAN GiveUpOnDevice = FALSE; |
| ULONG ls_reject_code = 0; |
| |
| BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&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( cpqfcHBAdata, |
| ELS_LOGO_ACC, // Q Type |
| fchs ); // device to respond to |
| |
| // set login struct fields (LOGO_counter increment) |
| SetLoginFields( pLoggedInPort, fchs, FALSE, FALSE); |
| |
| // 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( cpqfcHBAdata, |
| ELS_PLOGI, // Q Type |
| fchs ); |
| |
| // Terminate I/O with "retry" potential |
| cpqfcTSTerminateExchange( cpqfcHBAdata, |
| &pLoggedInPort->ScsiNexus, |
| PORTID_CHANGED); |
| } |
| else |
| { |
| printk(" Got 3 LOGOuts - terminating comm. with port_id %Xh\n", |
| fchs->s_id &&0xFFFFFF); |
| GiveUpOnDevice = TRUE; |
| } |
| } |
| else |
| { |
| GiveUpOnDevice = TRUE; |
| } |
| |
| |
| if( GiveUpOnDevice == TRUE ) |
| { |
| cpqfcTSTerminateExchange( cpqfcHBAdata, |
| &pLoggedInPort->ScsiNexus, |
| DEVICE_REMOVED); |
| } |
| } |
| else // we don't know this WWN! |
| { |
| // Q a ReJecT Reply with reason code |
| fchs->reserved = ls_reject_code; |
| cpqfcTSPutLinkQue( cpqfcHBAdata, |
| 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( cpqfcHBAdata, |
| ELS_RJT, // Q Type |
| fchs ); |
| |
| break; |
| } |
| else // Accept the command |
| { |
| cpqfcTSPutLinkQue( cpqfcHBAdata, |
| 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( (UCHAR*)&fchs->pl[i+1],(UCHAR*)&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( cpqfcHBAdata, 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( cpqfcHBAdata, |
| ELS_RJT, // Q Type |
| fchs ); |
| break; |
| } |
| } |
| |
| |
| static void ProcessELS_Reply( |
| CPQFCHBA* cpqfcHBAdata, TachFCHDR_GCMND* fchs) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| ULONG ox_id = (fchs->ox_rx_id >>16); |
| ULONG 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( (UCHAR*)&fchs->pl[0], (UCHAR*)&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( |
| cpqfcHBAdata, |
| 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( cpqfcHBAdata, |
| &pLoggedInPort->ScsiNexus, PORTID_CHANGED); |
| } |
| |
| // We have an fcPort struct - set fields accordingly |
| // not PDISC, originator |
| SetLoginFields( pLoggedInPort, fchs, FALSE, TRUE); |
| |
| // We just set a "port_id"; is it duplicated? |
| TestDuplicatePortId( cpqfcHBAdata, 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( cpqfcHBAdata, ELS_SCR, fchs); |
| } |
| |
| else |
| { |
| // Now we need a PRLI to enable FCP-SCSI operation |
| // set flags and Q up a ELS_PRLI |
| cpqfcTSPutLinkQue( cpqfcHBAdata, 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 |
| BOOLEAN NeedLogin = FALSE; |
| |
| // login payload acceptable; search for WWN in our list |
| // of (previously seen) fcPorts |
| |
| BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&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, TRUE, TRUE); |
| |
| // 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( cpqfcHBAdata, EXCHANGE_QUEUED, fchs ); |
| } |
| } |
| |
| // Complete commands Q'd while we were waiting for Login |
| |
| UnblockScsiDevice( cpqfcHBAdata->HostAdapter, pLoggedInPort); |
| } |
| else |
| { |
| printk("Not expecting PDISC (pdisc=FALSE)\n"); |
| NeedLogin = TRUE; |
| } |
| } |
| else |
| { |
| printk("PDISC PortID change: old %Xh, new %Xh\n", |
| pLoggedInPort->port_id, fchs->s_id &0xFFFFFF); |
| NeedLogin = TRUE; |
| |
| } |
| } |
| else |
| { |
| printk("PDISC ACC from unknown WWN\n"); |
| NeedLogin = TRUE; |
| } |
| |
| 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( cpqfcHBAdata, |
| ELS_LOGO, // Q Type |
| fchs ); // 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( cpqfcHBAdata, pLoggedInPort); |
| |
| // TriggerHBA( fcChip->Registers.ReMapMemBase, 0); |
| pLoggedInPort->pdisc = FALSE; |
| pLoggedInPort->prli = FALSE; |
| pLoggedInPort->plogi = FALSE; |
| |
| cpqfcTSTerminateExchange( cpqfcHBAdata, |
| &pLoggedInPort->ScsiNexus, PORTID_CHANGED); |
| } |
| cpqfcTSPutLinkQue( cpqfcHBAdata, 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, FALSE, TRUE); |
| |
| // OK, let's send a REPORT_LUNS command to determine |
| // whether VSA or PDA FCP-LUN addressing is used. |
| |
| cpqfcTSPutLinkQue( cpqfcHBAdata, 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( cpqfcHBAdata->HostAdapter, pLoggedInPort); |
| |
| } |
| else |
| { |
| // huh? |
| printk(" (unexpected) PRLI ACCept with plogi FALSE\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( cpqfcHBAdata, 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( cpqfcHBAdata, 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(cpqfcHBAdata->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* cpqfcHBAdata, TachFCHDR_GCMND* fchs) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| ULONG ox_id = (fchs->ox_rx_id >>16); |
| // ULONG 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 = FALSE; // block FCP-SCSI commands |
| } |
| |
| pLoggedInPort = pLoggedInPort->pNextPort; |
| } |
| |
| if( fchs->pl[2] == 0x0280) // ACCept? |
| { |
| // Send PLOGI or PDISC to these Fabric devices |
| SendLogins( cpqfcHBAdata, &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(cpqfcHBAdata->PciDev, fcChip, (fchs->ox_rx_id >>16)); |
| |
| Quit: |
| return; |
| } |
| |
| |
| |
| |
| |
| |
| |
| static void AnalyzeIncomingFrame( |
| CPQFCHBA *cpqfcHBAdata, |
| ULONG QNdx ) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| PFC_LINK_QUE fcLQ = cpqfcHBAdata->fcLQ; |
| TachFCHDR_GCMND* fchs = |
| (TachFCHDR_GCMND*)fcLQ->Qitem[QNdx].ulBuff; |
| // ULONG ls_reject_code; // reason for rejecting login |
| LONG ExchangeID; |
| // FC_LOGGEDIN_PORT *pLoggedInPort; |
| BOOLEAN 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( cpqfcHBAdata, 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( cpqfcHBAdata, 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( cpqfcHBAdata, 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? |
| { |
| // UCHAR *ptr = (UCHAR*)&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( cpqfcHBAdata, 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=FALSE; |
| 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( cpqfcHBAdata, |
| BLS_ABTS_ACC, // Q Type |
| fchs ); // void QueContent |
| AbortAccept = TRUE; |
| 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( cpqfcHBAdata->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( cpqfcHBAdata->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( cpqfcHBAdata->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 *cpqfcHBAdata, __u32 *FabricPortIds ) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| ULONG ulStatus=0; |
| TachFCHDR_GCMND fchs; // copy fields for transmission |
| int i; |
| ULONG loginType; |
| LONG 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=FALSE; |
| i = 0; |
| while( !LastPort) |
| { |
| // port IDs From NSR payload; byte swap needed? |
| BigEndianSwap( (UCHAR*)FabricPortIds, (UCHAR*)&PortIds[i], 4); |
| |
| // printk("FPortId[%d] %Xh ", i, PortIds[i]); |
| if( PortIds[i] & 0x80000000) |
| LastPort = TRUE; |
| |
| 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( |
| cpqfcHBAdata, |
| 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( cpqfcHBAdata, 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( cpqfcHBAdata->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( cpqfcHBAdata->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 |
| cpqfcHBAdata->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 *HostAdapter = Cmnd->device->host; |
| CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata; |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| PFC_LOGGEDIN_PORT pLoggedInPort; |
| int LunListLen=0; |
| int i; |
| ULONG x_ID = 0xFFFFFFFF; |
| UCHAR *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( cpqfcHBAdata, 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 @: ", |
| (ULONG)(pLoggedInPort->u.liWWN &0xFFFFFFFF), |
| (ULONG)(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: ; |
| } |
| |
| extern int is_private_data_of_cpqfc(CPQFCHBA *hba, void * pointer); |
| extern void cpqfc_free_private_data(CPQFCHBA *hba, cpqfc_passthru_private_t *data); |
| |
| static void |
| call_scsi_done(Scsi_Cmnd *Cmnd) |
| { |
| CPQFCHBA *hba; |
| hba = (CPQFCHBA *) Cmnd->device->host->hostdata; |
| // Was this command a cpqfc passthru ioctl ? |
| if (Cmnd->sc_request != NULL && Cmnd->device->host != NULL && |
| Cmnd->device->host->hostdata != NULL && |
| is_private_data_of_cpqfc((CPQFCHBA *) Cmnd->device->host->hostdata, |
| Cmnd->sc_request->upper_private_data)) { |
| cpqfc_free_private_data(hba, |
| Cmnd->sc_request->upper_private_data); |
| Cmnd->sc_request->upper_private_data = NULL; |
| 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* cpqfcHBAdata, |
| TachFCHDR_GCMND* fchs) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| PFC_LOGGEDIN_PORT pLoggedInPort; |
| struct scsi_cmnd *Cmnd = NULL; |
| struct scsi_device *ScsiDev = NULL; |
| LONG x_ID; |
| ULONG ulStatus; |
| UCHAR *ucBuff; |
| |
| if( !cpqfcHBAdata->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" |
| |
| |
| ScsiDev = scsi_get_host_dev (cpqfcHBAdata->HostAdapter); |
| if (!ScsiDev) |
| goto Done; |
| |
| Cmnd = scsi_get_command (ScsiDev, GFP_KERNEL); |
| if (!Cmnd) |
| goto Done; |
| |
| ucBuff = pLoggedInPort->ReportLunsPayload; |
| |
| memset( ucBuff, 0, REPORT_LUNS_PL); |
| |
| Cmnd->scsi_done = ScsiReportLunsDone; |
| |
| 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] = (UCHAR)REPORT_LUNS_PL; |
| Cmnd->cmd_len = 12; |
| |
| Cmnd->device->channel = pLoggedInPort->ScsiNexus.channel; |
| Cmnd->device->id = pLoggedInPort->ScsiNexus.target; |
| |
| |
| ulStatus = cpqfcTSBuildExchange( |
| cpqfcHBAdata, |
| SCSI_IRE, |
| fchs, |
| Cmnd, // buffer for Report Lun data |
| &x_ID );// fcController->fcExchanges index, -1 if failed |
| |
| if( !ulStatus ) // Exchange setup? |
| { |
| ulStatus = cpqfcTSStartExchange( cpqfcHBAdata, 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: |
| |
| if (Cmnd) |
| scsi_put_command (Cmnd); |
| if (ScsiDev) |
| scsi_free_host_dev (ScsiDev); |
| } |
| |
| |
| |
| |
| |
| |
| |
| static void CompleteBoardLockCmnd( CPQFCHBA *cpqfcHBAdata) |
| { |
| int i; |
| for( i = CPQFCTS_REQ_QUEUE_LEN-1; i>= 0; i--) |
| { |
| if( cpqfcHBAdata->BoardLockCmnd[i] != NULL ) |
| { |
| Scsi_Cmnd *Cmnd = cpqfcHBAdata->BoardLockCmnd[i]; |
| cpqfcHBAdata->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 *cpqfcHBAdata = (CPQFCHBA *)ptr; |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| PFC_LOGGEDIN_PORT pLoggedInPort = &fcChip->fcPorts; |
| ULONG i; |
| unsigned long flags; |
| DECLARE_MUTEX_LOCKED(BoardLock); |
| |
| PCI_TRACE( 0xA8) |
| |
| if( cpqfcHBAdata->BoardLock) // Worker Task Running? |
| goto Skip; |
| |
| // STOP _que function |
| spin_lock_irqsave( cpqfcHBAdata->HostAdapter->host_lock, flags); |
| |
| PCI_TRACE( 0xA8) |
| |
| |
| cpqfcHBAdata->BoardLock = &BoardLock; // stop Linux SCSI command queuing |
| |
| // release the IO lock (and re-enable interrupts) |
| spin_unlock_irqrestore( cpqfcHBAdata->HostAdapter->host_lock, flags); |
| |
| // Ensure no contention from _quecommand or Worker process |
| CPQ_SPINLOCK_HBA( cpqfcHBAdata) |
| |
| PCI_TRACE( 0xA8) |
| |
| |
| disable_irq( cpqfcHBAdata->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( cpqfcHBAdata->BadTargetCmnd[i] ) |
| { |
| Scsi_Cmnd *Cmnd = cpqfcHBAdata->BadTargetCmnd[i]; |
| cpqfcHBAdata->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, |
| (ULONG)(pLoggedInPort->u.liWWN &0xFFFFFFFF), |
| (ULONG)(pLoggedInPort->u.liWWN>>32)); |
| |
| */ |
| cpqfcTSImplicitLogout( cpqfcHBAdata, 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( cpqfcHBAdata->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( cpqfcHBAdata, i ); |
| |
| } |
| } |
| } |
| else // not an ABTS |
| { |
| |
| // We expect the WorkerThread to change the xchng type to |
| // abort and set appropriate timeout. |
| cpqfcTSPutLinkQue( cpqfcHBAdata, BLS_ABTS, &i ); // timed-out |
| } |
| } |
| } |
| else // time not expired... |
| { |
| // decrement timeout: 1 or more seconds left |
| --Exchanges->fcExchange[i].timeOut; |
| } |
| } |
| } |
| |
| |
| enable_irq( cpqfcHBAdata->HostAdapter->irq); |
| |
| |
| CPQ_SPINUNLOCK_HBA( cpqfcHBAdata) |
| |
| cpqfcHBAdata->BoardLock = NULL; // Linux SCSI commands may be queued |
| |
| // Now, complete any Cmnd we Q'd up while BoardLock was held |
| |
| CompleteBoardLockCmnd( cpqfcHBAdata); |
| |
| |
| // restart the timer to run again (1 sec later) |
| Skip: |
| mod_timer( &cpqfcHBAdata->cpqfcTStimer, jiffies + HZ); |
| |
| PCI_TRACEO( i, 0xA8) |
| return; |
| } |
| |
| |
| // put valid FC-AL physical address in spec order |
| static const UCHAR 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( ULONG al_pa ) |
| { |
| int i; |
| |
| for( i = number_of_al_pa -1; i >= 0; i--) // dec. |
| { |
| if( valid_al_pa[i] == (UCHAR)al_pa ) // take lowest 8 bits |
| return i; // success - found valid al_pa; return decimal LoopID |
| } |
| return -1; // failed - not found |
| } |
| #endif |
| |
| extern cpqfc_passthru_private_t *cpqfc_private(Scsi_Request *sr); |
| |
| // 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) |
| ULONG port_id, // search linked list for al_pa, or |
| UCHAR wwn[8], // search linked list for WWN, or... |
| PFC_LOGGEDIN_PORT *pLastLoggedInPort ) |
| |
| { |
| PFC_LOGGEDIN_PORT pLoggedInPort = &fcChip->fcPorts; |
| BOOLEAN target_id_valid=FALSE; |
| BOOLEAN port_id_valid=FALSE; |
| BOOLEAN wwn_valid=FALSE; |
| int i; |
| |
| |
| if( Cmnd != NULL ) |
| target_id_valid = TRUE; |
| |
| else if( port_id ) // note! 24-bit NULL address is illegal |
| port_id_valid = TRUE; |
| |
| 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 = TRUE; // 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->device->id) |
| && |
| (pLoggedInPort->ScsiNexus.channel == Cmnd->device->channel)) |
| { |
| // For "passthru" modes, the IOCTL caller is responsible |
| // for setting the FCP-LUN addressing |
| if (Cmnd->sc_request != NULL && Cmnd->device->host != NULL && |
| Cmnd->device->host->hostdata != NULL && |
| is_private_data_of_cpqfc((CPQFCHBA *) Cmnd->device->host->hostdata, |
| Cmnd->sc_request->upper_private_data)) { |
| /* This is a passthru... */ |
| cpqfc_passthru_private_t *pd; |
| pd = Cmnd->sc_request->upper_private_data; |
| Cmnd->SCp.phase = pd->bus; |
| // Cmnd->SCp.have_data_in = pd->pdrive; |
| Cmnd->SCp.have_data_in = Cmnd->device->lun; |
| } else { |
| /* This is not a 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) |
| { |
| if (Cmnd->device->lun > sizeof(pLoggedInPort->ScsiNexus.lun)) |
| return NULL; |
| // we KNOW all the valid LUNs... 0xFF is invalid! |
| Cmnd->SCp.have_data_in = pLoggedInPort->ScsiNexus.lun[Cmnd->device->lun]; |
| if (pLoggedInPort->ScsiNexus.lun[Cmnd->device->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->device->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 *HostAdapter, |
| PFC_LOGGEDIN_PORT pLoggedInPort) |
| { |
| CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata; |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| ULONG x_ID; |
| BOOLEAN TachFroze = FALSE; |
| |
| |
| // 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) // (24-bit port ID) |
| == pLoggedInPort->port_id) |
| { |
| // printk(" re-val xID %Xh ", x_ID); |
| if( !TachFroze ) // freeze if not already frozen |
| TachFroze |= FreezeTach( cpqfcHBAdata); |
| 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 *HostAdapter, |
| PFC_LOGGEDIN_PORT pLoggedInPort) |
| { |
| CPQFCHBA *cpqfcHBAdata = (CPQFCHBA *)HostAdapter->hostdata; |
| Scsi_Cmnd* *SCptr = &cpqfcHBAdata->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 = FALSE; // 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->device->id == pLoggedInPort->ScsiNexus.target) |
| && |
| (Cmnd->device->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); |
| } |
| 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, |
| BOOLEAN PDisc, |
| BOOLEAN Originator) |
| { |
| LOGIN_PAYLOAD logi; // FC-PH Port Login |
| PRLI_REQUEST prli; // copy for BIG ENDIAN switch |
| int i; |
| #ifdef WWN_DBG |
| ULONG ulBuff; |
| #endif |
| |
| BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&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 = TRUE; |
| pLoggedInPort->pdisc = FALSE; |
| pLoggedInPort->prli = FALSE; // ELS_PLOGI resets |
| pLoggedInPort->flogi = FALSE; // ELS_PLOGI resets |
| pLoggedInPort->logo = FALSE; // 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 = TRUE; |
| |
| |
| for( i=0; i<8; i++) // copy the LOGIN port's WWN |
| pLoggedInPort->u.ucWWN[i] = logi.port_name[i]; |
| |
| #ifdef WWN_DBG |
| ulBuff = (ULONG)pLoggedInPort->u.liWWN; |
| if( pLoggedInPort->Originator) |
| printk("o"); |
| else |
| printk("r"); |
| printk("PLOGI port_id %Xh, WWN %08X", |
| pLoggedInPort->port_id, ulBuff); |
| |
| ulBuff = (ULONG)(pLoggedInPort->u.liWWN >> 32); |
| printk("%08Xh fcPort %p\n", ulBuff, pLoggedInPort); |
| #endif |
| break; |
| |
| |
| |
| |
| case 0x00000005: // ELS_LOGO (logout) |
| |
| |
| pLoggedInPort->plogi = FALSE; |
| pLoggedInPort->pdisc = FALSE; |
| pLoggedInPort->prli = FALSE; // ELS_PLOGI resets |
| pLoggedInPort->flogi = FALSE; // ELS_PLOGI resets |
| pLoggedInPort->logo = TRUE; // ELS_PLOGI resets |
| pLoggedInPort->LOGO_counter++; // ELS_PLOGI resets |
| pLoggedInPort->LOGO_timer = 0; |
| #ifdef WWN_DBG |
| ulBuff = (ULONG)pLoggedInPort->u.liWWN; |
| if( pLoggedInPort->Originator) |
| printk("o"); |
| else |
| printk("r"); |
| printk("LOGO port_id %Xh, WWN %08X", |
| pLoggedInPort->port_id, ulBuff); |
| |
| ulBuff = (ULONG)(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 = TRUE; // ready to accept FCP-SCSI I/O |
| |
| |
| |
| #ifdef WWN_DBG |
| ulBuff = (ULONG)pLoggedInPort->u.liWWN; |
| if( pLoggedInPort->Originator) |
| printk("o"); |
| else |
| printk("r"); |
| printk("PDISC port_id %Xh, WWN %08X", |
| pLoggedInPort->port_id, ulBuff); |
| |
| ulBuff = (ULONG)(pLoggedInPort->u.liWWN >> 32); |
| printk("%08Xh\n", ulBuff); |
| #endif |
| |
| |
| |
| break; |
| |
| |
| |
| case 0x1020L: // PRLI? |
| case 0x1002L: // PRLI ACCept? |
| BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&prli, sizeof(prli)); |
| |
| pLoggedInPort->fcp_info = prli.fcp_info; // target/initiator flags |
| pLoggedInPort->prli = TRUE; // PLOGI resets, PDISC doesn't |
| |
| pLoggedInPort->pdisc = TRUE; // expect to send (or receive) PDISC |
| // next time |
| pLoggedInPort->LOGO_timer = 0; // will be set next LinkDown |
| #ifdef WWN_DBG |
| ulBuff = (ULONG)pLoggedInPort->u.liWWN; |
| if( pLoggedInPort->Originator) |
| printk("o"); |
| else |
| printk("r"); |
| printk("PRLI port_id %Xh, WWN %08X", |
| pLoggedInPort->port_id, ulBuff); |
| |
| ulBuff = (ULONG)(pLoggedInPort->u.liWWN >> 32); |
| printk("%08Xh\n", ulBuff); |
| #endif |
| |
| break; |
| |
| } |
| |
| return; |
| } |
| |
| |
| |
| |
| |
| |
| static void BuildLinkServicePayload( PTACHYON fcChip, ULONG 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 UCHARs |
| // get from Tach registers WWN hi & lo |
| LoadWWN( fcChip, plogi->port_name, 0); |
| // fill in with World Wide Name based Node/Fabric Name - 8 UCHARs |
| // 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( (UCHAR*)&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( (UCHAR*)&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( (UCHAR*)&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( (UCHAR*)&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 UCHARs of the node name |
| // (if private loop, upper two UCHARs 0) |
| logo->reserved = 0; |
| |
| logo->n_port_identifier[0] = (UCHAR)(fcChip->Registers.my_al_pa); |
| logo->n_port_identifier[1] = |
| (UCHAR)(fcChip->Registers.my_al_pa>>8); |
| logo->n_port_identifier[2] = |
| (UCHAR)(fcChip->Registers.my_al_pa>>16); |
| // fill in with World Wide Name based Port Name - 8 UCHARs |
| // get from Tach registers WWN hi & lo |
| LoadWWN( fcChip, logo->port_name, 0); |
| |
| BigEndianSwap( (UCHAR*)&LogoutPayload, |
| payload, sizeof(LogoutPayload) ); // 16 UCHAR 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( (UCHAR*)&LogoutPayload, payload, 4 ); // 4 UCHAR 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( (UCHAR*)&rjt, payload, 8 ); // 8 UCHAR 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( (UCHAR*)&PrliPayload, payload, prli->payload_length); |
| break; |
| |
| |
| |
| default: // no can do - programming error |
| printk(" BuildLinkServicePayload unknown!\n"); |
| break; |
| } |
| } |
| |
| // loads 8 UCHARs for PORT name or NODE name base on |
| // controller's WWN. |
| void LoadWWN( PTACHYON fcChip, UCHAR* dest, UCHAR type) |
| { |
| UCHAR* bPtr, i; |
| |
| switch( type ) |
| { |
| case 0: // Port_Name |
| bPtr = (UCHAR*)&fcChip->Registers.wwn_hi; |
| for( i =0; i<4; i++) |
| dest[i] = *bPtr++; |
| bPtr = (UCHAR*)&fcChip->Registers.wwn_lo; |
| for( i =4; i<8; i++) |
| dest[i] = *bPtr++; |
| break; |
| case 1: // Node/Fabric _Name |
| bPtr = (UCHAR*)&fcChip->Registers.wwn_hi; |
| for( i =0; i<4; i++) |
| dest[i] = *bPtr++; |
| bPtr = (UCHAR*)&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, |
| ULONG* reject_explain) |
| { |
| LOGIN_PAYLOAD login; |
| |
| // source, dest, len (should be mult. of 4) |
| BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&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, ULONG* reject_explain) |
| { |
| PRLI_REQUEST prli; // buffer for BIG ENDIAN |
| |
| // source, dest, len (should be mult. of 4) |
| BigEndianSwap( (UCHAR*)&fchs->pl[0], (UCHAR*)&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 UCHARs as required by Fibre Channel (i.e. BIG ENDIAN) |
| // INPUTS: |
| // source - ptr to LITTLE ENDIAN ULONGS |
| // cnt - number of UCHARs to switch (should be mult. of ULONG) |
| // OUTPUTS: |
| // dest - ptr to BIG ENDIAN copy |
| // RETURN: |
| // none |
| // |
| void BigEndianSwap( UCHAR *source, UCHAR *dest, USHORT cnt) |
| { |
| int i,j; |
| |
| source+=3; // start at MSB of 1st ULONG |
| for( j=0; j < cnt; j+=4, source+=4, dest+=4) // every ULONG |
| { |
| for( i=0; i<4; i++) // every UCHAR in ULONG |
| *(dest+i) = *(source-i); |
| } |
| } |
| |
| |
| |
| |
| // Build FC Exchanges............ |
| |
| static void buildFCPstatus( |
| PTACHYON fcChip, |
| ULONG ExchangeID); |
| |
| static LONG FindFreeExchange( PTACHYON fcChip, ULONG type ); |
| |
| static ULONG build_SEST_sgList( |
| struct pci_dev *pcidev, |
| ULONG *SESTalPairStart, |
| Scsi_Cmnd *Cmnd, |
| ULONG *sgPairs, |
| PSGPAGES *sgPages_head // link list of TL Ext. S/G pages from O/S Pool |
| ); |
| |
| static int build_FCP_payload( Scsi_Cmnd *Cmnd, |
| UCHAR* payload, ULONG type, ULONG 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: |
| // cpqfcHBAdata - 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 |
| ULONG cpqfcTSBuildExchange( |
| CPQFCHBA *cpqfcHBAdata, |
| ULONG type, // e.g. PLOGI |
| TachFCHDR_GCMND* InFCHS, // incoming FCHS |
| void *Data, // the CDB, scatter/gather, etc. |
| LONG *fcExchangeIndex ) // points to allocated exchange, |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| ULONG ulStatus = 0; // assume OK |
| USHORT ox_ID, rx_ID=0xFFFF; |
| ULONG SfsLen=0L; |
| TachLiteIRB* pIRB; |
| IRBflags IRB_flags; |
| UCHAR *pIRB_flags = (UCHAR*)&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; |
| ULONG fcp_dl; // total byte length of DATA transferred |
| ULONG fl; // frame length (FC frame size, 128, 256, 512, 1024) |
| ULONG 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((UCHAR *)&CMDfchs->ox_rx_id, (UCHAR *)&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 = (USHORT)(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( |
| cpqfcHBAdata->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, (UCHAR*)&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) |
| |
| // ULONG 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( |
| cpqfcHBAdata->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, (UCHAR*)&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( |
| cpqfcHBAdata->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, (UCHAR*)&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( |
| cpqfcHBAdata->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) |
| |
| // ULONG 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( cpqfcHBAdata->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, ULONG 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, |
| ULONG *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 = (ULONG) *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, |
| 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 ULONG build_SEST_sgList( |
| struct pci_dev *pcidev, |
| ULONG *SESTalPairStart, // the 3 len/address buffers in SEST |
| Scsi_Cmnd *Cmnd, |
| ULONG *sgPairs, |
| PSGPAGES *sgPages_head) // link list of TL Ext. S/G pages from O/S Pool |
| |
| { |
| ULONG i, AllocatedPages=0; // Tach Ext. S/G page allocations |
| ULONG* alPair = SESTalPairStart; |
| ULONG* ext_sg_page_phys_addr_place = NULL; |
| int PairCount; |
| unsigned long ulBuff, contigaddr; |
| ULONG total_data_len=0; // (in bytes) |
| ULONG bytes_to_go = Cmnd->request_bufflen; // total xfer (S/G sum) |
| ULONG 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, |
| 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 = (ULONG)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, |
| 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, |
| Cmnd->sc_data_direction); |
| 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); |
| } |
| |
| 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++ = (ULONG)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, |
| 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 = (ULONG *) 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 = (ULONG) 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 |
| ULONG cpqfcTSStartExchange( |
| CPQFCHBA *cpqfcHBAdata, |
| LONG ExchangeID ) |
| { |
| PTACHYON fcChip = &cpqfcHBAdata->fcChip; |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| FC_EXCHANGE *pExchange = &Exchanges->fcExchange[ ExchangeID ]; // shorthand |
| USHORT producer, consumer; |
| ULONG ulStatus=0; |
| short int ErqIndex; |
| BOOLEAN CompleteExchange = FALSE; // e.g. ACC replies are complete |
| BOOLEAN SestType=FALSE; |
| ULONG 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 = (USHORT)fcChip->ERQ->producerIndex; // copies for logical arith. |
| consumer = (USHORT)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 = (USHORT)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 = TRUE; |
| 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 = TRUE; // 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 = TRUE; |
| // 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 = TRUE; |
| |
| // 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 = TRUE; // 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 = TRUE; // 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( cpqfcHBAdata->HostAdapter->irq); // only way to get Sem. |
| |
| down_interruptible( cpqfcHBAdata->TYOBcomplete); |
| |
| disable_irq( cpqfcHBAdata->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( cpqfcHBAdata->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( cpqfcHBAdata->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 LONG FindFreeExchange( PTACHYON fcChip, ULONG type ) |
| { |
| FC_EXCHANGES *Exchanges = fcChip->Exchanges; |
| ULONG i; |
| ULONG 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( TRUE ) |
| { |
| 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( TRUE ) |
| { |
| 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, |
| ULONG 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, |
| 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, |
| ULONG 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, |
| 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, |
| cmd->sc_data_direction); |
| } |
| } |
| |
| // We call this routine to free an Exchange for any reason: |
| // completed successfully, completed with error, aborted, etc. |
| |
| // returns FALSE if Exchange failed and "retry" is acceptable |
| // returns TRUE if Exchange was successful, or retry is impossible |
| // (e.g. port/device gone). |
| //scompleteexchange |
| |
| void cpqfcTSCompleteExchange( |
| struct pci_dev *pcidev, |
| PTACHYON fcChip, |
| ULONG 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) |
| { |
| UCHAR 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( (ULONG)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 |
| { |
| // printk(" not calling scsi_done on x_ID %Xh, Cmnd %p\n", |
| // x_ID, Exchanges->fcExchange[ x_ID ].Cmnd); |
| } |
| } |
| else{ |
| 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) |
| |
| |
| return; |
| } // (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, ULONG 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; |
| UCHAR 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)) |
| { |
| UCHAR *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, |
| UCHAR* payload, ULONG type, ULONG 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++ = (UCHAR)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++ = (UCHAR)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++ = (UCHAR)(fcp_dl >>24); // (MSB) 8 bytes data len FCP_DL |
| *payload++ = (UCHAR)(fcp_dl >>16); |
| *payload++ = (UCHAR)(fcp_dl >>8); |
| *payload++ = (UCHAR)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++ = (UCHAR)(fcp_dl >>24); // (MSB) 8 bytes data len FCP_DL |
| *payload++ = (UCHAR)(fcp_dl >>16); |
| *payload++ = (UCHAR)(fcp_dl >>8); |
| *payload++ = (UCHAR)fcp_dl; // (LSB) |
| // 4 bytes RESERVED |
| *payload++ = 0; |
| *payload++ = 0; |
| *payload++ = 0; |
| *payload++ = 0; |
| break; |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |