All pastes #466982 Raw Edit

Miscellany

public text v1 · immutable
·published 2007-05-02 07:03 UTC
rendered paste body
[root@proxy ftsrc]# cat fasttrak.c
/*********************************************************************
* fasttrak.c
*
*   Copyright (C) 1999-2002     Promise Technology, Inc.
*   May be copied or modified under the terms of the
*   GNU Lesser General Public License
*
*********************************************************************/
#include <stdarg.h>

// Ryan Mod
#include <linux/autoconf.h>

#include <asm/io.h>

// Ryan Mod
// #include <asm/irq.h>

#include <asm/byteorder.h>
#include <linux/version.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/pci_ids.h>
#include <linux/pci_regs.h>

#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/spinlock.h>
#include <scsi/scsi.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_eh.h>
#include <scsi/scsi_device.h>
#include <asm/delay.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/types.h>
#include <linux/hdreg.h>

// #include <linux/delay.h>
// #include <sched.h>
// #include <linux/blk.h>
// #include <linux/tqueue.h>
// #include <linux/wrapper.h>
// #include <linux/module.h>

#include "global.h"
#include "fasttrak.h"

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
#define KERNEL_VERSION_24x
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,10)
MODULE_LICENSE("GPL");          //Actually LGPL
#endif

unchar  bResetController;
struct timer_list       submit_timer ;          // for submit_timer
struct timer_list       error_timer_ch0 ;       // for error_handler
struct timer_list       error_timer_ch1 ;       // for error_handler
struct timer_list       error_timer_ch2 ;       // for error_handler
struct timer_list       error_timer_ch3 ;       // for error_handler
struct timer_list       Init_Timer;
#ifdef  KERNEL_VERSION_24x
        static DECLARE_WAIT_QUEUE_HEAD(Wait24);
#else
        static struct wait_queue *Wait22=NULL;
#endif
ulong   InitTime = 5000 ;               // 5 sec
unchar  *ENGINE ;
unchar  *P_BVIR ;
ScsiEngReq_p    CurScsiReq;
extern void ft_handler_wrapper(int irq, void *dev_id, struct pt_regs *regs);
extern void submit_timer_handler(ulong data);
// Support Controller of Device ID
// -----------------------------------------------
struct proc_dir_entry proc_scsi_fasttrak = {
#ifdef  KERNEL_VERSION_24x
   0,                                   // PROC_SCSI_FASTTRAK,
#else
   PROC_SCSI_NOT_PRESENT,               // PROC_SCSI_FASTTRAK,
#endif
   8,                                   // length of "FastTrak"
   "FastTrak",                          // name of directory entry
   S_IFDIR | S_IRUGO | S_IXUGO,
   2,
};
/*********************************************************************
 * Function:    unchar InitGDS( void )
 * Description: Initial Global Data Structure
 *      ft_adapter[FT_Max_Ada]
 *      ft_channel[FT_Max_Ch]
 *      ft_drive[FT_Max_Dev]
 ********************************************************************/
