diff options
author | Stephen M. Cameron <scameron@beardog.cce.hp.com> | 2009-12-08 14:09:11 -0800 |
---|---|---|
committer | James Bottomley <James.Bottomley@suse.de> | 2009-12-10 09:55:00 -0600 |
commit | edd163687ea59f01d6b43c9e1fdaa0126fa30191 (patch) | |
tree | 419b8b983cbc7c72ffa3917ce8b706caa63084a1 /drivers/scsi | |
parent | 0109abffbf91b76e2a34e324a407d780a55fc1ab (diff) |
[SCSI] hpsa: add driver for HP Smart Array controllers.
This driver supports a subset of HP Smart Array Controllers.
It is a SCSI alternative to the cciss driver.
[akpm@linux-foundation.org: avoid helpful cleanup patches]
[achiang@hp.com: make device attrs static]
[akpm@linux-foundation.org: msleep() does set_current_state() itself]
Signed-off-by: Stephen M. Cameron <scameron@beardog.cce.hp.com>
Signed-off-by: Mike Miller <mikem@beardog.cce.hp.com>
Signed-off-by: Alex Chiang <achiang@hp.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: James Bottomley <James.Bottomley@suse.de>
Diffstat (limited to 'drivers/scsi')
-rw-r--r-- | drivers/scsi/Kconfig | 10 | ||||
-rw-r--r-- | drivers/scsi/Makefile | 1 | ||||
-rw-r--r-- | drivers/scsi/hpsa.c | 3531 | ||||
-rw-r--r-- | drivers/scsi/hpsa.h | 273 | ||||
-rw-r--r-- | drivers/scsi/hpsa_cmd.h | 326 |
5 files changed, 4141 insertions, 0 deletions
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 36900c71a59..9191d1ea645 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -388,6 +388,16 @@ config BLK_DEV_3W_XXXX_RAID Please read the comments at the top of <file:drivers/scsi/3w-xxxx.c>. +config SCSI_HPSA + tristate "HP Smart Array SCSI driver" + depends on PCI && SCSI + help + This driver supports HP Smart Array Controllers (circa 2009). + It is a SCSI alternative to the cciss driver, which is a block + driver. Anyone wishing to use HP Smart Array controllers who + would prefer the devices be presented to linux as SCSI devices, + rather than as generic block devices should say Y here. + config SCSI_3W_9XXX tristate "3ware 9xxx SATA-RAID support" depends on PCI && SCSI diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 280d3c657d6..92a8c500b23 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -91,6 +91,7 @@ obj-$(CONFIG_SCSI_BFA_FC) += bfa/ obj-$(CONFIG_SCSI_PAS16) += pas16.o obj-$(CONFIG_SCSI_T128) += t128.o obj-$(CONFIG_SCSI_DMX3191D) += dmx3191d.o +obj-$(CONFIG_SCSI_HPSA) += hpsa.o obj-$(CONFIG_SCSI_DTC3280) += dtc.o obj-$(CONFIG_SCSI_SYM53C8XX_2) += sym53c8xx_2/ obj-$(CONFIG_SCSI_ZALON) += zalon7xx.o diff --git a/drivers/scsi/hpsa.c b/drivers/scsi/hpsa.c new file mode 100644 index 00000000000..bb96fdd58e2 --- /dev/null +++ b/drivers/scsi/hpsa.c @@ -0,0 +1,3531 @@ +/* + * Disk Array driver for HP Smart Array SAS controllers + * Copyright 2000, 2009 Hewlett-Packard Development Company, L.P. + * + * 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; version 2 of the License. + * + * 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, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Questions/Comments/Bugfixes to iss_storagedev@hp.com + * + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/timer.h> +#include <linux/seq_file.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/smp_lock.h> +#include <linux/compat.h> +#include <linux/blktrace_api.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/completion.h> +#include <linux/moduleparam.h> +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.h> +#include <linux/cciss_ioctl.h> +#include <linux/string.h> +#include <linux/bitmap.h> +#include <asm/atomic.h> +#include <linux/kthread.h> +#include "hpsa_cmd.h" +#include "hpsa.h" + +/* HPSA_DRIVER_VERSION must be 3 byte values (0-255) separated by '.' */ +#define HPSA_DRIVER_VERSION "1.0.0" +#define DRIVER_NAME "HP HPSA Driver (v " HPSA_DRIVER_VERSION ")" + +/* How long to wait (in milliseconds) for board to go into simple mode */ +#define MAX_CONFIG_WAIT 30000 +#define MAX_IOCTL_CONFIG_WAIT 1000 + +/*define how many times we will try a command because of bus resets */ +#define MAX_CMD_RETRIES 3 + +/* Embedded module documentation macros - see modules.h */ +MODULE_AUTHOR("Hewlett-Packard Company"); +MODULE_DESCRIPTION("Driver for HP Smart Array Controller version " \ + HPSA_DRIVER_VERSION); +MODULE_SUPPORTED_DEVICE("HP Smart Array Controllers"); +MODULE_VERSION(HPSA_DRIVER_VERSION); +MODULE_LICENSE("GPL"); + +static int hpsa_allow_any; +module_param(hpsa_allow_any, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(hpsa_allow_any, + "Allow hpsa driver to access unknown HP Smart Array hardware"); + +/* define the PCI info for the cards we can control */ +static const struct pci_device_id hpsa_pci_device_id[] = { + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSC, 0x103C, 0x3223}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSC, 0x103C, 0x3234}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSC, 0x103C, 0x323D}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSE, 0x103C, 0x3241}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSE, 0x103C, 0x3243}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSE, 0x103C, 0x3245}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSE, 0x103C, 0x3247}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSE, 0x103C, 0x3249}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSE, 0x103C, 0x324a}, + {PCI_VENDOR_ID_HP, PCI_DEVICE_ID_HP_CISSE, 0x103C, 0x324b}, + {PCI_VENDOR_ID_HP, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_STORAGE_RAID << 8, 0xffff << 8, 0}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, hpsa_pci_device_id); + +/* board_id = Subsystem Device ID & Vendor ID + * product = Marketing Name for the board + * access = Address of the struct of function pointers + */ +static struct board_type products[] = { + {0x3223103C, "Smart Array P800", &SA5_access}, + {0x3234103C, "Smart Array P400", &SA5_access}, + {0x323d103c, "Smart Array P700M", &SA5_access}, + {0x3241103C, "Smart Array P212", &SA5_access}, + {0x3243103C, "Smart Array P410", &SA5_access}, + {0x3245103C, "Smart Array P410i", &SA5_access}, + {0x3247103C, "Smart Array P411", &SA5_access}, + {0x3249103C, "Smart Array P812", &SA5_access}, + {0x324a103C, "Smart Array P712m", &SA5_access}, + {0x324b103C, "Smart Array P711m", &SA5_access}, + {0xFFFF103C, "Unknown Smart Array", &SA5_access}, +}; + +static int number_of_controllers; + +static irqreturn_t do_hpsa_intr(int irq, void *dev_id); +static int hpsa_ioctl(struct scsi_device *dev, int cmd, void *arg); +static void start_io(struct ctlr_info *h); + +#ifdef CONFIG_COMPAT +static int hpsa_compat_ioctl(struct scsi_device *dev, int cmd, void *arg); +#endif + +static void cmd_free(struct ctlr_info *h, struct CommandList *c); +static void cmd_special_free(struct ctlr_info *h, struct CommandList *c); +static struct CommandList *cmd_alloc(struct ctlr_info *h); +static struct CommandList *cmd_special_alloc(struct ctlr_info *h); +static void fill_cmd(struct CommandList *c, __u8 cmd, struct ctlr_info *h, + void *buff, size_t size, __u8 page_code, unsigned char *scsi3addr, + int cmd_type); + +static int hpsa_scsi_queue_command(struct scsi_cmnd *cmd, + void (*done)(struct scsi_cmnd *)); + +static int hpsa_eh_device_reset_handler(struct scsi_cmnd *scsicmd); +static int hpsa_slave_alloc(struct scsi_device *sdev); +static void hpsa_slave_destroy(struct scsi_device *sdev); + +static ssize_t raid_level_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t lunid_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t unique_id_show(struct device *dev, + struct device_attribute *attr, char *buf); +static void hpsa_update_scsi_devices(struct ctlr_info *h, int hostno); +static ssize_t host_store_rescan(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static int check_for_unit_attention(struct ctlr_info *h, + struct CommandList *c); +static void check_ioctl_unit_attention(struct ctlr_info *h, + struct CommandList *c); + +static DEVICE_ATTR(raid_level, S_IRUGO, raid_level_show, NULL); +static DEVICE_ATTR(lunid, S_IRUGO, lunid_show, NULL); +static DEVICE_ATTR(unique_id, S_IRUGO, unique_id_show, NULL); +static DEVICE_ATTR(rescan, S_IWUSR, NULL, host_store_rescan); + +static struct device_attribute *hpsa_sdev_attrs[] = { + &dev_attr_raid_level, + &dev_attr_lunid, + &dev_attr_unique_id, + NULL, +}; + +static struct device_attribute *hpsa_shost_attrs[] = { + &dev_attr_rescan, + NULL, +}; + +static struct scsi_host_template hpsa_driver_template = { + .module = THIS_MODULE, + .name = "hpsa", + .proc_name = "hpsa", + .queuecommand = hpsa_scsi_queue_command, + .can_queue = 512, + .this_id = -1, + .sg_tablesize = MAXSGENTRIES, + .cmd_per_lun = 512, + .use_clustering = ENABLE_CLUSTERING, + .eh_device_reset_handler = hpsa_eh_device_reset_handler, + .ioctl = hpsa_ioctl, + .slave_alloc = hpsa_slave_alloc, + .slave_destroy = hpsa_slave_destroy, +#ifdef CONFIG_COMPAT + .compat_ioctl = hpsa_compat_ioctl, +#endif + .sdev_attrs = hpsa_sdev_attrs, + .shost_attrs = hpsa_shost_attrs, +}; + +static inline struct ctlr_info *sdev_to_hba(struct scsi_device *sdev) +{ + unsigned long *priv = shost_priv(sdev->host); + return (struct ctlr_info *) *priv; +} + +static struct task_struct *hpsa_scan_thread; +static DEFINE_MUTEX(hpsa_scan_mutex); +static LIST_HEAD(hpsa_scan_q); +static int hpsa_scan_func(void *data); + +/** + * add_to_scan_list() - add controller to rescan queue + * @h: Pointer to the controller. + * + * Adds the controller to the rescan queue if not already on the queue. + * + * returns 1 if added to the queue, 0 if skipped (could be on the + * queue already, or the controller could be initializing or shutting + * down). + **/ +static int add_to_scan_list(struct ctlr_info *h) +{ + struct ctlr_info *test_h; + int found = 0; + int ret = 0; + + if (h->busy_initializing) + return 0; + + /* + * If we don't get the lock, it means the driver is unloading + * and there's no point in scheduling a new scan. + */ + if (!mutex_trylock(&h->busy_shutting_down)) + return 0; + + mutex_lock(&hpsa_scan_mutex); + list_for_each_entry(test_h, &hpsa_scan_q, scan_list) { + if (test_h == h) { + found = 1; + break; + } + } + if (!found && !h->busy_scanning) { + INIT_COMPLETION(h->scan_wait); + list_add_tail(&h->scan_list, &hpsa_scan_q); + ret = 1; + } + mutex_unlock(&hpsa_scan_mutex); + mutex_unlock(&h->busy_shutting_down); + + return ret; +} + +/** + * remove_from_scan_list() - remove controller from rescan queue + * @h: Pointer to the controller. + * + * Removes the controller from the rescan queue if present. Blocks if + * the controller is currently conducting a rescan. The controller + * can be in one of three states: + * 1. Doesn't need a scan + * 2. On the scan list, but not scanning yet (we remove it) + * 3. Busy scanning (and not on the list). In this case we want to wait for + * the scan to complete to make sure the scanning thread for this + * controller is completely idle. + **/ +static void remove_from_scan_list(struct ctlr_info *h) +{ + struct ctlr_info *test_h, *tmp_h; + + mutex_lock(&hpsa_scan_mutex); + list_for_each_entry_safe(test_h, tmp_h, &hpsa_scan_q, scan_list) { + if (test_h == h) { /* state 2. */ + list_del(&h->scan_list); + complete_all(&h->scan_wait); + mutex_unlock(&hpsa_scan_mutex); + return; + } + } + if (h->busy_scanning) { /* state 3. */ + mutex_unlock(&hpsa_scan_mutex); + wait_for_completion(&h->scan_wait); + } else { /* state 1, nothing to do. */ + mutex_unlock(&hpsa_scan_mutex); + } +} + +/* hpsa_scan_func() - kernel thread used to rescan controllers + * @data: Ignored. + * + * A kernel thread used scan for drive topology changes on + * controllers. The thread processes only one controller at a time + * using a queue. Controllers are added to the queue using + * add_to_scan_list() and removed from the queue either after done + * processing or using remove_from_scan_list(). + * + * returns 0. + **/ +static int hpsa_scan_func(__attribute__((unused)) void *data) +{ + struct ctlr_info *h; + int host_no; + + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + if (kthread_should_stop()) + break; + + while (1) { + mutex_lock(&hpsa_scan_mutex); + if (list_empty(&hpsa_scan_q)) { + mutex_unlock(&hpsa_scan_mutex); + break; + } + h = list_entry(hpsa_scan_q.next, struct ctlr_info, + scan_list); + list_del(&h->scan_list); + h->busy_scanning = 1; + mutex_unlock(&hpsa_scan_mutex); + host_no = h->scsi_host ? h->scsi_host->host_no : -1; + hpsa_update_scsi_devices(h, host_no); + complete_all(&h->scan_wait); + mutex_lock(&hpsa_scan_mutex); + h->busy_scanning = 0; + mutex_unlock(&hpsa_scan_mutex); + } + } + return 0; +} + +static int check_for_unit_attention(struct ctlr_info *h, + struct CommandList *c) +{ + if (c->err_info->SenseInfo[2] != UNIT_ATTENTION) + return 0; + + switch (c->err_info->SenseInfo[12]) { + case STATE_CHANGED: + dev_warn(&h->pdev->dev, "hpsa%d: a state change " + "detected, command retried\n", h->ctlr); + break; + case LUN_FAILED: + dev_warn(&h->pdev->dev, "hpsa%d: LUN failure " + "detected, action required\n", h->ctlr); + break; + case REPORT_LUNS_CHANGED: + dev_warn(&h->pdev->dev, "hpsa%d: report LUN data " + "changed\n", h->ctlr); + /* + * Here, we could call add_to_scan_list and wake up the scan thread, + * except that it's quite likely that we will get more than one + * REPORT_LUNS_CHANGED condition in quick succession, which means + * that those which occur after the first one will likely happen + * *during* the hpsa_scan_thread's rescan. And the rescan code is not + * robust enough to restart in the middle, undoing what it has already + * done, and it's not clear that it's even possible to do this, since + * part of what it does is notify the SCSI mid layer, which starts + * doing it's own i/o to read partition tables and so on, and the + * driver doesn't have visibility to know what might need undoing. + * In any event, if possible, it is horribly complicated to get right + * so we just don't do it for now. + * + * Note: this REPORT_LUNS_CHANGED condition only occurs on the MSA2012. + */ + break; + case POWER_OR_RESET: + dev_warn(&h->pdev->dev, "hpsa%d: a power on " + "or device reset detected\n", h->ctlr); + break; + case UNIT_ATTENTION_CLEARED: + dev_warn(&h->pdev->dev, "hpsa%d: unit attention " + "cleared by another initiator\n", h->ctlr); + break; + default: + dev_warn(&h->pdev->dev, "hpsa%d: unknown " + "unit attention detected\n", h->ctlr); + break; + } + return 1; +} + +static ssize_t host_store_rescan(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ctlr_info *h; + struct Scsi_Host *shost = class_to_shost(dev); + unsigned long *priv = shost_priv(shost); + h = (struct ctlr_info *) *priv; + if (add_to_scan_list(h)) { + wake_up_process(hpsa_scan_thread); + wait_for_completion_interruptible(&h->scan_wait); + } + return count; +} + +/* Enqueuing and dequeuing functions for cmdlists. */ +static inline void addQ(struct hlist_head *list, struct CommandList *c) +{ + hlist_add_head(&c->list, list); +} + +static void enqueue_cmd_and_start_io(struct ctlr_info *h, + struct CommandList *c) +{ + unsigned long flags; + spin_lock_irqsave(&h->lock, flags); + addQ(&h->reqQ, c); + h->Qdepth++; + start_io(h); + spin_unlock_irqrestore(&h->lock, flags); +} + +static inline void removeQ(struct CommandList *c) +{ + if (WARN_ON(hlist_unhashed(&c->list))) + return; + hlist_del_init(&c->list); +} + +static inline int is_hba_lunid(unsigned char scsi3addr[]) +{ + return memcmp(scsi3addr, RAID_CTLR_LUNID, 8) == 0; +} + +static inline int is_logical_dev_addr_mode(unsigned char scsi3addr[]) +{ + return (scsi3addr[3] & 0xC0) == 0x40; +} + +static const char *raid_label[] = { "0", "4", "1(1+0)", "5", "5+1", "ADG", + "UNKNOWN" +}; +#define RAID_UNKNOWN (ARRAY_SIZE(raid_label) - 1) + +static ssize_t raid_level_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t l = 0; + int rlevel; + struct ctlr_info *h; + struct scsi_device *sdev; + struct hpsa_scsi_dev_t *hdev; + unsigned long flags; + + sdev = to_scsi_device(dev); + h = sdev_to_hba(sdev); + spin_lock_irqsave(&h->lock, flags); + hdev = sdev->hostdata; + if (!hdev) { + spin_unlock_irqrestore(&h->lock, flags); + return -ENODEV; + } + + /* Is this even a logical drive? */ + if (!is_logical_dev_addr_mode(hdev->scsi3addr)) { + spin_unlock_irqrestore(&h->lock, flags); + l = snprintf(buf, PAGE_SIZE, "N/A\n"); + return l; + } + + rlevel = hdev->raid_level; + spin_unlock_irqrestore(&h->lock, flags); + if (rlevel < 0 || rlevel > RAID_UNKNOWN) + rlevel = RAID_UNKNOWN; + l = snprintf(buf, PAGE_SIZE, "RAID %s\n", raid_label[rlevel]); + return l; +} + +static ssize_t lunid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ctlr_info *h; + struct scsi_device *sdev; + struct hpsa_scsi_dev_t *hdev; + unsigned long flags; + unsigned char lunid[8]; + + sdev = to_scsi_device(dev); + h = sdev_to_hba(sdev); + spin_lock_irqsave(&h->lock, flags); + hdev = sdev->hostdata; + if (!hdev) { + spin_unlock_irqrestore(&h->lock, flags); + return -ENODEV; + } + memcpy(lunid, hdev->scsi3addr, sizeof(lunid)); + spin_unlock_irqrestore(&h->lock, flags); + return snprintf(buf, 20, "0x%02x%02x%02x%02x%02x%02x%02x%02x\n", + lunid[0], lunid[1], lunid[2], lunid[3], + lunid[4], lunid[5], lunid[6], lunid[7]); +} + +static ssize_t unique_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ctlr_info *h; + struct scsi_device *sdev; + struct hpsa_scsi_dev_t *hdev; + unsigned long flags; + unsigned char sn[16]; + + sdev = to_scsi_device(dev); + h = sdev_to_hba(sdev); + spin_lock_irqsave(&h->lock, flags); + hdev = sdev->hostdata; + if (!hdev) { + spin_unlock_irqrestore(&h->lock, flags); + return -ENODEV; + } + memcpy(sn, hdev->device_id, sizeof(sn)); + spin_unlock_irqrestore(&h->lock, flags); + return snprintf(buf, 16 * 2 + 2, + "%02X%02X%02X%02X%02X%02X%02X%02X" + "%02X%02X%02X%02X%02X%02X%02X%02X\n", + sn[0], sn[1], sn[2], sn[3], + sn[4], sn[5], sn[6], sn[7], + sn[8], sn[9], sn[10], sn[11], + sn[12], sn[13], sn[14], sn[15]); +} + +static int hpsa_find_target_lun(struct ctlr_info *h, + unsigned char scsi3addr[], int bus, int *target, int *lun) +{ + /* finds an unused bus, target, lun for a new physical device + * assumes h->devlock is held + */ + int i, found = 0; + DECLARE_BITMAP(lun_taken, HPSA_MAX_SCSI_DEVS_PER_HBA); + + memset(&lun_taken[0], 0, HPSA_MAX_SCSI_DEVS_PER_HBA >> 3); + + for (i = 0; i < h->ndevices; i++) { + if (h->dev[i]->bus == bus && h->dev[i]->target != -1) + set_bit(h->dev[i]->target, lun_taken); + } + + for (i = 0; i < HPSA_MAX_SCSI_DEVS_PER_HBA; i++) { + if (!test_bit(i, lun_taken)) { + /* *bus = 1; */ + *target = i; + *lun = 0; + found = 1; + break; + } + } + return !found; +} + +/* Add an entry into h->dev[] array. */ +static int hpsa_scsi_add_entry(struct ctlr_info *h, int hostno, + struct hpsa_scsi_dev_t *device, + struct hpsa_scsi_dev_t *added[], int *nadded) +{ + /* assumes h->devlock is held */ + int n = h->ndevices; + int i; + unsigned char addr1[8], addr2[8]; + struct hpsa_scsi_dev_t *sd; + + if (n >= HPSA_MAX_SCSI_DEVS_PER_HBA) { + dev_err(&h->pdev->dev, "too many devices, some will be " + "inaccessible.\n"); + return -1; + } + + /* physical devices do not have lun or target assigned until now. */ + if (device->lun != -1) + /* Logical device, lun is already assigned. */ + goto lun_assigned; + + /* If this device a non-zero lun of a multi-lun device + * byte 4 of the 8-byte LUN addr will contain the logical + * unit no, zero otherise. + */ + if (device->scsi3addr[4] == 0) { + /* This is not a non-zero lun of a multi-lun device */ + if (hpsa_find_target_lun(h, device->scsi3addr, + device->bus, &device->target, &device->lun) != 0) + return -1; + goto lun_assigned; + } + + /* This is a non-zero lun of a multi-lun device. + * Search through our list and find the device which + * has the same 8 byte LUN address, excepting byte 4. + * Assign the same bus and target for this new LUN. + * Use the logical unit number from the firmware. + */ + memcpy(addr1, device->scsi3addr, 8); + addr1[4] = 0; + for (i = 0; i < n; i++) { + sd = h->dev[i]; + memcpy(addr2, sd->scsi3addr, 8); + addr2[4] = 0; + /* differ only in byte 4? */ + if (memcmp(addr1, addr2, 8) == 0) { + device->bus = sd->bus; + device->target = sd->target; + device->lun = device->scsi3addr[4]; + break; + } + } + if (device->lun == -1) { + dev_warn(&h->pdev->dev, "physical device with no LUN=0," + " suspect firmware bug or unsupported hardware " + "configuration.\n"); + return -1; + } + +lun_assigned: + + h->dev[n] = device; + h->ndevices++; + added[*nadded] = device; + (*nadded)++; + + /* initially, (before registering with scsi layer) we don't + * know our hostno and we don't want to print anything first + * time anyway (the scsi layer's inquiries will show that info) + */ + /* if (hostno != -1) */ + dev_info(&h->pdev->dev, "%s device c%db%dt%dl%d added.\n", + scsi_device_type(device->devtype), hostno, + device->bus, device->target, device->lun); + return 0; +} + +/* Remove an entry from h->dev[] array. */ +static void hpsa_scsi_remove_entry(struct ctlr_info *h, int hostno, int entry, + struct hpsa_scsi_dev_t *removed[], int *nremoved) +{ + /* assumes h->devlock is held */ + int i; + struct hpsa_scsi_dev_t *sd; + + if (entry < 0 || entry >= HPSA_MAX_SCSI_DEVS_PER_HBA) + BUG(); + + sd = h->dev[entry]; + removed[*nremoved] = h->dev[entry]; + (*nremoved)++; + + for (i = entry; i < h->ndevices-1; i++) + h->dev[i] = h->dev[i+1]; + h->ndevices--; + dev_info(&h->pdev->dev, "%s device c%db%dt%dl%d removed.\n", + scsi_device_type(sd->devtype), hostno, sd->bus, sd->target, + sd->lun); +} + +#define SCSI3ADDR_EQ(a, b) ( \ + (a)[7] == (b)[7] && \ + (a)[6] == (b)[6] && \ + (a)[5] == (b)[5] && \ + (a)[4] == (b)[4] && \ + (a)[3] == (b)[3] && \ + (a)[2] == (b)[2] && \ + (a)[1] == (b)[1] && \ + (a)[0] == (b)[0]) + +static void fixup_botched_add(struct ctlr_info *h, + struct hpsa_scsi_dev_t *added) +{ + /* called when scsi_add_device fails in order to re-adjust + * h->dev[] to match the mid layer's view. + */ + unsigned long flags; + int i, j; + + spin_lock_irqsave(&h->lock, flags); + for (i = 0; i < h->ndevices; i++) { + if (h->dev[i] == added) { + for (j = i; j < h->ndevices-1; j++) + h->dev[j] = h->dev[j+1]; + h->ndevices--; + break; + } + } + spin_unlock_irqrestore(&h->lock, flags); + kfree(added); +} + +static inline int device_is_the_same(struct hpsa_scsi_dev_t *dev1, + struct hpsa_scsi_dev_t *dev2) +{ + if ((is_logical_dev_addr_mode(dev1->scsi3addr) || + (dev1->lun != -1 && dev2->lun != -1)) && + dev1->devtype != 0x0C) + return (memcmp(dev1, dev2, sizeof(*dev1)) == 0); + + /* we compare everything except lun and target as these + * are not yet assigned. Compare parts likely + * to differ first + */ + if (memcmp(dev1->scsi3addr, dev2->scsi3addr, + sizeof(dev1->scsi3addr)) != 0) + return 0; + if (memcmp(dev1->device_id, dev2->device_id, + sizeof(dev1->device_id)) != 0) + return 0; + if (memcmp(dev1->model, dev2->model, sizeof(dev1->model)) != 0) + return 0; + if (memcmp(dev1->vendor, dev2->vendor, sizeof(dev1->vendor)) != 0) + return 0; + if (memcmp(dev1->revision, dev2->revision, sizeof(dev1->revision)) != 0) + return 0; + if (dev1->devtype != dev2->devtype) + return 0; + if (dev1->raid_level != dev2->raid_level) + return 0; + if (dev1->bus != dev2->bus) + return 0; + return 1; +} + +/* Find needle in haystack. If exact match found, return DEVICE_SAME, + * and return needle location in *index. If scsi3addr matches, but not + * vendor, model, serial num, etc. return DEVICE_CHANGED, and return needle + * location in *index. If needle not found, return DEVICE_NOT_FOUND. + */ +static int hpsa_scsi_find_entry(struct hpsa_scsi_dev_t *needle, + struct hpsa_scsi_dev_t *haystack[], int haystack_size, + int *index) +{ + int i; +#define DEVICE_NOT_FOUND 0 +#define DEVICE_CHANGED 1 +#define DEVICE_SAME 2 + for (i = 0; i < haystack_size; i++) { + if (SCSI3ADDR_EQ(needle->scsi3addr, haystack[i]->scsi3addr)) { + *index = i; + if (device_is_the_same(needle, haystack[i])) + return DEVICE_SAME; + else + return DEVICE_CHANGED; + } + } + *index = -1; + return DEVICE_NOT_FOUND; +} + +static int adjust_hpsa_scsi_table(struct ctlr_info *h, int hostno, + struct hpsa_scsi_dev_t *sd[], int nsds) +{ + /* sd contains scsi3 addresses and devtypes, and inquiry + * data. This function takes what's in sd to be the current + * reality and updates h->dev[] to reflect that reality. + */ + int i, entry, device_change, changes = 0; + struct hpsa_scsi_dev_t *csd; + unsigned long flags; + struct hpsa_scsi_dev_t **added, **removed; + int nadded, nremoved; + struct Scsi_Host *sh = NULL; + + added = kzalloc(sizeof(*added) * HPSA_MAX_SCSI_DEVS_PER_HBA, + GFP_KERNEL); + removed = kzalloc(sizeof(*removed) * HPSA_MAX_SCSI_DEVS_PER_HBA, + GFP_KERNEL); + + if (!added || !removed) { + dev_warn(&h->pdev->dev, "out of memory in " + "adjust_hpsa_scsi_table\n"); + goto free_and_out; + } + + spin_lock_irqsave(&h->devlock, flags); + + /* find any devices in h->dev[] that are not in + * sd[] and remove them from h->dev[], and for any + * devices which have changed, remove the old device + * info and add the new device info. + */ + i = 0; + nremoved = 0; + nadded = 0; + while (i < h->ndevices) { + csd = h->dev[i]; + device_change = hpsa_scsi_find_entry(csd, sd, nsds, &entry); + if (device_change == DEVICE_NOT_FOUND) { + changes++; + hpsa_scsi_remove_entry(h, hostno, i, + removed, &nremoved); + continue; /* remove ^^^, hence i not incremented */ + } else if (device_change == DEVICE_CHANGED) { + changes++; + hpsa_scsi_remove_entry(h, hostno, i, + removed, &nremoved); + (void) hpsa_scsi_add_entry(h, hostno, sd[entry], + added, &nadded); + /* add can't fail, we just removed one. */ + sd[entry] = NULL; /* prevent it from being freed */ + } + i++; + } + + /* Now, make sure every device listed in sd[] is also + * listed in h->dev[], adding them if they aren't found + */ + + for (i = 0; i < nsds; i++) { + if (!sd[i]) /* if already added above. */ + continue; + device_change = hpsa_scsi_find_entry(sd[i], h->dev, + h->ndevices, &entry); + if (device_change == DEVICE_NOT_FOUND) { + changes++; + if (hpsa_scsi_add_entry(h, hostno, sd[i], + added, &nadded) != 0) + break; + sd[i] = NULL; /* prevent from being freed later. */ + } else if (device_change == DEVICE_CHANGED) { + /* should never happen... */ + changes++; + dev_warn(&h->pdev->dev, + "device unexpectedly changed.\n"); + /* but if it does happen, we just ignore that device */ + } + } + spin_unlock_irqrestore(&h->devlock, flags); + + /* Don't notify scsi mid layer of any changes the first time through + * (or if there are no changes) scsi_scan_host will do it later the + * first time through. + */ + if (hostno == -1 || !changes) + goto free_and_out; + + sh = h->scsi_host; + /* Notify scsi mid layer of any removed devices */ + for (i = 0; i < nremoved; i++) { + struct scsi_device *sdev = + scsi_device_lookup(sh, removed[i]->bus, + removed[i]->target, removed[i]->lun); + if (sdev != NULL) { + scsi_remove_device(sdev); + scsi_device_put(sdev); + } else { + /* We don't expect to get here. + * future cmds to this device will get selection + * timeout as if the device was gone. + */ + dev_warn(&h->pdev->dev, "didn't find c%db%dt%dl%d " + " for removal.", hostno, removed[i]->bus, + removed[i]->target, removed[i]->lun); + } + kfree(removed[i]); + removed[i] = NULL; + } + + /* Notify scsi mid layer of any added devices */ + for (i = 0; i < nadded; i++) { + if (scsi_add_device(sh, added[i]->bus, + added[i]->target, added[i]->lun) == 0) + continue; + dev_warn(&h->pdev->dev, "scsi_add_device c%db%dt%dl%d failed, " + "device not added.\n", hostno, added[i]->bus, + added[i]->target, added[i]->lun); + /* now we have to remove it from h->dev, + * since it didn't get added to scsi mid layer + */ + fixup_botched_add(h, added[i]); + } + +free_and_out: + kfree(added); + kfree(removed); + return 0; +} + +/* + * Lookup bus/target/lun and retrun corresponding struct hpsa_scsi_dev_t * + * Assume's h->devlock is held. + */ +static struct hpsa_scsi_dev_t *lookup_hpsa_scsi_dev(struct ctlr_info *h, + int bus, int target, int lun) +{ + int i; + struct hpsa_scsi_dev_t *sd; + + for (i = 0; i < h->ndevices; i++) { + sd = h->dev[i]; + if (sd->bus == bus && sd->target == target && sd->lun == lun) + return sd; + } + return NULL; +} + +/* link sdev->hostdata to our per-device structure. */ +static int hpsa_slave_alloc(struct scsi_device *sdev) +{ + struct hpsa_scsi_dev_t *sd; + unsigned long flags; + struct ctlr_info *h; + + h = sdev_to_hba(sdev); + spin_lock_irqsave(&h->devlock, flags); + sd = lookup_hpsa_scsi_dev(h, sdev_channel(sdev), + sdev_id(sdev), sdev->lun); + if (sd != NULL) + sdev->hostdata = sd; + spin_unlock_irqrestore(&h->devlock, flags); + return 0; +} + +static void hpsa_slave_destroy(struct scsi_device *sdev) +{ + return; /* nothing to do. */ +} + +static void hpsa_scsi_setup(struct ctlr_info *h) +{ + h->ndevices = 0; + h->scsi_host = NULL; + spin_lock_init(&h->devlock); + return; +} + +static void complete_scsi_command(struct CommandList *cp, + int timeout, __u32 tag) +{ + struct scsi_cmnd *cmd; + struct ctlr_info *h; + struct ErrorInfo *ei; + + unsigned char sense_key; + unsigned char asc; /* additional sense code */ + unsigned char ascq; /* additional sense code qualifier */ + + ei = cp->err_info; + cmd = (struct scsi_cmnd *) cp->scsi_cmd; + h = cp->h; + + scsi_dma_unmap(cmd); /* undo the DMA mappings */ + + cmd->result = (DID_OK << 16); /* host byte */ + cmd->result |= (COMMAND_COMPLETE << 8); /* msg byte */ + cmd->result |= (ei->ScsiStatus << 1); + + /* copy the sense data whether we need to or not. */ + memcpy(cmd->sense_buffer, ei->SenseInfo, + ei->SenseLen > SCSI_SENSE_BUFFERSIZE ? + SCSI_SENSE_BUFFERSIZE : + ei->SenseLen); + scsi_set_resid(cmd, ei->ResidualCnt); + + if (ei->CommandStatus == 0) { + cmd->scsi_done(cmd); + cmd_free(h, cp); + return; + } + + /* an error has occurred */ + switch (ei->CommandStatus) { + + case CMD_TARGET_STATUS: + if (ei->ScsiStatus) { + /* Get sense key */ + sense_key = 0xf & ei->SenseInfo[2]; + /* Get additional sense code */ + asc = ei->SenseInfo[12]; + /* Get addition sense code qualifier */ + ascq = ei->SenseInfo[13]; + } + + if (ei->ScsiStatus == SAM_STAT_CHECK_CONDITION) { + if (check_for_unit_attention(h, cp)) { + cmd->result = DID_SOFT_ERROR << 16; + break; + } + if (sense_key == ILLEGAL_REQUEST) { + /* + * SCSI REPORT_LUNS is commonly unsupported on + * Smart Array. Suppress noisy complaint. + */ + if (cp->Request.CDB[0] == REPORT_LUNS) + break; + + /* If ASC/ASCQ indicate Logical Unit + * Not Supported condition, + */ + if ((asc == 0x25) && (ascq == 0x0)) { + dev_warn(&h->pdev->dev, "cp %p " + "has check condition\n", cp); + break; + } + } + + if (sense_key == NOT_READY) { + /* If Sense is Not Ready, Logical Unit + * Not ready, Manual Intervention + * required + */ + if ((asc == 0x04) && (ascq == 0x03)) { + cmd->result = DID_NO_CONNECT << 16; + dev_warn(&h->pdev->dev, "cp %p " + "has check condition: unit " + "not ready, manual " + "intervention required\n", cp); + break; + } + } + + + /* Must be some other type of check condition */ + dev_warn(&h->pdev->dev, "cp %p has check condition: " + "unknown type: " + "Sense: 0x%x, ASC: 0x%x, ASCQ: 0x%x, " + "Returning result: 0x%x, " + "cmd=[%02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x]\n", + cp, sense_key, asc, ascq, + cmd->result, + cmd->cmnd[0], cmd->cmnd[1], + cmd->cmnd[2], cmd->cmnd[3], + cmd->cmnd[4], cmd->cmnd[5], + cmd->cmnd[6], cmd->cmnd[7], + cmd->cmnd[8], cmd->cmnd[9]); + break; + } + + |