diff options
Diffstat (limited to 'drivers/scsi/qla2xxx/qla_mr.c')
-rw-r--r-- | drivers/scsi/qla2xxx/qla_mr.c | 3476 |
1 files changed, 3476 insertions, 0 deletions
diff --git a/drivers/scsi/qla2xxx/qla_mr.c b/drivers/scsi/qla2xxx/qla_mr.c new file mode 100644 index 000000000000..729b74389f83 --- /dev/null +++ b/drivers/scsi/qla2xxx/qla_mr.c @@ -0,0 +1,3476 @@ +/* + * QLogic Fibre Channel HBA Driver + * Copyright (c) 2003-2013 QLogic Corporation + * + * See LICENSE.qla2xxx for copyright and licensing details. + */ +#include "qla_def.h" +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/ratelimit.h> +#include <linux/vmalloc.h> +#include <scsi/scsi_tcq.h> +#include <linux/utsname.h> + + +/* QLAFX00 specific Mailbox implementation functions */ + +/* + * qlafx00_mailbox_command + * Issue mailbox command and waits for completion. + * + * Input: + * ha = adapter block pointer. + * mcp = driver internal mbx struct pointer. + * + * Output: + * mb[MAX_MAILBOX_REGISTER_COUNT] = returned mailbox data. + * + * Returns: + * 0 : QLA_SUCCESS = cmd performed success + * 1 : QLA_FUNCTION_FAILED (error encountered) + * 6 : QLA_FUNCTION_TIMEOUT (timeout condition encountered) + * + * Context: + * Kernel context. + */ +static int +qlafx00_mailbox_command(scsi_qla_host_t *vha, struct mbx_cmd_32 *mcp) + +{ + int rval; + unsigned long flags = 0; + device_reg_t __iomem *reg; + uint8_t abort_active; + uint8_t io_lock_on; + uint16_t command = 0; + uint32_t *iptr; + uint32_t __iomem *optr; + uint32_t cnt; + uint32_t mboxes; + unsigned long wait_time; + struct qla_hw_data *ha = vha->hw; + scsi_qla_host_t *base_vha = pci_get_drvdata(ha->pdev); + + if (ha->pdev->error_state > pci_channel_io_frozen) { + ql_log(ql_log_warn, vha, 0x115c, + "error_state is greater than pci_channel_io_frozen, " + "exiting.\n"); + return QLA_FUNCTION_TIMEOUT; + } + + if (vha->device_flags & DFLG_DEV_FAILED) { + ql_log(ql_log_warn, vha, 0x115f, + "Device in failed state, exiting.\n"); + return QLA_FUNCTION_TIMEOUT; + } + + reg = ha->iobase; + io_lock_on = base_vha->flags.init_done; + + rval = QLA_SUCCESS; + abort_active = test_bit(ABORT_ISP_ACTIVE, &base_vha->dpc_flags); + + if (ha->flags.pci_channel_io_perm_failure) { + ql_log(ql_log_warn, vha, 0x1175, + "Perm failure on EEH timeout MBX, exiting.\n"); + return QLA_FUNCTION_TIMEOUT; + } + + if (ha->flags.isp82xx_fw_hung) { + /* Setting Link-Down error */ + mcp->mb[0] = MBS_LINK_DOWN_ERROR; + ql_log(ql_log_warn, vha, 0x1176, + "FW hung = %d.\n", ha->flags.isp82xx_fw_hung); + rval = QLA_FUNCTION_FAILED; + goto premature_exit; + } + + /* + * Wait for active mailbox commands to finish by waiting at most tov + * seconds. This is to serialize actual issuing of mailbox cmds during + * non ISP abort time. + */ + if (!wait_for_completion_timeout(&ha->mbx_cmd_comp, mcp->tov * HZ)) { + /* Timeout occurred. Return error. */ + ql_log(ql_log_warn, vha, 0x1177, + "Cmd access timeout, cmd=0x%x, Exiting.\n", + mcp->mb[0]); + return QLA_FUNCTION_TIMEOUT; + } + + ha->flags.mbox_busy = 1; + /* Save mailbox command for debug */ + ha->mcp32 = mcp; + + ql_dbg(ql_dbg_mbx, vha, 0x1178, + "Prepare to issue mbox cmd=0x%x.\n", mcp->mb[0]); + + spin_lock_irqsave(&ha->hardware_lock, flags); + + /* Load mailbox registers. */ + optr = (uint32_t __iomem *)®->ispfx00.mailbox0; + + iptr = mcp->mb; + command = mcp->mb[0]; + mboxes = mcp->out_mb; + + for (cnt = 0; cnt < ha->mbx_count; cnt++) { + if (mboxes & BIT_0) + WRT_REG_DWORD(optr, *iptr); + + mboxes >>= 1; + optr++; + iptr++; + } + + /* Issue set host interrupt command to send cmd out. */ + ha->flags.mbox_int = 0; + clear_bit(MBX_INTERRUPT, &ha->mbx_cmd_flags); + + ql_dump_buffer(ql_dbg_mbx + ql_dbg_buffer, vha, 0x1172, + (uint8_t *)mcp->mb, 16); + ql_dump_buffer(ql_dbg_mbx + ql_dbg_buffer, vha, 0x1173, + ((uint8_t *)mcp->mb + 0x10), 16); + ql_dump_buffer(ql_dbg_mbx + ql_dbg_buffer, vha, 0x1174, + ((uint8_t *)mcp->mb + 0x20), 8); + + /* Unlock mbx registers and wait for interrupt */ + ql_dbg(ql_dbg_mbx, vha, 0x1179, + "Going to unlock irq & waiting for interrupts. " + "jiffies=%lx.\n", jiffies); + + /* Wait for mbx cmd completion until timeout */ + if ((!abort_active && io_lock_on) || IS_NOPOLLING_TYPE(ha)) { + set_bit(MBX_INTR_WAIT, &ha->mbx_cmd_flags); + + QLAFX00_SET_HST_INTR(ha, ha->mbx_intr_code); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + wait_for_completion_timeout(&ha->mbx_intr_comp, mcp->tov * HZ); + + clear_bit(MBX_INTR_WAIT, &ha->mbx_cmd_flags); + + } else { + ql_dbg(ql_dbg_mbx, vha, 0x112c, + "Cmd=%x Polling Mode.\n", command); + + QLAFX00_SET_HST_INTR(ha, ha->mbx_intr_code); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + wait_time = jiffies + mcp->tov * HZ; /* wait at most tov secs */ + while (!ha->flags.mbox_int) { + if (time_after(jiffies, wait_time)) + break; + + /* Check for pending interrupts. */ + qla2x00_poll(ha->rsp_q_map[0]); + + if (!ha->flags.mbox_int && + !(IS_QLA2200(ha) && + command == MBC_LOAD_RISC_RAM_EXTENDED)) + usleep_range(10000, 11000); + } /* while */ + ql_dbg(ql_dbg_mbx, vha, 0x112d, + "Waited %d sec.\n", + (uint)((jiffies - (wait_time - (mcp->tov * HZ)))/HZ)); + } + + /* Check whether we timed out */ + if (ha->flags.mbox_int) { + uint32_t *iptr2; + + ql_dbg(ql_dbg_mbx, vha, 0x112e, + "Cmd=%x completed.\n", command); + + /* Got interrupt. Clear the flag. */ + ha->flags.mbox_int = 0; + clear_bit(MBX_INTERRUPT, &ha->mbx_cmd_flags); + + if (ha->mailbox_out32[0] != MBS_COMMAND_COMPLETE) + rval = QLA_FUNCTION_FAILED; + + /* Load return mailbox registers. */ + iptr2 = mcp->mb; + iptr = (uint32_t *)&ha->mailbox_out32[0]; + mboxes = mcp->in_mb; + for (cnt = 0; cnt < ha->mbx_count; cnt++) { + if (mboxes & BIT_0) + *iptr2 = *iptr; + + mboxes >>= 1; + iptr2++; + iptr++; + } + } else { + + rval = QLA_FUNCTION_TIMEOUT; + } + + ha->flags.mbox_busy = 0; + + /* Clean up */ + ha->mcp32 = NULL; + + if ((abort_active || !io_lock_on) && !IS_NOPOLLING_TYPE(ha)) { + ql_dbg(ql_dbg_mbx, vha, 0x113a, + "checking for additional resp interrupt.\n"); + + /* polling mode for non isp_abort commands. */ + qla2x00_poll(ha->rsp_q_map[0]); + } + + if (rval == QLA_FUNCTION_TIMEOUT && + mcp->mb[0] != MBC_GEN_SYSTEM_ERROR) { + if (!io_lock_on || (mcp->flags & IOCTL_CMD) || + ha->flags.eeh_busy) { + /* not in dpc. schedule it for dpc to take over. */ + ql_dbg(ql_dbg_mbx, vha, 0x115d, + "Timeout, schedule isp_abort_needed.\n"); + + if (!test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags) && + !test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags) && + !test_bit(ISP_ABORT_RETRY, &vha->dpc_flags)) { + + ql_log(ql_log_info, base_vha, 0x115e, + "Mailbox cmd timeout occurred, cmd=0x%x, " + "mb[0]=0x%x, eeh_busy=0x%x. Scheduling ISP " + "abort.\n", command, mcp->mb[0], + ha->flags.eeh_busy); + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + } + } else if (!abort_active) { + /* call abort directly since we are in the DPC thread */ + ql_dbg(ql_dbg_mbx, vha, 0x1160, + "Timeout, calling abort_isp.\n"); + + if (!test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags) && + !test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags) && + !test_bit(ISP_ABORT_RETRY, &vha->dpc_flags)) { + + ql_log(ql_log_info, base_vha, 0x1161, + "Mailbox cmd timeout occurred, cmd=0x%x, " + "mb[0]=0x%x. Scheduling ISP abort ", + command, mcp->mb[0]); + + set_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags); + clear_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + if (ha->isp_ops->abort_isp(vha)) { + /* Failed. retry later. */ + set_bit(ISP_ABORT_NEEDED, + &vha->dpc_flags); + } + clear_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags); + ql_dbg(ql_dbg_mbx, vha, 0x1162, + "Finished abort_isp.\n"); + } + } + } + +premature_exit: + /* Allow next mbx cmd to come in. */ + complete(&ha->mbx_cmd_comp); + + if (rval) { + ql_log(ql_log_warn, base_vha, 0x1163, + "**** Failed mbx[0]=%x, mb[1]=%x, mb[2]=%x, " + "mb[3]=%x, cmd=%x ****.\n", + mcp->mb[0], mcp->mb[1], mcp->mb[2], mcp->mb[3], command); + } else { + ql_dbg(ql_dbg_mbx, base_vha, 0x1164, "Done %s.\n", __func__); + } + + return rval; +} + +/* + * qlafx00_driver_shutdown + * Indicate a driver shutdown to firmware. + * + * Input: + * ha = adapter block pointer. + * + * Returns: + * local function return status code. + * + * Context: + * Kernel context. + */ +static int +qlafx00_driver_shutdown(scsi_qla_host_t *vha, int tmo) +{ + int rval; + struct mbx_cmd_32 mc; + struct mbx_cmd_32 *mcp = &mc; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1166, + "Entered %s.\n", __func__); + + mcp->mb[0] = MBC_MR_DRV_SHUTDOWN; + mcp->out_mb = MBX_0; + mcp->in_mb = MBX_0; + if (tmo) + mcp->tov = tmo; + else + mcp->tov = MBX_TOV_SECONDS; + mcp->flags = 0; + rval = qlafx00_mailbox_command(vha, mcp); + + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x1167, + "Failed=%x.\n", rval); + } else { + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1168, + "Done %s.\n", __func__); + } + + return rval; +} + +/* + * qlafx00_get_firmware_state + * Get adapter firmware state. + * + * Input: + * ha = adapter block pointer. + * TARGET_QUEUE_LOCK must be released. + * ADAPTER_STATE_LOCK must be released. + * + * Returns: + * qla7xxx local function return status code. + * + * Context: + * Kernel context. + */ +static int +qlafx00_get_firmware_state(scsi_qla_host_t *vha, uint32_t *states) +{ + int rval; + struct mbx_cmd_32 mc; + struct mbx_cmd_32 *mcp = &mc; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1169, + "Entered %s.\n", __func__); + + mcp->mb[0] = MBC_GET_FIRMWARE_STATE; + mcp->out_mb = MBX_0; + mcp->in_mb = MBX_1|MBX_0; + mcp->tov = MBX_TOV_SECONDS; + mcp->flags = 0; + rval = qlafx00_mailbox_command(vha, mcp); + + /* Return firmware states. */ + states[0] = mcp->mb[1]; + + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x116a, + "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); + } else { + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x116b, + "Done %s.\n", __func__); + } + return rval; +} + +/* + * qlafx00_init_firmware + * Initialize adapter firmware. + * + * Input: + * ha = adapter block pointer. + * dptr = Initialization control block pointer. + * size = size of initialization control block. + * TARGET_QUEUE_LOCK must be released. + * ADAPTER_STATE_LOCK must be released. + * + * Returns: + * qlafx00 local function return status code. + * + * Context: + * Kernel context. + */ +int +qlafx00_init_firmware(scsi_qla_host_t *vha, uint16_t size) +{ + int rval; + struct mbx_cmd_32 mc; + struct mbx_cmd_32 *mcp = &mc; + struct qla_hw_data *ha = vha->hw; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x116c, + "Entered %s.\n", __func__); + + mcp->mb[0] = MBC_INITIALIZE_FIRMWARE; + + mcp->mb[1] = 0; + mcp->mb[2] = MSD(ha->init_cb_dma); + mcp->mb[3] = LSD(ha->init_cb_dma); + + mcp->out_mb = MBX_3|MBX_2|MBX_1|MBX_0; + mcp->in_mb = MBX_0; + mcp->buf_size = size; + mcp->flags = MBX_DMA_OUT; + mcp->tov = MBX_TOV_SECONDS; + rval = qlafx00_mailbox_command(vha, mcp); + + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x116d, + "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); + } else { + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x116e, + "Done %s.\n", __func__); + } + return rval; +} + +/* + * qlafx00_mbx_reg_test + */ +static int +qlafx00_mbx_reg_test(scsi_qla_host_t *vha) +{ + int rval; + struct mbx_cmd_32 mc; + struct mbx_cmd_32 *mcp = &mc; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x116f, + "Entered %s.\n", __func__); + + + mcp->mb[0] = MBC_MAILBOX_REGISTER_TEST; + mcp->mb[1] = 0xAAAA; + mcp->mb[2] = 0x5555; + mcp->mb[3] = 0xAA55; + mcp->mb[4] = 0x55AA; + mcp->mb[5] = 0xA5A5; + mcp->mb[6] = 0x5A5A; + mcp->mb[7] = 0x2525; + mcp->mb[8] = 0xBBBB; + mcp->mb[9] = 0x6666; + mcp->mb[10] = 0xBB66; + mcp->mb[11] = 0x66BB; + mcp->mb[12] = 0xB6B6; + mcp->mb[13] = 0x6B6B; + mcp->mb[14] = 0x3636; + mcp->mb[15] = 0xCCCC; + + + mcp->out_mb = MBX_15|MBX_14|MBX_13|MBX_12|MBX_11|MBX_10|MBX_9|MBX_8| + MBX_7|MBX_6|MBX_5|MBX_4|MBX_3|MBX_2|MBX_1|MBX_0; + mcp->in_mb = MBX_15|MBX_14|MBX_13|MBX_12|MBX_11|MBX_10|MBX_9|MBX_8| + MBX_7|MBX_6|MBX_5|MBX_4|MBX_3|MBX_2|MBX_1|MBX_0; + mcp->buf_size = 0; + mcp->flags = MBX_DMA_OUT; + mcp->tov = MBX_TOV_SECONDS; + rval = qlafx00_mailbox_command(vha, mcp); + if (rval == QLA_SUCCESS) { + if (mcp->mb[17] != 0xAAAA || mcp->mb[18] != 0x5555 || + mcp->mb[19] != 0xAA55 || mcp->mb[20] != 0x55AA) + rval = QLA_FUNCTION_FAILED; + if (mcp->mb[21] != 0xA5A5 || mcp->mb[22] != 0x5A5A || + mcp->mb[23] != 0x2525 || mcp->mb[24] != 0xBBBB) + rval = QLA_FUNCTION_FAILED; + if (mcp->mb[25] != 0x6666 || mcp->mb[26] != 0xBB66 || + mcp->mb[27] != 0x66BB || mcp->mb[28] != 0xB6B6) + rval = QLA_FUNCTION_FAILED; + if (mcp->mb[29] != 0x6B6B || mcp->mb[30] != 0x3636 || + mcp->mb[31] != 0xCCCC) + rval = QLA_FUNCTION_FAILED; + } + + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x1170, + "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); + } else { + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1171, + "Done %s.\n", __func__); + } + return rval; +} + +/** + * qlafx00_pci_config() - Setup ISPFx00 PCI configuration registers. + * @ha: HA context + * + * Returns 0 on success. + */ +int +qlafx00_pci_config(scsi_qla_host_t *vha) +{ + uint16_t w; + struct qla_hw_data *ha = vha->hw; + + pci_set_master(ha->pdev); + pci_try_set_mwi(ha->pdev); + + pci_read_config_word(ha->pdev, PCI_COMMAND, &w); + w |= (PCI_COMMAND_PARITY | PCI_COMMAND_SERR); + w &= ~PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(ha->pdev, PCI_COMMAND, w); + + /* PCIe -- adjust Maximum Read Request Size (2048). */ + if (pci_find_capability(ha->pdev, PCI_CAP_ID_EXP)) + pcie_set_readrq(ha->pdev, 2048); + + ha->chip_revision = ha->pdev->revision; + + return QLA_SUCCESS; +} + +/** + * qlafx00_warm_reset() - Perform warm reset of iSA(CPUs being reset on SOC). + * @ha: HA context + * + */ +static inline void +qlafx00_soc_cpu_reset(scsi_qla_host_t *vha) +{ + unsigned long flags = 0; + struct qla_hw_data *ha = vha->hw; + int i, core; + uint32_t cnt; + + /* Set all 4 cores in reset */ + for (i = 0; i < 4; i++) { + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_SW_RST_CONTROL_REG_CORE0 + 8*i), (0xF01)); + } + + /* Set all 4 core Clock gating control */ + for (i = 0; i < 4; i++) { + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_SW_RST_CONTROL_REG_CORE0 + 4 + 8*i), (0x01010101)); + } + + /* Reset all units in Fabric */ + QLAFX00_SET_HBA_SOC_REG(ha, SOC_FABRIC_RST_CONTROL_REG, (0x11F0101)); + + /* Reset all interrupt control registers */ + for (i = 0; i < 115; i++) { + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_INTERRUPT_SOURCE_I_CONTROL_REG + 4*i), (0x0)); + } + + /* Reset Timers control registers. per core */ + for (core = 0; core < 4; core++) + for (i = 0; i < 8; i++) + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_CORE_TIMER_REG + 0x100*core + 4*i), (0x0)); + + /* Reset per core IRQ ack register */ + for (core = 0; core < 4; core++) + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_IRQ_ACK_REG + 0x100*core), (0x3FF)); + + /* Set Fabric control and config to defaults */ + QLAFX00_SET_HBA_SOC_REG(ha, SOC_FABRIC_CONTROL_REG, (0x2)); + QLAFX00_SET_HBA_SOC_REG(ha, SOC_FABRIC_CONFIG_REG, (0x3)); + + spin_lock_irqsave(&ha->hardware_lock, flags); + + /* Kick in Fabric units */ + QLAFX00_SET_HBA_SOC_REG(ha, SOC_FABRIC_RST_CONTROL_REG, (0x0)); + + /* Kick in Core0 to start boot process */ + QLAFX00_SET_HBA_SOC_REG(ha, SOC_SW_RST_CONTROL_REG_CORE0, (0xF00)); + + /* Wait 10secs for soft-reset to complete. */ + for (cnt = 10; cnt; cnt--) { + msleep(1000); + barrier(); + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +/** + * qlafx00_soft_reset() - Soft Reset ISPFx00. + * @ha: HA context + * + * Returns 0 on success. + */ +void +qlafx00_soft_reset(scsi_qla_host_t *vha) +{ + struct qla_hw_data *ha = vha->hw; + + if (unlikely(pci_channel_offline(ha->pdev) && + ha->flags.pci_channel_io_perm_failure)) + return; + + ha->isp_ops->disable_intrs(ha); + qlafx00_soc_cpu_reset(vha); + ha->isp_ops->enable_intrs(ha); +} + +/** + * qlafx00_chip_diag() - Test ISPFx00 for proper operation. + * @ha: HA context + * + * Returns 0 on success. + */ +int +qlafx00_chip_diag(scsi_qla_host_t *vha) +{ + int rval = 0; + struct qla_hw_data *ha = vha->hw; + struct req_que *req = ha->req_q_map[0]; + + ha->fw_transfer_size = REQUEST_ENTRY_SIZE * req->length; + + rval = qlafx00_mbx_reg_test(vha); + if (rval) { + ql_log(ql_log_warn, vha, 0x1165, + "Failed mailbox send register test\n"); + } else { + /* Flag a successful rval */ + rval = QLA_SUCCESS; + } + return rval; +} + +void +qlafx00_config_rings(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct device_reg_fx00 __iomem *reg = &ha->iobase->ispfx00; + struct init_cb_fx *icb; + struct req_que *req = ha->req_q_map[0]; + struct rsp_que *rsp = ha->rsp_q_map[0]; + + /* Setup ring parameters in initialization control block. */ + icb = (struct init_cb_fx *)ha->init_cb; + icb->request_q_outpointer = __constant_cpu_to_le16(0); + icb->response_q_inpointer = __constant_cpu_to_le16(0); + icb->request_q_length = cpu_to_le16(req->length); + icb->response_q_length = cpu_to_le16(rsp->length); + icb->request_q_address[0] = cpu_to_le32(LSD(req->dma)); + icb->request_q_address[1] = cpu_to_le32(MSD(req->dma)); + icb->response_q_address[0] = cpu_to_le32(LSD(rsp->dma)); + icb->response_q_address[1] = cpu_to_le32(MSD(rsp->dma)); + + WRT_REG_DWORD(®->req_q_in, 0); + WRT_REG_DWORD(®->req_q_out, 0); + + WRT_REG_DWORD(®->rsp_q_in, 0); + WRT_REG_DWORD(®->rsp_q_out, 0); + + /* PCI posting */ + RD_REG_DWORD(®->rsp_q_out); +} + +char * +qlafx00_pci_info_str(struct scsi_qla_host *vha, char *str) +{ + struct qla_hw_data *ha = vha->hw; + int pcie_reg; + + pcie_reg = pci_find_capability(ha->pdev, PCI_CAP_ID_EXP); + if (pcie_reg) { + strcpy(str, "PCIe iSA"); + return str; + } + return str; +} + +char * +qlafx00_fw_version_str(struct scsi_qla_host *vha, char *str) +{ + struct qla_hw_data *ha = vha->hw; + + sprintf(str, "%s", ha->mr.fw_version); + return str; +} + +void +qlafx00_enable_intrs(struct qla_hw_data *ha) +{ + unsigned long flags = 0; + + spin_lock_irqsave(&ha->hardware_lock, flags); + ha->interrupts_on = 1; + QLAFX00_ENABLE_ICNTRL_REG(ha); + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +void +qlafx00_disable_intrs(struct qla_hw_data *ha) +{ + unsigned long flags = 0; + + spin_lock_irqsave(&ha->hardware_lock, flags); + ha->interrupts_on = 0; + QLAFX00_DISABLE_ICNTRL_REG(ha); + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +static void +qlafx00_tmf_iocb_timeout(void *data) +{ + srb_t *sp = (srb_t *)data; + struct srb_iocb *tmf = &sp->u.iocb_cmd; + + tmf->u.tmf.comp_status = CS_TIMEOUT; + complete(&tmf->u.tmf.comp); +} + +static void +qlafx00_tmf_sp_done(void *data, void *ptr, int res) +{ + srb_t *sp = (srb_t *)ptr; + struct srb_iocb *tmf = &sp->u.iocb_cmd; + + complete(&tmf->u.tmf.comp); +} + +static int +qlafx00_async_tm_cmd(fc_port_t *fcport, uint32_t flags, + uint32_t lun, uint32_t tag) +{ + scsi_qla_host_t *vha = fcport->vha; + struct srb_iocb *tm_iocb; + srb_t *sp; + int rval = QLA_FUNCTION_FAILED; + + sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL); + if (!sp) + goto done; + + tm_iocb = &sp->u.iocb_cmd; + sp->type = SRB_TM_CMD; + sp->name = "tmf"; + qla2x00_init_timer(sp, qla2x00_get_async_timeout(vha)); + tm_iocb->u.tmf.flags = flags; + tm_iocb->u.tmf.lun = lun; + tm_iocb->u.tmf.data = tag; + sp->done = qlafx00_tmf_sp_done; + tm_iocb->timeout = qlafx00_tmf_iocb_timeout; + init_completion(&tm_iocb->u.tmf.comp); + + rval = qla2x00_start_sp(sp); + if (rval != QLA_SUCCESS) + goto done_free_sp; + + ql_dbg(ql_dbg_async, vha, 0x507b, + "Task management command issued target_id=%x\n", + fcport->tgt_id); + + wait_for_completion(&tm_iocb->u.tmf.comp); + + rval = tm_iocb->u.tmf.comp_status == CS_COMPLETE ? + QLA_SUCCESS : QLA_FUNCTION_FAILED; + +done_free_sp: + sp->free(vha, sp); +done: + return rval; +} + +int +qlafx00_abort_target(fc_port_t *fcport, unsigned int l, int tag) +{ + return qlafx00_async_tm_cmd(fcport, TCF_TARGET_RESET, l, tag); +} + +int +qlafx00_lun_reset(fc_port_t *fcport, unsigned int l, int tag) +{ + return qlafx00_async_tm_cmd(fcport, TCF_LUN_RESET, l, tag); +} + +int +qlafx00_iospace_config(struct qla_hw_data *ha) +{ + if (pci_request_selected_regions(ha->pdev, ha->bars, + QLA2XXX_DRIVER_NAME)) { + ql_log_pci(ql_log_fatal, ha->pdev, 0x014e, + "Failed to reserve PIO/MMIO regions (%s), aborting.\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + + /* Use MMIO operations for all accesses. */ + if (!(pci_resource_flags(ha->pdev, 0) & IORESOURCE_MEM)) { + ql_log_pci(ql_log_warn, ha->pdev, 0x014f, + "Invalid pci I/O region size (%s).\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + if (pci_resource_len(ha->pdev, 0) < BAR0_LEN_FX00) { + ql_log_pci(ql_log_warn, ha->pdev, 0x0127, + "Invalid PCI mem BAR0 region size (%s), aborting\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + + ha->cregbase = + ioremap_nocache(pci_resource_start(ha->pdev, 0), BAR0_LEN_FX00); + if (!ha->cregbase) { + ql_log_pci(ql_log_fatal, ha->pdev, 0x0128, + "cannot remap MMIO (%s), aborting\n", pci_name(ha->pdev)); + goto iospace_error_exit; + } + + if (!(pci_resource_flags(ha->pdev, 2) & IORESOURCE_MEM)) { + ql_log_pci(ql_log_warn, ha->pdev, 0x0129, + "region #2 not an MMIO resource (%s), aborting\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + if (pci_resource_len(ha->pdev, 2) < BAR2_LEN_FX00) { + ql_log_pci(ql_log_warn, ha->pdev, 0x012a, + "Invalid PCI mem BAR2 region size (%s), aborting\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + + ha->iobase = + ioremap_nocache(pci_resource_start(ha->pdev, 2), BAR2_LEN_FX00); + if (!ha->iobase) { + ql_log_pci(ql_log_fatal, ha->pdev, 0x012b, + "cannot remap MMIO (%s), aborting\n", pci_name(ha->pdev)); + goto iospace_error_exit; + } + + /* Determine queue resources */ + ha->max_req_queues = ha->max_rsp_queues = 1; + + ql_log_pci(ql_log_info, ha->pdev, 0x012c, + "Bars 0x%x, iobase0 0x%p, iobase2 0x%p\n", + ha->bars, ha->cregbase, ha->iobase); + + return 0; + +iospace_error_exit: + return -ENOMEM; +} + +static void +qlafx00_save_queue_ptrs(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct req_que *req = ha->req_q_map[0]; + struct rsp_que *rsp = ha->rsp_q_map[0]; + + req->length_fx00 = req->length; + req->ring_fx00 = req->ring; + req->dma_fx00 = req->dma; + + rsp->length_fx00 = rsp->length; + rsp->ring_fx00 = rsp->ring; + rsp->dma_fx00 = rsp->dma; + + ql_dbg(ql_dbg_init, vha, 0x012d, + "req: %p, ring_fx00: %p, length_fx00: 0x%x," + "req->dma_fx00: 0x%llx\n", req, req->ring_fx00, + req->length_fx00, (u64)req->dma_fx00); + + ql_dbg(ql_dbg_init, vha, 0x012e, + "rsp: %p, ring_fx00: %p, length_fx00: 0x%x," + "rsp->dma_fx00: 0x%llx\n", rsp, rsp->ring_fx00, + rsp->length_fx00, (u64)rsp->dma_fx00); +} + +static int +qlafx00_config_queues(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct req_que *req = ha->req_q_map[0]; + struct rsp_que *rsp = ha->rsp_q_map[0]; + dma_addr_t bar2_hdl = pci_resource_start(ha->pdev, 2); + + req->length = ha->req_que_len; + req->ring = (void *)ha->iobase + ha->req_que_off; + req->dma = bar2_hdl + ha->req_que_off; + if ((!req->ring) || (req->length == 0)) { + ql_log_pci(ql_log_info, ha->pdev, 0x012f, + "Unable to allocate memory for req_ring\n"); + return QLA_FUNCTION_FAILED; + } + + ql_dbg(ql_dbg_init, vha, 0x0130, + "req: %p req_ring pointer %p req len 0x%x " + "req off 0x%x\n, req->dma: 0x%llx", + req, req->ring, req->length, + ha->req_que_off, (u64)req->dma); + + rsp->length = ha->rsp_que_len; + rsp->ring = (void *)ha->iobase + ha->rsp_que_off; + rsp->dma = bar2_hdl + ha->rsp_que_off; + if ((!rsp->ring) || (rsp->length == 0)) { + ql_log_pci(ql_log_info, ha->pdev, 0x0131, + "Unable to allocate memory for rsp_ring\n"); + return QLA_FUNCTION_FAILED; + } + + ql_dbg(ql_dbg_init, vha, 0x0132, + "rsp: %p rsp_ring pointer %p rsp len 0x%x " + "rsp off 0x%x, rsp->dma: 0x%llx\n", + rsp, rsp->ring, rsp->length, + ha->rsp_que_off, (u64)rsp->dma); + + return QLA_SUCCESS; +} + +static int +qlafx00_init_fw_ready(scsi_qla_host_t *vha) +{ + int rval = 0; + unsigned long wtime; + uint16_t wait_time; /* Wait time */ + struct qla_hw_data *ha = vha->hw; + struct device_reg_fx00 __iomem *reg = &ha->iobase->ispfx00; + uint32_t aenmbx, aenmbx7 = 0; + uint32_t state[5]; + bool done = false; + + /* 30 seconds wait - Adjust if required */ + wait_time = 30; + + /* wait time before firmware ready */ + wtime = jiffies + (wait_time * HZ); + do { + aenmbx = RD_REG_DWORD(®->aenmailbox0); + barrier(); + ql_dbg(ql_dbg_mbx, vha, 0x0133, + "aenmbx: 0x%x\n", aenmbx); + + switch (aenmbx) { + case MBA_FW_NOT_STARTED: + case MBA_FW_STARTING: + break; + + case MBA_SYSTEM_ERR: + case MBA_REQ_TRANSFER_ERR: + case MBA_RSP_TRANSFER_ERR: + case MBA_FW_INIT_FAILURE: + qlafx00_soft_reset(vha); + break; + + case MBA_FW_RESTART_CMPLT: + /* Set the mbx and rqstq intr code */ + aenmbx7 = RD_REG_DWORD(®->aenmailbox7); + ha->mbx_intr_code = MSW(aenmbx7); + ha->rqstq_intr_code = LSW(aenmbx7); + ha->req_que_off = RD_REG_DWORD(®->aenmailbox1); + ha->rsp_que_off = RD_REG_DWORD(®->aenmailbox3); + ha->req_que_len = RD_REG_DWORD(®->aenmailbox5); + ha->rsp_que_len = RD_REG_DWORD(®->aenmailbox6); + WRT_REG_DWORD(®->aenmailbox0, 0); + RD_REG_DWORD_RELAXED(®->aenmailbox0); + ql_dbg(ql_dbg_init, vha, 0x0134, + "f/w returned mbx_intr_code: 0x%x, " + "rqstq_intr_code: 0x%x\n", + ha->mbx_intr_code, ha->rqstq_intr_code); + QLAFX00_CLR_INTR_REG(ha, QLAFX00_HST_INT_STS_BITS); + rval = QLA_SUCCESS; + done = true; + break; + + default: + /* If fw is apparently not ready. In order to continue, + * we might need to issue Mbox cmd, but the problem is + * that the DoorBell vector values that come with the + * 8060 AEN are most likely gone by now (and thus no + * bell would be rung on the fw side when mbox cmd is + * issued). We have to therefore grab the 8060 AEN + * shadow regs (filled in by FW when the last 8060 + * AEN was being posted). + * Do the following to determine what is needed in + * order to get the FW ready: + * 1. reload the 8060 AEN values from the shadow regs + * 2. clear int status to get rid of possible pending + * interrupts + * 3. issue Get FW State Mbox cmd to determine fw state + * Set the mbx and rqstq intr code from Shadow Regs + */ + aenmbx7 = RD_REG_DWORD(®->initval7); + ha->mbx_intr_code = MSW(aenmbx7); + ha->rqstq_intr_code = LSW(aenmbx7); + ha->req_que_off = RD_REG_DWORD(®->initval1); + ha->rsp_que_off = RD_REG_DWORD(®->initval3); + ha->req_que_len = RD_REG_DWORD(®->initval5); + ha->rsp_que_len = RD_REG_DWORD(®->initval6); + ql_dbg(ql_dbg_init, vha, 0x0135, + "f/w returned mbx_intr_code: 0x%x, " + "rqstq_intr_code: 0x%x\n", + ha->mbx_intr_code, ha->rqstq_intr_code); + QLAFX00_CLR_INTR_REG(ha, QLAFX00_HST_INT_STS_BITS); + + /* Get the FW state */ + rval = qlafx00_get_firmware_state(vha, state); + if (rval != QLA_SUCCESS) { + /* Retry if timer has not expired */ + break; + } + + if (state[0] == FSTATE_FX00_CONFIG_WAIT) { + /* Firmware is waiting to be + * initialized by driver + */ + rval = QLA_SUCCESS; + done = true; + break; + } + + /* Issue driver shutdown and wait until f/w recovers. + * Driver should continue to poll until 8060 AEN is + * received indicating firmware recovery. + */ + ql_dbg(ql_dbg_init, vha, 0x0136, + "Sending Driver shutdown fw_state 0x%x\n", + state[0]); + + rval = qlafx00_driver_shutdown(vha, 10); + if (rval != QLA_SUCCESS) { + rval = QLA_FUNCTION_FAILED; + break; + } + msleep(500); + + wtime = jiffies + (wait_time * HZ); + break; + } + + if (!done) { + if (time_after_eq(jiffies, wtime)) { + ql_dbg(ql_dbg_init, vha, 0x0137, + "Init f/w failed: aen[7]: 0x%x\n", + RD_REG_DWORD(®->aenmailbox7)); + rval = QLA_FUNCTION_FAILED; + done = true; + break; + } + /* Delay for a while */ + msleep(500); + } + } while (!done); + + if (rval) + ql_dbg(ql_dbg_init, vha, 0x0138, + "%s **** FAILED ****.\n", __func__); + else + ql_dbg(ql_dbg_init, vha, 0x0139, + "%s **** SUCCESS ****.\n", __func__); + + return rval; +} + +/* + * qlafx00_fw_ready() - Waits for firmware ready. + * @ha: HA context + * + * Returns 0 on success. + */ +int +qlafx00_fw_ready(scsi_qla_host_t *vha) +{ + int rval; + unsigned long wtime; + uint16_t wait_time; /* Wait time if loop is coming ready */ + uint32_t state[5]; + + rval = QLA_SUCCESS; + + wait_time = 10; + + /* wait time before firmware ready */ + wtime = jiffies + (wait_time * HZ); + + /* Wait for ISP to finish init */ + if (!vha->flags.init_done) + ql_dbg(ql_dbg_init, vha, 0x013a, + "Waiting for init to complete...\n"); + + do { + rval = qlafx00_get_firmware_state(vha, state); + + if (rval == QLA_SUCCESS) { + if (state[0] == FSTATE_FX00_INITIALIZED) { + ql_dbg(ql_dbg_init, vha, 0x013b, + "fw_state=%x\n", state[0]); + rval = QLA_SUCCESS; + break; + } + } + rval = QLA_FUNCTION_FAILED; + + if (time_after_eq(jiffies, wtime)) + break; + + /* Delay for a while */ + msleep(500); + + ql_dbg(ql_dbg_init, vha, 0x013c, + "fw_state=%x curr time=%lx.\n", state[0], jiffies); + } while (1); + + + if (rval) + ql_dbg(ql_dbg_init, vha, 0x013d, + "Firmware ready **** FAILED ****.\n"); + else + ql_dbg(ql_dbg_init, vha, 0x013e, + "Firmware ready **** SUCCESS ****.\n"); + + return rval; +} + +static int +qlafx00_find_all_targets(scsi_qla_host_t *vha, + struct list_head *new_fcports) +{ + int rval; + uint16_t tgt_id; + fc_port_t *fcport, *new_fcport; + int found; + struct qla_hw_data *ha = vha->hw; + + rval = QLA_SUCCESS; + + if (!test_bit(LOOP_RESYNC_ACTIVE, &vha->dpc_flags)) + return QLA_FUNCTION_FAILED; + + if ((atomic_read(&vha->loop_down_timer) || + STATE_TRANSITION(vha))) { + atomic_set(&vha->loop_down_timer, 0); + set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); + return QLA_FUNCTION_FAILED; + } + + ql_dbg(ql_dbg_disc + ql_dbg_init, vha, 0x2088, + "Listing Target bit map...\n"); + ql_dump_buffer(ql_dbg_disc + ql_dbg_init, vha, + 0x2089, (uint8_t *)ha->gid_list, 32); + + /* Allocate temporary rmtport for any new rmtports discovered. */ + new_fcport = qla2x00_alloc_fcport(vha, GFP_KERNEL); + if (new_fcport == NULL) + return QLA_MEMORY_ALLOC_FAILED; + + for_each_set_bit(tgt_id, (void *)ha->gid_list, + QLAFX00_TGT_NODE_LIST_SIZE) { + + /* Send get target node info */ + new_fcport->tgt_id = tgt_id; + rval = qlafx00_fx_disc(vha, new_fcport, + FXDISC_GET_TGT_NODE_INFO); + if (rval != QLA_SUCCESS) { + ql_log(ql_log_warn, vha, 0x208a, + "Target info scan failed -- assuming zero-entry " + "result...\n"); + continue; + } + + /* Locate matching device in database. */ + found = 0; + list_for_each_entry(fcport, &vha->vp_fcports, list) { + if (memcmp(new_fcport->port_name, + fcport->port_name, WWN_SIZE)) + continue; + + found++; + + /* + * If tgt_id is same and state FCS_ONLINE, nothing + * changed. + */ + if (fcport->tgt_id == new_fcport->tgt_id && + atomic_read(&fcport->state) == FCS_ONLINE) + break; + + /* + * Tgt ID changed or device was marked to be updated. + */ + ql_dbg(ql_dbg_disc + ql_dbg_init, vha, 0x208b, + "TGT-ID Change(%s): Present tgt id: " + "0x%x state: 0x%x " + "wwnn = %llx wwpn = %llx.\n", + __func__, fcport->tgt_id, + atomic_read(&fcport->state), + (unsigned long long)wwn_to_u64(fcport->node_name), + (unsigned long long)wwn_to_u64(fcport->port_name)); + + ql_log(ql_log_info, vha, 0x208c, |