unchar InitGDS( void ) {
   unchar       uc;
   // initialize ft_adapter[ada]
   for( uc=0 ; uc < FT_Max_Ada ; uc++ ) {
        memset(&ft_adapter[uc],0,sizeof(ft_adapter_t));
        ft_adapter[uc].id = uc ;
        ft_adapter[uc].pchannel[0] = (ft_channel_t *)&ft_channel[uc*2+0];
        ft_adapter[uc].pchannel[1] = &ft_channel[uc*2+1];
        spin_lock_init( &ft_adapter[uc].ft_lock );
   }
   // initialize ft_channel[ch]
   for( uc=0 ; uc < FT_Max_Ch ; uc++ )  {
        memset(&ft_channel[uc],0,sizeof(ft_channel_t));
        ft_channel[uc].id = uc ;
        ft_channel[uc].padapter  = &ft_adapter[uc/2];
        ft_channel[uc].pdrive[0] = &ft_drive[uc*2+0];
        ft_channel[uc].pdrive[1] = &ft_drive[uc*2+1];
   }
   // initialize ft_drive[dev]
   for( uc=0 ; uc < FT_Max_Dev ; uc++ ) {
        memset(&ft_drive[uc],0,sizeof(ft_drive_t));
        ft_drive[uc].id = uc ;
        ft_drive[uc].pchannel = &ft_channel[uc/2];
        ft_drive[uc].select   = uc%2 ? 0xB0 : 0xA0 ;
   }
   // By Bennett, Initial FastCheck event variable.
   EventPtr = 0;
   EventIoctlPtr = 0;
   return(0);
}
/*********************************************************************
 * Function:
 *   int TX4ImprovePerformance(void)
 *
 * Description:
 *   To improve TX4 performance by fixing conflict between Intel chip
 *   on TX4 with Intel ChipSets as bellow ,
 *   (1) Intel ChipSet Device ID : 0x2440  0x244C  0x2410  0x2420
 *       solution: PCI configuration offset 0xFF bit 2 , set to 1
 *   (2) Intel ChipSet Device ID = 0x2418  0x2428  0x244E  0x2448
 *       solution: PCI configuration offset 0x50 bit 2 , set to 1
*********************************************************************/
void TX4ImprovePerformance(void) {
   ushort TX4_compatible_1[5] = { 0x2440, 0x244C, 0x2410, 0x2420, 0 } ;
   ushort TX4_compatible_2[5] = { 0x2418, 0x2428, 0x244E, 0x2448, 0 } ;
   unchar class ;
   unchar value ;
   #ifdef       KERNEL_VERSION_24x
        struct pci_dev  *intel_chip_pci_dev = NULL ;
   #else
        struct pci_dev  *intel_chip_pci_dev = pci_devices ;
   #endif

   class =  0 ;
   while( TX4_compatible_1[class] ) {
        while( (intel_chip_pci_dev = pci_find_device(PCI_VENDOR_ID_INTEL,TX4_compatible_1[class],intel_chip_pci_dev)) ) {
           DEB(printk("TX4ImprovePerformance : TX4_compatible_1 : find[%x] %x:%x \n",class,PCI_VENDOR_ID_INTEL,TX4_compatible_1[class]););
           pci_read_config_byte(intel_chip_pci_dev,0xFF,&value);
           pci_write_config_byte( intel_chip_pci_dev,0xFF,value|(1<<2) );
        }
        class++;
   }    // End of while( TX4_compatible_1[class] )

   class =  0 ;
   while( TX4_compatible_2[class] ) {
        while( (intel_chip_pci_dev = pci_find_device(PCI_VENDOR_ID_INTEL,TX4_compatible_2[class],intel_chip_pci_dev)) ) {
           DEB(printk("TX4ImprovePerformance : TX4_compatible_2 : find[%x] %x:%x \n",class,PCI_VENDOR_ID_INTEL,TX4_compatible_2[class]););
           pci_read_config_byte(intel_chip_pci_dev,0x50,&value);
           pci_write_config_byte( intel_chip_pci_dev,0x50,value|(1<<2) );
        }
        class++;
   }    // End of while( TX4_compatible_2[class] )

   return;
}
// -----------------------------------------------
unchar init_ioports( unchar ada )
{
   unchar               uc;
   ft_adapter_t         *adapter = &ft_adapter[ada] ;
   struct pci_dev       *ft_pci_dev = adapter->ft_pci_dev;
   ushort               deviceid= ft_pci_dev->device;
   ushort               command ;
   uint                 base[6];

   for( uc = 0 ; uc < 5 ; uc++ ) {
     pci_read_config_dword(ft_pci_dev,PCI_BASE_ADDRESS_0+4*uc,&base[uc]);
     base[uc] = base[uc] & PCI_BASE_ADDRESS_IO_MASK ;
     if(uc%2)   base[uc] += 2 ;
//   DEB(printk("FastTrak : io_base[%d]:%#x\n",i,temp_p->io_base[i]));
   }

   adapter->pchannel[0]->base = base[0] ;
   adapter->pchannel[0]->ctrl = base[1] ;
   adapter->pchannel[1]->base = base[2] ;
   adapter->pchannel[1]->ctrl = base[3] ;
   adapter->pchannel[0]->bm   = base[4] ;
   adapter->pchannel[1]->bm   = base[4] + 8 ;

   pci_read_config_word(ft_pci_dev,PCI_COMMAND,&command);
   command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
   pci_write_config_word(ft_pci_dev,PCI_COMMAND,command);
   pci_read_config_dword(ft_pci_dev,0x30,&adapter->rom_base);

   if( ! ( adapter->pchannel[0]->bm &= 0xFFF0 ) )
        return(2);

   for( uc=0 ; uc < ChPAda ; uc++ ) {   // ch per adapter : 2
        if( ! check_region(adapter->pchannel[uc]->base,8) )
           request_region(adapter->pchannel[uc]->base,8,"FT(base)");
        if( ! check_region(adapter->pchannel[uc]->ctrl,1) )
           request_region(adapter->pchannel[uc]->ctrl,1,"FT(ctrl)");
        if( ! check_region(adapter->pchannel[uc]->bm,8) )
           request_region(adapter->pchannel[uc]->bm,8,"FT(BM)");
   }

   // fix-me : if different adapters use same irq - not error
   if( (ada == 1) && (ft_adapter[0].ft_pci_dev->irq == ft_pci_dev->irq) )
        DEB(printk("Two FT adapters request same irq number\n"););
   else {
        if( request_irq(ft_pci_dev->irq,ft_handler_wrapper,
           SA_INTERRUPT | SA_SHIRQ,"FastTrak",&ft_adapter[0])) {
           printk("FastTrak : adapter%d request_irq() error.\n",ada);
           return(2);
        }
   }

   if( DID27x(deviceid) )
        return(0);

   //   Allow byte alignment for ... , such as PRD Table ...
   outb( 1 , adapter->pchannel[0]->bm + 0x1A );
   outb( 1 , adapter->pchannel[0]->bm + 0x1B );

   //   Select External Clock
   if( (deviceid == DID265) || (deviceid == DID267) ) {
        unchar  ctrl_init_sta, uata_clock;
        ushort  sys_ctrl_reg_1F_port = adapter->pchannel[0]->bm + 0x1F ;
        ushort  sys_ctrl_reg_1C_port = adapter->pchannel[0]->bm + 0x1C ;
        unchar  sys_ctrl_reg_1F_data,sys_ctrl_reg_1C_data, outdata;

        pci_read_config_byte(ft_pci_dev,0x50,&ctrl_init_sta);
        uata_clock = ( ctrl_init_sta & 0x40 ) << 1 ;
        sys_ctrl_reg_1F_data = inb(sys_ctrl_reg_1F_port);
        sys_ctrl_reg_1C_data = inb(sys_ctrl_reg_1C_port);
        outdata = ( sys_ctrl_reg_1F_data & 0x3F ) | uata_clock ;
        outb( outdata | 1 , sys_ctrl_reg_1F_port ); // burst
        outb( sys_ctrl_reg_1C_data | 0x03 , sys_ctrl_reg_1C_port );

        DEB(
           printk("sys_ctrl_reg_1F_port = %p\n",sys_ctrl_reg_1F_port);
           printk("ctrl_init_sta = %#x\n",ctrl_init_sta);
           printk("uata_clock = %#x\n",uata_clock);
           printk("sys_ctrl_reg_1F_data = %#x\n",sys_ctrl_reg_1F_data);
           printk("outdata = %#x\n",outdata);
        );
   }

   return(0);
}
// -------------------------------------------------------------------
int input_data(unchar dev,void *buffer,unsigned wcount)
{
   ushort       *ptr=(ushort *)buffer;

   while(wcount--) {
        *ptr++=inw(ft_drive[dev].pchannel->base);
   }
   return(0);
}
/*********************************************************************
 * Function:
 *   unchar identify_device(unchar dev,void *id)
 *
 * Return:
 *   0  success
 *   1  select time out
 *   2  identify time out
 *  #3  request_irq() fail
*********************************************************************/
unchar identify_device(unchar dev,void *id)
{
   ulong        flags;
   unchar       error;
   ft_channel_t *p = ft_drive[dev].pchannel ;
   ushort       base = p->base ;
   ushort       ctrl = p->ctrl ;
   ft_drive_t   *drive=&ft_drive[dev];

   DebFtFun(printk("DebFtFun : identify_device(dev=%d).\n",dev););
   if( Check1F7(dev,3*1000,0,BSY) )
        return(1);      // drive[] busy

   outb( drive->select , base+6 ) ;
   outb( drive->select , base+6 ) ;             // for 277
   if( Check1F7(dev,3,DRDY|SEEK,BSY|DF) )
        return(1);      // drive[] not exist

   save_flags(flags);
   cli();
//   free_irq(p->padapter->irq,"FT_IRQ");       // for some special HDDs
   outb( 2 , ctrl ) ;   // disable nIEN
   outb( drive->select , base+6 ) ;
   outb( drive->select , base+6 ) ;             // for 277
   outb( 0xEC , base+7 ) ;
   error = Check1F7(dev,10*1000,DRDY|SEEK|DRQ,BSY|DF) ;
   inb(base+7) ;
   if( error )  error = 2 ;             // drive[] not exist
   else {
        struct ft_driveid       *p_id = (struct ft_driveid *)id;
        input_data(dev,id,512/2);
        drive->present = 1 ;
        drive->LBA_48bit = 0 ;
        if( (p_id->cmd_set[1] & (1<<10)) && (p_id->cfs[2] & (1<<10)) )
           drive->LBA_48bit = 1 ;
        outb( 0 , ctrl ) ;      // Enable nIEN
   }

//   if(request_irq(p->padapter->ft_pci_dev->irq,ft_handler_wrapper,SA_INTERRUPT|SA_SHIRQ,
//      "FastTrak","FT_IRQ")) {
//      error = 3 ;
//      printk("FastTrak : request_irq() error in identify.\n");
//   }

   restore_flags(flags);
   DEB(printk("in identify:dev/error=%d/%#x\n",dev,error););
   return(error);
}
// -------------------------------------------------------------------
int set_feature(unchar dev,unchar *pio_mode,unchar *dma_mode,unchar *udma_mode)
{
   ft_drive_t           *drive  = &ft_drive[dev];
   struct ft_driveid    *id     = drive->identify;
   struct pci_dev       *ft_pci_dev = drive->pchannel->padapter->ft_pci_dev;
   ushort               deviceid= ft_pci_dev->device ;
   ushort               base    = drive->pchannel->base;
   ushort               bm      = drive->pchannel->bm;
   ushort               IndexReg= bm + 1 ;
   ushort               DataReg = bm + 3 ;
   ushort               cycle_time, mode, Pin40;
   unchar               xfermode=0, IORDYp;
   ulong                flags;
   unchar               ret, error;

   if( Check1F7(dev,3*1000,0,BSY) )
        return(1);      // drive[] busy

   //   --- UDMA & DMA transfer mode
   if( id->capability & 1) {
        if( (id->field_valid & 2) && (mode=(id->dma_mword & 0x000F)) ) {
           xfermode = 0;
           mode=mode>>1;
           while(mode) {        // --- MDMA transfer mode
                xfermode++;
                mode=mode>>1;
           }
           *dma_mode = xfermode ;
           xfermode = 0x20 | *dma_mode ;
        }
        if( (id->field_valid & 4) && (mode=(id->dma_ultra & 0x00FF)) ) {
           xfermode = 0;
           mode=mode>>1;
           while(mode) {        // --- UDMA transfer mode
                xfermode++;
                mode=mode>>1;
           }
           if ( drive->downmode && (drive->xfermode & 0x40) ) {
                xfermode = (drive->xfermode & 0x07) - 1 ; // e.g. ECC
                drive->downmode = 0 ;
           }
#ifndef Linux_Intel_0140
           else if( (deviceid == DID262) && (xfermode > 4) )
                xfermode = 4 ;  // PDC20262 only supports to UDMA4
           else if( DID26x(deviceid) && (xfermode > 5) )
                xfermode = 5 ;  // PDC20265/267 only support to UDMA5
           else if( (deviceid == DID270) && (xfermode > 5) )
                xfermode = 5 ;  //  PDC20270 only support to UDMA5
#else      // modify for Intel v2.30.0140.1
           else if( (deviceid == DID267) && (xfermode > 5) )
                xfermode = 5 ;  // PDC20267 only support to UDMA5
           else if( (deviceid == DID277) && (xfermode > 5) )
                xfermode = 5 ;  //  PDC20277 support UDMA5 instead of UDMA6
#endif
           *udma_mode = xfermode ;
           xfermode = 0x40 | *udma_mode ;
        }
   }

   // Get PIO transfer mode & Check Cable Status (80/40pins)
   if( DID27x(deviceid) ) {
        outb( (( drive->select == 0xA0 ) ? 0x13 : 0x1B) , IndexReg );
        IORDYp = inb( DataReg ) & ( 1<<2 ) ;
        outb( 0x0B , IndexReg );
        Pin40  = inb( DataReg ) & ( 1<<3 ) ;
   }
   else {       // PIO Mode on PDC20262/265/267
        unchar  C_REG , offset;
        ushort  State ;         // Controller Initial State
        offset = ( dev % 4 < 2 ) ? 0x62 : 0x6A ;
        pci_read_config_byte(ft_pci_dev, offset, &C_REG);
        IORDYp = C_REG & ( 1<<6 ) ;
        pci_read_config_word(ft_pci_dev, 0x50, &State);
        Pin40  = ( dev % 4 >= 2 ) ? State & (1<<11) : State & (1<<10) ;
   }

   if( IORDYp ) cycle_time=id->eide_pio_iordy;
   else         cycle_time=id->eide_pio;

   if   (cycle_time > 383)      *pio_mode=0;
   else  if(cycle_time > 240)   *pio_mode=1;
   else  if(cycle_time > 180)   *pio_mode=2;
   else  if(cycle_time > 120)   *pio_mode=3;
   else    *pio_mode=4;

   if( *udma_mode == 0xFF && *dma_mode == 0xFF )
        xfermode = 0x08 | *pio_mode ;
   if( (*udma_mode != 0xFF) && (*udma_mode > 2) && Pin40 ) {
        printk("Warning: PROMISE IDE%d ",drive->pchannel->id+1);
        printk("needs 80-pin cable for UDMA%d .\n",*udma_mode);
        *udma_mode = 2;
        xfermode = 0x40 | *udma_mode ;
        printk("Warning: Drive%d Set Feature To UDMA2 \n",dev+1) ;
   }
   if( Check1F7(dev,3*1000,0,BSY) )
        return(2);      // drive[] busy

   save_flags(flags);
   cli();
   outb( 0x03, base+1 );
   outb( xfermode, base+2 );
   outb( drive->select, base+6 );
   outb( drive->select, base+6 );               // for 277
   outb( 0xEF, base+7 );
   DEB(printk("xfermode=%#x,select=%#x\n",xfermode,drive->select););
   error = Check1F7(dev,3*1000,0,BSY) ;

   // Setting tHOLD bit to 0 if using UDMA mode 2
   if( DID27x(deviceid) && (*udma_mode == 2) ) {
        outb( 0x10 , IndexReg );
        outb( inb(DataReg)&(~(1<<7)) , DataReg );
   }

   restore_flags(flags);
   if( error ) {
        printk("FastTrak : PROMISE Set_Feature time out.\n");
        return(3);
   }
   ft_drive[dev].xfermode = xfermode ;
   // get new inquiry data after set feature for data structure
   if( (ret=identify_device(dev,ft_drive[dev].identify)) ) {
        printk("FastTrak: SF Identify(DEV%d) Error(%d)\n",dev+1,ret);
        return(4);
   }
   return(0);
}
// -------------------------------------------------------------------
int speed_27x(unchar dev, unchar pio_mode, unchar dma_mode, unchar udma_mode)
{
   ft_drive_t   *drive   = &ft_drive[dev];
   ushort       IndexReg = drive->pchannel->bm + 1 ;
   ushort       DataReg  = drive->pchannel->bm + 3 ;
   unchar       DataValPIO[5][3]  = {{0xFB,0x2B,0xAC},{0x46,0x29,0xA4},{0x23,0x26,0x64},{0x27,0x0D,0x35},{0x23,0x09,0x25}} ;
   unchar       DataValDMA[3][2]  = {{0xDF,0x5F},{0x6B,0x27},{0x69,0x25}} ;
   unchar       DataValUDMA[7][3] = {{0x4A,0x0F,0xD5},{0x3A,0x0A,0xD0},{0x2A,0x07,0xCD},{0x1A,0x05,0xCD},{0x1A,0x03,0xCD},{0x1A,0x02,0xCB},{0x1A,0x01,0xCB}} ;
   unchar       IndexVal = (drive->id % 2) ? 0x14 : 0x0C ;
   unchar       ui;

   //   PIO mode
   for( ui=0 ; ui<2 ; ui++ ) {
        outb( IndexVal+ui , IndexReg );
        outb( DataValPIO[pio_mode][ui] , DataReg );
//      printk("PIO - %#x/%#x - %#x/%#x\n",IndexReg,IndexVal+ui,DataReg,DataValPIO[pio_mode][ui]);
   }
   outb( IndexVal+7 , IndexReg );
   outb( DataValPIO[pio_mode][2] , DataReg );
//   printk("PIO - %#x/%#x - %#x/%#x\n",IndexReg,IndexVal+7,DataReg,DataValPIO[pio_mode][2]);

   //   MDMA mode
   for( ui=0 ; ui<2 ; ui++ ) {
        outb( IndexVal+2+ui , IndexReg );
        outb( DataValDMA[dma_mode][ui] , DataReg );
//      printk("DMA - %#x/%#x - %#x/%#x\n",IndexReg,IndexVal+2+ui,DataReg,DataValDMA[dma_mode][ui]);
   }

   //   UDMA mode
   for( ui=0 ; ui<3 ; ui++ ) {
        outb( IndexVal+4+ui , IndexReg );
        outb( DataValUDMA[udma_mode][ui] , DataReg );
//      printk("UDMA - %#x/%#x - %#x/%#x\n",IndexReg,IndexVal+4+ui,DataReg,DataValUDMA[udma_mode][ui]);
   }
   return(0);
}
// -------------------------------------------------------------------
int speed_26x(unchar dev, unchar pio_mode, unchar dma_mode, unchar udma_mode)
{
   ft_drive_t           *drive  = &ft_drive[dev];
   struct pci_dev       *ft_pci_dev = drive->pchannel->padapter->ft_pci_dev;
   unchar               PA=0,PB=0,MB=0,MC=0;
   unchar               A_REG,B_REG,C_REG;
   unchar               offset;

   switch(pio_mode) {
        case 0: PA=0x09; PB=0x13; break;
        case 1: PA=0x05; PB=0x0C; break;
        case 2: PA=0x03; PB=0x08; break;
        case 3: PA=0x02; PB=0x06; break;
        case 4: PA=0x01; PB=0x04; break;
   }

   switch(dma_mode) {
        case 0: MB=0x07; MC=0x0F; break;
        case 1: MB=0x03; MC=0x04; break;
        case 2: MB=0x03; MC=0x03; break;
   }

   switch(udma_mode) {
        case 0: MB=0x03; MC=0x03; break;
        case 1: MB=0x02; MC=0x02; break;
        case 2: MB=0x01; MC=0x01; break;
        case 3: MB=0x02; MC=0x02; break;
        case 4:
        case 5: MB=0x01; MC=0x01; break;
   }

   offset = ( dev % 4 ) * 4 ;
   if( drive->select == 0xA0 )
        A_REG = ( pio_mode >= 3 ) ? 0xF0|PA : 0xD0|PA ; // master drive
   else A_REG = ( pio_mode >= 3 ) ? 0x70|PA : 0x50|PA ; // slave  drive
   B_REG=MB<<5|PB;
   C_REG=MC;

   pci_write_config_byte(ft_pci_dev, 0x60+offset, A_REG);
   pci_write_config_byte(ft_pci_dev, 0x61+offset, B_REG);
   pci_write_config_byte(ft_pci_dev, 0x62+offset, C_REG);

   DEB(printk("bus=%#x,fn=%#x,offset=%d",bus,fn,offset));
   DEB(printk("Register:A/B/C=%#x/%#x/%#x\n",A_REG,B_REG,C_REG));
   return(0);
}
// -------------------------------------------------------------------
int set_speed(unchar dev, unchar pio, unchar dma, unchar udma)
{
   ushort deviceid = ft_drive[dev].pchannel->padapter->deviceid ;
   DebFtFun(printk("%#x/%#x/%#x\n",pio,dma,udma););
   if( DID26x(deviceid) )
        speed_26x( dev,pio,dma,udma );
   else if( DID27x(deviceid) ) {
        if( deviceid != DID270 )
           speed_27x( dev,pio,dma,udma );
   }
   return(0);
}
/*********************************************************************
 * Function:
 *   void FreeResource(char force, unchar *str) ;
 *
 * Description:
 *   Free Memory Resource and print message if need .
 ********************************************************************/
