All of lore.kernel.org
 help / color / mirror / Atom feed
From: Luben Tuikov <luben_tuikov@adaptec.com>
To: SCSI Mailing List <linux-scsi@vger.kernel.org>
Subject: [ANNOUNCE] Adaptec SAS/SATA device driver [15/27]
Date: Thu, 17 Feb 2005 12:36:53 -0500	[thread overview]
Message-ID: <4214D635.5030102@adaptec.com> (raw)

OSM code.  Part 2/3.

+
+ASD_COMMAND_BUILD_STATUS
+asd_setup_data(struct asd_softc *asd, struct scb *scb, Scsi_Cmnd *cmd)
+{
+	struct asd_ssp_task_hscb *ssp_hscb;
+	struct sg_element 	 *sg;
+	int			  dir;
+	int			  error;
+
+	/*
+	 * All SSP, STP, and SATA SCBs have their direction
+	 * flags and SG/elements in the same place, so using
+	 * any of their definitions here is safe.
+	 */
+	ssp_hscb = &scb->hscb->ssp_task;
+	scb->sg_count = 0;
+	error = 0;
+
+	if (cmd->use_sg != 0) {
+		struct	scatterlist *cur_seg;
+		u_int	nseg;
+
+		cur_seg = (struct scatterlist *)cmd->request_buffer;
+		dir = scsi_to_pci_dma_dir(cmd->sc_data_direction);
+		nseg = asd_map_sg(asd, cur_seg, cmd->use_sg, dir);
+		scb->sg_count = nseg;
+		if (nseg > ASD_NSEG) {
+			asd_unmap_sg(asd, cur_seg, nseg, dir);
+			return ASD_COMMAND_BUILD_FAILED;
+		}
+		for (sg = scb->sg_list; nseg > 0; nseg--, cur_seg++, sg++) {
+			dma_addr_t addr;
+			uint32_t len;
+
+			addr = sg_dma_address(cur_seg);
+			len = sg_dma_len(cur_seg);
+			error = asd_sg_setup(sg, addr, len, /*last*/nseg == 1);
+			if (error != 0)
+				break;
+		}
+		if (error != 0) {
+			asd_unmap_sg(asd, cur_seg, scb->sg_count, dir);
+			return ASD_COMMAND_BUILD_FAILED;
+		}
+	} else if (cmd->request_bufflen != 0) {
+		dma_addr_t addr;
+
+		sg = scb->sg_list;
+		dir = scsi_to_pci_dma_dir(cmd->sc_data_direction);
+		addr = asd_map_single(asd,
+				      cmd->request_buffer,
+				      cmd->request_bufflen, dir);
+		scb->platform_data->buf_busaddr = addr;
+		error = asd_sg_setup(sg, addr, cmd->request_bufflen, /*last*/1);
+		if (error != 0) {
+			asd_unmap_single(asd, addr, cmd->request_bufflen, dir);
+			return ASD_COMMAND_BUILD_FAILED;
+		}
+		scb->sg_count = 1;
+	} else {
+		scb->sg_count = 0;
+		dir = PCI_DMA_NONE;
+	}
+
+	if (scb->sg_count != 0) {
+		size_t sg_copy_size;
+
+		sg_copy_size = scb->sg_count * sizeof(*sg);
+		if (scb->sg_count > 3)
+			sg_copy_size = 2 * sizeof(*sg);
+		memcpy(ssp_hscb->sg_elements, scb->sg_list, sg_copy_size);
+		if (scb->sg_count > 3) {
+			/*
+			 * Setup SG sub-list.
+			 */
+			sg = &ssp_hscb->sg_elements[1];
+			sg->next_sg_offset = 2 * sizeof(*sg);
+			sg->flags |= SG_EOS;
+			sg++;
+			sg->bus_address = asd_htole64(scb->sg_list_busaddr);
+			memset(&sg->length, 0,
+			       sizeof(*sg)-offsetof(struct sg_element, length));
+		}
+	}
+
+	switch (dir) {
+	case PCI_DMA_BIDIRECTIONAL:
+		ssp_hscb->data_dir_flags |= DATA_DIR_UNSPECIFIED;
+		break;
+	case PCI_DMA_TODEVICE:
+		ssp_hscb->data_dir_flags |= DATA_DIR_OUTBOUND;
+		break;
+	case PCI_DMA_FROMDEVICE:
+		ssp_hscb->data_dir_flags |= DATA_DIR_INBOUND;
+		break;
+	case PCI_DMA_NONE:
+		ssp_hscb->data_dir_flags |= DATA_DIR_NO_XFER;
+		break;
+	}
+
+	return ASD_COMMAND_BUILD_OK;
+}
+
+static ASD_COMMAND_BUILD_STATUS
+asd_build_sas_scb(struct asd_softc *asd, struct scb *scb, union asd_cmd *acmd)
+{
+	struct asd_ssp_task_hscb	*ssp_hscb;
+	struct asd_target 		*targ;
+	Scsi_Cmnd 			*cmd;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) 
+	int				 msg_bytes;
+	uint8_t 			 tag_msgs[2];
+#endif
+
+	asd_push_post_stack(asd, scb, acmd, asd_scb_done);
+
+	cmd = &acmd_scsi_cmd(acmd);
+#ifdef MULTIPATH_IO
+	targ = scb->platform_data->dev->current_target;
+
+	scb->platform_data->dev->current_target = list_entry(
+		scb->platform_data->dev->current_target->multipath.next,
+		struct asd_target, multipath);
+#else
+	targ = scb->platform_data->dev->target;
+#endif
+	ssp_hscb = &scb->hscb->ssp_task;
+
+	ssp_hscb->header.opcode = SCB_INITIATE_SSP_TASK;
+
+	/*
+	 * Build the SAS frame header.
+	 */
+	asd_build_sas_header(targ, ssp_hscb);
+
+	ssp_hscb->protocol_conn_rate |= PROTOCOL_TYPE_SSP;
+	ssp_hscb->xfer_len = asd_htole32(cmd->request_bufflen);
+
+	/*
+	 * Hnadle for multi-lun devices. 
+	 */
+	memcpy(ssp_hscb->lun, scb->platform_data->dev->saslun, SAS_LUN_LEN);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0) 
+	msg_bytes = scsi_populate_tag_msg(cmd, tag_msgs);
+	if (msg_bytes && tag_msgs[0] != MSG_SIMPLE_TAG)
+		ssp_hscb->task_attr |= tag_msgs[0] & 0x3;
+#endif
+	memcpy(ssp_hscb->cdb, cmd->cmnd, cmd->cmd_len);
+	memset(&ssp_hscb->cdb[cmd->cmd_len], 0,
+	       SCB_EMBEDDED_CDB_SIZE - cmd->cmd_len);
+
+	return (asd_setup_data(asd, scb, cmd));
+}
+
+/*
+ * This routine is called from a tasklet, so we must re-acquire
+ * our lock prior when accessing data-structures that need protection.
+ */
+static void
+asd_runq_tasklet(unsigned long data) {
+	struct asd_softc	*asd;
+	struct asd_device 	*dev;
+	u_long			 flags;
+
+	asd = (struct asd_softc *) data;
+	asd_lock(asd, &flags);
+	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);
+		/* Yeild to our interrupt handler */
+		asd_unlock(asd, &flags);
+		asd_lock(asd, &flags);
+	}
+	asd_unlock(asd, &flags);
+}
+
+static void
+asd_unblock_tasklet(unsigned long data)
+{
+	struct asd_softc *asd;
+
+	asd = (struct asd_softc *) data;
+	scsi_unblock_requests(asd->platform_data->scsi_host);
+}
+
+/*
+ * Function:
+ * 	asd_discovery_thread()
+ *
+ * Description:
+ *	Thread to handle device discovery, topology changes and async. event
+ *	from attached device(s).
+ */	   	  
+static int
+asd_discovery_thread(void *data)
+{
+	struct asd_softc	*asd;
+	u_long			 flags;
+	u_char			 id;
+
+	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_disc_%d", asd->profile.unit);
+#else
+	daemonize("asd_disc_%d", asd->profile.unit);
+	current->flags |= PF_FREEZE;	
+#endif
+	unlock_kernel();
+	
+	while (1) {
+		int 	pending;
+
+		/*
+		 * Use down_interruptible() rather than down() to
+		 * avoid inclusion in the load average.
+		 */
+		down_interruptible(&asd->platform_data->discovery_sem);
+
+		/* Check to see if we've been signaled to exit. */
+		asd_lock(asd, &flags);
+		if ((asd->platform_data->flags & ASD_DISCOVERY_SHUTDOWN) != 0) {
+			asd_unlock(asd, &flags);
+			break;
+		}
+		asd_unlock(asd, &flags);
+
+		/*
+		 * For now, only handle ID ADDR RCVD event.
+		 */
+		pending = 0;
+		for (id = 0; id < asd->hw_profile.max_phys; id++)
+			pending += asd_check_phy_events(asd, id);
+
+		for (id = 0; id < asd->hw_profile.max_ports; id++)
+			pending += asd_check_port_events(asd, id);
+
+		if ((pending == 0) && 
+		    (asd->platform_data->flags & ASD_DISCOVERY_INIT) != 0) {
+			asd_lock(asd, &flags);
+			asd->platform_data->flags &= ~ASD_DISCOVERY_INIT;
+			asd_unlock(asd, &flags);
+			asd_release_hostq(asd);
+		}
+	}
+
+	up(&asd->platform_data->discovery_ending_sem);
+
+	return (0);
+}
+
+/*
+ * Function:
+ * 	asd_kill_discovery_thread()
+ * 
+ * Description:
+ * 	Kill the discovery thread.
+ */
+static void
+asd_kill_discovery_thread(struct asd_softc *asd)
+{
+	u_long	flags;
+
+	asd_lock(asd, &flags);
+
+	if (asd->platform_data->discovery_pid != 0) {
+		asd->platform_data->flags |= ASD_DISCOVERY_SHUTDOWN;
+		asd_unlock(asd, &flags);
+
+		up(&asd->platform_data->discovery_sem);
+
+		/*
+		 * Wait for the discovery thread to exit before continuing
+		 * with the unloading processes.
+		 */
+		down_interruptible(&asd->platform_data->discovery_ending_sem);
+
+		asd->platform_data->discovery_pid = 0;
+	} else {
+		asd_unlock(asd, &flags);
+	}
+}
+
+#ifdef ASD_EH_SIMULATION
+/*
+ * Function:
+ * 	asd_kill_eh_simul_thread()
+ * 
+ * Description:
+ * 	Kill the EH Simulation thread.
+ */ 	   	  
+static void
+asd_kill_eh_simul_thread(struct asd_softc  *asd)
+{
+	u_long	flags;
+
+	asd_lock(asd, &flags);
+
+	if (asd->platform_data->eh_simul_pid != 0) {
+		asd->platform_data->flags |= ASD_EH_SIMUL_SHUTDOWN;
+		asd_unlock(asd, &flags);
+		up(&asd->platform_data->eh_simul_sem);
+		down_interruptible(&asd->platform_data->ehandler_ending_sem);
+		asd->platform_data->eh_simul_pid = 0;
+	} else {
+		asd_unlock(asd, &flags);
+	}
+}
+#endif /* ASD_EH_SIMULATION */
+
+/*
+ * Function:
+ *	asd_check_phy_events()
+ *	
+ * Description:
+ *	Check if any phy events that needs to be handled.
+ */
+static int
+asd_check_phy_events(struct asd_softc *asd, u_int phy_id)
+{
+	struct	asd_phy	*phy;
+	u_long		 flags;
+
+	phy = asd->phy_list[phy_id];
+
+	asd_lock(asd, &flags);
+
+	if ((phy->state != ASD_PHY_WAITING_FOR_ID_ADDR) &&
+	    (list_empty(&phy->pending_scbs))) {
+		asd_unlock(asd, &flags);
+		return 0;
+	}
+
+	if ((phy->state == ASD_PHY_WAITING_FOR_ID_ADDR) &&
+	    ((phy->attr & ASD_SATA_SPINUP_HOLD) != 0) &&
+	    (list_empty(&phy->pending_scbs))) {
+
+		asd_hwi_release_sata_spinup_hold(asd, phy);
+
+		return 1;
+	}
+
+	/* Handle ID Address Frame that has been received. */
+	if ((phy->events & ASD_ID_ADDR_RCVD) != 0) {
+		asd_process_id_addr_evt(asd, phy);
+		phy->events &= ~ASD_ID_ADDR_RCVD;
+		phy->state = ASD_PHY_CONNECTED;
+	}
+
+	asd_unlock(asd, &flags);
+
+	return 1;
+}
+
+/*
+ * Function:
+ *	asd_check_port_events()
+ *
+ * Description:
+ *	Check if any port events that need to be handled.
+ */
+static int
+asd_check_port_events(struct asd_softc *asd, u_int port_id)
+{
+	struct asd_port	*port;
+
+	port = asd->port_list[port_id];
+
+	if ((port->events & ASD_DISCOVERY_PROCESS) != 0) {
+		if ((port->events & ASD_DISCOVERY_EVENT) != 0) {
+			port->events &= ~ASD_DISCOVERY_EVENT;
+
+			asd_run_state_machine(&port->dc.sm_context);
+		}
+
+		if ((port->events & ASD_DISCOVERY_PROCESS) != 0) {
+			return 1;
+		}
+	}
+
+	if (((port->events & ASD_DISCOVERY_REQ) != 0) || 
+		((port->events & ASD_DISCOVERY_RETRY) != 0)) {
+		if (asd_initiate_port_discovery(asd, port) == 0) {
+			port->events |= ASD_DISCOVERY_PROCESS;
+			/* 
+			 * Only clear the event if it is handled successfully. 
+			 */
+			if ((port->events & ASD_VALIDATION_REQ) != 0) {
+				/*
+				 * Validate any targets if needed.
+				 */
+				if (!list_empty(&port->targets_to_validate))
+					asd_configure_port_targets(asd, port);
+
+				port->events &= ~ASD_VALIDATION_REQ;
+			}
+
+			port->events &= ~(ASD_DISCOVERY_REQ | 
+				ASD_DISCOVERY_RETRY | ASD_VALIDATION_REQ);
+
+			asd_do_discovery(asd, port);
+		}
+	}
+
+	if ((port->events & ASD_LOSS_OF_SIGNAL) != 0) {
+		asd_handle_loss_of_signal(asd, port);
+		port->events &= ~ASD_LOSS_OF_SIGNAL;
+	}
+
+	if ((port->events & ASD_VALIDATION_REQ) != 0) {
+		/*
+		 * Validate any targets if needed.
+		 */
+		if (!list_empty(&port->targets_to_validate))
+			asd_configure_port_targets(asd, port);
+
+		port->events &= ~ASD_VALIDATION_REQ;
+	}
+
+	return (0);
+}
+
+/*
+ * Function:
+ *	asd_configure_port_targets
+ *
+ * Description:
+ *	Configure any new targets that have been added.
+ *	Clean up targets that have been removed.
+ */
+static void
+asd_configure_port_targets(struct asd_softc *asd, struct asd_port *port)
+{
+	struct asd_target	*targ;
+	struct asd_target	*tmp_targ;
+	struct list_head	 validate_list;
+	u_long			 flags;
+
+	INIT_LIST_HEAD(&validate_list);
+
+	/*
+	 * TODO:
+	 * Access to the validate_list itself is protected, but we still need
+	 * to make sure that the individual target's validate_links are
+	 * protected.
+	 */
+	asd_list_lock(&flags);
+
+	list_move_all(&validate_list, &port->targets_to_validate);
+
+	asd_list_unlock(&flags);
+
+	/* 
+	 * We might be calling a routine that destroys the target, so use 
+	 * the safe version of list traversal.
+	 */
+	list_for_each_entry_safe(targ, tmp_targ, &validate_list,
+				 validate_links) {
+
+		list_del(&targ->validate_links);
+
+		if (targ->flags & ASD_TARG_HOT_ADDED) {
+			targ->flags &= ~ASD_TARG_HOT_ADDED;
+
+			/*
+			 * We only need to configure the target if it is
+			 * a SAS or SATA END-DEVICE.
+		      	 */	 
+			if (targ->management_type == ASD_DEVICE_END) {
+				asd_configure_target(asd, targ);
+			} else {
+				targ->flags |= ASD_TARG_ONLINE;
+			}
+
+		} else if (targ->flags & ASD_TARG_HOT_REMOVED) {
+			if (targ->flags & ASD_TARG_MAPPED) {
+				/*
+				 * Tell the OS that the device is gone.
+				 */
+				asd_destroy_target(asd, targ);
+			} else {
+				/*
+				 * This target is not exported to the OS,
+				 * so we aren't going to report this device as
+				 * missing.
+				 */
+				asd_free_target(asd, targ);
+			}
+		} else {
+			asd_log(ASD_DBG_ERROR, "Invalid Target Flags.\n");
+		}
+	}
+}
+
+/*
+ * Function:
+ *	asd_configure_target()
+ *
+ * Description:
+ *	Configure target that was HOT-ADDED. 
+ */ 
+static void
+asd_configure_target(struct asd_softc *asd, struct asd_target *targ)
+{
+	u_int	ch;
+	u_int	id;
+	u_int	lun;
+	
+	ch = targ->src_port->id;
+	id = targ->target;
+	/* TODO : Currently the lun is set 0. This needs to be fixed. */
+	lun = 0;
+
+	/* Report the new target found to the user or OS. */
+	asd_print("New device attached at ");
+	asd_print("Host: scsi%d Channel: %d Id: %d Lun: %d\n",
+		  asd->platform_data->scsi_host->host_no, ch, id, lun);
+	
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
+	/*
+	 * For 2.6 kernel, notify the scsi layer that a new had been
+	 * device added.
+	 */
+	scsi_add_device(asd->platform_data->scsi_host, ch, id, lun);
+#endif
+	targ->flags |= (ASD_TARG_ONLINE | ASD_TARG_MAPPED);
+}
+
+/*
+ * Function:
+ *	asd_destroy_target()
+ *
+ * Description:
+ *	Destroy target that was HOT-REMOVED.
+ */ 
+static void
+asd_destroy_target(struct asd_softc *asd, struct asd_target *targ)
+{
+	u_long		flags;
+	unsigned	num_luns;
+	unsigned	i;
+
+	/*
+	 * For an end-device, we need to free the device and report 
+	 * the change to the user or scsi layer.
+	 */
+	if (targ->management_type != ASD_DEVICE_END) {
+		asd_lock(asd, &flags);
+		/* Free the DDB site used by this target. */
+		asd_free_ddb(asd, targ->ddb_profile.conn_handle);
+		/*
+		 * For non end-device, nothing much need to be done.
+		 * All we need to do is free up the target.
+		 */
+		asd_free_target(asd, targ);
+		asd_unlock(asd, &flags);
+		return;
+	}
+
+	switch (targ->command_set_type) {
+	case ASD_COMMAND_SET_SCSI:
+		num_luns = targ->scsi_cmdset.num_luns;
+		break;
+
+	case ASD_COMMAND_SET_ATA:
+	case ASD_COMMAND_SET_ATAPI:
+		num_luns = 1;
+		break;
+
+	default:
+		return;
+	}
+
+	for (i = 0 ; i < num_luns ; i++)
+		asd_init_dev_itnl_exp(asd, targ, i);
+}
+
+static void
+asd_init_dev_itnl_exp(struct asd_softc *asd, struct asd_target *targ,
+		      u_int lun)
+{
+	struct asd_device *dev;
+	u_long		   itnl_timeout;
+	u_long		   flags;
+
+	asd_lock(asd, &flags);
+
+	dev = targ->devices[lun];
+	if (dev == NULL)
+		panic("Dev is corrupted.\n");
+
+	itnl_timeout = ((targ->ddb_profile.itnl_const/1000) + 2) * HZ;
+
+	asd_setup_dev_timer(dev, itnl_timeout, asd_dev_intl_times_out);
+
+	asd_unlock(asd, &flags);
+}
+
+void
+asd_dev_intl_times_out(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;
+
+	/*
+	 * It seems that aftet ITNL timer expired, there are still 
+	 * outstanding IO(s) pending with the firmware.
+	 */
+	if (dev->active > 0) {
+		/*
+		 * Request the firmware to abort all IO(s) pending on
+		 * its queue.
+		 */
+		asd_log(ASD_DBG_ERROR, "ITNL expired, DEV is still ACTIVE.\n");
+
+		dev->flags |= ASD_DEV_DESTROY_WAS_ACTIVE;
+		asd_clear_device_io(asd, dev);
+	} else {
+		/*
+		 * No more active IO(s). Schedule a deferred process to
+		 * destroy the device.
+		 */
+		asd_setup_dev_dpc_task(dev, asd_destroy_device);
+	}
+
+	asd_unlock(asd, &flags);
+}
+
+static void
+asd_clear_device_io(struct asd_softc *asd, struct asd_device *dev)
+{
+	struct scb	*scb;
+	
+	/*
+	 * Send a request to the firmware to abort all IOs pending on its
+	 * queue that are intended for the target.
+	 */	 
+	if ((scb = asd_hwi_get_scb(asd, 1)) == NULL) {
+		asd_log(ASD_DBG_ERROR, "Failed to get free SCB "
+			"for CLEARING firmware queue.\n");
+		return;
+ 	}
+
+	scb->platform_data->dev = dev;
+	scb->platform_data->targ = dev->target;
+
+	asd_hwi_build_clear_nexus(scb, CLR_NXS_I_T_L,
+				 (NOT_IN_Q | SEND_Q | EXEC_Q),
+				  ASD_TARG_HOT_REMOVED);
+				
+	scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+	asd_push_post_stack(asd, scb, (void *) dev, asd_clear_device_io_done);
+	asd_hwi_post_scb(asd, scb);
+}
+
+static void
+asd_clear_device_io_done(struct asd_softc *asd, struct scb *scb,
+			 struct asd_done_list *dl)
+{
+	asd_log(ASD_DBG_ERROR, "DL Opcode = 0x%x.\n", dl->opcode);
+
+	asd_hwi_free_scb(asd, scb);
+}
+
+void
+asd_destroy_device(void *arg)
+{
+	struct asd_softc	*asd;
+	struct asd_device	*dev;
+	struct scsi_cmnd	*cmd;
+	union asd_cmd		*acmd;
+	u_long	 		 flags;
+
+	dev = (struct asd_device *) arg;
+	asd = dev->target->softc;
+
+	asd_lock(asd, &flags);
+	
+	dev->flags &= ~ASD_DEV_DPC_ACTIVE;
+	/*
+	 * Prior to free up the device, make sure no IOs are
+	 * pending on the device queue.
+	 * Return all pending IOs to the scsi layer.
+	 */
+	while (!list_empty(&dev->busyq)) {
+		acmd = list_entry(dev->busyq.next, 
+				  union asd_cmd, acmd_links);
+		list_del(&acmd->acmd_links);
+		cmd = &acmd_scsi_cmd(acmd);
+#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);
+	}
+
+	/* 
+	 * If the device has been exposed to the scsi layer, 
+	 * we need to notify the scsi layer that the device has
+	 * been removed and free up the the device.
+	 */
+	asd_print("%s: device %d:%d:%d:%d has been removed.\n",
+		  ASD_DRIVER_NAME, asd->platform_data->scsi_host->host_no,
+		  dev->ch, dev->target->target, dev->lun);
+
+	if ((dev->flags & ASD_DEV_UNCONFIGURED) != 0) {
+		/*
+		 * The device has not been exposed to the scsi 
+		 * layer yet, all we need to do is free up the device.
+		 */
+		asd_free_device(asd, dev);
+		asd_unlock(asd, &flags);
+		return;
+	}
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,6)
+	if ((dev->flags & ASD_DEV_DESTROY_WAS_ACTIVE) == 0)
+		scsi_device_set_state(dev->scsi_device, SDEV_OFFLINE);
+#endif	
+	/*
+	 * For 2.6 kernel, notify the scsi layer that
+	 * a device had been removed. Ends up in asd_slave_destroy().
+	 */
+	asd_unlock(asd, &flags);
+
+	scsi_remove_device(dev->scsi_device);
+#else
+
+	if ((dev->flags & ASD_DEV_DESTROY_WAS_ACTIVE) == 0)
+		dev->scsi_device->online = 0;
+
+	if (dev->target->refcount == 1) {
+		/*
+		 * Last device attached on this target.
+		 * Free the target's DDB site.
+		 */
+		asd_free_ddb(asd, dev->target->ddb_profile.conn_handle);
+	}
+		
+	/* 
+	 * The device is no longer active.
+	 * It is safe to free up the device.
+	 */
+	asd_free_device(asd, dev);
+
+	asd_unlock(asd, &flags);
+#endif
+}
+
+static void
+asd_handle_loss_of_signal(struct asd_softc *asd, struct asd_port *port)
+{
+	struct asd_target	*targ;
+	struct asd_target	*multipath_target;
+	struct asd_phy		*phy;
+	struct asd_phy		*tmp_phy;
+	u_long			 flags;
+	int			 num_phy_online;
+
+	phy = NULL;
+	num_phy_online = 0;
+
+	asd_lock(asd, &flags);
+
+	/*
+	 * We have lost signal from the port to the attached device.
+       	 * If the port is a narrow port, we need to have all the attached
+	 * targets validated.
+	 * If the port is a wide port, we need to check if there is other 
+	 * link is still connected to the attached device. 
+	 */	 
+	if (port->num_phys > 1) {
+		/* 
+		 * Wide port. We can still functional if other links still
+		 * up and attached to the end device;
+		 */
+		list_for_each_entry_safe(phy, tmp_phy,
+					 &port->phys_attached, links) {
+
+			if (phy->state == ASD_PHY_CONNECTED) {
+				num_phy_online++;
+				continue;
+			}
+			/* 
+			 * We need to disassociate the phy that no longer
+			 * belongs to this wide port.
+			 */
+			if ((phy->state == ASD_PHY_ONLINE)  || 
+			    (phy->state == ASD_PHY_OFFLINE)) {
+				list_del_init(&phy->links);
+				phy->src_port = NULL;
+				port->num_phys--;
+				port->conn_mask &= ~(1 << phy->id);
+			}
+		}
+	}
+
+	if (num_phy_online == 0) {
+		/* 
+		 * Links are down from the port to the attached device. 
+		 * All targets attached to the port need to be validated.
+		 */
+		while (!list_empty(&port->targets)) {
+			targ = list_entry(port->targets.next,
+					  struct asd_target,
+					  all_domain_targets);
+
+			/*
+			 * If this is a multipath target, then move the device
+			 * mapping before removing the device.
+			 */
+			if (!list_empty(&targ->multipath)) {
+
+				multipath_target = list_entry(
+					targ->multipath.next,
+					struct asd_target, multipath);
+
+				asd_remap_device(asd, targ, multipath_target);
+
+				/*
+				 * This device wasn't in the target list
+				 * because it was a multipath.  Add it to the
+				 * list.
+				 */
+				list_add_tail(
+					&multipath_target->all_domain_targets,
+					&multipath_target->src_port->targets);
+
+				targ->flags &= ~ASD_TARG_MAPPED;
+			}
+
+			list_del_init(&targ->all_domain_targets);
+			list_del_init(&targ->children);
+			list_del_init(&targ->siblings);
+			list_del_init(&targ->multipath);
+			targ->flags |= ASD_TARG_HOT_REMOVED;
+			list_add_tail(&targ->validate_links,
+				      &port->targets_to_validate);
+		}
+		INIT_LIST_HEAD(&port->targets);
+		port->events |= ASD_VALIDATION_REQ;
+		/*
+		 * We need to disassociate all the attached phys from this
+		 * port.
+		 */
+		while (!list_empty(&port->phys_attached)) {
+			phy = list_entry(port->phys_attached.next,
+					 struct asd_phy, links);
+			list_del_init(&phy->links);
+			phy->src_port = NULL;
+			port->num_phys--;
+			port->conn_mask = 0;
+			port->state = ASD_PORT_UNUSED;
+		}
+	}
+
+	asd_unlock(asd, &flags);
+}
+
+/* 
+ * parse thro' list of all devices discovered and retireve target by 
+ * sas address.
+ */
+struct asd_target * 
+asd_get_sas_target_from_sasaddr(struct asd_softc *asd, struct asd_port *port, 
+				uint8_t *sasaddr)
+{
+	struct asd_target 	*target;
+	u_long			 flags;
+	
+	if ((asd == NULL) || (port == NULL) || (sasaddr == NULL)) {
+		return NULL;
+	}
+
+	asd_lock(asd, &flags);
+	list_for_each_entry(target, &port->targets, all_domain_targets) {
+		if (memcmp(target->ddb_profile.sas_addr, 
+			   sasaddr, 
+			   SAS_ADDR_LEN) == 0) {
+			asd_unlock(asd, &flags);
+			return target;
+		}
+	}	
+	asd_unlock(asd, &flags);
+
+	return NULL;
+}
+
+/* parse thro' devices exported to os and retrieve target by SAS address */
+struct asd_target * 
+asd_get_os_target_from_sasaddr(struct asd_softc *asd, struct asd_domain *dm,
+			       uint8_t *sasaddr)
+{
+	struct asd_target 	*target;
+	int 			 i;
+	u_long 			 flags;
+	
+	if ((asd == NULL) || (dm == NULL))
+		return NULL;
+	
+	asd_lock(asd, &flags);
+	for (i = 0; i < ASD_MAX_TARGETS; i++) {
+		if ((target = dm->targets[i]) != NULL) {
+			if (memcmp(target->ddb_profile.sas_addr, 
+			   	   sasaddr, 
+			   	   SAS_ADDR_LEN) == 0) {
+				asd_unlock(asd, &flags);
+				return target;
+			}
+		}
+	}
+	asd_unlock(asd, &flags);
+	return NULL;
+}
+
+/* parse thro' devices exported to os and retrieve target by port 
+ * This routine will have to be discarded soon.
+ * */
+struct asd_target * 
+asd_get_os_target_from_port(struct asd_softc *asd, struct asd_port *port, 
+			    struct asd_domain *dm)
+{
+	int i;
+	u_long flags;
+	
+	if ((asd == NULL) || (dm == NULL) || (port == NULL))
+		return NULL;
+	
+	asd_lock(asd, &flags);
+	for (i = 0; i < ASD_MAX_TARGETS; i++) {
+		if ((dm->targets[i] != NULL) 
+		   && (dm->targets[i]->src_port == port)) {
+			asd_unlock(asd, &flags);
+			return dm->targets[i];
+		}
+	}
+	asd_unlock(asd, &flags);
+
+	return NULL;
+}
+
+struct asd_device *
+asd_get_device_from_lun(struct asd_softc *asd, struct asd_target *targ, 
+			uint8_t *saslun)
+{
+	int 	k;
+	u_long 	flags;
+	
+	if ((asd == NULL) || (targ == NULL)) 
+		return NULL;
+
+	if ((targ->transport_type == ASD_TRANSPORT_STP) || 
+	    (targ->transport_type == ASD_TRANSPORT_ATA)) {
+		return targ->devices[0];
+	}
+
+	if (saslun == NULL)
+		return NULL;
+
+	asd_list_lock(&flags);
+	for (k = 0; k < ASD_MAX_LUNS; k++) {
+		if (targ->devices[k]) {
+			if (memcmp(&targ->devices[k]->saslun, 
+			    saslun, 8) == 0) {
+				asd_list_unlock(&flags);
+				return targ->devices[k];
+			}
+		}
+	}
+	asd_list_unlock(&flags);
+
+	return NULL;
+}
+
+int 
+asd_get_os_platform_map_from_sasaddr(struct asd_softc *asd, 
+				     struct asd_port *port, 
+				     uint8_t *sasaddr, uint8_t *saslun, 
+				     uint8_t *host, uint8_t *bus, 
+				     uint8_t *target, uint8_t *lun)
+{
+	Scsi_Device 		*scsi_device;
+	struct asd_domain	*dm;
+	struct asd_target	*targ;
+	struct asd_device	*dev;
+	struct Scsi_Host 	*scsi_host;
+	
+	dm = asd->platform_data->domains[port->id];
+	if (dm == NULL)
+		return 0;
+	
+	if ((targ = 
+		asd_get_os_target_from_sasaddr(asd, dm, sasaddr)) == NULL)
+		return -ENODEV;
+
+	if ((dev = asd_get_device_from_lun(asd, targ, saslun)) == NULL)	
+		return -ENODEV;
+
+	scsi_device = dev->scsi_device;
+	scsi_host = asd->platform_data->scsi_host;
+
+	*host = scsi_host->host_no;
+	*target = scsi_device->id;
+	*bus = scsi_device->channel;
+	*lun = scsi_device->lun;
+
+	return 0;
+}
+
+struct asd_port *
+asd_get_sas_addr_from_platform_map(struct asd_softc *asd, 
+				   uint8_t host, uint8_t bus, uint8_t target, 
+				   uint8_t lun, uint8_t *sasaddr,
+				   uint8_t *saslun)
+{
+	struct asd_domain	*dm;
+	struct asd_target	*targ;
+	struct asd_device	*dev;
+	struct Scsi_Host 	*scsi_host;
+	struct asd_port		*port;
+	u_long 			 flags;
+
+	asd_lock(asd, &flags);
+
+	port = NULL;
+	scsi_host = asd->platform_data->scsi_host;
+	if ((scsi_host == NULL) || 
+	   ((host != 0xFF) && (host != scsi_host->host_no)))
+		goto exit;
+
+	if ((bus >= asd->platform_data->num_domains) ||
+	    ((dm = asd->platform_data->domains[bus]) == NULL))
+		goto exit;
+
+	if ((target >= ASD_MAX_TARGETS) ||
+	    ((targ = dm->targets[target]) == NULL))
+		goto exit;
+
+	if (lun >= ASD_MAX_LUNS)
+		goto exit;
+
+	if ((dev = targ->devices[lun]) == NULL)
+		goto exit;
+
+	/* Get LUN address. */
+	memcpy(saslun, dev->saslun, SAS_LUN_LEN);
+	/* Get SAS address. */
+	memcpy(sasaddr, targ->ddb_profile.sas_addr, SAS_ADDR_LEN);
+
+	port = targ->src_port;
+
+exit:	
+	asd_unlock(asd, &flags);
+	return (port);
+}
+
+struct scb *
+asd_find_pending_scb_by_qtag(struct asd_softc *asd, uint32_t qtag)
+{
+	struct scb 		*list_scb;
+	struct scb 		*scb;
+	struct scsi_cmnd 	*cmd;
+	union asd_cmd 		*acmd;
+	u_long 			flags;
+	
+	list_scb = NULL;
+	scb = NULL;
+	cmd = NULL;
+	acmd = NULL;
+
+	asd_list_lock(&flags);
+	list_for_each_entry(list_scb, &asd->platform_data->pending_os_scbs,
+			    owner_links) {
+		acmd = list_scb->io_ctx;
+		cmd = &acmd_scsi_cmd(acmd);
+		if (cmd->tag == qtag) {
+			scb = list_scb;
+			break;
+		}
+	}
+	asd_list_unlock(&flags);
+	return scb;
+}
+
+/*
+ * Function:
+ * 	asd_process_id_addr_evt()
+ *
+ * Description:
+ * 	Process Identify Address frame received by the phy. 
+ * 	If the phy has no src port, associate the phy with a port.
+ *	Otherwise, validate if the phy still belongs to the same src port.
+ *	Trigger discovery as needed.
+ */
+static void
+asd_process_id_addr_evt(struct asd_softc *asd, struct asd_phy *phy)
+{
+	struct asd_port	*port;
+	struct asd_phy	*list_phy;
+	int		 wide_port;
+	int		 prev_attached_phy;
+	int		 port_no;
+
+	ASD_LOCK_ASSERT(asd);
+
+	port = NULL;
+	prev_attached_phy = 0;
+	wide_port = 0;
+	/*
+	 * Check existing ports whether the current phy attached to the
+         * port has the same ID ADDR as this new phy.
+	 * If so, this phy shall be associated to the port and aggregate
+	 * as a wide port if it a different phy.
+	 * Else, if it is the same phy then it must be after enable-phy.
+	 */
+	for (port_no = 0; port_no < asd->hw_profile.max_ports; port_no++) {
+		port = asd->port_list[port_no];
+
+		if ((port->state & ASD_PORT_ONLINE) == 0) {
+			continue;
+		}
+
+		if (port->link_type != ASD_LINK_SAS) {
+			/*
+			 * For SATA link, check if the phy is the same as
+		         * the one currently attached to the port.
+			 */
+			list_phy = list_entry(port->phys_attached.next,
+					      struct asd_phy, links);
+			if (list_phy->id == phy->id)
+				prev_attached_phy = 1;
+
+			continue;
+		}
+
+		list_for_each_entry(list_phy, &port->phys_attached, links) {
+			/*
+			 * Check the id_addr that we just got in.
+			 */
+			if (memcmp(PHY_GET_ID_SAS_ADDR(list_phy),
+	       			   PHY_GET_ID_SAS_ADDR(phy), 
+				   SAS_ADDR_LEN) != 0) {
+
+				continue;
+			}
+			/*
+			 * Check the sas address that assigned by the BIOS
+			 */
+			if (memcmp(list_phy->sas_addr, phy->sas_addr, 
+				   SAS_ADDR_LEN) != 0) {
+				continue;
+			}
+			/*
+			 * Check if this is the same phy that was previously
+			 * attached to this port.
+			 * The new phy can only become a wide port if it isn't
+			 * previously attached to this port.
+		         */	 
+			if (list_phy->id == phy->id)
+				prev_attached_phy = 1;
+			else
+				wide_port = 1;
+			
+			break;
+		}
+
+		if ((wide_port == 1) || (prev_attached_phy == 1))
+			break;
+	}
+
+	if ((wide_port == 0) && (prev_attached_phy == 0)) {
+		/*
+	 	 * The phy is not associated with any port. 
+	 	 * Create a new port for this phy.
+	 	 */
+		for (port_no = 0; port_no < asd->hw_profile.max_ports; 
+		     port_no++) {
+			port = asd->port_list[port_no];
+
+			if ((port->state == ASD_PORT_UNUSED) && 
+			    (port->num_phys == 0))
+				break;
+		}
+		memcpy(port->sas_addr, phy->sas_addr, SAS_ADDR_LEN);
+		/* 
+		 * Generate SAS hashed address to be used as the port 
+		 * SAS address. 
+		 */
+		asd_hwi_hash(port->sas_addr, port->hashed_sas_addr);
+
+		/* 
+		 * During enable phy, we have determined whether we have a 
+		 * direct attached SATA device.
+		 */  
+		if (phy->attr & ASD_SATA_MODE) {
+			/*
+			 * TODO: More things to do for SATA II device which 
+			 *	 supports tag queueing. Need to setup sister
+			 *	 ddb, other additional SATA information. Need
+			 *	 to add SATA specific fields in target DDB
+			 *	 profile.
+			 */
+			port->link_type = ASD_LINK_SATA;
+			port->management_type = ASD_DEVICE_END;
+
+		} else if (phy->bytes_dmaed_rcvd.id_addr_rcvd.addr_frame_type & 
+			SAS_END_DEVICE) {
+			/*
+			 * Direct Attached SAS device.
+			 */
+			port->link_type = ASD_LINK_SAS;
+			port->management_type = ASD_DEVICE_END;
+
+		} else if (phy->bytes_dmaed_rcvd.id_addr_rcvd.addr_frame_type & 
+			SAS_EDGE_EXP_DEVICE) {
+			/*
+			 * Edge Expander device.
+			 */
+			port->link_type = ASD_LINK_SAS;
+			port->management_type = ASD_DEVICE_EDGE_EXPANDER;
+		} else {
+			/*
+			 * Fanout Expander device
+			 */
+			if (phy->bytes_dmaed_rcvd.id_addr_rcvd.addr_frame_type & 
+				SAS_FANOUT_EXP_DEVICE) {
+
+				port->link_type = ASD_LINK_SAS;
+				port->management_type =
+					ASD_DEVICE_FANOUT_EXPANDER;
+			}
+		}
+
+		port->state = ASD_PORT_ONLINE;
+	}
+
+	phy->src_port = port;
+
+	if (prev_attached_phy == 0) {
+		list_add_tail(&phy->links, &port->phys_attached);
+		port->num_phys++;
+	}
+	asd_setup_port_data(asd, port, phy);
+
+	/*
+	 * Check to see if the port already has discovery running on it.
+	 */
+	if (((port->events & ASD_DISCOVERY_REQ) == 0) &&
+		((port->events & ASD_DISCOVERY_PROCESS) == 0)) {
+		/*
+		 * The port does not have a discovery running on it.
+		 */
+		port->events |= ASD_DISCOVERY_REQ;
+
+		port->conn_mask |= (1 << phy->id);
+
+		asd->num_discovery++;
+
+	} else {
+		/*
+		 * We should only get here if this is a wide port.  We will not
+		 * mark this phy in the connection mask yet, the discovery
+		 * thread will do that when it is finished.
+		 */
+		ASSERT(wide_port == 1);
+	}
+}
+
+/*
+ * Function:
+ *	asd_setup_port_data()
+ *
+ * Description:
+ *	Setup port settings based on the phy info.
+ */
+static void 
+asd_setup_port_data(struct asd_softc *asd, struct asd_port *port,
+		    struct asd_phy *phy)
+{
+	u_int	lowest_rate;
+
+	/* 
+	 * If this port was previously disabled, change the port state to 
+	 * ONLINE.   
+	 */
+	if (port->state != ASD_PORT_ONLINE)
+		port->state = ASD_PORT_ONLINE;
+
+	port->attr = phy->attr;	
+
+	/* 
+	 * Get the negotiated connection rate.
+	 *
+	 * Notice the two different definitions of connection rate.
+	 * SAS_RATE_XXX is used on the OPEN ADDRESS FRAME.
+	 * For wide port configuration, we need to use lowest link rate of 
+	 * all attached phys as the port connection rate.
+	 */
+	lowest_rate = phy->conn_rate;
+	if (port->num_phys > 1) {
+		struct asd_phy	*list_phy;
+
+		/* Wide port configuration. */
+		list_for_each_entry(list_phy, &port->phys_attached, links) {
+			if (phy->conn_rate < lowest_rate)
+				lowest_rate = phy->conn_rate;
+		}
+	}
+
+	port->conn_rate = ((lowest_rate == SAS_30GBPS_RATE) ? SAS_RATE_30GBPS :
+							      SAS_RATE_15GBPS);
+}
+
+/*
+ * Function:
+ * 	asd_initiate_port_discovery()
+ * 
+ * Description:
+ *	Check any events such as phy events, id_addr frame or dynamic 
+ *	configuration changes that required discovery.
+ */	   	  
+static int
+asd_initiate_port_discovery(struct asd_softc *asd, struct asd_port *port)
+{
+	struct asd_phy		*phy;
+	struct asd_target 	*targ;
+	struct asd_target	*multipath_target;
+	uint64_t		 sas_addr;
+	u_long			 flags;
+
+	// TODO: fix locking - lists should be locked
+
+	if (list_empty(&port->phys_attached)) {
+		asd_log(ASD_DBG_ERROR, "Corrupted port, no phy(s) attached "
+			"to it.\n");
+		return (-1);
+	}
+
+	phy = list_entry(port->phys_attached.next, struct asd_phy, links);
+
+	if ((port->link_type == ASD_LINK_SATA) &&
+	    (port->management_type = ASD_DEVICE_END)) {
+		/*
+	 	 * For direct-attached SATA end-device, generate the SAS addr
+        	 * internally.
+	 	*/
+		sas_addr = asd_htobe64(asd_be64toh(*((uint64_t *)
+				       asd->hw_profile.wwn)) + 0x10 + phy->id);
+	} else {
+		sas_addr = (*(uint64_t *) PHY_GET_ID_SAS_ADDR(phy));
+	}
+
+	/*
+	 * Look up this target to see if it already exists.
+	 */
+	targ = asd_find_target(&port->targets, (uint8_t *) &sas_addr);
+	if (targ != NULL) {
+		list_del_init(&targ->all_domain_targets);
+		list_del_init(&targ->children);
+		list_del_init(&targ->siblings);
+
+		while (!list_empty(&targ->multipath)) {
+			multipath_target = list_entry(targ->multipath.next,
+					  struct asd_target, multipath);
+
+			list_del_init(&multipath_target->multipath);
+
+			list_add_tail(&multipath_target->multipath,
+				&port->targets);
+		}
+
+		list_add_tail(&targ->all_domain_targets, &port->targets);
+	} else {
+		targ = asd_alloc_target(asd, port);
+	}
+
+	if (targ == NULL) {
+		/* 
+		 * TODO: Return for now.
+		 *  	 Probably, we should return and put
+		 *  	 the discovery thread back to sleep
+		 *  	 and restart at some point later on. 
+		 */
+		asd_log(ASD_DBG_ERROR, "Failed to allocate a target !!\n");
+		return (-1);
+	}
+
+	asd_lock(asd, &flags);
+
+	if (asd_setup_target_data(asd, phy, targ) != 0) {
+		asd_log(ASD_DBG_ERROR, "Failed to setup target data !!\n");
+		return (-1);
+	}
+
+     	memcpy(port->attached_sas_addr, targ->ddb_profile.sas_addr,
+	       SAS_ADDR_LEN);
+
+	switch (port->link_type)
+	{
+	case ASD_LINK_SAS:
+		targ->management_type = port->management_type;
+
+		switch (port->management_type)
+		{
+		case ASD_DEVICE_END:
+			/*
+			 * It can't be an STP type because it is directly
+			 * attached to the initiator.
+			 */
+			targ->command_set_type = ASD_COMMAND_SET_SCSI;
+			targ->device_protocol_type = ASD_DEVICE_PROTOCOL_SCSI;
+			targ->transport_type = ASD_TRANSPORT_SSP;
+			break;
+
+		case ASD_DEVICE_EDGE_EXPANDER:
+			targ->command_set_type = ASD_COMMAND_SET_SMP;
+			targ->device_protocol_type = ASD_DEVICE_PROTOCOL_SMP;
+			targ->transport_type = ASD_TRANSPORT_SMP;
+			break;
+
+		case ASD_DEVICE_FANOUT_EXPANDER:
+			targ->command_set_type = ASD_COMMAND_SET_SMP;
+			targ->device_protocol_type = ASD_DEVICE_PROTOCOL_SMP;
+			targ->transport_type = ASD_TRANSPORT_SMP;
+			break;
+
+		case ASD_DEVICE_NONE:
+		case ASD_DEVICE_UNKNOWN:
+			targ->command_set_type = ASD_COMMAND_SET_UNKNOWN;
+			targ->device_protocol_type = 
+				ASD_DEVICE_PROTOCOL_UNKNOWN;
+			targ->transport_type = ASD_TRANSPORT_UNKNOWN;
+			break;
+		}
+		break;
+
+	case ASD_LINK_SATA:
+		targ->command_set_type = ASD_COMMAND_SET_UNKNOWN;
+		targ->device_protocol_type = ASD_DEVICE_PROTOCOL_ATA;
+		targ->transport_type = ASD_TRANSPORT_ATA;
+		targ->management_type = ASD_DEVICE_END;
+		break;
+
+	default:
+		targ->command_set_type = ASD_COMMAND_SET_UNKNOWN;
+		targ->device_protocol_type = ASD_DEVICE_PROTOCOL_UNKNOWN;
+		targ->transport_type = ASD_TRANSPORT_UNKNOWN;
+		targ->management_type = ASD_DEVICE_NONE;
+		break;
+	}
+
+	port->tree_root = targ;
+
+	asd_unlock(asd, &flags);
+
+	return (0);
+}	
+
+/*
+ * Function:
+ * 	asd_setup_target_data()
+ * 
+ * Description:
+ *	Setup target info based on the Identify Address frame received.
+ * 	Also, setup a hardware ddb site for this target.
+ */
+static int
+asd_setup_target_data(struct asd_softc *asd, struct asd_phy *phy,
+		      struct asd_target *targ)
+{
+	uint64_t	sas_addr;
+
+	ASD_LOCK_ASSERT(asd);
+
+	/* Set the sister ddb to invalid for now. */
+	targ->ddb_profile.sister_ddb = ASD_INVALID_DDB_INDEX;
+
+	targ->ddb_profile.conn_rate = phy->src_port->conn_rate;
+
+	/* Set a default ITNL timer, applicable for SAS device only. */
+	targ->ddb_profile.itnl_const = ITNL_TIMEOUT_CONST;
+		
+	/* Setup target protocol. */
+	if (phy->bytes_dmaed_rcvd.id_addr_rcvd.tgt_port_type & SSP_TGT_PORT) {
+
+		targ->transport_type = ASD_TRANSPORT_SSP;
+		targ->command_set_type = ASD_COMMAND_SET_SCSI;
+
+	} else if (phy->bytes_dmaed_rcvd.id_addr_rcvd.tgt_port_type & 
+		SMP_TGT_PORT) {
+
+		targ->transport_type = ASD_TRANSPORT_SMP;
+		targ->command_set_type = ASD_COMMAND_SET_SMP;
+
+	} else if (phy->bytes_dmaed_rcvd.id_addr_rcvd.tgt_port_type & 
+		STP_TGT_PORT) {
+		/* 
+		 * We don't know the command set yet (could be ATAPI or ATA)
+		 * We won't know until IDENTIFY / PIDENTIFY.
+		 */
+		targ->transport_type = ASD_TRANSPORT_STP;
+	} else {
+		/* 
+		 * We don't know the command set yet (could be ATAPI or ATA)
+		 * We won't know until IDENTIFY / PIDENTIFY.
+		 */
+		targ->transport_type = ASD_TRANSPORT_ATA;
+	}
+
+	if (targ->transport_type == ASD_TRANSPORT_ATA) {
+
+		sas_addr =  asd_htobe64(asd_be64toh(*((uint64_t *)
+			asd->hw_profile.wwn)) + 0x10 + phy->id);
+
+		/*
+		 * Setup the SAS Address for this target based on the
+		 * generated SAS Address.
+		 */
+		memcpy(targ->ddb_profile.sas_addr, &sas_addr, SAS_ADDR_LEN);
+	} else {
+		/*
+		 * Setup the SAS Address for this target based on the Identify
+		 * Frame received for this phy.
+		 */
+		memcpy(targ->ddb_profile.sas_addr, PHY_GET_ID_SAS_ADDR(phy),
+		       SAS_ADDR_LEN);
+	}
+
+	asd_hwi_hash(targ->ddb_profile.sas_addr, 
+		     targ->ddb_profile.hashed_sas_addr);
+		
+	/* 
+	 * Based on the target port type, enable the OPEN bit so that 
+	 * OPEN address will be issued when opening an connection.
+	 */
+	if ((phy->bytes_dmaed_rcvd.id_addr_rcvd.tgt_port_type & SSP_TGT_PORT) &&
+		(phy->bytes_dmaed_rcvd.id_addr_rcvd.tgt_port_type & 
+			SMP_TGT_PORT) &&
+		(phy->bytes_dmaed_rcvd.id_addr_rcvd.tgt_port_type & 
+			STP_TGT_PORT)) {
+
+		targ->ddb_profile.open_affl = OPEN_AFFILIATION;
+
+		/*
+		 * TODO: More to be done for STP in the case affiliation is
+		 *	 supported.
+	 	 */	    
+	} else {
+		/* 
+		 * For direct attached SATA device and SATA Port Multi, 
+		 * OPEN Address frame and afflitiation policy are not 
+		 * supported.
+		 */   
+		targ->ddb_profile.open_affl = 0;
+	}
+
+	/* Setup a hardware DDB site for this target. */
+	if (asd_hwi_setup_ddb_site(asd, targ) != 0) {
+		/* 
+		 * Failed to setup ddb site due to no free site.
+		 * TODO: More handling needed here once ddb site recycling 
+		 *	 algorithm is implemented.
+		 *	 For now, just return failure.	 
+		 */
+		return (-1);
+	}
+      		
+	return (0);
+}
+
+/*
+ * Function:
+ * 	asd_map_target()
+ *
+ * Description:
+ *	Mapped the target to the domain.
+ */
+int
+asd_map_target(struct asd_softc *asd, struct asd_target *targ)
+{
+	struct asd_domain	*dm;
+	struct asd_port		*port;
+	u_int			 i;
+	unsigned		 channel;
+
+	port = targ->src_port;
+
+	/*
+	 * Map the target to the domain.
+	 */
+	if (targ->flags & ASD_TARG_MAP_BOOT) {
+		targ->flags &= ~ASD_TARG_MAP_BOOT;
+		channel = 0;
+	} else {
+		channel = port->id;
+	}
+
+	dm = asd->platform_data->domains[channel];
+
+	if (dm == NULL) {
+		dm = asd_alloc_domain(asd, channel);
+		if (dm == NULL) {
+			return (-1);
+		}
+	}
+		
+	for (i = 0; i < ASD_MAX_TARGETS; i++) {
+		if (dm->targets[i] == NULL) {
+			dm->targets[i] = targ;
+			targ->target = i;
+			targ->domain = dm;
+			break;
+		}
+	}
+
+	if (i == ASD_MAX_TARGETS) {
+		return (-1);
+	}
+
+	/* Increment domain ref count for new target mapped. */
+	asd_domain_addref(dm);
+
+	return (0);
+}
+
+
+/***************************** PCI Entry Points *******************************/
+/* 
+ * Function:
+ *	asd_pci_dev_probe()
+ *
+ * Description:
+ *	This routine will be called when OS finds a controller that matches
+ *	and entry in our supported PCI ID table.  It will perform hardware
+ *	initialization and bring our device to the online state. 
+ */
+static int
+asd_pci_dev_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	struct asd_softc		*asd;
+	struct asd_pci_driver_data	*drv_data;
+	asd_dev_t			 dev;
+	unsigned long			 flags;
+	int				 error;
+
+	asd_print("Probing Adaptec AIC-94xx Controller(s)...\n");
+	
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+	if (asd_init_stat.asd_init_state != 1) {
+		asd_print("%s: Ignoring PCI device found after initialization. "
+			  "\n", ASD_DRIVER_NAME);
+		return (-ENODEV);
+	}
+#endif
+
+	/* Sanity checking to make sure the same device is not probed twice. */
+	asd_list_lock(&flags);
+	list_for_each_entry(asd, &asd_hbas, link) {
+		struct pci_dev *probed_pdev;
+
+		probed_pdev = asd_pci_dev(asd);
+		if ((probed_pdev->bus->number == pdev->bus->number) && 
+		    (probed_pdev->devfn == pdev->devfn)) {
+			/* A Duplicate PCI Device found. Ignore it. */
+			asd_print("%s: Ignoring duplicate PCI device.\n", 
+			  	  ASD_DRIVER_NAME);
+			asd_list_unlock(&flags);
+			return (-ENODEV);
+		}
+	}
+	asd_list_unlock(&flags);
+
+	drv_data = (struct asd_pci_driver_data *) (id->driver_data);
+	if (drv_data == NULL) {
+		asd_log(ASD_DBG_ERROR, "PCI Driver Data not found.\n");
+		return (-ENODEV);
+	}
+
+	if (pdev->class == (PCI_CLASS_STORAGE_RAID << 8)
+	    && !attach_HostRAID)
+		return -ENODEV;
+
+	if (pci_enable_device(pdev) != 0)
+		return (-ENODEV);	
+	
+	pci_set_master(pdev);
+		
+	dev = asd_pdev_to_dev(pdev);
+	/* Allocate a softc structure for the current PCI Device found. */
+	asd = asd_alloc_softc(dev);
+	if (asd == NULL)
+		return (-ENOMEM);
+	
+	asd->pci_entry = id;
+	/*
+	 * Perform profile setup for the specific controller.
+	 * Always success.
+	 */
+	error = drv_data->setup(asd);
+	
+	asd->platform_data->domains = asd_alloc_mem(
+					    (asd->hw_profile.max_ports *
+					    sizeof(struct asd_domain)),
+					    GFP_ATOMIC);
+
+	if (asd->platform_data->domains == NULL) {
+		asd_free_softc(asd);
+		return (-ENOMEM);
+	}
+	memset(asd->platform_data->domains, 0x0, (asd->hw_profile.max_ports *
+						  sizeof(struct asd_domain)));
+
+	/*
+	 * Setup PCI consistent dma transfer mask to 32-bit and below 
+	 * 4GB boundary.
+	 */
+	if (asd_set_consistent_dma_mask(asd, 0xFFFFFFFF) != 0) {
+		asd_log(ASD_DBG_ERROR, "Failed to set PCI consistent "
+			"dma mask.\n");
+		asd_free_softc(asd);
+		return (-ENODEV);
+	}	
+	
+	/*
+	 * Setup the dma transfer mask to 64-bit only the following conditions
+	 * are met:
+	 *    (a) the system is running on 64-bit or 'PAE' mode.
+	 *    (b) the controller can do DMA transfer above 4GB boundary.
+	 *    (c) dma address can be above 4GB boundary.
+	 * Otherwise set the dma transfer mask to 32-bit.
+	 */
+	 if ((asd->profile.dma64_addr == 1)	&& 
+	     (ASD_DMA_64BIT_SUPPORT == 1)) {
+		uint64_t    mask;
+		
+		mask = 0xFFFFFFFFFFFFFFFFULL;
+	 	if (asd_set_dma_mask(asd, mask) != 0) {
+			/* 
+			 * Failed to set dma mask to 64-bit, throttle down
+			 * to 32-bit instead.
+			 */
+			 mask = 0xFFFFFFFFULL;
+			 if (asd_set_dma_mask(asd, mask) != 0) {
+			 	/* If this also failed, we need to exit. */
+				asd_log(ASD_DBG_ERROR, "Failed to set DMA "
+					"mask. \n");
+				asd_free_softc(asd);
+				return (-ENODEV);
+			}	 
+	 	}
+		asd->profile.dma_mask = (dma_addr_t) mask;		
+	 } else {
+	 	if (asd_set_dma_mask(asd, 0xFFFFFFFF) != 0) {
+			asd_log(ASD_DBG_ERROR, "Failed to set DMA "
+					"mask. \n");
+			asd_free_softc(asd);
+			return (-ENODEV);
+		}
+		asd->profile.dma_mask = 0xFFFFFFFF;
+	}
+
+	/* 
+	 * TBD: Review locking. This is a single threaded operation and 
+	 * 2.6.x kernel on a SMP machine complains about a potential 
+	 * deadlock due to irqs being disabled. 
+	 */
+	/*asd_lock(asd, &flags);*/
+	/* Initialize the controller. */
+	if (asd_init_hw(asd) != 0) {
+		//asd_unlock(asd, &flags);
+		asd_log(ASD_DBG_ERROR, "Failed to initialize the HW.\n");
+		asd_free_softc(asd);
+		return (-ENODEV);
+	} 	
+	pci_set_drvdata(pdev, asd);
+	/*asd_unlock(asd, &flags);*/
+	
+	asd_list_lock(&flags);
+	list_add_tail(&asd->link, &asd_hbas);
+	asd_list_unlock(&flags);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
+	/* The controller is hot-plugged, register it with the SCSI midlayer. */
+	if (asd_init_stat.asd_init_state == 0) {
+		error = asd_register_host(asd);
+		if (error != 0) {
+			asd_log(ASD_DBG_ERROR, "Failed to register host.\n");
+			asd_list_lock(&flags);
+			list_del(&asd->link);
+			asd_list_unlock(&flags);
+			asd_free_softc(asd);
+		}
+	}
+#endif
+	if (!error)
+		/* Enable the Host interrupt. */
+		asd_intr_enable(asd, 1);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
+	/* 
+	 * For the controller that is hot-plugged, we need to request the 
+	 * midlayer to perform bus scan.
+	 */
+	if (asd_init_stat.asd_init_state == 0) {
+		error = asd_initiate_bus_scan(asd);
+		if (error != 0) {
+			asd_log(ASD_DBG_ERROR, "Failed in performing "
+				"bus scan.\n");
+			asd_list_lock(&flags);
+			list_del(&asd->link);
+			asd_list_unlock(&flags);
+			asd_free_softc(asd);
+		}
+	}	
+#endif
+
+	return (error);
+}


                 reply	other threads:[~2005-02-17 17:37 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=4214D635.5030102@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.