All of lore.kernel.org
 help / color / mirror / Atom feed
* [ANNOUNCE] Adaptec SAS/SATA device driver [14/27]
@ 2005-02-17 17:36 Luben Tuikov
  0 siblings, 0 replies; only message in thread
From: Luben Tuikov @ 2005-02-17 17:36 UTC (permalink / raw)
  To: SCSI Mailing List

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(&current->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;
+	}
+}


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2005-02-17 17:36 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-02-17 17:36 [ANNOUNCE] Adaptec SAS/SATA device driver [14/27] Luben Tuikov

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.