void FreeResource(char force, unchar *str) {
   free_pages((ulong)ENGINE,5);
   free_pages((ulong)P_BVIR,1);
   if( force )  printk(str);
   else         DEB(printk(str););
   return;
}
/*********************************************************************
 * Function:
 *   int fasttrak_proc_info(char *, char **, off_t, int, int, int)
 *
 * Description:
 *   in user space , user can view "/proc/scsi/FastTrak/x"
 *   to run fasttrak_proc_info() in kernel space.
 * PS:
 *   the rebuilding information reported under the condition of
 *   one MIRROR array , not support two MIRROR array concurrently.
*********************************************************************/
int fasttrak_proc_info(char *buffer, char **start, off_t offset,
                        int length, int hostno, int func)
{
   ft_proc_t info;
   static char  StopFlag = 0 ;

   if( StopFlag-- )     return(0);

   info.buffer = buffer;
   info.length = length;
   info.offset = offset;
   info.position = 0;
   if (start) {
        *start = buffer;
   }

   GetProcInfo(&info);

   StopFlag = 1;
   if (info.position > info.offset)
        return (info.position - info.offset);
   else
        return 0;

}
/*********************************************************************
 * Function:
 *      int fasttrak_ioctl(Scsi_Device *device,int cmd,void *arg)
 *
 * Description:
 *      Ioctl Interface: commands list
 *          . 0x3801: Read  FastTrak Status         (ft_info_t)
 *          . 0x3802: Read  Identify Drive x Data   (ft_driveid)
 *          . 0x3803: Read  Swap Box Information    (ft_swapbox_t)
 *          . 0x3804: Set Disable Mirroring Synchronous Flag
 *          . 0x3805: Control Swap Box power (on/off - 1/0)
*********************************************************************/
static int fasttrak_ioctl(Scsi_Device *device,int cmd,void *arg)
{
   uint         ui ;
   unchar       *args;

   if( !arg ) {
        DEB(printk("fasttrak_ioctl: Unable to handle NULL pointer.\n"););
        //return(0x10);
        return -EINVAL ;
   }
   args = (unchar *) arg ;
   switch( cmd ) {
        // monitor FastTrak Adapter/Array/Drive Status (arg="")
        case 0x3801 : {
           memset(args,0,sizeof(ft_info_t));
           RepArrInfo(args);
           return( 0 );
        }
        case 0x3802 : {         // Read Identify Data (arg="0")
           unchar       *tmp, error;
           error = 0 ;
           if( !(args[0] >= '0' && args[0] <= FT_Max_Dev + '0') )
                error = 1 ;
           else if( !ft_drive[args[0]-'1'].present )
                error = 1 ;
           if( error ) {
                strcpy(args,"fasttrak_ioctl: Drive not exist.\n");
                return(-1);
           }
           tmp = (unchar *)ft_drive[args[0]-'1'].identify ;
           ui = 0 ;
           do {
                args[ui] = tmp[ui];
           } while( ++ui < 512 ) ;
           args[ui] = 0 ;
           return( 0 );
        }
        case 0x3803 : { // Get Swap Box Information
           unchar *ptr, ret;
           //memset(&ft_box,0,sizeof(ft_swapbox_t)*FT_Max_Dev);
           //structure can't not be reset, fix in 1.02.0.25
           if( (ret=GetAllSwapInfo()) ) {
                sprintf(args,"IOCTL: System is busy.");
                return(ret);
           }
           ptr = (unchar *) &ft_box ;
           ui = 0 ;
           while( ui < sizeof(ft_swapbox_t)*FT_Max_Dev ) {
                args[ui] = ptr[ui] ;
                ui++ ;
           }
           return(0);
        }
        case 0x3804 : // Enable(1)/Disable(0) Rebuild Option with args[0]
           return( Ioctl3804(args) ) ;
        case 0x3805 : {
           unchar       dev,fun;
           ulong        flags;
           ushort       base2;

           dev = args[0] ;      // user count drive from '1'
           fun = args[1] ;      // fun : on/off - '1'/'0'
           if( (dev < '1') || (dev > '0'+FT_Max_Dev) )
                return(-1);
           if( (fun!='0') && (fun!='1') )
                return(-2);
           dev -= '1';
           fun -= '0';
           base2 = ft_drive[dev].pchannel->base + 2 ;
           if( ft_drive[dev].lbusy )
                return(-3);             // device busy
           save_flags(flags);
           cli();
           SWAP_STATE(0,dev);
           if( fun )                    // power on
                outb( 0x12,base2 );     //    Green LED
           else                         // power off
                outb( 0x1,base2 );      //    RED LED
           SWAP_STATE(1,dev);
           restore_flags(flags);
           return(0);
        }
        case 0x3806 : {
           static int event = 0;
           snmp_log_t *p;
           memset(args,0,sizeof(snmp_log_t));

           p = (snmp_log_t *)args;
           if ( !snmp_log_index )
                return(-1);
           while (event < snmp_log_index) {
                p[event].sequence = snmp_log[event].sequence ;
                p[event].eventid  = snmp_log[event].eventid;
                p[event].arrayid  = snmp_log[event].arrayid;
                p[event].diskid   = snmp_log[event].diskid;
                event += 1;
           }
           p[event].sequence = p[event].eventid = 0 ;
           p[event].arrayid  = p[event].diskid  = 0 ;
           event = snmp_log_index = 0;
           memset(snmp_log,0,sizeof(snmp_log_t)*50);
           return(0);
        }
#ifdef Linux_Fujitsu_0060
        case 0x3807 :                           // RAID1 Synchronization
                return( Ioctl3807(args) ) ;
        case 0x3808 :
                return( Ioctl3808(args) ) ;     //Disk Write Cache
        case 0x3809 :
                return( Ioctl3809(args) ) ;     //Flush Cache
#endif
        case 0x380A :
        case 0x380B :
        case 0x380C : {
           extern int ATIoctl(int cmd , unchar *args);
           DEB(printk("ATIoctl cmd %#x.\n",cmd););
           return(ATIoctl(cmd,args));
        }
        case 0x380E :   // FastCheck event ioctl ( add by hank & bennett )
                return( Ioctl380E( args ) );
        case 0x380F :   // BIOS flash ioctl ( add by hank & bennett )
                return( Ioctl380F( device , args ) );
        default :
           printk("fasttrak_ioctl not support the fun (%#x).\n",cmd);
           return(-1);
   }
}
/*********************************************************************
 * Function:
 *   init_handler
 *
 * Description:
 *   Waitting bInitRAID() terminates.
*********************************************************************/
void init_handler(ulong wait)
{
   if(blConfigDoneFlag!=TRUE) {
        if( wait ) {
           if( (wait!=InitTime) && !(wait%1000) )
                printk("Waiting initial ...\n");
           init_timer(&Init_Timer);
           Init_Timer.expires   = jiffies + 5;  // 5*10 ms
           Init_Timer.data      = wait-50 ;     // 50 ms
           Init_Timer.function  = init_handler;
           add_timer(&Init_Timer);
        }
        return;
   }

#ifdef  KERNEL_VERSION_24x
   wake_up(&Wait24);
#else
   module_wake_up(&Wait22);
#endif
        return;
}
/*********************************************************************
 * Function:
 *   FindAdapters24()
 *
 * Description: for Linux Kernel 2.2.x & 2.4.x
 *   1. find controller
 *   2. find FastTrak adapter ( ClassCode=0x0104 )
*********************************************************************/
int FindAdaptersNumber( void ) {
   unchar               class ;
   unchar               adapters ;
#ifdef  KERNEL_VERSION_24x
   struct pci_dev       *ft_pci_dev = NULL ;
#else
   struct pci_dev       *ft_pci_dev = pci_devices ;
#endif
   class = adapters = 0 ;
   while( FT_DEVID[class] ) {
        while( (ft_pci_dev = pci_find_device(PCI_VENDOR_ID_PROMISE,FT_DEVID[class],ft_pci_dev)) ) {
           if( (ft_pci_dev->class)>>8 == 0x0104 ) {
                ft_adapter_t    *pada = &ft_adapter[adapters];
                if( adapters >= FT_Max_Ada ) {
                   printk("FastTrak: The Driver only supports one/two ");
                   printk("FastTrak adapter(s).\n");
                   printk("Others do not be supported:\n");
                   printk("       -- FastTrak%s not been enabled.\n",
                        (FT_DEVID[class] == DID262) ? "66" : "100" );
                   return(adapters);
                }
                pada->present   = 1;
                pada->ft_pci_dev= ft_pci_dev;
                pada->deviceid  = ft_pci_dev->device;
                pada->irq       = ft_pci_dev->irq;
                adapters ++;
           }
        }
        class++;
   }    // End of while( FT_DEVID[class] )
   return(adapters);
}
/*********************************************************************
 * Function:
 *   FindAdapters()
 *
 * Description:
 *   1. find controller
 *   2. find FastTrak adapter ( ClassCode=0x0104 )
 *   3. request ioports and irq , and then fill out structure
*********************************************************************/
int FindAdapters( void )
{
   unchar               uc ;
   unchar               adapters ;

   unsigned int         ldata;
   unsigned short       fwport;

   if (!pci_present()) {                // return non-zero on detection
        DEB(printk("FastTrak : PCI card is not present. \n"));
        return 0;
   }
   if( !(adapters = FindAdaptersNumber()) )
        return(0);
   for( uc=0 ; uc < FT_Max_Ada ; uc++ ) {
        if( ft_adapter[uc].present ) {
           if( init_ioports(uc) ) {
                printk("FastTrak: init_ioports(%d) error.\n",uc);
                return(0);
           }
           else {
                // origin by Bennett
                   // read base address #5
                pci_read_config_dword( ft_adapter[uc].ft_pci_dev , PCI_BASE_ADDRESS_5 , &ldata );
                flash_pmem[uc] = (unsigned char *)ldata;
                //printk( "Hank: BA#5(flash) = %8.8X\n" , (unsigned int)flash_pmem[uc] );

                pci_read_config_word( ft_adapter[uc].ft_pci_dev , PCI_BASE_ADDRESS_4 , &fwport );
                fwport &= 0xFFFC;

                index1[uc] = fwport + 0x09;
                //printk( "Hank: index1 = %4.4X\n" , index1[uc] );
                data1[uc]  = fwport + 0x0B;
                //printk( "Hank: data1 = %4.4X\n" , data1[uc] );

                fwport += 0x14; // for flash memory write
                flash_port[uc] = fwport;
                //printk( "Hank: Flash Write port = %4.4X\n" , fwport );
           }
        }
   }
   return(adapters);
}
/*********************************************************************
 * Function:
 *   do_fasttrak_detect
 *
 * Description:
 *   Try to detect , initialize and register an FastTrak adapter.
 *   Return non-zero number on detection.
 *   The driver only support one FastTrak adapter now.
*********************************************************************/
int do_fasttrak_detect( struct SHT *template ) {
   unchar               dev, result ;
   unchar               adapters ;
   struct Scsi_Host     *shpnt ;
   ft_adapter_t         *ft_ada0;

  /*
   *    Initial Global Data Structure :
   *            ft_adapter[FT_Max_Ada]
   *            ft_channel[FT_Max_Ch]
   *            ft_drive[FT_Max_Dev]
   */
   if( InitGDS() ) {
        printk("FastTrak: Initial Global Data Structure fail.\n");
        return(0);
   }

  InitForSpecificOEM() ;

  /*
   *    Find Adapter :
   *            1. find controller
   *            2. find FastTrak adapter ( ClassCode=0x0104 )
   *            3. request ioports and irq , and then fill out structure
   */
   adapters = FindAdapters();
   DEB(printk("FastTrak : total %d FastTrak adapter(s).\n",adapters));
   if( !adapters )      return(0);

   TX4ImprovePerformance() ;

  /*
   *    Register Array :
   *            register arrays to OS not drives to OS .otherwise ,
   *            the driver will return(0) on above code.
   *    FastTrak all arrays(0-3) are handled by shpnt(SCSI HOST),
   *            if there are many FastTrak adapters, they can be
   *            define zero device in order to tell user there are
   *            two/three/... FastTrak adapters exist.But they do
   *            not handle any FastTrak array.
   */
   shpnt = scsi_register(template,sizeof(ft_adapter_t));
   shpnt -> max_channel = 0 ;           // channel: primary
   shpnt -> max_id = 4 ;                // dev : array[0-3]
   shpnt -> max_lun = 1 ;               // one device
   if( !shpnt->hostdata ) {
        printk("FastTrak: Bad scsi hostdata for card.\n");
        // scsi_unregister(shpnt); release .....
        return(0);
   }
   ft_ada0 = (ft_adapter_t *) shpnt->hostdata ;
   memcpy(ft_ada0,&ft_adapter[0],sizeof(ft_adapter_t));

   if( adapters >= FT_Max_Ada ) {
        struct Scsi_Host        *shpnt2 ;
        shpnt2 = scsi_register(template,sizeof(ft_adapter_t));
        shpnt2 -> max_channel = 0 ;     // channel: primary
        shpnt2 -> max_id = 0 ;          // dev : array[0-3]
        shpnt2 -> max_lun = 0 ;         // one device
   }

  /*
   *    Find Drives :
   *            1. identify to know which drive exist.
   *            2. if drive exist , and then set feature and speed.
   *            3. in set_feature() , identify drive once more ,
   *                    in order to record new information to structure.
   */
   for( dev = 0 ; dev < FT_Max_Dev ; dev++ ) {
        unchar          pio, dma, udma;
        ft_drive_t      *drive = &ft_drive[dev];
        ft_channel_t    *channel = drive->pchannel ;
        if(!channel->padapter->present)
           break;       // Adapter does not exist
        drive->identify = kmalloc(512,GFP_ATOMIC);
        result = identify_device(dev,drive->identify) ;
        if( result==1 || result==2 )    continue;       // drive not exist
//      if( result == 3 )               return(0);      // irq error
        DEB(printk("FastTrak : Drive[%d] Exist.\n",dev+1););
        DEB(printk("FastTrak : Drive[%d] Exist.\n",dev+1););
        pio = dma = udma = 0xFF;
        if( set_feature(dev,&pio,&dma,&udma) )  {
           printk("FastTrak: set_feature fail.\n");
           return(0);
        }
        DEB(printk("Feature(%d) P/D/U = %#x/%#x/%#x\n",dev,pio,dma,udma););
        set_speed(dev,pio,dma,udma);
   }

   {    /////////////   Set AT
        extern  char SetATConf(void) ;
        SetATConf();
   }

   init_timer(&submit_timer);
   submit_timer.expires = jiffies + 200;
   submit_timer.data = 0;
   submit_timer.function = submit_timer_handler;
   add_timer(&submit_timer);

  /*
   *    Initialize RAID ENGINE :
   *            1. allocate memory for RAID ENGINE arguments
   *            2. start initialize RAID ENGINE :  bInitRAID
   */
   ENGINE = (unchar *) __get_free_pages(GFP_ATOMIC,5); // 5(15B00 bytes)
   P_BVIR = (unchar *) __get_free_pages(GFP_ATOMIC,1); // 1(1D38  bytes)
   memset(ENGINE, 0, PAGE_SIZE << 5);
   memset(P_BVIR, 0, PAGE_SIZE << 1);
   if( (!ENGINE) || (!P_BVIR) ) {
        printk("FastTrak : System does not allocate ");
        printk("enough memory to FastTrak !!\n");
        return(0);
   }
   bInitRAID(bPOWER_UP,virt_to_bus(ENGINE),ENGINE,0X15B00,P_BVIR);

   init_timer(&Init_Timer);
   Init_Timer.expires   = jiffies + 5 ;         //1*HZ;
   Init_Timer.data      = InitTime ;
   Init_Timer.function  = init_handler;
   add_timer(&Init_Timer);

   #ifdef       KERNEL_VERSION_24x
//      sleep_on(&Wait24);
        interruptible_sleep_on(&Wait24);
   #else
        module_interruptible_sleep_on(&Wait22);
   #endif

   if( !InitTime ) {
        FreeResource(1,"FastTrak: bInitRAID Fail.\n");
        return(0);
   }

  /*
   *    Fill out data structure after RAID initialize
   *            1. define ft_drive[dev].arr
   *            2. define ft_drive[dev].strm
   */

   if( DefDriveStruct() ) {
        FreeResource(1,"FastTrak: Define Drive Structure Error.\n");
        return(0);
   }

  /*
   *    Initialize SwapBox/PTI LED :
   *            Drive Fail - red light
   *            Normal     - green light
   *            Critical   - orange light
   */
   InitLED();

   /* get swap box info */
   for( dev=0 ; dev < FT_Max_Dev ; dev+=2 )
        GetSwapInfo(dev);               //General rule for proc info

   CurScsiReq = kmalloc(sizeof(*CurScsiReq),GFP_ATOMIC);
   InitQueueBuffer();

   return(adapters);
}
/*********************************************************************
 * Function:
 *   fasttrak_detect
 *
 * Description:
 *   Try to detect , initialize and register an FastTrak adapter.
 *   Return non-zero number on detection.
 *   The driver only support one FastTrak adapter now.
*********************************************************************/
int fasttrak_detect( struct SHT *template )
{
   unchar               adapters ;
   char                 name[20];
   extern int ProductName(char *name) ;

   ProductName(name);
   printk("PROMISE %s Series Linux Driver ",name);
   printk(VERSION " " VERDATE"\n");
//   spin_unlock_irq(&io_request_lock);
   DebScsiFun(printk("DebScsiFun : fasttrak_detect(). \n"););
   template -> proc_dir   = &proc_scsi_fasttrak;
   template -> proc_info  = fasttrak_proc_info;
   adapters = do_fasttrak_detect( template );
//   spin_lock_irq(&io_request_lock);
   return(adapters);
}
/***********************************************************
 * Function:
 *   fasttrak_queuecommand
 *
 * Description:
 *   cmd - SCSI command.
 *   fn  - function called on completion.
***********************************************************/
extern int do_queuecommand(ScsiEngReq_p p);
int fasttrak_queuecommand(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *))
{
   ushort       us;
   int          ret;
   ulong        my_flags;
   ft_adapter_t *ft_ada0 = (ft_adapter_t *) cmd->host->hostdata ;

   DebScsiFun(printk("DebScsiFun : fasttrak_queuecommand()\n"););
   if( !ft_ada0 )       ;       // only avoid warning on UP compiling
   spin_lock_irqsave(&ft_ada0->ft_lock,my_flags);
   memset(CurScsiReq,0,sizeof(*CurScsiReq));
   CurScsiReq->cmd = (void *)cmd ;
   CurScsiReq->fn  = (void *)fn ;
   CurScsiReq->target = cmd->target ;
   CurScsiReq->use_sg = cmd->use_sg ;
   CurScsiReq->buffer = (unchar *)cmd->buffer ;
   CurScsiReq->sense  = (unchar *)cmd->sense_buffer ;
   for( us=0 ; us < 12 ; us ++ )
        CurScsiReq->cmnd[us] = cmd->cmnd[us] ;
   if(cmd->use_sg) {
        struct scatterlist *sgpnt;
        sgpnt = (struct scatterlist *) cmd->request_buffer;
        for( us=0 ; us < cmd->use_sg ; us++ ) {
           CurScsiReq->WStartAddr[us] = virt_to_bus(sgpnt[us].address) ;
           CurScsiReq->WCount[us]     = sgpnt[us].length ;
        }
        CurScsiReq->WCount[us] |= (1<<31);      // EOT=1
   }
   ret = do_queuecommand( CurScsiReq ) ;
   spin_unlock_irqrestore(&ft_ada0->ft_lock,my_flags);
   return( ret ) ;
}
/***********************************************************
 * Function:
 *   EndRequest(unchar id,int result)
 *
 * Description:
 *   End SCSI request and return to SCSI high/middle level
***********************************************************/
char EndRequest(void *cmnd,void *fun,int result)
{
   Scsi_Cmnd *cmd;
   *cmd  = (Scsi_Cmnd *)cmnd ;
   void (*fn)(Scsi_Cmnd *) = (void *)fun ;

   cmd->result = result ;
   cmd->scsi_done = fn ;
   fn(cmd);
   DEB(printk("End SCSI request .\n"));
   return(0);
}
/***********************************************************
 * Function:
 *   fasttrak_abort
 *
 * Description:
 *   cmd - SCSI command.
***********************************************************/
int fasttrak_abort(Scsi_Cmnd *cmd)
{
   // This is called when a scsi cmnd timeout at the os level
   // Returning SCSI_ABORT_ERROR will cause the os to call our
   // reset routine. This is how miniport handles timeouts.
   printk("fasttrak_abort: returning SCSI_ABORT_ERROR\n");
   return SCSI_ABORT_ERROR;
}
int fasttrak_abort_eh(Scsi_Cmnd *cmd)
{
   printk("fasttrak_abort_eh: returning SCSI_ABORT_ERROR\n");
   return SCSI_ABORT_ERROR;     // 0x05
}
/***********************************************************
 * Function:
 *   fasttrak_reset
 *
 * Description:
 *   cmd - SCSI command.
 *   reset_flags - reset flags.
***********************************************************/
extern int reset_controller(void);
int fasttrak_reset(Scsi_Cmnd *cmd, unsigned int reset_flags)
{
   printk("fasttrak_reset: returning SCSI_RESET_SUCCESS\n");
   reset_controller();
   return SCSI_RESET_SUCCESS;
}
int fasttrak_reset_eh(Scsi_Cmnd *cmd)
{
   printk("fasttrak_reset_eh: returning SCSI_RESET_SUCCESS\n");
   reset_controller();
   return SCSI_RESET_SUCCESS;   // 0x02
}
/***********************************************************
 * Function:
 *   fasttrak_bios_param
 *
 * Description:
 *   This function returns unit geometry in cylinders/heads/sectors
***********************************************************/
int fasttrak_bios_param(Disk *disk, kdev_t dev, int Geometry[])
{
   Geometry[0]=64;              // heads
   Geometry[1]=32;              // sectors
   Geometry[2]=disk->capacity / (Geometry[0]*Geometry[1]);  // cylinders

   if (disk->capacity > 0x200000) {     // 1GB
        Geometry[0]=255;        // heads
        Geometry[1]=63;         // sectors
        Geometry[2]=disk->capacity/(Geometry[0]*Geometry[1]); // cylinders

        // capacity larger than 526GB(C/H/S = 65535/255/63)
        if (Geometry[2] > 65535) {
           Geometry[2] = 65535;
           Geometry[1] = disk->capacity / (Geometry[2]*Geometry[0]);
        }
   }

   return 0;
}
// -----------------------------------------------
ulong wV2P(unchar *p_b,unchar *p_wCount)
{
   ulong        addr;

   DebEngFun(printk("DebEngFun : wV2P(0x%-lx)\n",(ulong)p_b););
   addr = virt_to_bus(p_b);
   return((ulong)cpu_to_le32(addr));
}
// -----------------------------------------------
unchar *p_P2V(ulong wAddr,ushort hCount)
{
   DebEngFun(printk("DebEngFun : p_P2V(0x%-lx)\n",wAddr););
   return((unchar *)bus_to_virt(wAddr));
}
// -----------------------------------------------

