URL
https://opencores.org/ocsvn/or1k/or1k/trunk
Subversion Repositories or1k
[/] [or1k/] [trunk/] [uclinux/] [uClinux-2.0.x/] [drivers/] [scsi/] [megaraid.c] - Rev 1765
Compare with Previous | Blame | View Log
/*=================================================================== * * Linux MegaRAID device driver * * Copyright 1998 American Megatrends Inc. * * 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 of the License, or (at your option) any later version. * * Version : 0.93 * * Description: Linux device driver for AMI MegaRAID controller * * Supported controllers: MegaRAID 418, 428, 438, 466 * * Maintainer: Jeff L Jones <jeffreyj@ami.com> * * History: * * Version 0.90: * Original source contributed by Dell; integrated it into the kernel and * cleaned up some things. Added support for 438/466 controllers. * * Version 0.91: * Aligned mailbox area on 16-byte boundry. * Added schedule() at the end to properly clean up. * Made improvements for conformity to linux driver standards. * * Version 0.92: * Added support for 2.1 kernels. * Reads from pci_dev struct, so it's not dependent on pcibios. * Added some missing virt_to_bus() translations. * Added support for SMP. * Changed global cli()'s to spinlocks for 2.1, and simulated * spinlocks for 2.0. * Removed setting of SA_INTERRUPT flag when requesting Irq. * * Version 0.92ac: * Small changes to the comments/formatting. Plus a couple of * added notes. Returned to the authors. No actual code changes * save printk levels. * 8 Oct 98 Alan Cox <alan.cox@linux.org> * * Merged with 2.1.131 source tree. * 12 Dec 98 K. Baranowski <kgb@knm.org.pl> * * Version 0.93: * Added support for vendor specific ioctl commands (0x80+xxh) * Changed some fields in MEGARAID struct to better values. * Added signature check for Rp controllers under 2.0 kernels * Changed busy-wait loop to be time-based * Fixed SMP race condition in isr * Added kfree (sgList) on release * Added #include linux/version.h to megaraid.h for hosts.h * Changed max_id to represent max logical drives instead of targets. * * * BUGS: * Some older 2.1 kernels (eg. 2.1.90) have a bug in pci.c that * fails to detect the controller as a pci device on the system. * *===================================================================*/ #define CRLFSTR "\n" #include <linux/config.h> #include <linux/version.h> #ifdef MODULE #include <linux/module.h> #if LINUX_VERSION_CODE >= 0x20100 char kernel_version[] = UTS_RELEASE; MODULE_AUTHOR ("American Megatrends Inc."); MODULE_DESCRIPTION ("AMI MegaRAID driver"); #endif #endif #include <linux/types.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/malloc.h> #include <linux/ioport.h> #include <linux/fcntl.h> #include <linux/delay.h> #include <linux/pci.h> #include <linux/proc_fs.h> #include <linux/blk.h> #include <linux/wait.h> #include <linux/tqueue.h> #include <linux/interrupt.h> #include <linux/sched.h> #include <linux/stat.h> #include <linux/malloc.h> /* for kmalloc() */ #include <linux/config.h> /* for CONFIG_PCI */ #if LINUX_VERSION_CODE < 0x20100 #include <linux/bios32.h> #else #include <asm/spinlock.h> #endif #include <asm/io.h> #include <asm/irq.h> #include "sd.h" #include "scsi.h" #include "hosts.h" #include "megaraid.h" //================================================================ // // #Defines // //================================================================ #if LINUX_VERSION_CODE < 0x020100 #define ioremap vremap #define iounmap vfree /* simulate spin locks */ typedef struct { volatile char lock; } spinlock_t; #define spin_lock_init(x) { (x)->lock = 0;} #define spin_lock_irqsave(x,flags) { while ((x)->lock) barrier();\ (x)->lock=1; save_flags(flags);\ cli();} #define spin_unlock_irqrestore(x,flags) { (x)->lock=0; restore_flags(flags);} #endif #if LINUX_VERSION_CODE >= 0x020100 #define queue_task_irq(a,b) queue_task(a,b) #define queue_task_irq_off(a,b) queue_task(a,b) #endif #define MAX_SERBUF 160 #define COM_BASE 0x2f8 #define ENQUEUE(obj,type,list,next) \ { type **node; long cpuflag; \ spin_lock_irqsave(&mega_lock,cpuflag);\ for(node=&(list); *node; node=(type **)&(*node)->##next); \ (*node) = obj; \ (*node)->##next = NULL; \ spin_unlock_irqrestore(&mega_lock,cpuflag);\ }; #define DEQUEUE(obj,type,list,next) \ { long cpuflag; \ spin_lock_irqsave(&mega_lock,cpuflag);\ if ((obj=list) != NULL) {\ list = (type *)(list)->##next; \ } \ spin_unlock_irqrestore(&mega_lock,cpuflag);\ }; u_long RDINDOOR (mega_host_config * megaCfg) { return readl (megaCfg->base + 0x20); } void WRINDOOR (mega_host_config * megaCfg, u_long value) { writel (value, megaCfg->base + 0x20); } u_long RDOUTDOOR (mega_host_config * megaCfg) { return readl (megaCfg->base + 0x2C); } void WROUTDOOR (mega_host_config * megaCfg, u_long value) { writel (value, megaCfg->base + 0x2C); } //================================================================ // // Function prototypes // //================================================================ static int MegaIssueCmd (mega_host_config * megaCfg, u_char * mboxData, mega_scb * scb, int intr); static int build_sglist (mega_host_config * megaCfg, mega_scb * scb, u_long * buffer, u_long * length); static void mega_runque (void *); static void mega_rundoneq (void); static void mega_cmd_done (mega_host_config *, mega_scb *, int); static mega_scb *mega_ioctl (mega_host_config * megaCfg, Scsi_Cmnd * SCpnt); /* set SERDEBUG to 1 to enable serial debugging */ #define SERDEBUG 0 #if SERDEBUG static void ser_init (void); static void ser_puts (char *str); static void ser_putc (char c); static int ser_printk (const char *fmt,...); #endif //================================================================ // // Global variables // //================================================================ static int numCtlrs = 0; static mega_host_config *megaCtlrs[12] = {0}; /* Change this to 0 if you want to see the raw drives */ static int use_raid = 1; /* Queue of pending/completed SCBs */ static mega_scb *qPending = NULL; static Scsi_Cmnd *qCompleted = NULL; volatile static spinlock_t mega_lock; static struct tq_struct runq = {0, 0, mega_runque, NULL}; struct proc_dir_entry proc_scsi_megaraid = { PROC_SCSI_MEGARAID, 8, "megaraid", S_IFDIR | S_IRUGO | S_IXUGO, 2 }; #if SERDEBUG static char strbuf[MAX_SERBUF + 1]; static void ser_init () { unsigned port = COM_BASE; outb (0x80, port + 3); outb (0, port + 1); /* 9600 Baud, if 19200: outb(6,port) */ outb (12, port); outb (3, port + 3); outb (0, port + 1); } static void ser_puts (char *str) { char *ptr; ser_init (); for (ptr = str; *ptr; ++ptr) ser_putc (*ptr); } static void ser_putc (char c) { unsigned port = COM_BASE; while ((inb (port + 5) & 0x20) == 0); outb (c, port); if (c == 0x0a) { while ((inb (port + 5) & 0x20) == 0); outb (0x0d, port); } } static int ser_printk (const char *fmt,...) { va_list args; int i; long flags; va_start (args, fmt); i = vsprintf (strbuf, fmt, args); ser_puts (strbuf); va_end (args); return i; } #define TRACE(a) { ser_printk a;} #else #define TRACE(A) #endif void callDone (Scsi_Cmnd * SCpnt) { if (SCpnt->result) { TRACE (("*** %.08lx %.02x <%d.%d.%d> = %x\n", SCpnt->serial_number, SCpnt->cmnd[0], SCpnt->channel, SCpnt->target, SCpnt->lun, SCpnt->result)); } SCpnt->scsi_done (SCpnt); } /*------------------------------------------------------------------------- * * Local functions * *-------------------------------------------------------------------------*/ //================================================ // Initialize SCB structures //================================================ static void initSCB (mega_host_config * megaCfg) { int idx; for (idx = 0; idx < megaCfg->max_cmds; idx++) { megaCfg->scbList[idx].idx = -1; megaCfg->scbList[idx].flag = 0; megaCfg->scbList[idx].sgList = NULL; megaCfg->scbList[idx].SCpnt = NULL; } } //=========================== // Allocate a SCB structure //=========================== static mega_scb * allocateSCB (mega_host_config * megaCfg, Scsi_Cmnd * SCpnt) { int idx; long flags; spin_lock_irqsave (&mega_lock, flags); for (idx = 0; idx < megaCfg->max_cmds; idx++) { if (megaCfg->scbList[idx].idx < 0) { /* Set Index and SCB pointer */ megaCfg->scbList[idx].flag = 0; megaCfg->scbList[idx].idx = idx; megaCfg->scbList[idx].SCpnt = SCpnt; megaCfg->scbList[idx].next = NULL; spin_unlock_irqrestore (&mega_lock, flags); if (megaCfg->scbList[idx].sgList == NULL) { megaCfg->scbList[idx].sgList = kmalloc (sizeof (mega_sglist) * MAX_SGLIST, GFP_ATOMIC | GFP_DMA); } return &megaCfg->scbList[idx]; } } spin_unlock_irqrestore (&mega_lock, flags); printk (KERN_WARNING "Megaraid: Could not allocate free SCB!!!\n"); return NULL; } //======================= // Free a SCB structure //======================= static void freeSCB (mega_scb * scb) { scb->flag = 0; scb->idx = -1; scb->next = NULL; scb->SCpnt = NULL; } /* Run through the list of completed requests */ static void mega_rundoneq () { mega_host_config *megaCfg; Scsi_Cmnd *SCpnt; long islogical; while (1) { DEQUEUE (SCpnt, Scsi_Cmnd, qCompleted, host_scribble); if (SCpnt == NULL) return; megaCfg = (mega_host_config *) SCpnt->host->hostdata; /* Check if we're allowing access to RAID drives or physical * if use_raid == 1 and this wasn't a disk on the max channel or * if use_raid == 0 and this was a disk on the max channel * then fail. */ islogical = (SCpnt->channel == megaCfg->host->max_channel) ? 1 : 0; if (SCpnt->cmnd[0] == INQUIRY && ((((u_char *) SCpnt->request_buffer)[0] & 0x1F) == TYPE_DISK) && (islogical != use_raid)) { SCpnt->result = 0xF0; } /* Convert result to error */ switch (SCpnt->result) { case 0x00: case 0x02: SCpnt->result |= (DID_OK << 16); break; case 0x8: SCpnt->result |= (DID_BUS_BUSY << 16); break; default: SCpnt->result |= (DID_BAD_TARGET << 16); break; } /* Callback */ callDone (SCpnt); } } /* Add command to the list of completed requests */ static void mega_cmd_done (mega_host_config * megaCfg, mega_scb * pScb, int status) { long flags; pScb->SCpnt->result = status; ENQUEUE (pScb->SCpnt, Scsi_Cmnd, qCompleted, host_scribble); spin_lock_irqsave (&mega_lock, flags); freeSCB (pScb); spin_unlock_irqrestore (&mega_lock, flags); } /*---------------------------------------------------- * Process pending queue list * * Run as a scheduled task *----------------------------------------------------*/ static void mega_runque (void *dummy) { mega_host_config *megaCfg; mega_scb *pScb; long flags; /* Take care of any completed requests */ mega_rundoneq (); DEQUEUE (pScb, mega_scb, qPending, next); if (pScb) { megaCfg = (mega_host_config *) pScb->SCpnt->host->hostdata; if (megaCfg->mbox->busy || megaCfg->flag & (IN_ISR | PENDING)) { TRACE (("%.08lx %.02x <%d.%d.%d> busy%d isr%d pending%d\n", pScb->SCpnt->serial_number, pScb->SCpnt->cmnd[0], pScb->SCpnt->channel, pScb->SCpnt->target, pScb->SCpnt->lun, megaCfg->mbox->busy, (megaCfg->flag & IN_ISR) ? 1 : 0, (megaCfg->flag & PENDING) ? 1 : 0)); } if (MegaIssueCmd (megaCfg, pScb->mboxData, pScb, 1)) { /* We're BUSY... come back later */ spin_lock_irqsave (&mega_lock, flags); pScb->next = qPending; qPending = pScb; spin_unlock_irqrestore (&mega_lock, flags); if (!(megaCfg->flag & PENDING)) { /* If PENDING, irq will schedule task */ queue_task (&runq, &tq_scheduler); } } } } /*------------------------------------------------------------------- * * Build a SCB from a Scsi_Cmnd * * Returns a SCB pointer, or NULL * If NULL is returned, the scsi_done function MUST have been called * *-------------------------------------------------------------------*/ static mega_scb * mega_build_cmd (mega_host_config * megaCfg, Scsi_Cmnd * SCpnt) { mega_scb *pScb; mega_mailbox *mbox; mega_passthru *pthru; long seg; if (SCpnt->cmnd[0] & 0x80) /* ioctl from megamgr */ return mega_ioctl (megaCfg, SCpnt); /* We don't support multi-luns */ if (SCpnt->lun != 0) { SCpnt->result = (DID_BAD_TARGET << 16); callDone (SCpnt); return NULL; } /*----------------------------------------------------- * * Logical drive commands * *-----------------------------------------------------*/ if (SCpnt->channel == megaCfg->host->max_channel) { switch (SCpnt->cmnd[0]) { case TEST_UNIT_READY: memset (SCpnt->request_buffer, 0, SCpnt->request_bufflen); SCpnt->result = (DID_OK << 16); callDone (SCpnt); return NULL; case MODE_SENSE: memset (SCpnt->request_buffer, 0, SCpnt->cmnd[4]); SCpnt->result = (DID_OK << 16); callDone (SCpnt); return NULL; case READ_CAPACITY: case INQUIRY: /* Allocate a SCB and initialize passthru */ if ((pScb = allocateSCB (megaCfg, SCpnt)) == NULL) { SCpnt->result = (DID_ERROR << 16); callDone (SCpnt); return NULL; } pthru = &pScb->pthru; mbox = (mega_mailbox *) & pScb->mboxData; memset (mbox, 0, sizeof (pScb->mboxData)); memset (pthru, 0, sizeof (mega_passthru)); pthru->timeout = 0; pthru->ars = 0; pthru->islogical = 1; pthru->logdrv = SCpnt->target; pthru->cdblen = SCpnt->cmd_len; pthru->dataxferaddr = virt_to_bus (SCpnt->request_buffer); pthru->dataxferlen = SCpnt->request_bufflen; memcpy (pthru->cdb, SCpnt->cmnd, SCpnt->cmd_len); /* Initialize mailbox area */ mbox->cmd = MEGA_MBOXCMD_PASSTHRU; mbox->xferaddr = virt_to_bus (pthru); return pScb; case READ_6: case WRITE_6: case READ_10: case WRITE_10: /* Allocate a SCB and initialize mailbox */ if ((pScb = allocateSCB (megaCfg, SCpnt)) == NULL) { SCpnt->result = (DID_ERROR << 16); callDone (SCpnt); return NULL; } mbox = (mega_mailbox *) & pScb->mboxData; memset (mbox, 0, sizeof (pScb->mboxData)); mbox->logdrv = SCpnt->target; mbox->cmd = (*SCpnt->cmnd == READ_6 || *SCpnt->cmnd == READ_10) ? MEGA_MBOXCMD_LREAD : MEGA_MBOXCMD_LWRITE; /* 6-byte */ if (*SCpnt->cmnd == READ_6 || *SCpnt->cmnd == WRITE_6) { mbox->numsectors = (u_long) SCpnt->cmnd[4]; mbox->lba = ((u_long) SCpnt->cmnd[1] << 16) | ((u_long) SCpnt->cmnd[2] << 8) | (u_long) SCpnt->cmnd[3]; mbox->lba &= 0x1FFFFF; } /* 10-byte */ if (*SCpnt->cmnd == READ_10 || *SCpnt->cmnd == WRITE_10) { mbox->numsectors = (u_long) SCpnt->cmnd[8] | ((u_long) SCpnt->cmnd[7] << 8); mbox->lba = ((u_long) SCpnt->cmnd[2] << 24) | ((u_long) SCpnt->cmnd[3] << 16) | ((u_long) SCpnt->cmnd[4] << 8) | (u_long) SCpnt->cmnd[5]; } /* Calculate Scatter-Gather info */ mbox->numsgelements = build_sglist (megaCfg, pScb, (u_long *) & mbox->xferaddr, (u_long *) & seg); return pScb; default: SCpnt->result = (DID_BAD_TARGET << 16); callDone (SCpnt); return NULL; } } /*----------------------------------------------------- * * Passthru drive commands * *-----------------------------------------------------*/ else { /* Allocate a SCB and initialize passthru */ if ((pScb = allocateSCB (megaCfg, SCpnt)) == NULL) { SCpnt->result = (DID_ERROR << 16); callDone (SCpnt); return NULL; } pthru = &pScb->pthru; mbox = (mega_mailbox *) pScb->mboxData; memset (mbox, 0, sizeof (pScb->mboxData)); memset (pthru, 0, sizeof (mega_passthru)); pthru->timeout = 0; pthru->ars = 0; pthru->islogical = 0; pthru->channel = SCpnt->channel; pthru->target = SCpnt->target; pthru->cdblen = SCpnt->cmd_len; memcpy (pthru->cdb, SCpnt->cmnd, SCpnt->cmd_len); pthru->numsgelements = build_sglist (megaCfg, pScb, (u_long *) & pthru->dataxferaddr, (u_long *) & pthru->dataxferlen); /* Initialize mailbox */ mbox->cmd = MEGA_MBOXCMD_PASSTHRU; mbox->xferaddr = virt_to_bus (pthru); return pScb; } return NULL; } /*-------------------------------------------------------------------- * build RAID commands for controller, passed down through ioctl() *--------------------------------------------------------------------*/ static mega_scb * mega_ioctl (mega_host_config * megaCfg, Scsi_Cmnd * SCpnt) { mega_scb *pScb; mega_ioctl_mbox *mbox; mega_mailbox *mailbox; mega_passthru *pthru; long seg; if ((pScb = allocateSCB (megaCfg, SCpnt)) == NULL) { SCpnt->result = (DID_ERROR << 16); callDone (SCpnt); return NULL; } mbox = (mega_ioctl_mbox *) & pScb->mboxData; mailbox = (mega_mailbox *) & pScb->mboxData; memset (mailbox, 0, sizeof (pScb->mboxData)); if (SCpnt->cmnd[0] == 0x83) { /* passthrough command */ char cdblen = SCpnt->cmnd[2]; pthru = &pScb->pthru; memset (pthru, 0, sizeof (mega_passthru)); pthru->islogical = SCpnt->cmnd[cdblen + 3] & 0x80; pthru->timeout = SCpnt->cmnd[cdblen + 3] & 0x07; pthru->reqsenselen = 10; /* ? MAX_SENSE; */ pthru->ars = SCpnt->cmnd[cdblen + 3] & 0x08; pthru->logdrv = SCpnt->cmnd[cdblen + 4]; pthru->channel = SCpnt->cmnd[cdblen + 5]; pthru->target = SCpnt->cmnd[cdblen + 6]; pthru->cdblen = cdblen; memcpy (pthru->cdb, SCpnt->cmnd, SCpnt->cmnd[2]); mailbox->cmd = MEGA_MBOXCMD_PASSTHRU; mailbox->xferaddr = virt_to_bus (pthru); pthru->numsgelements = build_sglist (megaCfg, pScb, (u_long *) & pthru->dataxferaddr, (u_long *) & pthru->dataxferlen); return pScb; } /* else normal (nonpassthru) command */ mbox->cmd = SCpnt->cmnd[0] & 0x7F; mbox->channel = SCpnt->cmnd[1]; mbox->param = SCpnt->cmnd[2]; mbox->pad[0] = SCpnt->cmnd[3]; mbox->logdrv = SCpnt->cmnd[4]; mbox->numsgelements = build_sglist (megaCfg, pScb, (u_long *) & mbox->xferaddr, (u_long *) & seg); return (pScb); } /*-------------------------------------------------------------------- * Interrupt service routine *--------------------------------------------------------------------*/ static void megaraid_isr (int irq, void *devp, struct pt_regs *regs) { mega_host_config *megaCfg; u_char byte, idx, sIdx; u_long dword; mega_mailbox *mbox; mega_scb *pScb; long flags; int qCnt, qStatus; megaCfg = (mega_host_config *) devp; mbox = (mega_mailbox *) megaCfg->mbox; if (megaCfg->host->irq == irq) { #if LINUX_VERSION_CODE >= 0x20100 spin_lock_irqsave (&io_request_lock, flags); #endif spin_lock_irqsave (&mega_lock, flags); if (megaCfg->flag & IN_ISR) { TRACE (("ISR called reentrantly!!\n")); } megaCfg->flag |= IN_ISR; /* Check if a valid interrupt is pending */ if (megaCfg->flag & BOARD_QUARTZ) { dword = RDOUTDOOR (megaCfg); if (dword != 0x10001234) { /* Spurious interrupt */ megaCfg->flag &= ~IN_ISR; spin_unlock_irqrestore (&mega_lock, flags); #if LINUX_VERSION_CODE >= 0x20100 spin_unlock_irqrestore (&io_request_lock, flags); #endif return; } WROUTDOOR (megaCfg, dword); } else { byte = READ_PORT (megaCfg->host->io_port, INTR_PORT); if ((byte & VALID_INTR_BYTE) == 0) { /* Spurious interrupt */ megaCfg->flag &= ~IN_ISR; spin_unlock_irqrestore (&mega_lock, flags); #if LINUX_VERSION_CODE >= 0x20100 spin_unlock_irqrestore (&io_request_lock, flags); #endif return; } WRITE_PORT (megaCfg->host->io_port, INTR_PORT, byte); } qCnt = mbox->numstatus; qStatus = mbox->status; if (qCnt > 1) { TRACE (("ISR: Received %d status\n", qCnt)) printk (KERN_DEBUG "Got numstatus = %d\n", qCnt); } for (idx = 0; idx < qCnt; idx++) { sIdx = mbox->completed[idx]; if (sIdx > 0) { pScb = &megaCfg->scbList[sIdx - 1]; spin_unlock_irqrestore (&mega_lock, flags); /* megalock within cmd_done */ mega_cmd_done (megaCfg, &megaCfg->scbList[sIdx - 1], qStatus); spin_lock_irqsave (&mega_lock, flags); } } if (megaCfg->flag & BOARD_QUARTZ) { WRINDOOR (megaCfg, virt_to_bus (megaCfg->mbox) | 0x2); while (RDINDOOR (megaCfg) & 0x02); } else { CLEAR_INTR (megaCfg->host->io_port); } megaCfg->flag &= ~IN_ISR; megaCfg->flag &= ~PENDING; spin_unlock_irqrestore (&mega_lock, flags); mega_runque (NULL); #if LINUX_VERSION_CODE >= 0x20100 spin_unlock_irqrestore (&io_request_lock, flags); #endif #if 0 /* Queue as a delayed ISR routine */ queue_task_irq_off (&runq, &tq_immediate); mark_bh (IMMEDIATE_BH); #endif } } /*==================================================*/ /* Wait until the controller's mailbox is available */ /*==================================================*/ static int busyWaitMbox (mega_host_config * megaCfg) { mega_mailbox *mbox = (mega_mailbox *) megaCfg->mbox; long counter; for (counter = 0; counter < 30000; counter++) { udelay (100); if (!mbox->busy) return 0; } return -1; /* give up after 3 seconds */ } //===================================================== // Post a command to the card // // Arguments: // mega_host_config *megaCfg - Controller structure // u_char *mboxData - Mailbox area, 16 bytes // mega_scb *pScb - SCB posting (or NULL if N/A) // int intr - if 1, interrupt, 0 is blocking //===================================================== static int MegaIssueCmd (mega_host_config * megaCfg, u_char * mboxData, mega_scb * pScb, int intr) { mega_mailbox *mbox = (mega_mailbox *) megaCfg->mbox; long flags; u_char byte; u_long cmdDone; mboxData[0x1] = (pScb ? pScb->idx + 1 : 0x00); /* Set cmdid */ mboxData[0xF] = 1; /* Set busy */ spin_lock_irqsave(&mega_lock,flags); /* one bad report of problem when issuing a command while pending. * Wasn't able to duplicate, but it doesn't really affect performance * anyway, so don't allow command while PENDING */ if (megaCfg->flag & PENDING) { spin_unlock_irqrestore(&mega_lock,flags); return -1; } /* Wait until mailbox is free */ if (busyWaitMbox (megaCfg)) { if (pScb) { TRACE (("Mailbox busy %.08lx <%d.%d.%d>\n", pScb->SCpnt->serial_number, pScb->SCpnt->channel, pScb->SCpnt->target, pScb->SCpnt->lun)); } else { TRACE(("pScb NULL in MegaIssueCmd!\n")); } spin_unlock_irqrestore(&mega_lock,flags); return -1; } /* Copy mailbox data into host structure */ memset (mbox, 0, sizeof (mega_mailbox)); memcpy (mbox, mboxData, 16); /* Kick IO */ megaCfg->flag |= PENDING; if (intr) { /* Issue interrupt (non-blocking) command */ if (megaCfg->flag & BOARD_QUARTZ) { mbox->mraid_poll = 0; mbox->mraid_ack = 0; WRINDOOR (megaCfg, virt_to_bus (megaCfg->mbox) | 0x1); } else { ENABLE_INTR (megaCfg->host->io_port); ISSUE_COMMAND (megaCfg->host->io_port); } spin_unlock_irqrestore(&mega_lock,flags); } else { /* Issue non-ISR (blocking) command */ if (megaCfg->flag & BOARD_QUARTZ) { mbox->mraid_poll = 0; mbox->mraid_ack = 0; WRINDOOR (megaCfg, virt_to_bus (megaCfg->mbox) | 0x1); while ((cmdDone = RDOUTDOOR (megaCfg)) != 0x10001234); WROUTDOOR (megaCfg, cmdDone); spin_unlock_irqrestore(&mega_lock,flags); if (pScb) { mega_cmd_done (megaCfg, pScb, mbox->status); mega_rundoneq (); } WRINDOOR (megaCfg, virt_to_bus (megaCfg->mbox) | 0x2); while (RDINDOOR (megaCfg) & 0x2); megaCfg->flag &= ~PENDING; } else { DISABLE_INTR (megaCfg->host->io_port); ISSUE_COMMAND (megaCfg->host->io_port); while (!((byte = READ_PORT (megaCfg->host->io_port, INTR_PORT)) & INTR_VALID)); WRITE_PORT (megaCfg->host->io_port, INTR_PORT, byte); ENABLE_INTR (megaCfg->host->io_port); CLEAR_INTR (megaCfg->host->io_port); megaCfg->flag &= ~PENDING; spin_unlock_irqrestore(&mega_lock,flags); if (pScb) { mega_cmd_done (megaCfg, pScb, mbox->status); mega_rundoneq (); } else { TRACE (("Error: NULL pScb!\n")); } } } return 0; } /*------------------------------------------------------------------- * Copies data to SGLIST *-------------------------------------------------------------------*/ static int build_sglist (mega_host_config * megaCfg, mega_scb * scb, u_long * buffer, u_long * length) { struct scatterlist *sgList; int idx; /* Scatter-gather not used */ if (scb->SCpnt->use_sg == 0) { *buffer = virt_to_bus (scb->SCpnt->request_buffer); *length = (u_long) scb->SCpnt->request_bufflen; return 0; } sgList = (struct scatterlist *) scb->SCpnt->buffer; if (scb->SCpnt->use_sg == 1) { *buffer = virt_to_bus (sgList[0].address); *length = (u_long) sgList[0].length; return 0; } /* Copy Scatter-Gather list info into controller structure */ for (idx = 0; idx < scb->SCpnt->use_sg; idx++) { scb->sgList[idx].address = virt_to_bus (sgList[idx].address); scb->sgList[idx].length = (u_long) sgList[idx].length; } /* Reset pointer and length fields */ *buffer = virt_to_bus (scb->sgList); *length = 0; /* Return count of SG requests */ return scb->SCpnt->use_sg; } /*-------------------------------------------------------------------- * Initializes the adress of the controller's mailbox register * The mailbox register is used to issue commands to the card. * Format of the mailbox area: * 00 01 command * 01 01 command id * 02 02 # of sectors * 04 04 logical bus address * 08 04 physical buffer address * 0C 01 logical drive # * 0D 01 length of scatter/gather list * 0E 01 reserved * 0F 01 mailbox busy * 10 01 numstatus byte * 11 01 status byte *--------------------------------------------------------------------*/ static int mega_register_mailbox (mega_host_config * megaCfg, u_long paddr) { /* align on 16-byte boundry */ megaCfg->mbox = &megaCfg->mailbox; megaCfg->mbox = (mega_mailbox *) ((((ulong) megaCfg->mbox) + 16) & 0xfffffff0); paddr = (paddr + 16) & 0xfffffff0; /* Register mailbox area with the firmware */ if (megaCfg->flag & BOARD_QUARTZ) { } else { WRITE_PORT (megaCfg->host->io_port, MBOX_PORT0, paddr & 0xFF); WRITE_PORT (megaCfg->host->io_port, MBOX_PORT1, (paddr >> 8) & 0xFF); WRITE_PORT (megaCfg->host->io_port, MBOX_PORT2, (paddr >> 16) & 0xFF); WRITE_PORT (megaCfg->host->io_port, MBOX_PORT3, (paddr >> 24) & 0xFF); WRITE_PORT (megaCfg->host->io_port, ENABLE_MBOX_REGION, ENABLE_MBOX_BYTE); CLEAR_INTR (megaCfg->host->io_port); ENABLE_INTR (megaCfg->host->io_port); } return 0; } /*------------------------------------------------------------------- * Issue an adapter info query to the controller *-------------------------------------------------------------------*/ static int mega_i_query_adapter (mega_host_config * megaCfg) { mega_RAIDINQ *adapterInfo; mega_mailbox *mbox; u_char mboxData[16]; u_long paddr; spin_lock_init (&mega_lock); /* Initialize adapter inquiry */ paddr = virt_to_bus (megaCfg->mega_buffer); mbox = (mega_mailbox *) mboxData; memset ((void *) megaCfg->mega_buffer, 0, sizeof (megaCfg->mega_buffer)); memset (mbox, 0, 16); /* Initialize mailbox registers */ mbox->cmd = MEGA_MBOXCMD_ADAPTERINQ; mbox->xferaddr = paddr; /* Issue a blocking command to the card */ MegaIssueCmd (megaCfg, mboxData, NULL, 0); /* Initialize host/local structures with Adapter info */ adapterInfo = (mega_RAIDINQ *) megaCfg->mega_buffer; megaCfg->host->max_channel = adapterInfo->AdpInfo.ChanPresent; /* megaCfg->host->max_id = adapterInfo->AdpInfo.MaxTargPerChan; */ megaCfg->host->max_id = 9; /* max logical drives + 1 */ megaCfg->numldrv = adapterInfo->LogdrvInfo.NumLDrv; #if 0 printk ("KERN_DEBUG ---- Logical drive info ----\n"); for (i = 0; i < megaCfg->numldrv; i++) { printk ("%d: size: %ld prop: %x state: %x\n", i, adapterInfo->LogdrvInfo.LDrvSize[i], adapterInfo->LogdrvInfo.LDrvProp[i], adapterInfo->LogdrvInfo.LDrvState[i]); } printk (KERN_DEBUG "---- Physical drive info ----\n"); for (i = 0; i < MAX_PHYSICAL_DRIVES; i++) { if (i && !(i % 8)) printk ("\n"); printk ("%d: %x ", i, adapterInfo->PhysdrvInfo.PDrvState[i]); } printk ("\n"); #endif megaCfg->max_cmds = adapterInfo->AdpInfo.MaxConcCmds; #ifdef HP /* use HP firmware and bios version encoding */ sprintf (megaCfg->fwVer, "%c%d%d.%d%d", adapterInfo->AdpInfo.FwVer[2], adapterInfo->AdpInfo.FwVer[1] >> 8, adapterInfo->AdpInfo.FwVer[1] & 0x0f, adapterInfo->AdpInfo.FwVer[2] >> 8, adapterInfo->AdpInfo.FwVer[2] & 0x0f); sprintf (megaCfg->biosVer, "%c%d%d.%d%d", adapterInfo->AdpInfo.BiosVer[2], adapterInfo->AdpInfo.BiosVer[1] >> 8, adapterInfo->AdpInfo.BiosVer[1] & 0x0f, adapterInfo->AdpInfo.BiosVer[2] >> 8, adapterInfo->AdpInfo.BiosVer[2] & 0x0f); #else memcpy (megaCfg->fwVer, adapterInfo->AdpInfo.FwVer, 4); megaCfg->fwVer[4] = 0; memcpy (megaCfg->biosVer, adapterInfo->AdpInfo.BiosVer, 4); megaCfg->biosVer[4] = 0; #endif printk (KERN_INFO "megaraid: [%s:%s] detected %d logical drives" CRLFSTR, megaCfg->fwVer, megaCfg->biosVer, megaCfg->numldrv); return 0; } /*------------------------------------------------------------------------- * * Driver interface functions * *-------------------------------------------------------------------------*/ /*---------------------------------------------------------- * Returns data to be displayed in /proc/scsi/megaraid/X *----------------------------------------------------------*/ int megaraid_proc_info (char *buffer, char **start, off_t offset, int length, int inode, int inout) { *start = buffer; return 0; } int findCard (Scsi_Host_Template * pHostTmpl, u_short pciVendor, u_short pciDev, long flag) { mega_host_config *megaCfg; struct Scsi_Host *host; u_char pciBus, pciDevFun, megaIrq; u_long megaBase; u_short pciIdx = 0; #if LINUX_VERSION_CODE < 0x20100 while (!pcibios_find_device (pciVendor, pciDev, pciIdx, &pciBus, &pciDevFun)) { if (flag & BOARD_QUARTZ) { u_int magic; pcibios_read_config_dword (pciBus, pciDevFun, PCI_CONF_AMISIG, &magic); if (magic != AMI_SIGNATURE) { continue; /* not an AMI board */ } } #else struct pci_dev *pdev = pci_devices; while ((pdev = pci_find_device (pciVendor, pciDev, pdev))) { pciBus = pdev->bus->number; pciDevFun = pdev->devfn; #endif printk (KERN_INFO "megaraid: found 0x%4.04x:0x%4.04x:idx %d:bus %d:slot %d:fun %d\n", pciVendor, pciDev, pciIdx, pciBus, PCI_SLOT (pciDevFun), PCI_FUNC (pciDevFun)); /* Read the base port and IRQ from PCI */ #if LINUX_VERSION_CODE < 0x20100 pcibios_read_config_dword (pciBus, pciDevFun, PCI_BASE_ADDRESS_0, (u_int *) & megaBase); pcibios_read_config_byte (pciBus, pciDevFun, PCI_INTERRUPT_LINE, &megaIrq); #else megaBase = pdev->base_address[0]; megaIrq = pdev->irq; #endif pciIdx++; if (flag & BOARD_QUARTZ) { megaBase &= PCI_BASE_ADDRESS_MEM_MASK; megaBase = (long) ioremap (megaBase, 128); } else { megaBase &= PCI_BASE_ADDRESS_IO_MASK; megaBase += 0x10; } /* Initialize SCSI Host structure */ host = scsi_register (pHostTmpl, sizeof (mega_host_config)); megaCfg = (mega_host_config *) host->hostdata; memset (megaCfg, 0, sizeof (mega_host_config)); printk (KERN_INFO " scsi%d: Found a MegaRAID controller at 0x%x, IRQ: %d" CRLFSTR, host->host_no, (u_int) megaBase, megaIrq); /* Copy resource info into structure */ megaCfg->flag = flag; megaCfg->host = host; megaCfg->base = megaBase; megaCfg->host->irq = megaIrq; megaCfg->host->io_port = megaBase; megaCfg->host->n_io_port = 16; megaCfg->host->unique_id = (pciBus << 8) | pciDevFun; megaCtlrs[numCtlrs++] = megaCfg; if (flag != BOARD_QUARTZ) { /* Request our IO Range */ if (check_region (megaBase, 16)) { printk (KERN_WARNING "megaraid: Couldn't register I/O range!" CRLFSTR); scsi_unregister (host); continue; } request_region (megaBase, 16, "megaraid"); } /* Request our IRQ */ if (request_irq (megaIrq, megaraid_isr, SA_SHIRQ, "megaraid", megaCfg)) { printk (KERN_WARNING "megaraid: Couldn't register IRQ %d!" CRLFSTR, megaIrq); scsi_unregister (host); continue; } mega_register_mailbox (megaCfg, virt_to_bus ((void *) &megaCfg->mailbox)); mega_i_query_adapter (megaCfg); /* Initialize SCBs */ initSCB (megaCfg); } return pciIdx; } /*--------------------------------------------------------- * Detects if a megaraid controller exists in this system *---------------------------------------------------------*/ int megaraid_detect (Scsi_Host_Template * pHostTmpl) { int count = 0; pHostTmpl->proc_dir = &proc_scsi_megaraid; #if LINUX_VERSION_CODE < 0x20100 if (!pcibios_present ()) { printk (KERN_WARNING "megaraid: PCI bios not present." CRLFSTR); return 0; } #endif count += findCard (pHostTmpl, 0x101E, 0x9010, 0); count += findCard (pHostTmpl, 0x101E, 0x9060, 0); count += findCard (pHostTmpl, 0x8086, 0x1960, BOARD_QUARTZ); return count; } /*--------------------------------------------------------------------- * Release the controller's resources *---------------------------------------------------------------------*/ int megaraid_release (struct Scsi_Host *pSHost) { mega_host_config *megaCfg; mega_mailbox *mbox; u_char mboxData[16]; int i; megaCfg = (mega_host_config *) pSHost->hostdata; mbox = (mega_mailbox *) mboxData; /* Flush cache to disk */ memset (mbox, 0, 16); mboxData[0] = 0xA; /* Issue a blocking (interrupts disabled) command to the card */ MegaIssueCmd (megaCfg, mboxData, NULL, 0); schedule (); /* Free our resources */ if (megaCfg->flag & BOARD_QUARTZ) { iounmap ((void *) megaCfg->base); } else { release_region (megaCfg->host->io_port, 16); } free_irq (megaCfg->host->irq, megaCfg); /* Must be freed first, otherwise extra interrupt is generated */ for (i = 0; i < megaCfg->max_cmds; i++) { if (megaCfg->scbList[i].sgList) kfree (megaCfg->scbList[i].sgList); /* free sgList */ } scsi_unregister (pSHost); return 0; } /*---------------------------------------------- * Get information about the card/driver *----------------------------------------------*/ const char * megaraid_info (struct Scsi_Host *pSHost) { static char buffer[512]; mega_host_config *megaCfg; mega_RAIDINQ *adapterInfo; megaCfg = (mega_host_config *) pSHost->hostdata; adapterInfo = (mega_RAIDINQ *) megaCfg->mega_buffer; sprintf (buffer, "AMI MegaRAID %s %d commands %d targs %d chans", megaCfg->fwVer, adapterInfo->AdpInfo.MaxConcCmds, megaCfg->host->max_id, megaCfg->host->max_channel); return buffer; } /*----------------------------------------------------------------- * Perform a SCSI command * Mailbox area: * 00 01 command * 01 01 command id * 02 02 # of sectors * 04 04 logical bus address * 08 04 physical buffer address * 0C 01 logical drive # * 0D 01 length of scatter/gather list * 0E 01 reserved * 0F 01 mailbox busy * 10 01 numstatus byte * 11 01 status byte *-----------------------------------------------------------------*/ int megaraid_queue (Scsi_Cmnd * SCpnt, void (*pktComp) (Scsi_Cmnd *)) { mega_host_config *megaCfg; mega_scb *pScb; megaCfg = (mega_host_config *) SCpnt->host->hostdata; if (!(megaCfg->flag & (1L << SCpnt->channel))) { printk (KERN_INFO "scsi%d: scanning channel %c for devices.\n", megaCfg->host->host_no, SCpnt->channel + 'A'); megaCfg->flag |= (1L << SCpnt->channel); } SCpnt->scsi_done = pktComp; /* Allocate and build a SCB request */ if ((pScb = mega_build_cmd (megaCfg, SCpnt)) != NULL) { /* Add SCB to the head of the pending queue */ ENQUEUE (pScb, mega_scb, qPending, next); /* Issue the command to the card */ mega_runque (NULL); } return 0; } /*---------------------------------------------------------------------- * Issue a blocking command to the controller *----------------------------------------------------------------------*/ volatile static int internal_done_flag = 0; volatile static int internal_done_errcode = 0; static void internal_done (Scsi_Cmnd * SCpnt) { internal_done_errcode = SCpnt->result; internal_done_flag++; } /* * This seems dangerous in an SMP environment because * while spinning on internal_done_flag in 2.0.x SMP * no IRQ's will be taken, including those that might * be needed to clear this. * * I think this should be using a wait queue ? * -- AC */ /* * I'll probably fix this in the next version, but * megaraid_command() will never get called since can_queue is set, * except maybe in a *really* old kernel in which case it's very * unlikely they'd be using SMP anyway. Really this function is * just here for completeness. * - JLJ */ int megaraid_command (Scsi_Cmnd * SCpnt) { internal_done_flag = 0; /* Queue command, and wait until it has completed */ megaraid_queue (SCpnt, internal_done); while (!internal_done_flag) barrier (); return internal_done_errcode; } /*--------------------------------------------------------------------- * Abort a previous SCSI request *---------------------------------------------------------------------*/ int megaraid_abort (Scsi_Cmnd * SCpnt) { mega_host_config *megaCfg; int idx; long flags; spin_lock_irqsave (&mega_lock, flags); megaCfg = (mega_host_config *) SCpnt->host->hostdata; TRACE (("ABORT!!! %.08lx %.02x <%d.%d.%d>\n", SCpnt->serial_number, SCpnt->cmnd[0], SCpnt->channel, SCpnt->target, SCpnt->lun)); /* * Walk list of SCBs for any that are still outstanding */ for (idx = 0; idx < megaCfg->max_cmds; idx++) { if (megaCfg->scbList[idx].idx >= 0) { if (megaCfg->scbList[idx].SCpnt == SCpnt) { freeSCB (&megaCfg->scbList[idx]); SCpnt->result = (DID_RESET << 16) | (SUGGEST_RETRY << 24); callDone (SCpnt); } } } spin_unlock_irqrestore (&mega_lock, flags); return SCSI_ABORT_SNOOZE; } /*--------------------------------------------------------------------- * Reset a previous SCSI request *---------------------------------------------------------------------*/ int megaraid_reset (Scsi_Cmnd * SCpnt, unsigned int rstflags) { mega_host_config *megaCfg; int idx; long flags; spin_lock_irqsave (&mega_lock, flags); megaCfg = (mega_host_config *) SCpnt->host->hostdata; TRACE (("RESET: %.08lx %.02x <%d.%d.%d>\n", SCpnt->serial_number, SCpnt->cmnd[0], SCpnt->channel, SCpnt->target, SCpnt->lun)); /* * Walk list of SCBs for any that are still outstanding */ for (idx = 0; idx < megaCfg->max_cmds; idx++) { if (megaCfg->scbList[idx].idx >= 0) { SCpnt = megaCfg->scbList[idx].SCpnt; freeSCB (&megaCfg->scbList[idx]); SCpnt->result = (DID_RESET << 16) | (SUGGEST_RETRY << 24); callDone (SCpnt); } } spin_unlock_irqrestore (&mega_lock, flags); return SCSI_RESET_PUNT; } /*------------------------------------------------------------- * Return the disk geometry for a particular disk * Input: * Disk *disk - Disk geometry * kdev_t dev - Device node * int *geom - Returns geometry fields * geom[0] = heads * geom[1] = sectors * geom[2] = cylinders *-------------------------------------------------------------*/ int megaraid_biosparam (Disk * disk, kdev_t dev, int *geom) { int heads, sectors, cylinders; mega_host_config *megaCfg; /* Get pointer to host config structure */ megaCfg = (mega_host_config *) disk->device->host->hostdata; /* Default heads (64) & sectors (32) */ heads = 64; sectors = 32; cylinders = disk->capacity / (heads * sectors); /* Handle extended translation size for logical drives > 1Gb */ if (disk->capacity >= 0x200000) { heads = 255; sectors = 63; cylinders = disk->capacity / (heads * sectors); } /* return result */ geom[0] = heads; geom[1] = sectors; geom[2] = cylinders; return 0; } #ifdef MODULE Scsi_Host_Template driver_template = MEGARAID; #include "scsi_module.c" #endif