📄 cpqfctsworker.c
字号:
/* 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/malloc.h>#include <linux/ioport.h>#include <linux/kernel.h>#include <linux/stat.h>#include <linux/blk.h>#include <linux/interrupt.h>#include <linux/delay.h>#include <linux/smp_lock.h>#define __KERNEL_SYSCALLS__#define SHUTDOWN_SIGS (sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM))#include <linux/unistd.h>#include <asm/system.h>#include <asm/irq.h>#include <asm/dma.h>#include "sd.h"#include "hosts.h" // struct Scsi_Host definition for T handler#include "cpqfcTSchip.h"#include "cpqfcTSstructs.h"//#define LOGIN_DBG 1// REMARKS:// Since Tachyon chips may be permitted to wait from 500ms up to 2 sec// to empty an outgoing frame from its FIFO to the Fibre Channel stream,// we cannot do everything we need to in the interrupt handler. Specifically,// every time a link re-init (e.g. LIP) takes place, all SCSI I/O has to be// suspended until the login sequences have been completed. Login commands// are frames just like SCSI commands are frames; they are subject to the same// timeout issues and delays. Also, various specs provide up to 2 seconds for// devices to log back in (i.e. respond with ACC to a login frame), so I/O to// that device has to be suspended.// A serious problem here occurs on highly loaded FC-AL systems. If our FC port// has a low priority (e.g. high arbitrated loop physical address, alpa), and// some other device is hogging bandwidth (permissible under FC-AL), we might// time out thinking the link is hung, when it's simply busy. Many such// considerations complicate the design. Although Tachyon assumes control// (in silicon) for many link-specific issues, the Linux driver is left with the// rest, which turns out to be a difficult, time critical chore.// These "worker" functions will handle things like FC Logins; all// processes with I/O to our device must wait for the Login to complete// and (if successful) I/O to resume. In the event of a malfunctioning or // very busy loop, it may take hundreds of millisecs or even seconds to complete// a frame send. We don't want to hang up the entire server (and all// processes which don't depend on Fibre) during this wait.// The Tachyon chip can have around 30,000 I/O operations ("exchanges")// open at one time. However, each exchange must be initiated // synchronously (i.e. each of the 30k I/O had to be started one at a// time by sending a starting frame via Tachyon's outbound que). // To accomodate kernel "module" build, this driver limits the exchanges// to 256, because of the contiguous physical memory limitation of 128M.// Typical FC Exchanges are opened presuming the FC frames start without errors,// while Exchange completion is handled in the interrupt handler. This// optimizes performance for the "everything's working" case.// However, when we have FC related errors or hot plugging of FC ports, we pause// I/O and handle FC-specific tasks in the worker thread. These FC-specific// functions will handle things like FC Logins and Aborts. As the Login sequence// completes to each and every target, I/O can resume to that target. // Our kernel "worker thread" must share the HBA with threads calling // "queuecommand". We define a "BoardLock" semaphore which indicates// to "queuecommand" that the HBA is unavailable, and Cmnds are added to a// board lock Q. When the worker thread finishes with the board, the board// lock Q commands are completed with status causing immediate retry.// Typically, the board is locked while Logins are in progress after an// FC Link Down condition. When Cmnds are re-queued after board lock, the// particular Scsi channel/target may or may not have logged back in. When// the device is waiting for login, the "prli" flag is clear, in which case// commands are passed to a Link Down Q. Whenever the login finally completes,// the LinkDown Q is completed, again with status causing immediate retry.// When FC devices are logged in, we build and start FC commands to the// devices.// NOTE!! As of May 2000, kernel 2.2.14, the error recovery logic for devices // that never log back in (e.g. physically removed) is NOT completely// understood. I've still seen instances of system hangs on failed Write // commands (possibly from the ext2 layer?) on device removal. Such special// cases need to be evaluated from a system/application view - e.g., how// exactly does the system want me to complete commands when the device is// physically removed??// local functionsstatic 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 struct fs_struct *fs; DECLARE_MUTEX_LOCKED(fcQueReady); DECLARE_MUTEX_LOCKED(fcTYOBcomplete); DECLARE_MUTEX_LOCKED(TachFrozen); DECLARE_MUTEX_LOCKED(BoardLock); ENTER("WorkerThread"); lock_kernel(); /* * If we were started as result of loading a module, close all of the * user space pages. We don't need them, and if we didn't close them * they would be locked into memory. */ exit_mm(current); current->session = 1; current->pgrp = 1; /* Become as one with the init task */ exit_fs(current); /* current->fs->count--; */ fs = init_task.fs; // Some kernels compiled for SMP, while actually running // on a uniproc machine, will return NULL for this call if( !fs) { printk(" cpqfcTS FATAL: fs is NULL! Is this an SMP kernel on uniproc machine?\n "); } else { current->fs = fs; atomic_inc(&fs->count); } siginitsetinv(¤t->blocked, SHUTDOWN_SIGS); /* * Set the name of this process. */ sprintf(current->comm, "cpqfcTS_wt_%d", HostAdapter->host_no); 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( &io_request_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( &io_request_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 DBGvoid 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 occured, so login frames // are invalid
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -