/* * QLogic Fibre Channel HBA Driver * Copyright (c) 2003-2014 QLogic Corporation * * See LICENSE.qla2xxx for copyright and licensing details. */ #include #include #include "qla_def.h" #include "qla_gbl.h" #define TIMEOUT_100_MS 100 static const uint32_t qla8044_reg_tbl[] = { QLA8044_PEG_HALT_STATUS1, QLA8044_PEG_HALT_STATUS2, QLA8044_PEG_ALIVE_COUNTER, QLA8044_CRB_DRV_ACTIVE, QLA8044_CRB_DEV_STATE, QLA8044_CRB_DRV_STATE, QLA8044_CRB_DRV_SCRATCH, QLA8044_CRB_DEV_PART_INFO1, QLA8044_CRB_IDC_VER_MAJOR, QLA8044_FW_VER_MAJOR, QLA8044_FW_VER_MINOR, QLA8044_FW_VER_SUB, QLA8044_CMDPEG_STATE, QLA8044_ASIC_TEMP, }; /* 8044 Flash Read/Write functions */ uint32_t qla8044_rd_reg(struct qla_hw_data *ha, ulong addr) { return readl((void __iomem *) (ha->nx_pcibase + addr)); } void qla8044_wr_reg(struct qla_hw_data *ha, ulong addr, uint32_t val) { writel(val, (void __iomem *)((ha)->nx_pcibase + addr)); } int qla8044_rd_direct(struct scsi_qla_host *vha, const uint32_t crb_reg) { struct qla_hw_data *ha = vha->hw; if (crb_reg < CRB_REG_INDEX_MAX) return qla8044_rd_reg(ha, qla8044_reg_tbl[crb_reg]); else return QLA_FUNCTION_FAILED; } void qla8044_wr_direct(struct scsi_qla_host *vha, const uint32_t crb_reg, const uint32_t value) { struct qla_hw_data *ha = vha->hw; if (crb_reg < CRB_REG_INDEX_MAX) qla8044_wr_reg(ha, qla8044_reg_tbl[crb_reg], value); } static int qla8044_set_win_base(scsi_qla_host_t *vha, uint32_t addr) { uint32_t val; int ret_val = QLA_SUCCESS; struct qla_hw_data *ha = vha->hw; qla8044_wr_reg(ha, QLA8044_CRB_WIN_FUNC(ha->portnum), addr); val = qla8044_rd_reg(ha, QLA8044_CRB_WIN_FUNC(ha->portnum)); if (val != addr) { ql_log(ql_log_warn, vha, 0xb087, "%s: Failed to set register window : " "addr written 0x%x, read 0x%x!\n", __func__, addr, val); ret_val = QLA_FUNCTION_FAILED; } return ret_val; } static int qla8044_rd_reg_indirect(scsi_qla_host_t *vha, uint32_t addr, uint32_t *data) { int ret_val = QLA_SUCCESS; struct qla_hw_data *ha = vha->hw; ret_val = qla8044_set_win_base(vha, addr); if (!ret_val) *data = qla8044_rd_reg(ha, QLA8044_WILDCARD); else ql_log(ql_log_warn, vha, 0xb088, "%s: failed read of addr 0x%x!\n", __func__, addr); return ret_val; } static int qla8044_wr_reg_indirect(scsi_qla_host_t *vha, uint32_t addr, uint32_t data) { int ret_val = QLA_SUCCESS; struct qla_hw_data *ha = vha->hw; ret_val = qla8044_set_win_base(vha, addr); if (!ret_val) qla8044_wr_reg(ha, QLA8044_WILDCARD, data); else ql_log(ql_log_warn, vha, 0xb089, "%s: failed wrt to addr 0x%x, data 0x%x\n", __func__, addr, data); return ret_val; } /* * qla8044_read_write_crb_reg - Read from raddr and write value to waddr. * * @ha : Pointer to adapter structure * @raddr : CRB address to read from * @waddr : CRB address to write to * */ static void qla8044_read_write_crb_reg(struct scsi_qla_host *vha, uint32_t raddr, uint32_t waddr) { uint32_t value; qla8044_rd_reg_indirect(vha, raddr, &value); qla8044_wr_reg_indirect(vha, waddr, value); } static int qla8044_poll_wait_for_ready(struct scsi_qla_host *vha, uint32_t addr1, uint32_t mask) { unsigned long timeout; uint32_t temp; /* jiffies after 100ms */ timeout = jiffies + msecs_to_jiffies(TIMEOUT_100_MS); do { qla8044_rd_reg_indirect(vha, addr1, &temp); if ((temp & mask) != 0) break; if (time_after_eq(jiffies, timeout)) { ql_log(ql_log_warn, vha, 0xb151, "Error in processing rdmdio entry\n"); return -1; } } while (1); return 0; } static uint32_t qla8044_ipmdio_rd_reg(struct scsi_qla_host *vha, uint32_t addr1, uint32_t addr3, uint32_t mask, uint32_t addr) { uint32_t temp; int ret = 0; ret = qla8044_poll_wait_for_ready(vha, addr1, mask); if (ret == -1) return -1; temp = (0x40000000 | addr); qla8044_wr_reg_indirect(vha, addr1, temp); ret = qla8044_poll_wait_for_ready(vha, addr1, mask); if (ret == -1) return 0; qla8044_rd_reg_indirect(vha, addr3, &ret); return ret; } static int qla8044_poll_wait_ipmdio_bus_idle(struct scsi_qla_host *vha, uint32_t addr1, uint32_t addr2, uint32_t addr3, uint32_t mask) { unsigned long timeout; uint32_t temp; /* jiffies after 100 msecs */ timeout = jiffies + msecs_to_jiffies(TIMEOUT_100_MS); do { temp = qla8044_ipmdio_rd_reg(vha, addr1, addr3, mask, addr2); if ((temp & 0x1) != 1) break; if (time_after_eq(jiffies, timeout)) { ql_log(ql_log_warn, vha, 0xb152, "Error in processing mdiobus idle\n"); return -1; } } while (1); return 0; } static int qla8044_ipmdio_wr_reg(struct scsi_qla_host *vha, uint32_t addr1, uint32_t addr3, uint32_t mask, uint32_t addr, uint32_t value) { int ret = 0; ret = qla8044_poll_wait_for_ready(vha, addr1, mask); if (ret == -1) return -1; qla8044_wr_reg_indirect(vha, addr3, value); qla8044_wr_reg_indirect(vha, addr1, addr); ret = qla8044_poll_wait_for_ready(vha, addr1, mask); if (ret == -1) return -1; return 0; } /* * qla8044_rmw_crb_reg - Read value from raddr, AND with test_mask, * Shift Left,Right/OR/XOR with values RMW header and write value to waddr. * * @vha : Pointer to adapter structure * @raddr : CRB address to read from * @waddr : CRB address to write to * @p_rmw_hdr : header with shift/or/xor values. * */ static void qla8044_rmw_crb_reg(struct scsi_qla_host *vha, uint32_t raddr, uint32_t waddr, struct qla8044_rmw *p_rmw_hdr) { uint32_t value; if (p_rmw_hdr->index_a) value = vha->reset_tmplt.array[p_rmw_hdr->index_a]; else qla8044_rd_reg_indirect(vha, raddr, &value); value &= p_rmw_hdr->test_mask; value <<= p_rmw_hdr->shl; value >>= p_rmw_hdr->shr; value |= p_rmw_hdr->or_value; value ^= p_rmw_hdr->xor_value; qla8044_wr_reg_indirect(vha, waddr, value); return; } static inline void qla8044_set_qsnt_ready(struct scsi_qla_host *vha) { uint32_t qsnt_state; struct qla_hw_data *ha = vha->hw; qsnt_state = qla8044_rd_direct(vha, QLA8044_CRB_DRV_STATE_INDEX); qsnt_state |= (1 << ha->portnum); qla8044_wr_direct(vha, QLA8044_CRB_DRV_STATE_INDEX, qsnt_state); ql_log(ql_log_info, vha, 0xb08e, "%s(%ld): qsnt_state: 0x%08x\n", __func__, vha->host_no, qsnt_state); } void qla8044_clear_qsnt_ready(struct scsi_qla_host *vha) { uint32_t qsnt_state; struct qla_hw_data *ha = vha->hw; qsnt_state = qla8044_rd_direct(vha, QLA8044_CRB_DRV_STATE_INDEX); qsnt_state &= ~(1 << ha->portnum); qla8044_wr_direct(vha, QLA8044_CRB_DRV_STATE_INDEX, qsnt_state); ql_log(ql_log_info, vha, 0xb08f, "%s(%ld): qsnt_state: 0x%08x\n", __func__, vha->host_no, qsnt_state); } /** * qla8044_lock_recovery - Recovers the idc_lock. * @vha : Pointer to adapter structure * * Lock Recovery Register * 5-2 Lock recovery owner: Function ID of driver doing lock recovery, * valid if bits 1..0 are set by driver doing lock recovery. * 1-0 1 - Driver intends to force unlock the IDC lock. * 2 - Driver is moving forward to unlock the IDC lock. Driver clears * this field after force unlocking the IDC lock. * * Lock Recovery process * a. Read the IDC_LOCK_RECOVERY register. If the value in bits 1..0 is * greater than 0, then wait for the other driver to unlock otherwise * move to the next step. * b. Indicate intent to force-unlock by writing 1h to the IDC_LOCK_RECOVERY * register bits 1..0 and also set the function# in bits 5..2. * c. Read the IDC_LOCK_RECOVERY register again after a delay of 200ms. * Wait for the other driver to perform lock recovery if the function * number in bits 5..2 has changed, otherwise move to the next step. * d. Write a value of 2h to the IDC_LOCK_RECOVERY register bits 1..0 * leaving your function# in bits 5..2. * e. Force unlock using the DRIVER_UNLOCK register and immediately clear * the IDC_LOCK_RECOVERY bits 5..0 by writing 0. **/ static int qla8044_lock_recovery(struct scsi_qla_host *vha) { uint32_t lock = 0, lockid; struct qla_hw_data *ha = vha->hw; lockid = qla8044_rd_reg(ha, QLA8044_DRV_LOCKRECOVERY); /* Check for other Recovery in progress, go wait */ if ((lockid & IDC_LOCK_RECOVERY_STATE_MASK) != 0) return QLA_FUNCTION_FAILED; /* Intent to Recover */ qla8044_wr_reg(ha, QLA8044_DRV_LOCKRECOVERY, (ha->portnum << IDC_LOCK_RECOVERY_STATE_SHIFT_BITS) | INTENT_TO_RECOVER); msleep(200); /* Check Intent to Recover is advertised */ lockid = qla8044_rd_reg(ha, QLA8044_DRV_LOCKRECOVERY); if ((lockid & IDC_LOCK_RECOVERY_OWNER_MASK) != (ha->portnum << IDC_LOCK_RECOVERY_STATE_SHIFT_BITS)) return QLA_FUNCTION_FAILED; ql_dbg(ql_dbg_p3p, vha, 0xb08B, "%s:%d: IDC Lock recovery initiated\n" , __func__, ha->portnum); /* Proceed to Recover */ qla8044_wr_reg(ha, QLA8044_DRV_LOCKRECOVERY, (ha->portnum << IDC_LOCK_RECOVERY_STATE_SHIFT_BITS) | PROCEED_TO_RECOVER); /* Force Unlock() */ qla8044_wr_reg(ha, QLA8044_DRV_LOCK_ID, 0xFF); qla8044_rd_reg(ha, QLA8044_DRV_UNLOCK); /* Clear bits 0-5 in IDC_RECOVERY register*/ qla8044_wr_reg(ha, QLA8044_DRV_LOCKRECOVERY, 0); /* Get lock() */ lock = qla8044_rd_reg(ha, QLA8044_DRV_LOCK); if (lock) { lockid = qla8044_rd_reg(ha, QLA8044_DRV_LOCK_ID); lockid = ((lockid + (1 << 8)) & ~0xFF) | ha->portnum; qla8044_wr_reg(ha, QLA8044_DRV_LOCK_ID, lockid); return QLA_SUCCESS; } else return QLA_FUNCTION_FAILED; } int qla8044_idc_lock(struct qla_hw_data *ha) { uint32_t ret_val = QLA_SUCCESS, timeout = 0, status = 0; uint32_t lock_id, lock_cnt, func_num, tmo_owner = 0, first_owner = 0; scsi_qla_host_t *vha = pci_get_drvdata(ha->pdev); while (status == 0) { /* acquire semaphore5 from PCI HW block */ status = qla8044_rd_reg(ha, QLA8044_DRV_LOCK); if (status) { /* Increment Counter (8-31) and update func_num (0-7) on * getting a successful lock */ lock_id = qla8044_rd_reg(ha, QLA8044_DRV_LOCK_ID); lock_id = ((lock_id + (1 << 8)) & ~0xFF) | ha->portnum; qla8044_wr_reg(ha, QLA8044_DRV_LOCK_ID, lock_id); break; } if (timeout == 0) first_owner = qla8044_rd_reg(ha, QLA8044_DRV_LOCK_ID); if (++timeout >= (QLA8044_DRV_LOCK_TIMEOUT / QLA8044_DRV_LOCK_MSLEEP)) { tmo_owner = qla8044_rd_reg(ha, QLA8044_DRV_LOCK_ID); func_num = tmo_owner & 0xFF; lock_cnt = tmo_owner >> 8; ql_log(ql_log_warn, vha, 0xb114, "%s: Lock by func %d failed after 2s, lock held " "by func %d, lock count %d, first_owner %d\n", __func__, ha->portnum, func_num, lock_cnt, (first_owner & 0xFF)); if (first_owner != tmo_owner) { /* Some other driver got lock, * OR same driver got lock again (counter * value changed), when we were waiting for * lock. Retry for another 2 sec */ ql_dbg(ql_dbg_p3p, vha, 0xb115, "%s: %d: IDC lock failed\n", __func__, ha->portnum); timeout = 0; } else { /* Same driver holding lock > 2sec. * Force Recovery */ if (qla8044_lock_recovery(vha) == QLA_SUCCESS) { /* Recovered and got lock */ ret_val = QLA_SUCCESS; ql_dbg(ql_dbg_p3p, vha, 0xb116, "%s:IDC lock Recovery by %d" "successful...\n", __func__, ha->portnum); } /* Recovery Failed, some other function * has the lock, wait for 2secs * and retry */ ql_dbg(ql_dbg_p3p, vha, 0xb08a, "%s: IDC lock Recovery by %d " "failed, Retrying timeout\n", __func__, ha->portnum); timeout = 0; } } msleep(QLA8044_DRV_LOCK_MSLEEP); } return ret_val; } void qla8044_idc_unlock(struct qla_hw_data *ha) { int id; scsi_qla_host_t *vha = pci_get_drvdata(ha->pdev); id = qla8044_rd_reg(ha, QLA8044_DRV_LOCK_ID); if ((id & 0xFF) != ha->portnum) { ql_log(ql_log_warn, vha, 0xb118, "%s: IDC Unlock by %d failed, lock owner is %d!\n", __func__, ha->portnum, (id & 0xFF)); return; } /* Keep lock counter value, update the ha->func_num to 0xFF */ qla8044_wr_reg(ha, QLA8044_DRV_LOCK_ID, (id | 0xFF)); qla8044_rd_reg(ha, QLA8044_DRV_UNLOCK); } /* 8044 Flash Lock/Unlock functions */ static int qla8044_flash_lock(scsi_qla_host_t *vha) { int lock_owner; int timeout = 0; uint32_t lock_status = 0; int ret_val = QLA_SUCCESS; struct qla_hw_data *ha = vha->hw; while (lock_status == 0) { lock_status = qla8044_rd_reg(ha, QLA8044_FLASH_LOCK); if (lock_status) break; if (++timeout >= QLA8044_FLASH_LOCK_TIMEOUT / 20) { lock_owner = qla8044_rd_reg(ha, QLA8044_FLASH_LOCK_ID); ql_log(ql_log_warn, vha, 0xb113, "%s: Simultaneous flash access by following ports, active port = %d: accessing port = %d", __func__, ha->portnum, lock_owner); ret_val = QLA_FUNCTION_FAILED; break; } msleep(20); } qla8044_wr_reg(ha, QLA8044_FLASH_LOCK_ID, ha->portnum); return ret_val; } static void qla8044_flash_unlock(scsi_qla_host_t *vha) { struct qla_hw_data *ha = vha->hw; /* Reading FLASH_UNLOCK register unlocks the Flash */ qla8044_wr_reg(ha, QLA8044_FLASH_LOCK_ID, 0xFF); qla8044_rd_reg(ha, QLA8044_FLASH_UNLOCK); } static void qla8044_flash_lock_recovery(struct scsi_qla_host *vha) { if (qla8044_flash_lock(vha)) { /* Someone else is holding the lock. */ ql_log(ql_log_warn, vha, 0xb120, "Resetting flash_lock\n"); } /* * Either we got the lock, or someone * else died while holding it. * In either case, unlock. */ qla8044_flash_unlock(vha); } /* * Address and length are byte address */ static int qla8044_read_flash_data(scsi_qla_host_t *vha, uint8_t *p_data, uint32_t flash_addr, int u32_word_count) { int i, ret_val = QLA_SUCCESS; uint32_t u32_word; if (qla8044_flash_lock(vha) != QLA_SUCCESS) { ret_val = QLA_FUNCTION_FAILED; goto exit_lock_error; } if (flash_addr & 0x03) { ql_log(ql_log_warn, vha, 0xb117, "%s: Illegal addr = 0x%x\n", __func__, flash_addr); ret_val = QLA_FUNCTION_FAILED; goto exit_flash_read; } for (i = 0; i < u32_word_count; i++) { if (qla8044_wr_reg_indirect(vha, QLA8044_FLASH_DIRECT_WINDOW, (flash_addr & 0xFFFF0000))) { ql_log(ql_log_warn, vha, 0xb119, "%s: failed to write addr 0x%x to " "FLASH_DIRECT_WINDOW\n! ", __func__, flash_addr); ret_val = QLA_FUNCTION_FAILED; goto exit_flash_read; } ret_val = qla8044_rd_reg_indirect(vha, QLA8044_FLASH_DIRECT_DATA(flash_addr), &u32_word); if (ret_val != QLA_SUCCESS) { ql_log(ql_log_warn, vha, 0xb08c, "%s: failed to read addr 0x%x!\n", __func__, flash_addr); goto exit_flash_read; } *(uint32_t *)p_data = u32_word; p_data = p_data + 4; flash_addr = flash_addr + 4; } exit_flash_read: qla8044_flash_unlock(vha); exit_lock_error: return ret_val; } /* * Address and length are byte address */ void * qla8044_read_optrom_data(struct scsi_qla_host *vha, void *buf, uint32_t offset, uint32_t length) { scsi_block_requests(vha->host); if (qla8044_read_flash_data(vha, buf, offset, length / 4) != QLA_SUCCESS) { ql_log(ql_log_warn, vha, 0xb08d, "%s: Failed to read from flash\n", __func__); } scsi_unblock_requests(vha->host); return buf; } static inline int qla8044_need_reset(struct scsi_qla_host *vha) { uint32_t drv_state, drv_active; int rval; struct qla_hw_data *ha = vha->hw; drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); drv_state = qla8044_rd_direct(vha, QLA8044_CRB_DRV_STATE_INDEX); rval = drv_state & (1 << ha->portnum); if (ha->flags.eeh_busy && drv_active) rval = 1; return rval; } /* * qla8044_write_list - Write the value (p_entry->arg2) to address specified * by p_entry->arg1 for all entries in header with delay of p_hdr->delay between * entries. * * @vha : Pointer to adapter structure * @p_hdr : reset_entry header for WRITE_LIST opcode. * */ static void qla8044_write_list(struct scsi_qla_host *vha, struct qla8044_reset_entry_hdr *p_hdr) { struct qla8044_entry *p_entry; uint32_t i; p_entry = (struct qla8044_entry *)((char *)p_hdr + sizeof(struct qla8044_reset_entry_hdr)); for (i = 0; i < p_hdr->count; i++, p_entry++) { qla8044_wr_reg_indirect(vha, p_entry->arg1, p_entry->arg2); if (p_hdr->delay) udelay((uint32_t)(p_hdr->delay)); } } /* * qla8044_read_write_list - Read from address specified by p_entry->arg1, * write value read to address specified by p_entry->arg2, for all entries in * header with delay of p_hdr->delay between entries. * * @vha : Pointer to adapter structure * @p_hdr : reset_entry header for READ_WRITE_LIST opcode. * */ static void qla8044_read_write_list(struct scsi_qla_host *vha, struct qla8044_reset_entry_hdr *p_hdr) { struct qla8044_entry *p_entry; uint32_t i; p_entry = (struct qla8044_entry *)((char *)p_hdr + sizeof(struct qla8044_reset_entry_hdr)); for (i = 0; i < p_hdr->count; i++, p_entry++) { qla8044_read_write_crb_reg(vha, p_entry->arg1, p_entry->arg2); if (p_hdr->delay) udelay((uint32_t)(p_hdr->delay)); } } /* * qla8044_poll_reg - Poll the given CRB addr for duration msecs till * value read ANDed with test_mask is equal to test_result. * * @ha : Pointer to adapter structure * @addr : CRB register address * @duration : Poll for total of "duration" msecs * @test_mask : Mask value read with "test_mask" * @test_result : Compare (value&test_mask) with test_result. * * Return Value - QLA_SUCCESS/QLA_FUNCTION_FAILED */ static int qla8044_poll_reg(struct scsi_qla_host *vha, uint32_t addr, int duration, uint32_t test_mask, uint32_t test_result) { uint32_t value; int timeout_error; uint8_t retries; int ret_val = QLA_SUCCESS; ret_val = qla8044_rd_reg_indirect(vha, addr, &value); if (ret_val == QLA_FUNCTION_FAILED) { timeout_error = 1; goto exit_poll_reg; } /* poll every 1/10 of the total duration */ retries = duration/10; do { if ((value & test_mask) != test_result) { timeout_error = 1; msleep(duration/10); ret_val = qla8044_rd_reg_indirect(vha, addr, &value); if (ret_val == QLA_FUNCTION_FAILED) { timeout_error = 1; goto exit_poll_reg; } } else { timeout_error = 0; break; } } while (retries--); exit_poll_reg: if (timeout_error) { vha->reset_tmplt.seq_error++; ql_log(ql_log_fatal, vha, 0xb090, "%s: Poll Failed: 0x%08x 0x%08x 0x%08x\n", __func__, value, test_mask, test_result); } return timeout_error; } /* * qla8044_poll_list - For all entries in the POLL_LIST header, poll read CRB * register specified by p_entry->arg1 and compare (value AND test_mask) with * test_result to validate it. Wait for p_hdr->delay between processing entries. * * @ha : Pointer to adapter structure * @p_hdr : reset_entry header for POLL_LIST opcode. * */ static void qla8044_poll_list(struct scsi_qla_host *vha, struct qla8044_reset_entry_hdr *p_hdr) { long delay; struct qla8044_entry *p_entry; struct qla8044_poll *p_poll; uint32_t i; uint32_t value; p_poll = (struct qla8044_poll *) ((char *)p_hdr + sizeof(struct qla8044_reset_entry_hdr)); /* Entries start after 8 byte qla8044_poll, poll header contains * the test_mask, test_value. */ p_entry = (struct qla8044_entry *)((char *)p_poll + sizeof(struct qla8044_poll)); delay = (long)p_hdr->delay; if (!delay) { for (i = 0; i < p_hdr->count; i++, p_entry++) qla8044_poll_reg(vha, p_entry->arg1, delay, p_poll->test_mask, p_poll->test_value); } else { for (i = 0; i < p_hdr->count; i++, p_entry++) { if (delay) { if (qla8044_poll_reg(vha, p_entry->arg1, delay, p_poll->test_mask, p_poll->test_value)) { /*If * (data_read&test_mask != test_value) * read TIMEOUT_ADDR (arg1) and * ADDR (arg2) registers */ qla8044_rd_reg_indirect(vha, p_entry->arg1, &value); qla8044_rd_reg_indirect(vha, p_entry->arg2, &value); } } } } } /* * qla8044_poll_write_list - Write dr_value, ar_value to dr_addr/ar_addr, * read ar_addr, if (value& test_mask != test_mask) re-read till timeout * expires. * * @vha : Pointer to adapter structure * @p_hdr : reset entry header for POLL_WRITE_LIST opcode. * */ static void qla8044_poll_write_list(struct scsi_qla_host *vha, struct qla8044_reset_entry_hdr *p_hdr) { long delay; struct qla8044_quad_entry *p_entry; struct qla8044_poll *p_poll; uint32_t i; p_poll = (struct qla8044_poll *)((char *)p_hdr + sizeof(struct qla8044_reset_entry_hdr)); p_entry = (struct qla8044_quad_entry *)((char *)p_poll + sizeof(struct qla8044_poll)); delay = (long)p_hdr->delay; for (i = 0; i < p_hdr->count; i++, p_entry++) { qla8044_wr_reg_indirect(vha, p_entry->dr_addr, p_entry->dr_value); qla8044_wr_reg_indirect(vha, p_entry->ar_addr, p_entry->ar_value); if (delay) { if (qla8044_poll_reg(vha, p_entry->ar_addr, delay, p_poll->test_mask, p_poll->test_value)) { ql_dbg(ql_dbg_p3p, vha, 0xb091, "%s: Timeout Error: poll list, ", __func__); ql_dbg(ql_dbg_p3p, vha, 0xb092, "item_num %d, entry_num %d\n", i, vha->reset_tmplt.seq_index); } } } } /* * qla8044_read_modify_write - Read value from p_entry->arg1, modify the * value, write value to p_entry->arg2. Process entries with p_hdr->delay * between entries. * * @vha : Pointer to adapter structure * @p_hdr : header with shift/or/xor values. * */ static void qla8044_read_modify_write(struct scsi_qla_host *vha, struct qla8044_reset_entry_hdr *p_hdr) { struct qla8044_entry *p_entry; struct qla8044_rmw *p_rmw_hdr; uint32_t i; p_rmw_hdr = (struct qla8044_rmw *)((char *)p_hdr + sizeof(struct qla8044_reset_entry_hdr)); p_entry = (struct qla8044_entry *)((char *)p_rmw_hdr + sizeof(struct qla8044_rmw)); for (i = 0; i < p_hdr->count; i++, p_entry++) { qla8044_rmw_crb_reg(vha, p_entry->arg1, p_entry->arg2, p_rmw_hdr); if (p_hdr->delay) udelay((uint32_t)(p_hdr->delay)); } } /* * qla8044_pause - Wait for p_hdr->delay msecs, called between processing * two entries of a sequence. * * @vha : Pointer to adapter structure * @p_hdr : Common reset entry header. * */ static void qla8044_pause(struct scsi_qla_host *vha, struct qla8044_reset_entry_hdr *p_hdr) { if (p_hdr->delay) mdelay((uint32_t)((long)p_hdr->delay)); } /* * qla8044_template_end - Indicates end of reset sequence processing. * * @vha : Pointer to adapter structure * @p_hdr : Common reset entry header. * */ static void qla8044_template_end(struct scsi_qla_host *vha, struct qla8044_reset_entry_hdr *p_hdr) { vha->reset_tmplt.template_end = 1; if (vha->reset_tmplt.seq_error == 0) { ql_dbg(ql_dbg_p3p, vha, 0xb093, "%s: Reset sequence completed SUCCESSFULLY.\n", __func__); } else { ql_log(ql_log_fatal, vha, 0xb094, "%s: Reset sequence completed with some timeout " "errors.\n", __func__); } } /* * qla8044_poll_read_list - Write ar_value to ar_addr register, read ar_addr, * if (value & test_mask != test_value) re-read till timeout value expires, * read dr_addr register and assign to reset_tmplt.array. * * @vha : Pointer to adapter structure * @p_hdr : Common reset entry header. * */ static void qla8044_poll_read_list(struct scsi_qla_host *vha, struct qla8044_reset_entry_hdr *p_hdr) { long delay; int index; struct qla8044_quad_entry *p_entry; struct qla8044_poll *p_poll; uint32_t i; uint32_t value; p_poll = (struct qla8044_poll *) ((char *)p_hdr + sizeof(struct qla8044_reset_entry_hdr)); p_entry = (struct qla8044_quad_entry *) ((char *)p_poll + sizeof(struct qla8044_poll)); delay = (long)p_hdr->delay; for (i = 0; i < p_hdr->count; i++, p_entry++) { qla8044_wr_reg_indirect(vha, p_entry->ar_addr, p_entry->ar_value); if (delay) { if (qla8044_poll_reg(vha, p_entry->ar_addr, delay, p_poll->test_mask, p_poll->test_value)) { ql_dbg(ql_dbg_p3p, vha, 0xb095, "%s: Timeout Error: poll " "list, ", __func__); ql_dbg(ql_dbg_p3p, vha, 0xb096, "Item_num %d, " "entry_num %d\n", i, vha->reset_tmplt.seq_index); } else { index = vha->reset_tmplt.array_index; qla8044_rd_reg_indirect(vha, p_entry->dr_addr, &value); vha->reset_tmplt.array[index++] = value; if (index == QLA8044_MAX_RESET_SEQ_ENTRIES) vha->reset_tmplt.array_index = 1; } } } } /* * qla8031_process_reset_template - Process all entries in reset template * till entry with SEQ_END opcode, which indicates end of the reset template * processing. Each entry has a Reset Entry header, entry opcode/command, with * size of the entry, number of entries in sub-sequence and delay in microsecs * or timeout in millisecs. * * @ha : Pointer to adapter structure * @p_buff : Common reset entry header. * */ static void qla8044_process_reset_template(struct scsi_qla_host *vha, char *p_buff) { int index, entries; struct qla8044_reset_entry_hdr *p_hdr; char *p_entry = p_buff; vha->reset_tmplt.seq_end = 0; vha->reset_tmplt.template_end = 0; entries = vha->reset_tmplt.hdr->entries; index = vha->reset_tmplt.seq_index; for (; (!vha->reset_tmplt.seq_end) && (index < entries); index++) { p_hdr = (struct qla8044_reset_entry_hdr *)p_entry; switch (p_hdr->cmd) { case OPCODE_NOP: break; case OPCODE_WRITE_LIST: qla8044_write_list(vha, p_hdr); break; case OPCODE_READ_WRITE_LIST: qla8044_read_write_list(vha, p_hdr); break; case OPCODE_POLL_LIST: qla8044_poll_list(vha, p_hdr); break; case OPCODE_POLL_WRITE_LIST: qla8044_poll_write_list(vha, p_hdr); break; case OPCODE_READ_MODIFY_WRITE: qla8044_read_modify_write(vha, p_hdr); break; case OPCODE_SEQ_PAUSE: qla8044_pause(vha, p_hdr); break; case OPCODE_SEQ_END: vha->reset_tmplt.seq_end = 1; break; case OPCODE_TMPL_END: qla8044_template_end(vha, p_hdr); break; case OPCODE_POLL_READ_LIST: qla8044_poll_read_list(vha, p_hdr); break; default: ql_log(ql_log_fatal, vha, 0xb097, "%s: Unknown command ==> 0x%04x on " "entry = %d\n", __func__, p_hdr->cmd, index); break; } /* *Set pointer to next entry in the sequence. */ p_entry += p_hdr->size; } vha->reset_tmplt.seq_index = index; } static void qla8044_process_init_seq(struct scsi_qla_host *vha) { qla8044_process_reset_template(vha, vha->reset_tmplt.init_offset); if (vha->reset_tmplt.seq_end != 1) ql_log(ql_log_fatal, vha, 0xb098, "%s: Abrupt INIT Sub-Sequence end.\n", __func__); } static void qla8044_process_stop_seq(struct scsi_qla_host *vha) { vha->reset_tmplt.seq_index = 0; qla8044_process_reset_template(vha, vha->reset_tmplt.stop_offset); if (vha->reset_tmplt.seq_end != 1) ql_log(ql_log_fatal, vha, 0xb099, "%s: Abrupt STOP Sub-Sequence end.\n", __func__); } static void qla8044_process_start_seq(struct scsi_qla_host *vha) { qla8044_process_reset_template(vha, vha->reset_tmplt.start_offset); if (vha->reset_tmplt.template_end != 1) ql_log(ql_log_fatal, vha, 0xb09a, "%s: Abrupt START Sub-Sequence end.\n", __func__); } static int qla8044_lockless_flash_read_u32(struct scsi_qla_host *vha, uint32_t flash_addr, uint8_t *p_data, int u32_word_count) { uint32_t i; uint32_t u32_word; uint32_t flash_offset; uint32_t addr = flash_addr; int ret_val = QLA_SUCCESS; flash_offset = addr & (QLA8044_FLASH_SECTOR_SIZE - 1); if (addr & 0x3) { ql_log(ql_log_fatal, vha, 0xb09b, "%s: Illegal addr = 0x%x\n", __func__, addr); ret_val = QLA_FUNCTION_FAILED; goto exit_lockless_read; } ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_DIRECT_WINDOW, (addr)); if (ret_val != QLA_SUCCESS) { ql_log(ql_log_fatal, vha, 0xb09c, "%s: failed to write addr 0x%x to FLASH_DIRECT_WINDOW!\n", __func__, addr); goto exit_lockless_read; } /* Check if data is spread across multiple sectors */ if ((flash_offset + (u32_word_count * sizeof(uint32_t))) > (QLA8044_FLASH_SECTOR_SIZE - 1)) { /* Multi sector read */ for (i = 0; i < u32_word_count; i++) { ret_val = qla8044_rd_reg_indirect(vha, QLA8044_FLASH_DIRECT_DATA(addr), &u32_word); if (ret_val != QLA_SUCCESS) { ql_log(ql_log_fatal, vha, 0xb09d, "%s: failed to read addr 0x%x!\n", __func__, addr); goto exit_lockless_read; } *(uint32_t *)p_data = u32_word; p_data = p_data + 4; addr = addr + 4; flash_offset = flash_offset + 4; if (flash_offset > (QLA8044_FLASH_SECTOR_SIZE - 1)) { /* This write is needed once for each sector */ ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_DIRECT_WINDOW, (addr)); if (ret_val != QLA_SUCCESS) { ql_log(ql_log_fatal, vha, 0xb09f, "%s: failed to write addr " "0x%x to FLASH_DIRECT_WINDOW!\n", __func__, addr); goto exit_lockless_read; } flash_offset = 0; } } } else { /* Single sector read */ for (i = 0; i < u32_word_count; i++) { ret_val = qla8044_rd_reg_indirect(vha, QLA8044_FLASH_DIRECT_DATA(addr), &u32_word); if (ret_val != QLA_SUCCESS) { ql_log(ql_log_fatal, vha, 0xb0a0, "%s: failed to read addr 0x%x!\n", __func__, addr); goto exit_lockless_read; } *(uint32_t *)p_data = u32_word; p_data = p_data + 4; addr = addr + 4; } } exit_lockless_read: return ret_val; } /* * qla8044_ms_mem_write_128b - Writes data to MS/off-chip memory * * @vha : Pointer to adapter structure * addr : Flash address to write to * data : Data to be written * count : word_count to be written * * Return Value - QLA_SUCCESS/QLA_FUNCTION_FAILED */ static int qla8044_ms_mem_write_128b(struct scsi_qla_host *vha, uint64_t addr, uint32_t *data, uint32_t count) { int i, j, ret_val = QLA_SUCCESS; uint32_t agt_ctrl; unsigned long flags; struct qla_hw_data *ha = vha->hw; /* Only 128-bit aligned access */ if (addr & 0xF) { ret_val = QLA_FUNCTION_FAILED; goto exit_ms_mem_write; } write_lock_irqsave(&ha->hw_lock, flags); /* Write address */ ret_val = qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_ADDR_HI, 0); if (ret_val == QLA_FUNCTION_FAILED) { ql_log(ql_log_fatal, vha, 0xb0a1, "%s: write to AGT_ADDR_HI failed!\n", __func__); goto exit_ms_mem_write_unlock; } for (i = 0; i < count; i++, addr += 16) { if (!((addr_in_range(addr, QLA8044_ADDR_QDR_NET, QLA8044_ADDR_QDR_NET_MAX)) || (addr_in_range(addr, QLA8044_ADDR_DDR_NET, QLA8044_ADDR_DDR_NET_MAX)))) { ret_val = QLA_FUNCTION_FAILED; goto exit_ms_mem_write_unlock; } ret_val = qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_ADDR_LO, addr); /* Write data */ ret_val += qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_WRDATA_LO, *data++); ret_val += qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_WRDATA_HI, *data++); ret_val += qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_WRDATA_ULO, *data++); ret_val += qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_WRDATA_UHI, *data++); if (ret_val == QLA_FUNCTION_FAILED) { ql_log(ql_log_fatal, vha, 0xb0a2, "%s: write to AGT_WRDATA failed!\n", __func__); goto exit_ms_mem_write_unlock; } /* Check write status */ ret_val = qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_CTRL, MIU_TA_CTL_WRITE_ENABLE); ret_val += qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_CTRL, MIU_TA_CTL_WRITE_START); if (ret_val == QLA_FUNCTION_FAILED) { ql_log(ql_log_fatal, vha, 0xb0a3, "%s: write to AGT_CTRL failed!\n", __func__); goto exit_ms_mem_write_unlock; } for (j = 0; j < MAX_CTL_CHECK; j++) { ret_val = qla8044_rd_reg_indirect(vha, MD_MIU_TEST_AGT_CTRL, &agt_ctrl); if (ret_val == QLA_FUNCTION_FAILED) { ql_log(ql_log_fatal, vha, 0xb0a4, "%s: failed to read " "MD_MIU_TEST_AGT_CTRL!\n", __func__); goto exit_ms_mem_write_unlock; } if ((agt_ctrl & MIU_TA_CTL_BUSY) == 0) break; } /* Status check failed */ if (j >= MAX_CTL_CHECK) { ql_log(ql_log_fatal, vha, 0xb0a5, "%s: MS memory write failed!\n", __func__); ret_val = QLA_FUNCTION_FAILED; goto exit_ms_mem_write_unlock; } } exit_ms_mem_write_unlock: write_unlock_irqrestore(&ha->hw_lock, flags); exit_ms_mem_write: return ret_val; } static int qla8044_copy_bootloader(struct scsi_qla_host *vha) { uint8_t *p_cache; uint32_t src, count, size; uint64_t dest; int ret_val = QLA_SUCCESS; struct qla_hw_data *ha = vha->hw; src = QLA8044_BOOTLOADER_FLASH_ADDR; dest = qla8044_rd_reg(ha, QLA8044_BOOTLOADER_ADDR); size = qla8044_rd_reg(ha, QLA8044_BOOTLOADER_SIZE); /* 128 bit alignment check */ if (size & 0xF) size = (size + 16) & ~0xF; /* 16 byte count */ count = size/16; p_cache = vmalloc(size); if (p_cache == NULL) { ql_log(ql_log_fatal, vha, 0xb0a6, "%s: Failed to allocate memory for " "boot loader cache\n", __func__); ret_val = QLA_FUNCTION_FAILED; goto exit_copy_bootloader; } ret_val = qla8044_lockless_flash_read_u32(vha, src, p_cache, size/sizeof(uint32_t)); if (ret_val == QLA_FUNCTION_FAILED) { ql_log(ql_log_fatal, vha, 0xb0a7, "%s: Error reading F/W from flash!!!\n", __func__); goto exit_copy_error; } ql_dbg(ql_dbg_p3p, vha, 0xb0a8, "%s: Read F/W from flash!\n", __func__); /* 128 bit/16 byte write to MS memory */ ret_val = qla8044_ms_mem_write_128b(vha, dest, (uint32_t *)p_cache, count); if (ret_val == QLA_FUNCTION_FAILED) { ql_log(ql_log_fatal, vha, 0xb0a9, "%s: Error writing F/W to MS !!!\n", __func__); goto exit_copy_error; } ql_dbg(ql_dbg_p3p, vha, 0xb0aa, "%s: Wrote F/W (size %d) to MS !!!\n", __func__, size); exit_copy_error: vfree(p_cache); exit_copy_bootloader: return ret_val; } static int qla8044_restart(struct scsi_qla_host *vha) { int ret_val = QLA_SUCCESS; struct qla_hw_data *ha = vha->hw; qla8044_process_stop_seq(vha); /* Collect minidump */ if (ql2xmdenable) qla8044_get_minidump(vha); else ql_log(ql_log_fatal, vha, 0xb14c, "Minidump disabled.\n"); qla8044_process_init_seq(vha); if (qla8044_copy_bootloader(vha)) { ql_log(ql_log_fatal, vha, 0xb0ab, "%s: Copy bootloader, firmware restart failed!\n", __func__); ret_val = QLA_FUNCTION_FAILED; goto exit_restart; } /* * Loads F/W from flash */ qla8044_wr_reg(ha, QLA8044_FW_IMAGE_VALID, QLA8044_BOOT_FROM_FLASH); qla8044_process_start_seq(vha); exit_restart: return ret_val; } /* * qla8044_check_cmd_peg_status - Check peg status to see if Peg is * initialized. * * @ha : Pointer to adapter structure * * Return Value - QLA_SUCCESS/QLA_FUNCTION_FAILED */ static int qla8044_check_cmd_peg_status(struct scsi_qla_host *vha) { uint32_t val, ret_val = QLA_FUNCTION_FAILED; int retries = CRB_CMDPEG_CHECK_RETRY_COUNT; struct qla_hw_data *ha = vha->hw; do { val = qla8044_rd_reg(ha, QLA8044_CMDPEG_STATE); if (val == PHAN_INITIALIZE_COMPLETE) { ql_dbg(ql_dbg_p3p, vha, 0xb0ac, "%s: Command Peg initialization " "complete! state=0x%x\n", __func__, val); ret_val = QLA_SUCCESS; break; } msleep(CRB_CMDPEG_CHECK_DELAY); } while (--retries); return ret_val; } static int qla8044_start_firmware(struct scsi_qla_host *vha) { int ret_val = QLA_SUCCESS; if (qla8044_restart(vha)) { ql_log(ql_log_fatal, vha, 0xb0ad, "%s: Restart Error!!!, Need Reset!!!\n", __func__); ret_val = QLA_FUNCTION_FAILED; goto exit_start_fw; } else ql_dbg(ql_dbg_p3p, vha, 0xb0af, "%s: Restart done!\n", __func__); ret_val = qla8044_check_cmd_peg_status(vha); if (ret_val) { ql_log(ql_log_fatal, vha, 0xb0b0, "%s: Peg not initialized!\n", __func__); ret_val = QLA_FUNCTION_FAILED; } exit_start_fw: return ret_val; } void qla8044_clear_drv_active(struct qla_hw_data *ha) { uint32_t drv_active; struct scsi_qla_host *vha = pci_get_drvdata(ha->pdev); drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); drv_active &= ~(1 << (ha->portnum)); ql_log(ql_log_info, vha, 0xb0b1, "%s(%ld): drv_active: 0x%08x\n", __func__, vha->host_no, drv_active); qla8044_wr_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX, drv_active); } /* * qla8044_device_bootstrap - Initialize device, set DEV_READY, start fw * @ha: pointer to adapter structure * * Note: IDC lock must be held upon entry **/ static int qla8044_device_bootstrap(struct scsi_qla_host *vha) { int rval = QLA_FUNCTION_FAILED; int i; uint32_t old_count = 0, count = 0; int need_reset = 0; uint32_t idc_ctrl; struct qla_hw_data *ha = vha->hw; need_reset = qla8044_need_reset(vha); if (!need_reset) { old_count = qla8044_rd_direct(vha, QLA8044_PEG_ALIVE_COUNTER_INDEX); for (i = 0; i < 10; i++) { msleep(200); count = qla8044_rd_direct(vha, QLA8044_PEG_ALIVE_COUNTER_INDEX); if (count != old_count) { rval = QLA_SUCCESS; goto dev_ready; } } qla8044_flash_lock_recovery(vha); } else { /* We are trying to perform a recovery here. */ if (ha->flags.isp82xx_fw_hung) qla8044_flash_lock_recovery(vha); } /* set to DEV_INITIALIZING */ ql_log(ql_log_info, vha, 0xb0b2, "%s: HW State: INITIALIZING\n", __func__); qla8044_wr_direct(vha, QLA8044_CRB_DEV_STATE_INDEX, QLA8XXX_DEV_INITIALIZING); qla8044_idc_unlock(ha); rval = qla8044_start_firmware(vha); qla8044_idc_lock(ha); if (rval != QLA_SUCCESS) { ql_log(ql_log_info, vha, 0xb0b3, "%s: HW State: FAILED\n", __func__); qla8044_clear_drv_active(ha); qla8044_wr_direct(vha, QLA8044_CRB_DEV_STATE_INDEX, QLA8XXX_DEV_FAILED); return rval; } /* For ISP8044, If IDC_CTRL GRACEFUL_RESET_BIT1 is set , reset it after * device goes to INIT state. */ idc_ctrl = qla8044_rd_reg(ha, QLA8044_IDC_DRV_CTRL); if (idc_ctrl & GRACEFUL_RESET_BIT1) { qla8044_wr_reg(ha, QLA8044_IDC_DRV_CTRL, (idc_ctrl & ~GRACEFUL_RESET_BIT1)); ha->fw_dumped = 0; } dev_ready: ql_log(ql_log_info, vha, 0xb0b4, "%s: HW State: READY\n", __func__); qla8044_wr_direct(vha, QLA8044_CRB_DEV_STATE_INDEX, QLA8XXX_DEV_READY); return rval; } /*-------------------------Reset Sequence Functions-----------------------*/ static void qla8044_dump_reset_seq_hdr(struct scsi_qla_host *vha) { u8 *phdr; if (!vha->reset_tmplt.buff) { ql_log(ql_log_fatal, vha, 0xb0b5, "%s: Error Invalid reset_seq_template\n", __func__); return; } phdr = vha->reset_tmplt.buff; ql_dbg(ql_dbg_p3p, vha, 0xb0b6, "Reset Template :\n\t0x%X 0x%X 0x%X 0x%X" "0x%X 0x%X 0x%X 0x%X 0x%X 0x%X\n" "\t0x%X 0x%X 0x%X 0x%X 0x%X 0x%X\n\n", *phdr, *(phdr+1), *(phdr+2), *(phdr+3), *(phdr+4), *(phdr+5), *(phdr+6), *(phdr+7), *(phdr + 8), *(phdr+9), *(phdr+10), *(phdr+11), *(phdr+12), *(phdr+13), *(phdr+14), *(phdr+15)); } /* * qla8044_reset_seq_checksum_test - Validate Reset Sequence template. * * @ha : Pointer to adapter structure * * Return Value - QLA_SUCCESS/QLA_FUNCTION_FAILED */ static int qla8044_reset_seq_checksum_test(struct scsi_qla_host *vha) { uint32_t sum = 0; uint16_t *buff = (uint16_t *)vha->reset_tmplt.buff; int u16_count = vha->reset_tmplt.hdr->size / sizeof(uint16_t); while (u16_count-- > 0) sum += *buff++; while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); /* checksum of 0 indicates a valid template */ if (~sum) { return QLA_SUCCESS; } else { ql_log(ql_log_fatal, vha, 0xb0b7, "%s: Reset seq checksum failed\n", __func__); return QLA_FUNCTION_FAILED; } } /* * qla8044_read_reset_template - Read Reset Template from Flash, validate * the template and store offsets of stop/start/init offsets in ha->reset_tmplt. * * @ha : Pointer to adapter structure */ void qla8044_read_reset_template(struct scsi_qla_host *vha) { uint8_t *p_buff; uint32_t addr, tmplt_hdr_def_size, tmplt_hdr_size; vha->reset_tmplt.seq_error = 0; vha->reset_tmplt.buff = vmalloc(QLA8044_RESTART_TEMPLATE_SIZE); if (vha->reset_tmplt.buff == NULL) { ql_log(ql_log_fatal, vha, 0xb0b8, "%s: Failed to allocate reset template resources\n", __func__); goto exit_read_reset_template; } p_buff = vha->reset_tmplt.buff; addr = QLA8044_RESET_TEMPLATE_ADDR; tmplt_hdr_def_size = sizeof(struct qla8044_reset_template_hdr) / sizeof(uint32_t); ql_dbg(ql_dbg_p3p, vha, 0xb0b9, "%s: Read template hdr size %d from Flash\n", __func__, tmplt_hdr_def_size); /* Copy template header from flash */ if (qla8044_read_flash_data(vha, p_buff, addr, tmplt_hdr_def_size)) { ql_log(ql_log_fatal, vha, 0xb0ba, "%s: Failed to read reset template\n", __func__); goto exit_read_template_error; } vha->reset_tmplt.hdr = (struct qla8044_reset_template_hdr *) vha->reset_tmplt.buff; /* Validate the template header size and signature */ tmplt_hdr_size = vha->reset_tmplt.hdr->hdr_size/sizeof(uint32_t); if ((tmplt_hdr_size != tmplt_hdr_def_size) || (vha->reset_tmplt.hdr->signature != RESET_TMPLT_HDR_SIGNATURE)) { ql_log(ql_log_fatal, vha, 0xb0bb, "%s: Template Header size invalid %d " "tmplt_hdr_def_size %d!!!\n", __func__, tmplt_hdr_size, tmplt_hdr_def_size); goto exit_read_template_error; } addr = QLA8044_RESET_TEMPLATE_ADDR + vha->reset_tmplt.hdr->hdr_size; p_buff = vha->reset_tmplt.buff + vha->reset_tmplt.hdr->hdr_size; tmplt_hdr_def_size = (vha->reset_tmplt.hdr->size - vha->reset_tmplt.hdr->hdr_size)/sizeof(uint32_t); ql_dbg(ql_dbg_p3p, vha, 0xb0bc, "%s: Read rest of the template size %d\n", __func__, vha->reset_tmplt.hdr->size); /* Copy rest of the template */ if (qla8044_read_flash_data(vha, p_buff, addr, tmplt_hdr_def_size)) { ql_log(ql_log_fatal, vha, 0xb0bd, "%s: Failed to read reset template\n", __func__); goto exit_read_template_error; } /* Integrity check */ if (qla8044_reset_seq_checksum_test(vha)) { ql_log(ql_log_fatal, vha, 0xb0be, "%s: Reset Seq checksum failed!\n", __func__); goto exit_read_template_error; } ql_dbg(ql_dbg_p3p, vha, 0xb0bf, "%s: Reset Seq checksum passed! Get stop, " "start and init seq offsets\n", __func__); /* Get STOP, START, INIT sequence offsets */ vha->reset_tmplt.init_offset = vha->reset_tmplt.buff + vha->reset_tmplt.hdr->init_seq_offset; vha->reset_tmplt.start_offset = vha->reset_tmplt.buff + vha->reset_tmplt.hdr->start_seq_offset; vha->reset_tmplt.stop_offset = vha->reset_tmplt.buff + vha->reset_tmplt.hdr->hdr_size; qla8044_dump_reset_seq_hdr(vha); goto exit_read_reset_template; exit_read_template_error: vfree(vha->reset_tmplt.buff); exit_read_reset_template: return; } void qla8044_set_idc_dontreset(struct scsi_qla_host *vha) { uint32_t idc_ctrl; struct qla_hw_data *ha = vha->hw; idc_ctrl = qla8044_rd_reg(ha, QLA8044_IDC_DRV_CTRL); idc_ctrl |= DONTRESET_BIT0; ql_dbg(ql_dbg_p3p, vha, 0xb0c0, "%s: idc_ctrl = %d\n", __func__, idc_ctrl); qla8044_wr_reg(ha, QLA8044_IDC_DRV_CTRL, idc_ctrl); } static inline void qla8044_set_rst_ready(struct scsi_qla_host *vha) { uint32_t drv_state; struct qla_hw_data *ha = vha->hw; drv_state = qla8044_rd_direct(vha, QLA8044_CRB_DRV_STATE_INDEX); /* For ISP8044, drv_active register has 1 bit per function, * shift 1 by func_num to set a bit for the function.*/ drv_state |= (1 << ha->portnum); ql_log(ql_log_info, vha, 0xb0c1, "%s(%ld): drv_state: 0x%08x\n", __func__, vha->host_no, drv_state); qla8044_wr_direct(vha, QLA8044_CRB_DRV_STATE_INDEX, drv_state); } /** * qla8044_need_reset_handler - Code to start reset sequence * @vha: pointer to adapter structure * * Note: IDC lock must be held upon entry */ static void qla8044_need_reset_handler(struct scsi_qla_host *vha) { uint32_t dev_state = 0, drv_state, drv_active; unsigned long reset_timeout; struct qla_hw_data *ha = vha->hw; ql_log(ql_log_fatal, vha, 0xb0c2, "%s: Performing ISP error recovery\n", __func__); if (vha->flags.online) { qla8044_idc_unlock(ha); qla2x00_abort_isp_cleanup(vha); ha->isp_ops->get_flash_version(vha, vha->req->ring); ha->isp_ops->nvram_config(vha); qla8044_idc_lock(ha); } dev_state = qla8044_rd_direct(vha, QLA8044_CRB_DEV_STATE_INDEX); drv_state = qla8044_rd_direct(vha, QLA8044_CRB_DRV_STATE_INDEX); drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); ql_log(ql_log_info, vha, 0xb0c5, "%s(%ld): drv_state = 0x%x, drv_active = 0x%x dev_state = 0x%x\n", __func__, vha->host_no, drv_state, drv_active, dev_state); qla8044_set_rst_ready(vha); /* wait for 10 seconds for reset ack from all functions */ reset_timeout = jiffies + (ha->fcoe_reset_timeout * HZ); do { if (time_after_eq(jiffies, reset_timeout)) { ql_log(ql_log_info, vha, 0xb0c4, "%s: Function %d: Reset Ack Timeout!, drv_state: 0x%08x, drv_active: 0x%08x\n", __func__, ha->portnum, drv_state, drv_active); break; } qla8044_idc_unlock(ha); msleep(1000); qla8044_idc_lock(ha); dev_state = qla8044_rd_direct(vha, QLA8044_CRB_DEV_STATE_INDEX); drv_state = qla8044_rd_direct(vha, QLA8044_CRB_DRV_STATE_INDEX); drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); } while (((drv_state & drv_active) != drv_active) && (dev_state == QLA8XXX_DEV_NEED_RESET)); /* Remove IDC participation of functions not acknowledging */ if (drv_state != drv_active) { ql_log(ql_log_info, vha, 0xb0c7, "%s(%ld): Function %d turning off drv_active of non-acking function 0x%x\n", __func__, vha->host_no, ha->portnum, (drv_active ^ drv_state)); drv_active = drv_active & drv_state; qla8044_wr_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX, drv_active); } else { /* * Reset owner should execute reset recovery, * if all functions acknowledged */ if ((ha->flags.nic_core_reset_owner) && (dev_state == QLA8XXX_DEV_NEED_RESET)) { ha->flags.nic_core_reset_owner = 0; qla8044_device_bootstrap(vha); return; } } /* Exit if non active function */ if (!(drv_active & (1 << ha->portnum))) { ha->flags.nic_core_reset_owner = 0; return; } /* * Execute Reset Recovery if Reset Owner or Function 7 * is the only active function */ if (ha->flags.nic_core_reset_owner || ((drv_state & drv_active) == QLA8044_FUN7_ACTIVE_INDEX)) { ha->flags.nic_core_reset_owner = 0; qla8044_device_bootstrap(vha); } } static void qla8044_set_drv_active(struct scsi_qla_host *vha) { uint32_t drv_active; struct qla_hw_data *ha = vha->hw; drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); /* For ISP8044, drv_active register has 1 bit per function, * shift 1 by func_num to set a bit for the function.*/ drv_active |= (1 << ha->portnum); ql_log(ql_log_info, vha, 0xb0c8, "%s(%ld): drv_active: 0x%08x\n", __func__, vha->host_no, drv_active); qla8044_wr_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX, drv_active); } static int qla8044_check_drv_active(struct scsi_qla_host *vha) { uint32_t drv_active; struct qla_hw_data *ha = vha->hw; drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); if (drv_active & (1 << ha->portnum)) return QLA_SUCCESS; else return QLA_TEST_FAILED; } static void qla8044_clear_idc_dontreset(struct scsi_qla_host *vha) { uint32_t idc_ctrl; struct qla_hw_data *ha = vha->hw; idc_ctrl = qla8044_rd_reg(ha, QLA8044_IDC_DRV_CTRL); idc_ctrl &= ~DONTRESET_BIT0; ql_log(ql_log_info, vha, 0xb0c9, "%s: idc_ctrl = %d\n", __func__, idc_ctrl); qla8044_wr_reg(ha, QLA8044_IDC_DRV_CTRL, idc_ctrl); } static int qla8044_set_idc_ver(struct scsi_qla_host *vha) { int idc_ver; uint32_t drv_active; int rval = QLA_SUCCESS; struct qla_hw_data *ha = vha->hw; drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); if (drv_active == (1 << ha->portnum)) { idc_ver = qla8044_rd_direct(vha, QLA8044_CRB_DRV_IDC_VERSION_INDEX); idc_ver &= (~0xFF); idc_ver |= QLA8044_IDC_VER_MAJ_VALUE; qla8044_wr_direct(vha, QLA8044_CRB_DRV_IDC_VERSION_INDEX, idc_ver); ql_log(ql_log_info, vha, 0xb0ca, "%s: IDC version updated to %d\n", __func__, idc_ver); } else { idc_ver = qla8044_rd_direct(vha, QLA8044_CRB_DRV_IDC_VERSION_INDEX); idc_ver &= 0xFF; if (QLA8044_IDC_VER_MAJ_VALUE != idc_ver) { ql_log(ql_log_info, vha, 0xb0cb, "%s: qla4xxx driver IDC version %d " "is not compatible with IDC version %d " "of other drivers!\n", __func__, QLA8044_IDC_VER_MAJ_VALUE, idc_ver); rval = QLA_FUNCTION_FAILED; goto exit_set_idc_ver; } } /* Update IDC_MINOR_VERSION */ idc_ver = qla8044_rd_reg(ha, QLA8044_CRB_IDC_VER_MINOR); idc_ver &= ~(0x03 << (ha->portnum * 2)); idc_ver |= (QLA8044_IDC_VER_MIN_VALUE << (ha->portnum * 2)); qla8044_wr_reg(ha, QLA8044_CRB_IDC_VER_MINOR, idc_ver); exit_set_idc_ver: return rval; } static int qla8044_update_idc_reg(struct scsi_qla_host *vha) { uint32_t drv_active; int rval = QLA_SUCCESS; struct qla_hw_data *ha = vha->hw; if (vha->flags.init_done) goto exit_update_idc_reg; qla8044_idc_lock(ha); qla8044_set_drv_active(vha); drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); /* If we are the first driver to load and * ql2xdontresethba is not set, clear IDC_CTRL BIT0. */ if ((drv_active == (1 << ha->portnum)) && !ql2xdontresethba) qla8044_clear_idc_dontreset(vha); rval = qla8044_set_idc_ver(vha); if (rval == QLA_FUNCTION_FAILED) qla8044_clear_drv_active(ha); qla8044_idc_unlock(ha); exit_update_idc_reg: return rval; } /** * qla8044_need_qsnt_handler - Code to start qsnt * @vha: pointer to adapter structure */ static void qla8044_need_qsnt_handler(struct scsi_qla_host *vha) { unsigned long qsnt_timeout; uint32_t drv_state, drv_active, dev_state; struct qla_hw_data *ha = vha->hw; if (vha->flags.online) qla2x00_quiesce_io(vha); else return; qla8044_set_qsnt_ready(vha); /* Wait for 30 secs for all functions to ack qsnt mode */ qsnt_timeout = jiffies + (QSNT_ACK_TOV * HZ); drv_state = qla8044_rd_direct(vha, QLA8044_CRB_DRV_STATE_INDEX); drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); /* Shift drv_active by 1 to match drv_state. As quiescent ready bit position is at bit 1 and drv active is at bit 0 */ drv_active = drv_active << 1; while (drv_state != drv_active) { if (time_after_eq(jiffies, qsnt_timeout)) { /* Other functions did not ack, changing state to * DEV_READY */ clear_bit(ISP_QUIESCE_NEEDED, &vha->dpc_flags); qla8044_wr_direct(vha, QLA8044_CRB_DEV_STATE_INDEX, QLA8XXX_DEV_READY); qla8044_clear_qsnt_ready(vha); ql_log(ql_log_info, vha, 0xb0cc, "Timeout waiting for quiescent ack!!!\n"); return; } qla8044_idc_unlock(ha); msleep(1000); qla8044_idc_lock(ha); drv_state = qla8044_rd_direct(vha, QLA8044_CRB_DRV_STATE_INDEX); drv_active = qla8044_rd_direct(vha, QLA8044_CRB_DRV_ACTIVE_INDEX); drv_active = drv_active << 1; } /* All functions have Acked. Set quiescent state */ dev_state = qla8044_rd_direct(vha, QLA8044_CRB_DEV_STATE_INDEX); if (dev_state == QLA8XXX_DEV_NEED_QUIESCENT) { qla8044_wr_direct(vha, QLA8044_CRB_DEV_STATE_INDEX, QLA8XXX_DEV_QUIESCENT); ql_log(ql_log_info, vha, 0xb0cd, "%s: HW State: QUIESCENT\n", __func__); } } /* * qla8044_device_state_handler - Adapter state machine * @ha: pointer to host adapter structure. * * Note: IDC lock must be UNLOCKED upon entry **/ int qla8044_device_state_handler(struct scsi_qla_host *vha) { uint32_t dev_state; int rval = QLA_SUCCESS; unsigned long dev_init_timeout; struct qla_hw_data *ha = vha->hw; rval = qla8044_update_idc_reg(vha); if (rval == QLA_FUNCTION_FAILED) goto exit_error; dev_state = qla8044_rd_direct(vha, QLA8044_CRB_DEV_STATE_INDEX); ql_dbg(ql_dbg_p3p, vha, 0xb0ce, "Device state is 0x%x = %s\n", dev_state, dev_state < MAX_STATES ? qdev_state(dev_state) : "Unknown"); /* wait for 30 seconds for device to go ready */ dev_init_timeout = jiffies + (ha->fcoe_dev_init_timeout * HZ); qla8044_idc_lock(ha); while (1) { if (time_after_eq(jiffies, dev_init_timeout)) { if (qla8044_check_drv_active(vha) == QLA_SUCCESS) { ql_log(ql_log_warn, vha, 0xb0cf, "%s: Device Init Failed 0x%x = %s\n", QLA2XXX_DRIVER_NAME, dev_state, dev_state < MAX_STATES ? qdev_state(dev_state) : "Unknown"); qla8044_wr_direct(vha, QLA8044_CRB_DEV_STATE_INDEX, QLA8XXX_DEV_FAILED); } } dev_state = qla8044_rd_direct(vha, QLA8044_CRB_DEV_STATE_INDEX); ql_log(ql_log_info, vha, 0xb0d0, "Device state is 0x%x = %s\n", dev_state, dev_state < MAX_STATES ? qdev_state(dev_state) : "Unknown"); /* NOTE: Make sure idc unlocked upon exit of switch statement */ switch (dev_state) { case QLA8XXX_DEV_READY: ha->flags.nic_core_reset_owner = 0; goto exit; case QLA8XXX_DEV_COLD: rval = qla8044_device_bootstrap(vha); break; case QLA8XXX_DEV_INITIALIZING: qla8044_idc_unlock(ha); msleep(1000); qla8044_idc_lock(ha); break; case QLA8XXX_DEV_NEED_RESET: /* For ISP8044, if NEED_RESET is set by any driver, * it should be honored, irrespective of IDC_CTRL * DONTRESET_BIT0 */ qla8044_need_reset_handler(vha); break; case QLA8XXX_DEV_NEED_QUIESCENT: /* idc locked/unlocked in handler */ qla8044_need_qsnt_handler(vha); /* Reset the init timeout after qsnt handler */ dev_init_timeout = jiffies + (ha->fcoe_reset_timeout * HZ); break; case QLA8XXX_DEV_QUIESCENT: ql_log(ql_log_info, vha, 0xb0d1, "HW State: QUIESCENT\n"); qla8044_idc_unlock(ha); msleep(1000); qla8044_idc_lock(ha); /* Reset the init timeout after qsnt handler */ dev_init_timeout = jiffies + (ha->fcoe_reset_timeout * HZ); break; case QLA8XXX_DEV_FAILED: ha->flags.nic_core_reset_owner = 0; qla8044_idc_unlock(ha); qla8xxx_dev_failed_handler(vha); rval = QLA_FUNCTION_FAILED; qla8044_idc_lock(ha); goto exit; default: qla8044_idc_unlock(ha); qla8xxx_dev_failed_handler(vha); rval = QLA_FUNCTION_FAILED; qla8044_idc_lock(ha); goto exit; } } exit: qla8044_idc_unlock(ha); exit_error: return rval; } /** * qla4_8xxx_check_temp - Check the ISP82XX temperature. * @vha: adapter block pointer. * * Note: The caller should not hold the idc lock. */ static int qla8044_check_temp(struct scsi_qla_host *vha) { uint32_t temp, temp_state, temp_val; int status = QLA_SUCCESS; temp = qla8044_rd_direct(vha, QLA8044_CRB_TEMP_STATE_INDEX); temp_state = qla82xx_get_temp_state(temp); temp_val = qla82xx_get_temp_val(temp); if (temp_state == QLA82XX_TEMP_PANIC) { ql_log(ql_log_warn, vha, 0xb0d2, "Device temperature %d degrees C" " exceeds maximum allowed. Hardware has been shut" " down\n", temp_val); status = QLA_FUNCTION_FAILED; return status; } else if (temp_state == QLA82XX_TEMP_WARN) { ql_log(ql_log_warn, vha, 0xb0d3, "Device temperature %d" " degrees C exceeds operating range." " Immediate action needed.\n", temp_val); } return 0; } int qla8044_read_temperature(scsi_qla_host_t *vha) { uint32_t temp; temp = qla8044_rd_direct(vha, QLA8044_CRB_TEMP_STATE_INDEX); return qla82xx_get_temp_val(temp); } /** * qla8044_check_fw_alive - Check firmware health * @vha: Pointer to host adapter structure. * * Context: Interrupt */ int qla8044_check_fw_alive(struct scsi_qla_host *vha) { uint32_t fw_heartbeat_counter; uint32_t halt_status1, halt_status2; int status = QLA_SUCCESS; fw_heartbeat_counter = qla8044_rd_direct(vha, QLA8044_PEG_ALIVE_COUNTER_INDEX); /* If PEG_ALIVE_COUNTER is 0xffffffff, AER/EEH is in progress, ignore */ if (fw_heartbeat_counter == 0xffffffff) { ql_dbg(ql_dbg_p3p, vha, 0xb0d4, "scsi%ld: %s: Device in frozen " "state, QLA82XX_PEG_ALIVE_COUNTER is 0xffffffff\n", vha->host_no, __func__); return status; } if (vha->fw_heartbeat_counter == fw_heartbeat_counter) { vha->seconds_since_last_heartbeat++; /* FW not alive after 2 seconds */ if (vha->seconds_since_last_heartbeat == 2) { vha->seconds_since_last_heartbeat = 0; halt_status1 = qla8044_rd_direct(vha, QLA8044_PEG_HALT_STATUS1_INDEX); halt_status2 = qla8044_rd_direct(vha, QLA8044_PEG_HALT_STATUS2_INDEX); ql_log(ql_log_info, vha, 0xb0d5, "scsi(%ld): %s, ISP8044 " "Dumping hw/fw registers:\n" " PEG_HALT_STATUS1: 0x%x, " "PEG_HALT_STATUS2: 0x%x,\n", vha->host_no, __func__, halt_status1, halt_status2); status = QLA_FUNCTION_FAILED; } } else vha->seconds_since_last_heartbeat = 0; vha->fw_heartbeat_counter = fw_heartbeat_counter; return status; } void qla8044_watchdog(struct scsi_qla_host *vha) { uint32_t dev_state, halt_status; int halt_status_unrecoverable = 0; struct qla_hw_data *ha = vha->hw; /* don't poll if reset is going on or FW hang in quiescent state */ if (!(test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags) || test_bit(FCOE_CTX_RESET_NEEDED, &vha->dpc_flags))) { dev_state = qla8044_rd_direct(vha, QLA8044_CRB_DEV_STATE_INDEX); if (qla8044_check_fw_alive(vha)) { ha->flags.isp82xx_fw_hung = 1; ql_log(ql_log_warn, vha, 0xb10a, "Firmware hung.\n"); qla82xx_clear_pending_mbx(vha); } if (qla8044_check_temp(vha)) { set_bit(ISP_UNRECOVERABLE, &vha->dpc_flags); ha->flags.isp82xx_fw_hung = 1; qla2xxx_wake_dpc(vha); } else if (dev_state == QLA8XXX_DEV_NEED_RESET && !test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags)) { ql_log(ql_log_info, vha, 0xb0d6, "%s: HW State: NEED RESET!\n", __func__); set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); qla2xxx_wake_dpc(vha); } else if (dev_state == QLA8XXX_DEV_NEED_QUIESCENT && !test_bit(ISP_QUIESCE_NEEDED, &vha->dpc_flags)) { ql_log(ql_log_info, vha, 0xb0d7, "%s: HW State: NEED QUIES detected!\n", __func__); set_bit(ISP_QUIESCE_NEEDED, &vha->dpc_flags); qla2xxx_wake_dpc(vha); } else { /* Check firmware health */ if (ha->flags.isp82xx_fw_hung) { halt_status = qla8044_rd_direct(vha, QLA8044_PEG_HALT_STATUS1_INDEX); if (halt_status & QLA8044_HALT_STATUS_FW_RESET) { ql_log(ql_log_fatal, vha, 0xb0d8, "%s: Firmware " "error detected device " "is being reset\n", __func__); } else if (halt_status & QLA8044_HALT_STATUS_UNRECOVERABLE) { halt_status_unrecoverable = 1; } /* Since we cannot change dev_state in interrupt * context, set appropriate DPC flag then wakeup * DPC */ if (halt_status_unrecoverable) { set_bit(ISP_UNRECOVERABLE, &vha->dpc_flags); } else { if (dev_state == QLA8XXX_DEV_QUIESCENT) { set_bit(FCOE_CTX_RESET_NEEDED, &vha->dpc_flags); ql_log(ql_log_info, vha, 0xb0d9, "%s: FW CONTEXT Reset " "needed!\n", __func__); } else { ql_log(ql_log_info, vha, 0xb0da, "%s: " "detect abort needed\n", __func__); set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); } } qla2xxx_wake_dpc(vha); } } } } static int qla8044_minidump_process_control(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr) { struct qla8044_minidump_entry_crb *crb_entry; uint32_t read_value, opcode, poll_time, addr, index; uint32_t crb_addr, rval = QLA_SUCCESS; unsigned long wtime; struct qla8044_minidump_template_hdr *tmplt_hdr; int i; struct qla_hw_data *ha = vha->hw; ql_dbg(ql_dbg_p3p, vha, 0xb0dd, "Entering fn: %s\n", __func__); tmplt_hdr = (struct qla8044_minidump_template_hdr *) ha->md_tmplt_hdr; crb_entry = (struct qla8044_minidump_entry_crb *)entry_hdr; crb_addr = crb_entry->addr; for (i = 0; i < crb_entry->op_count; i++) { opcode = crb_entry->crb_ctrl.opcode; if (opcode & QLA82XX_DBG_OPCODE_WR) { qla8044_wr_reg_indirect(vha, crb_addr, crb_entry->value_1); opcode &= ~QLA82XX_DBG_OPCODE_WR; } if (opcode & QLA82XX_DBG_OPCODE_RW) { qla8044_rd_reg_indirect(vha, crb_addr, &read_value); qla8044_wr_reg_indirect(vha, crb_addr, read_value); opcode &= ~QLA82XX_DBG_OPCODE_RW; } if (opcode & QLA82XX_DBG_OPCODE_AND) { qla8044_rd_reg_indirect(vha, crb_addr, &read_value); read_value &= crb_entry->value_2; opcode &= ~QLA82XX_DBG_OPCODE_AND; if (opcode & QLA82XX_DBG_OPCODE_OR) { read_value |= crb_entry->value_3; opcode &= ~QLA82XX_DBG_OPCODE_OR; } qla8044_wr_reg_indirect(vha, crb_addr, read_value); } if (opcode & QLA82XX_DBG_OPCODE_OR) { qla8044_rd_reg_indirect(vha, crb_addr, &read_value); read_value |= crb_entry->value_3; qla8044_wr_reg_indirect(vha, crb_addr, read_value); opcode &= ~QLA82XX_DBG_OPCODE_OR; } if (opcode & QLA82XX_DBG_OPCODE_POLL) { poll_time = crb_entry->crb_strd.poll_timeout; wtime = jiffies + poll_time; qla8044_rd_reg_indirect(vha, crb_addr, &read_value); do { if ((read_value & crb_entry->value_2) == crb_entry->value_1) { break; } else if (time_after_eq(jiffies, wtime)) { /* capturing dump failed */ rval = QLA_FUNCTION_FAILED; break; } else { qla8044_rd_reg_indirect(vha, crb_addr, &read_value); } } while (1); opcode &= ~QLA82XX_DBG_OPCODE_POLL; } if (opcode & QLA82XX_DBG_OPCODE_RDSTATE) { if (crb_entry->crb_strd.state_index_a) { index = crb_entry->crb_strd.state_index_a; addr = tmplt_hdr->saved_state_array[index]; } else { addr = crb_addr; } qla8044_rd_reg_indirect(vha, addr, &read_value); index = crb_entry->crb_ctrl.state_index_v; tmplt_hdr->saved_state_array[index] = read_value; opcode &= ~QLA82XX_DBG_OPCODE_RDSTATE; } if (opcode & QLA82XX_DBG_OPCODE_WRSTATE) { if (crb_entry->crb_strd.state_index_a) { index = crb_entry->crb_strd.state_index_a; addr = tmplt_hdr->saved_state_array[index]; } else { addr = crb_addr; } if (crb_entry->crb_ctrl.state_index_v) { index = crb_entry->crb_ctrl.state_index_v; read_value = tmplt_hdr->saved_state_array[index]; } else { read_value = crb_entry->value_1; } qla8044_wr_reg_indirect(vha, addr, read_value); opcode &= ~QLA82XX_DBG_OPCODE_WRSTATE; } if (opcode & QLA82XX_DBG_OPCODE_MDSTATE) { index = crb_entry->crb_ctrl.state_index_v; read_value = tmplt_hdr->saved_state_array[index]; read_value <<= crb_entry->crb_ctrl.shl; read_value >>= crb_entry->crb_ctrl.shr; if (crb_entry->value_2) read_value &= crb_entry->value_2; read_value |= crb_entry->value_3; read_value += crb_entry->value_1; tmplt_hdr->saved_state_array[index] = read_value; opcode &= ~QLA82XX_DBG_OPCODE_MDSTATE; } crb_addr += crb_entry->crb_strd.addr_stride; } return rval; } static void qla8044_minidump_process_rdcrb(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t r_addr, r_stride, loop_cnt, i, r_value; struct qla8044_minidump_entry_crb *crb_hdr; uint32_t *data_ptr = *d_ptr; ql_dbg(ql_dbg_p3p, vha, 0xb0de, "Entering fn: %s\n", __func__); crb_hdr = (struct qla8044_minidump_entry_crb *)entry_hdr; r_addr = crb_hdr->addr; r_stride = crb_hdr->crb_strd.addr_stride; loop_cnt = crb_hdr->op_count; for (i = 0; i < loop_cnt; i++) { qla8044_rd_reg_indirect(vha, r_addr, &r_value); *data_ptr++ = r_addr; *data_ptr++ = r_value; r_addr += r_stride; } *d_ptr = data_ptr; } static int qla8044_minidump_process_rdmem(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t r_addr, r_value, r_data; uint32_t i, j, loop_cnt; struct qla8044_minidump_entry_rdmem *m_hdr; unsigned long flags; uint32_t *data_ptr = *d_ptr; struct qla_hw_data *ha = vha->hw; ql_dbg(ql_dbg_p3p, vha, 0xb0df, "Entering fn: %s\n", __func__); m_hdr = (struct qla8044_minidump_entry_rdmem *)entry_hdr; r_addr = m_hdr->read_addr; loop_cnt = m_hdr->read_data_size/16; ql_dbg(ql_dbg_p3p, vha, 0xb0f0, "[%s]: Read addr: 0x%x, read_data_size: 0x%x\n", __func__, r_addr, m_hdr->read_data_size); if (r_addr & 0xf) { ql_dbg(ql_dbg_p3p, vha, 0xb0f1, "[%s]: Read addr 0x%x not 16 bytes aligned\n", __func__, r_addr); return QLA_FUNCTION_FAILED; } if (m_hdr->read_data_size % 16) { ql_dbg(ql_dbg_p3p, vha, 0xb0f2, "[%s]: Read data[0x%x] not multiple of 16 bytes\n", __func__, m_hdr->read_data_size); return QLA_FUNCTION_FAILED; } ql_dbg(ql_dbg_p3p, vha, 0xb0f3, "[%s]: rdmem_addr: 0x%x, read_data_size: 0x%x, loop_cnt: 0x%x\n", __func__, r_addr, m_hdr->read_data_size, loop_cnt); write_lock_irqsave(&ha->hw_lock, flags); for (i = 0; i < loop_cnt; i++) { qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_ADDR_LO, r_addr); r_value = 0; qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_ADDR_HI, r_value); r_value = MIU_TA_CTL_ENABLE; qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_CTRL, r_value); r_value = MIU_TA_CTL_START_ENABLE; qla8044_wr_reg_indirect(vha, MD_MIU_TEST_AGT_CTRL, r_value); for (j = 0; j < MAX_CTL_CHECK; j++) { qla8044_rd_reg_indirect(vha, MD_MIU_TEST_AGT_CTRL, &r_value); if ((r_value & MIU_TA_CTL_BUSY) == 0) break; } if (j >= MAX_CTL_CHECK) { write_unlock_irqrestore(&ha->hw_lock, flags); return QLA_SUCCESS; } for (j = 0; j < 4; j++) { qla8044_rd_reg_indirect(vha, MD_MIU_TEST_AGT_RDDATA[j], &r_data); *data_ptr++ = r_data; } r_addr += 16; } write_unlock_irqrestore(&ha->hw_lock, flags); ql_dbg(ql_dbg_p3p, vha, 0xb0f4, "Leaving fn: %s datacount: 0x%x\n", __func__, (loop_cnt * 16)); *d_ptr = data_ptr; return QLA_SUCCESS; } /* ISP83xx flash read for _RDROM _BOARD */ static uint32_t qla8044_minidump_process_rdrom(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t fl_addr, u32_count, rval; struct qla8044_minidump_entry_rdrom *rom_hdr; uint32_t *data_ptr = *d_ptr; rom_hdr = (struct qla8044_minidump_entry_rdrom *)entry_hdr; fl_addr = rom_hdr->read_addr; u32_count = (rom_hdr->read_data_size)/sizeof(uint32_t); ql_dbg(ql_dbg_p3p, vha, 0xb0f5, "[%s]: fl_addr: 0x%x, count: 0x%x\n", __func__, fl_addr, u32_count); rval = qla8044_lockless_flash_read_u32(vha, fl_addr, (u8 *)(data_ptr), u32_count); if (rval != QLA_SUCCESS) { ql_log(ql_log_fatal, vha, 0xb0f6, "%s: Flash Read Error,Count=%d\n", __func__, u32_count); return QLA_FUNCTION_FAILED; } else { data_ptr += u32_count; *d_ptr = data_ptr; return QLA_SUCCESS; } } static void qla8044_mark_entry_skipped(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, int index) { entry_hdr->d_ctrl.driver_flags |= QLA82XX_DBG_SKIPPED_FLAG; ql_log(ql_log_info, vha, 0xb0f7, "scsi(%ld): Skipping entry[%d]: ETYPE[0x%x]-ELEVEL[0x%x]\n", vha->host_no, index, entry_hdr->entry_type, entry_hdr->d_ctrl.entry_capture_mask); } static int qla8044_minidump_process_l2tag(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t addr, r_addr, c_addr, t_r_addr; uint32_t i, k, loop_count, t_value, r_cnt, r_value; unsigned long p_wait, w_time, p_mask; uint32_t c_value_w, c_value_r; struct qla8044_minidump_entry_cache *cache_hdr; int rval = QLA_FUNCTION_FAILED; uint32_t *data_ptr = *d_ptr; ql_dbg(ql_dbg_p3p, vha, 0xb0f8, "Entering fn: %s\n", __func__); cache_hdr = (struct qla8044_minidump_entry_cache *)entry_hdr; loop_count = cache_hdr->op_count; r_addr = cache_hdr->read_addr; c_addr = cache_hdr->control_addr; c_value_w = cache_hdr->cache_ctrl.write_value; t_r_addr = cache_hdr->tag_reg_addr; t_value = cache_hdr->addr_ctrl.init_tag_value; r_cnt = cache_hdr->read_ctrl.read_addr_cnt; p_wait = cache_hdr->cache_ctrl.poll_wait; p_mask = cache_hdr->cache_ctrl.poll_mask; for (i = 0; i < loop_count; i++) { qla8044_wr_reg_indirect(vha, t_r_addr, t_value); if (c_value_w) qla8044_wr_reg_indirect(vha, c_addr, c_value_w); if (p_mask) { w_time = jiffies + p_wait; do { qla8044_rd_reg_indirect(vha, c_addr, &c_value_r); if ((c_value_r & p_mask) == 0) { break; } else if (time_after_eq(jiffies, w_time)) { /* capturing dump failed */ return rval; } } while (1); } addr = r_addr; for (k = 0; k < r_cnt; k++) { qla8044_rd_reg_indirect(vha, addr, &r_value); *data_ptr++ = r_value; addr += cache_hdr->read_ctrl.read_addr_stride; } t_value += cache_hdr->addr_ctrl.tag_value_stride; } *d_ptr = data_ptr; return QLA_SUCCESS; } static void qla8044_minidump_process_l1cache(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t addr, r_addr, c_addr, t_r_addr; uint32_t i, k, loop_count, t_value, r_cnt, r_value; uint32_t c_value_w; struct qla8044_minidump_entry_cache *cache_hdr; uint32_t *data_ptr = *d_ptr; cache_hdr = (struct qla8044_minidump_entry_cache *)entry_hdr; loop_count = cache_hdr->op_count; r_addr = cache_hdr->read_addr; c_addr = cache_hdr->control_addr; c_value_w = cache_hdr->cache_ctrl.write_value; t_r_addr = cache_hdr->tag_reg_addr; t_value = cache_hdr->addr_ctrl.init_tag_value; r_cnt = cache_hdr->read_ctrl.read_addr_cnt; for (i = 0; i < loop_count; i++) { qla8044_wr_reg_indirect(vha, t_r_addr, t_value); qla8044_wr_reg_indirect(vha, c_addr, c_value_w); addr = r_addr; for (k = 0; k < r_cnt; k++) { qla8044_rd_reg_indirect(vha, addr, &r_value); *data_ptr++ = r_value; addr += cache_hdr->read_ctrl.read_addr_stride; } t_value += cache_hdr->addr_ctrl.tag_value_stride; } *d_ptr = data_ptr; } static void qla8044_minidump_process_rdocm(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t r_addr, r_stride, loop_cnt, i, r_value; struct qla8044_minidump_entry_rdocm *ocm_hdr; uint32_t *data_ptr = *d_ptr; struct qla_hw_data *ha = vha->hw; ql_dbg(ql_dbg_p3p, vha, 0xb0f9, "Entering fn: %s\n", __func__); ocm_hdr = (struct qla8044_minidump_entry_rdocm *)entry_hdr; r_addr = ocm_hdr->read_addr; r_stride = ocm_hdr->read_addr_stride; loop_cnt = ocm_hdr->op_count; ql_dbg(ql_dbg_p3p, vha, 0xb0fa, "[%s]: r_addr: 0x%x, r_stride: 0x%x, loop_cnt: 0x%x\n", __func__, r_addr, r_stride, loop_cnt); for (i = 0; i < loop_cnt; i++) { r_value = readl((void __iomem *)(r_addr + ha->nx_pcibase)); *data_ptr++ = r_value; r_addr += r_stride; } ql_dbg(ql_dbg_p3p, vha, 0xb0fb, "Leaving fn: %s datacount: 0x%lx\n", __func__, (long unsigned int) (loop_cnt * sizeof(uint32_t))); *d_ptr = data_ptr; } static void qla8044_minidump_process_rdmux(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t r_addr, s_stride, s_addr, s_value, loop_cnt, i, r_value; struct qla8044_minidump_entry_mux *mux_hdr; uint32_t *data_ptr = *d_ptr; ql_dbg(ql_dbg_p3p, vha, 0xb0fc, "Entering fn: %s\n", __func__); mux_hdr = (struct qla8044_minidump_entry_mux *)entry_hdr; r_addr = mux_hdr->read_addr; s_addr = mux_hdr->select_addr; s_stride = mux_hdr->select_value_stride; s_value = mux_hdr->select_value; loop_cnt = mux_hdr->op_count; for (i = 0; i < loop_cnt; i++) { qla8044_wr_reg_indirect(vha, s_addr, s_value); qla8044_rd_reg_indirect(vha, r_addr, &r_value); *data_ptr++ = s_value; *data_ptr++ = r_value; s_value += s_stride; } *d_ptr = data_ptr; } static void qla8044_minidump_process_queue(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t s_addr, r_addr; uint32_t r_stride, r_value, r_cnt, qid = 0; uint32_t i, k, loop_cnt; struct qla8044_minidump_entry_queue *q_hdr; uint32_t *data_ptr = *d_ptr; ql_dbg(ql_dbg_p3p, vha, 0xb0fd, "Entering fn: %s\n", __func__); q_hdr = (struct qla8044_minidump_entry_queue *)entry_hdr; s_addr = q_hdr->select_addr; r_cnt = q_hdr->rd_strd.read_addr_cnt; r_stride = q_hdr->rd_strd.read_addr_stride; loop_cnt = q_hdr->op_count; for (i = 0; i < loop_cnt; i++) { qla8044_wr_reg_indirect(vha, s_addr, qid); r_addr = q_hdr->read_addr; for (k = 0; k < r_cnt; k++) { qla8044_rd_reg_indirect(vha, r_addr, &r_value); *data_ptr++ = r_value; r_addr += r_stride; } qid += q_hdr->q_strd.queue_id_stride; } *d_ptr = data_ptr; } /* ISP83xx functions to process new minidump entries... */ static uint32_t qla8044_minidump_process_pollrd(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t r_addr, s_addr, s_value, r_value, poll_wait, poll_mask; uint16_t s_stride, i; struct qla8044_minidump_entry_pollrd *pollrd_hdr; uint32_t *data_ptr = *d_ptr; pollrd_hdr = (struct qla8044_minidump_entry_pollrd *) entry_hdr; s_addr = pollrd_hdr->select_addr; r_addr = pollrd_hdr->read_addr; s_value = pollrd_hdr->select_value; s_stride = pollrd_hdr->select_value_stride; poll_wait = pollrd_hdr->poll_wait; poll_mask = pollrd_hdr->poll_mask; for (i = 0; i < pollrd_hdr->op_count; i++) { qla8044_wr_reg_indirect(vha, s_addr, s_value); poll_wait = pollrd_hdr->poll_wait; while (1) { qla8044_rd_reg_indirect(vha, s_addr, &r_value); if ((r_value & poll_mask) != 0) { break; } else { usleep_range(1000, 1100); if (--poll_wait == 0) { ql_log(ql_log_fatal, vha, 0xb0fe, "%s: TIMEOUT\n", __func__); goto error; } } } qla8044_rd_reg_indirect(vha, r_addr, &r_value); *data_ptr++ = s_value; *data_ptr++ = r_value; s_value += s_stride; } *d_ptr = data_ptr; return QLA_SUCCESS; error: return QLA_FUNCTION_FAILED; } static void qla8044_minidump_process_rdmux2(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t sel_val1, sel_val2, t_sel_val, data, i; uint32_t sel_addr1, sel_addr2, sel_val_mask, read_addr; struct qla8044_minidump_entry_rdmux2 *rdmux2_hdr; uint32_t *data_ptr = *d_ptr; rdmux2_hdr = (struct qla8044_minidump_entry_rdmux2 *) entry_hdr; sel_val1 = rdmux2_hdr->select_value_1; sel_val2 = rdmux2_hdr->select_value_2; sel_addr1 = rdmux2_hdr->select_addr_1; sel_addr2 = rdmux2_hdr->select_addr_2; sel_val_mask = rdmux2_hdr->select_value_mask; read_addr = rdmux2_hdr->read_addr; for (i = 0; i < rdmux2_hdr->op_count; i++) { qla8044_wr_reg_indirect(vha, sel_addr1, sel_val1); t_sel_val = sel_val1 & sel_val_mask; *data_ptr++ = t_sel_val; qla8044_wr_reg_indirect(vha, sel_addr2, t_sel_val); qla8044_rd_reg_indirect(vha, read_addr, &data); *data_ptr++ = data; qla8044_wr_reg_indirect(vha, sel_addr1, sel_val2); t_sel_val = sel_val2 & sel_val_mask; *data_ptr++ = t_sel_val; qla8044_wr_reg_indirect(vha, sel_addr2, t_sel_val); qla8044_rd_reg_indirect(vha, read_addr, &data); *data_ptr++ = data; sel_val1 += rdmux2_hdr->select_value_stride; sel_val2 += rdmux2_hdr->select_value_stride; } *d_ptr = data_ptr; } static uint32_t qla8044_minidump_process_pollrdmwr(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t poll_wait, poll_mask, r_value, data; uint32_t addr_1, addr_2, value_1, value_2; struct qla8044_minidump_entry_pollrdmwr *poll_hdr; uint32_t *data_ptr = *d_ptr; poll_hdr = (struct qla8044_minidump_entry_pollrdmwr *) entry_hdr; addr_1 = poll_hdr->addr_1; addr_2 = poll_hdr->addr_2; value_1 = poll_hdr->value_1; value_2 = poll_hdr->value_2; poll_mask = poll_hdr->poll_mask; qla8044_wr_reg_indirect(vha, addr_1, value_1); poll_wait = poll_hdr->poll_wait; while (1) { qla8044_rd_reg_indirect(vha, addr_1, &r_value); if ((r_value & poll_mask) != 0) { break; } else { usleep_range(1000, 1100); if (--poll_wait == 0) { ql_log(ql_log_fatal, vha, 0xb0ff, "%s: TIMEOUT\n", __func__); goto error; } } } qla8044_rd_reg_indirect(vha, addr_2, &data); data &= poll_hdr->modify_mask; qla8044_wr_reg_indirect(vha, addr_2, data); qla8044_wr_reg_indirect(vha, addr_1, value_2); poll_wait = poll_hdr->poll_wait; while (1) { qla8044_rd_reg_indirect(vha, addr_1, &r_value); if ((r_value & poll_mask) != 0) { break; } else { usleep_range(1000, 1100); if (--poll_wait == 0) { ql_log(ql_log_fatal, vha, 0xb100, "%s: TIMEOUT2\n", __func__); goto error; } } } *data_ptr++ = addr_2; *data_ptr++ = data; *d_ptr = data_ptr; return QLA_SUCCESS; error: return QLA_FUNCTION_FAILED; } #define ISP8044_PEX_DMA_ENGINE_INDEX 8 #define ISP8044_PEX_DMA_BASE_ADDRESS 0x77320000 #define ISP8044_PEX_DMA_NUM_OFFSET 0x10000UL #define ISP8044_PEX_DMA_CMD_ADDR_LOW 0x0 #define ISP8044_PEX_DMA_CMD_ADDR_HIGH 0x04 #define ISP8044_PEX_DMA_CMD_STS_AND_CNTRL 0x08 #define ISP8044_PEX_DMA_READ_SIZE (16 * 1024) #define ISP8044_PEX_DMA_MAX_WAIT (100 * 100) /* Max wait of 100 msecs */ static int qla8044_check_dma_engine_state(struct scsi_qla_host *vha) { struct qla_hw_data *ha = vha->hw; int rval = QLA_SUCCESS; uint32_t dma_eng_num = 0, cmd_sts_and_cntrl = 0; uint64_t dma_base_addr = 0; struct qla8044_minidump_template_hdr *tmplt_hdr = NULL; tmplt_hdr = ha->md_tmplt_hdr; dma_eng_num = tmplt_hdr->saved_state_array[ISP8044_PEX_DMA_ENGINE_INDEX]; dma_base_addr = ISP8044_PEX_DMA_BASE_ADDRESS + (dma_eng_num * ISP8044_PEX_DMA_NUM_OFFSET); /* Read the pex-dma's command-status-and-control register. */ rval = qla8044_rd_reg_indirect(vha, (dma_base_addr + ISP8044_PEX_DMA_CMD_STS_AND_CNTRL), &cmd_sts_and_cntrl); if (rval) return QLA_FUNCTION_FAILED; /* Check if requested pex-dma engine is available. */ if (cmd_sts_and_cntrl & BIT_31) return QLA_SUCCESS; return QLA_FUNCTION_FAILED; } static int qla8044_start_pex_dma(struct scsi_qla_host *vha, struct qla8044_minidump_entry_rdmem_pex_dma *m_hdr) { struct qla_hw_data *ha = vha->hw; int rval = QLA_SUCCESS, wait = 0; uint32_t dma_eng_num = 0, cmd_sts_and_cntrl = 0; uint64_t dma_base_addr = 0; struct qla8044_minidump_template_hdr *tmplt_hdr = NULL; tmplt_hdr = ha->md_tmplt_hdr; dma_eng_num = tmplt_hdr->saved_state_array[ISP8044_PEX_DMA_ENGINE_INDEX]; dma_base_addr = ISP8044_PEX_DMA_BASE_ADDRESS + (dma_eng_num * ISP8044_PEX_DMA_NUM_OFFSET); rval = qla8044_wr_reg_indirect(vha, dma_base_addr + ISP8044_PEX_DMA_CMD_ADDR_LOW, m_hdr->desc_card_addr); if (rval) goto error_exit; rval = qla8044_wr_reg_indirect(vha, dma_base_addr + ISP8044_PEX_DMA_CMD_ADDR_HIGH, 0); if (rval) goto error_exit; rval = qla8044_wr_reg_indirect(vha, dma_base_addr + ISP8044_PEX_DMA_CMD_STS_AND_CNTRL, m_hdr->start_dma_cmd); if (rval) goto error_exit; /* Wait for dma operation to complete. */ for (wait = 0; wait < ISP8044_PEX_DMA_MAX_WAIT; wait++) { rval = qla8044_rd_reg_indirect(vha, (dma_base_addr + ISP8044_PEX_DMA_CMD_STS_AND_CNTRL), &cmd_sts_and_cntrl); if (rval) goto error_exit; if ((cmd_sts_and_cntrl & BIT_1) == 0) break; udelay(10); } /* Wait a max of 100 ms, otherwise fallback to rdmem entry read */ if (wait >= ISP8044_PEX_DMA_MAX_WAIT) { rval = QLA_FUNCTION_FAILED; goto error_exit; } error_exit: return rval; } static int qla8044_minidump_pex_dma_read(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { struct qla_hw_data *ha = vha->hw; int rval = QLA_SUCCESS; struct qla8044_minidump_entry_rdmem_pex_dma *m_hdr = NULL; uint32_t chunk_size, read_size; uint8_t *data_ptr = (uint8_t *)*d_ptr; void *rdmem_buffer = NULL; dma_addr_t rdmem_dma; struct qla8044_pex_dma_descriptor dma_desc; rval = qla8044_check_dma_engine_state(vha); if (rval != QLA_SUCCESS) { ql_dbg(ql_dbg_p3p, vha, 0xb147, "DMA engine not available. Fallback to rdmem-read.\n"); return QLA_FUNCTION_FAILED; } m_hdr = (void *)entry_hdr; rdmem_buffer = dma_alloc_coherent(&ha->pdev->dev, ISP8044_PEX_DMA_READ_SIZE, &rdmem_dma, GFP_KERNEL); if (!rdmem_buffer) { ql_dbg(ql_dbg_p3p, vha, 0xb148, "Unable to allocate rdmem dma buffer\n"); return QLA_FUNCTION_FAILED; } /* Prepare pex-dma descriptor to be written to MS memory. */ /* dma-desc-cmd layout: * 0-3: dma-desc-cmd 0-3 * 4-7: pcid function number * 8-15: dma-desc-cmd 8-15 * dma_bus_addr: dma buffer address * cmd.read_data_size: amount of data-chunk to be read. */ dma_desc.cmd.dma_desc_cmd = (m_hdr->dma_desc_cmd & 0xff0f); dma_desc.cmd.dma_desc_cmd |= ((PCI_FUNC(ha->pdev->devfn) & 0xf) << 0x4); dma_desc.dma_bus_addr = rdmem_dma; dma_desc.cmd.read_data_size = chunk_size = ISP8044_PEX_DMA_READ_SIZE; read_size = 0; /* * Perform rdmem operation using pex-dma. * Prepare dma in chunks of ISP8044_PEX_DMA_READ_SIZE. */ while (read_size < m_hdr->read_data_size) { if (m_hdr->read_data_size - read_size < ISP8044_PEX_DMA_READ_SIZE) { chunk_size = (m_hdr->read_data_size - read_size); dma_desc.cmd.read_data_size = chunk_size; } dma_desc.src_addr = m_hdr->read_addr + read_size; /* Prepare: Write pex-dma descriptor to MS memory. */ rval = qla8044_ms_mem_write_128b(vha, m_hdr->desc_card_addr, (void *)&dma_desc, (sizeof(struct qla8044_pex_dma_descriptor)/16)); if (rval) { ql_log(ql_log_warn, vha, 0xb14a, "%s: Error writing rdmem-dma-init to MS !!!\n", __func__); goto error_exit; } ql_dbg(ql_dbg_p3p, vha, 0xb14b, "%s: Dma-descriptor: Instruct for rdmem dma " "(chunk_size 0x%x).\n", __func__, chunk_size); /* Execute: Start pex-dma operation. */ rval = qla8044_start_pex_dma(vha, m_hdr); if (rval) goto error_exit; memcpy(data_ptr, rdmem_buffer, chunk_size); data_ptr += chunk_size; read_size += chunk_size; } *d_ptr = (void *)data_ptr; error_exit: if (rdmem_buffer) dma_free_coherent(&ha->pdev->dev, ISP8044_PEX_DMA_READ_SIZE, rdmem_buffer, rdmem_dma); return rval; } static uint32_t qla8044_minidump_process_rddfe(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { int loop_cnt; uint32_t addr1, addr2, value, data, temp, wrVal; uint8_t stride, stride2; uint16_t count; uint32_t poll, mask, modify_mask; uint32_t wait_count = 0; uint32_t *data_ptr = *d_ptr; struct qla8044_minidump_entry_rddfe *rddfe; rddfe = (struct qla8044_minidump_entry_rddfe *) entry_hdr; addr1 = rddfe->addr_1; value = rddfe->value; stride = rddfe->stride; stride2 = rddfe->stride2; count = rddfe->count; poll = rddfe->poll; mask = rddfe->mask; modify_mask = rddfe->modify_mask; addr2 = addr1 + stride; for (loop_cnt = 0x0; loop_cnt < count; loop_cnt++) { qla8044_wr_reg_indirect(vha, addr1, (0x40000000 | value)); wait_count = 0; while (wait_count < poll) { qla8044_rd_reg_indirect(vha, addr1, &temp); if ((temp & mask) != 0) break; wait_count++; } if (wait_count == poll) { ql_log(ql_log_warn, vha, 0xb153, "%s: TIMEOUT\n", __func__); goto error; } else { qla8044_rd_reg_indirect(vha, addr2, &temp); temp = temp & modify_mask; temp = (temp | ((loop_cnt << 16) | loop_cnt)); wrVal = ((temp << 16) | temp); qla8044_wr_reg_indirect(vha, addr2, wrVal); qla8044_wr_reg_indirect(vha, addr1, value); wait_count = 0; while (wait_count < poll) { qla8044_rd_reg_indirect(vha, addr1, &temp); if ((temp & mask) != 0) break; wait_count++; } if (wait_count == poll) { ql_log(ql_log_warn, vha, 0xb154, "%s: TIMEOUT\n", __func__); goto error; } qla8044_wr_reg_indirect(vha, addr1, ((0x40000000 | value) + stride2)); wait_count = 0; while (wait_count < poll) { qla8044_rd_reg_indirect(vha, addr1, &temp); if ((temp & mask) != 0) break; wait_count++; } if (wait_count == poll) { ql_log(ql_log_warn, vha, 0xb155, "%s: TIMEOUT\n", __func__); goto error; } qla8044_rd_reg_indirect(vha, addr2, &data); *data_ptr++ = wrVal; *data_ptr++ = data; } } *d_ptr = data_ptr; return QLA_SUCCESS; error: return -1; } static uint32_t qla8044_minidump_process_rdmdio(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { int ret = 0; uint32_t addr1, addr2, value1, value2, data, selVal; uint8_t stride1, stride2; uint32_t addr3, addr4, addr5, addr6, addr7; uint16_t count, loop_cnt; uint32_t mask; uint32_t *data_ptr = *d_ptr; struct qla8044_minidump_entry_rdmdio *rdmdio; rdmdio = (struct qla8044_minidump_entry_rdmdio *) entry_hdr; addr1 = rdmdio->addr_1; addr2 = rdmdio->addr_2; value1 = rdmdio->value_1; stride1 = rdmdio->stride_1; stride2 = rdmdio->stride_2; count = rdmdio->count; mask = rdmdio->mask; value2 = rdmdio->value_2; addr3 = addr1 + stride1; for (loop_cnt = 0; loop_cnt < count; loop_cnt++) { ret = qla8044_poll_wait_ipmdio_bus_idle(vha, addr1, addr2, addr3, mask); if (ret == -1) goto error; addr4 = addr2 - stride1; ret = qla8044_ipmdio_wr_reg(vha, addr1, addr3, mask, addr4, value2); if (ret == -1) goto error; addr5 = addr2 - (2 * stride1); ret = qla8044_ipmdio_wr_reg(vha, addr1, addr3, mask, addr5, value1); if (ret == -1) goto error; addr6 = addr2 - (3 * stride1); ret = qla8044_ipmdio_wr_reg(vha, addr1, addr3, mask, addr6, 0x2); if (ret == -1) goto error; ret = qla8044_poll_wait_ipmdio_bus_idle(vha, addr1, addr2, addr3, mask); if (ret == -1) goto error; addr7 = addr2 - (4 * stride1); data = qla8044_ipmdio_rd_reg(vha, addr1, addr3, mask, addr7); if (data == -1) goto error; selVal = (value2 << 18) | (value1 << 2) | 2; stride2 = rdmdio->stride_2; *data_ptr++ = selVal; *data_ptr++ = data; value1 = value1 + stride2; *d_ptr = data_ptr; } return 0; error: return -1; } static uint32_t qla8044_minidump_process_pollwr(struct scsi_qla_host *vha, struct qla8044_minidump_entry_hdr *entry_hdr, uint32_t **d_ptr) { uint32_t addr1, addr2, value1, value2, poll, r_value; uint32_t wait_count = 0; struct qla8044_minidump_entry_pollwr *pollwr_hdr; pollwr_hdr = (struct qla8044_minidump_entry_pollwr *)entry_hdr; addr1 = pollwr_hdr->addr_1; addr2 = pollwr_hdr->addr_2; value1 = pollwr_hdr->value_1; value2 = pollwr_hdr->value_2; poll = pollwr_hdr->poll; while (wait_count < poll) { qla8044_rd_reg_indirect(vha, addr1, &r_value); if ((r_value & poll) != 0) break; wait_count++; } if (wait_count == poll) { ql_log(ql_log_warn, vha, 0xb156, "%s: TIMEOUT\n", __func__); goto error; } qla8044_wr_reg_indirect(vha, addr2, value2); qla8044_wr_reg_indirect(vha, addr1, value1); wait_count = 0; while (wait_count < poll) { qla8044_rd_reg_indirect(vha, addr1, &r_value); if ((r_value & poll) != 0) break; wait_count++; } return QLA_SUCCESS; error: return -1; } /* * * qla8044_collect_md_data - Retrieve firmware minidump data. * @ha: pointer to adapter structure **/ int qla8044_collect_md_data(struct scsi_qla_host *vha) { int num_entry_hdr = 0; struct qla8044_minidump_entry_hdr *entry_hdr; struct qla8044_minidump_template_hdr *tmplt_hdr; uint32_t *data_ptr; uint32_t data_collected = 0, f_capture_mask; int i, rval = QLA_FUNCTION_FAILED; uint64_t now; uint32_t timestamp, idc_control; struct qla_hw_data *ha = vha->hw; if (!ha->md_dump) { ql_log(ql_log_info, vha, 0xb101, "%s(%ld) No buffer to dump\n", __func__, vha->host_no); return rval; } if (ha->fw_dumped) { ql_log(ql_log_warn, vha, 0xb10d, "Firmware has been previously dumped (%p) " "-- ignoring request.\n", ha->fw_dump); goto md_failed; } ha->fw_dumped = 0; if (!ha->md_tmplt_hdr || !ha->md_dump) { ql_log(ql_log_warn, vha, 0xb10e, "Memory not allocated for minidump capture\n"); goto md_failed; } qla8044_idc_lock(ha); idc_control = qla8044_rd_reg(ha, QLA8044_IDC_DRV_CTRL); if (idc_control & GRACEFUL_RESET_BIT1) { ql_log(ql_log_warn, vha, 0xb112, "Forced reset from application, " "ignore minidump capture\n"); qla8044_wr_reg(ha, QLA8044_IDC_DRV_CTRL, (idc_control & ~GRACEFUL_RESET_BIT1)); qla8044_idc_unlock(ha); goto md_failed; } qla8044_idc_unlock(ha); if (qla82xx_validate_template_chksum(vha)) { ql_log(ql_log_info, vha, 0xb109, "Template checksum validation error\n"); goto md_failed; } tmplt_hdr = (struct qla8044_minidump_template_hdr *) ha->md_tmplt_hdr; data_ptr = (uint32_t *)((uint8_t *)ha->md_dump); num_entry_hdr = tmplt_hdr->num_of_entries; ql_dbg(ql_dbg_p3p, vha, 0xb11a, "Capture Mask obtained: 0x%x\n", tmplt_hdr->capture_debug_level); f_capture_mask = tmplt_hdr->capture_debug_level & 0xFF; /* Validate whether required debug level is set */ if ((f_capture_mask & 0x3) != 0x3) { ql_log(ql_log_warn, vha, 0xb10f, "Minimum required capture mask[0x%x] level not set\n", f_capture_mask); } tmplt_hdr->driver_capture_mask = ql2xmdcapmask; ql_log(ql_log_info, vha, 0xb102, "[%s]: starting data ptr: %p\n", __func__, data_ptr); ql_log(ql_log_info, vha, 0xb10b, "[%s]: no of entry headers in Template: 0x%x\n", __func__, num_entry_hdr); ql_log(ql_log_info, vha, 0xb10c, "[%s]: Total_data_size 0x%x, %d obtained\n", __func__, ha->md_dump_size, ha->md_dump_size); /* Update current timestamp before taking dump */ now = get_jiffies_64(); timestamp = (u32)(jiffies_to_msecs(now) / 1000); tmplt_hdr->driver_timestamp = timestamp; entry_hdr = (struct qla8044_minidump_entry_hdr *) (((uint8_t *)ha->md_tmplt_hdr) + tmplt_hdr->first_entry_offset); tmplt_hdr->saved_state_array[QLA8044_SS_OCM_WNDREG_INDEX] = tmplt_hdr->ocm_window_reg[ha->portnum]; /* Walk through the entry headers - validate/perform required action */ for (i = 0; i < num_entry_hdr; i++) { if (data_collected > ha->md_dump_size) { ql_log(ql_log_info, vha, 0xb103, "Data collected: [0x%x], " "Total Dump size: [0x%x]\n", data_collected, ha->md_dump_size); return rval; } if (!(entry_hdr->d_ctrl.entry_capture_mask & ql2xmdcapmask)) { entry_hdr->d_ctrl.driver_flags |= QLA82XX_DBG_SKIPPED_FLAG; goto skip_nxt_entry; } ql_dbg(ql_dbg_p3p, vha, 0xb104, "Data collected: [0x%x], Dump size left:[0x%x]\n", data_collected, (ha->md_dump_size - data_collected)); /* Decode the entry type and take required action to capture * debug data */ switch (entry_hdr->entry_type) { case QLA82XX_RDEND: qla8044_mark_entry_skipped(vha, entry_hdr, i); break; case QLA82XX_CNTRL: rval = qla8044_minidump_process_control(vha, entry_hdr); if (rval != QLA_SUCCESS) { qla8044_mark_entry_skipped(vha, entry_hdr, i); goto md_failed; } break; case QLA82XX_RDCRB: qla8044_minidump_process_rdcrb(vha, entry_hdr, &data_ptr); break; case QLA82XX_RDMEM: rval = qla8044_minidump_pex_dma_read(vha, entry_hdr, &data_ptr); if (rval != QLA_SUCCESS) { rval = qla8044_minidump_process_rdmem(vha, entry_hdr, &data_ptr); if (rval != QLA_SUCCESS) { qla8044_mark_entry_skipped(vha, entry_hdr, i); goto md_failed; } } break; case QLA82XX_BOARD: case QLA82XX_RDROM: rval = qla8044_minidump_process_rdrom(vha, entry_hdr, &data_ptr); if (rval != QLA_SUCCESS) { qla8044_mark_entry_skipped(vha, entry_hdr, i); } break; case QLA82XX_L2DTG: case QLA82XX_L2ITG: case QLA82XX_L2DAT: case QLA82XX_L2INS: rval = qla8044_minidump_process_l2tag(vha, entry_hdr, &data_ptr); if (rval != QLA_SUCCESS) { qla8044_mark_entry_skipped(vha, entry_hdr, i); goto md_failed; } break; case QLA8044_L1DTG: case QLA8044_L1ITG: case QLA82XX_L1DAT: case QLA82XX_L1INS: qla8044_minidump_process_l1cache(vha, entry_hdr, &data_ptr); break; case QLA82XX_RDOCM: qla8044_minidump_process_rdocm(vha, entry_hdr, &data_ptr); break; case QLA82XX_RDMUX: qla8044_minidump_process_rdmux(vha, entry_hdr, &data_ptr); break; case QLA82XX_QUEUE: qla8044_minidump_process_queue(vha, entry_hdr, &data_ptr); break; case QLA8044_POLLRD: rval = qla8044_minidump_process_pollrd(vha, entry_hdr, &data_ptr); if (rval != QLA_SUCCESS) qla8044_mark_entry_skipped(vha, entry_hdr, i); break; case QLA8044_RDMUX2: qla8044_minidump_process_rdmux2(vha, entry_hdr, &data_ptr); break; case QLA8044_POLLRDMWR: rval = qla8044_minidump_process_pollrdmwr(vha, entry_hdr, &data_ptr); if (rval != QLA_SUCCESS) qla8044_mark_entry_skipped(vha, entry_hdr, i); break; case QLA8044_RDDFE: rval = qla8044_minidump_process_rddfe(vha, entry_hdr, &data_ptr); if (rval != QLA_SUCCESS) qla8044_mark_entry_skipped(vha, entry_hdr, i); break; case QLA8044_RDMDIO: rval = qla8044_minidump_process_rdmdio(vha, entry_hdr, &data_ptr); if (rval != QLA_SUCCESS) qla8044_mark_entry_skipped(vha, entry_hdr, i); break; case QLA8044_POLLWR: rval = qla8044_minidump_process_pollwr(vha, entry_hdr, &data_ptr); if (rval != QLA_SUCCESS) qla8044_mark_entry_skipped(vha, entry_hdr, i); break; case QLA82XX_RDNOP: default: qla8044_mark_entry_skipped(vha, entry_hdr, i); break; } data_collected = (uint8_t *)data_ptr - (uint8_t *)((uint8_t *)ha->md_dump); skip_nxt_entry: /* * next entry in the template */ entry_hdr = (struct qla8044_minidump_entry_hdr *) (((uint8_t *)entry_hdr) + entry_hdr->entry_size); } if (data_collected != ha->md_dump_size) { ql_log(ql_log_info, vha, 0xb105, "Dump data mismatch: Data collected: " "[0x%x], total_data_size:[0x%x]\n", data_collected, ha->md_dump_size); rval = QLA_FUNCTION_FAILED; goto md_failed; } ql_log(ql_log_info, vha, 0xb110, "Firmware dump saved to temp buffer (%ld/%p %ld/%p).\n", vha->host_no, ha->md_tmplt_hdr, vha->host_no, ha->md_dump); ha->fw_dumped = 1; qla2x00_post_uevent_work(vha, QLA_UEVENT_CODE_FW_DUMP); ql_log(ql_log_info, vha, 0xb106, "Leaving fn: %s Last entry: 0x%x\n", __func__, i); md_failed: return rval; } void qla8044_get_minidump(struct scsi_qla_host *vha) { struct qla_hw_data *ha = vha->hw; if (!qla8044_collect_md_data(vha)) { ha->fw_dumped = 1; ha->prev_minidump_failed = 0; } else { ql_log(ql_log_fatal, vha, 0xb0db, "%s: Unable to collect minidump\n", __func__); ha->prev_minidump_failed = 1; } } static int qla8044_poll_flash_status_reg(struct scsi_qla_host *vha) { uint32_t flash_status; int retries = QLA8044_FLASH_READ_RETRY_COUNT; int ret_val = QLA_SUCCESS; while (retries--) { ret_val = qla8044_rd_reg_indirect(vha, QLA8044_FLASH_STATUS, &flash_status); if (ret_val) { ql_log(ql_log_warn, vha, 0xb13c, "%s: Failed to read FLASH_STATUS reg.\n", __func__); break; } if ((flash_status & QLA8044_FLASH_STATUS_READY) == QLA8044_FLASH_STATUS_READY) break; msleep(QLA8044_FLASH_STATUS_REG_POLL_DELAY); } if (!retries) ret_val = QLA_FUNCTION_FAILED; return ret_val; } static int qla8044_write_flash_status_reg(struct scsi_qla_host *vha, uint32_t data) { int ret_val = QLA_SUCCESS; uint32_t cmd; cmd = vha->hw->fdt_wrt_sts_reg_cmd; ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_ADDR, QLA8044_FLASH_STATUS_WRITE_DEF_SIG | cmd); if (ret_val) { ql_log(ql_log_warn, vha, 0xb125, "%s: Failed to write to FLASH_ADDR.\n", __func__); goto exit_func; } ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_WRDATA, data); if (ret_val) { ql_log(ql_log_warn, vha, 0xb126, "%s: Failed to write to FLASH_WRDATA.\n", __func__); goto exit_func; } ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_CONTROL, QLA8044_FLASH_SECOND_ERASE_MS_VAL); if (ret_val) { ql_log(ql_log_warn, vha, 0xb127, "%s: Failed to write to FLASH_CONTROL.\n", __func__); goto exit_func; } ret_val = qla8044_poll_flash_status_reg(vha); if (ret_val) ql_log(ql_log_warn, vha, 0xb128, "%s: Error polling flash status reg.\n", __func__); exit_func: return ret_val; } /* * This function assumes that the flash lock is held. */ static int qla8044_unprotect_flash(scsi_qla_host_t *vha) { int ret_val; struct qla_hw_data *ha = vha->hw; ret_val = qla8044_write_flash_status_reg(vha, ha->fdt_wrt_enable); if (ret_val) ql_log(ql_log_warn, vha, 0xb139, "%s: Write flash status failed.\n", __func__); return ret_val; } /* * This function assumes that the flash lock is held. */ static int qla8044_protect_flash(scsi_qla_host_t *vha) { int ret_val; struct qla_hw_data *ha = vha->hw; ret_val = qla8044_write_flash_status_reg(vha, ha->fdt_wrt_disable); if (ret_val) ql_log(ql_log_warn, vha, 0xb13b, "%s: Write flash status failed.\n", __func__); return ret_val; } static int qla8044_erase_flash_sector(struct scsi_qla_host *vha, uint32_t sector_start_addr) { uint32_t reversed_addr; int ret_val = QLA_SUCCESS; ret_val = qla8044_poll_flash_status_reg(vha); if (ret_val) { ql_log(ql_log_warn, vha, 0xb12e, "%s: Poll flash status after erase failed..\n", __func__); } reversed_addr = (((sector_start_addr & 0xFF) << 16) | (sector_start_addr & 0xFF00) | ((sector_start_addr & 0xFF0000) >> 16)); ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_WRDATA, reversed_addr); if (ret_val) { ql_log(ql_log_warn, vha, 0xb12f, "%s: Failed to write to FLASH_WRDATA.\n", __func__); } ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_ADDR, QLA8044_FLASH_ERASE_SIG | vha->hw->fdt_erase_cmd); if (ret_val) { ql_log(ql_log_warn, vha, 0xb130, "%s: Failed to write to FLASH_ADDR.\n", __func__); } ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_CONTROL, QLA8044_FLASH_LAST_ERASE_MS_VAL); if (ret_val) { ql_log(ql_log_warn, vha, 0xb131, "%s: Failed write to FLASH_CONTROL.\n", __func__); } ret_val = qla8044_poll_flash_status_reg(vha); if (ret_val) { ql_log(ql_log_warn, vha, 0xb132, "%s: Poll flash status failed.\n", __func__); } return ret_val; } /* * qla8044_flash_write_u32 - Write data to flash * * @ha : Pointer to adapter structure * addr : Flash address to write to * p_data : Data to be written * * Return Value - QLA_SUCCESS/QLA_FUNCTION_FAILED * * NOTE: Lock should be held on entry */ static int qla8044_flash_write_u32(struct scsi_qla_host *vha, uint32_t addr, uint32_t *p_data) { int ret_val = QLA_SUCCESS; ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_ADDR, 0x00800000 | (addr >> 2)); if (ret_val) { ql_log(ql_log_warn, vha, 0xb134, "%s: Failed write to FLASH_ADDR.\n", __func__); goto exit_func; } ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_WRDATA, *p_data); if (ret_val) { ql_log(ql_log_warn, vha, 0xb135, "%s: Failed write to FLASH_WRDATA.\n", __func__); goto exit_func; } ret_val = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_CONTROL, 0x3D); if (ret_val) { ql_log(ql_log_warn, vha, 0xb136, "%s: Failed write to FLASH_CONTROL.\n", __func__); goto exit_func; } ret_val = qla8044_poll_flash_status_reg(vha); if (ret_val) { ql_log(ql_log_warn, vha, 0xb137, "%s: Poll flash status failed.\n", __func__); } exit_func: return ret_val; } static int qla8044_write_flash_buffer_mode(scsi_qla_host_t *vha, uint32_t *dwptr, uint32_t faddr, uint32_t dwords) { int ret = QLA_FUNCTION_FAILED; uint32_t spi_val; if (dwords < QLA8044_MIN_OPTROM_BURST_DWORDS || dwords > QLA8044_MAX_OPTROM_BURST_DWORDS) { ql_dbg(ql_dbg_user, vha, 0xb123, "Got unsupported dwords = 0x%x.\n", dwords); return QLA_FUNCTION_FAILED; } qla8044_rd_reg_indirect(vha, QLA8044_FLASH_SPI_CONTROL, &spi_val); qla8044_wr_reg_indirect(vha, QLA8044_FLASH_SPI_CONTROL, spi_val | QLA8044_FLASH_SPI_CTL); qla8044_wr_reg_indirect(vha, QLA8044_FLASH_ADDR, QLA8044_FLASH_FIRST_TEMP_VAL); /* First DWORD write to FLASH_WRDATA */ ret = qla8044_wr_reg_indirect(vha, QLA8044_FLASH_WRDATA, *dwptr++); qla8044_wr_reg_indirect(vha, QLA8044_FLASH_CONTROL, QLA8044_FLASH_FIRST_MS_PATTERN); ret = qla8044_poll_flash_status_reg(vha); if (ret) { ql_log(ql_log_warn, vha, 0xb124, "%s: Failed.\n", __func__); goto exit_func; } dwords--; qla8044_wr_reg_indirect(vha, QLA8044_FLASH_ADDR, QLA8044_FLASH_SECOND_TEMP_VAL); /* Second to N-1 DWORDS writes */ while (dwords != 1) { qla8044_wr_reg_indirect(vha, QLA8044_FLASH_WRDATA, *dwptr++); qla8044_wr_reg_indirect(vha, QLA8044_FLASH_CONTROL, QLA8044_FLASH_SECOND_MS_PATTERN); ret = qla8044_poll_flash_status_reg(vha); if (ret) { ql_log(ql_log_warn, vha, 0xb129, "%s: Failed.\n", __func__); goto exit_func; } dwords--; } qla8044_wr_reg_indirect(vha, QLA8044_FLASH_ADDR, QLA8044_FLASH_FIRST_TEMP_VAL | (faddr >> 2)); /* Last DWORD write */ qla8044_wr_reg_indirect(vha, QLA8044_FLASH_WRDATA, *dwptr++); qla8044_wr_reg_indirect(vha, QLA8044_FLASH_CONTROL, QLA8044_FLASH_LAST_MS_PATTERN); ret = qla8044_poll_flash_status_reg(vha); if (ret) { ql_log(ql_log_warn, vha, 0xb12a, "%s: Failed.\n", __func__); goto exit_func; } qla8044_rd_reg_indirect(vha, QLA8044_FLASH_SPI_STATUS, &spi_val); if ((spi_val & QLA8044_FLASH_SPI_CTL) == QLA8044_FLASH_SPI_CTL) { ql_log(ql_log_warn, vha, 0xb12b, "%s: Failed.\n", __func__); spi_val = 0; /* Operation failed, clear error bit. */ qla8044_rd_reg_indirect(vha, QLA8044_FLASH_SPI_CONTROL, &spi_val); qla8044_wr_reg_indirect(vha, QLA8044_FLASH_SPI_CONTROL, spi_val | QLA8044_FLASH_SPI_CTL); } exit_func: return ret; } static int qla8044_write_flash_dword_mode(scsi_qla_host_t *vha, uint32_t *dwptr, uint32_t faddr, uint32_t dwords) { int ret = QLA_FUNCTION_FAILED; uint32_t liter; for (liter = 0; liter < dwords; liter++, faddr += 4, dwptr++) { ret = qla8044_flash_write_u32(vha, faddr, dwptr); if (ret) { ql_dbg(ql_dbg_p3p, vha, 0xb141, "%s: flash address=%x data=%x.\n", __func__, faddr, *dwptr); break; } } return ret; } int qla8044_write_optrom_data(struct scsi_qla_host *vha, void *buf, uint32_t offset, uint32_t length) { int rval = QLA_FUNCTION_FAILED, i, burst_iter_count; int dword_count, erase_sec_count; uint32_t erase_offset; uint8_t *p_cache, *p_src; erase_offset = offset; p_cache = kcalloc(length, sizeof(uint8_t), GFP_KERNEL); if (!p_cache) return QLA_FUNCTION_FAILED; memcpy(p_cache, buf, length); p_src = p_cache; dword_count = length / sizeof(uint32_t); /* Since the offset and legth are sector aligned, it will be always * multiple of burst_iter_count (64) */ burst_iter_count = dword_count / QLA8044_MAX_OPTROM_BURST_DWORDS; erase_sec_count = length / QLA8044_SECTOR_SIZE; /* Suspend HBA. */ scsi_block_requests(vha->host); /* Lock and enable write for whole operation. */ qla8044_flash_lock(vha); qla8044_unprotect_flash(vha); /* Erasing the sectors */ for (i = 0; i < erase_sec_count; i++) { rval = qla8044_erase_flash_sector(vha, erase_offset); ql_dbg(ql_dbg_user, vha, 0xb138, "Done erase of sector=0x%x.\n", erase_offset); if (rval) { ql_log(ql_log_warn, vha, 0xb121, "Failed to erase the sector having address: " "0x%x.\n", erase_offset); goto out; } erase_offset += QLA8044_SECTOR_SIZE; } ql_dbg(ql_dbg_user, vha, 0xb13f, "Got write for addr = 0x%x length=0x%x.\n", offset, length); for (i = 0; i < burst_iter_count; i++) { /* Go with write. */ rval = qla8044_write_flash_buffer_mode(vha, (uint32_t *)p_src, offset, QLA8044_MAX_OPTROM_BURST_DWORDS); if (rval) { /* Buffer Mode failed skip to dword mode */ ql_log(ql_log_warn, vha, 0xb122, "Failed to write flash in buffer mode, " "Reverting to slow-write.\n"); rval = qla8044_write_flash_dword_mode(vha, (uint32_t *)p_src, offset, QLA8044_MAX_OPTROM_BURST_DWORDS); } p_src += sizeof(uint32_t) * QLA8044_MAX_OPTROM_BURST_DWORDS; offset += sizeof(uint32_t) * QLA8044_MAX_OPTROM_BURST_DWORDS; } ql_dbg(ql_dbg_user, vha, 0xb133, "Done writing.\n"); out: qla8044_protect_flash(vha); qla8044_flash_unlock(vha); scsi_unblock_requests(vha->host); kfree(p_cache); return rval; } #define LEG_INT_PTR_B31 (1 << 31) #define LEG_INT_PTR_B30 (1 << 30) #define PF_BITS_MASK (0xF << 16) /** * qla8044_intr_handler() - Process interrupts for the ISP8044 * @irq: interrupt number * @dev_id: SCSI driver HA context * * Called by system whenever the host adapter generates an interrupt. * * Returns handled flag. */ irqreturn_t qla8044_intr_handler(int irq, void *dev_id) { scsi_qla_host_t *vha; struct qla_hw_data *ha; struct rsp_que *rsp; struct device_reg_82xx __iomem *reg; int status = 0; unsigned long flags; unsigned long iter; uint32_t stat; uint16_t mb[8]; uint32_t leg_int_ptr = 0, pf_bit; rsp = (struct rsp_que *) dev_id; if (!rsp) { ql_log(ql_log_info, NULL, 0xb143, "%s(): NULL response queue pointer\n", __func__); return IRQ_NONE; } ha = rsp->hw; vha = pci_get_drvdata(ha->pdev); if (unlikely(pci_channel_offline(ha->pdev))) return IRQ_HANDLED; leg_int_ptr = qla8044_rd_reg(ha, LEG_INTR_PTR_OFFSET); /* Legacy interrupt is valid if bit31 of leg_int_ptr is set */ if (!(leg_int_ptr & (LEG_INT_PTR_B31))) { ql_dbg(ql_dbg_p3p, vha, 0xb144, "%s: Legacy Interrupt Bit 31 not set, " "spurious interrupt!\n", __func__); return IRQ_NONE; } pf_bit = ha->portnum << 16; /* Validate the PCIE function ID set in leg_int_ptr bits [19..16] */ if ((leg_int_ptr & (PF_BITS_MASK)) != pf_bit) { ql_dbg(ql_dbg_p3p, vha, 0xb145, "%s: Incorrect function ID 0x%x in " "legacy interrupt register, " "ha->pf_bit = 0x%x\n", __func__, (leg_int_ptr & (PF_BITS_MASK)), pf_bit); return IRQ_NONE; } /* To de-assert legacy interrupt, write 0 to Legacy Interrupt Trigger * Control register and poll till Legacy Interrupt Pointer register * bit32 is 0. */ qla8044_wr_reg(ha, LEG_INTR_TRIG_OFFSET, 0); do { leg_int_ptr = qla8044_rd_reg(ha, LEG_INTR_PTR_OFFSET); if ((leg_int_ptr & (PF_BITS_MASK)) != pf_bit) break; } while (leg_int_ptr & (LEG_INT_PTR_B30)); reg = &ha->iobase->isp82; spin_lock_irqsave(&ha->hardware_lock, flags); for (iter = 1; iter--; ) { if (RD_REG_DWORD(®->host_int)) { stat = RD_REG_DWORD(®->host_status); if ((stat & HSRX_RISC_INT) == 0) break; switch (stat & 0xff) { case 0x1: case 0x2: case 0x10: case 0x11: qla82xx_mbx_completion(vha, MSW(stat)); status |= MBX_INTERRUPT; break; case 0x12: mb[0] = MSW(stat); mb[1] = RD_REG_WORD(®->mailbox_out[1]); mb[2] = RD_REG_WORD(®->mailbox_out[2]); mb[3] = RD_REG_WORD(®->mailbox_out[3]); qla2x00_async_event(vha, rsp, mb); break; case 0x13: qla24xx_process_response_queue(vha, rsp); break; default: ql_dbg(ql_dbg_p3p, vha, 0xb146, "Unrecognized interrupt type " "(%d).\n", stat & 0xff); break; } } WRT_REG_DWORD(®->host_int, 0); } qla2x00_handle_mbx_completion(ha, status); spin_unlock_irqrestore(&ha->hardware_lock, flags); return IRQ_HANDLED; } static int qla8044_idc_dontreset(struct qla_hw_data *ha) { uint32_t idc_ctrl; idc_ctrl = qla8044_rd_reg(ha, QLA8044_IDC_DRV_CTRL); return idc_ctrl & DONTRESET_BIT0; } static void qla8044_clear_rst_ready(scsi_qla_host_t *vha) { uint32_t drv_state; drv_state = qla8044_rd_direct(vha, QLA8044_CRB_DRV_STATE_INDEX); /* * For ISP8044, drv_active register has 1 bit per function, * shift 1 by func_num to set a bit for the function. * For ISP82xx, drv_active has 4 bits per function */ drv_state &= ~(1 << vha->hw->portnum); ql_dbg(ql_dbg_p3p, vha, 0xb13d, "drv_state: 0x%08x\n", drv_state); qla8044_wr_direct(vha, QLA8044_CRB_DRV_STATE_INDEX, drv_state); } int qla8044_abort_isp(scsi_qla_host_t *vha) { int rval; uint32_t dev_state; struct qla_hw_data *ha = vha->hw; qla8044_idc_lock(ha); dev_state = qla8044_rd_direct(vha, QLA8044_CRB_DEV_STATE_INDEX); if (ql2xdontresethba) qla8044_set_idc_dontreset(vha); /* If device_state is NEED_RESET, go ahead with * Reset,irrespective of ql2xdontresethba. This is to allow a * non-reset-owner to force a reset. Non-reset-owner sets * the IDC_CTRL BIT0 to prevent Reset-owner from doing a Reset * and then forces a Reset by setting device_state to * NEED_RESET. */ if (dev_state == QLA8XXX_DEV_READY) { /* If IDC_CTRL DONTRESETHBA_BIT0 is set don't do reset * recovery */ if (qla8044_idc_dontreset(ha) == DONTRESET_BIT0) { ql_dbg(ql_dbg_p3p, vha, 0xb13e, "Reset recovery disabled\n"); rval = QLA_FUNCTION_FAILED; goto exit_isp_reset; } ql_dbg(ql_dbg_p3p, vha, 0xb140, "HW State: NEED RESET\n"); qla8044_wr_direct(vha, QLA8044_CRB_DEV_STATE_INDEX, QLA8XXX_DEV_NEED_RESET); } /* For ISP8044, Reset owner is NIC, iSCSI or FCOE based on priority * and which drivers are present. Unlike ISP82XX, the function setting * NEED_RESET, may not be the Reset owner. */ qla83xx_reset_ownership(vha); qla8044_idc_unlock(ha); rval = qla8044_device_state_handler(vha); qla8044_idc_lock(ha); qla8044_clear_rst_ready(vha); exit_isp_reset: qla8044_idc_unlock(ha); if (rval == QLA_SUCCESS) { ha->flags.isp82xx_fw_hung = 0; ha->flags.nic_core_reset_hdlr_active = 0; rval = qla82xx_restart_isp(vha); } return rval; } void qla8044_fw_dump(scsi_qla_host_t *vha, int hardware_locked) { struct qla_hw_data *ha = vha->hw; if (!ha->allow_cna_fw_dump) return; scsi_block_requests(vha->host); ha->flags.isp82xx_no_md_cap = 1; qla8044_idc_lock(ha); qla82xx_set_reset_owner(vha); qla8044_idc_unlock(ha); qla2x00_wait_for_chip_reset(vha); scsi_unblock_requests(vha->host); }