| /* 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< |