* [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(¤t->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.