All of lore.kernel.org
 help / color / mirror / Atom feed
* [ANNOUNCE] Adaptec SAS/SATA device driver [15/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 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);
+}


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

only message in thread, other threads:[~2005-02-17 17:37 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 [15/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.