From: Luben Tuikov <luben_tuikov@adaptec.com>
To: SCSI Mailing List <linux-scsi@vger.kernel.org>
Subject: [ANNOUNCE] Adaptec SAS/SATA device driver [14/27]
Date: Thu, 17 Feb 2005 12:36:47 -0500 [thread overview]
Message-ID: <4214D62F.30809@adaptec.com> (raw)
OSM code. Part 1/3.
diff -Nru a/drivers/scsi/adp94xx/adp94xx_osm.c b/drivers/scsi/adp94xx/adp94xx_osm.c
--- /dev/null Wed Dec 31 16:00:00 196900
+++ b/drivers/scsi/adp94xx/adp94xx_osm.c 2005-02-16 16:08:12 -05:00
@@ -0,0 +1,1923 @@
+/*
+ * Adaptec ADP94xx SAS HBA device driver for Linux.
+ *
+ * Written by: David Chaw <david_chaw@adaptec.com>
+ * Modified by: Naveen Chandrasekaran <naveen_chandrasekaran@adaptec.com>
+ *
+ * Copyright (c) 2004 Adaptec Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * $Id: //depot/razor/linux/src/adp94xx_osm.c#192 $
+ *
+ */
+#define KDB_ENABLE 0
+
+#if KDB_ENABLE
+#include "linux/kdb.h"
+#endif
+#include "adp94xx_osm.h"
+#include "adp94xx_inline.h"
+#include "adp94xx_sata.h"
+#include "adp94xx_hwi.h"
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+#include <linux/moduleparam.h>
+#endif
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_AUTHOR("Maintainer: David Chaw <david_chaw@adaptec.com>");
+MODULE_DESCRIPTION("Adaptec Linux SAS/SATA Family Driver");
+
+/* Global variables */
+LIST_HEAD(asd_hbas);
+static Scsi_Host_Template asd_sht;
+static asd_init_status_t asd_init_stat;
+static int asd_init_result;
+spinlock_t asd_list_spinlock;
+
+#ifdef ASD_EH_SIMULATION
+static u_long cmd_cnt = 0;
+#endif
+
+u_int debug_mask = 0x0;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+/* For dynamic sglist size calculation. */
+u_int asd_nseg;
+#endif
+
+/* Device Queue Depth Handling. */
+#define ASD_DEF_TCQ_PER_DEVICE 32
+#define ASD_MAX_TCQ_PER_DEVICE 64
+#define ASD_MIN_TCQ_PER_DEVICE 1
+
+static u_int cmd_per_lun = ASD_DEF_TCQ_PER_DEVICE;
+
+/* By default we do not attach to HostRAID enabled controllers.
+ */
+#define ASD_DEF_ATTACH_HOSTRAID 0
+static int attach_HostRAID = ASD_DEF_ATTACH_HOSTRAID;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+module_param_named(attach_HostRAID, attach_HostRAID, int, S_IRUGO|S_IWUSR);
+module_param_named(cmd_per_lun, cmd_per_lun, uint, S_IRUGO|S_IWUSR);
+module_param_named(debug_mask, debug_mask, uint, S_IRUGO|S_IWUSR);
+#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) */
+MODULE_PARM(attach_HostRAID, "i");
+MODULE_PARM(cmd_per_lun, "i");
+MODULE_PARM(debug_mask, "i");
+#ifndef MODULE
+static int adp94xx_setup(char *s);
+__setup("adp94xx=", adp94xx_setup);
+#endif /* MODULE */
+#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) */
+
+MODULE_PARM_DESC(attach_HostRAID,
+ "integer, disable(0) or enable(1) attaching to\n"
+ "\tHostRAID enabled controllers."
+ " Default:" __stringify(ASD_DEF_ATTACH_HOSTRAID));
+MODULE_PARM_DESC(cmd_per_lun,
+ "integer, queue depth for all attached targets that\n"
+ "\tsupport tag queueing."
+ " Default:" __stringify(ASD_DEF_TCQ_PER_DEVICE)
+ " Min:" __stringify(ASD_MIN_TCQ_PER_DEVICE)
+ " Max:" __stringify(ASD_MAX_TCQ_PER_DEVICE));
+MODULE_PARM_DESC(debug_mask,
+ "integer, the debug mask, must have ASD_DEBUG macro\n"
+ "\tdefined at compilation time.");
+
+/* The driver version */
+static const char __driver_version[] \
+ __attribute__ ((section ("driver_version"))) \
+ = "driver_version=" ASD_DRIVER_VERSION "-" \
+ __stringify(ASD_RELEASE_VERSION);
+static const char *asd_driver_version \
+ = __driver_version + sizeof("driver_version=") - 1;
+
+/* Module entry points */
+static int __init asd_init(void);
+static void asd_exit(void);
+
+/* Initialization */
+static void asd_size_nseg(void);
+
+/* Midlayer entry points */
+static int asd_detect(Scsi_Host_Template *);
+static const char *asd_info(struct Scsi_Host *);
+static int asd_queue(Scsi_Cmnd *, void (*)(Scsi_Cmnd *));
+irqreturn_t asd_isr(int, void *, struct pt_regs *);
+static int asd_abort(Scsi_Cmnd *);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
+static int asd_proc_info(struct Scsi_Host *, char *, char **, off_t,
+ int, int);
+static int asd_bios_param(struct scsi_device *sdev,
+ struct block_device *bdev, sector_t capacity,
+ int geom[]);
+static int asd_slave_alloc(Scsi_Device *);
+static int asd_slave_configure(Scsi_Device *);
+static void asd_slave_destroy(Scsi_Device *);
+static int asd_initiate_bus_scan(struct asd_softc *asd);
+#else
+static int asd_release(struct Scsi_Host *);
+static int asd_proc_info(char *, char **, off_t, int, int, int);
+static void asd_select_queue_depth(struct Scsi_Host *, Scsi_Device *);
+static int asd_bios_param(Disk *disk, kdev_t dev, int geom[]);
+#endif
+
+typedef enum {
+ ASD_QUEUE_NONE,
+ ASD_QUEUE_BASIC,
+ ASD_QUEUE_TAGGED
+} asd_queue_alg;
+
+static void asd_set_device_queue_depth(struct asd_softc *asd,
+ struct asd_device *dev);
+static u_int asd_get_user_tagdepth(struct asd_softc *asd,
+ struct asd_device *dev);
+static void asd_set_tags(struct asd_softc *asd, struct asd_device *dev,
+ asd_queue_alg alg);
+static void asd_print_path(struct asd_softc *asd, struct asd_device *dev);
+
+/* Device Queue Handling. */
+static struct asd_domain *
+ asd_alloc_domain(struct asd_softc *asd, u_int channel_mapping);
+static void asd_free_domain(struct asd_softc *asd, struct asd_domain *dm);
+static void asd_dev_timed_unfreeze(u_long arg);
+static void asd_init_dev_itnl_exp(struct asd_softc *asd,
+ struct asd_target *targ, u_int lun);
+void asd_dev_intl_times_out(u_long arg);
+
+static asd_scb_post_t asd_scb_done;
+static void asd_handle_sas_status(struct asd_softc *asd,
+ struct asd_device *dev, struct scb *scb,
+ struct ssp_resp_edb *edb, u_int edb_len);
+static ASD_COMMAND_BUILD_STATUS
+ asd_build_sas_scb(struct asd_softc *asd, struct scb *scb,
+ union asd_cmd *acmd);
+static void asd_runq_tasklet(unsigned long data);
+static void asd_unblock_tasklet(unsigned long data);
+static void asd_flush_device_queue(struct asd_softc *asd,
+ struct asd_device *dev);
+static inline void
+ asd_check_device_queue(struct asd_softc *asd,
+ struct asd_device *dev);
+inline void asd_run_device_queues(struct asd_softc *asd);
+
+/* Discovery and Device async. event thread. */
+static int asd_discovery_thread(void *data);
+static void asd_kill_discovery_thread(struct asd_softc *asd);
+static int asd_check_phy_events(struct asd_softc *asd, u_int phy_id);
+static int asd_check_port_events(struct asd_softc *asd, u_int port_id);
+static void asd_process_id_addr_evt(struct asd_softc *asd,
+ struct asd_phy *phy);
+static int asd_initiate_port_discovery(struct asd_softc *asd,
+ struct asd_port *port);
+static void asd_handle_loss_of_signal(struct asd_softc *asd,
+ struct asd_port *port);
+static void asd_setup_port_data(struct asd_softc *asd,
+ struct asd_port *port, struct asd_phy *phy);
+static int asd_setup_target_data(struct asd_softc *asd,
+ struct asd_phy *phy,
+ struct asd_target *targ);
+static void asd_configure_port_targets(struct asd_softc *asd,
+ struct asd_port *port);
+static void asd_configure_target(struct asd_softc *asd,
+ struct asd_target *targ);
+static void asd_destroy_target(struct asd_softc *asd,
+ struct asd_target *targ);
+static void asd_clear_device_io(struct asd_softc *asd,
+ struct asd_device *dev);
+static asd_scb_post_t asd_clear_device_io_done;
+
+/* Error Recovery thread. */
+static int asd_ehandler_thread(void *data);
+static void asd_kill_ehandler_thread(struct asd_softc *asd);
+static asd_scb_eh_post_t asd_ehandler_done;
+
+#ifdef ASD_EH_SIMULATION
+static void asd_eh_simul_done(struct asd_softc *asd, struct scb *scb);
+static int asd_eh_simul_thread(void *data);
+static void asd_kill_eh_simul_thread(struct asd_softc *asd);
+#endif
+
+/* PCI entry points */
+static int asd_pci_dev_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id);
+static void asd_pci_dev_remove(struct pci_dev *pdev);
+
+/*
+ * PCI Device specific initialization routine.
+ */
+typedef int (asd_pdev_setup_t) (struct asd_softc *);
+
+struct asd_pci_driver_data {
+ /* Controller Description. */
+ char *description;
+
+ /* Controller Specific Setup Routine. */
+ asd_pdev_setup_t *setup;
+};
+
+static asd_pdev_setup_t asd_aic9410_setup;
+
+/* Supported PCI Vendor & Device ID */
+#ifndef PCI_VENDOR_ID_ADAPTEC2
+#define PCI_VENDOR_ID_ADAPTEC2 0x9005
+#endif
+#define PCI_CLASS_DEFAULT 0
+#define PCI_CLASS_MASK_DEFAULT 0
+
+static struct asd_pci_driver_data asd_aic9410_drv_data = \
+{ \
+ "Adaptec AIC-9410 SAS/SATA Controller", \
+ asd_aic9410_setup \
+};
+
+static struct pci_device_id asd_pci_ids_table[] = {
+ {
+ PCI_VENDOR_ID_ADAPTEC2, 0x0412, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_DEFAULT, PCI_CLASS_MASK_DEFAULT,
+ (unsigned long) &asd_aic9410_drv_data
+ },
+ {
+ PCI_VENDOR_ID_ADAPTEC2, 0x041E, PCI_ANY_ID, PCI_ANY_ID,
+ PCI_CLASS_DEFAULT, PCI_CLASS_MASK_DEFAULT,
+ (unsigned long) &asd_aic9410_drv_data
+ },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, asd_pci_ids_table);
+
+struct pci_driver adp94xx_pci_driver = {
+ .name = ASD_DRIVER_NAME,
+ .probe = asd_pci_dev_probe,
+ .remove = asd_pci_dev_remove,
+ .id_table = asd_pci_ids_table
+};
+
+/* Local functions */
+static struct asd_softc *asd_get_softc(struct asd_softc *asd);
+static int asd_get_unit(void);
+static int asd_register_host(struct asd_softc *asd);
+static void __asd_unregister_host(struct asd_softc *asd);
+static int asd_init_hw(struct asd_softc *asd);
+static int asd_map_io_handle(struct asd_softc *asd);
+static int asd_mem_mapped_io_handle(struct asd_softc *asd);
+static int asd_io_mapped_io_handle(struct asd_softc *asd);
+
+/********************************* Inlines ************************************/
+
+static inline void asd_init_tasklets(struct asd_softc *asd);
+static inline void asd_kill_tasklets(struct asd_softc *asd);
+
+static inline void
+asd_init_tasklets(struct asd_softc *asd)
+{
+ tasklet_init(&asd->platform_data->runq_tasklet, asd_runq_tasklet,
+ (unsigned long) asd);
+ tasklet_init(&asd->platform_data->unblock_tasklet, asd_unblock_tasklet,
+ (unsigned long) asd);
+}
+
+static inline void
+asd_kill_tasklets(struct asd_softc *asd)
+{
+ tasklet_kill(&asd->platform_data->runq_tasklet);
+ tasklet_kill(&asd->platform_data->unblock_tasklet);
+}
+
+struct asd_device *
+asd_get_device(struct asd_softc *asd, u_int ch, u_int id, u_int lun, int alloc)
+{
+ struct asd_domain *dm;
+ struct asd_target *targ;
+ struct asd_device *dev;
+
+ ASD_LOCK_ASSERT(asd);
+
+ /*
+ * Domain and target structures are allocated by our
+ * discovery process. Fail if our mapping attempt
+ * finds either of these path components missing.
+ */
+ if ((ch >= asd->platform_data->num_domains) ||
+ ((dm = asd->platform_data->domains[ch]) == NULL)) {
+ return (NULL);
+ }
+
+ if ((id >= ASD_MAX_TARGETS) ||
+ ((targ = dm->targets[id]) == NULL)) {
+ return (NULL);
+ }
+
+ if (lun >= ASD_MAX_LUNS) {
+ return (NULL);
+ }
+
+ dev = targ->devices[lun];
+
+ return (dev);
+}
+
+void
+asd_unmap_scb(struct asd_softc *asd, struct scb *scb)
+{
+ Scsi_Cmnd *cmd;
+ int direction;
+
+ cmd = &acmd_scsi_cmd(scb->io_ctx);
+ direction = scsi_to_pci_dma_dir(cmd->sc_data_direction);
+
+ if (cmd->use_sg != 0) {
+ struct scatterlist *sg;
+
+ sg = (struct scatterlist *)cmd->request_buffer;
+ asd_unmap_sg(asd, sg, cmd->use_sg, direction);
+ } else if (cmd->request_bufflen != 0) {
+ asd_unmap_single(asd,
+ scb->platform_data->buf_busaddr,
+ cmd->request_bufflen, direction);
+ }
+}
+
+static inline void
+asd_check_device_queue(struct asd_softc *asd, struct asd_device *dev)
+{
+ ASD_LOCK_ASSERT(asd);
+
+ if ((dev->flags & ASD_DEV_FREEZE_TIL_EMPTY) != 0 && dev->active == 0) {
+ dev->flags &= ~ASD_DEV_FREEZE_TIL_EMPTY;
+ dev->qfrozen--;
+ }
+
+ if (list_empty(&dev->busyq) || dev->openings == 0 ||
+ dev->qfrozen != 0 || dev->target->qfrozen != 0)
+ return;
+
+ asd_flush_device_queue(asd, dev);
+}
+
+inline void
+asd_run_device_queues(struct asd_softc *asd)
+{
+ struct asd_device *dev;
+
+ while ((dev = asd_next_device_to_run(asd)) != NULL) {
+ list_del(&dev->links);
+ dev->flags &= ~ASD_DEV_ON_RUN_LIST;
+ asd_check_device_queue(asd, dev);
+ }
+}
+
+static inline void asd_target_addref(struct asd_target *targ);
+static inline void asd_target_release(struct asd_softc *asd,
+ struct asd_target *targ);
+static inline void asd_domain_addref(struct asd_domain *dm);
+static inline void asd_domain_release(struct asd_softc *asd,
+ struct asd_target *targ);
+
+static inline void
+asd_target_addref(struct asd_target *targ)
+{
+ targ->refcount++;
+}
+
+static inline void
+asd_target_release(struct asd_softc *asd, struct asd_target *targ)
+{
+ targ->refcount--;
+ if (targ->refcount == 0) {
+ asd_free_target(asd, targ);
+ }
+}
+
+static inline void
+asd_domain_addref(struct asd_domain *dm)
+{
+ dm->refcount++;
+}
+
+static inline void
+asd_domain_release(struct asd_softc *asd, struct asd_target *targ)
+{
+ targ->domain->refcount--;
+
+ targ->domain->targets[targ->target] = NULL;
+
+ if (targ->domain->refcount == 0) {
+ asd_free_domain(asd, targ->domain);
+ }
+}
+
+/******************************************************************************/
+
+/*
+ * Function:
+ * asd_get_softc()
+ *
+ * Description:
+ * Search if the requested host is in our HBA list.
+ */
+static struct asd_softc *
+asd_get_softc(struct asd_softc *asd)
+{
+ struct asd_softc *entry;
+ unsigned long flags;
+
+ asd_list_lock(&flags);
+ list_for_each_entry(entry, &asd_hbas, link) {
+ if (entry == asd) {
+ asd_list_unlock(&flags);
+ return (asd);
+ }
+ }
+ asd_list_unlock(&flags);
+
+ return (NULL);
+}
+
+/*
+ * Function:
+ * asd_get_softc_by_hba_index()
+ *
+ * Description:
+ * Search and return the host corresponding to the user requested
+ * hba_index from our HBA list.
+ */
+struct asd_softc *
+asd_get_softc_by_hba_index(uint32_t hba_index)
+{
+ struct asd_softc *entry;
+ unsigned long flags;
+
+ asd_list_lock(&flags);
+ list_for_each_entry(entry, &asd_hbas, link) {
+ if (entry->asd_hba_index == hba_index) {
+ asd_list_unlock(&flags);
+ return (entry);
+ }
+ }
+ asd_list_unlock(&flags);
+
+ return (NULL);
+}
+
+/*
+ * Function:
+ * asd_get_number_of_hbas_present()
+ *
+ * Description:
+ * Return the total number of SAS HBAs present
+ */
+int
+asd_get_number_of_hbas_present(void)
+{
+ struct asd_softc *entry;
+ unsigned long flags;
+ int hba_count;
+
+ hba_count = 0;
+ asd_list_lock(&flags);
+ list_for_each_entry(entry, &asd_hbas, link) {
+ hba_count++;
+ }
+ asd_list_unlock(&flags);
+
+ return hba_count;
+}
+
+/*
+ * Function:
+ * asd_get_unit()
+ *
+ * Description:
+ * Find the smallest available unit number to use for a new device.
+ * Avoid using a static count to handle the "repeated hot-(un)plug"
+ * scenario.
+ */
+static int
+asd_get_unit(void)
+{
+ struct asd_softc *asd;
+ unsigned long flags;
+ int unit;
+
+ unit = 0;
+ asd_list_lock(&flags);
+retry:
+ list_for_each_entry(asd, &asd_hbas, link) {
+ if (asd->profile.unit == unit) {
+ unit++;
+ goto retry;
+ }
+ }
+ asd_list_unlock(&flags);
+ return (unit);
+}
+
+static void
+asd_print_path(struct asd_softc *asd, struct asd_device *dev)
+{
+ if (dev != NULL)
+ asd_print("(scsi%d: Ch %d Id %d Lun %d): ",
+ asd->platform_data->scsi_host->host_no,
+ dev->ch, dev->id, dev->lun);
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) && !defined(MODULE)
+/*
+ * asd_setup_qtag_info
+ * This function interprets the cmd_per_lun setup parameter and sets
+ * its value internally.
+ */
+static void asd_setup_qtag_info(char *c)
+{
+ u_int tags;
+
+ tags = simple_strtoul(c, NULL, 0) & (~0);
+
+ if (tags < ASD_MIN_TCQ_PER_DEVICE || tags > ASD_MAX_TCQ_PER_DEVICE) {
+ asd_print(ASD_DRIVER_NAME": cmd_per_lun:%d out of range,"
+ " setting default:%d\n",tags,ASD_DEF_TCQ_PER_DEVICE);
+ tags = ASD_DEF_TCQ_PER_DEVICE;
+ } else {
+ asd_print(ASD_DRIVER_NAME": setting cmd_per_lun:%d\n", tags);
+ }
+
+ cmd_per_lun = tags;
+}
+
+static void
+asd_setup_debug_info(char *c)
+{
+ u_int dbg_mask;
+
+ dbg_mask = simple_strtoul(c, NULL, 0) & 0xFF;
+ asd_print("Setting the Debug Mask : 0x%x\n\n", dbg_mask);
+}
+
+/*
+ * Function:
+ * adp94xx_setup()
+ *
+ * Description:
+ * Handle Linux boot parameters. This routine allows for assigning a value
+ * to a parameter with a ':' between the parameter and the value.
+ * ie. adp94xx=cmd_per_lun:32
+ */
+static int
+adp94xx_setup(char *s)
+{
+ char *p;
+ char *end;
+ int i, n;
+
+ static struct {
+ const char *name;
+ uint32_t *flag;
+ } options[] = {
+ { "attach_HostRAID", &attach_HostRAID },
+ { "debug_mask", NULL },
+ { "cmd_per_lun", NULL },
+ };
+
+ end = strchr(s, '\0');
+ n = 0;
+
+ while ((p = strsep(&s, ",.")) != NULL) {
+ if (*p == '\0')
+ continue;
+ for (i = 0; i < NUM_ELEMENTS(options); i++) {
+
+ n = strlen(options[i].name);
+ if (strncmp(options[i].name, p, n) == 0)
+ break;
+ }
+ if (i == NUM_ELEMENTS(options))
+ continue;
+
+ if (strncmp(p, "cmd_per_lun", n) == 0) {
+ asd_setup_qtag_info(p + n + 1);
+ } else if (strncmp(p, "debug_mask", n) == 0) {
+ asd_setup_debug_info(p + n + 1);
+ } else if (p[n] == ':') {
+ *(options[i].flag) = simple_strtoul(p + n + 1, NULL,0);
+ } else {
+ *(options[i].flag) ^= 0xFFFFFFFF;
+ }
+ }
+ return (1);
+}
+#endif
+
+/*
+ * Function:
+ * asd_init()
+ *
+ * Description:
+ * This is the entry point which will be called during module loading.
+ */
+static int __init
+asd_init(void)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
+ asd_detect(&asd_sht);
+#else
+ scsi_register_module(MODULE_SCSI_HA, &asd_sht);
+#endif
+ if (asd_init_result != 0) {
+ asd_log(ASD_DBG_INIT, "Module init failed !\n");
+ asd_exit();
+ return (asd_init_result);
+ }
+
+ /* Register the IOCTL char device. */
+ if (asd_register_ioctl_dev() != 0) {
+ asd_log(ASD_DBG_INIT, "Failed to register IOCTL "
+ "dev.\n");
+ asd_exit();
+ asd_init_result = -1;
+ } else {
+ asd_init_stat.asd_ioctl_registered = 1;
+ }
+
+ return (asd_init_result);
+}
+
+/*
+ * Function:
+ * asd_exit()
+ *
+ * Description:
+ * This is the entry point which will be called during module unloading.
+ */
+static void
+asd_exit(void)
+{
+ struct asd_softc *asd;
+
+ asd_log(ASD_DBG_INIT, "Unloading module ...\n");
+
+ list_for_each_entry(asd, &asd_hbas, link) {
+ asd_kill_discovery_thread(asd);
+
+ asd_kill_ehandler_thread(asd);
+#ifdef ASD_EH_SIMULATION
+ asd_kill_eh_simul_thread(asd);
+#endif
+ }
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ scsi_unregister_module(MODULE_SCSI_HA, &asd_sht);
+#endif
+ if (asd_init_stat.asd_pci_registered == 1)
+ pci_unregister_driver(&adp94xx_pci_driver);
+
+ if (asd_init_stat.asd_ioctl_registered == 1)
+ asd_unregister_ioctl_dev();
+
+}
+
+/*
+ * Function:
+ * asd_aic9410_setup()
+ *
+ * Description:
+ * Setup host profile initialization for the AIC9410 controller.
+ */
+static int
+asd_aic9410_setup(struct asd_softc *asd)
+{
+ /*
+ * max_cmds_per_lun will be throttled once we know the attached
+ * device is DASD.
+ */
+ asd->profile.max_cmds_per_lun = 2;
+ asd->profile.can_queue = ASD_MAX_QUEUE;
+ asd->profile.initiator_id = 255;
+ asd->profile.max_luns = ASD_MAX_LUNS;
+ asd->profile.max_scsi_ids = ASD_MAX_TARGETS;
+ asd->profile.max_channels = ASD_MAX_PORTS;
+ /*
+ * For now, we assumed that the controller can support 64-bit
+ * addressing.
+ */
+ asd->profile.dma64_addr = 1;
+ asd->profile.name = ASD_DRIVER_NAME;
+
+ /* Controller specific profile. */
+ asd->hw_profile.max_devices = ASD_MAX_DEVICES;
+ asd->hw_profile.max_targets = ASD_MAX_TARGETS;
+ asd->hw_profile.max_ports = ASD_MAX_PORTS;
+ asd->hw_profile.max_phys = ASD_MAX_PHYS;
+ /*
+ * enabled_phys could be used as a bitmap for the user to specified
+ * which phys to be enabled.
+ */
+ asd->hw_profile.enabled_phys = (1 << asd->hw_profile.max_phys) - 1;
+ asd->hw_profile.max_scbs = ASD_MAX_USABLE_SCBS;
+ asd->hw_profile.max_ddbs = ASD_MAX_DDBS;
+ asd->hw_profile.rev_id = asd_pcic_read_byte(asd, PCIC_DEVREV_ID);
+
+ /*
+ * Default World Wide Name set for the Controller.
+ */
+ asd->hw_profile.wwn[0] = 0x50;
+ asd->hw_profile.wwn[1] = 0x00;
+ asd->hw_profile.wwn[2] = 0x0d;
+ asd->hw_profile.wwn[3] = 0x1f;
+ asd->hw_profile.wwn[4] = 0xed;
+ asd->hw_profile.wwn[5] = 0xcb;
+ asd->hw_profile.wwn[6] = 0xa9;
+ asd->hw_profile.wwn[7] = 0x89;
+ return (0);
+}
+
+/*
+ * Function:
+ * asd_size_nseg()
+ *
+ * Description:
+ *
+ * In pre-2.5.X...
+ * The midlayer allocates an S/G array dynamically when a command is issued
+ * using SCSI malloc. This array, which is in an OS dependent format that
+ * must later be copied to our private S/G list, is sized to house just the
+ * number of segments needed for the current transfer. Since the code that
+ * sizes the SCSI malloc pool does not take into consideration fragmentation
+ * of the pool, executing transactions numbering just a fraction of our
+ * concurrent transaction limit with list lengths aproaching AHC_NSEG will
+ * quickly depleat the SCSI malloc pool of usable space. Unfortunately, the
+ * mid-layer does not properly handle this scsi malloc failures for the S/G
+ * array and the result can be a lockup of the I/O subsystem. We try to size
+ * our S/G list so that it satisfies our drivers allocation requirements in
+ * addition to avoiding fragmentation of the SCSI malloc pool.
+ */
+static void
+asd_size_nseg(void)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ u_int cur_size;
+ u_int best_size;
+
+ /*
+ * The SCSI allocator rounds to the nearest 512 bytes
+ * and cannot allocate across a page boundary. Our algorithm
+ * is to start at 1K of scsi malloc space per-command and
+ * loop through all factors of the PAGE_SIZE and pick the best.
+ */
+ best_size = 0;
+ for (cur_size = 1024; cur_size <= PAGE_SIZE; cur_size *= 2) {
+ u_int nseg;
+
+ nseg = cur_size / sizeof(struct scatterlist);
+ if (nseg < ASD_LINUX_MIN_NSEG)
+ continue;
+
+ if (best_size == 0) {
+ best_size = cur_size;
+ asd_nseg = nseg;
+ } else {
+ u_int best_rem;
+ u_int cur_rem;
+
+ /*
+ * Compare the traits of the current "best_size"
+ * with the current size to determine if the
+ * current size is a better size.
+ */
+ best_rem = best_size % sizeof(struct scatterlist);
+ cur_rem = cur_size % sizeof(struct scatterlist);
+ if (cur_rem < best_rem) {
+ best_size = cur_size;
+ asd_nseg = nseg;
+ }
+ }
+ }
+#endif
+}
+
+/*
+ * Function:
+ * asd_detect()
+ *
+ * Description:
+ * This routine shall detect any supported controller.
+ */
+static int
+asd_detect(Scsi_Host_Template *sht)
+{
+ struct asd_softc *asd;
+ int error;
+ uint8_t hba_cnt;
+
+ asd_print("Loading AIC-94xx Linux SAS/SATA Family Driver, Rev: %s\n\n",
+ asd_driver_version);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ /*
+ * Release the lock that was held by the midlayer prior to calling us.
+ */
+ spin_unlock_irq(&io_request_lock);
+#endif
+ hba_cnt = 0;
+ asd_init_stat.asd_init_state = 1;
+
+ asd_list_lockinit();
+ /*
+ * Determine an appropriate size for our SG lists.
+ */
+ asd_size_nseg();
+
+ /* Register our PCI entry points and id table. */
+ error = pci_module_init(&adp94xx_pci_driver);
+ if (error != 0) {
+ asd_init_result = error;
+ goto exit;
+ } else {
+ asd_init_stat.asd_pci_registered = 1;
+ asd_init_result = error;
+ }
+
+ /* For every controller found, register it with the midlayer. */
+ list_for_each_entry(asd, &asd_hbas, link) {
+ error = asd_register_host(asd);
+ if (error != 0) {
+ asd_init_result = error;
+ goto exit;
+ }
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+ error = asd_initiate_bus_scan(asd);
+ if (error != 0) {
+ struct pci_dev *pci_dev = asd_pci_dev(asd);
+ asd_print("%s: couldn't initiate bus scan for "
+ "PCI device %x:%x.%x (%x:%x)\n",
+ asd_name(asd),
+ pci_dev->bus->number,
+ PCI_SLOT(pci_dev->devfn),
+ PCI_FUNC(pci_dev->devfn),
+ pci_dev->vendor,
+ pci_dev->device);
+ __asd_unregister_host(asd);
+ continue;
+ }
+#endif
+ asd->asd_hba_index = hba_cnt++;
+
+ asd_ctl_init_internal_data(asd);
+ }
+
+ asd_init_stat.asd_init_state = 0;
+
+exit:
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ /* Acquire the lock before returning to midlayer. */
+ spin_lock_irq(&io_request_lock);
+#endif
+ asd_print("AIC-94xx controller(s) attached = %d.\n\n", hba_cnt);
+
+ return (hba_cnt);
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+/*
+ * Function:
+ * asd_release()
+ *
+ * Description:
+ * Free the Scsi_Host structure during unloading module.
+ */
+static int
+asd_release(struct Scsi_Host *scsi_host)
+{
+ struct asd_softc *asd;
+ unsigned long flags;
+
+ if (scsi_host != NULL) {
+ asd = *((struct asd_softc **) scsi_host->hostdata);
+
+ if (asd_get_softc(asd) != NULL) {
+ asd_list_lock(&flags);
+ list_del(&asd->link);
+ asd_list_unlock(&flags);
+ asd_free_softc(asd);
+ }
+ }
+
+ return (0);
+}
+#endif
+
+/*
+ * Function:
+ * asd_register_host()
+ *
+ * Description:
+ * Register our controller with the scsi midlayer.
+ */
+static int
+asd_register_host(struct asd_softc *asd)
+{
+ struct Scsi_Host *scsi_host;
+ u_long flags;
+
+ asd_sht.name = ((struct asd_pci_driver_data *)
+ (asd->pci_entry->driver_data))->description;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
+ scsi_host = scsi_host_alloc(&asd_sht, sizeof(struct asd_softc *));
+#else
+ scsi_host = scsi_register(&asd_sht, sizeof(struct asd_softc *));
+#endif
+ if (scsi_host == NULL)
+ return (-ENOMEM);
+
+ *((struct asd_softc **) scsi_host->hostdata) = asd;
+
+ asd_lock(asd, &flags);
+
+ /* Fill in host related fields. */
+ asd->platform_data->scsi_host = scsi_host;
+ scsi_host->can_queue = asd->profile.can_queue;
+ scsi_host->cmd_per_lun = asd->profile.max_cmds_per_lun;
+ scsi_host->sg_tablesize = ASD_NSEG;
+ scsi_host->max_channel = asd->profile.max_channels;
+ scsi_host->max_id = asd->profile.max_scsi_ids;
+ scsi_host->max_lun = asd->profile.max_luns;
+ scsi_host->this_id = asd->profile.initiator_id;
+ scsi_host->unique_id = asd->profile.unit = asd_get_unit();
+ scsi_host->irq = asd->profile.irq;
+ asd_assign_host_lock(asd);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,4) && \
+ LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+ scsi_set_pci_device(scsi_host, asd->dev);
+#endif
+ asd_unlock(asd, &flags);
+
+ /*
+ * Create a discovery thread and block the OS
+ * discovery of targets until our first discovery
+ * pass has completed.
+ */
+ asd_freeze_hostq(asd);
+ asd->platform_data->flags |= ASD_DISCOVERY_INIT;
+
+ asd->platform_data->discovery_pid = kernel_thread(
+ asd_discovery_thread,
+ asd, 0);
+ if (asd->platform_data->discovery_pid < 0) {
+ asd_print("%s: Failed to create discovery thread, error=%d\n",
+ asd_name(asd), asd->platform_data->discovery_pid);
+ return (-asd->platform_data->discovery_pid);
+ }
+
+ asd->platform_data->ehandler_pid = kernel_thread(
+ asd_ehandler_thread,
+ asd, 0);
+ if (asd->platform_data->ehandler_pid < 0) {
+ asd_print("%s: Failed to create error handler thread, "
+ "error=%d\n",
+ asd_name(asd), asd->platform_data->ehandler_pid);
+ return (-asd->platform_data->ehandler_pid);
+ }
+
+#ifdef ASD_EH_SIMULATION
+ asd->platform_data->eh_simul_pid = kernel_thread(
+ asd_eh_simul_thread,
+ asd, 0);
+ if (asd->platform_data->eh_simul_pid < 0) {
+ asd_print("%s: Failed to create eh_simul thread, error=%d\n",
+ asd_name(asd), asd->platform_data->eh_simul_pid);
+ return (-asd->platform_data->eh_simul_pid);
+ }
+#endif
+
+ return (0);
+}
+
+/*
+ * Function:
+ * __asd_unregister_host()
+ *
+ * Description:
+ * Revert what asd_register_host() did. This function is to be used
+ * from asd_detect().
+ */
+static void __asd_unregister_host(struct asd_softc *asd)
+{
+#ifdef ASD_EH_SIMULATION
+ /* shutdown the simulation thread */
+ asd->platform_data->flags |= ASD_EH_SIMUL_SHUTDOWN;
+ up(&asd->platform_data->eh_simul_sem);
+#endif /* ASD_EH_SIMULATION */
+
+ /* shutdown the eh and discovery thread */
+ asd->platform_data->flags |=
+ (ASD_RECOVERY_SHUTDOWN | ASD_DISCOVERY_SHUTDOWN);
+ up(&asd->platform_data->ehandler_sem);
+ up(&asd->platform_data->discovery_sem);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
+ scsi_remove_host(asd->platform_data->scsi_host);
+ scsi_host_put(asd->platform_data->scsi_host);
+#else
+ scsi_unregister(asd->platform_data->scsi_host);
+#endif
+}
+
+/***************************** Queue Handling ********************************/
+
+struct asd_device *
+asd_alloc_device(struct asd_softc *asd, struct asd_target *targ,
+ u_int ch, u_int id, u_int lun)
+{
+ struct asd_device *dev;
+
+ dev = asd_alloc_mem(sizeof(*dev), GFP_ATOMIC);
+ if (dev == NULL)
+ return (NULL);
+
+ memset(dev, 0, sizeof(*dev));
+ init_timer(&dev->timer);
+ INIT_LIST_HEAD(&dev->busyq);
+ dev->flags = ASD_DEV_UNCONFIGURED;
+ dev->ch = ch;
+ dev->id = id;
+ dev->lun = lun;
+ memcpy(dev->saslun, &lun, sizeof(u_int)/*8*/);//TBD
+ dev->target = targ;
+#ifdef MULTIPATH_IO
+ dev->current_target = targ;
+#endif
+
+ /*
+ * We start out life using untagged
+ * transactions of which we allow one.
+ */
+ dev->openings = 1;
+
+ /*
+ * Set maxtags to 0. This will be changed if we
+ * later determine that we are dealing with
+ * a tagged queuing capable device.
+ */
+ dev->maxtags = 0;
+
+ targ->devices[lun] = dev;
+ asd_target_addref(targ);
+
+ return (dev);
+}
+
+void
+asd_free_device(struct asd_softc *asd, struct asd_device *dev)
+{
+ struct asd_target *targ;
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,41)
+ /*
+ * Ensure no scheduled workqueue entry is running for
+ * this device prior freeing the device.
+ */
+ if ((dev->flags & ASD_DEV_DPC_ACTIVE) != 0)
+ flush_scheduled_work();
+#endif
+ targ = dev->target;
+ targ->devices[dev->lun] = NULL;
+ asd_free_mem(dev);
+ asd_target_release(asd, targ);
+ targ->flags &= ~ASD_TARG_MAPPED;
+}
+
+struct asd_target *
+asd_alloc_target(struct asd_softc *asd, struct asd_port *src_port)
+{
+ struct asd_target *targ;
+
+ targ = asd_alloc_mem(sizeof(*targ), GFP_ATOMIC);
+ if (targ == NULL)
+ return (NULL);
+ memset(targ, 0, sizeof(*targ));
+
+ /*
+ * By default, we are not mapped into
+ * the OS topology view.
+ */
+ targ->domain = NULL;
+ targ->target = ASD_MAX_TARGETS;
+
+ /*
+ * Targets stay around until some event drops
+ * the sentinel reference count on the object.
+ */
+ targ->refcount = 0;
+
+ targ->softc = asd;
+ targ->flags |= ASD_TARG_FLAGS_NONE;
+ targ->src_port = src_port;
+ INIT_LIST_HEAD(&targ->children);
+ INIT_LIST_HEAD(&targ->siblings);
+ INIT_LIST_HEAD(&targ->all_domain_targets);
+ INIT_LIST_HEAD(&targ->validate_links);
+ INIT_LIST_HEAD(&targ->multipath);
+ init_timer(&targ->timer);
+ return (targ);
+}
+
+void
+asd_free_target(struct asd_softc *asd, struct asd_target *targ)
+{
+ list_del(&targ->children);
+ list_del(&targ->siblings);
+ list_del(&targ->multipath);
+ list_del(&targ->all_domain_targets);
+
+ if (targ->domain != NULL) {
+ asd_domain_release(asd, targ);
+ targ->domain = NULL;
+ }
+
+ if (targ->Phy != NULL) {
+ asd_free_mem(targ->Phy);
+ }
+
+ if (targ->RouteTable != NULL) {
+ asd_free_mem(targ->RouteTable);
+ }
+
+ asd_free_mem(targ);
+}
+
+static struct asd_domain *
+asd_alloc_domain(struct asd_softc *asd, u_int channel_mapping)
+{
+ struct asd_domain *dm;
+
+ dm = asd_alloc_mem(sizeof(*dm), GFP_ATOMIC);
+ if (dm == NULL)
+ return (NULL);
+
+ memset(dm, 0, sizeof(*dm));
+ asd->platform_data->domains[channel_mapping] = dm;
+ dm->channel_mapping = channel_mapping;
+
+ dm->refcount = 0;
+
+ return (dm);
+}
+
+static void
+asd_free_domain(struct asd_softc *asd, struct asd_domain *dm)
+{
+ u_int i;
+
+ for (i = 0; i < ASD_MAX_TARGETS; i++) {
+ if (dm->targets[i] != NULL) {
+ asd_print("%s: Freeing non-empty domain %p!\n",
+ asd_name(asd), dm);
+ break;
+ }
+ }
+ asd->platform_data->domains[dm->channel_mapping] = NULL;
+ asd_free_mem(dm);
+}
+
+static void
+asd_flush_device_queue(struct asd_softc *asd, struct asd_device *dev)
+{
+ union asd_cmd *acmd;
+ struct scsi_cmnd *cmd;
+ struct scb *scb;
+ struct asd_port *port;
+ ASD_COMMAND_BUILD_STATUS build_status;
+
+ ASD_LOCK_ASSERT(asd);
+
+ if ((dev->flags & ASD_DEV_ON_RUN_LIST) != 0)
+ panic("asd_flush_device_queue: running device on run list");
+
+ while (!list_empty(&dev->busyq) && (dev->openings > 0) &&
+ (dev->qfrozen == 0) && (dev->target->qfrozen == 0)) {
+
+ if (asd->platform_data->qfrozen != 0) {
+ /*
+ * Schedule us to run later. The only reason we are not
+ * running is because the whole controller Q is frozen.
+ */
+ list_add_tail(&dev->links,
+ &asd->platform_data->device_runq);
+ dev->flags |= ASD_DEV_ON_RUN_LIST;
+ return;
+ }
+
+ port = dev->target->src_port;
+ if (((port->events & ASD_DISCOVERY_PROCESS) != 0) &&
+ ((port->events & ASD_DISCOVERY_REQ) != 0) &&
+ ((port->events & ASD_VALIDATION_REQ) != 0)) {
+ /*
+ * Discovery is requested / on-going for the port
+ * that the device is currently attached to.
+ * Prevent any new IOs going to the device until
+ * the discovery is done and configuration is
+ * validated.
+ */
+ list_add_tail(&dev->links,
+ &asd->platform_data->device_runq);
+ dev->flags |= ASD_DEV_ON_RUN_LIST;
+ return;
+ }
+
+ acmd = list_entry(dev->busyq.next, union asd_cmd, acmd_links);
+ list_del(&acmd->acmd_links);
+ cmd = &acmd_scsi_cmd(acmd);
+
+ /*
+ * The target is in the process of being destroyed as
+ * it had been hot-removed. Return the IO back to the
+ * scsi layer.
+ */
+ if (dev->target->flags & ASD_TARG_HOT_REMOVED) {
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+ asd_cmd_set_host_status(cmd, DID_NO_CONNECT);
+#else
+ asd_cmd_set_offline_status(cmd);
+#endif
+ cmd->scsi_done(cmd);
+ continue;
+ }
+
+ if (cmd->sc_magic != ASD_CSMI_COMMAND) {
+ /*
+ * Get an scb to use.
+ */
+ if ((scb = asd_hwi_get_scb(asd, 0)) == NULL) {
+ list_add_tail(&acmd->acmd_links, &dev->busyq);
+ list_add_tail(&dev->links,
+ &asd->platform_data->device_runq);
+ dev->flags |= ASD_DEV_ON_RUN_LIST;
+ asd->flags |= ASD_SCB_RESOURCE_SHORTAGE;
+ asd->platform_data->qfrozen++;
+ return;
+ }
+
+ scb->platform_data->dev = dev;
+ scb->platform_data->targ = dev->target;
+ cmd->host_scribble = (char *)scb;
+ /*
+ * SCB build handlers return zero status to indicate
+ * that the SCB should be queued to the controller.
+ * Any other status indicates that the OS's cmd
+ * structure should be completed and that the build
+ * handler has updated its status accordingly.
+ */
+ switch (dev->target->command_set_type) {
+ case ASD_COMMAND_SET_SCSI:
+ build_status = asd_build_sas_scb(
+ asd, scb, acmd);
+ break;
+
+ case ASD_COMMAND_SET_ATA:
+ build_status = asd_build_ata_scb(
+ asd, scb, acmd);
+ break;
+
+ case ASD_COMMAND_SET_ATAPI:
+ build_status = asd_build_atapi_scb(
+ asd, scb, acmd);
+ break;
+
+ default:
+ build_status = ASD_COMMAND_BUILD_FAILED;
+ asd_cmd_set_host_status(cmd, DID_NO_CONNECT);
+
+ /*
+ * Fall through to complete and free the scb.
+ */
+ break;
+ }
+
+ if (build_status != ASD_COMMAND_BUILD_OK) {
+ /*
+ * Two cases here:
+ * 1) The command has been emulated, and it
+ * is ASD_COMMAND_BUILD_FINISHED
+ * 2) The command was malformed and it is
+ * ASD_COMMAND_BUILD_FAILED.
+ */
+ asd_hwi_free_scb(asd, scb);
+ cmd->scsi_done(cmd);
+ continue;
+ }
+ } else {
+ /* command generated by CSMI */
+ scb = (struct scb *)cmd->host_scribble;
+ }
+
+ dev->openings--;
+ dev->active++;
+ dev->commands_issued++;
+ list_add_tail(&scb->owner_links,
+ &asd->platform_data->pending_os_scbs);
+ scb->flags |= SCB_ACTIVE;
+ asd_hwi_post_scb(asd, scb);
+
+#ifdef ASD_EH_SIMULATION
+ if (asd_cmd_get_host_status(cmd) == 0x88) {
+ scb->flags |= SCB_TIMEDOUT;
+ scb->eh_state = SCB_EH_DEV_RESET_REQ;
+ scb->eh_post = asd_eh_simul_done;
+ list_add_tail(&scb->timedout_links,
+ &asd->timedout_scbs);
+ asd_print("Adding scb(%d) to timedout queue for "
+ "error recv. simulation.\n",
+ SCB_GET_INDEX(scb));
+ asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+ }
+#endif /* ASD_EH_SIMULATION */
+ if (cmd->sc_magic == ASD_CSMI_COMMAND)
+ asd_free_mem(cmd);
+ }
+
+}
+
+#ifdef ASD_EH_SIMULATION
+static void
+asd_eh_simul_done(struct asd_softc *asd, struct scb *scb)
+{
+ asd_print("EH SIMULATION DONE.\n");
+}
+
+static int
+asd_eh_simul_thread(void *data)
+{
+ struct asd_softc *asd;
+ struct scb *scb;
+ u_long flags;
+
+ asd = (struct asd_softc *) data;
+
+ lock_kernel();
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,60)
+ /*
+ * Don't care about any signals.
+ */
+ siginitsetinv(¤t->blocked, 0);
+ daemonize();
+ sprintf(current->comm, "asd_eh_simul_%d", asd->profile.unit);
+#else
+ daemonize("asd_eh_simul_%d", asd->profile.unit);
+ current->flags |= PF_FREEZE;
+#endif
+ unlock_kernel();
+
+ while (1) {
+sleep:
+ down_interruptible(&asd->platform_data->eh_simul_sem);
+
+ /* Check to see if we've been signaled to exit. */
+ asd_lock(asd, &flags);
+ if ((asd->platform_data->flags & ASD_EH_SIMUL_SHUTDOWN) != 0) {
+ asd_unlock(asd, &flags);
+ break;
+ }
+
+ asd_unlock(asd, &flags);
+
+ if (!list_empty(&asd->timedout_scbs)) {
+ scb = list_entry(asd->timedout_scbs.next, struct scb,
+ timedout_links);
+ if (scb == NULL) {
+ asd_print("Timedout queue is empty.\n");
+ goto sleep;
+ }
+
+ asd_lock(asd, &flags);
+
+ list_del(&scb->timedout_links);
+ scb->flags &= ~SCB_TIMEDOUT;
+
+ asd_unlock(asd, &flags);
+ }
+ }
+
+ return (0);
+}
+#endif /* ASD_EH_SIMULATION */
+
+static void
+asd_dev_timed_unfreeze(u_long arg)
+{
+ struct asd_softc *asd;
+ struct asd_device *dev;
+ u_long flags;
+
+ dev = (struct asd_device *) arg;
+ asd = dev->target->softc;
+
+ asd_lock(asd, &flags);
+ /*
+ * Release our hold on the device.
+ */
+ dev->flags &= ~ASD_DEV_TIMER_ACTIVE;
+ dev->active--;
+
+ if (dev->qfrozen > 0)
+ dev->qfrozen--;
+
+ if ((dev->qfrozen == 0) && (dev->target->qfrozen == 0) &&
+ ((dev->flags & ASD_DEV_ON_RUN_LIST) == 0))
+ asd_flush_device_queue(asd, dev);
+
+ if ((dev->flags & ASD_DEV_UNCONFIGURED) != 0 &&
+ list_empty(&dev->busyq) && dev->active == 0) {
+ asd_free_device(asd, dev);
+ }
+ asd_unlock(asd, &flags);
+}
+
+void
+asd_timed_run_dev_queue(u_long arg)
+{
+ struct asd_softc *asd;
+ struct asd_device *dev;
+ u_long flags;
+
+ dev = (struct asd_device *) arg;
+ asd = dev->target->softc;
+
+ asd_lock(asd, &flags);
+
+ dev->flags &= ~ASD_DEV_TIMER_ACTIVE;
+
+ if ((dev->qfrozen == 0) && (dev->target->qfrozen == 0) &&
+ ((dev->target->flags & ASD_TARG_HOT_REMOVED) == 0) &&
+ ((dev->flags & ASD_DEV_ON_RUN_LIST) == 0)) {
+
+ asd_flush_device_queue(asd, dev);
+ } else {
+ asd_log(ASD_DBG_ERROR, "DEV QF: %d TARG QF: %d "
+ "DEV FL: 0x%x TARG FL: 0x%x.\n",
+ dev->qfrozen, dev->target->qfrozen,
+ dev->flags, dev->target->flags);
+ }
+
+ asd_unlock(asd, &flags);
+}
+
+static void
+asd_scb_done(struct asd_softc *asd, struct scb *scb, struct asd_done_list *dl)
+{
+ Scsi_Cmnd *cmd;
+ struct asd_device *dev;
+
+ if ((scb->flags & SCB_ACTIVE) == 0) {
+ asd_print("SCB %d done'd twice\n", SCB_GET_INDEX(scb));
+ panic("Stopping for safety");
+ }
+
+ list_del(&scb->owner_links);
+
+ cmd = &acmd_scsi_cmd(scb->io_ctx);
+ dev = scb->platform_data->dev;
+ dev->active--;
+ dev->openings++;
+ if ((scb->flags & SCB_DEV_QFRZN) != 0) {
+ scb->flags &= ~SCB_DEV_QFRZN;
+ dev->qfrozen--;
+ }
+
+ asd_unmap_scb(asd, scb);
+
+ /*
+ * Guard against stale sense data.
+ * The Linux mid-layer assumes that sense
+ * was retrieved anytime the first byte of
+ * the sense buffer looks "sane".
+ */
+ cmd->sense_buffer[0] = 0;
+ cmd->resid = 0;
+
+ switch (dl->opcode) {
+ case TASK_COMP_W_UNDERRUN:
+ cmd->resid = asd_le32toh(dl->stat_blk.data.res_len);
+ /* FALLTHROUGH */
+ case TASK_COMP_WO_ERR:
+ asd_cmd_set_host_status(cmd, DID_OK);
+ break;
+
+ case SSP_TASK_COMP_W_RESP:
+ {
+ union edb *edb;
+ struct response_sb *rsp;
+ struct ssp_resp_edb *redb;
+ struct scb *escb;
+ u_int escb_index;
+ u_int edb_index;
+
+ rsp = &dl->stat_blk.response;
+ escb_index = asd_le16toh(rsp->empty_scb_tc);
+ edb_index = RSP_EDB_ELEM(rsp) - 1;
+ edb = asd_hwi_indexes_to_edb(asd, &escb, escb_index, edb_index);
+ if (edb == NULL) {
+ asd_print("Invalid EDB recv for SSP comp w/response.\n"
+ "Returning generic error to OS.\n");
+ asd_cmd_set_host_status(cmd, DID_ERROR);
+ break;
+ }
+ redb = &edb->ssp_resp;
+ cmd->resid = asd_le32toh(redb->res_len);
+ asd_handle_sas_status(asd, dev, scb, redb, RSP_EDB_BUFLEN(rsp));
+ asd_hwi_free_edb(asd, escb, edb_index);
+ break;
+ }
+
+ case TASK_ABORTED_ON_REQUEST:
+ asd_cmd_set_host_status(cmd, DID_ABORT);
+ break;
+
+ case TASK_CLEARED:
+ {
+ struct task_cleared_sb *task_clr;
+
+ task_clr = &dl->stat_blk.task_cleared;
+
+ asd_log(ASD_DBG_ERROR," Task Cleared for Tag: 0x%x, "
+ "TC: 0x%x.\n",
+ task_clr->tag_of_cleared_task, SCB_GET_INDEX(scb));
+
+ /*
+ * Pending command at the firmware's queues aborted upon
+ * request. If the device is offline then failed the IO.
+ * Otherwise, have the command retried again.
+ */
+ if (task_clr->clr_nxs_ctx == ASD_TARG_HOT_REMOVED) {
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+ asd_cmd_set_host_status(cmd, DID_NO_CONNECT);
+#else
+ asd_cmd_set_offline_status(cmd);
+#endif
+ } else
+ asd_cmd_set_host_status(cmd, DID_SOFT_ERROR);
+
+ break;
+ }
+
+ case TASK_INT_W_BRK_RCVD:
+ asd_log(ASD_DBG_ERROR, "TASK INT. WITH BREAK RECEIVED.\n");
+ asd_cmd_set_host_status(cmd, DID_SOFT_ERROR);
+ break;
+
+ case TASK_ABORTED_BY_ITNL_EXP:
+ {
+ struct itnl_exp_sb *itnl_exp;
+
+ itnl_exp = &dl->stat_blk.itnl_exp;
+
+ asd_log(ASD_DBG_ERROR, "ITNL EXP for SCB 0x%x Reason = 0x%x.\n",
+ SCB_GET_INDEX(scb), itnl_exp->reason);
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+ asd_cmd_set_host_status(cmd, DID_NO_CONNECT);
+#else
+ asd_cmd_set_offline_status(cmd);
+#endif
+ break;
+ }
+
+ case TASK_F_W_NAK_RCVD:
+ asd_log(ASD_DBG_ERROR, "TASK FAILED WITH NAK RECEIVED.\n");
+ asd_cmd_set_host_status(cmd, DID_SOFT_ERROR);
+ break;
+
+ default:
+ asd_log(ASD_DBG_ERROR, "UNHANDLED TASK.\n");
+ asd_cmd_set_host_status(cmd, DID_SOFT_ERROR);
+ break;
+ }
+
+ if ((dev->target->flags & ASD_TARG_HOT_REMOVED) != 0) {
+ /*
+ * If the target had been removed and all active IOs on
+ * the device have been completed, schedule the device to
+ * be destroyed.
+ */
+ if (list_empty(&dev->busyq) && (dev->active == 0) &&
+ ((dev->flags & ASD_DEV_DESTROY_WAS_ACTIVE) != 0)) {
+ /*
+ * Schedule a deferred process task to destroy
+ * the device.
+ */
+ asd_setup_dev_dpc_task(dev, asd_destroy_device);
+ }
+ } else {
+ if ((dev->flags & ASD_DEV_ON_RUN_LIST) == 0) {
+ list_add_tail(&dev->links,
+ &asd->platform_data->device_runq);
+ dev->flags |= ASD_DEV_ON_RUN_LIST;
+ }
+ }
+
+ /*
+ * Only free the scb if it hasn't timedout.
+ * For SCB that has timedout, error recovery has invoked and
+ * the timedout SCB will be freed in the error recovery path.
+ */
+ if ((scb->flags & SCB_TIMEDOUT) == 0)
+ asd_hwi_free_scb(asd, scb);
+
+ cmd->scsi_done(cmd);
+}
+
+void
+asd_scb_internal_done(struct asd_softc *asd, struct scb *scb,
+ struct asd_done_list *dl)
+{
+ if ((scb->flags & SCB_ACTIVE) == 0) {
+ asd_print("SCB %d done'd twice\n", SCB_GET_INDEX(scb));
+ panic("Stopping for safety");
+ }
+
+ list_del(&scb->owner_links);
+
+ /*
+ * In this case, "internal" means that the scb does not have
+ * a Scsi_Cmnd associated with it.
+ */
+ if ((scb->flags & SCB_INTERNAL) == 0) {
+ asd_unmap_scb(asd, scb);
+ }
+
+ switch (dl->opcode) {
+ case TASK_COMP_W_UNDERRUN:
+ /* FALLTHROUGH */
+ case TASK_COMP_WO_ERR:
+ break;
+ case SSP_TASK_COMP_W_RESP:
+ {
+ union edb *edb;
+ struct response_sb *rsp;
+ struct ssp_resp_edb *redb;
+ struct scb *escb;
+ u_int escb_index;
+ u_int edb_index;
+
+ rsp = &dl->stat_blk.response;
+ escb_index = asd_le16toh(rsp->empty_scb_tc);
+ edb_index = RSP_EDB_ELEM(rsp) - 1;
+ edb = asd_hwi_indexes_to_edb(asd, &escb, escb_index, edb_index);
+ if (edb == NULL) {
+ asd_print("Invalid EDB recv for SSP comp w/response.\n"
+ "Returning generic error to OS.\n");
+ break;
+ }
+ redb = &edb->ssp_resp;
+ asd_hwi_free_edb(asd, escb, edb_index);
+ }
+ default:
+ break;
+ }
+
+ /*
+ * Only free the scb if it hasn't timedout.
+ * For SCB that has timedout, error recovery has invoked and
+ * the timedout SCB will be freed in the error recovery path.
+ */
+ if ((scb->flags & SCB_TIMEDOUT) == 0)
+ asd_hwi_free_scb(asd, scb);
+}
+
+static void
+asd_handle_sas_status(struct asd_softc *asd, struct asd_device *dev,
+ struct scb *scb, struct ssp_resp_edb *edb, u_int edb_len)
+{
+ struct ssp_resp_iu *riu;
+ Scsi_Cmnd *cmd;
+
+ cmd = &acmd_scsi_cmd(scb->io_ctx);
+ riu = &edb->resp_frame.riu;
+ if (edb_len < offsetof(struct ssp_resp_edb, resp_frame.riu.res_2)) {
+ asd_print("Insufficient data recv for SSP comp w/response.\n"
+ "Returning generic error to OS.\n");
+ asd_cmd_set_host_status(cmd, DID_ERROR);
+ return;
+ }
+
+ switch (SSP_RIU_DATAPRES(riu)) {
+ case SSP_RIU_DATAPRES_RESP:
+ {
+ uint8_t resp_code;
+
+ resp_code = ((struct resp_data_iu *) &riu->data[0])->resp_code;
+
+ asd_print("Unhandled RESPONSE data (resp code: 0x%x).\n"
+ "Returning generic error to OS.\n",
+ resp_code);
+ asd_cmd_set_host_status(cmd, DID_SOFT_ERROR);
+ return;
+ }
+
+ case SSP_RIU_DATAPRES_SENSE:
+ {
+ uint32_t sense_len;
+
+ /* Copy sense data. */
+ if (edb_len < offsetof(struct ssp_resp_edb,
+ resp_frame.riu.sense_len)) {
+ asd_print("Insufficient data recv for sense len.\n"
+ "Returning generic error to OS.\n");
+ asd_cmd_set_host_status(cmd, DID_SOFT_ERROR);
+ return;
+ }
+ sense_len = edb_len - offsetof(struct ssp_resp_edb,
+ resp_frame.riu.data);
+ sense_len = MIN(sense_len, scsi_4btoul(riu->sense_len));
+ if (sense_len <= 0) {
+ asd_print("Insufficient data recv for sense data.\n"
+ "Returning generic error to OS.\n");
+ asd_cmd_set_host_status(cmd, DID_SOFT_ERROR);
+ return;
+ }
+ sense_len = MIN(sizeof(cmd->sense_buffer), sense_len);
+ memset(cmd->sense_buffer, 0, sizeof(cmd->sense_buffer));
+ memcpy(cmd->sense_buffer, riu->data, sense_len);
+ asd_cmd_set_driver_status(cmd, DRIVER_SENSE);
+
+ /*
+ * Power on reset or bus reset occurred, let's have the
+ * command retried again.
+ */
+ if ((cmd->sense_buffer[2] == UNIT_ATTENTION) &&
+ (cmd->sense_buffer[12] == 0x29)) {
+ asd_cmd_set_host_status(cmd, DID_ERROR);
+ break;
+ }
+ }
+ /* FALLTHROUGH*/
+ case SSP_RIU_DATAPRES_NONE:
+ asd_cmd_set_host_status(cmd, DID_OK);
+ asd_cmd_set_scsi_status(cmd, riu->status);
+ break;
+
+ default:
+ asd_log(ASD_DBG_ERROR, "Unknown response frame format.\n"
+ "Returning generic error to OS.\n");
+ asd_cmd_set_host_status(cmd, DID_SOFT_ERROR);
+ return;
+ }
+
+ /*
+ * We don't currently trust the mid-layer to
+ * properly deal with queue full or busy. So,
+ * when one occurs, we tell the mid-layer to
+ * unconditionally requeue the command to us
+ * so that we can retry it ourselves. We also
+ * implement our own throttling mechanism so
+ * we don't clobber the device with too many
+ * commands.
+ */
+ switch (riu->status) {
+ case SCSI_STATUS_OK:
+ case SCSI_STATUS_CHECK_COND:
+ case SCSI_STATUS_COND_MET:
+ case SCSI_STATUS_INTERMED:
+ case SCSI_STATUS_INTERMED_COND_MET:
+ case SCSI_STATUS_RESERV_CONFLICT:
+ case SCSI_STATUS_CMD_TERMINATED:
+ case SCSI_STATUS_ACA_ACTIVE:
+ case SCSI_STATUS_TASK_ABORTED:
+ break;
+
+ case SCSI_STATUS_QUEUE_FULL:
+ {
+ /*
+ * Note that dev->active may not be 100% accurate
+ * since it counts commands in the outgoing SCB queue
+ * that have yet to be seen by the end device. In
+ * practice, this doesn't matter since we will not queue
+ * additional commands until we receive a successful
+ * completion. If we have not dropped the count to
+ * the device's queue depth yet, we will see additional
+ * queue fulls as the outgoing SCB queue drains, resulting
+ * in further drops of the queue depth.
+ */
+ asd_print_path(asd, dev);
+ asd_print("Queue Full!\n");
+
+ dev->tag_success_count = 0;
+ if (dev->active != 0) {
+ /*
+ * Drop our opening count to the number
+ * of commands currently outstanding.
+ */
+ dev->openings = 0;
+ asd_print_path(asd, dev);
+ asd_print("Dropping tag count to %d\n", dev->active);
+ if (dev->active == dev->tags_on_last_queuefull) {
+ dev->last_queuefull_same_count++;
+ /*
+ * If we repeatedly see a queue full
+ * at the same queue depth, this
+ * device has a fixed number of tag
+ * slots. Lock in this tag depth
+ * so we stop seeing queue fulls from
+ * this device.
+ */
+ if (dev->last_queuefull_same_count ==
+ ASD_LOCK_TAGS_COUNT) {
+ dev->maxtags = dev->active;
+ asd_print_path(asd, dev);
+ asd_print("Locking tag count at %d\n",
+ dev->active);
+ }
+ } else {
+ dev->tags_on_last_queuefull = dev->active;
+ dev->last_queuefull_same_count = 0;
+ }
+
+ asd_set_tags(asd, dev,
+ (dev->flags & ASD_DEV_Q_BASIC) ?
+ ASD_QUEUE_BASIC : ASD_QUEUE_TAGGED);
+
+ asd_cmd_set_retry_status(cmd);
+ break;
+ }
+ /*
+ * Drop down to a single opening, and treat this
+ * as if the target returned BUSY SCSI status.
+ */
+ dev->openings = 1;
+ asd_cmd_set_scsi_status(cmd, SCSI_STATUS_BUSY);
+ /* FALLTHROUGH */
+ }
+ case SCSI_STATUS_BUSY:
+ asd_log(ASD_DBG_ERROR, "REVISITED: SCSI_STATUS_BUSY");
+
+ /*
+ * Set a short timer to defer sending commands for
+ * a bit since Linux will not delay in this case.
+ */
+ if ((dev->flags & ASD_DEV_TIMER_ACTIVE) != 0) {
+ asd_print("%s:%c:%d: Device Timer still active during "
+ "busy processing\n", asd_name(asd),
+ dev->target->domain->channel_mapping,
+ dev->target->target);
+ break;
+ }
+ dev->qfrozen++;
+ /*
+ * Keep the active count non-zero during
+ * the lifetime of the timer. This
+ * guarantees that the device will not
+ * be freed before our timer executes.
+ */
+ dev->active++;
+ dev->flags |= ASD_DEV_TIMER_ACTIVE;
+ init_timer(&dev->timer);
+ dev->timer.data = (u_long) dev;
+ dev->timer.expires = jiffies + (HZ/2);
+ dev->timer.function = asd_dev_timed_unfreeze;
+ add_timer(&dev->timer);
+ break;
+ default:
+ /*
+ * Unknown scsi status returned by the target.
+ * Have the command retried.
+ */
+ asd_cmd_set_host_status(cmd, DID_SOFT_ERROR);
+ break;
+ }
+}
reply other threads:[~2005-02-17 17:36 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4214D62F.30809@adaptec.com \
--to=luben_tuikov@adaptec.com \
--cc=linux-scsi@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.