* [ANNOUNCE] Adaptec SAS/SATA device driver [08/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
Hardware interface. Part 2/3.
+
+/*
+ * Function:
+ * asd_hwi_process_prim_event()
+ *
+ * Description:
+ * Process any recevied primitives that are not handled by the
+ * firmware (eg. BROADCAST, HARD_RESET, etc.)
+ */
+static void
+asd_hwi_process_prim_event(struct asd_softc *asd, struct asd_phy *phy,
+ u_int reg_addr, u_int reg_content)
+{
+ uint32_t reg_val;
+
+ reg_val = 0;
+ if (reg_addr == LmPRMSTAT0BYTE1) {
+ /*
+ * First byte of Primitive Status register is intended for
+ * BROADCAST primitives.
+ */
+ reg_val = (reg_content << 8) & 0xFF00;
+ switch (reg_val) {
+ case LmBROADCH:
+ case LmBROADRVCH0:
+ case LmBROADRVCH1:
+ asd_log(ASD_DBG_RUNTIME, "BROADCAST PRIMITIVE "
+ "received.\n");
+ /*
+ * Set the event that discovery is needed and
+ * wakeup discovery thread.
+ */
+ if(phy->src_port->events & ASD_DISCOVERY_PROCESS)
+ phy->src_port->events |= ASD_DISCOVERY_RETRY;
+ else
+ phy->src_port->events = ASD_DISCOVERY_REQ;
+ asd_wakeup_sem(&asd->platform_data->discovery_sem);
+ phy->brdcst_rcvd_cnt++;
+ break;
+
+ default:
+ asd_log(ASD_DBG_ERROR, "Unsupported BROADCAST "
+ "primitive.\n");
+ break;
+ }
+ } else if (reg_addr == LmPRMSTAT1BYTE0) {
+ reg_val = reg_content & 0xFF;
+ if (reg_val == LmHARDRST) {
+ asd_log(ASD_DBG_RUNTIME, "HARD_RESET primitive "
+ "received.\n");
+ }
+ } else if (reg_addr == LmPRMSTAT0BYTE3) {
+ reg_val = (reg_content << 24) & 0xFF000000;
+ if (reg_val == LmUNKNOWNP) {
+ asd_log(ASD_DBG_RUNTIME, "Undefined BREAK primitive "
+ "received.\n");
+ }
+ } else {
+ asd_log(ASD_DBG_ERROR, "Unsupported PRIMITIVE STATUS REG.\n");
+ }
+}
+
+/*
+ * Function:
+ * asd_hwi_process_phy_event()
+ *
+ * Description:
+ * Process received async. phy events.
+ */
+static void
+asd_hwi_process_phy_event(struct asd_softc *asd, struct asd_phy *phy,
+ u_int oob_status, u_int oob_mode)
+{
+ switch (oob_status) {
+ case DEVICE_REMOVED:
+ /*
+ * We received a phy event that notified us that the
+ * signal is lost with the direct attached device to
+ * this phy. The device has been hot removed.
+ */
+ asd_log(ASD_DBG_RUNTIME,
+ "PHY_EVENT (%d) - DEVICE HOT REMOVED.\n", phy->id);
+ phy->state = ASD_PHY_ONLINE;
+ phy->attr = (ASD_SSP_INITIATOR | ASD_SMP_INITIATOR |
+ ASD_STP_INITIATOR);
+
+ if (phy->src_port != NULL)
+ phy->src_port->events |= ASD_LOSS_OF_SIGNAL;
+
+ asd_wakeup_sem(&asd->platform_data->discovery_sem);
+ break;
+
+ case DEVICE_ADDED_W_CNT:
+ case DEVICE_ADDED_WO_CNT:
+ asd_log(ASD_DBG_RUNTIME,
+ "PHY_EVENT (%d) - DEVICE HOT ADDED.\n", phy->id);
+
+ /* There is a device attached. */
+ if (oob_status & CURRENT_DEVICE_PRESENT) {
+ phy->attr |= ASD_DEVICE_PRESENT;
+
+ if (oob_status & CURRENT_SPINUP_HOLD)
+ phy->attr |= ASD_SATA_SPINUP_HOLD;
+
+ phy->state = ASD_PHY_WAITING_FOR_ID_ADDR;
+ }
+
+ /* Get the negotiated connection rate. */
+ if (oob_mode & PHY_SPEED_30) {
+ phy->conn_rate = SAS_30GBPS_RATE;
+ } else if (oob_mode & PHY_SPEED_15) {
+ phy->conn_rate = SAS_15GBPS_RATE;
+ }
+
+ /* Get the transport mode. */
+ if (oob_mode & SAS_MODE) {
+ phy->attr |= ASD_SAS_MODE;
+ } else if (oob_mode & SATA_MODE) {
+ phy->attr |= ASD_SATA_MODE;
+ }
+
+ break;
+
+ case CURRENT_OOB1_ERROR:
+ case CURRENT_OOB2_ERROR:
+ asd_print("PHY_EVENT (%d) - OOB ERROR.\n", phy->id);
+ break;
+
+ default:
+ asd_log(ASD_DBG_ERROR,
+ "PHY_EVENT (%d) - UNKNOWN EVENT 0x%x.\n", phy->id,
+ oob_status);
+ break;
+ }
+}
+
+#ifdef ASD_TEST
+static void
+asd_hwi_dump_phy_id_addr(struct asd_phy *phy)
+{
+ u_char i;
+
+ asd_print("ID ADDRESS FRAME RECEIVED.\n");
+ asd_print("Addr Frame Type = 0x%x.\n",
+ phy->bytes_dmaed_rcvd.id_addr_rcvd.addr_frame_type);
+ asd_print("Init Port Type = 0x%x.\n",
+ phy->bytes_dmaed_rcvd.id_addr_rcvd.init_port_type);
+ asd_print("Tgt Port Type = 0x%x.\n",
+ phy->bytes_dmaed_rcvd.id_addr_rcvd.tgt_port_type);
+ asd_print("Phy ID = 0x%x.\n",
+ phy->bytes_dmaed_rcvd.id_addr_rcvd.phy_id);
+ for (i = 0; i < 8; i++)
+ asd_print("TGT Sas Addr[%d] = 0x%x.\n",
+ i, phy->bytes_dmaed_rcvd.id_addr_rcvd.sas_addr[i]);
+}
+#endif
+
+/*
+ * Function:
+ * asd_hwi_handle_link_rst_err()
+ *
+ * Description:
+ * Handle Link Reset Error event as a result of timedout while
+ * waiting for Identity Frame Address or Initial Device-to-Host
+ * Register FIS from direct-attached device.
+ */
+static void
+asd_hwi_handle_link_rst_err(struct asd_softc *asd, struct asd_phy *phy)
+{
+ struct scb *scb;
+
+ /*
+ * We are skipping OOB register initialization as it was done
+ * the first time we enable the phy.
+ * Should we redo this initialization again??
+ */
+
+ /*
+ * We want to retry the link reset sequence up to a certain
+ * amount of time to retry establishing connection with the device.
+ */
+ if (phy->link_rst_cnt > MAX_LINK_RESET_RETRY) {
+ /*
+ * We have tried link reset a few times, the device still
+ * failed to return the ID Frame Addr or Device-to-Host
+ * Register FIS.
+ * End the discovery process for this phy.
+ */
+ phy->state = ASD_PHY_ONLINE;
+ phy->attr = (ASD_SSP_INITIATOR | ASD_SMP_INITIATOR |
+ ASD_STP_INITIATOR);
+ asd_wakeup_sem(&asd->platform_data->discovery_sem);
+ }
+
+ if ((scb = asd_hwi_get_scb(asd, 0)) == NULL) {
+ asd_log(ASD_DBG_ERROR, "Out of SCB resources.\n");
+
+ return;
+ }
+
+ scb->io_ctx = (void *) phy;
+ scb->flags |= SCB_INTERNAL;
+
+ asd_hwi_build_control_phy(scb, phy, ENABLE_PHY);
+
+ list_add_tail(&scb->owner_links, &phy->pending_scbs);
+ phy->link_rst_cnt++;
+ asd_hwi_post_scb(asd, scb);
+}
+
+/*
+ * Function:
+ * asd_hwi_process_req_task()
+ *
+ * Description:
+ * Process the requested task recevied from the sequencer.
+ */
+static void
+asd_hwi_process_req_task(struct asd_softc *asd, uint8_t req_type,
+ uint16_t index)
+{
+ struct scb *scb;
+ int found;
+
+ if ((req_type != REQ_TASK_ABORT) && (req_type != REQ_DEVICE_RESET)) {
+ asd_log(ASD_DBG_ERROR, "Unsupported REQ TASK 0x%x.\n",
+ req_type);
+ return;
+ }
+
+ found = 0;
+ list_for_each_entry(scb, &asd->platform_data->pending_os_scbs,
+ owner_links) {
+ if (SCB_GET_INDEX(scb) == index) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (found == 0) {
+ asd_log(ASD_DBG_ERROR, "REQ TASK with invalid TC.\n");
+ return;
+ }
+
+ if (req_type == REQ_TASK_ABORT)
+ scb->eh_state = SCB_EH_ABORT_REQ;
+ else
+ scb->eh_state = SCB_EH_DEV_RESET_REQ;
+
+ scb->flags |= SCB_TIMEDOUT;
+ scb->eh_post = asd_hwi_req_task_done;
+ list_add_tail(&scb->timedout_links, &asd->timedout_scbs);
+ asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+}
+
+static void
+asd_hwi_req_task_done(struct asd_softc *asd, struct scb *scb)
+{
+ asd_log(ASD_DBG_ERROR, "Req Task completed.\n");
+}
+
+/*************** Helper Functions to build a specific SCB type. ***************/
+
+/*
+ * Function:
+ * asd_hwi_build_id_frame()
+ *
+ * Description:
+ * Build an Identify Frame address.
+ */
+static void
+asd_hwi_build_id_frame(struct asd_phy *phy)
+{
+ struct sas_id_addr *id_addr;
+ u_char i;
+
+ id_addr = (struct sas_id_addr *) phy->id_addr_map.vaddr;
+ memset(id_addr, 0x0, sizeof(*id_addr));
+
+ /* Set the device type to end-device. */
+ id_addr->addr_frame_type |= SAS_END_DEVICE;
+
+ for (i = 0; i < SAS_ADDR_LEN; i++)
+ id_addr->sas_addr[i] = phy->sas_addr[i];
+
+ /* Set the Initiator Port attributes. */
+ id_addr->init_port_type = (SSP_INIT_PORT | STP_INIT_PORT |
+ SMP_INIT_PORT);
+ id_addr->phy_id = phy->id;
+}
+
+/*
+ * Function:
+ * asd_hwi_build_control_phy()
+ *
+ * Description:
+ * Build a CONTROL PHY SCB.
+ * CONTROL PHY SCB is used to control the operation of a phy, such as
+ * enable or disable phy, execute hard reset, release spinup hold and
+ * control ATA device.
+ */
+void
+asd_hwi_build_control_phy(struct scb *scb, struct asd_phy *phy,
+ uint8_t sub_func)
+{
+ struct asd_control_phy_hscb *cntrlphy_hscb;
+ uint8_t phy_id;
+ uint8_t speed_mask;
+
+ speed_mask = 0;
+ phy_id = phy->id;
+ cntrlphy_hscb = &scb->hscb->control_phy;
+
+ cntrlphy_hscb->header.opcode = SCB_CONTROL_PHY;
+ cntrlphy_hscb->phy_id = phy_id;
+ cntrlphy_hscb->sub_func = sub_func;
+
+ if ((cntrlphy_hscb->sub_func == ENABLE_PHY) ||
+ (cntrlphy_hscb->sub_func == EXECUTE_HARD_RESET) ||
+ (cntrlphy_hscb->sub_func == PHY_NO_OP)) {
+ cntrlphy_hscb->func_mask = FUNCTION_MASK_DEFAULT;
+
+ /*
+ * Hot Plug timer needs to be disabled while performing
+ * Hard Reset.
+ */
+ if (cntrlphy_hscb->sub_func == EXECUTE_HARD_RESET)
+ cntrlphy_hscb->func_mask |= HOT_PLUG_DIS;
+
+ /* mask all speeds */
+ speed_mask = (SAS_SPEED_60_DIS | SAS_SPEED_30_DIS |
+ SAS_SPEED_15_DIS | SATA_SPEED_30_DIS |
+ SATA_SPEED_15_DIS);
+
+ /* enable required speed */
+ asd_hwi_set_speed_mask(phy->max_link_rate, &speed_mask);
+ asd_hwi_set_speed_mask(phy->min_link_rate, &speed_mask);
+
+#if SAS_COMSTOCK_SUPPORT
+ /* COMSTOCK only support 1.5 Gbits/s data transfer. */
+ speed_mask &= ~SATA_SPEED_15_DIS;
+ cntrlphy_hscb->speed_mask = speed_mask | (SATA_SPEED_30_DIS |
+ SAS_SPEED_60_DIS |
+ SAS_SPEED_30_DIS);
+#else
+ cntrlphy_hscb->speed_mask = speed_mask;
+#endif
+ /* Set to Hot plug time delay to 100 ms. */
+ cntrlphy_hscb->hot_plug_delay = HOTPLUG_DEFAULT_DELAY;
+ cntrlphy_hscb->port_type = (SSP_INITIATOR_PORT |
+ STP_INITIATOR_PORT |
+ SMP_INITIATOR_PORT);
+ } else {
+ cntrlphy_hscb->func_mask = 0;
+ cntrlphy_hscb->speed_mask = 0;
+ cntrlphy_hscb->hot_plug_delay = 0;
+ cntrlphy_hscb->port_type = 0;
+ }
+
+ cntrlphy_hscb->ovrd_devpres_timer = 0;
+ cntrlphy_hscb->devpres_timer_to_const_ovrd = 0;
+ cntrlphy_hscb->link_reset_retries = 0;
+ memset(&cntrlphy_hscb->res1[0], 0x0,
+ sizeof(struct asd_control_phy_hscb) -
+ offsetof(struct asd_control_phy_hscb, res1));
+ cntrlphy_hscb->conn_handle = 0xFFFF;
+}
+
+/*
+ * Function:
+ * asd_hwi_build_abort_task()
+ *
+ * Description:
+ * Build an ABORT TASK SCB.
+ * ABORT TASK scb is used to abort an SCB previously sent to the
+ * firmware.
+ */
+void
+asd_hwi_build_abort_task(struct scb *scb, struct scb *scb_to_abort)
+{
+ struct asd_abort_task_hscb *abort_hscb;
+ struct asd_target *targ;
+
+ targ = scb_to_abort->platform_data->targ;
+ abort_hscb = &scb->hscb->abort_task;
+ abort_hscb->header.opcode = SCB_ABORT_TASK;
+
+ memset(&abort_hscb->protocol_conn_rate, 0x0,
+ offsetof(struct asd_abort_task_hscb, res3) -
+ offsetof(struct asd_abort_task_hscb, protocol_conn_rate));
+ /*
+ * The conn_rate is only valid when aborting an SCB with SSP protocol.
+ */
+ if (targ->transport_type == ASD_TRANSPORT_SSP)
+ abort_hscb->protocol_conn_rate = targ->ddb_profile.conn_rate;
+
+ if ((SCB_GET_OPCODE(scb_to_abort) == SCB_INITIATE_SSP_TASK) ||
+ (SCB_GET_OPCODE(scb_to_abort) == SCB_INITIATE_LONG_SSP_TASK))
+ abort_hscb->protocol_conn_rate |= PROTOCOL_TYPE_SSP;
+ else if ((SCB_GET_OPCODE(scb_to_abort) == SCB_INITIATE_ATA_TASK) ||
+ (SCB_GET_OPCODE(scb_to_abort) == SCB_INITIATE_ATAPI_TASK))
+ abort_hscb->protocol_conn_rate |= PROTOCOL_TYPE_STP;
+ else
+ abort_hscb->protocol_conn_rate |= PROTOCOL_TYPE_SMP;
+ /*
+ * Build SSP Frame Header and SSP Task IU, these fields only valid
+ * when aborting an SCB with SSP protocol.
+ */
+ if (targ->transport_type == ASD_TRANSPORT_SSP) {
+ /* SSP Frame Header. */
+ abort_hscb->sas_header.frame_type = TASK_FRAME;
+ memcpy(abort_hscb->sas_header.hashed_dest_sasaddr,
+ targ->ddb_profile.hashed_sas_addr, HASHED_SAS_ADDR_LEN);
+ memcpy(abort_hscb->sas_header.hashed_src_sasaddr,
+ targ->src_port->hashed_sas_addr, HASHED_SAS_ADDR_LEN);
+ abort_hscb->sas_header.target_port_xfer_tag = 0xFFFF;
+ abort_hscb->sas_header.data_offset = 0;
+
+ /* SSP Task IU. */
+ if (scb_to_abort->platform_data->dev != NULL) {
+ /*
+ * We could be aborting task for SMP target or
+ * target during discovery and there is no device
+ * associated with that target.
+ */
+ memcpy(abort_hscb->task_iu.lun,
+ scb_to_abort->platform_data->dev->saslun,
+ SAS_LUN_LEN);
+ }
+
+ abort_hscb->task_iu.tmf = ABORT_TASK_TMF;
+ /*
+ * Setting tag_to_manage to 0xFFFF will indicate the sequencer
+ * to use conn_handle, lun, and tc_to_abort to determine the
+ * I_T_L_Q nexus of the task to be aborted.
+ */
+ abort_hscb->task_iu.tag_to_manage = 0xFFFF;
+ }
+
+ abort_hscb->sister_scb = 0xFFFF;
+ abort_hscb->conn_handle = targ->ddb_profile.conn_handle;
+
+ /*
+ * For Aborting SSP Task, we need to suspend the data transmission
+ * of the task to be aborted.
+ */
+ if (SCB_GET_OPCODE(scb_to_abort) == SCB_INITIATE_SSP_TASK) {
+ abort_hscb->suspend_data = SUSPEND_DATA;
+ scb->eh_state |= SCB_EH_SUSPEND_SENDQ;
+ }
+
+ abort_hscb->retry_cnt = TASK_RETRY_CNT;
+
+ /* Set the TC to be aborted. */
+ abort_hscb->tc_to_abort = asd_htole16(SCB_GET_INDEX(scb_to_abort));
+}
+
+/*
+ * Function:
+ * asd_hwi_build_query_task()
+ *
+ * Description:
+ * Build an QUERY TASK SCB.
+ * QUERY TASK scb is used to issue an SSP Task IU for a Query Task
+ * Task Management Function.
+ */
+void
+asd_hwi_build_query_task(struct scb *scb, struct scb *scb_to_query)
+{
+ struct asd_query_ssp_task_hscb *query_hscb;
+ struct asd_target *targ;
+
+ targ = scb_to_query->platform_data->targ;
+ query_hscb = &scb->hscb->query_ssp_task;
+ query_hscb->header.opcode = SCB_QUERY_SSP_TASK;
+
+ memset(&query_hscb->protocol_conn_rate, 0x0,
+ offsetof(struct asd_query_ssp_task_hscb, res3) -
+ offsetof(struct asd_query_ssp_task_hscb, protocol_conn_rate));
+
+ query_hscb->protocol_conn_rate = (targ->ddb_profile.conn_rate |
+ PROTOCOL_TYPE_SSP);
+ /* SSP Frame Header. */
+ query_hscb->sas_header.frame_type = TASK_FRAME;
+ memcpy(query_hscb->sas_header.hashed_dest_sasaddr,
+ targ->ddb_profile.hashed_sas_addr, HASHED_SAS_ADDR_LEN);
+ memcpy(query_hscb->sas_header.hashed_src_sasaddr,
+ targ->src_port->hashed_sas_addr, HASHED_SAS_ADDR_LEN);
+ query_hscb->sas_header.target_port_xfer_tag = 0xFFFF;
+ query_hscb->sas_header.data_offset = 0;
+
+ /* SSP Task IU. */
+ query_hscb->task_iu.tmf = QUERY_TASK_TMF;
+ /*
+ * Setting tag_to_manage to 0xFFFF will indicate the sequencer
+ * to use conn_handle, lun, and tc_to_query to determine the
+ * I_T_L_Q nexus of the task to be queried.
+ */
+ query_hscb->task_iu.tag_to_manage = 0xFFFF;
+
+ query_hscb->sister_scb = 0xFFFF;
+ query_hscb->conn_handle = targ->ddb_profile.conn_handle;
+ query_hscb->retry_cnt = TASK_RETRY_CNT;
+ /* Set the TC to be queried. */
+ query_hscb->tc_to_query = asd_htole16(SCB_GET_INDEX(scb_to_query));
+}
+
+/*
+ * Function:
+ * asd_hwi_build_clear_nexus()
+ *
+ * Description:
+ * Build a CLEAR NEXUS SCB.
+ * CLEAR NEXUS SCB is used to request the firmware that a set of pending
+ * transactions pending for a specified nexus be return to the driver
+ * and free the associated SCBs to the free list.
+ */
+void
+asd_hwi_build_clear_nexus(struct scb *scb, u_int nexus_ind, u_int parm,
+ u_int context)
+{
+ struct asd_clear_nexus_hscb *clr_nxs_hscb;
+ struct asd_target *targ;
+
+#define RESUME_SENDQ_REQ \
+do { \
+ if (context == SCB_EH_RESUME_SENDQ) \
+ clr_nxs_hscb->queue_ind = (uint8_t) RESUME_TX; \
+} while (0)
+
+ clr_nxs_hscb = &scb->hscb->clear_nexus;
+ clr_nxs_hscb->header.opcode = SCB_CLEAR_NEXUS;
+ targ = scb->platform_data->targ;
+
+ memset(&clr_nxs_hscb->nexus_ind, 0x0,
+ offsetof(struct asd_clear_nexus_hscb, res8) -
+ offsetof(struct asd_clear_nexus_hscb, nexus_ind));
+
+ clr_nxs_hscb->nexus_ind = nexus_ind;
+ switch (nexus_ind) {
+ case CLR_NXS_I_OR_T:
+ /* Clear Nexus intended for I or T. */
+ clr_nxs_hscb->conn_mask_to_clr = targ->src_port->conn_mask;
+ break;
+
+ case CLR_NXS_I_T_L:
+ /* Clear Nexus intended for I_T_L. */
+ if (scb->platform_data->dev != NULL) {
+ memcpy(clr_nxs_hscb->lun_to_clr,
+ scb->platform_data->dev->saslun,
+ SAS_LUN_LEN);
+ }
+ /* Fallthrough */
+ case CLR_NXS_IT_OR_TI:
+ /* Clear Nexus intended for I_T or T_I. */
+ clr_nxs_hscb->conn_handle_to_clr =
+ targ->ddb_profile.conn_handle;
+ clr_nxs_hscb->queue_ind = (uint8_t) parm;
+ break;
+
+ case CLR_NXS_I_T_L_Q_TAG:
+ /* Clear Nexus intended for I_T_L_Q (by tag). */
+ clr_nxs_hscb->tag_to_clr = (uint16_t) parm;
+ clr_nxs_hscb->conn_handle_to_clr =
+ targ->ddb_profile.conn_handle;
+ if (scb->platform_data->dev != NULL) {
+ memcpy(clr_nxs_hscb->lun_to_clr,
+ scb->platform_data->dev->saslun,
+ SAS_LUN_LEN);
+ }
+ RESUME_SENDQ_REQ;
+ break;
+
+ case CLR_NXS_I_T_L_Q_TC:
+ /* Clear Nexus intended for I_T_L_Q (by TC). */
+ clr_nxs_hscb->tc_to_clr = (uint16_t) parm;
+ clr_nxs_hscb->conn_handle_to_clr =
+ targ->ddb_profile.conn_handle;
+ if (scb->platform_data->dev != NULL) {
+ memcpy(clr_nxs_hscb->lun_to_clr,
+ scb->platform_data->dev->saslun,
+ SAS_LUN_LEN);
+ }
+ RESUME_SENDQ_REQ;
+ break;
+
+ case CLR_NXS_I_T_L_Q_STAG:
+ /* Clear Nexus intended for I_T_L_Q (by SATA tag). */
+ clr_nxs_hscb->conn_handle_to_clr =
+ targ->ddb_profile.conn_handle;
+ /*
+ * Bits 4-0 of the tag_to_clr contain the SATA tag to be
+ * cleared. Bits 15-5 shall be set to zero.
+ */
+ clr_nxs_hscb->tag_to_clr = ((uint16_t) parm && 0x001F);
+ RESUME_SENDQ_REQ;
+ break;
+
+ case CLR_NXS_ADAPTER:
+ /* Clear Nexus for Adapter. */
+ default:
+ /* Unsupported Clear Nexus function. */
+ break;
+ }
+
+ clr_nxs_hscb->nexus_ctx = (uint16_t) context;
+}
+
+/*
+ * Function:
+ * asd_hwi_build_ssp_tmf()
+ *
+ * Description:
+ * Build a SSP TMF SCB.
+ * SSP TMF SCB is used to issue an SSP Task information unit for a
+ * LOGICAL UNIT RESET, ABORT TASK SET, CLEAR TASK SET, or CLEAR ACA
+ * task management function.
+ */
+void
+asd_hwi_build_ssp_tmf(struct scb *scb, struct asd_target *targ,
+ uint8_t *lun, u_int tmf_opcode)
+{
+ struct asd_ssp_tmf_hscb *tmf_hscb;
+
+ tmf_hscb = &scb->hscb->ssp_tmf;
+ tmf_hscb->header.opcode = SCB_INITIATE_SSP_TMF;
+
+ memset(&tmf_hscb->protocol_conn_rate, 0x0,
+ offsetof(struct asd_ssp_tmf_hscb, res3) -
+ offsetof(struct asd_ssp_tmf_hscb, protocol_conn_rate));
+
+ tmf_hscb->protocol_conn_rate = (targ->ddb_profile.conn_rate |
+ PROTOCOL_TYPE_SSP);
+ /* SSP Frame Header. */
+ tmf_hscb->sas_header.frame_type = TASK_FRAME;
+ memcpy(tmf_hscb->sas_header.hashed_dest_sasaddr,
+ targ->ddb_profile.hashed_sas_addr, HASHED_SAS_ADDR_LEN);
+ memcpy(tmf_hscb->sas_header.hashed_src_sasaddr,
+ targ->src_port->hashed_sas_addr, HASHED_SAS_ADDR_LEN);
+ tmf_hscb->sas_header.target_port_xfer_tag = 0xFFFF;
+ tmf_hscb->sas_header.data_offset = 0;
+
+ /* SSP Task IU. */
+ memcpy(tmf_hscb->task_iu.lun, lun, SAS_LUN_LEN);
+ tmf_hscb->task_iu.tmf = tmf_opcode;
+
+ tmf_hscb->sister_scb = 0xFFFF;
+ tmf_hscb->conn_handle = targ->ddb_profile.conn_handle;
+
+ /* Suspend data transmission to the target. */
+ scb->eh_state |= SCB_EH_SUSPEND_SENDQ;
+ tmf_hscb->suspend_data = SUSPEND_DATA;
+ tmf_hscb->retry_cnt = TASK_RETRY_CNT;
+}
+
+/*
+ * Function:
+ * asd_hwi_build_smp_phy_req()
+ *
+ * Description:
+ * Build a SMP PHY related request.
+ */
+static void
+asd_hwi_build_smp_phy_req(struct asd_port *port, int req_type,
+ int phy_id, int ctx)
+{
+ struct SMPRequest *smp_req;
+
+ smp_req = port->dc.SMPRequestFrame;
+ memset(smp_req, 0, sizeof(*smp_req));
+
+ smp_req->SMPFrameType = SMP_REQUEST_FRAME;
+
+ switch (req_type) {
+ case PHY_CONTROL:
+ smp_req->Function = PHY_CONTROL;
+ smp_req->Request.PhyControl.PhyIdentifier = phy_id;
+ smp_req->Request.PhyControl.PhyOperation = ctx;
+ break;
+
+ case REPORT_PHY_ERROR_LOG:
+ smp_req->Function = REPORT_PHY_ERROR_LOG;
+ smp_req->Request.ReportPhyErrorLog.PhyIdentifier = phy_id;
+ break;
+
+ default:
+ panic("Unknown SMP PHY request type.\n");
+ break;
+ }
+
+ /*
+ * DC: Currently, we are not changing the programmed min/max
+ * physical link rate for LINK RESET or HARD RESET.
+ * We might need to change the link rate if CMSI application
+ * required..
+ */
+}
+
+/*
+ * Function:
+ * asd_hwi_build_smp_task()
+ *
+ * Description:
+ * Build a SMP TASK SCB.
+ * SMP TASK SCB is used to send an SMP TASK to an expander.
+ */
+void
+asd_hwi_build_smp_task(struct scb *scb, struct asd_target *targ,
+ uint64_t req_bus_addr, u_int req_len,
+ uint64_t resp_bus_addr, u_int resp_len)
+{
+ struct asd_smp_task_hscb *smp_hscb;
+
+ smp_hscb = &scb->hscb->smp_task;
+ smp_hscb->header.opcode = SCB_INITIATE_SMP_TASK;
+
+ memset(&smp_hscb->protocol_conn_rate, 0x0,
+ offsetof(struct asd_smp_task_hscb, res5) -
+ offsetof(struct asd_ssp_tmf_hscb, protocol_conn_rate));
+
+ smp_hscb->protocol_conn_rate = (targ->ddb_profile.conn_rate |
+ PROTOCOL_TYPE_SMP);
+ smp_hscb->smp_req_busaddr = req_bus_addr;
+ smp_hscb->smp_req_size = req_len;
+ smp_hscb->smp_req_ds = 0;
+ smp_hscb->sister_scb = 0xffff;
+ smp_hscb->conn_handle = targ->ddb_profile.conn_handle;
+ smp_hscb->smp_resp_busaddr = resp_bus_addr;
+ smp_hscb->smp_resp_size = resp_len;
+ smp_hscb->smp_resp_ds = 0;
+}
+
+/*
+ * Function:
+ * asd_hwi_build_ssp_task()
+ *
+ * Description:
+ * Build a SSP TASK SCB.
+ * SSP TASK SCB is used to send an SSP TASK to an expander.
+ */
+void
+asd_hwi_build_ssp_task(struct scb *scb, struct asd_target *targ,
+ uint8_t *saslun, uint8_t *cdb, uint32_t cdb_len,
+ uint8_t addl_cdb_len, uint32_t data_len)
+{
+ struct asd_ssp_task_hscb *ssp_hscb;
+
+ ssp_hscb = &scb->hscb->ssp_task;
+ ssp_hscb->header.opcode = SCB_INITIATE_SSP_TASK;
+ ssp_hscb->protocol_conn_rate = targ->ddb_profile.conn_rate
+ | PROTOCOL_TYPE_SSP;
+ ssp_hscb->xfer_len = asd_htole32(data_len);
+ ssp_hscb->sas_header.frame_type = OPEN_ADDR_FRAME;
+
+ memcpy(ssp_hscb->sas_header.hashed_dest_sasaddr,
+ targ->ddb_profile.hashed_sas_addr, HASHED_SAS_ADDR_LEN);
+
+ ssp_hscb->sas_header.res = 0;
+
+ memcpy(ssp_hscb->sas_header.hashed_src_sasaddr,
+ targ->src_port->hashed_sas_addr, HASHED_SAS_ADDR_LEN);
+
+ memset(ssp_hscb->sas_header.res1, 0,
+ offsetof(struct asd_sas_header, target_port_xfer_tag) -
+ offsetof(struct asd_sas_header, res1));
+
+ ssp_hscb->sas_header.target_port_xfer_tag = 0xFFFF;
+ ssp_hscb->sas_header.data_offset = 0;
+
+ /* SSP Command IU */
+ memset(ssp_hscb->lun, 0,
+ offsetof(struct asd_ssp_task_hscb, cdb) -
+ offsetof(struct asd_ssp_task_hscb, lun));
+ memcpy(ssp_hscb->lun, saslun, 8);
+ memcpy(ssp_hscb->cdb, cdb, cdb_len);
+
+ memset(&ssp_hscb->cdb[cdb_len], 0,
+ SCB_EMBEDDED_CDB_SIZE - cdb_len);
+
+ ssp_hscb->addl_cdb_len = addl_cdb_len;
+ ssp_hscb->sister_scb = 0xFFFF;
+ ssp_hscb->conn_handle = targ->ddb_profile.conn_handle;
+ ssp_hscb->retry_cnt = TASK_RETRY_CNT;
+
+ memset(&ssp_hscb->LAST_SSP_HSCB_FIELD, 0,
+ offsetof(struct asd_ssp_task_hscb, sg_elements) -
+ offsetof(struct asd_ssp_task_hscb, LAST_SSP_HSCB_FIELD));
+
+ return;
+}
+
+/*
+ * Function:
+ * asd_hwi_build_stp_task()
+ *
+ * Description:
+ * Build a STP TASK SCB.
+ * STP TASK SCB is used to send an ATA TASK to an expander.
+ */
+void
+asd_hwi_build_stp_task(struct scb *scb, struct asd_target *targ,
+ uint32_t data_len)
+{
+ struct asd_ata_task_hscb *ata_hscb;
+
+ ata_hscb = &scb->hscb->ata_task;
+ ata_hscb->header.opcode = SCB_INITIATE_ATA_TASK;
+ ata_hscb->protocol_conn_rate =
+ PROTOCOL_TYPE_SATA | targ->ddb_profile.conn_rate;
+ ata_hscb->xfer_len = asd_htole32(data_len);
+ ata_hscb->data_offset = 0;
+ ata_hscb->sister_scb = 0xffff;
+ ata_hscb->conn_handle = targ->ddb_profile.conn_handle;
+ ata_hscb->retry_cnt = TASK_RETRY_CNT;
+ ata_hscb->affiliation_policy = 0;
+
+ ata_hscb->ata_flags = 0;
+#ifdef TAGGED_QUEUING
+ // RST - add support for SATA II queueing
+ ata_hscb->ata_flags |= LEGACY_QUEUING;
+#else
+ ata_hscb->ata_flags |= UNTAGGED;
+#endif
+ return;
+}
+
+/*
+ * Function:
+ * asd_hwi_hash()
+ *
+ * Desctiption:
+ * Convert a 64-bit SAS address into a 24-bit Hash address.
+ * This is based on the hash implementation from the SAS 1.1 draft.
+ */
+void
+asd_hwi_hash(uint8_t *sas_addr, uint8_t *hashed_addr)
+{
+ const uint32_t distance_9_poly = 0x01DB2777;
+ uint32_t upperbits;
+ uint32_t lowerbits;
+ uint32_t msb;
+ uint32_t moving_one;
+ uint32_t leading_bit;
+ uint32_t regg;
+ int i;
+
+ upperbits = scsi_4btoul(sas_addr);
+ lowerbits = scsi_4btoul(sas_addr + 4);
+ msb = 0x01000000;
+ regg = 0;
+ moving_one = 0x80000000;
+ for (i = 31; i >= 0; i--) {
+ leading_bit = 0;
+ if (moving_one & upperbits)
+ leading_bit = msb;
+ regg <<= 1;
+ regg ^= leading_bit;
+ if (regg & msb)
+ regg ^= distance_9_poly;
+ moving_one >>= 1;
+ }
+ moving_one = 0x80000000;
+ for (i = 31; i >= 0; i--) {
+ leading_bit = 0;
+ if (moving_one & lowerbits)
+ leading_bit = msb;
+ regg <<= 1;
+ regg ^= leading_bit;
+ if (regg & msb)
+ regg ^= distance_9_poly;
+
+ moving_one >>= 1;
+ }
+ scsi_ulto3b(regg, hashed_addr);
+}
+
+/*************************** Error Handling routines **************************/
+
+void
+asd_recover_cmds(struct asd_softc *asd)
+{
+ struct scb *scb;
+ struct scb *free_scb;
+ struct scb *safe_scb;
+ u_long flags;
+
+ if (list_empty(&asd->timedout_scbs)) {
+ asd_log(ASD_DBG_ERROR, "Timed-out scbs already completed.\n");
+ goto exit;
+ }
+
+ list_for_each_entry_safe(scb, safe_scb, &asd->timedout_scbs,
+ timedout_links) {
+ asd_lock(asd, &flags);
+
+ /*
+ * Error recovery is in progress for this scb.
+ * Proceed to next one.
+ */
+ if ((scb->eh_state & SCB_EH_IN_PROGRESS) != 0) {
+ asd_unlock(asd, &flags);
+ continue;
+ }
+ /*
+ * Error recovery is completed for this scb.
+ */
+ if (scb->eh_state == SCB_EH_DONE) {
+ asd_unlock(asd, &flags);
+ goto done;
+ }
+ /*
+ * Only allowed one Error Recovery ongoing for a particular
+ * target.
+ */
+ if ((scb->platform_data->targ->flags &
+ ASD_TARG_IN_RECOVERY) != 0) {
+ asd_unlock(asd, &flags);
+ continue;
+ }
+ /*
+ * Acquire a free SCB from reserved pool to be used
+ * for error recovery purpose.
+ */
+ if ((free_scb = asd_hwi_get_scb(asd, 1)) == NULL) {
+ asd_log(ASD_DBG_ERROR, "Failed to get free SCB "
+ "for error recovery.\n");
+ asd_unlock(asd, &flags);
+ continue;
+ }
+
+ /* Mark this target to be in recovery mode. */
+ scb->platform_data->targ->flags |= ASD_TARG_IN_RECOVERY;
+
+ /* Freeze the target's queue. */
+ asd_freeze_targetq(asd, scb->platform_data->targ);
+
+ /* Initialiaze the state. */
+ scb->eh_state |= SCB_EH_IN_PROGRESS;
+ free_scb->eh_state = SCB_EH_INITIATED;
+ free_scb->eh_status = SCB_EH_SUCCEED;
+
+ switch (scb->eh_state & SCB_EH_LEVEL_MASK) {
+ case SCB_EH_ABORT_REQ:
+ asd_log(ASD_DBG_ERROR, "ABORT ER REQ.\n");
+ asd_hwi_abort_scb(asd, scb, free_scb);
+ break;
+
+ case SCB_EH_LU_RESET_REQ:
+ asd_log(ASD_DBG_ERROR, "LU RESET ER REQ.\n");
+ asd_hwi_reset_lu(asd, scb, free_scb);
+ break;
+
+ case SCB_EH_DEV_RESET_REQ:
+ asd_log(ASD_DBG_ERROR, "DEV RESET ER REQ.\n");
+ asd_hwi_reset_device(asd, scb, free_scb);
+ break;
+
+ case SCB_EH_PORT_RESET_REQ:
+ asd_log(ASD_DBG_ERROR, "PORT RESET ER REQ.\n");
+ asd_hwi_reset_port(asd, scb, free_scb);
+ break;
+
+ default:
+ asd_log(ASD_DBG_ERROR, "Unknown Error Recovery "
+ "Level.\n");
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+ }
+ asd_unlock(asd, &flags);
+
+done:
+ if (scb->eh_state == SCB_EH_DONE) {
+ /*
+ * Error recovery is done for this scb,
+ * Clean up and free the scb.
+ */
+ asd_lock(asd, &flags);
+
+ list_del(&scb->timedout_links);
+ scb->flags &= ~SCB_TIMEDOUT;
+ scb->platform_data->targ->flags &=
+ ~ASD_TARG_IN_RECOVERY;
+ /* Unfreeze the target's queue. */
+ asd_unfreeze_targetq(asd, scb->platform_data->targ);
+ scb->eh_post(asd, scb);
+
+ if (scb->eh_status != SCB_EH_FAILED) {
+ struct asd_device *dev;
+ /*
+ * Schedule a timer to run the device
+ * queue if the device is not frozen and
+ * not in the process of being removed.
+ */
+ dev = scb->platform_data->dev;
+ if ((dev != NULL) && (dev->qfrozen == 0) &&
+ (dev->flags & ASD_DEV_TIMER_ACTIVE) == 0 &&
+ (dev->target->flags &
+ ASD_TARG_HOT_REMOVED) == 0) {
+
+ asd_setup_dev_timer(
+ dev, HZ,
+ asd_timed_run_dev_queue);
+ }
+ /*
+ * Only free the scb if the error recovery is
+ * successful.
+ */
+ asd_hwi_free_scb(asd, scb);
+ }
+ asd_unlock(asd, &flags);
+ }
+ }
+
+exit:
+ return;
+}
+
+static void
+asd_scb_eh_timeout(u_long arg)
+{
+ struct asd_softc *asd;
+ struct scb *scb;
+ struct scb *err_scb;
+ u_long flags;
+
+ scb = (struct scb *) arg;
+ err_scb = (struct scb *) scb->post_stack[0].io_ctx;
+ asd = scb->softc;
+ asd_lock(asd, &flags);
+
+ scb->eh_state = SCB_EH_TIMEDOUT;
+ err_scb->eh_state &= ~SCB_EH_IN_PROGRESS;
+ /*
+ * Error recovery SCB timed out.
+ * If this error recovery requested by the OS, we need to mark the
+ * error recovery failed and make an attempt to perform the next
+ * level error recovery if possible.
+ */
+ switch (err_scb->eh_state) {
+ case SCB_EH_ABORT_REQ:
+ err_scb->eh_state = SCB_EH_LU_RESET_REQ;
+ break;
+
+ case SCB_EH_LU_RESET_REQ:
+ err_scb->eh_state = SCB_EH_DEV_RESET_REQ;
+ break;
+
+ case SCB_EH_DEV_RESET_REQ:
+ err_scb->eh_state = SCB_EH_PORT_RESET_REQ;
+ break;
+
+ case SCB_EH_PORT_RESET_REQ:
+ default:
+ /*
+ * Currently, our biggest hammer now is PORT RESET.
+ * We might perform ADAPTER RESET later on.
+ */
+ err_scb->eh_state = SCB_EH_DONE;
+ break;
+ }
+
+ err_scb->eh_status = SCB_EH_FAILED;
+ err_scb->platform_data->targ->flags &= ~ASD_TARG_IN_RECOVERY;
+ asd_unlock(asd, &flags);
+ asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+}
+
+int
+asd_hwi_check_cmd_pending(struct asd_softc *asd, struct scb *scb,
+ struct asd_done_list *dl)
+{
+ struct scb *abort_scb;
+
+ while (!list_empty(&asd->timedout_scbs)) {
+ abort_scb = list_entry(asd->timedout_scbs.next,
+ struct scb, timedout_links);
+ if (((struct asd_abort_task_hscb *)
+ &scb->hscb->abort_task)->tc_to_abort ==
+ asd_htole16(SCB_GET_INDEX(abort_scb))) {
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Function:
+ * asd_hwi_abort_scb()
+ *
+ * Description:
+ * This routine will issue an ABORT_TASK to abort the requested scb.
+ * ONLY SCB with protocol SSP, SMP and STP can be issued ABORT_TASK TMF.
+ */
+static void
+asd_hwi_abort_scb(struct asd_softc *asd, struct scb *scb_to_abort,
+ struct scb *scb)
+{
+ ASD_LOCK_ASSERT(asd);
+
+ asd_log(ASD_DBG_ERROR, "Curr State: 0x%x Status: 0x%x.\n",
+ scb->eh_state, scb->eh_status);
+
+ /*
+ * DC: We probably need to search the scb_to_abort in the
+ * target/device queue as initial step for internal requested
+ * command.
+ */
+ switch (scb->eh_state) {
+ case SCB_EH_INITIATED:
+ {
+ /*
+ * Validate the opcode of scb to be aborted.
+ * Only scb with specific opcode can be aborted.
+ */
+ if ((SCB_GET_OPCODE(scb_to_abort) != SCB_INITIATE_SSP_TASK) &&
+ (SCB_GET_OPCODE(scb_to_abort) != SCB_INITIATE_SMP_TASK) &&
+ (SCB_GET_OPCODE(scb_to_abort) !=
+ SCB_INITIATE_LONG_SSP_TASK) &&
+ (SCB_GET_OPCODE(scb_to_abort) != SCB_INITIATE_ATA_TASK) &&
+ (SCB_GET_OPCODE(scb_to_abort) != SCB_INITIATE_ATAPI_TASK)) {
+ asd_log(ASD_DBG_ERROR, "Requested to abort unsupported "
+ "SCB request.\n");
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_FAILED;
+ /*
+ * Recursively call this function again upon changing
+ * the state.
+ */
+ asd_hwi_abort_scb(asd, scb_to_abort, scb);
+ break;
+ }
+
+ scb->platform_data->targ = scb_to_abort->platform_data->targ;
+ scb->platform_data->dev = scb_to_abort->platform_data->dev;
+ scb->eh_state = SCB_EH_ABORT_REQ;
+ asd_hwi_abort_scb(asd, (struct scb *) scb_to_abort, scb);
+ break;
+ }
+
+ case SCB_EH_ABORT_REQ:
+ /* Build the ABORT_TASK SCB. */
+ asd_hwi_build_abort_task(scb, scb_to_abort);
+
+ scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+ asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+ asd_push_post_stack(asd, scb, (void *) scb_to_abort,
+ asd_hwi_abort_scb_done);
+ /* Post the ABORT_TASK SCB. */
+ asd_hwi_post_scb(asd, scb);
+ break;
+
+ case SCB_EH_CLR_NXS_REQ:
+ /*
+ * The Clear Nexus SCB has been prepared in the abort
+ * post routine. All we need is to post it to the firmware.
+ */
+ scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+ asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+ asd_push_post_stack(asd, scb, (void *) scb_to_abort,
+ asd_hwi_abort_scb_done);
+ asd_hwi_post_scb(asd, scb);
+ break;
+
+ case SCB_EH_RESUME_SENDQ:
+ /*
+ * We are here because the ABORT TMF failed.
+ * We need to resume the data transmission of the task
+ * that was going to be aborted.
+ */
+ scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+ asd_hwi_build_clear_nexus(scb, CLR_NXS_I_T_L_Q_TC,
+ SCB_GET_INDEX(scb_to_abort),
+ SCB_EH_RESUME_SENDQ);
+ asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+ asd_push_post_stack(asd, scb, (void *) scb_to_abort,
+ asd_hwi_abort_scb_done);
+ asd_hwi_post_scb(asd, scb);
+ break;
+
+ case SCB_EH_DONE:
+ scb_to_abort->eh_state = scb->eh_state;
+ scb_to_abort->eh_status = scb->eh_status;
+
+ if ((scb_to_abort->eh_state == SCB_EH_DONE) &&
+ (scb_to_abort->eh_status == SCB_EH_FAILED)) {
+ /*
+ * Failed to perform abort error recovery for the
+ * failed command, we shall procced with the next
+ * level of error recovery (Logical Unit Reset).
+ * We need to change the scb eh_state to
+ * SCB_EH_LU_RESET_REQ.
+ */
+ scb_to_abort->eh_state = SCB_EH_LU_RESET_REQ;
+ scb_to_abort->platform_data->targ->flags &=
+ ~ASD_TARG_IN_RECOVERY;
+ }
+ asd_hwi_free_scb(asd, scb);
+ asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+ break;
+
+ default:
+ asd_log(ASD_DBG_ERROR, "Invalid EH State 0x%x.\n",
+ scb->eh_state);
+
+ scb_to_abort->eh_state = SCB_EH_DONE;
+ scb_to_abort->eh_status = SCB_EH_FAILED;
+ asd_hwi_free_scb(asd, scb);
+ asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+ break;
+ }
+}
+
+static void
+asd_hwi_abort_scb_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);
+
+ /*
+ * There is a possibility that this post routine is called after
+ * the SCB timedout. So, only delete the timer if the SCB hasn't
+ * timedout.
+ */
+ if (scb->eh_state != SCB_EH_TIMEDOUT)
+ del_timer_sync(&scb->platform_data->timeout);
+
+ /*
+ * This post routine is shared by ABORT_TASK and CLEAR_NEXUS
+ * issued by this abort handler code.
+ * Hence, the DL opcodes also apply to both SCB task.
+ */
+ switch (dl->opcode) {
+ case TASK_COMP_WO_ERR:
+ case TMF_F_W_TAG_NOT_FOUND:
+ case TMF_F_W_CONN_HNDL_NOT_FOUND:
+ case TMF_F_W_TAG_ALREADY_DONE:
+ case TMF_F_W_TAG_ALREADY_FREE:
+ case TMF_F_W_TC_NOT_FOUND:
+ /*
+ * For SMP or STP target, firmware will only try to abort
+ * the task if it is still in its execution queue.
+ * If the task to be aborted couldn't be found, mostly like it
+ * has been issued to the target.
+ * We should try next level error recovery.
+ */
+ if (scb->platform_data->targ->device_protocol_type
+ != ASD_DEVICE_PROTOCOL_SCSI) {
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+ }
+ /* Fall thru */
+ case TASK_ABORTED_BY_ITNL_EXP:
+ /* Indicate that Abort succeeded. */
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_SUCCEED;
+ break;
+
+ case TASK_F_W_NAK_RCVD:
+ /*
+ * Indicate that Abort failed.
+ * If this is a failure in issuing ABORT TMF, we need to
+ * resume the data transmission of the task that was
+ * going to be aborted.
+ */
+ if ((scb->eh_state & SCB_EH_SUSPEND_SENDQ) != 0)
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ else
+ scb->eh_state = SCB_EH_DONE;
+
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+
+ case SSP_TASK_COMP_W_RESP:
+ {
+ union edb *edb;
+ struct scb *escb;
+ struct ssp_resp_edb *redb;
+ struct ssp_resp_iu *riu;
+ u_int edb_index;
+
+ edb = asd_hwi_get_edb_from_dl(asd, scb, dl, &escb, &edb_index);
+ if (edb == NULL) {
+ asd_log(ASD_DBG_ERROR, "Invalid EDB recv for SSP "
+ "comp w/response.\n");
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ scb->eh_status = SCB_EH_SUCCEED;
+ break;
+ }
+
+ /*
+ * Search if the aborted command still pending on the firmware
+ * queue. If so, we need to send CLEAR_NEXUS to have the
+ * command freed and returned to us.
+ */
+ if (!asd_hwi_check_cmd_pending(asd, scb, dl)) {
+ asd_log(ASD_DBG_RUNTIME, "Aborted cmd has been "
+ "completed.\n");
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_SUCCEED;
+ asd_hwi_free_edb(asd, escb, edb_index);
+ break;
+ }
+
+ redb = &edb->ssp_resp;
+ riu = &redb->resp_frame.riu;
+ if (SSP_RIU_DATAPRES(riu) == SSP_RIU_DATAPRES_RESP) {
+ uint8_t resp_code;
+
+ resp_code = ((struct resp_data_iu *)
+ &riu->data[0])->resp_code;
+
+ /* Handle the SSP TMF response code. */
+ asd_hwi_map_tmf_resp(scb, resp_code);
+
+ if (scb->eh_state == SCB_EH_CLR_NXS_REQ) {
+ asd_log(ASD_DBG_ERROR,
+ "Tag to Clear=0x%x.\n",
+ asd_be16toh(redb->tag_to_clear));
+ /*
+ * Upon receving TMF_COMPLETE, we need to send
+ * CLEAR_NEXUS SCB for tag_to_clear.
+ */
+ asd_hwi_build_clear_nexus(scb,
+ CLR_NXS_I_T_L_Q_TAG,
+ redb->tag_to_clear,
+ /*ctx*/0);
+ }
+ } else {
+ /*
+ * Response Data not available. Protocol error.
+ * Indicate that Abort failed.
+ */
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ scb->eh_status = SCB_EH_FAILED;
+ }
+
+ asd_hwi_free_edb(asd, escb, edb_index);
+ break;
+ }
+
+ case TASK_F_W_OPEN_REJECT:
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+
+ case RESUME_COMPLETE:
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+
+ case TASK_CLEARED:
+ /*
+ * Previous error recovery SCB that timed-out and
+ * was aborted.
+ * All we need to do here is just free the scb.
+ */
+ asd_hwi_free_scb(asd, scb);
+ return;
+
+ default:
+ asd_log(ASD_DBG_ERROR, "DL opcode not handled.\n");
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+ }
+
+ asd_hwi_abort_scb(asd, (struct scb *) scb->io_ctx, scb);
+}
+
+/*
+ * Function:
+ * asd_hwi_reset_lu()
+ *
+ * Description:
+ * Issue a Logical Unit Reset to the end device.
+ * LU Reset can only be done for SSP end device.
+ */
+static void
+asd_hwi_reset_lu(struct asd_softc *asd, struct scb *scb_to_reset,
+ struct scb *scb)
+{
+ ASD_LOCK_ASSERT(asd);
+
+ asd_log(ASD_DBG_ERROR, "Curr State: 0x%x Status: 0x%x.\n",
+ scb->eh_state, scb->eh_status);
+
+ switch (scb->eh_state) {
+ case SCB_EH_INITIATED:
+ {
+ struct asd_target *targ;
+ struct asd_device *dev;
+
+ targ = scb_to_reset->platform_data->targ;
+ dev = scb_to_reset->platform_data->dev;
+ if ((targ == NULL) || (dev == NULL)) {
+ /* This shouldn't happen. */
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_FAILED;
+ asd_hwi_reset_lu(asd, scb_to_reset, scb);
+ break;
+ }
+
+ /*
+ * Only SSP end device can be issued a LU reset.
+ */
+ if (targ->device_protocol_type != ASD_DEVICE_PROTOCOL_SCSI) {
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_FAILED;
+ asd_hwi_reset_lu(asd, (struct scb *) scb_to_reset, scb);
+ break;
+ }
+
+ scb->platform_data->targ = targ;
+ scb->platform_data->dev = dev;
+ scb->eh_state = SCB_EH_LU_RESET_REQ;
+ asd_hwi_reset_lu(asd, (struct scb *) scb_to_reset, scb);
+ break;
+ }
+
+ case SCB_EH_LU_RESET_REQ:
+ /* Build LUR Task Management Function. */
+ asd_hwi_build_ssp_tmf(scb, scb->platform_data->targ,
+ scb->platform_data->dev->saslun,
+ LOGICAL_UNIT_RESET_TMF);
+ scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+ asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+ asd_push_post_stack(asd, scb, (void *) scb_to_reset,
+ asd_hwi_reset_lu_done);
+ asd_hwi_post_scb(asd, scb);
+ break;
+
+ case SCB_EH_CLR_NXS_REQ:
+ asd_hwi_build_clear_nexus(scb, CLR_NXS_I_T_L,
+ (RESUME_TX | NOT_IN_Q | SEND_Q),
+ /*ctx*/0);
+ scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+ asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+ asd_push_post_stack(asd, scb, (void *) scb_to_reset,
+ asd_hwi_reset_lu_done);
+ asd_hwi_post_scb(asd, scb);
+ break;
+
+ case SCB_EH_RESUME_SENDQ:
+ /*
+ * If we failed to perform LU Reset, we need to resume
+ * the firmware send queue for the I_T_L.
+ */
+ asd_hwi_build_clear_nexus(scb, CLR_NXS_I_T_L,
+ RESUME_TX, /*ctx*/0);
+ scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+ asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+ asd_push_post_stack(asd, scb, (void *) scb_to_reset,
+ asd_hwi_reset_lu_done);
+ asd_hwi_post_scb(asd, scb);
+ break;
+
+ case SCB_EH_DONE:
+ {
+ scb_to_reset->eh_state = scb->eh_state;
+ scb_to_reset->eh_status = scb->eh_status;
+
+ if ((scb_to_reset->eh_state == SCB_EH_DONE) &&
+ (scb_to_reset->eh_status == SCB_EH_FAILED)) {
+ /*
+ * Failed to perform LU Reset or LU Reset is not
+ * supported then we shall procced with
+ * the next level of error recovery (Device Port Reset).
+ * We need to change the scb eh_state to
+ * SCB_EH_DEV_RESET_REQ.
+ */
+ scb_to_reset->eh_state = SCB_EH_DEV_RESET_REQ;
+ scb_to_reset->platform_data->targ->flags &=
+ ~ASD_TARG_IN_RECOVERY;
+ }
+ asd_hwi_free_scb(asd, scb);
+ asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+ break;
+ }
+
+ default:
+ asd_log(ASD_DBG_ERROR, "Invalid EH State 0x%x.\n",
+ scb->eh_state);
+ scb_to_reset->eh_state = SCB_EH_DONE;
+ scb_to_reset->eh_status = SCB_EH_FAILED;
+ asd_hwi_free_scb(asd, scb);
+ asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+ break;
+ }
+}
+
+static void
+asd_hwi_reset_lu_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);
+
+ /*
+ * There is a possibility that this post routine is called after
+ * the SCB timedout. So, only delete the timer if the SCB hasn't
+ * timedout.
+ */
+ if (scb->eh_state != SCB_EH_TIMEDOUT)
+ del_timer_sync(&scb->platform_data->timeout);
+
+ switch (dl->opcode) {
+ case TASK_COMP_WO_ERR:
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_SUCCEED;
+ break;
+
+ case TMF_F_W_TC_NOT_FOUND:
+ case TMF_F_W_TAG_NOT_FOUND:
+ case TMF_F_W_CONN_HNDL_NOT_FOUND:
+ case TASK_F_W_NAK_RCVD:
+ if ((scb->eh_state & SCB_EH_SUSPEND_SENDQ) != 0)
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ else
+ scb->eh_state = SCB_EH_DONE;
+
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+
+ case SSP_TASK_COMP_W_RESP:
+ {
+ union edb *edb;
+ struct scb *escb;
+ struct ssp_resp_edb *redb;
+ struct ssp_resp_iu *riu;
+ u_int edb_index;
+
+ edb = asd_hwi_get_edb_from_dl(asd, scb, dl, &escb, &edb_index);
+ if (edb == NULL) {
+ asd_log(ASD_DBG_ERROR, "Invalid EDB recv for SSP "
+ "comp w/response.\n");
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+ }
+
+ redb = &edb->ssp_resp;
+ riu = &redb->resp_frame.riu;
+ if (SSP_RIU_DATAPRES(riu) == SSP_RIU_DATAPRES_RESP) {
+ uint8_t resp_code;
+
+ resp_code = ((struct resp_data_iu *)
+ &riu->data[0])->resp_code;
+
+ /* Handle the SSP TMF response code. */
+ asd_hwi_map_tmf_resp(scb, resp_code);
+ } else {
+ /*
+ * Response Data not available. Protocol error.
+ * Indicate that LUR failed.
+ */
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ scb->eh_status = SCB_EH_FAILED;
+ }
+
+ asd_hwi_free_edb(asd, escb, edb_index);
+ break;
+ }
+
+ case TASK_F_W_OPEN_REJECT:
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+
+ case RESUME_COMPLETE:
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+
+ case TASK_CLEARED:
+ /*
+ * Previous error recovery SCB that timed-out and
+ * was aborted.
+ * All we need to do here is just free the scb.
+ */
+ asd_hwi_free_scb(asd, scb);
+ return;
+
+ default:
+ asd_log(ASD_DBG_ERROR, "DL Opcode not handled.\n");
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+ }
+
+ asd_hwi_reset_lu(asd, (struct scb *) scb->io_ctx, scb);
+}
+
+void
+asd_hwi_map_tmf_resp(struct scb *scb, u_int resp_code)
+{
+ /*
+ * Handle TMF Response upon completion of issuing
+ * Task Management Function to the target..
+ * Based on the response code, we will move to certain
+ * error recovery state.
+ */
+ switch (resp_code) {
+ case TMF_COMPLETE:
+ scb->eh_state = SCB_EH_CLR_NXS_REQ;
+ scb->eh_status = SCB_EH_SUCCEED;
+ break;
+
+ case INVALID_FRAME:
+ case TMF_FAILED:
+ case TMF_SUCCEEDED:
+ case TMF_NOT_SUPPORTED:
+ case INVALID_LUN:
+ default:
+ /* We treat all this as a failure case. */
+ scb->eh_state = SCB_EH_RESUME_SENDQ;
+ scb->eh_status = SCB_EH_FAILED;
+ break;
+ }
+}
+
+/*
+ * Function:
+ * asd_hwi_reset_device()
+ *
+ * Description:
+ * Issue a device reset to the failing device.
+ */
+static void
+asd_hwi_reset_device(struct asd_softc *asd, struct scb *scb_to_reset,
+ struct scb *scb)
+{
+ ASD_LOCK_ASSERT(asd);
+
+ asd_log(ASD_DBG_ERROR, "Curr State: 0x%x Status: 0x%x.\n",
+ scb->eh_state, scb->eh_status);
+
+ switch (scb->eh_state) {
+ case SCB_EH_INITIATED:
+ {
+ struct asd_target *targ;
+ struct asd_device *dev;
+
+ targ = scb_to_reset->platform_data->targ;
+ dev = scb_to_reset->platform_data->dev;
+ if ((targ == NULL) || (dev == NULL)) {
+ /* This shouldn't happen. */
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_FAILED;
+ asd_hwi_reset_device(asd, scb_to_reset, scb);
+ break;
+ }
+
+ /*
+ * DC: Currently we are not handling error recovery for
+ * expander.
+ * Logic for that will be added later on.
+ */
+ if ((targ->device_protocol_type != ASD_DEVICE_PROTOCOL_SCSI) &&
+ (targ->device_protocol_type != ASD_DEVICE_PROTOCOL_ATA)) {
+ scb->eh_state = SCB_EH_DONE;
+ scb->eh_status = SCB_EH_FAILED;
+ asd_hwi_reset_device(asd, scb_to_reset, scb);
+ break;
+ }
+
+ scb->platform_data->targ = targ;
+ scb->platform_data->dev = dev;
+
+ /*
+ * Saving final post routine and the scb_to_reset in
+ * the first post stack slot. We need to access the
+ * scb_to_reset during the device reset process.
+ */
+ asd_push_post_stack(asd, scb, (void *) scb_to_reset,
+ asd_hwi_reset_device_done);
+
+ if (SCB_GET_SRC_PORT(scb_to_reset)->management_type ==
+ ASD_DEVICE_END) {
+ /* We or'ed it to remember its previous state. */
+ scb->eh_state |= SCB_EH_CLR_NXS_REQ;
+ } else {
+ /*
+ * For device attached behind expander,
+ * prior to device reset, we will try to
+ * obtain the error report log of the phy
+ * that the device is connected to.
+ */
+ scb->eh_state = SCB_EH_PHY_REPORT_REQ;
+ }
+ asd_hwi_reset_device(asd, scb_to_reset, scb);
+ break;
+ }
+
+ case SCB_EH_DEV_RESET_REQ:
+ {
+ struct asd_target *targ;
+ struct asd_port *port;
+
+ targ = scb->platform_data->targ;
+ port = SCB_GET_SRC_PORT(scb);
+ /*
+ * Perform a specific device reset for device that is
+ * direct-attached or expander-attached.
+ */
+ if (port->management_type == ASD_DEVICE_END) {
+ /* Device is directly attached to the initiator. */
+ asd_hwi_reset_end_device(asd, scb);
+ } else {
+ /* Device is attached behind expander. */
+ asd_hwi_reset_exp_device(asd, scb);
+ }
+ break;
+ }
+
+ case SCB_EH_PHY_NO_OP_REQ:
+ {
+ struct asd_phy *phy;
+
+ phy = (struct asd_phy *) scb->io_ctx;
+ /*
+ * Upon completion of HARD RESET, we need to initialize
+ * OOB registers to enable hot-plug timer.
+ * PHY NO OP currently only applied for direct-attached
+ * device.
+ */
+ asd_hwi_build_control_phy(scb, phy, PHY_NO_OP);
+ scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+ asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+ asd_push_post_stack(asd, scb, scb->io_ctx,
+ asd_hwi_reset_end_device_done);
+ asd_hwi_post_scb(asd, scb);
+ break;
+ }
+
+ case SCB_EH_PHY_REPORT_REQ:
+ /*
+ * Obtain the report error log of the phy that the failing
+ * device is connected to.
+ * Ideally, we want to traverse the route and obtain the
+ * error report log of each phy that has pathway to the
+ * device.
+ */
+ asd_hwi_report_phy_err_log(asd, scb);
+ break;
+
+ case SCB_EH_CLR_NXS_REQ:
+ case (SCB_EH_CLR_NXS_REQ|SCB_EH_INITIATED):
+ case (SCB_EH_CLR_NXS_REQ|SCB_EH_PHY_REPORT_REQ):
+ /*
+ * Prior to performing Link Reset or Hard Reset to the
+ * target, we need to clear the firmware's execution queue
+ * and suspend the data transimission to the target.
+ */
+ if (((scb->eh_state & SCB_EH_INITIATED) != 0) ||
+ ((scb->eh_state & SCB_EH_PHY_REPORT_REQ) != 0)) {
+ asd_hwi_build_clear_nexus(scb, CLR_NXS_IT_OR_TI,
+ (SUSPEND_TX | EXEC_Q),
+ /*ctx*/0);
+ asd_push_post_stack(asd, scb, (void *) scb_to_reset,
+ asd_hwi_reset_device_done);
+ } else {
+ /*
+ * Upon completion of Device Reset, we need to issue
+ * CLEAR NEXUS to the firmware to free up the SCB
+ * resume data transmission.
+ * Also, we will be using the first post stack
+ * we save at the beginning.
+ */
+ asd_hwi_build_clear_nexus(scb, CLR_NXS_IT_OR_TI,
+ (RESUME_TX|SEND_Q|NOT_IN_Q),
+ /*ctx*/0);
+ }
+ scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+ asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+ asd_hwi_post_scb(asd, scb);
+ break;
+
+ case SCB_EH_DONE:
+ scb_to_reset->eh_state = scb->eh_state;
+ scb_to_reset->eh_status = scb->eh_status;
+
+ if ((scb_to_reset->eh_state == SCB_EH_DONE) &&
+ (scb_to_reset->eh_status == SCB_EH_FAILED)) {
+ /*
+ * Failed to perform DEVICE RESET,
+ * we shall procced with the next level of
+ * error recovery (Port Reset).
+ * We need to change the scb eh_state to
+ * SCB_EH_PORT_RESET_REQ.
+ */
+ scb_to_reset->eh_state = SCB_EH_PORT_RESET_REQ;
+ scb_to_reset->platform_data->targ->flags &=
+ ~ASD_TARG_IN_RECOVERY;
+ }
+ asd_hwi_free_scb(asd, scb);
+ asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+ break;
+
+ default:
+ asd_log(ASD_DBG_ERROR, "Invalid EH State 0x%x.\n",
+ scb->eh_state);
+
+ scb_to_reset->eh_state = SCB_EH_DONE;
+ scb_to_reset->eh_status = SCB_EH_FAILED;
+ asd_hwi_free_scb(asd, scb);
+ asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+ break;
+ }
+}
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2005-02-17 17:36 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-02-17 17:36 [ANNOUNCE] Adaptec SAS/SATA device driver [08/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.