#ifdef MODULE
Scsi_Host_Template driver_template = FASTTRAK;
#include <linux/module.h>

int init_module(void) {

   driver_template.module = &__this_module;
   scsi_register_module(MODULE_SCSI_HA, &driver_template);
   if (driver_template.present) {
        return 0;
   }
   free_pages((ulong)ENGINE,5);
   free_pages((ulong)P_BVIR,1);
   scsi_unregister_module(MODULE_SCSI_HA, &driver_template);
   return -1;
}

void cleanup_module(void) {
   unchar       ch;
   uint         irq0, irq1;
   del_timer(&submit_timer);
   del_timer(&error_timer_ch0);
   del_timer(&error_timer_ch1);
   del_timer(&error_timer_ch2);
   del_timer(&error_timer_ch3);

   irq0 = ft_adapter[0].ft_pci_dev->irq ;
   free_irq(irq0,&ft_adapter[0]) ;
   if(ft_adapter[1].present) {
        irq1 = ft_adapter[1].ft_pci_dev->irq ;
        if(irq0 != irq1)
           free_irq(irq1,&ft_adapter[0]) ;
   }

   for( ch=0 ; ch < FT_Max_Ch ; ch++ ) {
        if(ft_channel[ch].padapter->present) {
           release_region(ft_channel[ch].base,8);
           release_region(ft_channel[ch].ctrl,1);
           release_region(ft_channel[ch].bm,8);
        }
   }

   free_pages((ulong)ENGINE,5);
   free_pages((ulong)P_BVIR,1);
   driver_template.proc_dir   = &proc_scsi_fasttrak;
   scsi_unregister_module(MODULE_SCSI_HA, &driver_template);
   driver_template.proc_dir   = &proc_scsi_fasttrak;
   return;
}

#endif
[root@proxy ftsrc]#