From: Jamie Wellnitz Subject: Update lpfc to 8.2.8.1 References: bnc#420767 This patch adds a few features (including FC authentication and a few management ioctls) to lpfc 8.2.8 and generates 8.2.8.1. Signed-off-by: Hannes Reinecke --- drivers/scsi/lpfc/Makefile | 5 drivers/scsi/lpfc/lpfc.h | 129 + drivers/scsi/lpfc/lpfc_attr.c | 976 ++++++++++++- drivers/scsi/lpfc/lpfc_auth.c | 838 +++++++++++ drivers/scsi/lpfc/lpfc_auth.h | 92 + drivers/scsi/lpfc/lpfc_auth_access.c | 598 ++++++++ drivers/scsi/lpfc/lpfc_auth_access.h | 245 +++ drivers/scsi/lpfc/lpfc_crtn.h | 37 drivers/scsi/lpfc/lpfc_disc.h | 3 drivers/scsi/lpfc/lpfc_els.c | 663 +++++++++ drivers/scsi/lpfc/lpfc_hbadisc.c | 154 +- drivers/scsi/lpfc/lpfc_hw.h | 52 drivers/scsi/lpfc/lpfc_init.c | 154 +- drivers/scsi/lpfc/lpfc_ioctl.c | 2519 +++++++++++++++++++++++++++++++++++ drivers/scsi/lpfc/lpfc_ioctl.h | 184 ++ drivers/scsi/lpfc/lpfc_logmsg.h | 1 drivers/scsi/lpfc/lpfc_mbox.c | 2 drivers/scsi/lpfc/lpfc_menlo.c | 1174 ++++++++++++++++ drivers/scsi/lpfc/lpfc_scsi.c | 36 drivers/scsi/lpfc/lpfc_security.c | 339 ++++ drivers/scsi/lpfc/lpfc_security.h | 24 drivers/scsi/lpfc/lpfc_sli.c | 59 drivers/scsi/lpfc/lpfc_sli.h | 3 drivers/scsi/lpfc/lpfc_version.h | 2 drivers/scsi/lpfc/lpfc_vport.c | 16 25 files changed, 8149 insertions(+), 156 deletions(-) --- a/drivers/scsi/lpfc/lpfc_attr.c +++ b/drivers/scsi/lpfc/lpfc_attr.c @@ -41,6 +41,7 @@ #include "lpfc_compat.h" #include "lpfc_crtn.h" #include "lpfc_vport.h" +#include "lpfc_auth_access.h" #define LPFC_DEF_DEVLOSS_TMO 30 #define LPFC_MIN_DEVLOSS_TMO 1 @@ -50,6 +51,15 @@ #define LPFC_LINK_SPEED_BITMAP 0x00000117 #define LPFC_LINK_SPEED_STRING "0, 1, 2, 4, 8" +extern struct bin_attribute sysfs_menlo_attr; + +/* + * Write key size should be multiple of 4. If write key is changed + * make sure that library write key is also changed. + */ +#define LPFC_REG_WRITE_KEY_SIZE 4 +#define LPFC_REG_WRITE_KEY "EMLX" + /** * lpfc_jedec_to_ascii: Hex to ascii convertor according to JEDEC rules. * @incr: integer to convert. @@ -551,7 +561,7 @@ lpfc_do_offline(struct lpfc_hba *phba, u * -EIO reset not configured or error posting the event * zero for success **/ -static int +int lpfc_selective_reset(struct lpfc_hba *phba) { struct completion online_compl; @@ -1080,6 +1090,141 @@ lpfc_poll_store(struct device *dev, stru return strlen(buf); } +static ssize_t +lpfc_auth_state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + switch (vport->auth.auth_state) { + case LPFC_AUTH_UNKNOWN: + if (vport->auth.auth_msg_state == LPFC_AUTH_NEGOTIATE || + vport->auth.auth_msg_state == LPFC_DHCHAP_CHALLENGE || + vport->auth.auth_msg_state == LPFC_DHCHAP_REPLY || + vport->auth.auth_msg_state == LPFC_DHCHAP_SUCCESS_REPLY) + return snprintf(buf, PAGE_SIZE, "Authenticating\n"); + else + return snprintf(buf, PAGE_SIZE, "Not Authenticated\n"); + case LPFC_AUTH_FAIL: + return snprintf(buf, PAGE_SIZE, "Failed\n"); + case LPFC_AUTH_SUCCESS: + if (vport->auth.auth_msg_state == LPFC_AUTH_NEGOTIATE || + vport->auth.auth_msg_state == LPFC_DHCHAP_CHALLENGE || + vport->auth.auth_msg_state == LPFC_DHCHAP_REPLY || + vport->auth.auth_msg_state == LPFC_DHCHAP_SUCCESS_REPLY) + return snprintf(buf, PAGE_SIZE, "Authenticating\n"); + else if (vport->auth.auth_msg_state == LPFC_DHCHAP_SUCCESS) + return snprintf(buf, PAGE_SIZE, "Authenticated\n"); + } + return snprintf(buf, PAGE_SIZE, "Unknown\n"); +} + +static ssize_t +lpfc_auth_dir_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + if (!vport->cfg_enable_auth || + vport->auth.auth_state != LPFC_AUTH_SUCCESS) + return snprintf(buf, PAGE_SIZE, "Unknown\n"); + if (vport->auth.direction == AUTH_DIRECTION_LOCAL) + return snprintf(buf, PAGE_SIZE, "Local Authenticated\n"); + else if (vport->auth.direction == AUTH_DIRECTION_REMOTE) + return snprintf(buf, PAGE_SIZE, "Remote Authenticated\n"); + else if (vport->auth.direction == AUTH_DIRECTION_BIDI) + return snprintf(buf, PAGE_SIZE, "Bidi Authentication\n"); + return snprintf(buf, PAGE_SIZE, "Unknown\n"); +} + +static ssize_t +lpfc_auth_protocol_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + if (vport->cfg_enable_auth && + vport->auth.auth_state == LPFC_AUTH_SUCCESS) + return snprintf(buf, PAGE_SIZE, "1 (DH-CHAP)\n"); + else + return snprintf(buf, PAGE_SIZE, "Unknown\n"); +} + +static ssize_t +lpfc_auth_dhgroup_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + if (!vport->cfg_enable_auth || + vport->auth.auth_state != LPFC_AUTH_SUCCESS) + return snprintf(buf, PAGE_SIZE, "Unknown\n"); + switch (vport->auth.group_id) { + case DH_GROUP_NULL: + return snprintf(buf, PAGE_SIZE, "0 (NULL)\n"); + case DH_GROUP_1024: + return snprintf(buf, PAGE_SIZE, "1 (1024)\n"); + case DH_GROUP_1280: + return snprintf(buf, PAGE_SIZE, "2 (1280)\n"); + case DH_GROUP_1536: + return snprintf(buf, PAGE_SIZE, "3 (1536)\n"); + case DH_GROUP_2048: + return snprintf(buf, PAGE_SIZE, "4 (2048)\n"); + } + return snprintf(buf, PAGE_SIZE, "%d (Unrecognized)\n", + vport->auth.group_id); +} + +static ssize_t +lpfc_auth_hash_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + if (!vport->cfg_enable_auth || + vport->auth.auth_state != LPFC_AUTH_SUCCESS) + return snprintf(buf, PAGE_SIZE, "Unknown\n"); + switch (vport->auth.hash_id) { + case FC_SP_HASH_MD5: + return snprintf(buf, PAGE_SIZE, "5 (MD5)\n"); + case FC_SP_HASH_SHA1: + return snprintf(buf, PAGE_SIZE, "6 (SHA1)\n"); + } + return snprintf(buf, PAGE_SIZE, "%d (Unrecognized)\n", + vport->auth.hash_id); +} +static ssize_t +lpfc_auth_last_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + struct timeval last_time; + if (!vport->cfg_enable_auth || vport->auth.last_auth == 0) + return snprintf(buf, PAGE_SIZE, "%d\n", -1); + jiffies_to_timeval((jiffies - vport->auth.last_auth), &last_time); + return snprintf(buf, PAGE_SIZE, "%ld\n", last_time.tv_sec); +} + +static ssize_t +lpfc_auth_next_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + unsigned long next_jiff; + struct timeval next_time; + if (!vport->cfg_enable_auth || + vport->auth.last_auth == 0 || + vport->auth.reauth_interval == 0) + return snprintf(buf, PAGE_SIZE, "%d\n", -1); + /* calculate the amount of time left until next auth */ + next_jiff = (msecs_to_jiffies(vport->auth.reauth_interval * 60000) + + vport->auth.last_auth) - jiffies; + jiffies_to_timeval(next_jiff, &next_time); + return snprintf(buf, PAGE_SIZE, "%ld\n", next_time.tv_sec); +} + /** * lpfc_param_show: Return a cfg attribute value in decimal. * @@ -1512,7 +1657,38 @@ static DEVICE_ATTR(max_xri, S_IRUGO, lpf static DEVICE_ATTR(used_xri, S_IRUGO, lpfc_used_xri_show, NULL); static DEVICE_ATTR(npiv_info, S_IRUGO, lpfc_npiv_info_show, NULL); static DEVICE_ATTR(lpfc_temp_sensor, S_IRUGO, lpfc_temp_sensor_show, NULL); +static DEVICE_ATTR(auth_state, S_IRUGO, lpfc_auth_state_show, NULL); +static DEVICE_ATTR(auth_dir, S_IRUGO, lpfc_auth_dir_show, NULL); +static DEVICE_ATTR(auth_protocol, S_IRUGO, lpfc_auth_protocol_show, NULL); +static DEVICE_ATTR(auth_dhgroup, S_IRUGO, lpfc_auth_dhgroup_show, NULL); +static DEVICE_ATTR(auth_hash, S_IRUGO, lpfc_auth_hash_show, NULL); +static DEVICE_ATTR(auth_last, S_IRUGO, lpfc_auth_last_show, NULL); +static DEVICE_ATTR(auth_next, S_IRUGO, lpfc_auth_next_show, NULL); + +static int +lpfc_parse_wwn(const char *ns, uint8_t *nm) +{ + unsigned int i, j; + memset(nm, 0, 8); + + /* Validate and store the new name */ + for (i = 0, j = 0; i < 16; i++) { + if ((*ns >= 'a') && (*ns <= 'f')) + j = ((j << 4) | ((*ns++ - 'a') + 10)); + else if ((*ns >= 'A') && (*ns <= 'F')) + j = ((j << 4) | ((*ns++ - 'A') + 10)); + else if ((*ns >= '0') && (*ns <= '9')) + j = ((j << 4) | (*ns++ - '0')); + else + return -EINVAL; + if (i % 2) { + nm[i/2] = j & 0xff; + j = 0; + } + } + return 0; +} static char *lpfc_soft_wwn_key = "C99G71SL8032A"; @@ -1908,6 +2084,87 @@ lpfc_vport_param_store(nodev_tmo) static DEVICE_ATTR(lpfc_nodev_tmo, S_IRUGO | S_IWUSR, lpfc_nodev_tmo_show, lpfc_nodev_tmo_store); +static ssize_t +lpfc_authenticate(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *)shost->hostdata; + struct lpfc_hba *phba = vport->phba; + struct lpfc_nodelist *ndlp; + int status; + struct lpfc_name wwpn; + + if (lpfc_parse_wwn(buf, wwpn.u.wwn)) + return -EINVAL; + + if (vport->port_state == LPFC_VPORT_FAILED) { + lpfc_issue_lip(shost); + return strlen(buf); + } + if ((vport->fc_flag & FC_OFFLINE_MODE) || + (phba->sli.sli_flag & LPFC_BLOCK_MGMT_IO) || + (!vport->cfg_enable_auth)) + return -EPERM; + + /* If vport already in the middle of authentication do not restart */ + if ((vport->auth.auth_msg_state == LPFC_AUTH_NEGOTIATE) || + (vport->auth.auth_msg_state == LPFC_DHCHAP_CHALLENGE) || + (vport->auth.auth_msg_state == LPFC_DHCHAP_REPLY)) + return -EAGAIN; + + if (wwn_to_u64(wwpn.u.wwn) == AUTH_FABRIC_WWN) + ndlp = lpfc_findnode_did(vport, Fabric_DID); + else + ndlp = lpfc_findnode_wwnn(vport, &wwpn); + if (!ndlp || !NLP_CHK_NODE_ACT(ndlp)) + return -EPERM; + status = lpfc_start_node_authentication(ndlp); + if (status) + return status; + return strlen(buf); +} +static DEVICE_ATTR(lpfc_authenticate, S_IRUGO | S_IWUSR, NULL, + lpfc_authenticate); + +static ssize_t +lpfc_update_auth_config(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *)shost->hostdata; + struct lpfc_hba *phba = vport->phba; + struct lpfc_nodelist *ndlp; + struct lpfc_name wwpn; + int status; + + if (lpfc_parse_wwn(buf, wwpn.u.wwn)) + return -EINVAL; + + if ((vport->fc_flag & FC_OFFLINE_MODE) || + (phba->sli.sli_flag & LPFC_BLOCK_MGMT_IO) || + (!vport->cfg_enable_auth)) + return -EPERM; + + /* If vport already in the middle of authentication do not restart */ + if ((vport->auth.auth_msg_state == LPFC_AUTH_NEGOTIATE) || + (vport->auth.auth_msg_state == LPFC_DHCHAP_CHALLENGE) || + (vport->auth.auth_msg_state == LPFC_DHCHAP_REPLY)) + return -EAGAIN; + + if (wwn_to_u64(wwpn.u.wwn) == AUTH_FABRIC_WWN) + ndlp = lpfc_findnode_did(vport, Fabric_DID); + else + ndlp = lpfc_findnode_wwnn(vport, &wwpn); + if (!ndlp || !NLP_CHK_NODE_ACT(ndlp)) + return -EPERM; + status = lpfc_get_auth_config(ndlp, &wwpn); + if (status) + return -EPERM; + return strlen(buf); +} +static DEVICE_ATTR(lpfc_update_auth_config, S_IRUGO | S_IWUSR, + NULL, lpfc_update_auth_config); /* # lpfc_devloss_tmo: If set, it will hold all I/O errors on devices that @@ -2753,6 +3010,48 @@ LPFC_ATTR_R(use_msi, 2, 0, 2, "Use Messa "MSI-X (2), if possible"); /* +# lpfc_enable_auth: controls FC Authentication. +# 0 = Authentication OFF +# 1 = Authentication ON +# Value range [0,1]. Default value is 0. +*/ +static int lpfc_enable_auth; +module_param(lpfc_enable_auth, int, 0); +MODULE_PARM_DESC(lpfc_enable_auth, "Enable FC Authentication"); +lpfc_vport_param_show(enable_auth); +lpfc_vport_param_init(enable_auth, 0, 0, 1); +static int +lpfc_enable_auth_set(struct lpfc_vport *vport, int val) +{ + if (val == vport->cfg_enable_auth) + return 0; + if (val == 0) { + spin_lock_irq(&fc_security_user_lock); + list_del(&vport->sc_users); + spin_unlock_irq(&fc_security_user_lock); + vport->cfg_enable_auth = val; + lpfc_fc_queue_security_work(vport, + &vport->sc_offline_work); + return 0; + } else if (val == 1) { + spin_lock_irq(&fc_security_user_lock); + list_add_tail(&vport->sc_users, &fc_security_user_list); + spin_unlock_irq(&fc_security_user_lock); + vport->cfg_enable_auth = val; + lpfc_fc_queue_security_work(vport, + &vport->sc_online_work); + return 0; + } + lpfc_printf_vlog(vport, KERN_ERR, LOG_INIT, + "0431 lpfc_enable_auth attribute cannot be set to %d, " + "allowed range is [0, 1]\n", val); + return -EINVAL; +} +lpfc_vport_param_store(enable_auth); +static DEVICE_ATTR(lpfc_enable_auth, S_IRUGO | S_IWUSR, + lpfc_enable_auth_show, lpfc_enable_auth_store); + +/* # lpfc_enable_hba_reset: Allow or prevent HBA resets to the hardware. # 0 = HBA resets disabled # 1 = HBA resets enabled (default) @@ -2825,6 +3124,16 @@ struct device_attribute *lpfc_hba_attrs[ &dev_attr_lpfc_poll, &dev_attr_lpfc_poll_tmo, &dev_attr_lpfc_use_msi, + &dev_attr_lpfc_enable_auth, + &dev_attr_lpfc_authenticate, + &dev_attr_lpfc_update_auth_config, + &dev_attr_auth_state, + &dev_attr_auth_dir, + &dev_attr_auth_protocol, + &dev_attr_auth_dhgroup, + &dev_attr_auth_hash, + &dev_attr_auth_last, + &dev_attr_auth_next, &dev_attr_lpfc_soft_wwnn, &dev_attr_lpfc_soft_wwpn, &dev_attr_lpfc_soft_wwn_enable, @@ -2855,6 +3164,14 @@ struct device_attribute *lpfc_vport_attr &dev_attr_nport_evt_cnt, &dev_attr_npiv_info, &dev_attr_lpfc_enable_da_id, + &dev_attr_auth_state, + &dev_attr_auth_dir, + &dev_attr_auth_protocol, + &dev_attr_auth_dhgroup, + &dev_attr_auth_hash, + &dev_attr_auth_last, + &dev_attr_auth_next, + &dev_attr_lpfc_max_scsicmpl_time, &dev_attr_lpfc_stat_data_ctrl, NULL, @@ -2888,21 +3205,23 @@ sysfs_ctlreg_write(struct kobject *kobj, struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; struct lpfc_hba *phba = vport->phba; - if ((off + count) > FF_REG_AREA_SIZE) + if ((off + count) > FF_REG_AREA_SIZE + LPFC_REG_WRITE_KEY_SIZE) return -ERANGE; - if (count == 0) return 0; + if (count <= LPFC_REG_WRITE_KEY_SIZE) + return 0; if (off % 4 || count % 4 || (unsigned long)buf % 4) return -EINVAL; - if (!(vport->fc_flag & FC_OFFLINE_MODE)) { - return -EPERM; - } + /* This is to protect HBA registers from accidental writes. */ + if (memcmp(buf, LPFC_REG_WRITE_KEY, LPFC_REG_WRITE_KEY_SIZE)) + return -EINVAL; spin_lock_irq(&phba->hbalock); - for (buf_off = 0; buf_off < count; buf_off += sizeof(uint32_t)) - writel(*((uint32_t *)(buf + buf_off)), + for (buf_off = 0; buf_off < count - LPFC_REG_WRITE_KEY_SIZE; + buf_off += sizeof(uint32_t)) + writel(*((uint32_t *)(buf + buf_off + LPFC_REG_WRITE_KEY_SIZE)), phba->ctrl_regs_memmap_p + off + buf_off); spin_unlock_irq(&phba->hbalock); @@ -2971,21 +3290,211 @@ static struct bin_attribute sysfs_ctlreg .write = sysfs_ctlreg_write, }; +static struct lpfc_sysfs_mbox * +lpfc_get_sysfs_mbox(struct lpfc_hba *phba, uint8_t create) +{ + struct lpfc_sysfs_mbox *sysfs_mbox; + pid_t pid; + + pid = current->pid; + + spin_lock_irq(&phba->hbalock); + list_for_each_entry(sysfs_mbox, &phba->sysfs_mbox_list, list) { + if (sysfs_mbox->pid == pid) { + spin_unlock_irq(&phba->hbalock); + return sysfs_mbox; + } + } + if (!create) { + spin_unlock_irq(&phba->hbalock); + return NULL; + } + spin_unlock_irq(&phba->hbalock); + sysfs_mbox = kzalloc(sizeof(struct lpfc_sysfs_mbox), + GFP_KERNEL); + if (!sysfs_mbox) + return NULL; + sysfs_mbox->state = SMBOX_IDLE; + sysfs_mbox->pid = pid; + spin_lock_irq(&phba->hbalock); + list_add_tail(&sysfs_mbox->list, &phba->sysfs_mbox_list); + + spin_unlock_irq(&phba->hbalock); + return sysfs_mbox; + +} /** * sysfs_mbox_idle: frees the sysfs mailbox. * @phba: lpfc_hba pointer **/ static void -sysfs_mbox_idle(struct lpfc_hba *phba) +sysfs_mbox_idle(struct lpfc_hba *phba, + struct lpfc_sysfs_mbox *sysfs_mbox) { - phba->sysfs_mbox.state = SMBOX_IDLE; - phba->sysfs_mbox.offset = 0; - - if (phba->sysfs_mbox.mbox) { - mempool_free(phba->sysfs_mbox.mbox, + list_del_init(&sysfs_mbox->list); + if (sysfs_mbox->mbox) { + mempool_free(sysfs_mbox->mbox, phba->mbox_mem_pool); - phba->sysfs_mbox.mbox = NULL; } + + if (sysfs_mbox->mbext) + kfree(sysfs_mbox->mbext); + + /* If txmit buffer allocated free txmit buffer */ + if (sysfs_mbox->txmit_buff) { + if (sysfs_mbox->txmit_buff->virt) + __lpfc_mbuf_free(phba, + sysfs_mbox->txmit_buff->virt, + sysfs_mbox->txmit_buff->phys); + kfree(sysfs_mbox->txmit_buff); + } + + /* If rcv buffer allocated free txmit buffer */ + if (sysfs_mbox->rcv_buff) { + if (sysfs_mbox->rcv_buff->virt) + __lpfc_mbuf_free(phba, + sysfs_mbox->rcv_buff->virt, + sysfs_mbox->rcv_buff->phys); + kfree(sysfs_mbox->rcv_buff); + } + + kfree(sysfs_mbox); +} + +static size_t +lpfc_syfs_mbox_copy_rcv_buff(struct lpfc_hba *phba, + struct lpfc_sysfs_mbox *sysfs_mbox, + char *buf, loff_t off, size_t count) +{ + uint32_t size; + spin_lock_irq(&phba->hbalock); + if (!sysfs_mbox->mbox) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -EAGAIN; + } + + if (sysfs_mbox->mbox->mb.mbxCommand == MBX_READ_EVENT_LOG) + size = sysfs_mbox->mbox->mb.un. + varRdEventLog.rcv_bde64.tus.f.bdeSize; + else + size = sysfs_mbox->mbox->mb.un. + varBIUdiag.un.s2.rcv_bde64.tus.f.bdeSize; + + + if ((count + off) > size) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ERANGE; + } + if (count > LPFC_BPL_SIZE) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ERANGE; + } + if (sysfs_mbox->extoff != off) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -EAGAIN; + } + + memcpy(buf, (uint8_t *) sysfs_mbox->rcv_buff->virt + off, count); + sysfs_mbox->extoff = off + count; + + if (sysfs_mbox->extoff >= size) + sysfs_mbox_idle(phba, sysfs_mbox); + + spin_unlock_irq(&phba->hbalock); + + return count; +} + +static size_t +lpfc_syfs_mbox_copy_extdata(struct lpfc_hba *phba, + struct lpfc_sysfs_mbox * sysfs_mbox, + char *buf, loff_t off, size_t count) +{ + uint32_t size; + + spin_lock_irq(&phba->hbalock); + if (!sysfs_mbox->mbox) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -EAGAIN; + } + + size = sysfs_mbox->mbox_data.out_ext_wlen * sizeof(uint32_t); + + if ((count + off) > size) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ERANGE; + } + + if (size > MAILBOX_EXT_SIZE) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ERANGE; + } + + if (sysfs_mbox->extoff != off) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -EAGAIN; + } + + memcpy(buf, (uint8_t *) sysfs_mbox->mbext + off, count); + sysfs_mbox->extoff = off + count; + + if (sysfs_mbox->extoff >= size) + sysfs_mbox_idle(phba, sysfs_mbox); + + spin_unlock_irq(&phba->hbalock); + + return count; +} + +static size_t +lpfc_syfs_mbox_copy_txmit_buff(struct lpfc_hba *phba, + struct lpfc_sysfs_mbox *sysfs_mbox, + char *buf, loff_t off, size_t count) +{ + uint32_t size; + spin_lock_irq(&phba->hbalock); + if (!sysfs_mbox->mbox || + (sysfs_mbox->offset != sizeof(struct lpfc_sysfs_mbox_data))) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -EAGAIN; + } + + size = sysfs_mbox->mbox->mb.un.varBIUdiag.un.s2.xmit_bde64. + tus.f.bdeSize; + + if ((count + off) > size) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ERANGE; + } + + if (size > LPFC_BPL_SIZE) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ERANGE; + } + + if (sysfs_mbox->extoff != off) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -EAGAIN; + } + + memcpy((uint8_t *) sysfs_mbox->txmit_buff->virt + off, buf, count); + sysfs_mbox->extoff = off + count; + + spin_unlock_irq(&phba->hbalock); + + return count; } /** @@ -3018,6 +3527,9 @@ sysfs_mbox_write(struct kobject *kobj, s struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; struct lpfc_hba *phba = vport->phba; struct lpfcMboxq *mbox = NULL; + struct lpfc_sysfs_mbox *sysfs_mbox; + uint8_t *ext; + uint32_t size; if ((count + off) > MAILBOX_CMD_SIZE) return -ERANGE; @@ -3029,34 +3541,232 @@ sysfs_mbox_write(struct kobject *kobj, s return 0; if (off == 0) { + sysfs_mbox = lpfc_get_sysfs_mbox(phba, 1); + if (sysfs_mbox == NULL) + return -ENOMEM; + /* + * If sysfs expect the reading of buffer and + * app doesnot know how to do it, use a different + * context. + */ + if (sysfs_mbox->state == SMBOX_READING_BUFF || + sysfs_mbox->state == SMBOX_READING_MBEXT) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + sysfs_mbox = lpfc_get_sysfs_mbox(phba, 1); + if (sysfs_mbox == NULL) + return -ENOMEM; + } + } else { + sysfs_mbox = lpfc_get_sysfs_mbox(phba, 0); + if (sysfs_mbox == NULL) + return -EAGAIN; + } + spin_lock_irq(&phba->hbalock); + if (sysfs_mbox->state == SMBOX_WRITING_MBEXT) { + if (!sysfs_mbox->mbox || + (sysfs_mbox->offset != + sizeof(struct lpfc_sysfs_mbox_data))) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -EAGAIN; + } + + size = sysfs_mbox->mbox_data.in_ext_wlen * sizeof(uint32_t); + + if ((count + sysfs_mbox->extoff) > size) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ERANGE; + } + + if (size > MAILBOX_EXT_SIZE) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ERANGE; + } + + if (!sysfs_mbox->mbext) { + spin_unlock_irq(&phba->hbalock); + + ext = kzalloc(MAILBOX_EXT_SIZE, GFP_KERNEL); + if (!ext) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ENOMEM; + } + + spin_lock_irq(&phba->hbalock); + sysfs_mbox->mbext = ext; + } + + if (sysfs_mbox->extoff != off) { + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -EAGAIN; + } + + memcpy((uint8_t *) sysfs_mbox->mbext + off, buf, count); + sysfs_mbox->extoff = off + count; + + spin_unlock_irq(&phba->hbalock); + + return count; + } + + spin_unlock_irq(&phba->hbalock); + + if (sysfs_mbox->state == SMBOX_WRITING_BUFF) + return lpfc_syfs_mbox_copy_txmit_buff(phba, + sysfs_mbox, buf, off, count); + + if ((count + off) > sizeof(struct lpfc_sysfs_mbox_data)) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ERANGE; + } + + if (off == 0) { mbox = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); - if (!mbox) + if (!mbox) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); return -ENOMEM; + } memset(mbox, 0, sizeof (LPFC_MBOXQ_t)); } spin_lock_irq(&phba->hbalock); if (off == 0) { - if (phba->sysfs_mbox.mbox) + if (sysfs_mbox->mbox) mempool_free(mbox, phba->mbox_mem_pool); else - phba->sysfs_mbox.mbox = mbox; - phba->sysfs_mbox.state = SMBOX_WRITING; + sysfs_mbox->mbox = mbox; + sysfs_mbox->state = SMBOX_WRITING; } else { - if (phba->sysfs_mbox.state != SMBOX_WRITING || - phba->sysfs_mbox.offset != off || - phba->sysfs_mbox.mbox == NULL) { - sysfs_mbox_idle(phba); + if (sysfs_mbox->state != SMBOX_WRITING || + sysfs_mbox->offset != off || + sysfs_mbox->mbox == NULL) { + sysfs_mbox_idle(phba, sysfs_mbox); spin_unlock_irq(&phba->hbalock); return -EAGAIN; } } - memcpy((uint8_t *) & phba->sysfs_mbox.mbox->mb + off, + memcpy((uint8_t *) & sysfs_mbox->mbox_data + off, buf, count); - phba->sysfs_mbox.offset = off + count; + sysfs_mbox->offset = off + count; + + if (sysfs_mbox->offset == sizeof(struct lpfc_sysfs_mbox_data)) { + memcpy((uint8_t *) & sysfs_mbox->mbox->mb, + (uint8_t *) &sysfs_mbox->mbox_data.mbox, + sizeof(MAILBOX_t)); + } + + if ((sysfs_mbox->offset == sizeof(struct lpfc_sysfs_mbox_data)) && + (sysfs_mbox->mbox_data.in_ext_wlen || + sysfs_mbox->mbox_data.out_ext_wlen)) { + + if (!sysfs_mbox->mbext) { + spin_unlock_irq(&phba->hbalock); + + ext = kzalloc(MAILBOX_EXT_SIZE, GFP_KERNEL); + if (!ext) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ENOMEM; + } + + spin_lock_irq(&phba->hbalock); + sysfs_mbox->mbext = ext; + } + } + + if ((sysfs_mbox->offset == sizeof(struct lpfc_sysfs_mbox_data)) && + (sysfs_mbox->mbox_data.in_ext_wlen)) { + sysfs_mbox->state = SMBOX_WRITING_MBEXT; + } + + if ((sysfs_mbox->offset == sizeof(struct lpfc_sysfs_mbox_data)) && + (sysfs_mbox->mbox->mb.mbxCommand == MBX_RUN_BIU_DIAG64)) { + sysfs_mbox->state = SMBOX_WRITING_BUFF; + spin_unlock_irq(&phba->hbalock); + + /* Allocate txmit buffer */ + sysfs_mbox->txmit_buff = + kzalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (!sysfs_mbox->txmit_buff) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ENOMEM; + } + INIT_LIST_HEAD(&sysfs_mbox->txmit_buff->list); + sysfs_mbox->txmit_buff->virt = + lpfc_mbuf_alloc(phba, 0, + &(sysfs_mbox->txmit_buff->phys)); + if (!sysfs_mbox->txmit_buff->virt) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ENOMEM; + } + + /* Allocate rcv buffer */ + sysfs_mbox->rcv_buff = + kzalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (!sysfs_mbox->rcv_buff) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ENOMEM; + } + INIT_LIST_HEAD(&sysfs_mbox->rcv_buff->list); + sysfs_mbox->rcv_buff->virt = + lpfc_mbuf_alloc(phba, 0, + &(sysfs_mbox->rcv_buff->phys)); + if (!sysfs_mbox->rcv_buff->virt) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ENOMEM; + } + return count; + } + if ((sysfs_mbox->offset == sizeof(struct lpfc_sysfs_mbox_data)) && + (sysfs_mbox->mbox->mb.mbxCommand == MBX_READ_EVENT_LOG)) { + sysfs_mbox->state = SMBOX_WRITING; + spin_unlock_irq(&phba->hbalock); + + + /* Allocate rcv buffer */ + sysfs_mbox->rcv_buff = + kzalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (!sysfs_mbox->rcv_buff) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ENOMEM; + } + INIT_LIST_HEAD(&sysfs_mbox->rcv_buff->list); + sysfs_mbox->rcv_buff->virt = + lpfc_mbuf_alloc(phba, 0, + &(sysfs_mbox->rcv_buff->phys)); + if (!sysfs_mbox->rcv_buff->virt) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -ENOMEM; + } + return count; + } spin_unlock_irq(&phba->hbalock); @@ -3095,6 +3805,42 @@ sysfs_mbox_read(struct kobject *kobj, st struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; struct lpfc_hba *phba = vport->phba; int rc; + int wait_4_menlo_maint = 0; + struct lpfc_sysfs_mbox *sysfs_mbox; + ssize_t ret; + sysfs_mbox = lpfc_get_sysfs_mbox(phba, 0); + + if (!sysfs_mbox) + return -EPERM; + + /* + * If sysfs expect the writing of buffer and + * app doesnot know how to do it, fail the mailbox + * command. + */ + if ((sysfs_mbox->state == SMBOX_WRITING_BUFF) && + (sysfs_mbox->extoff == 0)) { + spin_lock_irq(&phba->hbalock); + sysfs_mbox_idle(phba, sysfs_mbox); + spin_unlock_irq(&phba->hbalock); + return -EINVAL; + } + if (sysfs_mbox->state == SMBOX_READING_BUFF) { + ret = lpfc_syfs_mbox_copy_rcv_buff(phba, sysfs_mbox, + buf, off, count); + lpfc_printf_log(phba, KERN_INFO, LOG_INIT, + "1245 mbox: cmd 0x%x, 0x%x ret %x\n", + sysfs_mbox->mbox->mb.mbxCommand, + sysfs_mbox->mbox->mb.un.varWords[0], + (uint32_t)ret); + return ret; + } + + if (sysfs_mbox->state == SMBOX_READING_MBEXT) { + ret = lpfc_syfs_mbox_copy_extdata(phba, sysfs_mbox, + buf, off, count); + return ret; + } if (off > MAILBOX_CMD_SIZE) return -ERANGE; @@ -3111,16 +3857,18 @@ sysfs_mbox_read(struct kobject *kobj, st spin_lock_irq(&phba->hbalock); if (phba->over_temp_state == HBA_OVER_TEMP) { - sysfs_mbox_idle(phba); + sysfs_mbox_idle(phba, sysfs_mbox); spin_unlock_irq(&phba->hbalock); return -EACCES; } if (off == 0 && - phba->sysfs_mbox.state == SMBOX_WRITING && - phba->sysfs_mbox.offset >= 2 * sizeof(uint32_t)) { + ((sysfs_mbox->state == SMBOX_WRITING) || + (sysfs_mbox->state == SMBOX_WRITING_MBEXT) || + (sysfs_mbox->state == SMBOX_WRITING_BUFF) ) && + sysfs_mbox->offset >= 2 * sizeof(uint32_t)) { - switch (phba->sysfs_mbox.mbox->mb.mbxCommand) { + switch (sysfs_mbox->mbox->mb.mbxCommand) { /* Offline only */ case MBX_INIT_LINK: case MBX_DOWN_LINK: @@ -3133,12 +3881,11 @@ sysfs_mbox_read(struct kobject *kobj, st case MBX_RUN_DIAGS: case MBX_RESTART: case MBX_SET_MASK: - case MBX_SET_DEBUG: if (!(vport->fc_flag & FC_OFFLINE_MODE)) { printk(KERN_WARNING "mbox_read:Command 0x%x " "is illegal in on-line state\n", - phba->sysfs_mbox.mbox->mb.mbxCommand); - sysfs_mbox_idle(phba); + sysfs_mbox->mbox->mb.mbxCommand); + sysfs_mbox_idle(phba,sysfs_mbox); spin_unlock_irq(&phba->hbalock); return -EPERM; } @@ -3160,11 +3907,63 @@ sysfs_mbox_read(struct kobject *kobj, st case MBX_LOAD_EXP_ROM: case MBX_BEACON: case MBX_DEL_LD_ENTRY: - case MBX_SET_VARIABLE: + case MBX_SET_DEBUG: case MBX_WRITE_WWN: + case MBX_READ_EVENT_LOG_STATUS: + case MBX_WRITE_EVENT_LOG: case MBX_PORT_CAPABILITIES: case MBX_PORT_IOV_CONTROL: break; + case MBX_SET_VARIABLE: + lpfc_printf_log(phba, KERN_INFO, LOG_INIT, + "1226 mbox: set_variable 0x%x, 0x%x\n", + sysfs_mbox->mbox->mb.un.varWords[0], + sysfs_mbox->mbox->mb.un.varWords[1]); + if ((sysfs_mbox->mbox->mb.un.varWords[0] == + SETVAR_MLOMNT) && + (sysfs_mbox->mbox->mb.un.varWords[1] == 1)) { + wait_4_menlo_maint = 1; + phba->wait_4_mlo_maint_flg = 1; + } + break; + case MBX_RUN_BIU_DIAG64: + if (sysfs_mbox->mbox->mb.un.varBIUdiag.un.s2. + xmit_bde64.tus.f.bdeSize) { + sysfs_mbox->mbox->mb.un.varBIUdiag.un.s2. + xmit_bde64.addrHigh = + putPaddrHigh(sysfs_mbox-> + txmit_buff->phys); + sysfs_mbox->mbox->mb.un.varBIUdiag.un.s2. + xmit_bde64.addrLow = + putPaddrLow(sysfs_mbox-> + txmit_buff->phys); + } + + if (sysfs_mbox->mbox->mb.un.varBIUdiag.un.s2. + rcv_bde64.tus.f.bdeSize) { + sysfs_mbox->mbox->mb.un.varBIUdiag.un.s2. + rcv_bde64.addrHigh = + putPaddrHigh(sysfs_mbox-> + rcv_buff->phys); + sysfs_mbox->mbox->mb.un.varBIUdiag.un.s2. + rcv_bde64.addrLow = + putPaddrLow(sysfs_mbox->rcv_buff->phys); + } + break; + case MBX_READ_EVENT_LOG: + + if (sysfs_mbox->mbox->mb.un.varRdEventLog. + rcv_bde64.tus.f.bdeSize) { + sysfs_mbox->mbox->mb.un.varRdEventLog. + rcv_bde64.addrHigh = + putPaddrHigh(sysfs_mbox-> + rcv_buff->phys); + sysfs_mbox->mbox->mb.un.varRdEventLog. + rcv_bde64.addrLow = + putPaddrLow(sysfs_mbox->rcv_buff->phys); + } + break; + case MBX_READ_SPARM64: case MBX_READ_LA: case MBX_READ_LA64: @@ -3173,38 +3972,51 @@ sysfs_mbox_read(struct kobject *kobj, st case MBX_CONFIG_PORT: case MBX_RUN_BIU_DIAG: printk(KERN_WARNING "mbox_read: Illegal Command 0x%x\n", - phba->sysfs_mbox.mbox->mb.mbxCommand); - sysfs_mbox_idle(phba); + sysfs_mbox->mbox->mb.mbxCommand); + sysfs_mbox_idle(phba,sysfs_mbox); spin_unlock_irq(&phba->hbalock); return -EPERM; default: printk(KERN_WARNING "mbox_read: Unknown Command 0x%x\n", - phba->sysfs_mbox.mbox->mb.mbxCommand); - sysfs_mbox_idle(phba); + sysfs_mbox->mbox->mb.mbxCommand); + sysfs_mbox_idle(phba,sysfs_mbox); spin_unlock_irq(&phba->hbalock); return -EPERM; } + if (sysfs_mbox->mbox_data.in_ext_wlen || + sysfs_mbox->mbox_data.out_ext_wlen) { + sysfs_mbox->mbox->context2 = sysfs_mbox->mbext; + sysfs_mbox->mbox->in_ext_byte_len = + sysfs_mbox->mbox_data.in_ext_wlen * + sizeof(uint32_t); + sysfs_mbox->mbox->out_ext_byte_len = + sysfs_mbox->mbox_data.out_ext_wlen * + sizeof(uint32_t); + sysfs_mbox->mbox->mbox_offset_word = + sysfs_mbox->mbox_data.mboffset; + } + /* If HBA encountered an error attention, allow only DUMP * or RESTART mailbox commands until the HBA is restarted. */ if (phba->pport->stopped && - phba->sysfs_mbox.mbox->mb.mbxCommand != MBX_DUMP_MEMORY && - phba->sysfs_mbox.mbox->mb.mbxCommand != MBX_RESTART && - phba->sysfs_mbox.mbox->mb.mbxCommand != MBX_WRITE_VPARMS && - phba->sysfs_mbox.mbox->mb.mbxCommand != MBX_WRITE_WWN) + sysfs_mbox->mbox->mb.mbxCommand != MBX_DUMP_MEMORY && + sysfs_mbox->mbox->mb.mbxCommand != MBX_RESTART && + sysfs_mbox->mbox->mb.mbxCommand != MBX_WRITE_VPARMS && + sysfs_mbox->mbox->mb.mbxCommand != MBX_WRITE_WWN) lpfc_printf_log(phba, KERN_WARNING, LOG_MBOX, "1259 mbox: Issued mailbox cmd " "0x%x while in stopped state.\n", - phba->sysfs_mbox.mbox->mb.mbxCommand); + sysfs_mbox->mbox->mb.mbxCommand); - phba->sysfs_mbox.mbox->vport = vport; + sysfs_mbox->mbox->vport = vport; /* Don't allow mailbox commands to be sent when blocked * or when in the middle of discovery */ if (phba->sli.sli_flag & LPFC_BLOCK_MGMT_IO) { - sysfs_mbox_idle(phba); + sysfs_mbox_idle(phba,sysfs_mbox); spin_unlock_irq(&phba->hbalock); return -EAGAIN; } @@ -3214,43 +4026,86 @@ sysfs_mbox_read(struct kobject *kobj, st spin_unlock_irq(&phba->hbalock); rc = lpfc_sli_issue_mbox (phba, - phba->sysfs_mbox.mbox, + sysfs_mbox->mbox, MBX_POLL); spin_lock_irq(&phba->hbalock); } else { spin_unlock_irq(&phba->hbalock); rc = lpfc_sli_issue_mbox_wait (phba, - phba->sysfs_mbox.mbox, + sysfs_mbox->mbox, lpfc_mbox_tmo_val(phba, - phba->sysfs_mbox.mbox->mb.mbxCommand) * HZ); + sysfs_mbox->mbox->mb.mbxCommand) * HZ); spin_lock_irq(&phba->hbalock); } if (rc != MBX_SUCCESS) { if (rc == MBX_TIMEOUT) { - phba->sysfs_mbox.mbox = NULL; + sysfs_mbox->mbox = NULL; } - sysfs_mbox_idle(phba); + sysfs_mbox_idle(phba,sysfs_mbox); spin_unlock_irq(&phba->hbalock); return (rc == MBX_TIMEOUT) ? -ETIME : -ENODEV; } - phba->sysfs_mbox.state = SMBOX_READING; + if (wait_4_menlo_maint) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "1229 waiting for menlo mnt\n"); + spin_unlock_irq(&phba->hbalock); + if (phba->wait_4_mlo_maint_flg) + wait_event_interruptible_timeout( + phba->wait_4_mlo_m_q, + phba->wait_4_mlo_maint_flg == 0, + 60 * HZ); + spin_lock_irq(&phba->hbalock); + if (phba->wait_4_mlo_maint_flg) { + sysfs_mbox_idle(phba,sysfs_mbox); + phba->wait_4_mlo_maint_flg = 0; + spin_unlock_irq(&phba->hbalock); + return -EINTR; + } else + spin_unlock_irq(&phba->hbalock); + + spin_lock_irq(&phba->hbalock); + if (phba->wait_4_mlo_maint_flg != 0) { + sysfs_mbox_idle(phba,sysfs_mbox); + phba->wait_4_mlo_maint_flg = 0; + spin_unlock_irq(&phba->hbalock); + return -ETIME; + } + + } + sysfs_mbox->state = SMBOX_READING; } - else if (phba->sysfs_mbox.offset != off || - phba->sysfs_mbox.state != SMBOX_READING) { - printk(KERN_WARNING "mbox_read: Bad State\n"); - sysfs_mbox_idle(phba); + else if (sysfs_mbox->offset != off || + sysfs_mbox->state != SMBOX_READING) { + sysfs_mbox_idle(phba,sysfs_mbox); spin_unlock_irq(&phba->hbalock); return -EAGAIN; } - memcpy(buf, (uint8_t *) & phba->sysfs_mbox.mbox->mb + off, count); + memcpy(buf, (uint8_t *) & sysfs_mbox->mbox->mb + off, count); + + sysfs_mbox->offset = off + count; - phba->sysfs_mbox.offset = off + count; + if ((sysfs_mbox->offset == MAILBOX_CMD_SIZE) && + ((sysfs_mbox->mbox->mb.mbxCommand == MBX_RUN_BIU_DIAG64) || + (sysfs_mbox->mbox->mb.mbxCommand == MBX_READ_EVENT_LOG))) { + sysfs_mbox->state = SMBOX_READING_BUFF; + sysfs_mbox->extoff = 0; + spin_unlock_irq(&phba->hbalock); + return count; + } + + if ((sysfs_mbox->offset == MAILBOX_CMD_SIZE) && + sysfs_mbox->mbox_data.out_ext_wlen) { + sysfs_mbox->state = SMBOX_READING_MBEXT; + sysfs_mbox->extoff = 0; + spin_unlock_irq(&phba->hbalock); + return count; + } - if (phba->sysfs_mbox.offset == MAILBOX_CMD_SIZE) - sysfs_mbox_idle(phba); + if (sysfs_mbox->offset == MAILBOX_CMD_SIZE) + sysfs_mbox_idle(phba,sysfs_mbox); spin_unlock_irq(&phba->hbalock); @@ -3262,7 +4117,7 @@ static struct bin_attribute sysfs_mbox_a .name = "mbox", .mode = S_IRUSR | S_IWUSR, }, - .size = MAILBOX_CMD_SIZE, + .size = MAILBOX_MAX_XMIT_SIZE, .read = sysfs_mbox_read, .write = sysfs_mbox_write, }; @@ -3317,6 +4172,7 @@ lpfc_free_sysfs_attr(struct lpfc_vport * &sysfs_drvr_stat_data_attr); sysfs_remove_bin_file(&shost->shost_dev.kobj, &sysfs_mbox_attr); sysfs_remove_bin_file(&shost->shost_dev.kobj, &sysfs_ctlreg_attr); + sysfs_remove_bin_file(&shost->shost_dev.kobj, &sysfs_menlo_attr); } @@ -3935,7 +4791,6 @@ lpfc_get_cfgparam(struct lpfc_hba *phba) phba->cfg_soft_wwpn = 0L; lpfc_sg_seg_cnt_init(phba, lpfc_sg_seg_cnt); /* Also reinitialize the host templates with new values. */ - lpfc_vport_template.sg_tablesize = phba->cfg_sg_seg_cnt; lpfc_template.sg_tablesize = phba->cfg_sg_seg_cnt; /* * Since the sg_tablesize is module parameter, the sg_dma_buf_size @@ -3970,5 +4825,6 @@ lpfc_get_vport_cfgparam(struct lpfc_vpor lpfc_max_luns_init(vport, lpfc_max_luns); lpfc_scan_down_init(vport, lpfc_scan_down); lpfc_enable_da_id_init(vport, lpfc_enable_da_id); + lpfc_enable_auth_init(vport, lpfc_enable_auth); return; } --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_auth_access.c @@ -0,0 +1,598 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2006-2007 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.emulex.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ +#include +#include +#include +#include + +#include +#include +#include /* workqueue stuff, HZ */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lpfc_hw.h" +#include "lpfc_sli.h" +#include "lpfc_nl.h" +#include "lpfc_disc.h" +#include "lpfc_scsi.h" +#include "lpfc.h" +#include "lpfc_logmsg.h" +#include "lpfc_crtn.h" +#include "lpfc_vport.h" +#include "lpfc_auth_access.h" + +/* fc security */ +struct workqueue_struct *security_work_q = NULL; +struct list_head fc_security_user_list; +int fc_service_state = FC_SC_SERVICESTATE_UNKNOWN; +static int fc_service_pid; +DEFINE_SPINLOCK(fc_security_user_lock); + +static inline struct lpfc_vport * +lpfc_fc_find_vport(unsigned long host_no) +{ + struct lpfc_vport *vport; + struct Scsi_Host *shost; + + list_for_each_entry(vport, &fc_security_user_list, sc_users) { + shost = lpfc_shost_from_vport(vport); + if (shost && (shost->host_no == host_no)) + return vport; + } + + return NULL; +} + + +/** + * lpfc_fc_sc_add_timer + * + * + **/ + +void +lpfc_fc_sc_add_timer(struct fc_security_request *req, int timeout, + void (*complete)(struct fc_security_request *)) +{ + + init_timer(&req->timer); + + + req->timer.data = (unsigned long)req; + req->timer.expires = jiffies + timeout; + req->timer.function = (void (*)(unsigned long)) complete; + + add_timer(&req->timer); +} +/** + * lpfc_fc_sc_req_times_out + * + * + **/ + +void +lpfc_fc_sc_req_times_out(struct fc_security_request *req) +{ + + unsigned long flags; + int found = 0; + struct fc_security_request *fc_sc_req; + struct lpfc_vport *vport = req->vport; + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + + if (!req) + return; + + spin_lock_irqsave(shost->host_lock, flags); + + /* To avoid a completion race check to see if request is on the list */ + + list_for_each_entry(fc_sc_req, &vport->sc_response_wait_queue, rlist) + if (fc_sc_req == req) { + found = 1; + break; + } + + if (!found) { + spin_unlock_irqrestore(shost->host_lock, flags); + return; + } + + list_del(&fc_sc_req->rlist); + + spin_unlock_irqrestore(shost->host_lock, flags); + + lpfc_printf_vlog(vport, KERN_WARNING, LOG_SECURITY, + "1019 Request tranid %d timed out\n", + fc_sc_req->tran_id); + + switch (fc_sc_req->req_type) { + + case FC_NL_SC_GET_CONFIG_REQ: + lpfc_security_config(shost, -ETIMEDOUT, + fc_sc_req->data); + break; + + case FC_NL_SC_DHCHAP_MAKE_CHALLENGE_REQ: + lpfc_dhchap_make_challenge(shost, -ETIMEDOUT, + fc_sc_req->data, 0); + break; + + case FC_NL_SC_DHCHAP_MAKE_RESPONSE_REQ: + lpfc_dhchap_make_response(shost, -ETIMEDOUT, + fc_sc_req->data, 0); + break; + + case FC_NL_SC_DHCHAP_AUTHENTICATE_REQ: + lpfc_dhchap_authenticate(shost, -ETIMEDOUT, fc_sc_req->data, 0); + break; + } + + kfree(fc_sc_req); + +} + + +static inline struct fc_security_request * +lpfc_fc_find_sc_request(u32 tran_id, u32 type, struct lpfc_vport *vport) +{ + struct fc_security_request *fc_sc_req; + + list_for_each_entry(fc_sc_req, &vport->sc_response_wait_queue, rlist) + if (fc_sc_req->tran_id == tran_id && + fc_sc_req->req_type == type) + return fc_sc_req; + return NULL; +} + + + +/** + * lpfc_fc_sc_request + * + * + **/ + +int +lpfc_fc_sc_request(struct lpfc_vport *vport, + u32 msg_type, + struct fc_auth_req *auth_req, + u32 auth_req_len, /* includes length of struct fc_auth_req */ + struct fc_auth_rsp *auth_rsp, + u32 auth_rsp_len) /* includes length of struct fc_auth_rsp */ +{ + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + struct fc_security_request *fc_sc_req; + struct fc_nl_sc_message *fc_nl_sc_msg; + unsigned long flags; + u32 len; + u32 seq = ++vport->sc_tran_id; + + if (fc_service_state != FC_SC_SERVICESTATE_ONLINE) + return -EINVAL; + + if (vport->port_state == FC_PORTSTATE_DELETED) + return -EINVAL; + + fc_sc_req = kzalloc(sizeof(struct fc_security_request), GFP_KERNEL); + if (!fc_sc_req) + return -ENOMEM; + + fc_sc_req->req_type = msg_type; + fc_sc_req->data = auth_rsp; + fc_sc_req->data_len = auth_rsp_len; + fc_sc_req->vport = vport; + fc_sc_req->tran_id = seq; + + len = sizeof(struct fc_nl_sc_message) + auth_req_len; + fc_nl_sc_msg = kzalloc(sizeof(struct fc_nl_sc_message) + auth_req_len, + GFP_KERNEL); + if (!fc_nl_sc_msg) + return -ENOMEM; + fc_nl_sc_msg->msgtype = msg_type; + fc_nl_sc_msg->data_len = auth_req_len; + memcpy(fc_nl_sc_msg->data, auth_req, auth_req_len); + fc_nl_sc_msg->tran_id = seq; + + spin_lock_irqsave(shost->host_lock, flags); + list_add_tail(&fc_sc_req->rlist, &vport->sc_response_wait_queue); + spin_unlock_irqrestore(shost->host_lock, flags); + scsi_nl_send_vendor_msg(fc_service_pid, shost->host_no, + (SCSI_NL_VID_TYPE_PCI | PCI_VENDOR_ID_EMULEX), + (char *) fc_nl_sc_msg, len); + lpfc_fc_sc_add_timer(fc_sc_req, FC_SC_REQ_TIMEOUT, + lpfc_fc_sc_req_times_out); + return 0; +} + +/** + * lpfc_fc_security_get_config + * + * + **/ + +int +lpfc_fc_security_get_config(struct Scsi_Host *shost, + struct fc_auth_req *auth_req, + u32 auth_req_len, + struct fc_auth_rsp *auth_rsp, + u32 auth_rsp_len) +{ + + return(lpfc_fc_sc_request((struct lpfc_vport *) shost->hostdata, + FC_NL_SC_GET_CONFIG_REQ, auth_req, + auth_req_len, auth_rsp, auth_rsp_len)); + +} +EXPORT_SYMBOL(lpfc_fc_security_get_config); + +/** + * lpfc_fc_security_dhchap_make_challenge + * + * + **/ + +int +lpfc_fc_security_dhchap_make_challenge(struct Scsi_Host *shost, + struct fc_auth_req *auth_req, + u32 auth_req_len, + struct fc_auth_rsp *auth_rsp, + u32 auth_rsp_len) +{ + + return(lpfc_fc_sc_request((struct lpfc_vport *) shost->hostdata, + FC_NL_SC_DHCHAP_MAKE_CHALLENGE_REQ, + auth_req, auth_req_len, auth_rsp, auth_rsp_len)); + +} +EXPORT_SYMBOL(lpfc_fc_security_dhchap_make_challenge); + +/** + * lpfc_fc_security_dhchap_make_response + * + * + **/ + +int +lpfc_fc_security_dhchap_make_response(struct Scsi_Host *shost, + struct fc_auth_req *auth_req, + u32 auth_req_len, + struct fc_auth_rsp *auth_rsp, + u32 auth_rsp_len) +{ + + return(lpfc_fc_sc_request((struct lpfc_vport *) shost->hostdata, + FC_NL_SC_DHCHAP_MAKE_RESPONSE_REQ, + auth_req, auth_req_len, auth_rsp, auth_rsp_len)); + +} +EXPORT_SYMBOL(lpfc_fc_security_dhchap_make_response); + + +/** + * lpfc_fc_security_dhchap_authenticate + * + * + **/ + +int +lpfc_fc_security_dhchap_authenticate(struct Scsi_Host *shost, + struct fc_auth_req *auth_req, + u32 auth_req_len, + struct fc_auth_rsp *auth_rsp, + u32 auth_rsp_len) +{ + + return(lpfc_fc_sc_request((struct lpfc_vport *) shost->hostdata, + FC_NL_SC_DHCHAP_AUTHENTICATE_REQ, + auth_req, auth_req_len, auth_rsp, auth_rsp_len)); + +} +EXPORT_SYMBOL(lpfc_fc_security_dhchap_authenticate); + +/** + * lpfc_fc_queue_security_work - Queue work to the fc_host security workqueue. + * @shost: Pointer to Scsi_Host bound to fc_host. + * @work: Work to queue for execution. + * + * Return value: + * 1 - work queued for execution + * 0 - work is already queued + * -EINVAL - work queue doesn't exist + **/ +int +lpfc_fc_queue_security_work(struct lpfc_vport *vport, struct work_struct *work) +{ + if (unlikely(!security_work_q)) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1021 ERROR: attempted to queue security work, " + "when no workqueue created.\n"); + dump_stack(); + + return -EINVAL; + } + + return queue_work(security_work_q, work); + +} + + + + /** + * lpfc_fc_sc_schedule_notify_all + * + * + **/ + +void +lpfc_fc_sc_schedule_notify_all(int message) +{ + struct lpfc_vport *vport; + unsigned long flags; + + spin_lock_irqsave(&fc_security_user_lock, flags); + + list_for_each_entry(vport, &fc_security_user_list, sc_users) { + + switch (message) { + + case FC_NL_SC_REG: + lpfc_fc_queue_security_work(vport, + &vport->sc_online_work); + break; + + case FC_NL_SC_DEREG: + lpfc_fc_queue_security_work(vport, + &vport->sc_offline_work); + break; + } + } + + spin_unlock_irqrestore(&fc_security_user_lock, flags); +} + + + +/** + * lpfc_fc_sc_security_online + * + * + **/ + +void +lpfc_fc_sc_security_online(struct work_struct *work) +{ + struct lpfc_vport *vport = container_of(work, struct lpfc_vport, + sc_online_work); + lpfc_security_service_online(lpfc_shost_from_vport(vport)); + return; +} + +/** + * lpfc_fc_sc_security_offline + * + * + **/ +void +lpfc_fc_sc_security_offline(struct work_struct *work) +{ + struct lpfc_vport *vport = container_of(work, struct lpfc_vport, + sc_offline_work); + lpfc_security_service_offline(lpfc_shost_from_vport(vport)); + return; +} + + +/** + * lpfc_fc_sc_process_msg + * + * + **/ +static void +lpfc_fc_sc_process_msg(struct work_struct *work) +{ + struct fc_sc_msg_work_q_wrapper *wqw = + container_of(work, struct fc_sc_msg_work_q_wrapper, work); + + switch (wqw->msgtype) { + + case FC_NL_SC_GET_CONFIG_RSP: + lpfc_security_config(lpfc_shost_from_vport(wqw->fc_sc_req-> + vport), wqw->status, + wqw->fc_sc_req->data); + break; + + case FC_NL_SC_DHCHAP_MAKE_CHALLENGE_RSP: + lpfc_dhchap_make_challenge(lpfc_shost_from_vport(wqw-> + fc_sc_req->vport), wqw->status, + wqw->fc_sc_req->data, wqw->data_len); + break; + + case FC_NL_SC_DHCHAP_MAKE_RESPONSE_RSP: + lpfc_dhchap_make_response(lpfc_shost_from_vport(wqw-> + fc_sc_req->vport), wqw->status, + wqw->fc_sc_req->data, wqw->data_len); + break; + + case FC_NL_SC_DHCHAP_AUTHENTICATE_RSP: + lpfc_dhchap_authenticate(lpfc_shost_from_vport(wqw->fc_sc_req-> + vport), + wqw->status, + wqw->fc_sc_req->data, wqw->data_len); + break; + } + + kfree(wqw->fc_sc_req); + kfree(wqw); + + return; +} + + +/** + * lpfc_fc_sc_schedule_msg + * + * + **/ + +int +lpfc_fc_sc_schedule_msg(struct Scsi_Host *shost, + struct fc_nl_sc_message *fc_nl_sc_msg, int rcvlen) +{ + struct fc_security_request *fc_sc_req; + u32 req_type; + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + int err = 0; + struct fc_sc_msg_work_q_wrapper *wqw; + unsigned long flags; + + if (vport->port_state == FC_PORTSTATE_DELETED) { + printk(KERN_WARNING + "%s: Host being deleted.\n", __func__); + return -EBADR; + } + + wqw = kzalloc(sizeof(struct fc_sc_msg_work_q_wrapper), GFP_KERNEL); + + if (!wqw) + return -ENOMEM; + + switch (fc_nl_sc_msg->msgtype) { + case FC_NL_SC_GET_CONFIG_RSP: + req_type = FC_NL_SC_GET_CONFIG_REQ; + break; + + case FC_NL_SC_DHCHAP_MAKE_CHALLENGE_RSP: + req_type = FC_NL_SC_DHCHAP_MAKE_CHALLENGE_REQ; + break; + + case FC_NL_SC_DHCHAP_MAKE_RESPONSE_RSP: + req_type = FC_NL_SC_DHCHAP_MAKE_RESPONSE_REQ; + break; + + case FC_NL_SC_DHCHAP_AUTHENTICATE_RSP: + req_type = FC_NL_SC_DHCHAP_AUTHENTICATE_REQ; + break; + + default: + kfree(wqw); + return -EINVAL; + } + + spin_lock_irqsave(shost->host_lock, flags); + + fc_sc_req = lpfc_fc_find_sc_request(fc_nl_sc_msg->tran_id, + req_type, vport); + + if (!fc_sc_req) { + spin_unlock_irqrestore(shost->host_lock, flags); + lpfc_printf_vlog(vport, KERN_WARNING, LOG_SECURITY, + "1022 Security request does not exist.\n"); + kfree(wqw); + return -EBADR; + } + + list_del(&fc_sc_req->rlist); + + spin_unlock_irqrestore(shost->host_lock, flags); + + del_singleshot_timer_sync(&fc_sc_req->timer); + + wqw->status = 0; + wqw->fc_sc_req = fc_sc_req; + wqw->data_len = rcvlen; + wqw->msgtype = fc_nl_sc_msg->msgtype; + + if (!fc_sc_req->data || + (fc_sc_req->data_len < fc_nl_sc_msg->data_len)) { + wqw->status = -ENOBUFS; + wqw->data_len = 0; + lpfc_printf_vlog(vport, KERN_WARNING, LOG_SECURITY, + "1023 Warning - data may have been truncated. " + "data:%p reqdl:%x mesdl:%x\n", + fc_sc_req->data, + fc_sc_req->data_len, fc_nl_sc_msg->data_len); + } else { + memcpy(fc_sc_req->data, fc_nl_sc_msg->data, + fc_nl_sc_msg->data_len); + } + + INIT_WORK(&wqw->work, lpfc_fc_sc_process_msg); + lpfc_fc_queue_security_work(vport, &wqw->work); + + return err; +} + +int +lpfc_rcv_nl_msg(struct Scsi_Host *shost, void *payload, + uint32_t len, uint32_t pid) +{ + struct fc_nl_sc_message *msg = (struct fc_nl_sc_message *)payload; + int err = 0; + + switch (msg->msgtype) { + case FC_NL_SC_REG: + fc_service_pid = pid; + fc_service_state = FC_SC_SERVICESTATE_ONLINE; + lpfc_fc_sc_schedule_notify_all(FC_NL_SC_REG); + break; + case FC_NL_SC_DEREG: + fc_service_pid = pid; + fc_service_state = FC_SC_SERVICESTATE_OFFLINE; + lpfc_fc_sc_schedule_notify_all(FC_NL_SC_DEREG); + break; + case FC_NL_SC_GET_CONFIG_RSP: + case FC_NL_SC_DHCHAP_MAKE_CHALLENGE_RSP: + case FC_NL_SC_DHCHAP_MAKE_RESPONSE_RSP: + case FC_NL_SC_DHCHAP_AUTHENTICATE_RSP: + err = lpfc_fc_sc_schedule_msg(shost, msg, len); + break; + default: + printk(KERN_WARNING "%s: unknown msg type 0x%x len %d\n", + __func__, msg->msgtype, len); + break; + } + return err; +} + +void +lpfc_rcv_nl_event(struct notifier_block *this, + unsigned long event, + void *ptr) +{ + struct netlink_notify *n = ptr; + if ((event == NETLINK_URELEASE) && + (n->protocol == NETLINK_SCSITRANSPORT) && (n->pid)) { + printk(KERN_WARNING "Warning - Security Service Offline\n"); + fc_service_state = FC_SC_SERVICESTATE_OFFLINE; + lpfc_fc_sc_schedule_notify_all(FC_NL_SC_DEREG); + } +} --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_auth_access.h @@ -0,0 +1,245 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2006-2007 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.emulex.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ + +#define to_fc_internal(tmpl) container_of(tmpl, struct fc_internal, t) + +/* scsi_nl_hdr->version value */ +#define SCSI_NL_VERSION 1 + +/* scsi_nl_hdr->magic value */ +#define SCSI_NL_MAGIC 0xA1B2 + +/* scsi_nl_hdr->transport value */ +#define SCSI_NL_TRANSPORT 0 +#define SCSI_NL_TRANSPORT_FC 1 +#define SCSI_NL_MAX_TRANSPORTS 2 + +#define FC_NL_GROUP_CNT 0 + + /* Note: when specifying vendor_id to fc_host_post_vendor_event() + * be sure to read the Vendor Type and ID formatting requirements + * specified in scsi_netlink.h + */ + +#define FC_SC_REQ_TIMEOUT (60*HZ) + +enum fc_sc_service_state { + FC_SC_SERVICESTATE_UNKNOWN, + FC_SC_SERVICESTATE_ONLINE, + FC_SC_SERVICESTATE_OFFLINE, + FC_SC_SERVICESTATE_ERROR, +}; + +struct fc_security_request { + struct list_head rlist; + int pid; + u32 tran_id; + u32 req_type; + struct timer_list timer; + struct lpfc_vport *vport; + u32 data_len; + void *data; +}; + +struct fc_sc_msg_work_q_wrapper { + struct work_struct work; + struct fc_security_request *fc_sc_req; + u32 data_len; + int status; + u32 msgtype; +}; +struct fc_sc_notify_work_q_wrapper { + struct work_struct work; + struct Scsi_Host *shost; + int msg; +}; + +#define FC_DHCHAP 1 +#define FC_FCAP 2 +#define FC_FCPAP 3 +#define FC_KERBEROS 4 + +#define FC_AUTHMODE_UNKNOWN 0 +#define FC_AUTHMODE_NONE 1 +#define FC_AUTHMODE_ACTIVE 2 +#define FC_AUTHMODE_PASSIVE 3 + +#define FC_SP_HASH_MD5 0x5 +#define FC_SP_HASH_SHA1 0x6 + +#define DH_GROUP_NULL 0x00 +#define DH_GROUP_1024 0x01 +#define DH_GROUP_1280 0x02 +#define DH_GROUP_1536 0x03 +#define DH_GROUP_2048 0x04 + +#define MAX_AUTH_REQ_SIZE 1024 +#define MAX_AUTH_RSP_SIZE 1024 + +#define AUTH_FABRIC_WWN 0xFFFFFFFFFFFFFFFFLL + +struct fc_auth_req { + uint64_t local_wwpn; + uint64_t remote_wwpn; + union { + struct dhchap_challenge_req { + uint32_t transaction_id; + uint32_t dh_group_id; + uint32_t hash_id; + } dhchap_challenge; + struct dhchap_reply_req { + uint32_t transaction_id; + uint32_t dh_group_id; + uint32_t hash_id; + uint32_t bidirectional; + uint32_t received_challenge_len; + uint32_t received_public_key_len; + uint8_t data[0]; + } dhchap_reply; + struct dhchap_success_req { + uint32_t transaction_id; + uint32_t dh_group_id; + uint32_t hash_id; + uint32_t our_challenge_len; + uint32_t received_response_len; + uint32_t received_public_key_len; + uint32_t received_challenge_len; + uint8_t data[0]; + } dhchap_success; + }u; +} __attribute__ ((packed)); + +struct fc_auth_rsp { + uint64_t local_wwpn; + uint64_t remote_wwpn; + union { + struct authinfo { + uint8_t auth_mode; + uint16_t auth_timeout; + uint8_t bidirectional; + uint8_t type_priority[4]; + uint16_t type_len; + uint8_t hash_priority[4]; + uint16_t hash_len; + uint8_t dh_group_priority[8]; + uint16_t dh_group_len; + uint32_t reauth_interval; + } dhchap_security_config; + struct dhchap_challenge_rsp { + uint32_t transaction_id; + uint32_t our_challenge_len; + uint32_t our_public_key_len; + uint8_t data[0]; + } dhchap_challenge; + struct dhchap_reply_rsp { + uint32_t transaction_id; + uint32_t our_challenge_rsp_len; + uint32_t our_public_key_len; + uint32_t our_challenge_len; + uint8_t data[0]; + } dhchap_reply; + struct dhchap_success_rsp { + uint32_t transaction_id; + uint32_t authenticated; + uint32_t response_len; + uint8_t data[0]; + } dhchap_success; + }u; +}__attribute__ ((packed)); + +int +lpfc_fc_security_get_config(struct Scsi_Host *shost, + struct fc_auth_req *auth_req, + u32 req_len, + struct fc_auth_rsp *auth_rsp, + u32 rsp_len); +int +lpfc_fc_security_dhchap_make_challenge(struct Scsi_Host *shost, + struct fc_auth_req *auth_req, + u32 req_len, + struct fc_auth_rsp *auth_rsp, + u32 rsp_len); +int +lpfc_fc_security_dhchap_make_response(struct Scsi_Host *shost, + struct fc_auth_req *auth_req, + u32 req_len, + struct fc_auth_rsp *auth_rsp, + u32 rsp_len); +int +lpfc_fc_security_dhchap_authenticate(struct Scsi_Host *shost, + struct fc_auth_req *auth_req, + u32 req_len, + struct fc_auth_rsp *auth_rsp, + u32 rsp_len); + +int lpfc_fc_queue_security_work(struct lpfc_vport *, + struct work_struct *); + +/* + * FC Transport Message Types + */ + /* user -> kernel */ +#define FC_NL_EVENTS_REG 0x0001 +#define FC_NL_EVENTS_DEREG 0x0002 +#define FC_NL_SC_REG 0x0003 +#define FC_NL_SC_DEREG 0x0004 +#define FC_NL_SC_GET_CONFIG_RSP 0x0005 +#define FC_NL_SC_SET_CONFIG_RSP 0x0006 +#define FC_NL_SC_DHCHAP_MAKE_CHALLENGE_RSP 0x0007 +#define FC_NL_SC_DHCHAP_MAKE_RESPONSE_RSP 0x0008 +#define FC_NL_SC_DHCHAP_AUTHENTICATE_RSP 0x0009 + /* kernel -> user */ +//#define FC_NL_ASYNC_EVENT 0x0100 +#define FC_NL_SC_GET_CONFIG_REQ 0x0020 +#define FC_NL_SC_SET_CONFIG_REQ 0x0030 +#define FC_NL_SC_DHCHAP_MAKE_CHALLENGE_REQ 0x0040 +#define FC_NL_SC_DHCHAP_MAKE_RESPONSE_REQ 0x0050 +#define FC_NL_SC_DHCHAP_AUTHENTICATE_REQ 0x0060 + +/* + * Message Structures : + */ + +/* macro to round up message lengths to 8byte boundary */ +#define FC_NL_MSGALIGN(len) (((len) + 7) & ~7) + +#define FC_NETLINK_API_VERSION 1 + +/* Single Netlink Message type to send all FC Transport messages */ +#define FC_TRANSPORT_MSG NLMSG_MIN_TYPE + 1 + +/* SCSI_TRANSPORT_MSG event message header */ +/* +struct scsi_nl_hdr { + uint8_t version; + uint8_t transport; + uint16_t magic; + uint16_t msgtype; + uint16_t msglen; +} __attribute__((aligned(sizeof(uint64_t)))); +*/ +struct fc_nl_sc_message { + uint16_t msgtype; + uint16_t rsvd; + uint32_t tran_id; + uint32_t data_len; + uint8_t data[0]; +} __attribute__((aligned(sizeof(uint64_t)))); + --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_auth.c @@ -0,0 +1,838 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2006-2008 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.emulex.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ +/* See Fibre Channel protocol T11 FC-SP for details */ +#include +#include + +#include +#include +#include + +#include "lpfc_hw.h" +#include "lpfc_sli.h" +#include "lpfc_nl.h" +#include "lpfc_disc.h" +#include "lpfc.h" +#include "lpfc_crtn.h" +#include "lpfc_logmsg.h" +#include "lpfc_auth_access.h" +#include "lpfc_auth.h" + +void +lpfc_start_authentication(struct lpfc_vport *vport, + struct lpfc_nodelist *ndlp) +{ + uint32_t nego_payload_len; + uint8_t *nego_payload; + + nego_payload = kmalloc(MAX_AUTH_REQ_SIZE, GFP_KERNEL); + if (!nego_payload) + return; + vport->auth.trans_id++; + vport->auth.auth_msg_state = LPFC_AUTH_NEGOTIATE; + nego_payload_len = lpfc_build_auth_neg(vport, nego_payload); + lpfc_issue_els_auth(vport, ndlp, AUTH_NEGOTIATE, + nego_payload, nego_payload_len); + kfree(nego_payload); +} + +void +lpfc_dhchap_make_challenge(struct Scsi_Host *shost, int status, + void *rsp, uint32_t rsp_len) +{ + struct lpfc_vport *vport = (struct lpfc_vport *)shost->hostdata; + struct lpfc_nodelist *ndlp; + uint32_t chal_payload_len; + uint8_t *chal_payload; + struct fc_auth_rsp *auth_rsp = rsp; + + ndlp = lpfc_findnode_did(vport, Fabric_DID); + if (!ndlp || !NLP_CHK_NODE_ACT(ndlp)) { + kfree(rsp); + return; + } + + lpfc_printf_vlog(vport, KERN_INFO, LOG_SECURITY, + "1003 Send dhchap challenge local_wwpn " + "%llX remote_wwpn %llX \n", + (unsigned long long)auth_rsp->local_wwpn, + (unsigned long long)auth_rsp->remote_wwpn); + + chal_payload = kmalloc(MAX_AUTH_REQ_SIZE, GFP_KERNEL); + if (!chal_payload) { + kfree(rsp); + return; + } + vport->auth.auth_msg_state = LPFC_DHCHAP_CHALLENGE; + chal_payload_len = lpfc_build_dhchap_challenge(vport, + chal_payload, rsp); + lpfc_issue_els_auth(vport, ndlp, DHCHAP_CHALLENGE, + chal_payload, chal_payload_len); + kfree(chal_payload); + kfree(rsp); +} + + +void +lpfc_dhchap_make_response(struct Scsi_Host *shost, int status, + void *rsp, uint32_t rsp_len) +{ + struct lpfc_vport *vport = (struct lpfc_vport *)shost->hostdata; + struct lpfc_nodelist *ndlp; + uint32_t reply_payload_len; + uint8_t *reply_payload; + struct fc_auth_rsp *auth_rsp = rsp; + + ndlp = lpfc_findnode_did(vport, Fabric_DID); + if (!ndlp || !NLP_CHK_NODE_ACT(ndlp)) { + kfree(rsp); + return; + } + + lpfc_printf_vlog(vport, KERN_INFO, LOG_SECURITY, + "1004 Send dhchap reply local_wwpn " + "%llX remote_wwpn %llX \n", + (unsigned long long)auth_rsp->local_wwpn, + (unsigned long long)auth_rsp->remote_wwpn); + + reply_payload = kmalloc(MAX_AUTH_REQ_SIZE, GFP_KERNEL); + if (!reply_payload) { + kfree(rsp); + return; + } + + vport->auth.auth_msg_state = LPFC_DHCHAP_REPLY; + reply_payload_len = lpfc_build_dhchap_reply(vport, reply_payload, rsp); + lpfc_issue_els_auth(vport, ndlp, DHCHAP_REPLY, + reply_payload, reply_payload_len); + kfree(reply_payload); + kfree(rsp); + +} + + +void +lpfc_dhchap_authenticate(struct Scsi_Host *shost, + int status, void *rsp, + uint32_t rsp_len) +{ + struct fc_auth_rsp *auth_rsp = (struct fc_auth_rsp *)rsp; + struct lpfc_vport *vport = (struct lpfc_vport *)shost->hostdata; + struct lpfc_nodelist *ndlp; + + ndlp = lpfc_findnode_did(vport, Fabric_DID); + if (!ndlp || !NLP_CHK_NODE_ACT(ndlp)) { + kfree(rsp); + return; + } + if (status != 0) { + lpfc_issue_els_auth_reject(vport, ndlp, + AUTH_ERR, AUTHENTICATION_FAILED); + kfree(rsp); + return; + } + + if (auth_rsp->u.dhchap_success.authenticated) { + uint32_t suc_payload_len; + uint8_t *suc_payload; + + suc_payload = kmalloc(MAX_AUTH_REQ_SIZE, GFP_KERNEL); + if (!suc_payload) { + lpfc_issue_els_auth_reject(vport, ndlp, + AUTH_ERR, AUTHENTICATION_FAILED); + kfree(rsp); + return; + } + suc_payload_len = lpfc_build_dhchap_success(vport, + suc_payload, rsp); + if (suc_payload_len == sizeof(uint32_t)) { + /* Authentication is complete after sending this SUCCESS */ + vport->auth.auth_msg_state = LPFC_DHCHAP_SUCCESS; + } else { + /* Need to wait for SUCCESS from Auth Initiator */ + vport->auth.auth_msg_state = LPFC_DHCHAP_SUCCESS_REPLY; + } + lpfc_issue_els_auth(vport, ndlp, DHCHAP_SUCCESS, + suc_payload, suc_payload_len); + kfree(suc_payload); + vport->auth.direction |= AUTH_DIRECTION_LOCAL; + } else { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1005 AUTHENTICATION_FAILURE Nport:x%x\n", + ndlp->nlp_DID); + lpfc_issue_els_auth_reject(vport, ndlp, + AUTH_ERR, AUTHENTICATION_FAILED); + if (vport->auth.auth_state == LPFC_AUTH_SUCCESS) { + lpfc_port_auth_failed(ndlp); + } + } + + kfree(rsp); +} + +int +lpfc_unpack_auth_negotiate(struct lpfc_vport *vport, uint8_t *message, + uint8_t *reason, uint8_t *explanation) +{ + uint32_t prot_len; + uint32_t param_len; + int i, j = 0; + + /* Following is the format of the message. Name Format. + * uint16_t nameTag; + * uint16_t nameLength; + * uint8_t name[8]; + * AUTH_Negotiate Message + * uint32_t NumberOfAuthProtocals + * uint32_t AuthProtParameter#1Len + * uint32_t AuthProtID#1 (DH-CHAP = 0x1) + * AUTH_Negotiate DH-CHAP + * uint16_t DH-CHAPParameterTag (HashList = 0x1) + * uint16_t DH-CHAPParameterWordCount (number of uint32_t entries) + * uint8_t DH-CHAPParameter[]; (uint32_t entries) + * uint16_t DH-CHAPParameterTag (DHglDList = 0x2) + * uint16_t DH-CHAPParameterWordCount (number of uint32_t entries) + * uint8_t DH-CHAPParameter[]; (uint32_t entries) + * DHCHAP_Challenge Message + * uint32_t hashIdentifier; + * uint32_t dhgroupIdentifier; + * uint32_t challengevalueLen; + * uint8_t challengeValue[]; + * uint32_t dhvalueLen; + * uint8_t dhvalue[]; + */ + + /* Name Tag */ + if (be16_to_cpu(*(uint16_t *)message) != NAME_TAG) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1006 Bad Name tag in auth message 0x%x\n", + be16_to_cpu(*(uint16_t *)message)); + return 1; + } + message += sizeof(uint16_t); + + /* Name Length */ + if (be16_to_cpu(*(uint16_t *)message) != NAME_LEN) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1007 Bad Name length in auth message 0x%x\n", + be16_to_cpu(*(uint16_t *)message)); + return 1; + } + message += sizeof(uint16_t); + + /* Skip over Remote Port Name */ + message += NAME_LEN; + + /* Number of Auth Protocols must be 1 DH-CHAP */ + if (be32_to_cpu(*(uint32_t *)message) != 1) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1008 Bad Number of Protocols 0x%x\n", + be32_to_cpu(*(uint32_t *)message)); + return 1; + } + message += sizeof(uint32_t); + + /* Protocol Parameter Length */ + prot_len = be32_to_cpu(*(uint32_t *)message); + message += sizeof(uint32_t); + + /* Protocol Parameter type */ + if (be32_to_cpu(*(uint32_t *)message) != FC_DHCHAP) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1009 Bad param type 0x%x\n", + be32_to_cpu(*(uint32_t *)message)); + return 1; + } + message += sizeof(uint32_t); + + /* Parameter #1 Tag */ + if (be16_to_cpu(*(uint16_t *)message) != HASH_LIST_TAG) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1010 Bad Tag 1 0x%x\n", + be16_to_cpu(*(uint16_t *)message)); + return 1; + } + message += sizeof(uint16_t); + + /* Parameter #1 Length */ + param_len = be16_to_cpu(*(uint16_t *)message); + message += sizeof(uint16_t); + + /* Choose a hash function */ + for (i = 0; i < vport->auth.hash_len; i++) { + for (j = 0; j < param_len; j++) { + if (vport->auth.hash_priority[i] == + be32_to_cpu(((uint32_t *)message)[j])) + break; + } + if (j != param_len) + break; + } + if (i == vport->auth.hash_len && j == param_len) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1011 Auth_neg no hash function chosen.\n"); + return 1; + } + vport->auth.hash_id = vport->auth.hash_priority[i]; + message += sizeof(uint32_t) * param_len; + + /* Parameter #2 Tag */ + if (be16_to_cpu(*(uint16_t *)message) != DHGID_LIST_TAG) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1012 Auth_negotiate Bad Tag 2 0x%x\n", + be16_to_cpu(*(uint16_t *)message)); + return 1; + } + message += sizeof(uint16_t); + + /* Parameter #2 Length */ + param_len = be16_to_cpu(*(uint16_t *)message); + message += sizeof(uint16_t); + + /* Choose a DH Group */ + for (i = 0; i < vport->auth.dh_group_len; i++) { + for (j = 0; j < param_len; j++) { + if (vport->auth.dh_group_priority[i] == + be32_to_cpu(((uint32_t *)message)[j])) + break; + } + if (j != param_len) + break; + } + if (i == vport->auth.dh_group_len && j == param_len) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1013 Auth_negotiate no DH_group found. \n"); + return 1; + } + vport->auth.group_id = vport->auth.dh_group_priority[i]; + message += sizeof(uint32_t) * param_len; + + return 0; +} + +int +lpfc_unpack_dhchap_challenge(struct lpfc_vport *vport, uint8_t *message, + uint8_t *reason, uint8_t *explanation) +{ + int i; + + /* Following is the format of the message DHCHAP_Challenge. + * uint16_t nameTag; + * uint16_t nameLength; + * uint8_t name[8]; + * uint32_t hashIdentifier; + * uint32_t dhgroupIdentifier; + * uint32_t challengevalueLen; + * uint8_t challengeValue[]; + * uint32_t dhvalueLen; + * uint8_t dhvalue[]; + */ + + /* Name Tag */ + if (be16_to_cpu(*(uint16_t *)message) != NAME_TAG) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1014 dhchap challenge bad name tag 0x%x. \n", + be16_to_cpu(*(uint16_t *)message)); + return 1; + } + message += sizeof(uint16_t); + + /* Name Length */ + if (be16_to_cpu(*(uint16_t *)message) != NAME_LEN) { + *reason = AUTH_ERR; + *explanation = BAD_PAYLOAD; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1015 dhchap challenge bad name length " + "0x%x.\n", be16_to_cpu(*(uint16_t *)message)); + return 1; + } + message += sizeof(uint16_t); + + /* Remote Port Name */ + message += NAME_LEN; + + /* Hash ID */ + vport->auth.hash_id = be32_to_cpu(*(uint32_t *)message); /* Hash id */ + for (i = 0; i < vport->auth.hash_len; i++) { + if (vport->auth.hash_id == vport->auth.hash_priority[i]) + break; + } + if (i == vport->auth.hash_len) { + *reason = LOGIC_ERR; + *explanation = BAD_ALGORITHM; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1016 dhchap challenge Hash ID not Supported " + "0x%x. \n", vport->auth.hash_id); + return 1; + } + message += sizeof(uint32_t); + + vport->auth.group_id = + be32_to_cpu(*(uint32_t *)message); /* DH group id */ + for (i = 0; i < vport->auth.dh_group_len; i++) { + if (vport->auth.group_id == vport->auth.dh_group_priority[i]) + break; + } + if (i == vport->auth.dh_group_len) { + *reason = LOGIC_ERR; + *explanation = BAD_DHGROUP; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1017 dhchap challenge could not find DH " + "Group. \n"); + return 1; + } + message += sizeof(uint32_t); + + vport->auth.challenge_len = + be32_to_cpu(*(uint32_t *)message); /* Challenge Len */ + message += sizeof(uint32_t); + + /* copy challenge to vport */ + if (vport->auth.challenge != NULL) { + kfree(vport->auth.challenge); + } + vport->auth.challenge = kmalloc(vport->auth.challenge_len, GFP_KERNEL); + if (!vport->auth.challenge) { + *reason = AUTH_ERR; + return 1; + } + memcpy (vport->auth.challenge, message, vport->auth.challenge_len); + message += vport->auth.challenge_len; + + vport->auth.dh_pub_key_len = + be32_to_cpu(*(uint32_t *)message); /* DH Value Len */ + message += sizeof(uint32_t); + + if (vport->auth.dh_pub_key_len != 0) { + if (vport->auth.group_id == DH_GROUP_NULL) { + *reason = LOGIC_ERR; + *explanation = BAD_DHGROUP; + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1018 dhchap challenge No Public key " + "for non-NULL DH Group.\n"); + return 1; + } + + /* Copy to the vport to save for authentication */ + if (vport->auth.dh_pub_key != NULL) + kfree(vport->auth.dh_pub_key); + vport->auth.dh_pub_key = kmalloc(vport->auth.dh_pub_key_len, + GFP_KERNEL); + if (!vport->auth.dh_pub_key) { + *reason = AUTH_ERR; + return 1; + } + memcpy(vport->auth.dh_pub_key, message, + vport->auth.dh_pub_key_len); + } + return 0; +} + +int +lpfc_unpack_dhchap_reply(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_req *fc_req) +{ + uint32_t rsp_len; + uint32_t dh_len; + uint32_t challenge_len; + + /* Following is the format of the message DHCHAP_Reply. + * uint32_t Response Value Length; + * uint8_t Response Value[]; + * uint32_t DH Value Length; + * uint8_t DH Value[]; + * uint32_t Challenge Value Length; + * uint8_t Challenge Value[]; + */ + + rsp_len = be32_to_cpu(*(uint32_t *)message); /* Response Len */ + message += sizeof(uint32_t); + memcpy (fc_req->u.dhchap_success.data + vport->auth.challenge_len, + message, rsp_len); + fc_req->u.dhchap_success.received_response_len = rsp_len; + message += rsp_len; + + dh_len = be32_to_cpu(*(uint32_t *)message); /* DH Len */ + message += sizeof(uint32_t); + memcpy (fc_req->u.dhchap_success.data + vport->auth.challenge_len + + rsp_len, message, dh_len); + fc_req->u.dhchap_success.received_public_key_len = dh_len; + message += dh_len; + + challenge_len = be32_to_cpu(*(uint32_t *)message); /* Challenge Len */ + message += sizeof(uint32_t); + memcpy (fc_req->u.dhchap_success.data + vport->auth.challenge_len + + rsp_len + dh_len, + message, challenge_len); + fc_req->u.dhchap_success.received_challenge_len = challenge_len; + message += challenge_len; + + return (rsp_len + dh_len + challenge_len); +} + +int +lpfc_unpack_dhchap_success(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_req *fc_req) +{ + uint32_t rsp_len = 0; + + /* DHCHAP_Success. + * uint32_t responseValueLen; + * uint8_t response[]; + */ + + rsp_len = be32_to_cpu(*(uint32_t *)message); /* Response Len */ + message += sizeof(uint32_t); + memcpy(fc_req->u.dhchap_success.data + vport->auth.challenge_len, + message, rsp_len); + fc_req->u.dhchap_success.received_response_len = rsp_len; + + memcpy(fc_req->u.dhchap_success.data + + vport->auth.challenge_len + rsp_len, + vport->auth.dh_pub_key, vport->auth.dh_pub_key_len); + + fc_req->u.dhchap_success.received_public_key_len = + vport->auth.dh_pub_key_len; + + fc_req->u.dhchap_success.received_challenge_len = 0; + + return (vport->auth.challenge_len + rsp_len + + vport->auth.dh_pub_key_len); + return 0; +} + +int +lpfc_build_auth_neg(struct lpfc_vport *vport, uint8_t *message) +{ + uint8_t *message_start = message; + uint8_t *params_start; + uint32_t *params_len; + uint32_t len; + int i; + + /* Because some of the fields are not static in length + * and number we will pack on the fly.This will be expanded + * in the future to optionally offer DHCHAP or FCAP or both. + * The packing is done in Big Endian byte order DHCHAP_Reply. + * + * uint16_t nameTag; + * uint16_t nameLength; + * uint8_t name[8]; + * uint32_t available; For now we will only offer one + protocol ( DHCHAP ) for authentication. + * uint32_t potocolParamsLenId#1; + * uint32_t protocolId#1; 1 : DHCHAP. The protocol list is + * in order of preference. + * uint16_t parameter#1Tag 1 : HashList + * uint16_t parameter#1Len 2 : Count of how many parameter values + * follow in order of preference. + * uint16_t parameter#1value#1 5 : MD5 Hash Function + * uint16_t parameter#1value#2 6 : SHA-1 Hash Function + * uint16_t parameter#2Tag 2 : DHglDList + * uint16_t parameter#2Len 1 : Only One is supported now + * uint16_t parameter#2value#1 0 : NULL DH-CHAP Algorithm + * uint16_t parameter#2value#2 ... + * uint32_t protocolParamsLenId#2; + * uint32_t protocolId#2; 2 = FCAP + * uint16_t parameter#1Tag + * uint16_t parameter#1Len + * uint16_t parameter#1value#1 + * uint16_t parameter#1value#2 ... + * uint16_t parameter#2Tag + * uint16_t parameter#2Len + * uint16_t parameter#2value#1 + * uint16_t parameter#2value#2 ... + */ + + + /* Name Tag */ + *((uint16_t *)message) = cpu_to_be16(NAME_TAG); + message += sizeof(uint16_t); + + /* Name Len */ + *((uint16_t *)message) = cpu_to_be16(NAME_LEN); + message += sizeof(uint16_t); + + memcpy(message, vport->fc_portname.u.wwn, sizeof(uint64_t)); + + message += sizeof(uint64_t); + + /* Protocols Available */ + *((uint32_t *)message) = cpu_to_be32(PROTS_NUM); + message += sizeof(uint32_t); + + /* First Protocol Params Len */ + params_len = (uint32_t *)message; + message += sizeof(uint32_t); + + /* Start of first Param */ + params_start = message; + + /* Protocol Id */ + *((uint32_t *)message) = cpu_to_be32(FC_DHCHAP); + message += sizeof(uint32_t); + + /* Hash List Tag */ + *((uint16_t *)message) = cpu_to_be16(HASH_LIST_TAG); + message += sizeof(uint16_t); + + /* Hash Value Len */ + *((uint16_t *)message) = cpu_to_be16(vport->auth.hash_len); + message += sizeof(uint16_t); + + /* Hash Value each 4 byte words */ + for (i = 0; i < vport->auth.hash_len; i++) { + *((uint32_t *)message) = + cpu_to_be32(vport->auth.hash_priority[i]); + message += sizeof(uint32_t); + } + + /* DHgIDList Tag */ + *((uint16_t *)message) = cpu_to_be16(DHGID_LIST_TAG); + message += sizeof(uint16_t); + + /* DHgIDListValue Len */ + *((uint16_t *)message) = cpu_to_be16(vport->auth.dh_group_len); + + message += sizeof(uint16_t); + + /* DHgIDList each 4 byte words */ + + for (i = 0; i < vport->auth.dh_group_len; i++) { + *((uint32_t *)message) = + cpu_to_be32(vport->auth.dh_group_priority[i]); + message += sizeof(uint32_t); + } + + *params_len = cpu_to_be32(message - params_start); + + len = (uint32_t)(message - message_start); + + return len; +} + +int +lpfc_build_dhchap_challenge(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_rsp *fc_rsp) +{ + uint8_t *message_start = message; + + /* Because some of the fields are not static in length and number + * we will pack on the fly. The packing is done in Big Endian byte + * order DHCHAP_Challenge. + * + * uint16_t nameTag; + * uint16_t nameLength; + * uint8_t name[8]; + * uint32_t Hash_Identifier; + * uint32_t DH_Group_Identifier; + * uint32_t Challenge_Value_Length; + * uint8_t Challenge_Value[]; + * uint32_t DH_Value_Length; + * uint8_t DH_Value[]; + */ + + /* Name Tag */ + *((uint16_t *)message) = cpu_to_be16(NAME_TAG); + message += sizeof(uint16_t); + + /* Name Len */ + *((uint16_t *)message) = cpu_to_be16(NAME_LEN); + message += sizeof(uint16_t); + + memcpy(message, vport->fc_portname.u.wwn, NAME_LEN); + message += NAME_LEN; + + /* Hash Value each 4 byte words */ + *((uint32_t *)message) = cpu_to_be32(vport->auth.hash_id); + message += sizeof(uint32_t); + + /* DH group id each 4 byte words */ + *((uint32_t *)message) = cpu_to_be32(vport->auth.group_id); + message += sizeof(uint32_t); + + /* Challenge Length */ + *((uint32_t *)message) = cpu_to_be32(fc_rsp->u. + dhchap_challenge.our_challenge_len); + message += sizeof(uint32_t); + + /* copy challenge to vport to save */ + if (vport->auth.challenge) + kfree(vport->auth.challenge); + vport->auth.challenge_len = fc_rsp->u. + dhchap_challenge.our_challenge_len; + vport->auth.challenge = kmalloc(vport->auth.challenge_len, GFP_KERNEL); + + if (!vport->auth.challenge) + return 0; + + memcpy(vport->auth.challenge, fc_rsp->u.dhchap_challenge.data, + fc_rsp->u.dhchap_challenge.our_challenge_len); + + /* Challenge */ + memcpy(message, fc_rsp->u.dhchap_challenge.data, + fc_rsp->u.dhchap_challenge.our_challenge_len); + message += fc_rsp->u.dhchap_challenge.our_challenge_len; + + /* Public Key length */ + *((uint32_t *)message) = cpu_to_be32(fc_rsp->u. + dhchap_challenge.our_public_key_len); + message += sizeof(uint32_t); + + /* Public Key */ + memcpy(message, fc_rsp->u.dhchap_challenge.data + + fc_rsp->u.dhchap_challenge.our_challenge_len, + fc_rsp->u.dhchap_challenge.our_public_key_len); + message += fc_rsp->u.dhchap_challenge.our_public_key_len; + + return ((uint32_t)(message - message_start)); + +} + +int +lpfc_build_dhchap_reply(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_rsp *fc_rsp) + +{ + uint8_t *message_start = message; + + /* + * Because some of the fields are not static in length and + * number we will pack on the fly. The packing is done in + * Big Endian byte order DHCHAP_Reply. + * + * uint32_t ResonseLength; + * uint8_t ResponseValue[]; + * uint32_t DHLength; + * uint8_t DHValue[]; Our Public key + * uint32_t ChallengeLength; Used for bi-directional authentication + * uint8_t ChallengeValue[]; + * + * The combined key ( g^x mod p )^y mod p is used as the last + * hash of the password. + * + * g is the base 2 or 5. + * y is our private key. + * ( g^y mod p ) is our public key which we send. + * ( g^x mod p ) is their public key which we received. + */ + /* Response Value Length */ + *((uint32_t *)message) = cpu_to_be32(fc_rsp->u.dhchap_reply. + our_challenge_rsp_len); + + message += sizeof(uint32_t); + /* Response Value */ + memcpy(message, fc_rsp->u.dhchap_reply.data, + fc_rsp->u.dhchap_reply.our_challenge_rsp_len); + + message += fc_rsp->u.dhchap_reply.our_challenge_rsp_len; + /* DH Value Length */ + *((uint32_t *)message) = cpu_to_be32(fc_rsp->u.dhchap_reply. + our_public_key_len); + + message += sizeof(uint32_t); + /* DH Value */ + memcpy(message, fc_rsp->u.dhchap_reply.data + + fc_rsp->u.dhchap_reply.our_challenge_rsp_len, + fc_rsp->u.dhchap_reply.our_public_key_len); + + message += fc_rsp->u.dhchap_reply.our_public_key_len; + + if (vport->auth.bidirectional) { + + /* copy to vport to save */ + if (vport->auth.challenge) + kfree(vport->auth.challenge); + vport->auth.challenge_len = fc_rsp->u.dhchap_reply. + our_challenge_len; + vport->auth.challenge = kmalloc(vport->auth.challenge_len, + GFP_KERNEL); + if (!vport->auth.challenge) + return 0; + + memcpy(vport->auth.challenge, fc_rsp->u.dhchap_reply.data + + fc_rsp->u.dhchap_reply.our_challenge_rsp_len + + fc_rsp->u.dhchap_reply.our_public_key_len, + fc_rsp->u.dhchap_reply.our_challenge_len); + /* Challenge Value Length */ + *((uint32_t *)message) = cpu_to_be32(fc_rsp->u. + dhchap_reply.our_challenge_len); + message += sizeof(uint32_t); + /* Challenge Value */ + memcpy(message, fc_rsp->u.dhchap_reply.data + + fc_rsp->u.dhchap_reply.our_challenge_rsp_len + + fc_rsp->u.dhchap_reply.our_public_key_len, + fc_rsp->u.dhchap_reply.our_challenge_len); + + message += fc_rsp->u.dhchap_reply.our_challenge_len; + + } else { + *((uint32_t *)message) = 0; /* Challenge Len for No + bidirectional authentication */ + message += sizeof(uint32_t); /* Challenge Value Not Present */ + } + + return ((uint32_t)(message - message_start)); + +} + +int +lpfc_build_dhchap_success(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_rsp *fc_rsp) +{ + uint8_t *message_start = message; + + /* + * Because some of the fields are not static in length and number + * we will pack on the fly. The packing is done in Big Endian byte + * order DHCHAP_Success. + * uint32_t responseValueLen; + * uint8_t response[];. + */ + + *((uint32_t *)message) = cpu_to_be32(fc_rsp->u. + dhchap_success.response_len); + message += sizeof(uint32_t); + + memcpy(message, fc_rsp->u.dhchap_success.data, + fc_rsp->u.dhchap_success.response_len); + message += fc_rsp->u.dhchap_success.response_len; + + return ((uint32_t)(message - message_start)); +} + --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_auth.h @@ -0,0 +1,92 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2006-2007 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.emulex.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ + +#define N_DH_GROUP 4 +#define ELS_CMD_AUTH_BYTE 0x90 + +#define AUTH_REJECT 0xA +#define AUTH_NEGOTIATE 0xB +#define AUTH_DONE 0xC + +#define DHCHAP_CHALLENGE 0x10 +#define DHCHAP_REPLY 0x11 +#define DHCHAP_SUCCESS 0x12 + +#define FCAP_REQUEST 0x13 +#define FCAP_ACK 0x14 +#define FCAP_CONFIRM 0x15 + +#define PROTS_NUM 0x01 + +#define NAME_TAG 0x01 +#define NAME_LEN 0x08 + +#define HASH_LIST_TAG 0x01 + +#define DHGID_LIST_TAG 0x02 + +#define HBA_SECURITY 0x20 + +#define AUTH_ERR 0x1 +#define LOGIC_ERR 0x2 + +#define BAD_DHGROUP 0x2 +#define BAD_ALGORITHM 0x3 +#define AUTHENTICATION_FAILED 0x5 +#define BAD_PAYLOAD 0x6 +#define BAD_PROTOCOL 0x7 +#define RESTART 0x8 + +#define AUTH_VERSION 0x1 + +#define MAX_AUTH_MESSAGE_SIZE 1024 + +struct lpfc_auth_reject { + uint8_t reason; + uint8_t explanation; + uint8_t reserved[2]; +} __attribute__ ((packed)); + +struct lpfc_auth_message { /* Structure is in Big Endian format */ + uint8_t command_code; + uint8_t flags; + uint8_t message_code; + uint8_t protocol_ver; + uint32_t message_len; + uint32_t trans_id; + uint8_t data[0]; +} __attribute__ ((packed)); + +int lpfc_build_auth_neg(struct lpfc_vport *vport, uint8_t *message); +int lpfc_build_dhchap_challenge(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_rsp *fc_rsp); +int lpfc_build_dhchap_reply(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_rsp *fc_rsp); +int lpfc_build_dhchap_success(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_rsp *fc_rsp); + +int lpfc_unpack_auth_negotiate(struct lpfc_vport *vport, uint8_t *message, + uint8_t *reason, uint8_t *explanation); +int lpfc_unpack_dhchap_challenge(struct lpfc_vport *vport, uint8_t *message, + uint8_t *reason, uint8_t *explanation); +int lpfc_unpack_dhchap_reply(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_req *fc_req); +int lpfc_unpack_dhchap_success(struct lpfc_vport *vport, uint8_t *message, + struct fc_auth_req *fc_req); --- a/drivers/scsi/lpfc/lpfc_crtn.h +++ b/drivers/scsi/lpfc/lpfc_crtn.h @@ -21,6 +21,12 @@ typedef int (*node_filter)(struct lpfc_nodelist *, void *); struct fc_rport; +int lpfc_issue_els_auth(struct lpfc_vport *, struct lpfc_nodelist *, + uint8_t message_code, uint8_t *payload, + uint32_t payload_len); +int lpfc_issue_els_auth_reject(struct lpfc_vport *vport, + struct lpfc_nodelist *ndlp, + uint8_t reason, uint8_t explanation); void lpfc_dump_mem(struct lpfc_hba *, LPFC_MBOXQ_t *, uint16_t); void lpfc_read_nv(struct lpfc_hba *, LPFC_MBOXQ_t *); void lpfc_config_async(struct lpfc_hba *, LPFC_MBOXQ_t *, uint32_t); @@ -80,7 +86,10 @@ void lpfc_cleanup(struct lpfc_vport *); void lpfc_disc_timeout(unsigned long); struct lpfc_nodelist *__lpfc_findnode_rpi(struct lpfc_vport *, uint16_t); +struct lpfc_nodelist *lpfc_findnode_wwnn(struct lpfc_vport *, + struct lpfc_name *); +void lpfc_port_auth_failed(struct lpfc_nodelist *); void lpfc_worker_wake_up(struct lpfc_hba *); int lpfc_workq_post_event(struct lpfc_hba *, void *, void *, uint32_t); int lpfc_do_work(void *); @@ -95,6 +104,9 @@ void lpfc_more_plogi(struct lpfc_vport * void lpfc_more_adisc(struct lpfc_vport *); void lpfc_end_rscn(struct lpfc_vport *); int lpfc_els_chk_latt(struct lpfc_vport *); +struct lpfc_iocbq *lpfc_prep_els_iocb(struct lpfc_vport *, uint8_t, uint16_t, + uint8_t, struct lpfc_nodelist *, uint32_t, + uint32_t); int lpfc_els_abort_flogi(struct lpfc_hba *); int lpfc_initial_flogi(struct lpfc_vport *); int lpfc_initial_fdisc(struct lpfc_vport *); @@ -117,6 +129,8 @@ int lpfc_els_rsp_prli_acc(struct lpfc_vp void lpfc_cancel_retry_delay_tmo(struct lpfc_vport *, struct lpfc_nodelist *); void lpfc_els_retry_delay(unsigned long); void lpfc_els_retry_delay_handler(struct lpfc_nodelist *); +void lpfc_reauth_node(unsigned long); +void lpfc_reauthentication_handler(struct lpfc_nodelist *); void lpfc_els_unsol_event(struct lpfc_hba *, struct lpfc_sli_ring *, struct lpfc_iocbq *); int lpfc_els_handle_rscn(struct lpfc_vport *); @@ -258,7 +272,6 @@ void lpfc_free_sysfs_attr(struct lpfc_vp extern struct device_attribute *lpfc_hba_attrs[]; extern struct device_attribute *lpfc_vport_attrs[]; extern struct scsi_host_template lpfc_template; -extern struct scsi_host_template lpfc_vport_template; extern struct fc_function_template lpfc_transport_functions; extern struct fc_function_template lpfc_vport_transport_functions; extern int lpfc_sli_mode; @@ -276,6 +289,22 @@ void destroy_port(struct lpfc_vport *); int lpfc_get_instance(void); void lpfc_host_attrib_init(struct Scsi_Host *); +int lpfc_selective_reset(struct lpfc_hba *); +int lpfc_security_wait(struct lpfc_hba *); +int lpfc_get_security_enabled(struct Scsi_Host *); +void lpfc_security_service_online(struct Scsi_Host *); +void lpfc_security_service_offline(struct Scsi_Host *); +void lpfc_security_config(struct Scsi_Host *, int status, void *); +int lpfc_security_config_wait(struct lpfc_vport *vport); +void lpfc_dhchap_make_challenge(struct Scsi_Host *, int , void *, uint32_t); +void lpfc_dhchap_make_response(struct Scsi_Host *, int , void *, uint32_t); +void lpfc_dhchap_authenticate(struct Scsi_Host *, int , void *, uint32_t); +int lpfc_start_node_authentication(struct lpfc_nodelist *); +int lpfc_get_auth_config(struct lpfc_nodelist *, struct lpfc_name *); +void lpfc_start_discovery(struct lpfc_vport *vport); +void lpfc_start_authentication(struct lpfc_vport *, struct lpfc_nodelist *); +int lpfc_rcv_nl_msg(struct Scsi_Host *, void *, uint32_t, uint32_t); + extern void lpfc_debugfs_initialize(struct lpfc_vport *); extern void lpfc_debugfs_terminate(struct lpfc_vport *); extern void lpfc_debugfs_disc_trc(struct lpfc_vport *, int, char *, uint32_t, @@ -284,6 +313,11 @@ extern void lpfc_debugfs_slow_ring_trc(s uint32_t, uint32_t); extern struct lpfc_hbq_init *lpfc_hbq_defs[]; +extern uint8_t lpfc_security_service_state; +extern spinlock_t fc_security_user_lock; +extern struct list_head fc_security_user_list; +extern int fc_service_state; + /* Interface exported by fabric iocb scheduler */ void lpfc_fabric_abort_nport(struct lpfc_nodelist *); void lpfc_fabric_abort_hba(struct lpfc_hba *); @@ -293,6 +327,7 @@ void lpfc_adjust_queue_depth(struct lpfc void lpfc_ramp_down_queue_handler(struct lpfc_hba *); void lpfc_ramp_up_queue_handler(struct lpfc_hba *); void lpfc_scsi_dev_block(struct lpfc_hba *); +void lpfc_scsi_dev_rescan(struct lpfc_hba *); void lpfc_send_els_failure_event(struct lpfc_hba *, struct lpfc_iocbq *, --- a/drivers/scsi/lpfc/lpfc_disc.h +++ b/drivers/scsi/lpfc/lpfc_disc.h @@ -37,6 +37,7 @@ enum lpfc_work_type { LPFC_EVT_KILL, LPFC_EVT_ELS_RETRY, LPFC_EVT_DEV_LOSS, + LPFC_EVT_REAUTH, LPFC_EVT_FASTPATH_MGMT_EVT, }; @@ -99,10 +100,12 @@ struct lpfc_nodelist { #define NLP_USG_FREE_ACK_BIT 0x8 /* Indicate ndlp memory free invoked */ struct timer_list nlp_delayfunc; /* Used for delayed ELS cmds */ + struct timer_list nlp_reauth_tmr; /* Used for re-authentication */ struct fc_rport *rport; /* Corresponding FC transport port structure */ struct lpfc_vport *vport; struct lpfc_work_evt els_retry_evt; + struct lpfc_work_evt els_reauth_evt; struct lpfc_work_evt dev_loss_evt; unsigned long last_ramp_up_time; /* jiffy of last ramp up */ unsigned long last_q_full_time; /* jiffy of last queue full */ --- a/drivers/scsi/lpfc/lpfc_els.c +++ b/drivers/scsi/lpfc/lpfc_els.c @@ -38,6 +38,9 @@ #include "lpfc_crtn.h" #include "lpfc_vport.h" #include "lpfc_debugfs.h" +#include "lpfc_auth_access.h" +#include "lpfc_auth.h" +#include "lpfc_security.h" static int lpfc_els_retry(struct lpfc_hba *, struct lpfc_iocbq *, struct lpfc_iocbq *); @@ -143,7 +146,7 @@ lpfc_els_chk_latt(struct lpfc_vport *vpo * Pointer to the newly allocated/prepared els iocb data structure * NULL - when els iocb data structure allocation/preparation failed **/ -static struct lpfc_iocbq * +struct lpfc_iocbq * lpfc_prep_els_iocb(struct lpfc_vport *vport, uint8_t expectRsp, uint16_t cmdSize, uint8_t retry, struct lpfc_nodelist *ndlp, uint32_t did, @@ -653,6 +656,7 @@ lpfc_cmpl_els_flogi(struct lpfc_hba *phb struct lpfc_nodelist *ndlp = cmdiocb->context1; struct lpfc_dmabuf *pcmd = cmdiocb->context2, *prsp; struct serv_parm *sp; + struct lpfc_name wwpn; int rc; /* Check to see if link went down during discovery */ @@ -703,7 +707,10 @@ lpfc_cmpl_els_flogi(struct lpfc_hba *phb prsp = list_get_first(&pcmd->list, struct lpfc_dmabuf, list); sp = prsp->virt + sizeof(uint32_t); - + if (sp->cmn.security) + ndlp->nlp_flag |= NLP_SC_REQ; + else + ndlp->nlp_flag &= ~NLP_SC_REQ; /* FLOGI completes successfully */ lpfc_printf_vlog(vport, KERN_INFO, LOG_ELS, "0101 FLOGI completes sucessfully " @@ -711,6 +718,20 @@ lpfc_cmpl_els_flogi(struct lpfc_hba *phb irsp->un.ulpWord[4], sp->cmn.e_d_tov, sp->cmn.w2.r_a_tov, sp->cmn.edtovResolution); + if (vport->cfg_enable_auth) { + u64_to_wwn(AUTH_FABRIC_WWN, wwpn.u.wwn); + if (lpfc_get_auth_config(ndlp, &wwpn)) + goto flogifail; + } else { + vport->auth.security_active = 0; + if (sp->cmn.security) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1055 Authentication parameter is " + "disabled, but is required by " + "the fabric.\n"); + goto flogifail; + } + } if (vport->port_state == LPFC_FLOGI) { /* * If Common Service Parameters indicate Nport @@ -800,6 +821,10 @@ lpfc_issue_els_flogi(struct lpfc_vport * sp = (struct serv_parm *) pcmd; /* Setup CSPs accordingly for Fabric */ + + if (vport->cfg_enable_auth) + sp->cmn.security = 1; + sp->cmn.e_d_tov = 0; sp->cmn.w2.r_a_tov = 0; sp->cls1.classValid = 0; @@ -965,6 +990,17 @@ lpfc_initial_fdisc(struct lpfc_vport *vp struct lpfc_hba *phba = vport->phba; struct lpfc_nodelist *ndlp; + if (vport->cfg_enable_auth) { + if (lpfc_security_wait(phba)) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1049 Authentication is enabled but " + "authentication service is not " + "running\n"); + vport->auth.auth_mode = FC_AUTHMODE_UNKNOWN; + return 0; + } + } + /* First look for the Fabric ndlp */ ndlp = lpfc_findnode_did(vport, Fabric_DID); if (!ndlp) { @@ -2697,6 +2733,17 @@ lpfc_els_retry(struct lpfc_hba *phba, st lpfc_nlp_set_state(vport, ndlp, NLP_STE_NPR_NODE); lpfc_issue_els_logo(vport, ndlp, cmdiocb->retry); return 1; + case ELS_CMD_AUTH_NEG: + case ELS_CMD_DH_CHA: + case ELS_CMD_DH_REP: + case ELS_CMD_DH_SUC: + ndlp->nlp_prev_state = ndlp->nlp_state; + ndlp->nlp_state = NLP_STE_NPR_NODE; + lpfc_printf_log(phba, KERN_INFO, LOG_ELS, + "0143 Authentication LS_RJT Logical " + "busy\n"); + lpfc_start_authentication(vport, ndlp); + return 1; } } /* No retry ELS command to remote NPORT */ @@ -5085,6 +5132,363 @@ lpfc_els_flush_all_cmd(struct lpfc_hba return; } +static void +lpfc_els_rcv_auth_neg(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, + struct lpfc_nodelist *ndlp) +{ + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + struct lpfc_dmabuf *pcmd = cmdiocb->context2; + struct lpfc_auth_message *authcmd; + uint8_t reason, explanation; + uint32_t message_len; + uint32_t trans_id; + struct fc_auth_req *fc_req; + struct fc_auth_rsp *fc_rsp; + + authcmd = pcmd->virt; + message_len = be32_to_cpu(authcmd->message_len); + trans_id = be32_to_cpu(authcmd->trans_id); + + lpfc_els_rsp_acc(vport, ELS_CMD_ACC, cmdiocb, ndlp, NULL); + + vport->auth.trans_id = trans_id; + + if (lpfc_unpack_auth_negotiate(vport, authcmd->data, + &reason, &explanation)) { + lpfc_issue_els_auth_reject(vport, ndlp, reason, explanation); + return; + } + vport->auth.direction = AUTH_DIRECTION_NONE; + lpfc_printf_vlog(vport, KERN_WARNING, LOG_SECURITY, + "1033 Received auth_negotiate from Nport:x%x\n", + ndlp->nlp_DID); + + fc_req = kzalloc(sizeof(struct fc_auth_req), GFP_KERNEL); + + fc_req->local_wwpn = wwn_to_u64(vport->fc_portname.u.wwn); + if (ndlp->nlp_type & NLP_FABRIC) + fc_req->remote_wwpn = AUTH_FABRIC_WWN; + else + fc_req->remote_wwpn = wwn_to_u64(ndlp->nlp_portname.u.wwn); + fc_req->u.dhchap_challenge.transaction_id = vport->auth.trans_id; + fc_req->u.dhchap_challenge.dh_group_id = vport->auth.group_id; + fc_req->u.dhchap_challenge.hash_id = vport->auth.hash_id; + + fc_rsp = kzalloc(MAX_AUTH_RSP_SIZE, GFP_KERNEL); + + if (lpfc_fc_security_dhchap_make_challenge(shost, + fc_req, sizeof(struct fc_auth_req), + fc_rsp, MAX_AUTH_RSP_SIZE)) { + kfree(fc_rsp); + lpfc_issue_els_auth_reject(vport, ndlp, LOGIC_ERR, 0); + } + + kfree(fc_req); + +} + +static void +lpfc_els_rcv_chap_chal(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, + struct lpfc_nodelist *ndlp) +{ + + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + struct lpfc_dmabuf *pcmd = cmdiocb->context2; + struct lpfc_auth_message *authcmd; + uint8_t reason, explanation; + uint32_t message_len; + uint32_t trans_id; + struct fc_auth_req *fc_req; + struct fc_auth_rsp *fc_rsp; + uint32_t fc_req_len; + + authcmd = pcmd->virt; + message_len = be32_to_cpu(authcmd->message_len); + trans_id = be32_to_cpu(authcmd->trans_id); + + lpfc_els_rsp_acc(vport, ELS_CMD_ACC, cmdiocb, ndlp, NULL); + + if (vport->auth.auth_msg_state != LPFC_AUTH_NEGOTIATE) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1034 Not Expecting Challenge - Rejecting " + "Challenge.\n"); + lpfc_issue_els_auth_reject(vport, ndlp, AUTH_ERR, BAD_PROTOCOL); + return; + } + + if (trans_id != vport->auth.trans_id) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1035 Transport ID does not match - Rejecting " + "Challenge.\n"); + lpfc_issue_els_auth_reject(vport, ndlp, AUTH_ERR, BAD_PAYLOAD); + return; + } + + if (lpfc_unpack_dhchap_challenge(vport, authcmd->data, + &reason, &explanation)) { + lpfc_issue_els_auth_reject(vport, ndlp, reason, explanation); + return; + } + vport->auth.direction = AUTH_DIRECTION_NONE; + + fc_req_len = (sizeof(struct fc_auth_req) + + vport->auth.challenge_len + + vport->auth.dh_pub_key_len); + fc_req = kzalloc(fc_req_len, GFP_KERNEL); + fc_req->local_wwpn = wwn_to_u64(vport->fc_portname.u.wwn); + if (ndlp->nlp_type & NLP_FABRIC) + fc_req->remote_wwpn = AUTH_FABRIC_WWN; + else + fc_req->remote_wwpn = wwn_to_u64(ndlp->nlp_portname.u.wwn); + fc_req->u.dhchap_reply.transaction_id = vport->auth.trans_id; + fc_req->u.dhchap_reply.dh_group_id = vport->auth.group_id; + fc_req->u.dhchap_reply.hash_id = vport->auth.hash_id; + fc_req->u.dhchap_reply.bidirectional = vport->auth.bidirectional; + fc_req->u.dhchap_reply.received_challenge_len = + vport->auth.challenge_len; + fc_req->u.dhchap_reply.received_public_key_len = + vport->auth.dh_pub_key_len; + memcpy(fc_req->u.dhchap_reply.data, vport->auth.challenge, + vport->auth.challenge_len); + if (vport->auth.group_id != DH_GROUP_NULL) { + memcpy(fc_req->u.dhchap_reply.data + vport->auth.challenge_len, + vport->auth.dh_pub_key, vport->auth.dh_pub_key_len); + } + + fc_rsp = kzalloc(MAX_AUTH_RSP_SIZE, GFP_KERNEL); + + if (lpfc_fc_security_dhchap_make_response(shost, + fc_req, fc_req_len, + fc_rsp, MAX_AUTH_RSP_SIZE)) { + kfree(fc_rsp); + lpfc_issue_els_auth_reject(vport, ndlp, LOGIC_ERR, 0); + } + + kfree(fc_req); + +} + +static void +lpfc_els_rcv_auth_rjt(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, + struct lpfc_nodelist *ndlp) +{ + + struct lpfc_dmabuf *pcmd = cmdiocb->context2; + struct lpfc_auth_message *authcmd; + uint32_t message_len; + uint32_t trans_id; + struct lpfc_auth_reject *rjt; + struct lpfc_hba *phba = vport->phba; + + authcmd = pcmd->virt; + rjt = (struct lpfc_auth_reject *)authcmd->data; + + message_len = be32_to_cpu(authcmd->message_len); + trans_id = be32_to_cpu(authcmd->trans_id); + + lpfc_els_rsp_acc(vport, ELS_CMD_ACC, cmdiocb, ndlp, NULL); + + if (vport->auth.auth_state == LPFC_AUTH_SUCCESS) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1036 Authentication transaction reject - " + "re-auth request reason 0x%x exp 0x%x\n", + rjt->reason, rjt->explanation); + lpfc_port_auth_failed(ndlp); + if (vport->auth.auth_msg_state == LPFC_DHCHAP_SUCCESS) { + /* start authentication */ + lpfc_start_authentication(vport, ndlp); + } + } else { + if (rjt->reason == LOGIC_ERR && rjt->explanation == RESTART) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1037 Authentication transaction " + "reject - restarting authentication. " + "reason 0x%x exp 0x%x\n", + rjt->reason, rjt->explanation); + /* restart auth */ + lpfc_start_authentication(vport, ndlp); + } else { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1057 Authentication transaction " + "reject. reason 0x%x exp 0x%x\n", + rjt->reason, rjt->explanation); + vport->auth.auth_msg_state = LPFC_AUTH_REJECT; + if (!(phba->sli3_options & LPFC_SLI3_NPIV_ENABLED) && + (phba->link_state != LPFC_CLEAR_LA)) { + /* If Auth failed enable link interrupt. */ + lpfc_issue_clear_la(phba, vport); + } + } + } +} + +static void +lpfc_els_rcv_chap_reply(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, + struct lpfc_nodelist *ndlp) +{ + + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + struct lpfc_dmabuf *pcmd = cmdiocb->context2; + struct lpfc_auth_message *authcmd; + uint32_t message_len; + uint32_t trans_id; + struct fc_auth_req *fc_req; + struct fc_auth_rsp *fc_rsp; + uint32_t data_len; + + authcmd = pcmd->virt; + message_len = be32_to_cpu(authcmd->message_len); + trans_id = be32_to_cpu(authcmd->trans_id); + + lpfc_els_rsp_acc(vport, ELS_CMD_ACC, cmdiocb, ndlp, NULL); + + fc_req = kzalloc(MAX_AUTH_REQ_SIZE, GFP_KERNEL); + + fc_req->local_wwpn = wwn_to_u64(vport->fc_portname.u.wwn); + if (ndlp->nlp_type & NLP_FABRIC) + fc_req->remote_wwpn = AUTH_FABRIC_WWN; + else + fc_req->remote_wwpn = wwn_to_u64(ndlp->nlp_portname.u.wwn); + + if (vport->auth.auth_msg_state != LPFC_DHCHAP_CHALLENGE) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1039 Not Expecting Reply - rejecting. State " + "0x%x\n", vport->auth.auth_state); + + lpfc_issue_els_auth_reject(vport, ndlp, AUTH_ERR, BAD_PROTOCOL); + return; + } + + if (trans_id != vport->auth.trans_id) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1040 Bad Reply trans_id- rejecting. " + "Trans_id: 0x%x Expecting: 0x%x \n", + trans_id, vport->auth.trans_id); + lpfc_issue_els_auth_reject(vport, ndlp, AUTH_ERR, BAD_PAYLOAD); + return; + } + + /* Zero is a valid length to be returned */ + data_len = lpfc_unpack_dhchap_reply(vport, authcmd->data, fc_req); + fc_req->u.dhchap_success.hash_id = vport->auth.hash_id; + fc_req->u.dhchap_success.dh_group_id = vport->auth.group_id; + fc_req->u.dhchap_success.transaction_id = vport->auth.trans_id; + fc_req->u.dhchap_success.our_challenge_len = vport->auth.challenge_len; + memcpy(fc_req->u.dhchap_success.data, vport->auth.challenge, + vport->auth.challenge_len); + + fc_rsp = kzalloc(MAX_AUTH_RSP_SIZE, GFP_KERNEL); + + if (lpfc_fc_security_dhchap_authenticate(shost, fc_req, + (sizeof(struct fc_auth_req) + + data_len + vport->auth.challenge_len), + fc_rsp, MAX_AUTH_RSP_SIZE)) { + kfree(fc_rsp); + lpfc_issue_els_auth_reject(vport, ndlp, LOGIC_ERR, 0); + } + + kfree(fc_req); + +} + +static void +lpfc_els_rcv_chap_suc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb, + struct lpfc_nodelist *ndlp) +{ + + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + struct lpfc_dmabuf *pcmd = cmdiocb->context2; + struct lpfc_auth_message *authcmd; + uint32_t message_len; + uint32_t trans_id; + struct fc_auth_req *fc_req; + struct fc_auth_rsp *fc_rsp; + uint32_t data_len; + + authcmd = pcmd->virt; + message_len = be32_to_cpu(authcmd->message_len); + trans_id = be32_to_cpu(authcmd->trans_id); + + lpfc_els_rsp_acc(vport, ELS_CMD_ACC, cmdiocb, ndlp, NULL); + + if (vport->auth.auth_msg_state != LPFC_DHCHAP_REPLY && + vport->auth.auth_msg_state != LPFC_DHCHAP_SUCCESS_REPLY) { + lpfc_issue_els_auth_reject(vport, ndlp, AUTH_ERR, BAD_PROTOCOL); + return; + } + + if (trans_id != vport->auth.trans_id) { + lpfc_issue_els_auth_reject(vport, ndlp, AUTH_ERR, BAD_PAYLOAD); + return; + } + + if (vport->auth.auth_msg_state == LPFC_DHCHAP_REPLY && + vport->auth.bidirectional) { + + fc_req = kzalloc(MAX_AUTH_REQ_SIZE, GFP_KERNEL); + if (!fc_req) + return; + + fc_req->local_wwpn = wwn_to_u64(vport->fc_portname.u.wwn); + if (ndlp->nlp_type & NLP_FABRIC) + fc_req->remote_wwpn = AUTH_FABRIC_WWN; + else + fc_req->remote_wwpn = + wwn_to_u64(ndlp->nlp_portname.u.wwn); + fc_req->u.dhchap_success.hash_id = vport->auth.hash_id; + fc_req->u.dhchap_success.dh_group_id = vport->auth.group_id; + fc_req->u.dhchap_success.transaction_id = vport->auth.trans_id; + fc_req->u.dhchap_success.our_challenge_len = + vport->auth.challenge_len; + + memcpy(fc_req->u.dhchap_success.data, vport->auth.challenge, + vport->auth.challenge_len); + + /* Zero is a valid return length */ + data_len = lpfc_unpack_dhchap_success(vport, + authcmd->data, + fc_req); + + fc_rsp = kzalloc(MAX_AUTH_RSP_SIZE, GFP_KERNEL); + if (!fc_rsp) + return; + + if (lpfc_fc_security_dhchap_authenticate(shost, + fc_req, sizeof(struct fc_auth_req) + data_len, + fc_rsp, MAX_AUTH_RSP_SIZE)) { + kfree(fc_rsp); + lpfc_issue_els_auth_reject(vport, ndlp, LOGIC_ERR, 0); + } + + kfree(fc_req); + + } else { + vport->auth.auth_msg_state = LPFC_DHCHAP_SUCCESS; + + kfree(vport->auth.challenge); + vport->auth.challenge = NULL; + vport->auth.challenge_len = 0; + + if (vport->auth.auth_state != LPFC_AUTH_SUCCESS) { + vport->auth.auth_state = LPFC_AUTH_SUCCESS; + lpfc_printf_vlog(vport, KERN_INFO, LOG_SECURITY, + "1041 Authentication Successful\n"); + + lpfc_start_discovery(vport); + + } else { + lpfc_printf_vlog(vport, KERN_INFO, LOG_SECURITY, + "1042 Re-Authentication Successful\n"); + } + /* If config requires re-authentication start the timer */ + vport->auth.last_auth = jiffies; + if (vport->auth.reauth_interval) + mod_timer(&ndlp->nlp_reauth_tmr, jiffies + + vport->auth.reauth_interval * 60 * HZ); + } + vport->auth.direction |= AUTH_DIRECTION_REMOTE; +} + /** * lpfc_send_els_failure_event: Posts an ELS command failure event. * @phba: Pointer to hba context object. @@ -5462,6 +5866,48 @@ lpfc_els_unsol_buffer(struct lpfc_hba *p if (newnode) lpfc_nlp_put(ndlp); break; + case ELS_CMD_AUTH_RJT: + lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_UNSOL, + "RCV AUTH_RJT: did:x%x/ste:x%x flg:x%x", + did, vport->port_state, ndlp->nlp_flag); + + lpfc_els_rcv_auth_rjt(vport, elsiocb, ndlp); + break; + case ELS_CMD_AUTH_NEG: + lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_UNSOL, + "RCV AUTH_NEG: did:x%x/ste:x%x flg:x%x", + did, vport->port_state, ndlp->nlp_flag); + + lpfc_els_rcv_auth_neg(vport, elsiocb, ndlp); + break; + case ELS_CMD_DH_CHA: + lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_UNSOL, + "RCV DH_CHA: did:x%x/ste:x%x flg:x%x", + did, vport->port_state, ndlp->nlp_flag); + + lpfc_els_rcv_chap_chal(vport, elsiocb, ndlp); + break; + case ELS_CMD_DH_REP: + lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_UNSOL, + "RCV DH_REP: did:x%x/ste:x%x flg:x%x", + did, vport->port_state, ndlp->nlp_flag); + + lpfc_els_rcv_chap_reply(vport, elsiocb, ndlp); + break; + case ELS_CMD_DH_SUC: + lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_UNSOL, + "RCV DH_SUC: did:x%x/ste:x%x flg:x%x", + did, vport->port_state, ndlp->nlp_flag); + + lpfc_els_rcv_chap_suc(vport, elsiocb, ndlp); + break; + + case ELS_CMD_AUTH_DONE: + lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_UNSOL, + "RCV AUTH_DONE: did:x%x/ste:x%x flg:x%x", + did, vport->port_state, ndlp->nlp_flag); + + default: lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_UNSOL, "RCV ELS cmd: cmd:x%x did:x%x/ste:x%x", @@ -5747,7 +6193,7 @@ lpfc_cmpl_reg_new_vport(struct lpfc_hba } else { if (vport == phba->pport) lpfc_issue_fabric_reglogin(vport); - else + else if (!vport->cfg_enable_auth) lpfc_do_scr_ns_plogi(phba, vport); } @@ -5840,6 +6286,9 @@ lpfc_cmpl_els_fdisc(struct lpfc_hba *phb struct lpfc_nodelist *next_np; IOCB_t *irsp = &rspiocb->iocb; struct lpfc_iocbq *piocb; + struct lpfc_dmabuf *pcmd = cmdiocb->context2, *prsp; + struct serv_parm *sp; + struct lpfc_name wwpn; lpfc_printf_vlog(vport, KERN_INFO, LOG_ELS, "0123 FDISC completes. x%x/x%x prevDID: x%x\n", @@ -5867,11 +6316,26 @@ lpfc_cmpl_els_fdisc(struct lpfc_hba *phb irsp->ulpStatus, irsp->un.ulpWord[4]); goto fdisc_failed; } - if (vport->fc_vport->vport_state == FC_VPORT_INITIALIZING) - lpfc_vport_set_state(vport, FC_VPORT_FAILED); - lpfc_nlp_put(ndlp); - /* giving up on FDISC. Cancel discovery timer */ - lpfc_can_disctmo(vport); + prsp = list_get_first(&pcmd->list, struct lpfc_dmabuf, list); + sp = prsp->virt + sizeof(uint32_t); + if (sp->cmn.security) + ndlp->nlp_flag |= NLP_SC_REQ; + else + ndlp->nlp_flag &= ~NLP_SC_REQ; + if (vport->cfg_enable_auth) { + u64_to_wwn(AUTH_FABRIC_WWN, wwpn.u.wwn); + if (lpfc_get_auth_config(ndlp, &wwpn)) + goto fdisc_failed; + } else { + vport->auth.security_active = 0; + if (sp->cmn.security) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1056 Authentication mode is " + "disabled, but is required " + "by the fabric.\n"); + goto fdisc_failed; + } + } spin_lock_irq(shost->host_lock); vport->fc_flag |= FC_FABRIC; if (vport->phba->fc_topology == TOPOLOGY_LOOP) @@ -5905,7 +6369,7 @@ lpfc_cmpl_els_fdisc(struct lpfc_hba *phb if (vport->fc_flag & FC_VPORT_NEEDS_REG_VPI) lpfc_register_new_vport(phba, vport, ndlp); - else + else if (!vport->cfg_enable_auth) lpfc_do_scr_ns_plogi(phba, vport); goto out; fdisc_failed: @@ -5980,6 +6444,10 @@ lpfc_issue_els_fdisc(struct lpfc_vport * sp->cls2.seqDelivery = 1; sp->cls3.seqDelivery = 1; + /* Set the security service parameter */ + if (vport->cfg_enable_auth) + sp->cmn.security = 1; + pcmd += sizeof(uint32_t); /* CSP Word 2 */ pcmd += sizeof(uint32_t); /* CSP Word 3 */ pcmd += sizeof(uint32_t); /* CSP Word 4 */ @@ -6474,3 +6942,180 @@ void lpfc_fabric_abort_hba(struct lpfc_h (piocb->iocb_cmpl) (phba, piocb, piocb); } } +static void +lpfc_cmpl_els_auth(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, + struct lpfc_iocbq *rspiocb) +{ + IOCB_t *irsp = &rspiocb->iocb; + struct lpfc_vport *vport = cmdiocb->vport; + struct lpfc_nodelist *ndlp = (struct lpfc_nodelist *) cmdiocb->context1; + + /* Check to see if link went down during discovery */ + if (lpfc_els_chk_latt(vport)) { + vport->auth.auth_msg_state = LPFC_AUTH_NONE; + lpfc_els_free_iocb(phba, cmdiocb); + return; + } + + if (irsp->ulpStatus) { + if (irsp->ulpStatus == IOSTAT_LS_RJT) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_ELS, + "1043 Authentication LS_RJT\n"); + } + /* Check for retry */ + if (!lpfc_els_retry(phba, cmdiocb, rspiocb)) { + if (irsp->ulpStatus != IOSTAT_LS_RJT) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_ELS, + "1045 Issue AUTH_NEG failed." + "Status:%x\n", + irsp->ulpStatus); + } + if (vport->auth.auth_mode == FC_AUTHMODE_ACTIVE) { + lpfc_can_disctmo(vport); + lpfc_port_auth_failed(ndlp); + } + } + if (!(phba->sli3_options & LPFC_SLI3_NPIV_ENABLED) && + (phba->link_state != LPFC_CLEAR_LA)) + lpfc_issue_clear_la(phba, vport); + lpfc_els_free_iocb(phba, cmdiocb); + return; + } + + if (vport->auth.auth_msg_state == LPFC_DHCHAP_SUCCESS || + vport->auth.auth_msg_state == LPFC_DHCHAP_SUCCESS_REPLY) { + + kfree(vport->auth.challenge); + vport->auth.challenge = NULL; + vport->auth.challenge_len = 0; + kfree(vport->auth.dh_pub_key); + vport->auth.dh_pub_key = NULL; + vport->auth.dh_pub_key_len = 0; + + if (vport->auth.auth_msg_state == LPFC_DHCHAP_SUCCESS) { + if (vport->auth.auth_state != LPFC_AUTH_SUCCESS) { + lpfc_printf_vlog(vport, KERN_WARNING, + LOG_SECURITY, "1046 " + "Authentication Successful\n"); + vport->auth.auth_state = LPFC_AUTH_SUCCESS; + lpfc_start_discovery(vport); + } else { + lpfc_printf_vlog(vport, KERN_WARNING, + LOG_SECURITY, + "1047 Re-Authentication" + " Successful\n"); + } + } + /* restart authentication timer */ + vport->auth.last_auth = jiffies; + if (vport->auth.reauth_interval) + mod_timer(&ndlp->nlp_reauth_tmr, + jiffies + + vport->auth.reauth_interval * 60 * HZ); + } + lpfc_els_free_iocb(phba, cmdiocb); +} + +int +lpfc_issue_els_auth(struct lpfc_vport *vport, + struct lpfc_nodelist *ndlp, + uint8_t message_code, + uint8_t *payload, + uint32_t payload_len) +{ + struct lpfc_hba *phba = vport->phba; + struct lpfc_iocbq *elsiocb; + struct lpfc_auth_message *authreq; + + elsiocb = lpfc_prep_els_iocb(vport, 1, + sizeof(struct lpfc_auth_message) + payload_len, + 0, ndlp, ndlp->nlp_DID, ELS_CMD_AUTH); + + if (!elsiocb) + return 1; + authreq = (struct lpfc_auth_message *) + (((struct lpfc_dmabuf *) elsiocb->context2)->virt); + authreq->command_code = ELS_CMD_AUTH_BYTE; + authreq->flags = 0; + authreq->message_code = message_code; + authreq->protocol_ver = AUTH_VERSION; + authreq->message_len = cpu_to_be32(payload_len); + authreq->trans_id = cpu_to_be32(vport->auth.trans_id); + memcpy(authreq->data, payload, payload_len); + + elsiocb->iocb_cmpl = lpfc_cmpl_els_auth; + + if (lpfc_sli_issue_iocb(phba, &phba->sli.ring[LPFC_ELS_RING], + elsiocb, 0) == IOCB_ERROR) { + lpfc_els_free_iocb(phba, elsiocb); + return 1; + } + + return 0; +} + +static void +lpfc_cmpl_els_auth_reject(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, + struct lpfc_iocbq *rspiocb) +{ + struct lpfc_vport *vport = cmdiocb->vport; + IOCB_t *irsp = &rspiocb->iocb; + + if (irsp->ulpStatus) { + /* Check for retry */ + if (!lpfc_els_retry(phba, cmdiocb, rspiocb)) { + lpfc_printf_log(phba, KERN_ERR, LOG_ELS, + "1048 Issue AUTH_REJECT failed.\n"); + } + } else + vport->port_state = LPFC_VPORT_UNKNOWN; + + lpfc_els_free_iocb(phba, cmdiocb); +} + +int +lpfc_issue_els_auth_reject(struct lpfc_vport *vport, + struct lpfc_nodelist *ndlp, + uint8_t reason, uint8_t explanation) +{ + struct lpfc_hba *phba = vport->phba; + struct lpfc_iocbq *elsiocb; + struct lpfc_sli_ring *pring; + struct lpfc_sli *psli; + struct lpfc_auth_message *authreq; + struct lpfc_auth_reject *reject; + + psli = &phba->sli; + pring = &psli->ring[LPFC_ELS_RING]; + + vport->auth.auth_msg_state = LPFC_AUTH_REJECT; + + elsiocb = lpfc_prep_els_iocb(vport, 1, sizeof(struct lpfc_auth_message) + + sizeof(struct lpfc_auth_reject), 0, ndlp, + ndlp->nlp_DID, ELS_CMD_AUTH); + + if (!elsiocb) + return 1; + + authreq = (struct lpfc_auth_message *) + (((struct lpfc_dmabuf *) elsiocb->context2)->virt); + authreq->command_code = ELS_CMD_AUTH_BYTE; + authreq->flags = 0; + authreq->message_code = AUTH_REJECT; + authreq->protocol_ver = AUTH_VERSION; + reject = (struct lpfc_auth_reject *)authreq->data; + memset(reject, 0, sizeof(struct lpfc_auth_reject)); + reject->reason = reason; + reject->explanation = explanation; + + authreq->message_len = cpu_to_be32(sizeof(struct lpfc_auth_reject)); + authreq->trans_id = cpu_to_be32(vport->auth.trans_id); + elsiocb->iocb_cmpl = lpfc_cmpl_els_auth_reject; + + if (lpfc_sli_issue_iocb(phba, pring, elsiocb, 0) == IOCB_ERROR) { + lpfc_els_free_iocb(phba, elsiocb); + return 1; + } + + return 0; +} --- a/drivers/scsi/lpfc/lpfc.h +++ b/drivers/scsi/lpfc/lpfc.h @@ -217,18 +217,81 @@ struct lpfc_stats { uint32_t fcpLocalErr; }; +struct lpfc_dmabufext { + struct lpfc_dmabuf dma; + uint32_t size; + uint32_t flag; +}; + enum sysfs_mbox_state { SMBOX_IDLE, SMBOX_WRITING, - SMBOX_READING + SMBOX_WRITING_MBEXT, + SMBOX_READING_MBEXT, + SMBOX_READING, + SMBOX_WRITING_BUFF, + SMBOX_READING_BUFF +}; + +struct lpfc_sysfs_mbox_data { + MAILBOX_t mbox; + uint32_t mboffset; + uint32_t in_ext_wlen; + uint32_t out_ext_wlen; }; struct lpfc_sysfs_mbox { + struct lpfc_sysfs_mbox_data mbox_data; enum sysfs_mbox_state state; size_t offset; struct lpfcMboxq * mbox; + /* process id of the mgmt application */ + pid_t pid; + struct list_head list; + uint8_t * mbext; + uint32_t extoff; + struct lpfc_dmabuf * txmit_buff; + struct lpfc_dmabuf * rcv_buff; +}; +#define MENLO_DID 0x0000FC0E + +enum sysfs_menlo_state { + SMENLO_IDLE, + SMENLO_WRITING, + SMENLO_WRITING_MBEXT, + SMENLO_READING +}; + +struct lpfc_sysfs_menlo_hdr { + uint32_t cmd; + uint32_t cmdsize; + uint32_t rspsize; +}; + +struct lpfc_menlo_genreq64 { + size_t offset; + struct lpfc_iocbq *cmdiocbq; + struct lpfc_iocbq *rspiocbq; + struct lpfc_dmabuf *bmp; + struct lpfc_dmabufext *indmp; + struct ulp_bde64 *cmdbpl; + struct lpfc_dmabufext *outdmp; + uint32_t timeout; + struct list_head inhead; + struct list_head outhead; }; +struct lpfc_sysfs_menlo { + enum sysfs_menlo_state state; + /* process id of the mgmt application */ + struct lpfc_sysfs_menlo_hdr cmdhdr; + struct lpfc_menlo_genreq64 cr; + struct lpfc_menlo_genreq64 cx; + pid_t pid; + struct list_head list; +}; + + struct lpfc_hba; @@ -261,6 +324,52 @@ enum hba_state { LPFC_HBA_ERROR = -1 }; +enum auth_state { + LPFC_AUTH_UNKNOWN = 0, + LPFC_AUTH_SUCCESS = 1, + LPFC_AUTH_FAIL = 2, +}; +enum auth_msg_state { + LPFC_AUTH_NONE = 0, + LPFC_AUTH_REJECT = 1, /* Sent a Reject */ + LPFC_AUTH_NEGOTIATE = 2, /* Auth Negotiate */ + LPFC_DHCHAP_CHALLENGE = 3, /* Challenge */ + LPFC_DHCHAP_REPLY = 4, /* Reply */ + LPFC_DHCHAP_SUCCESS_REPLY = 5, /* Success with Reply */ + LPFC_DHCHAP_SUCCESS = 6, /* Success */ + LPFC_AUTH_DONE = 7, +}; + +struct lpfc_auth { + uint8_t auth_mode; + uint8_t bidirectional; + uint8_t hash_priority[4]; + uint32_t hash_len; + uint8_t dh_group_priority[8]; + uint32_t dh_group_len; + uint32_t reauth_interval; + + uint8_t security_active; + enum auth_state auth_state; + enum auth_msg_state auth_msg_state; + uint32_t trans_id; /* current transaction id. Can be set + by incomming transactions as well */ + uint32_t group_id; + uint32_t hash_id; + uint32_t direction; +#define AUTH_DIRECTION_NONE 0 +#define AUTH_DIRECTION_REMOTE 0x1 +#define AUTH_DIRECTION_LOCAL 0x2 +#define AUTH_DIRECTION_BIDI (AUTH_DIRECTION_LOCAL|AUTH_DIRECTION_REMOTE) + + uint8_t *challenge; + uint32_t challenge_len; + uint8_t *dh_pub_key; + uint32_t dh_pub_key_len; + + unsigned long last_auth; +}; + struct lpfc_vport { struct list_head listentry; struct lpfc_hba *phba; @@ -356,6 +465,14 @@ struct lpfc_vport { #define FC_UNLOADING 0x2 /* HBA in process of unloading drvr */ char *vname; /* Application assigned name */ + /* Fields used for accessing auth service */ + struct lpfc_auth auth; + uint32_t sc_tran_id; + struct list_head sc_response_wait_queue; + struct list_head sc_users; + struct work_struct sc_online_work; + struct work_struct sc_offline_work; + /* Vport Config Parameters */ uint32_t cfg_scan_down; uint32_t cfg_lun_queue_depth; @@ -371,6 +488,7 @@ struct lpfc_vport { uint32_t cfg_max_luns; uint32_t cfg_enable_da_id; uint32_t cfg_max_scsicmpl_time; + uint32_t cfg_enable_auth; uint32_t dev_loss_tmo_changed; @@ -445,6 +563,7 @@ struct lpfc_hba { struct lpfc_dmabuf slim2p; MAILBOX_t *mbox; + uint32_t *mbox_ext; uint32_t *inb_ha_copy; uint32_t *inb_counter; uint32_t inb_last_counter; @@ -573,7 +692,9 @@ struct lpfc_hba { uint64_t fc4OutputRequests; uint64_t fc4ControlRequests; - struct lpfc_sysfs_mbox sysfs_mbox; + /* List of mailbox commands issued through sysfs */ + struct list_head sysfs_mbox_list; + struct list_head sysfs_menlo_list; /* fastpath list. */ spinlock_t scsi_buf_list_lock; @@ -595,11 +716,13 @@ struct lpfc_hba { struct fc_host_statistics link_stats; enum intr_type_t intr_type; struct msix_entry msix_entries[LPFC_MSIX_VECTORS]; + struct lpfcdfc_host *dfc_host; struct list_head port_list; struct lpfc_vport *pport; /* physical lpfc_vport pointer */ uint16_t max_vpi; /* Maximum virtual nports */ -#define LPFC_MAX_VPI 0xFFFF /* Max number of VPI supported */ +#define LPFC_MAX_VPI 0xFFFF /* Max number of VPI supported */ +#define LPFC_INTR_VPI 100 /* Intermediate VPI supported */ unsigned long *vpi_bmask; /* vpi allocation table */ /* Data structure used by fabric iocb scheduler */ --- a/drivers/scsi/lpfc/lpfc_hbadisc.c +++ b/drivers/scsi/lpfc/lpfc_hbadisc.c @@ -59,6 +59,47 @@ static uint8_t lpfcAlpaArray[] = { static void lpfc_disc_timeout_handler(struct lpfc_vport *); static void lpfc_disc_flush_list(struct lpfc_vport *vport); +void +lpfc_start_discovery(struct lpfc_vport *vport) +{ + struct lpfc_hba *phba = vport->phba; + struct lpfc_vport **vports; + int i; + + if (vport->auth.security_active && + vport->auth.auth_state != LPFC_AUTH_SUCCESS) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_DISCOVERY, + "0285 Authentication not complete.\n"); + return; + } + if (vport->port_type == LPFC_NPIV_PORT) { + lpfc_do_scr_ns_plogi(phba, vport); + return; + } + + vports = lpfc_create_vport_work_array(phba); + if (vports != NULL) + for (i = 0; i <= phba->max_vpi && vports[i] != NULL; i++) { + if (vports[i]->port_type == LPFC_PHYSICAL_PORT) + continue; + if (phba->fc_topology == TOPOLOGY_LOOP) { + lpfc_vport_set_state(vports[i], + FC_VPORT_LINKDOWN); + continue; + } + if (phba->link_flag & LS_NPIV_FAB_SUPPORTED) + lpfc_initial_fdisc(vports[i]); + else { + lpfc_vport_set_state(vports[i], + FC_VPORT_NO_FABRIC_SUPP); + lpfc_printf_vlog(vports[i], KERN_ERR, LOG_ELS, + "0259 No NPIV Fabric " + "support\n"); + } + } + lpfc_destroy_vport_work_array(phba, vports); + lpfc_do_scr_ns_plogi(phba, vport); +} void lpfc_terminate_rport_io(struct fc_rport *rport) @@ -416,6 +457,15 @@ lpfc_work_list_done(struct lpfc_hba *phb */ lpfc_nlp_put(ndlp); break; + case LPFC_EVT_REAUTH: + ndlp = (struct lpfc_nodelist *) (evtp->evt_arg1); + lpfc_reauthentication_handler(ndlp); + free_evt = 0; /* evt is part of ndlp */ + /* decrement the node reference count held + * for this queued work + */ + lpfc_nlp_put(ndlp); + break; case LPFC_EVT_DEV_LOSS: ndlp = (struct lpfc_nodelist *)(evtp->evt_arg1); lpfc_dev_loss_tmo_handler(ndlp); @@ -648,6 +698,9 @@ lpfc_cleanup_rpis(struct lpfc_vport *vpo continue; if (ndlp->nlp_state == NLP_STE_UNUSED_NODE) continue; + /* Stop re-authentication timer of all nodes. */ + del_timer_sync(&ndlp->nlp_reauth_tmr); + if ((phba->sli3_options & LPFC_SLI3_VPORT_TEARDOWN) || ((vport->port_type == LPFC_NPIV_PORT) && (ndlp->nlp_DID == NameServer_DID))) @@ -697,6 +750,23 @@ lpfc_linkdown_port(struct lpfc_vport *vp lpfc_port_link_failure(vport); + vport->auth.auth_state = LPFC_AUTH_UNKNOWN; + vport->auth.auth_msg_state = LPFC_AUTH_NONE; +} + +void +lpfc_port_auth_failed(struct lpfc_nodelist *ndlp) +{ + struct lpfc_vport *vport = ndlp->vport; + + vport->auth.auth_state = LPFC_AUTH_FAIL; + vport->auth.auth_msg_state = LPFC_AUTH_NONE; + lpfc_nlp_set_state(vport, ndlp, NLP_STE_NPR_NODE); + if (ndlp->nlp_type & NLP_FABRIC) { + lpfc_port_link_failure(vport); + lpfc_vport_set_state(vport, FC_VPORT_FAILED); + lpfc_issue_els_logo(vport, ndlp, 0); + } } int @@ -801,7 +871,6 @@ lpfc_linkup_port(struct lpfc_vport *vpor return; fc_host_post_event(shost, fc_get_event_number(), FCH_EVT_LINKUP, 0); - spin_lock_irq(shost->host_lock); vport->fc_flag &= ~(FC_PT2PT | FC_PT2PT_PLOGI | FC_ABORT_DISCOVERY | FC_RSCN_MODE | FC_NLP_MORE | FC_RSCN_DISCOVERY); @@ -1424,8 +1493,6 @@ lpfc_mbx_cmpl_fabric_reg_login(struct lp MAILBOX_t *mb = &pmb->mb; struct lpfc_dmabuf *mp = (struct lpfc_dmabuf *) (pmb->context1); struct lpfc_nodelist *ndlp; - struct lpfc_vport **vports; - int i; ndlp = (struct lpfc_nodelist *) pmb->context2; pmb->context1 = NULL; @@ -1463,33 +1530,9 @@ lpfc_mbx_cmpl_fabric_reg_login(struct lp ndlp->nlp_type |= NLP_FABRIC; lpfc_nlp_set_state(vport, ndlp, NLP_STE_UNMAPPED_NODE); - if (vport->port_state == LPFC_FABRIC_CFG_LINK) { - vports = lpfc_create_vport_work_array(phba); - if (vports != NULL) - for(i = 0; - i <= phba->max_vpi && vports[i] != NULL; - i++) { - if (vports[i]->port_type == LPFC_PHYSICAL_PORT) - continue; - if (phba->fc_topology == TOPOLOGY_LOOP) { - lpfc_vport_set_state(vports[i], - FC_VPORT_LINKDOWN); - continue; - } - if (phba->link_flag & LS_NPIV_FAB_SUPPORTED) - lpfc_initial_fdisc(vports[i]); - else { - lpfc_vport_set_state(vports[i], - FC_VPORT_NO_FABRIC_SUPP); - lpfc_printf_vlog(vport, KERN_ERR, - LOG_ELS, - "0259 No NPIV " - "Fabric support\n"); - } - } - lpfc_destroy_vport_work_array(phba, vports); - lpfc_do_scr_ns_plogi(phba, vport); - } + if (vport->port_state == LPFC_FABRIC_CFG_LINK && + !vport->cfg_enable_auth) + lpfc_start_discovery(vport); lpfc_mbuf_free(phba, mp->virt, mp->phys); kfree(mp); @@ -1894,9 +1937,13 @@ lpfc_enable_node(struct lpfc_vport *vpor sizeof (struct lpfc_nodelist) - sizeof (struct list_head)); INIT_LIST_HEAD(&ndlp->els_retry_evt.evt_listp); INIT_LIST_HEAD(&ndlp->dev_loss_evt.evt_listp); + INIT_LIST_HEAD(&ndlp->els_reauth_evt.evt_listp); init_timer(&ndlp->nlp_delayfunc); ndlp->nlp_delayfunc.function = lpfc_els_retry_delay; ndlp->nlp_delayfunc.data = (unsigned long)ndlp; + init_timer(&ndlp->nlp_reauth_tmr); + ndlp->nlp_reauth_tmr.function = lpfc_reauth_node; + ndlp->nlp_reauth_tmr.data = (unsigned long)ndlp; ndlp->nlp_DID = did; ndlp->vport = vport; ndlp->nlp_sid = NLP_NO_SID; @@ -2264,9 +2311,12 @@ lpfc_cleanup_node(struct lpfc_vport *vpo ndlp->nlp_last_elscmd = 0; del_timer_sync(&ndlp->nlp_delayfunc); + del_timer_sync(&ndlp->nlp_reauth_tmr); list_del_init(&ndlp->els_retry_evt.evt_listp); list_del_init(&ndlp->dev_loss_evt.evt_listp); + if (!list_empty(&ndlp->els_reauth_evt.evt_listp)) + list_del_init(&ndlp->els_reauth_evt.evt_listp); lpfc_unreg_rpi(vport, ndlp); @@ -3073,7 +3123,14 @@ lpfc_filter_by_wwpn(struct lpfc_nodelist sizeof(ndlp->nlp_portname)) == 0; } -static struct lpfc_nodelist * +static int +lpfc_filter_by_wwnn(struct lpfc_nodelist *ndlp, void *param) +{ + return memcmp(&ndlp->nlp_nodename, param, + sizeof(ndlp->nlp_nodename)) == 0; +} + +struct lpfc_nodelist * __lpfc_find_node(struct lpfc_vport *vport, node_filter filter, void *param) { struct lpfc_nodelist *ndlp; @@ -3086,6 +3143,22 @@ __lpfc_find_node(struct lpfc_vport *vpor } /* + * Search node lists for a remote port matching filter criteria + * Caller needs to hold host_lock before calling this routine. + */ +struct lpfc_nodelist * +lpfc_find_node(struct lpfc_vport *vport, node_filter filter, void *param) +{ + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + struct lpfc_nodelist *ndlp; + + spin_lock_irq(shost->host_lock); + ndlp = __lpfc_find_node(vport, filter, param); + spin_unlock_irq(shost->host_lock); + return ndlp; +} + +/* * This routine looks up the ndlp lists for the given RPI. If rpi found it * returns the node list element pointer else return NULL. */ @@ -3111,6 +3184,21 @@ lpfc_findnode_wwpn(struct lpfc_vport *vp return ndlp; } +/* + * This routine looks up the ndlp lists for the given WWNN. If WWNN found it + * returns the node element list pointer else return NULL. + */ +struct lpfc_nodelist * +lpfc_findnode_wwnn(struct lpfc_vport *vport, struct lpfc_name *wwnn) +{ + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + struct lpfc_nodelist *ndlp; + + spin_lock_irq(shost->host_lock); + ndlp = __lpfc_find_node(vport, lpfc_filter_by_wwnn, wwnn); + spin_unlock_irq(shost->host_lock); + return ndlp; +} void lpfc_nlp_init(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp, uint32_t did) @@ -3118,9 +3206,13 @@ lpfc_nlp_init(struct lpfc_vport *vport, memset(ndlp, 0, sizeof (struct lpfc_nodelist)); INIT_LIST_HEAD(&ndlp->els_retry_evt.evt_listp); INIT_LIST_HEAD(&ndlp->dev_loss_evt.evt_listp); + INIT_LIST_HEAD(&ndlp->els_reauth_evt.evt_listp); init_timer(&ndlp->nlp_delayfunc); ndlp->nlp_delayfunc.function = lpfc_els_retry_delay; ndlp->nlp_delayfunc.data = (unsigned long)ndlp; + init_timer(&ndlp->nlp_reauth_tmr); + ndlp->nlp_reauth_tmr.function = lpfc_reauth_node; + ndlp->nlp_reauth_tmr.data = (unsigned long)ndlp; ndlp->nlp_DID = did; ndlp->vport = vport; ndlp->nlp_sid = NLP_NO_SID; --- a/drivers/scsi/lpfc/lpfc_hw.h +++ b/drivers/scsi/lpfc/lpfc_hw.h @@ -64,6 +64,7 @@ #define SLI3_IOCB_CMD_SIZE 128 #define SLI3_IOCB_RSP_SIZE 64 +#define BUF_SZ_4K 4096 /* Common Transport structures and definitions */ @@ -350,7 +351,8 @@ struct csp { uint16_t huntgroup:1; /* FC Word 1, bit 23 */ uint16_t simplex:1; /* FC Word 1, bit 22 */ - uint16_t word1Reserved1:3; /* FC Word 1, bit 21:19 */ + uint16_t security:1; /* FC Word 1, bit 21 */ + uint16_t word1Reserved1:2; /* FC Word 1, bit 20:19 */ uint16_t dhd:1; /* FC Word 1, bit 18 */ uint16_t contIncSeqCnt:1; /* FC Word 1, bit 17 */ uint16_t payloadlength:1; /* FC Word 1, bit 16 */ @@ -367,7 +369,8 @@ struct csp { uint16_t payloadlength:1; /* FC Word 1, bit 16 */ uint16_t contIncSeqCnt:1; /* FC Word 1, bit 17 */ uint16_t dhd:1; /* FC Word 1, bit 18 */ - uint16_t word1Reserved1:3; /* FC Word 1, bit 21:19 */ + uint16_t word1Reserved1:2; /* FC Word 1, bit 20:19 */ + uint16_t security:1; /* FC Word 1, bit 21 */ uint16_t simplex:1; /* FC Word 1, bit 22 */ uint16_t huntgroup:1; /* FC Word 1, bit 23 */ #endif @@ -506,6 +509,17 @@ struct serv_parm { /* Structure is in Bi #define ELS_CMD_SCR 0x62000000 #define ELS_CMD_RNID 0x78000000 #define ELS_CMD_LIRR 0x7A000000 +/* + * ELS commands for authentication + * ELS_CMD_AUTH<<24 | AUTH_NEGOTIATE<<8 | AUTH_VERSION + */ +#define ELS_CMD_AUTH 0x90000000 +#define ELS_CMD_AUTH_RJT 0x90000A01 +#define ELS_CMD_AUTH_NEG 0x90000B01 +#define ELS_CMD_AUTH_DONE 0x90000C01 +#define ELS_CMD_DH_CHA 0x90001001 +#define ELS_CMD_DH_REP 0x90001101 +#define ELS_CMD_DH_SUC 0x90001201 #else /* __LITTLE_ENDIAN_BITFIELD */ #define ELS_CMD_MASK 0xffff #define ELS_RSP_MASK 0xff @@ -542,6 +556,17 @@ struct serv_parm { /* Structure is in Bi #define ELS_CMD_SCR 0x62 #define ELS_CMD_RNID 0x78 #define ELS_CMD_LIRR 0x7A +/* + * ELS commands for authentication + * ELS_CMD_AUTH | AUTH_NEGOTIATE<<16 | AUTH_VERSION<<24 + */ +#define ELS_CMD_AUTH 0x00000090 +#define ELS_CMD_AUTH_RJT 0x010A0090 +#define ELS_CMD_AUTH_NEG 0x010B0090 +#define ELS_CMD_AUTH_DONE 0x010C0090 +#define ELS_CMD_DH_CHA 0x01100090 +#define ELS_CMD_DH_REP 0x01110090 +#define ELS_CMD_DH_SUC 0x01120090 #endif /* @@ -1319,6 +1344,9 @@ typedef struct { /* FireFly BIU registe #define MBX_HEARTBEAT 0x31 #define MBX_WRITE_VPARMS 0x32 #define MBX_ASYNCEVT_ENABLE 0x33 +#define MBX_READ_EVENT_LOG_STATUS 0x37 +#define MBX_READ_EVENT_LOG 0x38 +#define MBX_WRITE_EVENT_LOG 0x39 #define MBX_PORT_CAPABILITIES 0x3B #define MBX_PORT_IOV_CONTROL 0x3C @@ -1457,6 +1485,7 @@ typedef struct { /* FireFly BIU registe #define MBXERR_BAD_RCV_LENGTH 14 #define MBXERR_DMA_ERROR 15 #define MBXERR_ERROR 16 +#define MBXERR_UNKNOWN_CMD 18 #define MBX_NOT_FINISHED 255 #define MBX_BUSY 0xffffff /* Attempted cmd to busy Mailbox */ @@ -1624,6 +1653,13 @@ typedef struct { } un; } BIU_DIAG_VAR; +/* Structure for MB command READ_EVENT_LOG (0x38) */ +typedef struct { + uint32_t rsvd1; + uint32_t offset; + struct ulp_bde64 rcv_bde64; +}READ_EVENT_LOG_VAR; + /* Structure for MB Command INIT_LINK (05) */ typedef struct { @@ -2744,6 +2780,10 @@ typedef struct { /* Union of all Mailbox Command types */ #define MAILBOX_CMD_WSIZE 32 #define MAILBOX_CMD_SIZE (MAILBOX_CMD_WSIZE * sizeof(uint32_t)) +#define MAILBOX_EXT_WSIZE 512 +#define MAILBOX_EXT_SIZE (MAILBOX_EXT_WSIZE * sizeof(uint32_t)) +#define MAILBOX_HBA_EXT_OFFSET 0x100 +#define MAILBOX_MAX_XMIT_SIZE 1024 typedef union { uint32_t varWords[MAILBOX_CMD_WSIZE - 1]; /* first word is type/ @@ -2783,6 +2823,7 @@ typedef union { UNREG_VPI_VAR varUnregVpi; /* cmd = 0x97 (UNREG_VPI) */ ASYNCEVT_ENABLE_VAR varCfgAsyncEvent; /*cmd = x33 (CONFIG_ASYNC) */ struct config_msi_var varCfgMSI;/* cmd = x30 (CONFIG_MSI) */ + READ_EVENT_LOG_VAR varRdEventLog; /* cmd = 0x38 (READ_EVENT_LOG) */ } MAILVARIANTS; /* @@ -3364,14 +3405,16 @@ typedef struct _IOCB { /* IOCB structure #define SLI1_SLIM_SIZE (4 * 1024) /* Up to 498 IOCBs will fit into 16k - * 256 (MAILBOX_t) + 140 (PCB_t) + ( 32 (IOCB_t) * 498 ) = < 16384 + * 256 (MAILBOX_t) + 512 mailbox extension + + * 140 (PCB_t) + ( 32 (IOCB_t) * 498 ) = < 16384 */ #define SLI2_SLIM_SIZE (64 * 1024) /* Maximum IOCBs that will fit in SLI2 slim */ #define MAX_SLI2_IOCB 498 #define MAX_SLIM_IOCB_SIZE (SLI2_SLIM_SIZE - \ - (sizeof(MAILBOX_t) + sizeof(PCB_t))) + (sizeof(MAILBOX_t) + sizeof(PCB_t) + \ + sizeof(uint32_t) * MAILBOX_EXT_WSIZE)) /* HBQ entries are 4 words each = 4k */ #define LPFC_TOTAL_HBQ_SIZE (sizeof(struct lpfc_hbq_entry) * \ @@ -3379,6 +3422,7 @@ typedef struct _IOCB { /* IOCB structure struct lpfc_sli2_slim { MAILBOX_t mbx; + uint32_t mbx_ext_words[MAILBOX_EXT_WSIZE]; PCB_t pcb; IOCB_t IOCBs[MAX_SLIM_IOCB_SIZE]; }; --- a/drivers/scsi/lpfc/lpfc_init.c +++ b/drivers/scsi/lpfc/lpfc_init.c @@ -44,7 +44,22 @@ #include "lpfc_crtn.h" #include "lpfc_vport.h" #include "lpfc_version.h" - +#include "lpfc_auth_access.h" +#include "lpfc_security.h" +#include +#include + +/* vendor ID used in SCSI netlink calls */ +#define LPFC_NL_VENDOR_ID (SCSI_NL_VID_TYPE_PCI | PCI_VENDOR_ID_EMULEX) +const char *security_work_q_name = "fc_sc_wq"; +extern struct workqueue_struct *security_work_q; +extern struct list_head fc_security_user_list; +extern int fc_service_state; +void lpfc_fc_sc_security_online(struct work_struct *work); +void lpfc_fc_sc_security_offline(struct work_struct *work); +int lpfc_fc_queue_security_work(struct lpfc_vport *, struct work_struct *); +void lpfc_rcv_nl_event(struct notifier_block *, unsigned long , void *); +#include "lpfc_ioctl.h" static int lpfc_parse_vpd(struct lpfc_hba *, uint8_t *, int); static void lpfc_get_hba_model_desc(struct lpfc_hba *, uint8_t *, uint8_t *); static int lpfc_post_rcv_buf(struct lpfc_hba *); @@ -54,6 +69,26 @@ static struct scsi_transport_template *l static DEFINE_IDR(lpfc_hba_index); /** + * lpfc_hba_max_vpi - Get the maximum supported VPI for an HBA + * @device: The PCI device ID for this HBA + * + * Description: + * This routine will return the maximum supported VPI limit for each HBA. In + * most cases the maximum VPI limit will be 0xFFFF, which indicates that the + * driver supports whatever the HBA can support. In some cases the driver + * supports fewer VPI that the HBA supports. + */ +static inline uint16_t +lpfc_hba_max_vpi(unsigned short device) +{ + if ((device == PCI_DEVICE_ID_HELIOS) || + (device == PCI_DEVICE_ID_ZEPHYR)) + return LPFC_INTR_VPI; + else + return LPFC_MAX_VPI; +} + +/** * lpfc_config_port_prep: Perform lpfc initialization prior to config port. * @phba: pointer to lpfc hba data structure. * @@ -444,9 +479,20 @@ lpfc_config_port_post(struct lpfc_hba *p /* Set up error attention (ERATT) polling timer */ mod_timer(&phba->eratt_poll, jiffies + HZ * LPFC_ERATT_POLL_INTERVAL); + if (vport->cfg_enable_auth) { + if (lpfc_security_service_state == SECURITY_OFFLINE) { + lpfc_printf_log(vport->phba, KERN_ERR, LOG_SECURITY, + "1000 Authentication is enabled but " + "authentication service is not running\n"); + vport->auth.auth_mode = FC_AUTHMODE_UNKNOWN; + phba->link_state = LPFC_HBA_ERROR; + mempool_free(pmb, phba->mbox_mem_pool); + return 0; + } + } + lpfc_init_link(phba, pmb, phba->cfg_topology, phba->cfg_link_speed); pmb->mbox_cmpl = lpfc_sli_def_mbox_cmpl; - lpfc_set_loopback_flag(phba); rc = lpfc_sli_issue_mbox(phba, pmb, MBX_NOWAIT); if (rc != MBX_SUCCESS) { lpfc_printf_log(phba, KERN_ERR, LOG_INIT, @@ -886,8 +932,7 @@ lpfc_handle_eratt(struct lpfc_hba *phba) fc_host_post_vendor_event(shost, fc_get_event_number(), sizeof(temp_event_data), (char *) &temp_event_data, - SCSI_NL_VID_TYPE_PCI - | PCI_VENDOR_ID_EMULEX); + LPFC_NL_VENDOR_ID); spin_lock_irq(&phba->hbalock); phba->over_temp_state = HBA_OVER_TEMP; @@ -909,7 +954,7 @@ lpfc_handle_eratt(struct lpfc_hba *phba) shost = lpfc_shost_from_vport(vport); fc_host_post_vendor_event(shost, fc_get_event_number(), sizeof(event_data), (char *) &event_data, - SCSI_NL_VID_TYPE_PCI | PCI_VENDOR_ID_EMULEX); + LPFC_NL_VENDOR_ID); lpfc_offline_eratt(phba); } @@ -1675,8 +1720,15 @@ lpfc_cleanup(struct lpfc_vport *vport) void lpfc_stop_vport_timers(struct lpfc_vport *vport) { + struct fc_security_request *fc_sc_req; del_timer_sync(&vport->els_tmofunc); del_timer_sync(&vport->fc_fdmitmo); + while (!list_empty(&vport->sc_response_wait_queue)) { + fc_sc_req = list_get_first(&vport->sc_response_wait_queue, + struct fc_security_request, rlist); + del_timer_sync(&fc_sc_req->timer); + kfree(fc_sc_req); + } lpfc_can_disctmo(vport); return; } @@ -1963,12 +2015,7 @@ lpfc_create_port(struct lpfc_hba *phba, struct Scsi_Host *shost; int error = 0; - if (dev != &phba->pcidev->dev) - shost = scsi_host_alloc(&lpfc_vport_template, - sizeof(struct lpfc_vport)); - else - shost = scsi_host_alloc(&lpfc_template, - sizeof(struct lpfc_vport)); + shost = scsi_host_alloc(&lpfc_template, sizeof(struct lpfc_vport)); if (!shost) goto out; @@ -2017,6 +2064,15 @@ lpfc_create_port(struct lpfc_hba *phba, error = scsi_add_host(shost, dev); if (error) goto out_put_shost; + vport->auth.challenge = NULL; + vport->auth.challenge_len = 0; + vport->auth.dh_pub_key = NULL; + vport->auth.dh_pub_key_len = 0; + + INIT_WORK(&vport->sc_online_work, lpfc_fc_sc_security_online); + INIT_WORK(&vport->sc_offline_work, lpfc_fc_sc_security_offline); + INIT_LIST_HEAD(&vport->sc_users); + INIT_LIST_HEAD(&vport->sc_response_wait_queue); spin_lock_irq(&phba->hbalock); list_add_tail(&vport->listentry, &phba->port_list); @@ -2387,7 +2443,7 @@ lpfc_pci_probe_one(struct pci_dev *pdev, * establish the host. */ lpfc_get_cfgparam(phba); - phba->max_vpi = LPFC_MAX_VPI; + phba->max_vpi = lpfc_hba_max_vpi(phba->pcidev->device); /* Initialize timers used by driver */ init_timer(&phba->hb_tmofunc); @@ -2453,6 +2509,8 @@ lpfc_pci_probe_one(struct pci_dev *pdev, memset(phba->slim2p.virt, 0, SLI2_SLIM_SIZE); phba->mbox = phba->slim2p.virt + offsetof(struct lpfc_sli2_slim, mbx); + phba->mbox_ext = (phba->slim2p.virt + + offsetof(struct lpfc_sli2_slim, mbx_ext_words)); phba->pcb = (phba->slim2p.virt + offsetof(struct lpfc_sli2_slim, pcb)); phba->IOCBs = (phba->slim2p.virt + offsetof(struct lpfc_sli2_slim, IOCBs)); @@ -2548,11 +2606,30 @@ lpfc_pci_probe_one(struct pci_dev *pdev, /* Initialize list to save ELS buffers */ INIT_LIST_HEAD(&phba->elsbuf); + /* Initialize list of sysfs mailbox commands */ + INIT_LIST_HEAD(&phba->sysfs_mbox_list); + /* Initialize list of sysfs menlo commands */ + INIT_LIST_HEAD(&phba->sysfs_menlo_list); + vport = lpfc_create_port(phba, phba->brd_no, &phba->pcidev->dev); if (!vport) goto out_kthread_stop; shost = lpfc_shost_from_vport(vport); + + if ((lpfc_get_security_enabled)(shost)) { + unsigned long flags; + /* Triggers fcauthd to register if it is running */ + fc_host_post_event(shost, fc_get_event_number(), + FCH_EVT_PORT_ONLINE, shost->host_no); + spin_lock_irqsave(&fc_security_user_lock, flags); + list_add_tail(&vport->sc_users, &fc_security_user_list); + spin_unlock_irqrestore(&fc_security_user_lock, flags); + if (fc_service_state == FC_SC_SERVICESTATE_ONLINE) { + lpfc_fc_queue_security_work(vport, + &vport->sc_online_work); + } + } phba->pport = vport; lpfc_debugfs_initialize(vport); @@ -2610,6 +2687,14 @@ lpfc_pci_probe_one(struct pci_dev *pdev, phba->intr_type = INTx; } + phba->dfc_host = lpfcdfc_host_add(pdev, shost, phba); + if (!phba->dfc_host) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1201 Failed to allocate dfc_host \n"); + error = -ENOMEM; + goto out_free_irq; + } + if (lpfc_alloc_sysfs_attr(vport)) { lpfc_printf_log(phba, KERN_ERR, LOG_INIT, "1476 Failed to allocate sysfs attr\n"); @@ -2658,6 +2743,8 @@ out_remove_device: vport->load_flag |= FC_UNLOADING; spin_unlock_irq(shost->host_lock); out_free_irq: + if (phba->dfc_host) + lpfcdfc_host_del(phba->dfc_host); lpfc_stop_phba_timers(phba); phba->pport->work_port_events = 0; @@ -2720,6 +2807,12 @@ lpfc_pci_remove_one(struct pci_dev *pdev struct lpfc_hba *phba = vport->phba; int bars = pci_select_bars(pdev, IORESOURCE_MEM); + /* In case PCI channel permanently disabled, rescan SCSI devices */ + if (pdev->error_state == pci_channel_io_perm_failure) + lpfc_scsi_dev_rescan(phba); + lpfcdfc_host_del(phba->dfc_host); + phba->dfc_host = NULL; + spin_lock_irq(&phba->hbalock); vport->load_flag |= FC_UNLOADING; spin_unlock_irq(&phba->hbalock); @@ -3076,12 +3169,34 @@ lpfc_init(void) return -ENOMEM; } } + error = scsi_nl_add_driver(LPFC_NL_VENDOR_ID, &lpfc_template, + lpfc_rcv_nl_msg, lpfc_rcv_nl_event); + if (error) + goto out_release_transport; + security_work_q = create_singlethread_workqueue(security_work_q_name); + if (!security_work_q) + goto out_nl_remove_driver; + INIT_LIST_HEAD(&fc_security_user_list); error = pci_register_driver(&lpfc_driver); - if (error) { - fc_release_transport(lpfc_transport_template); - if (lpfc_enable_npiv) - fc_release_transport(lpfc_vport_transport_template); - } + if (error) + goto out_destroy_workqueue; + error = lpfc_cdev_init(); + if (error) + goto out_pci_unregister; + + return error; + +out_pci_unregister: + pci_unregister_driver(&lpfc_driver); +out_destroy_workqueue: + destroy_workqueue(security_work_q); + security_work_q = NULL; +out_nl_remove_driver: + scsi_nl_remove_driver(LPFC_NL_VENDOR_ID); +out_release_transport: + fc_release_transport(lpfc_transport_template); + if (lpfc_enable_npiv) + fc_release_transport(lpfc_vport_transport_template); return error; } @@ -3097,9 +3212,14 @@ static void __exit lpfc_exit(void) { pci_unregister_driver(&lpfc_driver); + if (security_work_q) + destroy_workqueue(security_work_q); + security_work_q = NULL; + scsi_nl_remove_driver(LPFC_NL_VENDOR_ID); fc_release_transport(lpfc_transport_template); if (lpfc_enable_npiv) fc_release_transport(lpfc_vport_transport_template); + lpfc_cdev_exit(); } module_init(lpfc_init); --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_ioctl.c @@ -0,0 +1,2519 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2006-2008 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.emulex.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ + +#include +#include +#include +#include + +#include +#include + +#include "lpfc_hw.h" +#include "lpfc_sli.h" +#include "lpfc_nl.h" +#include "lpfc_disc.h" +#include "lpfc_scsi.h" +#include "lpfc.h" +#include "lpfc_crtn.h" +#include "lpfc_ioctl.h" +#include "lpfc_logmsg.h" +#include "lpfc_vport.h" + + +struct lpfcdfc_event { + struct list_head node; + int ref; + wait_queue_head_t wq; + + /* Event type and waiter identifiers */ + uint32_t type_mask; + uint32_t req_id; + uint32_t reg_id; + + /* next two flags are here for the auto-delete logic */ + unsigned long wait_time_stamp; + int waiting; + + /* seen and not seen events */ + struct list_head events_to_get; + struct list_head events_to_see; +}; + +struct event_data { + struct list_head node; + uint32_t type; + uint32_t immed_dat; + void * data; + uint32_t len; +}; + + +/* values for a_topology */ +#define LNK_LOOP 0x1 +#define LNK_PUBLIC_LOOP 0x2 +#define LNK_FABRIC 0x3 +#define LNK_PT2PT 0x4 + +/* values for a_linkState */ +#define LNK_DOWN 0x1 +#define LNK_UP 0x2 +#define LNK_FLOGI 0x3 +#define LNK_DISCOVERY 0x4 +#define LNK_REDISCOVERY 0x5 +#define LNK_READY 0x6 + +struct lpfcdfc_host { + struct list_head node; + int inst; + struct lpfc_hba * phba; + struct lpfc_vport *vport; + struct Scsi_Host * host; + struct pci_dev * dev; + void (*base_ct_unsol_event)(struct lpfc_hba *, + struct lpfc_sli_ring *, + struct lpfc_iocbq *); + /* Threads waiting for async event */ + struct list_head ev_waiters; + uint32_t blocked; + uint32_t ref_count; +}; + + + + +static void lpfc_ioctl_timeout_iocb_cmpl(struct lpfc_hba *, + struct lpfc_iocbq *, struct lpfc_iocbq *); + +static struct lpfc_dmabufext * +dfc_cmd_data_alloc(struct lpfc_hba *, char *, + struct ulp_bde64 *, uint32_t); +static int dfc_cmd_data_free(struct lpfc_hba *, struct lpfc_dmabufext *); +static int dfc_rsp_data_copy(struct lpfc_hba *, uint8_t *, + struct lpfc_dmabufext *, + uint32_t); +static int lpfc_issue_ct_rsp(struct lpfc_hba *, uint32_t, struct lpfc_dmabuf *, + struct lpfc_dmabufext *); + +static struct lpfcdfc_host * lpfcdfc_host_from_hba(struct lpfc_hba *); + +static DEFINE_MUTEX(lpfcdfc_lock); + +static struct list_head lpfcdfc_hosts = LIST_HEAD_INIT(lpfcdfc_hosts); + +static int lpfcdfc_major = 0; + +static int +lpfc_ioctl_hba_rnid(struct lpfc_hba * phba, + struct lpfcCmdInput * cip, + void *dataout) +{ + struct nport_id idn; + struct lpfc_sli *psli; + struct lpfc_iocbq *cmdiocbq = NULL; + struct lpfc_iocbq *rspiocbq = NULL; + RNID *prsp; + uint32_t *pcmd; + uint32_t *psta; + IOCB_t *rsp; + struct lpfc_sli_ring *pring; + void *context2; + int i0; + int rtnbfrsiz; + struct lpfc_nodelist *pndl; + int rc = 0; + + psli = &phba->sli; + pring = &psli->ring[LPFC_ELS_RING]; + + if (copy_from_user((uint8_t *) &idn, (void __user *) cip->lpfc_arg1, + sizeof(struct nport_id))) { + rc = EIO; + return rc; + } + + if (idn.idType == LPFC_WWNN_TYPE) + pndl = lpfc_findnode_wwnn(phba->pport, + (struct lpfc_name *) idn.wwpn); + else + pndl = lpfc_findnode_wwpn(phba->pport, + (struct lpfc_name *) idn.wwpn); + + if (!pndl || !NLP_CHK_NODE_ACT(pndl)) + return ENODEV; + + for (i0 = 0; + i0 < 10 && (pndl->nlp_flag & NLP_ELS_SND_MASK) == NLP_RNID_SND; + i0++) { + mdelay(1000); + } + + if (i0 == 10) { + pndl->nlp_flag &= ~NLP_RNID_SND; + return EBUSY; + } + + cmdiocbq = lpfc_prep_els_iocb(phba->pport, 1, (2 * sizeof(uint32_t)), 0, + pndl, pndl->nlp_DID, ELS_CMD_RNID); + if (!cmdiocbq) + return ENOMEM; + + /* + * Context2 is used by prep/free to locate cmd and rsp buffers, + * but context2 is also used by iocb_wait to hold a rspiocb ptr. + * The rsp iocbq can be returned from the completion routine for + * iocb_wait, so save the prep/free value locally . It will be + * restored after returning from iocb_wait. + */ + context2 = cmdiocbq->context2; + + if ((rspiocbq = lpfc_sli_get_iocbq(phba)) == NULL) { + rc = ENOMEM; + goto sndrndqwt; + } + rsp = &(rspiocbq->iocb); + + pcmd = (uint32_t *) (((struct lpfc_dmabuf *) cmdiocbq->context2)->virt); + *pcmd++ = ELS_CMD_RNID; + + memset((void *) pcmd, 0, sizeof (RNID)); + ((RNID *) pcmd)->Format = 0; + ((RNID *) pcmd)->Format = RNID_TOPOLOGY_DISC; + cmdiocbq->context1 = NULL; + cmdiocbq->context2 = NULL; + cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; + + pndl->nlp_flag |= NLP_RNID_SND; + cmdiocbq->iocb.ulpTimeout = (phba->fc_ratov * 2) + 3 ; + + rc = lpfc_sli_issue_iocb_wait(phba, pring, cmdiocbq, rspiocbq, + (phba->fc_ratov * 2) + LPFC_DRVR_TIMEOUT); + pndl->nlp_flag &= ~NLP_RNID_SND; + cmdiocbq->context2 = context2; + + if (rc == IOCB_TIMEDOUT) { + lpfc_sli_release_iocbq(phba, rspiocbq); + cmdiocbq->context1 = NULL; + cmdiocbq->iocb_cmpl = lpfc_ioctl_timeout_iocb_cmpl; + return EIO; + } + + if (rc != IOCB_SUCCESS) { + rc = EIO; + goto sndrndqwt; + } + + if (rsp->ulpStatus == IOSTAT_SUCCESS) { + struct lpfc_dmabuf *buf_ptr1, *buf_ptr; + buf_ptr1 = (struct lpfc_dmabuf *)(cmdiocbq->context2); + buf_ptr = list_entry(buf_ptr1->list.next, struct lpfc_dmabuf, + list); + psta = (uint32_t*)buf_ptr->virt; + prsp = (RNID *) (psta + 1); /* then rnid response data */ + rtnbfrsiz = prsp->CommonLen + prsp->SpecificLen + + sizeof (uint32_t); + memcpy((uint8_t *) dataout, (uint8_t *) psta, rtnbfrsiz); + + if (rtnbfrsiz > cip->lpfc_outsz) + rtnbfrsiz = cip->lpfc_outsz; + if (copy_to_user + ((void __user *) cip->lpfc_arg2, (uint8_t *) & rtnbfrsiz, + sizeof (int))) + rc = EIO; + } else if (rsp->ulpStatus == IOSTAT_LS_RJT) { + uint8_t ls_rjt[8]; + uint32_t *ls_rjtrsp; + + ls_rjtrsp = (uint32_t*)(ls_rjt + 4); + + /* construct the LS_RJT payload */ + ls_rjt[0] = 0x01; + ls_rjt[1] = 0x00; + ls_rjt[2] = 0x00; + ls_rjt[3] = 0x00; + + *ls_rjtrsp = be32_to_cpu(rspiocbq->iocb.un.ulpWord[4]); + rtnbfrsiz = 8; + memcpy((uint8_t *) dataout, (uint8_t *) ls_rjt, rtnbfrsiz); + if (copy_to_user + ((void __user *) cip->lpfc_arg2, (uint8_t *) & rtnbfrsiz, + sizeof (int))) + rc = EIO; + } else + rc = EACCES; + +sndrndqwt: + if (cmdiocbq) + lpfc_els_free_iocb(phba, cmdiocbq); + + if (rspiocbq) + lpfc_sli_release_iocbq(phba, rspiocbq); + + return rc; +} + +static void +lpfc_ioctl_timeout_iocb_cmpl(struct lpfc_hba * phba, + struct lpfc_iocbq * cmd_iocb_q, + struct lpfc_iocbq * rsp_iocb_q) +{ + struct lpfc_timedout_iocb_ctxt *iocb_ctxt = cmd_iocb_q->context1; + + if (!iocb_ctxt) { + if (cmd_iocb_q->context2) + lpfc_els_free_iocb(phba, cmd_iocb_q); + else + lpfc_sli_release_iocbq(phba,cmd_iocb_q); + return; + } + + if (iocb_ctxt->outdmp) + dfc_cmd_data_free(phba, iocb_ctxt->outdmp); + + if (iocb_ctxt->indmp) + dfc_cmd_data_free(phba, iocb_ctxt->indmp); + + if (iocb_ctxt->mp) { + lpfc_mbuf_free(phba, + iocb_ctxt->mp->virt, + iocb_ctxt->mp->phys); + kfree(iocb_ctxt->mp); + } + + if (iocb_ctxt->bmp) { + lpfc_mbuf_free(phba, + iocb_ctxt->bmp->virt, + iocb_ctxt->bmp->phys); + kfree(iocb_ctxt->bmp); + } + + lpfc_sli_release_iocbq(phba,cmd_iocb_q); + + if (iocb_ctxt->rspiocbq) + lpfc_sli_release_iocbq(phba, iocb_ctxt->rspiocbq); + + kfree(iocb_ctxt); +} + + +static int +lpfc_ioctl_send_els(struct lpfc_hba * phba, + struct lpfcCmdInput * cip, void *dataout) +{ + struct lpfc_sli *psli = &phba->sli; + struct lpfc_sli_ring *pring = &psli->ring[LPFC_ELS_RING]; + struct lpfc_iocbq *cmdiocbq, *rspiocbq; + struct lpfc_dmabufext *pcmdext = NULL, *prspext = NULL; + struct lpfc_nodelist *pndl; + struct ulp_bde64 *bpl; + IOCB_t *rsp; + struct lpfc_dmabuf *pcmd, *prsp, *pbuflist = NULL; + uint16_t rpi = 0; + struct nport_id destID; + int rc = 0; + uint32_t cmdsize; + uint32_t rspsize; + uint32_t elscmd; + int iocb_status; + + elscmd = *(uint32_t *)cip->lpfc_arg2; + cmdsize = cip->lpfc_arg4; + rspsize = cip->lpfc_outsz; + + if (copy_from_user((uint8_t *)&destID, (void __user *)cip->lpfc_arg1, + sizeof(struct nport_id))) + return EIO; + + if ((rspiocbq = lpfc_sli_get_iocbq(phba)) == NULL) + return ENOMEM; + + rsp = &rspiocbq->iocb; + + if (destID.idType == 0) + pndl = lpfc_findnode_wwpn(phba->pport, + (struct lpfc_name *)&destID.wwpn); + else { + destID.d_id = (destID.d_id & Mask_DID); + pndl = lpfc_findnode_did(phba->pport, destID.d_id); + } + + if (!pndl || !NLP_CHK_NODE_ACT(pndl)) { + if (destID.idType == 0) { + lpfc_sli_release_iocbq(phba, rspiocbq); + return ENODEV; + } + if (!pndl) { + pndl = kmalloc(sizeof (struct lpfc_nodelist), + GFP_KERNEL); + if (!pndl) { + lpfc_sli_release_iocbq(phba, rspiocbq); + return ENODEV; + } + lpfc_nlp_init(phba->pport, pndl, destID.d_id); + lpfc_nlp_set_state(phba->pport, pndl, NLP_STE_NPR_NODE); + } else { + pndl = lpfc_enable_node(phba->pport, pndl, + NLP_STE_NPR_NODE); + if (!pndl) { + lpfc_sli_release_iocbq(phba, rspiocbq); + return ENODEV; + } + } + } else { + lpfc_nlp_get(pndl); + rpi = pndl->nlp_rpi; + } + + cmdiocbq = lpfc_prep_els_iocb(phba->pport, 1, cmdsize, 0, pndl, + pndl->nlp_DID, elscmd); + + /* release the new pndl once the iocb complete */ + lpfc_nlp_put(pndl); + + if (cmdiocbq == NULL) { + lpfc_sli_release_iocbq(phba, rspiocbq); + return EIO; + } + + pcmd = (struct lpfc_dmabuf *) cmdiocbq->context2; + prsp = (struct lpfc_dmabuf *) pcmd->list.next; + + /* + * If we exceed the size of the allocated mbufs we need to + * free them and allocate our own. + */ + if ((cmdsize > LPFC_BPL_SIZE) || (rspsize > LPFC_BPL_SIZE)) { + lpfc_mbuf_free(phba, pcmd->virt, pcmd->phys); + kfree(pcmd); + lpfc_mbuf_free(phba, prsp->virt, prsp->phys); + kfree(prsp); + cmdiocbq->context2 = NULL; + + pbuflist = (struct lpfc_dmabuf *) cmdiocbq->context3; + bpl = (struct ulp_bde64 *) pbuflist->virt; + pcmdext = dfc_cmd_data_alloc(phba, cip->lpfc_arg2, + bpl, cmdsize); + if (!pcmdext) { + lpfc_els_free_iocb(phba, cmdiocbq); + lpfc_sli_release_iocbq(phba, rspiocbq); + return ENOMEM; + } + bpl += pcmdext->flag; + prspext = dfc_cmd_data_alloc(phba, NULL, bpl, rspsize); + if (!prspext) { + dfc_cmd_data_free(phba, pcmdext); + lpfc_els_free_iocb(phba, cmdiocbq); + lpfc_sli_release_iocbq(phba, rspiocbq); + return ENOMEM; + } + } else { + /* Copy the command from user space */ + if (copy_from_user((uint8_t *) pcmd->virt, + (void __user *) cip->lpfc_arg2, + cmdsize)) { + lpfc_els_free_iocb(phba, cmdiocbq); + lpfc_sli_release_iocbq(phba, rspiocbq); + return EIO; + } + } + + cmdiocbq->iocb.ulpContext = rpi; + cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; + cmdiocbq->context1 = NULL; + cmdiocbq->context2 = NULL; + + iocb_status = lpfc_sli_issue_iocb_wait(phba, pring, cmdiocbq, rspiocbq, + (phba->fc_ratov*2) + LPFC_DRVR_TIMEOUT); + rc = iocb_status; + + if (rc == IOCB_SUCCESS) { + if (rsp->ulpStatus == IOSTAT_SUCCESS) { + if (rspsize < (rsp->un.ulpWord[0] & 0xffffff)) { + rc = ERANGE; + } else { + rspsize = rsp->un.ulpWord[0] & 0xffffff; + if (pbuflist) { + if (dfc_rsp_data_copy( + phba, + (uint8_t *) cip->lpfc_dataout, + prspext, + rspsize)) { + rc = EIO; + } else { + cip->lpfc_outsz = 0; + } + } else { + if (copy_to_user( (void __user *) + cip->lpfc_dataout, + (uint8_t *) prsp->virt, + rspsize)) { + rc = EIO; + } else { + cip->lpfc_outsz = 0; + } + } + } + } else if (rsp->ulpStatus == IOSTAT_LS_RJT) { + uint8_t ls_rjt[8]; + + /* construct the LS_RJT payload */ + ls_rjt[0] = 0x01; + ls_rjt[1] = 0x00; + ls_rjt[2] = 0x00; + ls_rjt[3] = 0x00; + memcpy(&ls_rjt[4], (uint8_t *) &rsp->un.ulpWord[4], + sizeof(uint32_t)); + + if (rspsize < 8) + rc = ERANGE; + else + rspsize = 8; + + memcpy(dataout, ls_rjt, rspsize); + } else + rc = EIO; + + if (copy_to_user((void __user *)cip->lpfc_arg3, + (uint8_t *)&rspsize, sizeof(uint32_t))) + rc = EIO; + } else { + rc = EIO; + } + + if (pbuflist) { + dfc_cmd_data_free(phba, pcmdext); + dfc_cmd_data_free(phba, prspext); + } else + cmdiocbq->context2 = (uint8_t *) pcmd; + + if (iocb_status != IOCB_TIMEDOUT) + lpfc_els_free_iocb(phba, cmdiocbq); + + lpfc_sli_release_iocbq(phba, rspiocbq); + return rc; +} + +static int +lpfc_ioctl_send_mgmt_rsp(struct lpfc_hba * phba, + struct lpfcCmdInput * cip) +{ + struct Scsi_Host *shost = lpfc_shost_from_vport(phba->pport); + struct ulp_bde64 *bpl; + struct lpfc_dmabuf *bmp = NULL; + struct lpfc_dmabufext *indmp = NULL; + uint32_t tag = (uint32_t)cip->lpfc_flag; /* XRI for XMIT_SEQUENCE */ + unsigned long reqbfrcnt = (unsigned long)cip->lpfc_arg2; + int rc = 0; + unsigned long iflag; + + if (!reqbfrcnt || (reqbfrcnt > (80 * BUF_SZ_4K))) { + rc = ERANGE; + return rc; + } + + bmp = kmalloc(sizeof (struct lpfc_dmabuf), GFP_KERNEL); + if (!bmp) { + rc = ENOMEM; + goto send_mgmt_rsp_exit; + } + spin_lock_irqsave(shost->host_lock, iflag); + bmp->virt = lpfc_mbuf_alloc(phba, 0, &bmp->phys); + spin_unlock_irqrestore(shost->host_lock, iflag); /* remove */ + if (!bmp->virt) { + rc = ENOMEM; + goto send_mgmt_rsp_free_bmp; + } + + INIT_LIST_HEAD(&bmp->list); + bpl = (struct ulp_bde64 *) bmp->virt; + + indmp = dfc_cmd_data_alloc(phba, cip->lpfc_arg1, bpl, reqbfrcnt); + if (!indmp) { + rc = ENOMEM; + goto send_mgmt_rsp_free_bmpvirt; + } + rc = lpfc_issue_ct_rsp(phba, tag, bmp, indmp); + if (rc) { + if (rc == IOCB_TIMEDOUT) + rc = ETIMEDOUT; + else if (rc == IOCB_ERROR) + rc = EACCES; + } + + dfc_cmd_data_free(phba, indmp); +send_mgmt_rsp_free_bmpvirt: + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); +send_mgmt_rsp_free_bmp: + kfree(bmp); +send_mgmt_rsp_exit: + return rc; +} + +static int +lpfc_ioctl_send_mgmt_cmd(struct lpfc_hba * phba, + struct lpfcCmdInput * cip, void *dataout) +{ + struct Scsi_Host *shost = lpfc_shost_from_vport(phba->pport); + struct lpfc_nodelist *pndl = NULL; + struct ulp_bde64 *bpl = NULL; + struct lpfc_name findwwn; + uint32_t finddid, timeout; + struct lpfc_iocbq *cmdiocbq = NULL, *rspiocbq = NULL; + struct lpfc_dmabufext *indmp = NULL, *outdmp = NULL; + IOCB_t *cmd = NULL, *rsp = NULL; + struct lpfc_dmabuf *bmp = NULL; + struct lpfc_sli *psli = NULL; + struct lpfc_sli_ring *pring = NULL; + int i0 = 0, rc = 0, reqbfrcnt, snsbfrcnt; + struct lpfc_timedout_iocb_ctxt *iocb_ctxt; + + psli = &phba->sli; + pring = &psli->ring[LPFC_ELS_RING]; + + if (!(psli->sli_flag & LPFC_SLI2_ACTIVE)) { + rc = EACCES; + goto send_mgmt_cmd_exit; + } + + reqbfrcnt = cip->lpfc_arg4; + snsbfrcnt = cip->lpfc_arg5; + + if (!reqbfrcnt || !snsbfrcnt + || (reqbfrcnt + snsbfrcnt > 80 * BUF_SZ_4K)) { + rc = ERANGE; + goto send_mgmt_cmd_exit; + } + + if (phba->pport->port_state != LPFC_VPORT_READY) { + rc = ENODEV; + goto send_mgmt_cmd_exit; + } + + if (cip->lpfc_cmd == LPFC_HBA_SEND_MGMT_CMD) { + rc = copy_from_user(&findwwn, (void __user *)cip->lpfc_arg3, + sizeof(struct lpfc_name)); + if (rc) { + rc = EIO; + goto send_mgmt_cmd_exit; + } + pndl = lpfc_findnode_wwpn(phba->pport, &findwwn); + /* Do additional get to pndl found so that at the end of the + * function we can do unditional lpfc_nlp_put on it. + */ + if (pndl && NLP_CHK_NODE_ACT(pndl)) + lpfc_nlp_get(pndl); + } else { + finddid = (uint32_t)(unsigned long)cip->lpfc_arg3; + pndl = lpfc_findnode_did(phba->pport, finddid); + if (!pndl || !NLP_CHK_NODE_ACT(pndl)) { + if (phba->pport->fc_flag & FC_FABRIC) { + if (!pndl) { + pndl = kmalloc(sizeof + (struct lpfc_nodelist), + GFP_KERNEL); + if (!pndl) { + rc = ENODEV; + goto send_mgmt_cmd_exit; + } + lpfc_nlp_init(phba->pport, pndl, + finddid); + lpfc_nlp_set_state(phba->pport, + pndl, NLP_STE_PLOGI_ISSUE); + /* Indicate free ioctl allocated + * memory for ndlp after it's done + */ + NLP_SET_FREE_REQ(pndl); + } else + lpfc_enable_node(phba->pport, + pndl, NLP_STE_PLOGI_ISSUE); + + if (lpfc_issue_els_plogi(phba->pport, + pndl->nlp_DID, 0)) { + rc = ENODEV; + goto send_mgmt_cmd_free_pndl_exit; + } + + /* Allow the node to complete discovery */ + while (i0++ < 4) { + if (pndl->nlp_state == + NLP_STE_UNMAPPED_NODE) + break; + msleep(500); + } + + if (i0 == 4) { + rc = ENODEV; + goto send_mgmt_cmd_free_pndl_exit; + } + } else { + rc = ENODEV; + goto send_mgmt_cmd_exit; + } + } else + /* Do additional get to pndl found so at the end of + * the function we can do unconditional lpfc_nlp_put. + */ + lpfc_nlp_get(pndl); + } + + if (!pndl || !NLP_CHK_NODE_ACT(pndl)) { + rc = ENODEV; + goto send_mgmt_cmd_exit; + } + + if (pndl->nlp_flag & NLP_ELS_SND_MASK) { + rc = ENODEV; + goto send_mgmt_cmd_free_pndl_exit; + } + + spin_lock_irq(shost->host_lock); + cmdiocbq = lpfc_sli_get_iocbq(phba); + if (!cmdiocbq) { + rc = ENOMEM; + spin_unlock_irq(shost->host_lock); + goto send_mgmt_cmd_free_pndl_exit; + } + cmd = &cmdiocbq->iocb; + + rspiocbq = lpfc_sli_get_iocbq(phba); + if (!rspiocbq) { + rc = ENOMEM; + goto send_mgmt_cmd_free_cmdiocbq; + } + spin_unlock_irq(shost->host_lock); + + rsp = &rspiocbq->iocb; + + bmp = kmalloc(sizeof (struct lpfc_dmabuf), GFP_KERNEL); + if (!bmp) { + rc = ENOMEM; + spin_lock_irq(shost->host_lock); + goto send_mgmt_cmd_free_rspiocbq; + } + + spin_lock_irq(shost->host_lock); + bmp->virt = lpfc_mbuf_alloc(phba, 0, &bmp->phys); + if (!bmp->virt) { + rc = ENOMEM; + goto send_mgmt_cmd_free_bmp; + } + spin_unlock_irq(shost->host_lock); + + INIT_LIST_HEAD(&bmp->list); + bpl = (struct ulp_bde64 *) bmp->virt; + indmp = dfc_cmd_data_alloc(phba, cip->lpfc_arg1, bpl, reqbfrcnt); + if (!indmp) { + rc = ENOMEM; + spin_lock_irq(shost->host_lock); + goto send_mgmt_cmd_free_bmpvirt; + } + + /* flag contains total number of BPLs for xmit */ + bpl += indmp->flag; + + outdmp = dfc_cmd_data_alloc(phba, NULL, bpl, snsbfrcnt); + if (!outdmp) { + rc = ENOMEM; + spin_lock_irq(shost->host_lock); + goto send_mgmt_cmd_free_indmp; + } + + cmd->un.genreq64.bdl.ulpIoTag32 = 0; + cmd->un.genreq64.bdl.addrHigh = putPaddrHigh(bmp->phys); + cmd->un.genreq64.bdl.addrLow = putPaddrLow(bmp->phys); + cmd->un.genreq64.bdl.bdeFlags = BUFF_TYPE_BLP_64; + cmd->un.genreq64.bdl.bdeSize = + (outdmp->flag + indmp->flag) * sizeof (struct ulp_bde64); + cmd->ulpCommand = CMD_GEN_REQUEST64_CR; + cmd->un.genreq64.w5.hcsw.Fctl = (SI | LA); + cmd->un.genreq64.w5.hcsw.Dfctl = 0; + cmd->un.genreq64.w5.hcsw.Rctl = FC_UNSOL_CTL; + cmd->un.genreq64.w5.hcsw.Type = FC_COMMON_TRANSPORT_ULP; + cmd->ulpBdeCount = 1; + cmd->ulpLe = 1; + cmd->ulpClass = CLASS3; + cmd->ulpContext = pndl->nlp_rpi; + cmd->ulpOwner = OWN_CHIP; + cmdiocbq->vport = phba->pport; + cmdiocbq->context1 = NULL; + cmdiocbq->context2 = NULL; + cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; + + if (cip->lpfc_flag == 0 ) + timeout = phba->fc_ratov * 2 ; + else + timeout = cip->lpfc_flag; + + cmd->ulpTimeout = timeout; + + rc = lpfc_sli_issue_iocb_wait(phba, pring, cmdiocbq, rspiocbq, + timeout + LPFC_DRVR_TIMEOUT); + + if (rc == IOCB_TIMEDOUT) { + lpfc_sli_release_iocbq(phba, rspiocbq); + iocb_ctxt = kmalloc(sizeof(struct lpfc_timedout_iocb_ctxt), + GFP_KERNEL); + if (!iocb_ctxt) { + rc = EACCES; + goto send_mgmt_cmd_free_pndl_exit; + } + + cmdiocbq->context1 = iocb_ctxt; + cmdiocbq->context2 = NULL; + iocb_ctxt->rspiocbq = NULL; + iocb_ctxt->mp = NULL; + iocb_ctxt->bmp = bmp; + iocb_ctxt->outdmp = outdmp; + iocb_ctxt->lpfc_cmd = NULL; + iocb_ctxt->indmp = indmp; + + cmdiocbq->iocb_cmpl = lpfc_ioctl_timeout_iocb_cmpl; + rc = EACCES; + goto send_mgmt_cmd_free_pndl_exit; + } + + if (rc != IOCB_SUCCESS) { + rc = EACCES; + goto send_mgmt_cmd_free_outdmp; + } + + if (rsp->ulpStatus) { + if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { + switch (rsp->un.ulpWord[4] & 0xff) { + case IOERR_SEQUENCE_TIMEOUT: + rc = ETIMEDOUT; + break; + case IOERR_INVALID_RPI: + rc = EFAULT; + break; + default: + rc = EACCES; + break; + } + goto send_mgmt_cmd_free_outdmp; + } + } else + outdmp->flag = rsp->un.genreq64.bdl.bdeSize; + + /* Copy back response data */ + if (outdmp->flag > snsbfrcnt) { + rc = ERANGE; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1209 C_CT Request error Data: x%x x%x\n", + outdmp->flag, BUF_SZ_4K); + goto send_mgmt_cmd_free_outdmp; + } + + /* copy back size of response, and response itself */ + memcpy(dataout, &outdmp->flag, sizeof (int)); + rc = dfc_rsp_data_copy (phba, cip->lpfc_arg2, outdmp, outdmp->flag); + if (rc) + rc = EIO; + +send_mgmt_cmd_free_outdmp: + spin_lock_irq(shost->host_lock); + dfc_cmd_data_free(phba, outdmp); +send_mgmt_cmd_free_indmp: + dfc_cmd_data_free(phba, indmp); +send_mgmt_cmd_free_bmpvirt: + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); +send_mgmt_cmd_free_bmp: + kfree(bmp); +send_mgmt_cmd_free_rspiocbq: + lpfc_sli_release_iocbq(phba, rspiocbq); +send_mgmt_cmd_free_cmdiocbq: + lpfc_sli_release_iocbq(phba, cmdiocbq); + spin_unlock_irq(shost->host_lock); +send_mgmt_cmd_free_pndl_exit: + lpfc_nlp_put(pndl); +send_mgmt_cmd_exit: + return rc; +} + +static inline struct lpfcdfc_event * +lpfcdfc_event_new(uint32_t ev_mask, + int ev_reg_id, + uint32_t ev_req_id) +{ + struct lpfcdfc_event * evt = kzalloc(sizeof(*evt), GFP_KERNEL); + if (evt == NULL) + return NULL; + + INIT_LIST_HEAD(&evt->events_to_get); + INIT_LIST_HEAD(&evt->events_to_see); + evt->type_mask = ev_mask; + evt->req_id = ev_req_id; + evt->reg_id = ev_reg_id; + evt->wait_time_stamp = jiffies; + init_waitqueue_head(&evt->wq); + + return evt; +} + +static inline void lpfcdfc_event_free(struct lpfcdfc_event * evt) +{ + struct event_data * ed; + + list_del(&evt->node); + + while(!list_empty(&evt->events_to_get)) { + ed = list_entry(evt->events_to_get.next, typeof(*ed), node); + list_del(&ed->node); + kfree(ed->data); + kfree(ed); + } + + while(!list_empty(&evt->events_to_see)) { + ed = list_entry(evt->events_to_see.next, typeof(*ed), node); + list_del(&ed->node); + kfree(ed->data); + kfree(ed); + } + + kfree(evt); +} + +#define lpfcdfc_event_ref(evt) evt->ref++ + +#define lpfcdfc_event_unref(evt) \ + if (--evt->ref < 0) \ + lpfcdfc_event_free(evt); + +static int +lpfc_ioctl_hba_get_event(struct lpfc_hba * phba, + struct lpfcCmdInput * cip, + void **dataout, int *data_size) +{ + uint32_t ev_mask = ((uint32_t)(unsigned long)cip->lpfc_arg3 & + FC_REG_EVENT_MASK); + int ev_reg_id = (uint32_t) cip->lpfc_flag; + uint32_t ev_req_id = 0; + struct lpfcdfc_host * dfchba; + struct lpfcdfc_event * evt; + struct event_data * evt_dat = NULL; + int ret_val = 0; + + /* All other events supported through NET_LINK_EVENTs */ + if (ev_mask != FC_REG_CT_EVENT) + return ENOENT; + + mutex_lock(&lpfcdfc_lock); + list_for_each_entry(dfchba, &lpfcdfc_hosts, node) + if (dfchba->phba == phba) + break; + mutex_unlock(&lpfcdfc_lock); + + BUG_ON(&dfchba->node == &lpfcdfc_hosts); + + if ((ev_mask == FC_REG_CT_EVENT) && + copy_from_user(&ev_req_id, (void __user *)cip->lpfc_arg2, + sizeof (uint32_t))) + return EIO; + + mutex_lock(&lpfcdfc_lock); + list_for_each_entry(evt, &dfchba->ev_waiters, node) + if (evt->reg_id == ev_reg_id) { + if(list_empty(&evt->events_to_get)) + break; + lpfcdfc_event_ref(evt); + evt->wait_time_stamp = jiffies; + evt_dat = list_entry(evt->events_to_get.prev, + struct event_data, node); + list_del(&evt_dat->node); + break; + } + mutex_unlock(&lpfcdfc_lock); + + if (evt_dat == NULL) + return ENOENT; + + BUG_ON((ev_mask & evt_dat->type) == 0); + + if (evt_dat->len > cip->lpfc_outsz) + evt_dat->len = cip->lpfc_outsz; + + if (copy_to_user((void __user *)cip->lpfc_arg2, &evt_dat->immed_dat, + sizeof (uint32_t)) || + copy_to_user((void __user *)cip->lpfc_arg1, &evt_dat->len, + sizeof (uint32_t))) { + ret_val = EIO; + goto error_get_event_exit; + } + + if (evt_dat->len > 0) { + *data_size = evt_dat->len; + *dataout = kmalloc(*data_size, GFP_KERNEL); + if (*dataout) + memcpy(*dataout, evt_dat->data, *data_size); + else + *data_size = 0; + + } else + *data_size = 0; + ret_val = 0; + +error_get_event_exit: + + kfree(evt_dat->data); + kfree(evt_dat); + mutex_lock(&lpfcdfc_lock); + lpfcdfc_event_unref(evt); + mutex_unlock(&lpfcdfc_lock); + + return ret_val; +} + +static int +lpfc_ioctl_hba_set_event(struct lpfc_hba * phba, + struct lpfcCmdInput * cip) +{ + uint32_t ev_mask = ((uint32_t)(unsigned long)cip->lpfc_arg3 & + FC_REG_EVENT_MASK); + int ev_reg_id = cip->lpfc_flag; + uint32_t ev_req_id = 0; + + struct lpfcdfc_host * dfchba; + struct lpfcdfc_event * evt; + + int ret_val = 0; + + /* All other events supported through NET_LINK_EVENTs */ + if (ev_mask != FC_REG_CT_EVENT) + return ENOENT; + + mutex_lock(&lpfcdfc_lock); + list_for_each_entry(dfchba, &lpfcdfc_hosts, node) { + if (dfchba->phba == phba) + break; + } + mutex_unlock(&lpfcdfc_lock); + BUG_ON(&dfchba->node == &lpfcdfc_hosts); + + if (ev_mask == FC_REG_CT_EVENT) + ev_req_id = ((uint32_t)(unsigned long)cip->lpfc_arg2); + + mutex_lock(&lpfcdfc_lock); + list_for_each_entry(evt, &dfchba->ev_waiters, node) { + if (evt->reg_id == ev_reg_id) { + lpfcdfc_event_ref(evt); + evt->wait_time_stamp = jiffies; + break; + } + } + mutex_unlock(&lpfcdfc_lock); + + if (&evt->node == &dfchba->ev_waiters) { + /* no event waiting struct yet - first call */ + evt = lpfcdfc_event_new(ev_mask, ev_reg_id, ev_req_id); + if (evt == NULL) + return ENOMEM; + + mutex_lock(&lpfcdfc_lock); + list_add(&evt->node, &dfchba->ev_waiters); + lpfcdfc_event_ref(evt); + mutex_unlock(&lpfcdfc_lock); + } + + evt->waiting = 1; + if (wait_event_interruptible(evt->wq, + (!list_empty(&evt->events_to_see) || + dfchba->blocked))) { + mutex_lock(&lpfcdfc_lock); + lpfcdfc_event_unref(evt); /* release ref */ + lpfcdfc_event_unref(evt); /* delete */ + mutex_unlock(&lpfcdfc_lock); + return EINTR; + } + + mutex_lock(&lpfcdfc_lock); + if (dfchba->blocked) { + lpfcdfc_event_unref(evt); + lpfcdfc_event_unref(evt); + mutex_unlock(&lpfcdfc_lock); + return ENODEV; + } + mutex_unlock(&lpfcdfc_lock); + + evt->wait_time_stamp = jiffies; + evt->waiting = 0; + + BUG_ON(list_empty(&evt->events_to_see)); + + mutex_lock(&lpfcdfc_lock); + list_move(evt->events_to_see.prev, &evt->events_to_get); + lpfcdfc_event_unref(evt); /* release ref */ + mutex_unlock(&lpfcdfc_lock); + + return ret_val; +} + +static int +lpfc_ioctl_loopback_mode(struct lpfc_hba *phba, + struct lpfcCmdInput *cip, void *dataout) +{ + struct Scsi_Host *shost; + struct lpfc_sli *psli = &phba->sli; + struct lpfc_sli_ring *pring = &psli->ring[LPFC_FCP_RING]; + uint32_t link_flags = cip->lpfc_arg4; + uint32_t timeout = cip->lpfc_arg5 * 100; + struct lpfc_vport **vports; + LPFC_MBOXQ_t *pmboxq; + int mbxstatus; + int i = 0; + int rc = 0; + + if ((phba->link_state == LPFC_HBA_ERROR) || + (psli->sli_flag & LPFC_BLOCK_MGMT_IO) || + (!(psli->sli_flag & LPFC_SLI2_ACTIVE))) + return EACCES; + + if ((pmboxq = mempool_alloc(phba->mbox_mem_pool,GFP_KERNEL)) == 0) + return ENOMEM; + + vports = lpfc_create_vport_work_array(phba); + if (vports != NULL) { + for(i = 0; i <= phba->max_vpi && vports[i] != NULL; i++){ + shost = lpfc_shost_from_vport(vports[i]); + scsi_block_requests(shost); + } + lpfc_destroy_vport_work_array(phba, vports); + } + else { + shost = lpfc_shost_from_vport(phba->pport); + scsi_block_requests(shost); + } + + while (pring->txcmplq_cnt) { + if (i++ > 500) /* wait up to 5 seconds */ + break; + + mdelay(10); + } + + memset((void *)pmboxq, 0, sizeof (LPFC_MBOXQ_t)); + pmboxq->mb.mbxCommand = MBX_DOWN_LINK; + pmboxq->mb.mbxOwner = OWN_HOST; + + mbxstatus = lpfc_sli_issue_mbox_wait(phba, pmboxq, LPFC_MBOX_TMO); + + if ((mbxstatus == MBX_SUCCESS) && (pmboxq->mb.mbxStatus == 0)) { + + /* wait for link down before proceeding */ + i = 0; + while (phba->link_state != LPFC_LINK_DOWN) { + if (i++ > timeout) { + rc = ETIMEDOUT; + goto loopback_mode_exit; + } + msleep(10); + } + + memset((void *)pmboxq, 0, sizeof (LPFC_MBOXQ_t)); + if (link_flags == INTERNAL_LOOP_BACK) + pmboxq->mb.un.varInitLnk.link_flags = FLAGS_LOCAL_LB; + else + pmboxq->mb.un.varInitLnk.link_flags = + FLAGS_TOPOLOGY_MODE_LOOP; + + pmboxq->mb.mbxCommand = MBX_INIT_LINK; + pmboxq->mb.mbxOwner = OWN_HOST; + + mbxstatus = lpfc_sli_issue_mbox_wait(phba, pmboxq, + LPFC_MBOX_TMO); + + if ((mbxstatus != MBX_SUCCESS) || (pmboxq->mb.mbxStatus)) + rc = ENODEV; + else { + phba->link_flag |= LS_LOOPBACK_MODE; + /* wait for the link attention interrupt */ + msleep(100); + + i = 0; + while (phba->link_state != LPFC_HBA_READY) { + if (i++ > timeout) { + rc = ETIMEDOUT; + break; + } + msleep(10); + } + } + } else + rc = ENODEV; + +loopback_mode_exit: + vports = lpfc_create_vport_work_array(phba); + if (vports != NULL) { + for(i = 0; i <= phba->max_vpi && vports[i] != NULL; i++){ + shost = lpfc_shost_from_vport(vports[i]); + scsi_unblock_requests(shost); + } + lpfc_destroy_vport_work_array(phba, vports); + } + else { + shost = lpfc_shost_from_vport(phba->pport); + scsi_unblock_requests(shost); + } + + /* + * Let SLI layer release mboxq if mbox command completed after timeout. + */ + if (mbxstatus != MBX_TIMEOUT) + mempool_free( pmboxq, phba->mbox_mem_pool); + + return rc; +} + +static int lpfcdfc_loop_self_reg(struct lpfc_hba *phba, uint16_t * rpi) +{ + LPFC_MBOXQ_t *mbox; + struct lpfc_dmabuf *dmabuff; + int status; + + mbox = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (mbox == NULL) + return ENOMEM; + + status = lpfc_reg_login(phba, 0, phba->pport->fc_myDID, + (uint8_t *)&phba->pport->fc_sparam, mbox, 0); + if (status) { + mempool_free(mbox, phba->mbox_mem_pool); + return ENOMEM; + } + + dmabuff = (struct lpfc_dmabuf *) mbox->context1; + mbox->context1 = NULL; + status = lpfc_sli_issue_mbox_wait(phba, mbox, LPFC_MBOX_TMO); + + if ((status != MBX_SUCCESS) || (mbox->mb.mbxStatus)) { + lpfc_mbuf_free(phba, dmabuff->virt, dmabuff->phys); + kfree(dmabuff); + if (status != MBX_TIMEOUT) + mempool_free(mbox, phba->mbox_mem_pool); + return ENODEV; + } + + *rpi = mbox->mb.un.varWords[0]; + + lpfc_mbuf_free(phba, dmabuff->virt, dmabuff->phys); + kfree(dmabuff); + mempool_free(mbox, phba->mbox_mem_pool); + + return 0; +} + +static int lpfcdfc_loop_self_unreg(struct lpfc_hba *phba, uint16_t rpi) +{ + LPFC_MBOXQ_t * mbox; + int status; + + /* Allocate mboxq structure */ + mbox = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (mbox == NULL) + return ENOMEM; + + lpfc_unreg_login(phba, 0, rpi, mbox); + status = lpfc_sli_issue_mbox_wait(phba, mbox, LPFC_MBOX_TMO); + + if ((status != MBX_SUCCESS) || (mbox->mb.mbxStatus)) { + if (status != MBX_TIMEOUT) + mempool_free(mbox, phba->mbox_mem_pool); + return EIO; + } + + mempool_free(mbox, phba->mbox_mem_pool); + return 0; +} + + +static int lpfcdfc_loop_get_xri(struct lpfc_hba *phba, uint16_t rpi, + uint16_t *txxri, uint16_t * rxxri) +{ + struct lpfc_sli *psli = &phba->sli; + struct lpfc_sli_ring *pring = &psli->ring[LPFC_ELS_RING]; + + struct lpfcdfc_host * dfchba; + struct lpfcdfc_event * evt; + + struct lpfc_iocbq *cmdiocbq, *rspiocbq; + IOCB_t *cmd, *rsp; + + struct lpfc_dmabuf * dmabuf; + struct ulp_bde64 *bpl = NULL; + struct lpfc_sli_ct_request *ctreq = NULL; + + int ret_val = 0; + + *txxri = 0; + *rxxri = 0; + + mutex_lock(&lpfcdfc_lock); + list_for_each_entry(dfchba, &lpfcdfc_hosts, node) { + if (dfchba->phba == phba) + break; + } + mutex_unlock(&lpfcdfc_lock); + BUG_ON(&dfchba->node == &lpfcdfc_hosts); + + evt = lpfcdfc_event_new(FC_REG_CT_EVENT, current->pid, + SLI_CT_ELX_LOOPBACK); + if (evt == NULL) + return ENOMEM; + + mutex_lock(&lpfcdfc_lock); + list_add(&evt->node, &dfchba->ev_waiters); + lpfcdfc_event_ref(evt); + mutex_unlock(&lpfcdfc_lock); + + cmdiocbq = lpfc_sli_get_iocbq(phba); + rspiocbq = lpfc_sli_get_iocbq(phba); + + dmabuf = kmalloc(sizeof (struct lpfc_dmabuf), GFP_KERNEL); + if (dmabuf) { + dmabuf->virt = lpfc_mbuf_alloc(phba, 0, &dmabuf->phys); + INIT_LIST_HEAD(&dmabuf->list); + bpl = (struct ulp_bde64 *) dmabuf->virt; + memset(bpl, 0, sizeof(*bpl)); + ctreq = (struct lpfc_sli_ct_request *)(bpl + 1); + bpl->addrHigh = + le32_to_cpu(putPaddrHigh(dmabuf->phys + sizeof(*bpl))); + bpl->addrLow = + le32_to_cpu(putPaddrLow(dmabuf->phys + sizeof(*bpl))); + bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; + bpl->tus.f.bdeSize = ELX_LOOPBACK_HEADER_SZ; + bpl->tus.w = le32_to_cpu(bpl->tus.w); + } + + if (cmdiocbq == NULL || rspiocbq == NULL || + dmabuf == NULL || bpl == NULL || ctreq == NULL) { + ret_val = ENOMEM; + goto err_get_xri_exit; + } + + cmd = &cmdiocbq->iocb; + rsp = &rspiocbq->iocb; + + memset(ctreq, 0, ELX_LOOPBACK_HEADER_SZ); + + ctreq->RevisionId.bits.Revision = SLI_CT_REVISION; + ctreq->RevisionId.bits.InId = 0; + ctreq->FsType = SLI_CT_ELX_LOOPBACK; + ctreq->FsSubType = 0; + ctreq->CommandResponse.bits.CmdRsp = ELX_LOOPBACK_XRI_SETUP; + ctreq->CommandResponse.bits.Size = 0; + + + cmd->un.xseq64.bdl.addrHigh = putPaddrHigh(dmabuf->phys); + cmd->un.xseq64.bdl.addrLow = putPaddrLow(dmabuf->phys); + cmd->un.xseq64.bdl.bdeFlags = BUFF_TYPE_BLP_64; + cmd->un.xseq64.bdl.bdeSize = sizeof(*bpl); + + cmd->un.xseq64.w5.hcsw.Fctl = LA; + cmd->un.xseq64.w5.hcsw.Dfctl = 0; + cmd->un.xseq64.w5.hcsw.Rctl = FC_UNSOL_CTL; + cmd->un.xseq64.w5.hcsw.Type = FC_COMMON_TRANSPORT_ULP; + + cmd->ulpCommand = CMD_XMIT_SEQUENCE64_CR; + cmd->ulpBdeCount = 1; + cmd->ulpLe = 1; + cmd->ulpClass = CLASS3; + cmd->ulpContext = rpi; + + cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; + cmdiocbq->vport = phba->pport; + + ret_val = lpfc_sli_issue_iocb_wait(phba, pring, cmdiocbq, rspiocbq, + (phba->fc_ratov * 2) + + LPFC_DRVR_TIMEOUT); + if (ret_val) + goto err_get_xri_exit; + + *txxri = rsp->ulpContext; + + evt->waiting = 1; + evt->wait_time_stamp = jiffies; + ret_val = wait_event_interruptible_timeout( + evt->wq, !list_empty(&evt->events_to_see), + ((phba->fc_ratov * 2) + LPFC_DRVR_TIMEOUT) * HZ); + if (list_empty(&evt->events_to_see)) + ret_val = (ret_val) ? EINTR : ETIMEDOUT; + else { + ret_val = IOCB_SUCCESS; + mutex_lock(&lpfcdfc_lock); + list_move(evt->events_to_see.prev, &evt->events_to_get); + mutex_unlock(&lpfcdfc_lock); + *rxxri = (list_entry(evt->events_to_get.prev, + typeof(struct event_data), + node))->immed_dat; + } + evt->waiting = 0; + +err_get_xri_exit: + mutex_lock(&lpfcdfc_lock); + lpfcdfc_event_unref(evt); /* release ref */ + lpfcdfc_event_unref(evt); /* delete */ + mutex_unlock(&lpfcdfc_lock); + + if(dmabuf) { + if(dmabuf->virt) + lpfc_mbuf_free(phba, dmabuf->virt, dmabuf->phys); + kfree(dmabuf); + } + + if (cmdiocbq && (ret_val != IOCB_TIMEDOUT)) + lpfc_sli_release_iocbq(phba, cmdiocbq); + if (rspiocbq) + lpfc_sli_release_iocbq(phba, rspiocbq); + + return ret_val; +} + +static int lpfcdfc_loop_post_rxbufs(struct lpfc_hba *phba, uint16_t rxxri, + size_t len) +{ + struct lpfc_sli *psli = &phba->sli; + struct lpfc_sli_ring *pring = &psli->ring[LPFC_ELS_RING]; + struct lpfc_iocbq *cmdiocbq; + IOCB_t *cmd = NULL; + struct list_head head, *curr, *next; + struct lpfc_dmabuf *rxbmp; + struct lpfc_dmabuf *dmp; + struct lpfc_dmabuf *mp[2] = {NULL, NULL}; + struct ulp_bde64 *rxbpl = NULL; + uint32_t num_bde; + struct lpfc_dmabufext *rxbuffer = NULL; + int ret_val = 0; + int i = 0; + + cmdiocbq = lpfc_sli_get_iocbq(phba); + rxbmp = kmalloc(sizeof (struct lpfc_dmabuf), GFP_KERNEL); + if (rxbmp != NULL) { + rxbmp->virt = lpfc_mbuf_alloc(phba, 0, &rxbmp->phys); + INIT_LIST_HEAD(&rxbmp->list); + rxbpl = (struct ulp_bde64 *) rxbmp->virt; + rxbuffer = dfc_cmd_data_alloc(phba, NULL, rxbpl, len); + } + + if(cmdiocbq == NULL || rxbmp == NULL || + rxbpl == NULL || rxbuffer == NULL) { + ret_val = ENOMEM; + goto err_post_rxbufs_exit; + } + + /* Queue buffers for the receive exchange */ + num_bde = (uint32_t)rxbuffer->flag; + dmp = &rxbuffer->dma; + + cmd = &cmdiocbq->iocb; + i = 0; + + INIT_LIST_HEAD(&head); + list_add_tail(&head, &dmp->list); + list_for_each_safe(curr, next, &head) { + mp[i] = list_entry(curr, struct lpfc_dmabuf, list); + list_del(curr); + + if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) { + mp[i]->buffer_tag = lpfc_sli_get_buffer_tag(phba); + cmd->un.quexri64cx.buff.bde.addrHigh = + putPaddrHigh(mp[i]->phys); + cmd->un.quexri64cx.buff.bde.addrLow = + putPaddrLow(mp[i]->phys); + cmd->un.quexri64cx.buff.bde.tus.f.bdeSize = + ((struct lpfc_dmabufext *)mp[i])->size; + cmd->un.quexri64cx.buff.buffer_tag = mp[i]->buffer_tag; + cmd->ulpCommand = CMD_QUE_XRI64_CX; + cmd->ulpPU = 0; + cmd->ulpLe = 1; + cmd->ulpBdeCount = 1; + cmd->unsli3.que_xri64cx_ext_words.ebde_count = 0; + + } else { + cmd->un.cont64[i].addrHigh = putPaddrHigh(mp[i]->phys); + cmd->un.cont64[i].addrLow = putPaddrLow(mp[i]->phys); + cmd->un.cont64[i].tus.f.bdeSize = + ((struct lpfc_dmabufext *)mp[i])->size; + cmd->ulpBdeCount = ++i; + + if ((--num_bde > 0) && (i < 2)) + continue; + + cmd->ulpCommand = CMD_QUE_XRI_BUF64_CX; + cmd->ulpLe = 1; + } + + cmd->ulpClass = CLASS3; + cmd->ulpContext = rxxri; + + ret_val = lpfc_sli_issue_iocb(phba, pring, cmdiocbq, 0); + + if (ret_val == IOCB_ERROR) { + dfc_cmd_data_free(phba, (struct lpfc_dmabufext *)mp[0]); + if (mp[1]) + dfc_cmd_data_free(phba, + (struct lpfc_dmabufext *)mp[1]); + dmp = list_entry(next, struct lpfc_dmabuf, list); + ret_val = EIO; + goto err_post_rxbufs_exit; + } + + lpfc_sli_ringpostbuf_put(phba, pring, mp[0]); + if (mp[1]) { + lpfc_sli_ringpostbuf_put(phba, pring, mp[1]); + mp[1] = NULL; + } + + /* The iocb was freed by lpfc_sli_issue_iocb */ + if ((cmdiocbq = lpfc_sli_get_iocbq(phba)) == NULL) { + dmp = list_entry(next, struct lpfc_dmabuf, list); + ret_val = EIO; + goto err_post_rxbufs_exit; + } + cmd = &cmdiocbq->iocb; + i = 0; + } + list_del(&head); + +err_post_rxbufs_exit: + + if(rxbmp) { + if(rxbmp->virt) + lpfc_mbuf_free(phba, rxbmp->virt, rxbmp->phys); + kfree(rxbmp); + } + + if (cmdiocbq) + lpfc_sli_release_iocbq(phba, cmdiocbq); + + return ret_val; +} +static int +lpfc_ioctl_loopback_test(struct lpfc_hba *phba, + struct lpfcCmdInput *cip, void *dataout) +{ + struct lpfcdfc_host * dfchba; + struct lpfcdfc_event * evt; + struct event_data * evdat; + + struct lpfc_sli *psli = &phba->sli; + struct lpfc_sli_ring *pring = &psli->ring[LPFC_ELS_RING]; + uint32_t size = cip->lpfc_outsz; + uint32_t full_size = size + ELX_LOOPBACK_HEADER_SZ; + size_t segment_len = 0, segment_offset = 0, current_offset = 0; + uint16_t rpi; + struct lpfc_iocbq *cmdiocbq, *rspiocbq; + IOCB_t *cmd, *rsp; + struct lpfc_sli_ct_request *ctreq; + struct lpfc_dmabuf *txbmp; + struct ulp_bde64 *txbpl = NULL; + struct lpfc_dmabufext *txbuffer = NULL; + struct list_head head; + struct lpfc_dmabuf *curr; + uint16_t txxri, rxxri; + uint32_t num_bde; + uint8_t *ptr = NULL, *rx_databuf = NULL; + int rc; + + if ((phba->link_state == LPFC_HBA_ERROR) || + (psli->sli_flag & LPFC_BLOCK_MGMT_IO) || + (!(psli->sli_flag & LPFC_SLI2_ACTIVE))) + return EACCES; + + if (!lpfc_is_link_up(phba) || !(phba->link_flag & LS_LOOPBACK_MODE)) + return EACCES; + + if ((size == 0) || (size > 80 * BUF_SZ_4K)) + return ERANGE; + + mutex_lock(&lpfcdfc_lock); + list_for_each_entry(dfchba, &lpfcdfc_hosts, node) { + if (dfchba->phba == phba) + break; + } + mutex_unlock(&lpfcdfc_lock); + BUG_ON(&dfchba->node == &lpfcdfc_hosts); + + rc = lpfcdfc_loop_self_reg(phba, &rpi); + if (rc) + return rc; + + rc = lpfcdfc_loop_get_xri(phba, rpi, &txxri, &rxxri); + if (rc) { + lpfcdfc_loop_self_unreg(phba, rpi); + return rc; + } + + rc = lpfcdfc_loop_post_rxbufs(phba, rxxri, full_size); + if (rc) { + lpfcdfc_loop_self_unreg(phba, rpi); + return rc; + } + + evt = lpfcdfc_event_new(FC_REG_CT_EVENT, current->pid, + SLI_CT_ELX_LOOPBACK); + if (evt == NULL) { + lpfcdfc_loop_self_unreg(phba, rpi); + return ENOMEM; + } + + mutex_lock(&lpfcdfc_lock); + list_add(&evt->node, &dfchba->ev_waiters); + lpfcdfc_event_ref(evt); + mutex_unlock(&lpfcdfc_lock); + + cmdiocbq = lpfc_sli_get_iocbq(phba); + rspiocbq = lpfc_sli_get_iocbq(phba); + txbmp = kmalloc(sizeof (struct lpfc_dmabuf), GFP_KERNEL); + + if (txbmp) { + txbmp->virt = lpfc_mbuf_alloc(phba, 0, &txbmp->phys); + INIT_LIST_HEAD(&txbmp->list); + txbpl = (struct ulp_bde64 *) txbmp->virt; + if (txbpl) + txbuffer = dfc_cmd_data_alloc(phba, NULL, + txbpl, full_size); + } + + if (cmdiocbq == NULL || rspiocbq == NULL + || txbmp == NULL || txbpl == NULL || txbuffer == NULL) { + rc = ENOMEM; + goto err_loopback_test_exit; + } + + cmd = &cmdiocbq->iocb; + rsp = &rspiocbq->iocb; + + INIT_LIST_HEAD(&head); + list_add_tail(&head, &txbuffer->dma.list); + list_for_each_entry(curr, &head, list) { + segment_len = ((struct lpfc_dmabufext *)curr)->size; + if (current_offset == 0) { + ctreq = curr->virt; + memset(ctreq, 0, ELX_LOOPBACK_HEADER_SZ); + ctreq->RevisionId.bits.Revision = SLI_CT_REVISION; + ctreq->RevisionId.bits.InId = 0; + ctreq->FsType = SLI_CT_ELX_LOOPBACK; + ctreq->FsSubType = 0; + ctreq->CommandResponse.bits.CmdRsp = ELX_LOOPBACK_DATA ; + ctreq->CommandResponse.bits.Size = size; + segment_offset = ELX_LOOPBACK_HEADER_SZ; + } else + segment_offset = 0; + + BUG_ON(segment_offset >= segment_len); + if (copy_from_user (curr->virt + segment_offset, + (void __user *)cip->lpfc_arg1 + + current_offset, + segment_len - segment_offset)) { + rc = EIO; + list_del(&head); + goto err_loopback_test_exit; + } + + current_offset += segment_len - segment_offset; + BUG_ON(current_offset > size); + } + list_del(&head); + + /* Build the XMIT_SEQUENCE iocb */ + + num_bde = (uint32_t)txbuffer->flag; + + cmd->un.xseq64.bdl.addrHigh = putPaddrHigh(txbmp->phys); + cmd->un.xseq64.bdl.addrLow = putPaddrLow(txbmp->phys); + cmd->un.xseq64.bdl.bdeFlags = BUFF_TYPE_BLP_64; + cmd->un.xseq64.bdl.bdeSize = (num_bde * sizeof(struct ulp_bde64)); + + cmd->un.xseq64.w5.hcsw.Fctl = (LS | LA); + cmd->un.xseq64.w5.hcsw.Dfctl = 0; + cmd->un.xseq64.w5.hcsw.Rctl = FC_UNSOL_CTL; + cmd->un.xseq64.w5.hcsw.Type = FC_COMMON_TRANSPORT_ULP; + + cmd->ulpCommand = CMD_XMIT_SEQUENCE64_CX; + cmd->ulpBdeCount = 1; + cmd->ulpLe = 1; + cmd->ulpClass = CLASS3; + cmd->ulpContext = txxri; + + cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; + cmdiocbq->vport = phba->pport; + + rc = lpfc_sli_issue_iocb_wait(phba, pring, cmdiocbq, rspiocbq, + (phba->fc_ratov * 2) + LPFC_DRVR_TIMEOUT); + + if ((rc != IOCB_SUCCESS) || (rsp->ulpStatus != IOCB_SUCCESS)) { + rc = EIO; + goto err_loopback_test_exit; + } + + evt->waiting = 1; + rc = wait_event_interruptible_timeout( + evt->wq, !list_empty(&evt->events_to_see), + ((phba->fc_ratov * 2) + LPFC_DRVR_TIMEOUT) * HZ); + evt->waiting = 0; + if (list_empty(&evt->events_to_see)) + rc = (rc) ? EINTR : ETIMEDOUT; + else { + ptr = dataout; + mutex_lock(&lpfcdfc_lock); + list_move(evt->events_to_see.prev, &evt->events_to_get); + evdat = list_entry(evt->events_to_get.prev, + typeof(*evdat), node); + mutex_unlock(&lpfcdfc_lock); + rx_databuf = evdat->data; + if (evdat->len != full_size) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1603 Loopback test did not receive expected " + "data length. actual length 0x%x expected " + "length 0x%x\n", + evdat->len, full_size); + rc = EIO; + } + else if (rx_databuf == NULL) + rc = EIO; + else { + rx_databuf += ELX_LOOPBACK_HEADER_SZ; + memcpy(ptr, rx_databuf, size); + rc = IOCB_SUCCESS; + } + } + +err_loopback_test_exit: + lpfcdfc_loop_self_unreg(phba, rpi); + + mutex_lock(&lpfcdfc_lock); + lpfcdfc_event_unref(evt); /* release ref */ + lpfcdfc_event_unref(evt); /* delete */ + mutex_unlock(&lpfcdfc_lock); + + if (cmdiocbq != NULL) + lpfc_sli_release_iocbq(phba, cmdiocbq); + + if (rspiocbq != NULL) + lpfc_sli_release_iocbq(phba, rspiocbq); + + if (txbmp != NULL) { + if (txbpl != NULL) { + if (txbuffer != NULL) + dfc_cmd_data_free(phba, txbuffer); + lpfc_mbuf_free(phba, txbmp->virt, txbmp->phys); + } + kfree(txbmp); + } + return rc; +} + +static int +dfc_rsp_data_copy(struct lpfc_hba * phba, + uint8_t * outdataptr, struct lpfc_dmabufext * mlist, + uint32_t size) +{ + struct lpfc_dmabufext *mlast = NULL; + int cnt, offset = 0; + struct list_head head, *curr, *next; + + if (!mlist) + return 0; + + list_add_tail(&head, &mlist->dma.list); + + list_for_each_safe(curr, next, &head) { + mlast = list_entry(curr, struct lpfc_dmabufext , dma.list); + if (!size) + break; + + /* We copy chucks of 4K */ + if (size > BUF_SZ_4K) + cnt = BUF_SZ_4K; + else + cnt = size; + + if (outdataptr) { + pci_dma_sync_single_for_device(phba->pcidev, + mlast->dma.phys, LPFC_BPL_SIZE, PCI_DMA_TODEVICE); + + /* Copy data to user space */ + if (copy_to_user + ((void __user *) (outdataptr + offset), + (uint8_t *) mlast->dma.virt, cnt)) + return 1; + } + offset += cnt; + size -= cnt; + } + list_del(&head); + return 0; +} + +static int +lpfc_issue_ct_rsp(struct lpfc_hba * phba, uint32_t tag, + struct lpfc_dmabuf * bmp, + struct lpfc_dmabufext * inp) +{ + struct lpfc_sli *psli; + IOCB_t *icmd; + struct lpfc_iocbq *ctiocb; + struct lpfc_sli_ring *pring; + uint32_t num_entry; + int rc = 0; + + psli = &phba->sli; + pring = &psli->ring[LPFC_ELS_RING]; + num_entry = inp->flag; + inp->flag = 0; + + /* Allocate buffer for command iocb */ + ctiocb = lpfc_sli_get_iocbq(phba); + if (!ctiocb) { + rc = ENOMEM; + goto issue_ct_rsp_exit; + } + icmd = &ctiocb->iocb; + + icmd->un.xseq64.bdl.ulpIoTag32 = 0; + icmd->un.xseq64.bdl.addrHigh = putPaddrHigh(bmp->phys); + icmd->un.xseq64.bdl.addrLow = putPaddrLow(bmp->phys); + icmd->un.xseq64.bdl.bdeFlags = BUFF_TYPE_BLP_64; + icmd->un.xseq64.bdl.bdeSize = (num_entry * sizeof (struct ulp_bde64)); + icmd->un.xseq64.w5.hcsw.Fctl = (LS | LA); + icmd->un.xseq64.w5.hcsw.Dfctl = 0; + icmd->un.xseq64.w5.hcsw.Rctl = FC_SOL_CTL; + icmd->un.xseq64.w5.hcsw.Type = FC_COMMON_TRANSPORT_ULP; + + pci_dma_sync_single_for_device(phba->pcidev, bmp->phys, LPFC_BPL_SIZE, + PCI_DMA_TODEVICE); + + /* Fill in rest of iocb */ + icmd->ulpCommand = CMD_XMIT_SEQUENCE64_CX; + icmd->ulpBdeCount = 1; + icmd->ulpLe = 1; + icmd->ulpClass = CLASS3; + icmd->ulpContext = (ushort) tag; + icmd->ulpTimeout = phba->fc_ratov * 2; + + /* Xmit CT response on exchange */ + lpfc_printf_log(phba, KERN_INFO, LOG_ELS, + "1200 Xmit CT response on exchange x%x Data: x%x x%x\n", + icmd->ulpContext, icmd->ulpIoTag, phba->link_state); + + ctiocb->iocb_cmpl = NULL; + ctiocb->iocb_flag |= LPFC_IO_LIBDFC; + ctiocb->vport = phba->pport; + rc = lpfc_sli_issue_iocb_wait(phba, pring, ctiocb, NULL, + phba->fc_ratov * 2 + LPFC_DRVR_TIMEOUT); + + if (rc == IOCB_TIMEDOUT) { + ctiocb->context1 = NULL; + ctiocb->context2 = NULL; + ctiocb->iocb_cmpl = lpfc_ioctl_timeout_iocb_cmpl; + return rc; + } + + /* Calling routine takes care of IOCB_ERROR => EIO translation */ + if (rc != IOCB_SUCCESS) + rc = IOCB_ERROR; + + lpfc_sli_release_iocbq(phba, ctiocb); +issue_ct_rsp_exit: + return rc; +} + + +static void +lpfcdfc_ct_unsol_event(struct lpfc_hba * phba, + struct lpfc_sli_ring * pring, + struct lpfc_iocbq * piocbq) +{ + struct lpfcdfc_host * dfchba = lpfcdfc_host_from_hba(phba); + uint32_t evt_req_id = 0; + uint32_t cmd; + uint32_t len; + struct lpfc_dmabuf *dmabuf = NULL; + struct lpfcdfc_event * evt; + struct event_data * evt_dat = NULL; + struct lpfc_iocbq * iocbq; + size_t offset = 0; + struct list_head head; + struct ulp_bde64 * bde; + dma_addr_t dma_addr; + int i; + struct lpfc_dmabuf *bdeBuf1 = piocbq->context2; + struct lpfc_dmabuf *bdeBuf2 = piocbq->context3; + struct lpfc_hbq_entry *hbqe; + + BUG_ON(&dfchba->node == &lpfcdfc_hosts); + INIT_LIST_HEAD(&head); + if (piocbq->iocb.ulpBdeCount == 0 || + piocbq->iocb.un.cont64[0].tus.f.bdeSize == 0) + goto error_unsol_ct_exit; + + if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) + dmabuf = bdeBuf1; + else { + dma_addr = getPaddr(piocbq->iocb.un.cont64[0].addrHigh, + piocbq->iocb.un.cont64[0].addrLow); + dmabuf = lpfc_sli_ringpostbuf_get(phba, pring, dma_addr); + } + BUG_ON(dmabuf == NULL); + evt_req_id = ((struct lpfc_sli_ct_request *)(dmabuf->virt))->FsType; + cmd = ((struct lpfc_sli_ct_request *) + (dmabuf->virt))->CommandResponse.bits.CmdRsp; + len = ((struct lpfc_sli_ct_request *) + (dmabuf->virt))->CommandResponse.bits.Size; + if (!(phba->sli3_options & LPFC_SLI3_HBQ_ENABLED)) + lpfc_sli_ringpostbuf_put(phba, pring, dmabuf); + + mutex_lock(&lpfcdfc_lock); + list_for_each_entry(evt, &dfchba->ev_waiters, node) { + if (!(evt->type_mask & FC_REG_CT_EVENT) || + evt->req_id != evt_req_id) + continue; + + lpfcdfc_event_ref(evt); + + if ((evt_dat = kzalloc(sizeof(*evt_dat), GFP_KERNEL)) == NULL) { + lpfcdfc_event_unref(evt); + break; + } + + mutex_unlock(&lpfcdfc_lock); + + INIT_LIST_HEAD(&head); + list_add_tail(&head, &piocbq->list); + if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) { + /* take accumulated byte count from the last iocbq */ + iocbq = list_entry(head.prev, typeof(*iocbq), list); + evt_dat->len = iocbq->iocb.unsli3.rcvsli3.acc_len; + } else { + list_for_each_entry(iocbq, &head, list) { + for (i = 0; i < iocbq->iocb.ulpBdeCount; i++) + evt_dat->len += + iocbq->iocb.un.cont64[i].tus.f.bdeSize; + } + } + + + evt_dat->data = kzalloc(evt_dat->len, GFP_KERNEL); + if (evt_dat->data == NULL) { + kfree (evt_dat); + mutex_lock(&lpfcdfc_lock); + lpfcdfc_event_unref(evt); + mutex_unlock(&lpfcdfc_lock); + goto error_unsol_ct_exit; + } + + list_for_each_entry(iocbq, &head, list) { + if (phba->sli3_options & LPFC_SLI3_HBQ_ENABLED) { + bdeBuf1 = iocbq->context2; + bdeBuf2 = iocbq->context3; + } + for (i = 0; i < iocbq->iocb.ulpBdeCount; i++) { + int size = 0; + if (phba->sli3_options & + LPFC_SLI3_HBQ_ENABLED) { + BUG_ON(i>1); + if (i == 0) { + hbqe = (struct lpfc_hbq_entry *) + &iocbq->iocb.un.ulpWord[0]; + size = hbqe->bde.tus.f.bdeSize; + dmabuf = bdeBuf1; + } else if (i == 1) { + hbqe = (struct lpfc_hbq_entry *) + &iocbq->iocb.unsli3. + sli3Words[4]; + size = hbqe->bde.tus.f.bdeSize; + dmabuf = bdeBuf2; + } + if ((offset + size) > evt_dat->len) + size = evt_dat->len - offset; + } else { + size = iocbq->iocb.un.cont64[i]. + tus.f.bdeSize; + bde = &iocbq->iocb.un.cont64[i]; + dma_addr = getPaddr(bde->addrHigh, + bde->addrLow); + dmabuf = lpfc_sli_ringpostbuf_get(phba, + pring, dma_addr); + } + if (dmabuf == NULL) { + kfree (evt_dat->data); + kfree (evt_dat); + mutex_lock(&lpfcdfc_lock); + lpfcdfc_event_unref(evt); + mutex_unlock(&lpfcdfc_lock); + goto error_unsol_ct_exit; + } + memcpy ((char *)(evt_dat->data) + offset, + dmabuf->virt, size); + offset += size; + if (evt_req_id != SLI_CT_ELX_LOOPBACK && + !(phba->sli3_options & + LPFC_SLI3_HBQ_ENABLED)) + lpfc_sli_ringpostbuf_put(phba, pring, + dmabuf); + else { + switch (cmd) { + case ELX_LOOPBACK_DATA: + dfc_cmd_data_free(phba, + (struct lpfc_dmabufext *) + dmabuf); + break; + case ELX_LOOPBACK_XRI_SETUP: + if (!(phba->sli3_options & + LPFC_SLI3_HBQ_ENABLED)) + lpfc_post_buffer(phba, + pring, + 1); + else + lpfc_in_buf_free(phba, + dmabuf); + break; + default: + if (!(phba->sli3_options & + LPFC_SLI3_HBQ_ENABLED)) + lpfc_post_buffer(phba, + pring, + 1); + break; + } + } + } + } + + mutex_lock(&lpfcdfc_lock); + evt_dat->immed_dat = piocbq->iocb.ulpContext; + evt_dat->type = FC_REG_CT_EVENT; + list_add(&evt_dat->node, &evt->events_to_see); + wake_up_interruptible(&evt->wq); + lpfcdfc_event_unref(evt); + if (evt_req_id == SLI_CT_ELX_LOOPBACK) + break; + } + mutex_unlock(&lpfcdfc_lock); + +error_unsol_ct_exit: + if(!list_empty(&head)) + list_del(&head); + if (evt_req_id != SLI_CT_ELX_LOOPBACK && + dfchba->base_ct_unsol_event != NULL) + (dfchba->base_ct_unsol_event)(phba, pring, piocbq); + + return; +} + + +struct lpfc_dmabufext * +__dfc_cmd_data_alloc(struct lpfc_hba * phba, + char *indataptr, struct ulp_bde64 * bpl, uint32_t size, + int nocopydata) +{ + struct lpfc_dmabufext *mlist = NULL; + struct lpfc_dmabufext *dmp; + int cnt, offset = 0, i = 0; + struct pci_dev *pcidev; + + pcidev = phba->pcidev; + + while (size) { + /* We get chunks of 4K */ + if (size > BUF_SZ_4K) + cnt = BUF_SZ_4K; + else + cnt = size; + + /* allocate struct lpfc_dmabufext buffer header */ + dmp = kmalloc(sizeof (struct lpfc_dmabufext), GFP_KERNEL); + if (dmp == 0) + goto out; + + INIT_LIST_HEAD(&dmp->dma.list); + + /* Queue it to a linked list */ + if (mlist) + list_add_tail(&dmp->dma.list, &mlist->dma.list); + else + mlist = dmp; + + /* allocate buffer */ + dmp->dma.virt = dma_alloc_coherent(&pcidev->dev, + cnt, + &(dmp->dma.phys), + GFP_KERNEL); + + if (dmp->dma.virt == NULL) + goto out; + + dmp->size = cnt; + + if (indataptr || nocopydata) { + if (indataptr) + /* Copy data from user space in */ + if (copy_from_user ((uint8_t *) dmp->dma.virt, + (void __user *) (indataptr + offset), + cnt)) { + goto out; + } + + pci_dma_sync_single_for_device(phba->pcidev, + dmp->dma.phys, LPFC_BPL_SIZE, PCI_DMA_TODEVICE); + + } else + memset((uint8_t *)dmp->dma.virt, 0, cnt); + bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; + + /* build buffer ptr list for IOCB */ + bpl->addrLow = le32_to_cpu(putPaddrLow(dmp->dma.phys)); + bpl->addrHigh = le32_to_cpu(putPaddrHigh(dmp->dma.phys)); + bpl->tus.f.bdeSize = (ushort) cnt; + bpl->tus.w = le32_to_cpu(bpl->tus.w); + bpl++; + + i++; + offset += cnt; + size -= cnt; + } + + mlist->flag = i; + return mlist; +out: + dfc_cmd_data_free(phba, mlist); + return NULL; +} + +static struct lpfc_dmabufext * +dfc_cmd_data_alloc(struct lpfc_hba * phba, + char *indataptr, struct ulp_bde64 * bpl, uint32_t size) +{ + /* if indataptr is null it is a rsp buffer. */ + return __dfc_cmd_data_alloc(phba, indataptr, bpl, size, + 0 /* don't copy user data */); +} + +int +__dfc_cmd_data_free(struct lpfc_hba * phba, struct lpfc_dmabufext * mlist) +{ + return dfc_cmd_data_free(phba, mlist); +} +static int +dfc_cmd_data_free(struct lpfc_hba * phba, struct lpfc_dmabufext * mlist) +{ + struct lpfc_dmabufext *mlast; + struct pci_dev *pcidev; + struct list_head head, *curr, *next; + + if ((!mlist) || (!lpfc_is_link_up(phba) && + (phba->link_flag & LS_LOOPBACK_MODE))) { + return 0; + } + + pcidev = phba->pcidev; + list_add_tail(&head, &mlist->dma.list); + + list_for_each_safe(curr, next, &head) { + mlast = list_entry(curr, struct lpfc_dmabufext , dma.list); + if (mlast->dma.virt) + dma_free_coherent(&pcidev->dev, + mlast->size, + mlast->dma.virt, + mlast->dma.phys); + kfree(mlast); + } + return 0; +} + + +/* The only reason we need that reverce find, is because we + * are bent on keeping original calling conventions. + */ +static struct lpfcdfc_host * +lpfcdfc_host_from_hba(struct lpfc_hba * phba) +{ + struct lpfcdfc_host * dfchba; + + mutex_lock(&lpfcdfc_lock); + list_for_each_entry(dfchba, &lpfcdfc_hosts, node) { + if (dfchba->phba == phba) + break; + } + mutex_unlock(&lpfcdfc_lock); + + return dfchba; +} + +struct lpfcdfc_host * +lpfcdfc_host_add (struct pci_dev * dev, + struct Scsi_Host * host, + struct lpfc_hba * phba) +{ + struct lpfcdfc_host * dfchba = NULL; + struct lpfc_sli_ring_mask * prt = NULL; + + dfchba = kzalloc(sizeof(*dfchba), GFP_KERNEL); + if (dfchba == NULL) + return NULL; + + dfchba->inst = phba->brd_no; + dfchba->phba = phba; + dfchba->vport = phba->pport; + dfchba->host = host; + dfchba->dev = dev; + dfchba->blocked = 0; + + spin_lock_irq(&phba->hbalock); + prt = phba->sli.ring[LPFC_ELS_RING].prt; + dfchba->base_ct_unsol_event = prt[2].lpfc_sli_rcv_unsol_event; + prt[2].lpfc_sli_rcv_unsol_event = lpfcdfc_ct_unsol_event; + prt[3].lpfc_sli_rcv_unsol_event = lpfcdfc_ct_unsol_event; + spin_unlock_irq(&phba->hbalock); + mutex_lock(&lpfcdfc_lock); + list_add_tail(&dfchba->node, &lpfcdfc_hosts); + INIT_LIST_HEAD(&dfchba->ev_waiters); + mutex_unlock(&lpfcdfc_lock); + + return dfchba; +} + + +void +lpfcdfc_host_del (struct lpfcdfc_host * dfchba) +{ + struct Scsi_Host * host; + struct lpfc_hba * phba = NULL; + struct lpfc_sli_ring_mask * prt = NULL; + struct lpfcdfc_event * evt; + + mutex_lock(&lpfcdfc_lock); + dfchba->blocked = 1; + + list_for_each_entry(evt, &dfchba->ev_waiters, node) { + wake_up_interruptible(&evt->wq); + } + + while (dfchba->ref_count) { + mutex_unlock(&lpfcdfc_lock); + msleep(2000); + mutex_lock(&lpfcdfc_lock); + } + + if (dfchba->dev->driver) { + host = pci_get_drvdata(dfchba->dev); + if ((host != NULL) && + (struct lpfc_vport *)host->hostdata == dfchba->vport) { + phba = dfchba->phba; + mutex_unlock(&lpfcdfc_lock); + spin_lock_irq(&phba->hbalock); + prt = phba->sli.ring[LPFC_ELS_RING].prt; + prt[2].lpfc_sli_rcv_unsol_event = + dfchba->base_ct_unsol_event; + prt[3].lpfc_sli_rcv_unsol_event = + dfchba->base_ct_unsol_event; + spin_unlock_irq(&phba->hbalock); + mutex_lock(&lpfcdfc_lock); + } + } + list_del_init(&dfchba->node); + mutex_unlock(&lpfcdfc_lock); + kfree (dfchba); +} + +/* + * Retrieve lpfc_hba * matching instance (board no) + * If found return lpfc_hba * + * If not found return NULL + */ +static struct lpfcdfc_host * +lpfcdfc_get_phba_by_inst(int inst) +{ + struct Scsi_Host * host = NULL; + struct lpfcdfc_host * dfchba; + + mutex_lock(&lpfcdfc_lock); + list_for_each_entry(dfchba, &lpfcdfc_hosts, node) { + if (dfchba->inst == inst) { + if (dfchba->dev->driver) { + host = pci_get_drvdata(dfchba->dev); + if ((host != NULL) && + (struct lpfc_vport *)host->hostdata == + dfchba->vport) { + mutex_unlock(&lpfcdfc_lock); + BUG_ON(dfchba->phba->brd_no != inst); + return dfchba; + } + } + mutex_unlock(&lpfcdfc_lock); + return NULL; + } + } + mutex_unlock(&lpfcdfc_lock); + + return NULL; +} + +static int +lpfcdfc_do_ioctl(struct lpfcCmdInput *cip) +{ + struct lpfcdfc_host * dfchba = NULL; + struct lpfc_hba *phba = NULL; + int rc; + uint32_t total_mem; + void *dataout; + + + /* Some ioctls are per module and do not need phba */ + switch (cip->lpfc_cmd) { + case LPFC_GET_DFC_REV: + break; + default: + dfchba = lpfcdfc_get_phba_by_inst(cip->lpfc_brd); + if (dfchba == NULL) + return EINVAL; + phba = dfchba->phba; + break; + }; + + if (phba) + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1601 libdfc ioctl entry Data: x%x x%lx x%lx x%x\n", + cip->lpfc_cmd, (unsigned long) cip->lpfc_arg1, + (unsigned long) cip->lpfc_arg2, cip->lpfc_outsz); + mutex_lock(&lpfcdfc_lock); + if (dfchba && dfchba->blocked) { + mutex_unlock(&lpfcdfc_lock); + return EINVAL; + } + if (dfchba) + dfchba->ref_count++; + mutex_unlock(&lpfcdfc_lock); + if (cip->lpfc_outsz >= BUF_SZ_4K) { + + /* + * Allocate memory for ioctl data. If buffer is bigger than 64k, + * then we allocate 64k and re-use that buffer over and over to + * xfer the whole block. This is because Linux kernel has a + * problem allocating more than 120k of kernel space memory. Saw + * problem with GET_FCPTARGETMAPPING... + */ + if (cip->lpfc_outsz <= (64 * 1024)) + total_mem = cip->lpfc_outsz; + else + total_mem = 64 * 1024; + } else { + /* Allocate memory for ioctl data */ + total_mem = BUF_SZ_4K; + } + + /* + * For LPFC_HBA_GET_EVENT allocate memory which is needed to store + * event info. Allocating maximum possible buffer size (64KB) can fail + * some times under heavy IO. + */ + if (cip->lpfc_cmd == LPFC_HBA_GET_EVENT) { + dataout = NULL; + } else { + dataout = kmalloc(total_mem, GFP_KERNEL); + + if (!dataout && dfchba != NULL) { + mutex_lock(&lpfcdfc_lock); + if (dfchba) + dfchba->ref_count--; + mutex_unlock(&lpfcdfc_lock); + return ENOMEM; + } + } + + switch (cip->lpfc_cmd) { + + case LPFC_GET_DFC_REV: + ((struct DfcRevInfo *) dataout)->a_Major = DFC_MAJOR_REV; + ((struct DfcRevInfo *) dataout)->a_Minor = DFC_MINOR_REV; + cip->lpfc_outsz = sizeof (struct DfcRevInfo); + rc = 0; + break; + + case LPFC_SEND_ELS: + rc = lpfc_ioctl_send_els(phba, cip, dataout); + break; + + case LPFC_HBA_SEND_MGMT_RSP: + rc = lpfc_ioctl_send_mgmt_rsp(phba, cip); + break; + + case LPFC_HBA_SEND_MGMT_CMD: + case LPFC_CT: + rc = lpfc_ioctl_send_mgmt_cmd(phba, cip, dataout); + break; + + case LPFC_HBA_GET_EVENT: + rc = lpfc_ioctl_hba_get_event(phba, cip, &dataout, &total_mem); + if ((total_mem) && (copy_to_user ((void __user *) + cip->lpfc_dataout, (uint8_t *) dataout, total_mem))) + rc = EIO; + /* This is to prevent copy_to_user at end of the function. */ + cip->lpfc_outsz = 0; + break; + + case LPFC_HBA_SET_EVENT: + rc = lpfc_ioctl_hba_set_event(phba, cip); + break; + + case LPFC_LOOPBACK_MODE: + rc = lpfc_ioctl_loopback_mode(phba, cip, dataout); + break; + + case LPFC_LOOPBACK_TEST: + rc = lpfc_ioctl_loopback_test(phba, cip, dataout); + break; + + case LPFC_HBA_RNID: + rc = lpfc_ioctl_hba_rnid(phba, cip, dataout); + break; + + default: + rc = EINVAL; + break; + } + + if (phba) + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1602 libdfc ioctl exit Data: x%x x%x x%lx\n", + rc, cip->lpfc_outsz, (unsigned long) cip->lpfc_dataout); + /* Copy data to user space config method */ + if (rc == 0) { + if (cip->lpfc_outsz) { + if (copy_to_user + ((void __user *) cip->lpfc_dataout, + (uint8_t *) dataout, cip->lpfc_outsz)) { + rc = EIO; + } + } + } + + kfree(dataout); + mutex_lock(&lpfcdfc_lock); + if (dfchba) + dfchba->ref_count--; + mutex_unlock(&lpfcdfc_lock); + + return rc; +} + +static int +lpfcdfc_ioctl(struct inode *inode, + struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc; + struct lpfcCmdInput *ci; + + if (!arg) + return -EINVAL; + + ci = (struct lpfcCmdInput *) kmalloc(sizeof (struct lpfcCmdInput), + GFP_KERNEL); + + if (!ci) + return -ENOMEM; + + if ((rc = copy_from_user + ((uint8_t *) ci, (void __user *) arg, + sizeof (struct lpfcCmdInput)))) { + kfree(ci); + return -EIO; + } + + rc = lpfcdfc_do_ioctl(ci); + + kfree(ci); + return -rc; +} + +#ifdef CONFIG_COMPAT +static long +lpfcdfc_compat_ioctl(struct file * file, unsigned int cmd, unsigned long arg) +{ + struct lpfcCmdInput32 arg32; + struct lpfcCmdInput arg64; + int ret; + + if(copy_from_user(&arg32, (void __user *)arg, + sizeof(struct lpfcCmdInput32))) + return -EFAULT; + + arg64.lpfc_brd = arg32.lpfc_brd; + arg64.lpfc_ring = arg32.lpfc_ring; + arg64.lpfc_iocb = arg32.lpfc_iocb; + arg64.lpfc_flag = arg32.lpfc_flag; + arg64.lpfc_arg1 = (void *)(unsigned long) arg32.lpfc_arg1; + arg64.lpfc_arg2 = (void *)(unsigned long) arg32.lpfc_arg2; + arg64.lpfc_arg3 = (void *)(unsigned long) arg32.lpfc_arg3; + arg64.lpfc_dataout = (void *)(unsigned long) arg32.lpfc_dataout; + arg64.lpfc_cmd = arg32.lpfc_cmd; + arg64.lpfc_outsz = arg32.lpfc_outsz; + arg64.lpfc_arg4 = arg32.lpfc_arg4; + arg64.lpfc_arg5 = arg32.lpfc_arg5; + + ret = lpfcdfc_do_ioctl(&arg64); + + arg32.lpfc_brd = arg64.lpfc_brd; + arg32.lpfc_ring = arg64.lpfc_ring; + arg32.lpfc_iocb = arg64.lpfc_iocb; + arg32.lpfc_flag = arg64.lpfc_flag; + arg32.lpfc_arg1 = (u32)(unsigned long) arg64.lpfc_arg1; + arg32.lpfc_arg2 = (u32)(unsigned long) arg64.lpfc_arg2; + arg32.lpfc_arg3 = (u32)(unsigned long) arg64.lpfc_arg3; + arg32.lpfc_dataout = (u32)(unsigned long) arg64.lpfc_dataout; + arg32.lpfc_cmd = arg64.lpfc_cmd; + arg32.lpfc_outsz = arg64.lpfc_outsz; + arg32.lpfc_arg4 = arg64.lpfc_arg4; + arg32.lpfc_arg5 = arg64.lpfc_arg5; + + if(copy_to_user((void __user *)arg, &arg32, + sizeof(struct lpfcCmdInput32))) + return -EFAULT; + + return -ret; +} +#endif + +static struct file_operations lpfc_fops = { + .owner = THIS_MODULE, + .ioctl = lpfcdfc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = lpfcdfc_compat_ioctl, +#endif +}; + +int +lpfc_cdev_init(void) +{ + + lpfcdfc_major = register_chrdev(0, LPFC_CHAR_DEV_NAME, &lpfc_fops); + if (lpfcdfc_major < 0) { + printk(KERN_ERR "%s:%d Unable to register \"%s\" device.\n", + __func__, __LINE__, LPFC_CHAR_DEV_NAME); + return lpfcdfc_major; + } + + return 0; +} + +void +lpfc_cdev_exit(void) +{ + unregister_chrdev(lpfcdfc_major, LPFC_CHAR_DEV_NAME); +} --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_ioctl.h @@ -0,0 +1,184 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2006 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.emulex.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ + +#define DFC_MAJOR_REV 81 +#define DFC_MINOR_REV 0 + +#define LPFC_MAX_EVENT 128 + +#define LPFC_CT 0x42 /* Send CT passthru command */ +#define LPFC_HBA_RNID 0x52 /* Send an RNID request */ +#define LPFC_HBA_REFRESHINFO 0x56 /* Do a refresh of the stats */ +#define LPFC_SEND_ELS 0x57 /* Send out an ELS command */ +#define LPFC_HBA_SET_EVENT 0x59 /* Set FCP event(s) */ +#define LPFC_HBA_GET_EVENT 0x5a /* Get FCP event(s) */ +#define LPFC_HBA_SEND_MGMT_CMD 0x5b /* Send a management command */ +#define LPFC_HBA_SEND_MGMT_RSP 0x5c /* Send a management response */ + +#define LPFC_GET_DFC_REV 0x68 /* Get the rev of the ioctl + driver */ +#define LPFC_LOOPBACK_TEST 0x72 /* Run Loopback test */ +#define LPFC_LOOPBACK_MODE 0x73 /* Enter Loopback mode */ +/* LPFC_LAST_IOCTL_USED 0x73 Last LPFC Ioctl used */ + +#define INTERNAL_LOOP_BACK 0x1 +#define EXTERNAL_LOOP_BACK 0x2 + +/* the DfcRevInfo structure */ +struct DfcRevInfo { + uint32_t a_Major; + uint32_t a_Minor; +} ; + +#define LPFC_WWPN_TYPE 0 +#define LPFC_PORTID_TYPE 1 +#define LPFC_WWNN_TYPE 2 + +struct nport_id { + uint32_t idType; /* 0 - wwpn, 1 - d_id, 2 - wwnn */ + uint32_t d_id; + uint8_t wwpn[8]; +}; + +#define LPFC_EVENT_LIP_OCCURRED 1 +#define LPFC_EVENT_LINK_UP 2 +#define LPFC_EVENT_LINK_DOWN 3 +#define LPFC_EVENT_LIP_RESET_OCCURRED 4 +#define LPFC_EVENT_RSCN 5 +#define LPFC_EVENT_PROPRIETARY 0xFFFF + +struct lpfc_hba_event_info { + uint32_t event_code; + uint32_t port_id; + union { + uint32_t rscn_event_info; + uint32_t pty_event_info; + } event; +}; + + +#define LPFC_CHAR_DEV_NAME "lpfcdfc" + +/* + * Diagnostic (DFC) Command & Input structures: (LPFC) + */ +struct lpfcCmdInput { + short lpfc_brd; + short lpfc_ring; + short lpfc_iocb; + short lpfc_flag; + void *lpfc_arg1; + void *lpfc_arg2; + void *lpfc_arg3; + char *lpfc_dataout; + uint32_t lpfc_cmd; + uint32_t lpfc_outsz; + uint32_t lpfc_arg4; + uint32_t lpfc_arg5; +}; +/* Used for ioctl command */ +#define LPFC_DFC_CMD_IOCTL_MAGIC 0xFC +#define LPFC_DFC_CMD_IOCTL _IOWR(LPFC_DFC_CMD_IOCTL_MAGIC, 0x1,\ + struct lpfcCmdInput) + +#ifdef CONFIG_COMPAT +/* 32 bit version */ +struct lpfcCmdInput32 { + short lpfc_brd; + short lpfc_ring; + short lpfc_iocb; + short lpfc_flag; + u32 lpfc_arg1; + u32 lpfc_arg2; + u32 lpfc_arg3; + u32 lpfc_dataout; + uint32_t lpfc_cmd; + uint32_t lpfc_outsz; + uint32_t lpfc_arg4; + uint32_t lpfc_arg5; +}; +#endif + +#define SLI_CT_ELX_LOOPBACK 0x10 + +enum ELX_LOOPBACK_CMD { + ELX_LOOPBACK_XRI_SETUP, + ELX_LOOPBACK_DATA, +}; + + +struct lpfc_link_info { + uint32_t a_linkEventTag; + uint32_t a_linkUp; + uint32_t a_linkDown; + uint32_t a_linkMulti; + uint32_t a_DID; + uint8_t a_topology; + uint8_t a_linkState; + uint8_t a_alpa; + uint8_t a_alpaCnt; + uint8_t a_alpaMap[128]; + uint8_t a_wwpName[8]; + uint8_t a_wwnName[8]; +}; + +enum lpfc_host_event_code { + LPFCH_EVT_LIP = 0x1, + LPFCH_EVT_LINKUP = 0x2, + LPFCH_EVT_LINKDOWN = 0x3, + LPFCH_EVT_LIPRESET = 0x4, + LPFCH_EVT_RSCN = 0x5, + LPFCH_EVT_ADAPTER_CHANGE = 0x103, + LPFCH_EVT_PORT_UNKNOWN = 0x200, + LPFCH_EVT_PORT_OFFLINE = 0x201, + LPFCH_EVT_PORT_ONLINE = 0x202, + LPFCH_EVT_PORT_FABRIC = 0x204, + LPFCH_EVT_LINK_UNKNOWN = 0x500, + LPFCH_EVT_VENDOR_UNIQUE = 0xffff, +}; + +#define ELX_LOOPBACK_HEADER_SZ \ + (size_t)(&((struct lpfc_sli_ct_request *)NULL)->un) + +struct lpfc_host_event { + uint32_t seq_num; + enum lpfc_host_event_code event_code; + uint32_t data; +}; + +struct lpfc_timedout_iocb_ctxt { + struct lpfc_iocbq *rspiocbq; + struct lpfc_dmabuf *mp; + struct lpfc_dmabuf *bmp; + struct lpfc_scsi_buf *lpfc_cmd; + struct lpfc_dmabufext *outdmp; + struct lpfc_dmabufext *indmp; +}; + +#ifdef __KERNEL__ +struct lpfcdfc_host; + +/* Initialize/Un-initialize char device */ +int lpfc_cdev_init(void); +void lpfc_cdev_exit(void); +void lpfcdfc_host_del(struct lpfcdfc_host *); +struct lpfcdfc_host *lpfcdfc_host_add(struct pci_dev *, struct Scsi_Host *, + struct lpfc_hba *); +#endif /* __KERNEL__ */ --- a/drivers/scsi/lpfc/lpfc_logmsg.h +++ b/drivers/scsi/lpfc/lpfc_logmsg.h @@ -32,6 +32,7 @@ #define LOG_FCP_ERROR 0x1000 /* log errors, not underruns */ #define LOG_LIBDFC 0x2000 /* Libdfc events */ #define LOG_VPORT 0x4000 /* NPIV events */ +#define LOG_SECURITY 0x8000 /* FC Security */ #define LOG_ALL_MSG 0xffff /* LOG all messages */ #define lpfc_printf_vlog(vport, level, mask, fmt, arg...) \ --- a/drivers/scsi/lpfc/lpfc_mbox.c +++ b/drivers/scsi/lpfc/lpfc_mbox.c @@ -1083,7 +1083,7 @@ lpfc_config_port(struct lpfc_hba *phba, phba->pcb->feature = FEATURE_INITIAL_SLI2; /* Setup Mailbox pointers */ - phba->pcb->mailBoxSize = sizeof(MAILBOX_t); + phba->pcb->mailBoxSize = sizeof(MAILBOX_t) + MAILBOX_EXT_SIZE; offset = (uint8_t *)phba->mbox - (uint8_t *)phba->slim2p.virt; pdma_addr = phba->slim2p.phys + offset; phba->pcb->mbAddrHigh = putPaddrHigh(pdma_addr); --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_menlo.c @@ -0,0 +1,1174 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2007-2008 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.emulex.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "lpfc_hw.h" +#include "lpfc_sli.h" +#include "lpfc_nl.h" +#include "lpfc_disc.h" +#include "lpfc_scsi.h" +#include "lpfc.h" +#include "lpfc_logmsg.h" +#include "lpfc_version.h" +#include "lpfc_compat.h" +#include "lpfc_crtn.h" +#include "lpfc_vport.h" + +#define MENLO_CMD_FW_DOWNLOAD 0x00000002 + +static void lpfc_menlo_iocb_timeout_cmpl(struct lpfc_hba *, + struct lpfc_iocbq *, struct lpfc_iocbq *); + +extern int +__dfc_cmd_data_free(struct lpfc_hba * phba, struct lpfc_dmabufext * mlist); + +extern struct lpfc_dmabufext * +__dfc_cmd_data_alloc(struct lpfc_hba * phba, + char *indataptr, struct ulp_bde64 * bpl, uint32_t size, + int nocopydata); +/* + * The size for the menlo interface is set at 336k because it only uses + * one bpl. A bpl can contain 85 BDE descriptors. Each BDE can represent + * up to 4k. I used 84 BDE entries to do this calculation because the + * 1st sysfs_menlo_write is for just the cmd header which is 12 bytes. + * size = PAGE_SZ * (sizeof(bpl) / sizeof(BDE)) -1; + */ +#define SYSFS_MENLO_ATTR_SIZE 344064 +typedef struct menlo_get_cmd +{ + uint32_t code; /* Command code */ + uint32_t context; /* Context */ + uint32_t length; /* Max response length */ +} menlo_get_cmd_t; + +typedef struct menlo_init_rsp +{ + uint32_t code; + uint32_t bb_credit; /* Menlo FC BB Credit */ + uint32_t frame_size; /* Menlo FC receive frame size */ + uint32_t fw_version; /* Menlo firmware version */ + uint32_t reset_status; /* Reason for previous reset */ + +#define MENLO_RESET_STATUS_NORMAL 0 +#define MENLO_RESET_STATUS_PANIC 1 + + uint32_t maint_status; /* Menlo Maintenance Mode status at link up */ + + +#define MENLO_MAINTENANCE_MODE_DISABLE 0 +#define MENLO_MAINTENANCE_MODE_ENABLE 1 + uint32_t fw_type; + uint32_t fru_data_valid; /* 0=invalid, 1=valid */ +} menlo_init_rsp_t; + +#define MENLO_CMD_GET_INIT 0x00000007 +#define MENLO_FW_TYPE_OPERATIONAL 0xABCD0001 +#define MENLO_FW_TYPE_GOLDEN 0xABCD0002 +#define MENLO_FW_TYPE_DIAG 0xABCD0003 + +void +BE_swap32_buffer(void *srcp, uint32_t cnt) +{ + uint32_t *src = srcp; + uint32_t *dest = srcp; + uint32_t ldata; + int i; + + for (i = 0; i < (int)cnt; i += sizeof (uint32_t)) { + ldata = *src; + ldata = cpu_to_le32(ldata); + *dest = ldata; + src++; + dest++; + } +} + + +static int +lpfc_alloc_menlo_genrequest64(struct lpfc_hba * phba, + struct lpfc_menlo_genreq64 *sysfs_menlo, + struct lpfc_sysfs_menlo_hdr *cmdhdr) +{ + struct Scsi_Host *shost = lpfc_shost_from_vport(phba->pport); + struct ulp_bde64 *bpl = NULL; + IOCB_t *cmd = NULL, *rsp = NULL; + struct lpfc_sli *psli = NULL; + struct lpfc_sli_ring *pring = NULL; + int rc = 0; + uint32_t cmdsize; + uint32_t rspsize; + + psli = &phba->sli; + pring = &psli->ring[LPFC_ELS_RING]; + + if (!(psli->sli_flag & LPFC_SLI2_ACTIVE)) { + rc = EACCES; + goto send_menlomgmt_cmd_exit; + } + + if (!sysfs_menlo) { + rc = EINVAL; + goto send_menlomgmt_cmd_exit; + } + + cmdsize = cmdhdr->cmdsize; + rspsize = cmdhdr->rspsize; + + if (!cmdsize || !rspsize || (cmdsize + rspsize > 80 * BUF_SZ_4K)) { + rc = ERANGE; + goto send_menlomgmt_cmd_exit; + } + + spin_lock_irq(shost->host_lock); + sysfs_menlo->cmdiocbq = lpfc_sli_get_iocbq(phba); + if (!sysfs_menlo->cmdiocbq) { + rc = ENOMEM; + spin_unlock_irq(shost->host_lock); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1202 alloc_menlo_genreq64: couldn't alloc cmdiocbq\n"); + goto send_menlomgmt_cmd_exit; + } + cmd = &sysfs_menlo->cmdiocbq->iocb; + + sysfs_menlo->rspiocbq = lpfc_sli_get_iocbq(phba); + if (!sysfs_menlo->rspiocbq) { + rc = ENOMEM; + spin_unlock_irq(shost->host_lock); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1203 alloc_menlo_genreq64: couldn't alloc rspiocbq\n"); + goto send_menlomgmt_cmd_exit; + } + spin_unlock_irq(shost->host_lock); + + rsp = &sysfs_menlo->rspiocbq->iocb; + + + sysfs_menlo->bmp = kmalloc(sizeof (struct lpfc_dmabuf), GFP_KERNEL); + if (!sysfs_menlo->bmp) { + rc = ENOMEM; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1204 alloc_menlo_genreq64: couldn't alloc bmp\n"); + goto send_menlomgmt_cmd_exit; + } + + spin_lock_irq(shost->host_lock); + sysfs_menlo->bmp->virt = lpfc_mbuf_alloc(phba, 0, + &sysfs_menlo->bmp->phys); + if (!sysfs_menlo->bmp->virt) { + rc = ENOMEM; + spin_unlock_irq(shost->host_lock); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1205 alloc_menlo_genreq64: couldn't alloc bpl\n"); + goto send_menlomgmt_cmd_exit; + } + spin_unlock_irq(shost->host_lock); + + INIT_LIST_HEAD(&sysfs_menlo->bmp->list); + bpl = (struct ulp_bde64 *) sysfs_menlo->bmp->virt; + memset((uint8_t*)bpl, 0 , 1024); + sysfs_menlo->indmp = __dfc_cmd_data_alloc(phba, NULL, bpl, cmdsize, 1); + if (!sysfs_menlo->indmp) { + rc = ENOMEM; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1206 alloc_menlo_genreq64: couldn't alloc cmdbuf\n"); + goto send_menlomgmt_cmd_exit; + } + sysfs_menlo->cmdbpl = bpl; + INIT_LIST_HEAD(&sysfs_menlo->inhead); + list_add_tail(&sysfs_menlo->inhead, &sysfs_menlo->indmp->dma.list); + + /* flag contains total number of BPLs for xmit */ + + bpl += sysfs_menlo->indmp->flag; + + sysfs_menlo->outdmp = __dfc_cmd_data_alloc(phba, NULL, bpl, rspsize, 0); + if (!sysfs_menlo->outdmp) { + rc = ENOMEM; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1207 alloc_menlo_genreq64: couldn't alloc rspbuf\n"); + goto send_menlomgmt_cmd_exit; + } + INIT_LIST_HEAD(&sysfs_menlo->outhead); + list_add_tail(&sysfs_menlo->outhead, &sysfs_menlo->outdmp->dma.list); + + cmd->un.genreq64.bdl.ulpIoTag32 = 0; + cmd->un.genreq64.bdl.addrHigh = putPaddrHigh(sysfs_menlo->bmp->phys); + cmd->un.genreq64.bdl.addrLow = putPaddrLow(sysfs_menlo->bmp->phys); + cmd->un.genreq64.bdl.bdeFlags = BUFF_TYPE_BLP_64; + cmd->un.genreq64.bdl.bdeSize = + (sysfs_menlo->outdmp->flag + sysfs_menlo->indmp->flag) + * sizeof(struct ulp_bde64); + cmd->ulpCommand = CMD_GEN_REQUEST64_CR; + cmd->un.genreq64.w5.hcsw.Fctl = (SI | LA); + cmd->un.genreq64.w5.hcsw.Dfctl = 0; + cmd->un.genreq64.w5.hcsw.Rctl = FC_FCP_CMND; + cmd->un.genreq64.w5.hcsw.Type = MENLO_TRANSPORT_TYPE; /* 0xfe */ + cmd->un.ulpWord[4] = MENLO_DID; /* 0x0000FC0E */ + cmd->ulpBdeCount = 1; + cmd->ulpClass = CLASS3; + cmd->ulpContext = MENLO_CONTEXT; /* 0 */ + cmd->ulpOwner = OWN_CHIP; + cmd->ulpPU = MENLO_PU; /* 3 */ + cmd->ulpLe = 1; /* Limited Edition */ + sysfs_menlo->cmdiocbq->vport = phba->pport; + sysfs_menlo->cmdiocbq->context1 = NULL; + sysfs_menlo->cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; + /* We want the firmware to timeout before we do */ + cmd->ulpTimeout = MENLO_TIMEOUT - 5; + + sysfs_menlo->timeout = cmd->ulpTimeout; + +send_menlomgmt_cmd_exit: + return rc; +} + +void +sysfs_menlo_genreq_free(struct lpfc_hba *phba, + struct lpfc_menlo_genreq64 *sysfs_menlo) +{ + if ( !list_empty(&sysfs_menlo->outhead)) + list_del_init( &sysfs_menlo->outhead); + + if (!list_empty(&sysfs_menlo->inhead)) + list_del_init( &sysfs_menlo->inhead); + + if (sysfs_menlo->outdmp) { + __dfc_cmd_data_free(phba, sysfs_menlo->outdmp); + sysfs_menlo->outdmp = NULL; + } + if (sysfs_menlo->indmp) { + __dfc_cmd_data_free(phba, sysfs_menlo->indmp); + sysfs_menlo->indmp = NULL; + } + if (sysfs_menlo->bmp) { + lpfc_mbuf_free(phba, sysfs_menlo->bmp->virt, + sysfs_menlo->bmp->phys); + kfree(sysfs_menlo->bmp); + sysfs_menlo->bmp = NULL; + } + if (sysfs_menlo->rspiocbq) { + lpfc_sli_release_iocbq(phba, sysfs_menlo->rspiocbq); + sysfs_menlo->rspiocbq = NULL; + } + + if (sysfs_menlo->cmdiocbq) { + lpfc_sli_release_iocbq(phba, sysfs_menlo->cmdiocbq); + sysfs_menlo->cmdiocbq = NULL; + } +} + +static void +sysfs_menlo_idle(struct lpfc_hba *phba, + struct lpfc_sysfs_menlo *sysfs_menlo) +{ + struct Scsi_Host *shost = lpfc_shost_from_vport(phba->pport); + + spin_lock_irq(&phba->hbalock); + list_del_init(&sysfs_menlo->list); + spin_unlock_irq(&phba->hbalock); + spin_lock_irq(shost->host_lock); + + if (sysfs_menlo->cr.cmdiocbq) + sysfs_menlo_genreq_free(phba, &sysfs_menlo->cr); + if (sysfs_menlo->cx.cmdiocbq) + sysfs_menlo_genreq_free(phba, &sysfs_menlo->cx); + + spin_unlock_irq(shost->host_lock); + kfree(sysfs_menlo); +} + +static void +lpfc_menlo_iocb_timeout_cmpl(struct lpfc_hba *phba, + struct lpfc_iocbq *cmdq, + struct lpfc_iocbq *rspq) +{ + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1241 Menlo IOCB timeout: deleting %p\n", + cmdq->context3); + sysfs_menlo_idle(phba, (struct lpfc_sysfs_menlo *)cmdq->context3); +} + +static void +lpfc_menlo_iocb_cmpl(struct lpfc_hba *phba, + struct lpfc_iocbq *cmdq, + struct lpfc_iocbq *rspq) +{ + struct lpfc_sysfs_menlo * sysfs_menlo = + (struct lpfc_sysfs_menlo *)cmdq->context2; + struct lpfc_dmabufext *mlast = NULL; + IOCB_t *rsp = NULL; + IOCB_t *cmd = NULL; + uint32_t * tmpptr = NULL; + menlo_init_rsp_t *mlorsp = NULL; + + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1254 Menlo IOCB complete: %p\n", + cmdq->context2); + rsp = &rspq->iocb; + cmd = &cmdq->iocb; + if ( !sysfs_menlo ) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1255 Menlo IOCB complete:NULL CTX \n"); + return; + } + if ( rsp->ulpStatus ) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1242 iocb async cmpl: ulpStatus 0x%x " + "ulpWord[4] 0x%x\n", + rsp->ulpStatus, rsp->un.ulpWord[4]); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1260 cr:%.08x %.08x %.08x %.08x " + "%.08x %.08x %.08x %.08x\n", + cmd->un.ulpWord[0], cmd->un.ulpWord[1], + cmd->un.ulpWord[2], cmd->un.ulpWord[3], + cmd->un.ulpWord[4], cmd->un.ulpWord[5], + *(uint32_t *)&cmd->un1, *((uint32_t *)&cmd->un1 + 1)); + mlast = list_get_first(&sysfs_menlo->cr.inhead, + struct lpfc_dmabufext, + dma.list); + if (!mlast) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1231 bad bpl:\n"); + goto lpfc_menlo_iocb_cmpl_ext; + } + tmpptr = ( uint32_t *) mlast->dma.virt; + BE_swap32_buffer ((uint8_t *) tmpptr, + sizeof( menlo_get_cmd_t)); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1261 cmd:%.08x %.08x %.08x\n", + *tmpptr, *(tmpptr+1), *(tmpptr+2)); + goto lpfc_menlo_iocb_cmpl_ext; + } + + mlast = list_get_first(&sysfs_menlo->cr.outhead, + struct lpfc_dmabufext, + dma.list); + if (!mlast) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1256 bad bpl:\n"); + goto lpfc_menlo_iocb_cmpl_ext; + } + mlorsp = ( menlo_init_rsp_t *) mlast->dma.virt; + BE_swap32_buffer ((uint8_t *) mlorsp, + sizeof( menlo_init_rsp_t)); + + if (mlorsp->code != 0) { + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1243 Menlo command error. code=%d.\n", mlorsp->code); + goto lpfc_menlo_iocb_cmpl_ext; + + } + + switch (mlorsp->fw_type) + { + case MENLO_FW_TYPE_OPERATIONAL: /* Menlo Operational */ + break; + case MENLO_FW_TYPE_GOLDEN: /* Menlo Golden */ + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1246 FCoE chip is running golden firmware. " + "Update FCoE chip firmware immediately %x\n", + mlorsp->fw_type); + break; + case MENLO_FW_TYPE_DIAG: /* Menlo Diag */ + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1247 FCoE chip is running diagnostic " + "firmware. Operational use suspended. %x\n", + mlorsp->fw_type); + break; + default: + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1248 FCoE chip is running unknown " + "firmware x%x.\n", mlorsp->fw_type); + break; + } + if (!mlorsp->fru_data_valid + && (mlorsp->fw_type == MENLO_FW_TYPE_OPERATIONAL) + && (!mlorsp->maint_status)) + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1249 Invalid FRU data found on adapter." + "Return adapter to Emulex for repair\n"); + +lpfc_menlo_iocb_cmpl_ext: + sysfs_menlo_idle(phba, (struct lpfc_sysfs_menlo *)cmdq->context2); +} + +static struct lpfc_sysfs_menlo * +lpfc_get_sysfs_menlo(struct lpfc_hba *phba, uint8_t create) +{ + struct lpfc_sysfs_menlo *sysfs_menlo; + pid_t pid; + + pid = current->pid; + + spin_lock_irq(&phba->hbalock); + list_for_each_entry(sysfs_menlo, &phba->sysfs_menlo_list, list) { + if (sysfs_menlo->pid == pid) { + spin_unlock_irq(&phba->hbalock); + return sysfs_menlo; + } + } + if (!create) { + spin_unlock_irq(&phba->hbalock); + return NULL; + } + spin_unlock_irq(&phba->hbalock); + sysfs_menlo = kzalloc(sizeof(struct lpfc_sysfs_menlo), + GFP_KERNEL); + if (!sysfs_menlo) + return NULL; + sysfs_menlo->state = SMENLO_IDLE; + sysfs_menlo->pid = pid; + spin_lock_irq(&phba->hbalock); + list_add_tail(&sysfs_menlo->list, &phba->sysfs_menlo_list); + + spin_unlock_irq(&phba->hbalock); + return sysfs_menlo; + +} + +static ssize_t +lpfc_menlo_write(struct lpfc_hba *phba, + char *buf, loff_t off, size_t count) +{ + struct lpfc_sysfs_menlo *sysfs_menlo; + struct lpfc_dmabufext *mlast = NULL; + struct lpfc_sysfs_menlo_hdr cmdhdrCR; + struct lpfc_menlo_genreq64 *genreq = NULL; + loff_t temp_off = 0; + struct ulp_bde64 *bpl = NULL; + int mlastcnt = 0; + uint32_t * tmpptr = NULL; + uint32_t addr_high = 0; + uint32_t addr_low = 0; + int hdr_offset = sizeof(struct lpfc_sysfs_menlo_hdr); + + if (off % 4 || count % 4 || (unsigned long)buf % 4) + return -EINVAL; + + if (count == 0) + return 0; + + if (off == 0) { + ssize_t rc; + struct lpfc_sysfs_menlo_hdr *cmdhdr = + (struct lpfc_sysfs_menlo_hdr *)buf; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1208 menlo_write: cmd %x cmdsz %d rspsz %d\n", + cmdhdr->cmd, cmdhdr->cmdsize, + cmdhdr->rspsize); + if (count != sizeof(struct lpfc_sysfs_menlo_hdr)) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1210 Invalid cmd size: cmd %x " + "cmdsz %d rspsz %d\n", + cmdhdr->cmd, cmdhdr->cmdsize, + cmdhdr->rspsize); + return -EINVAL; + } + + sysfs_menlo = lpfc_get_sysfs_menlo(phba, 1); + if (!sysfs_menlo) + return -ENOMEM; + sysfs_menlo->cmdhdr = *cmdhdr; + if (cmdhdr->cmd == MENLO_CMD_FW_DOWNLOAD) { + sysfs_menlo->cmdhdr.cmdsize + -= sizeof(struct lpfc_sysfs_menlo_hdr); + + rc = lpfc_alloc_menlo_genrequest64(phba, + &sysfs_menlo->cx, + &sysfs_menlo->cmdhdr); + if (rc != 0) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1211 genreq alloc failed: %d\n", + (int) rc); + sysfs_menlo_idle(phba,sysfs_menlo); + return -ENOMEM; + } + cmdhdrCR.cmd = cmdhdr->cmd; + cmdhdrCR.cmdsize = sizeof(struct lpfc_sysfs_menlo_hdr); + cmdhdrCR.rspsize = 4; + } else + cmdhdrCR = *cmdhdr; + + rc = lpfc_alloc_menlo_genrequest64(phba, + &sysfs_menlo->cr,&cmdhdrCR); + if (rc != 0) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1223 menlo_write: couldn't alloc genreq %d\n", + (int) rc); + sysfs_menlo_idle(phba,sysfs_menlo); + return -ENOMEM; + } + } else { + sysfs_menlo = lpfc_get_sysfs_menlo(phba, 0); + if (!sysfs_menlo) + return -EAGAIN; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1212 menlo_write: sysfs_menlo %p cmd %x cmdsz %d" + " rspsz %d cr-off %d cx-off %d count %d\n", + sysfs_menlo, + sysfs_menlo->cmdhdr.cmd, + sysfs_menlo->cmdhdr.cmdsize, + sysfs_menlo->cmdhdr.rspsize, + (int)sysfs_menlo->cr.offset, + (int)sysfs_menlo->cx.offset, + (int)count); + } + + if ((count + sysfs_menlo->cr.offset) > sysfs_menlo->cmdhdr.cmdsize) { + if ( sysfs_menlo->cmdhdr.cmdsize != 4) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1213 FCoE cmd overflow: off %d + cnt %d > cmdsz %d\n", + (int)sysfs_menlo->cr.offset, + (int)count, + (int)sysfs_menlo->cmdhdr.cmdsize); + sysfs_menlo_idle(phba, sysfs_menlo); + return -ERANGE; + } + } + + spin_lock_irq(&phba->hbalock); + if (sysfs_menlo->cmdhdr.cmd == MENLO_CMD_FW_DOWNLOAD) + genreq = &sysfs_menlo->cx; + else + genreq = &sysfs_menlo->cr; + + if (off == 0) { + if (sysfs_menlo->cmdhdr.cmd == MENLO_CMD_FW_DOWNLOAD) { + tmpptr = NULL; + genreq = &sysfs_menlo->cr; + + if (!mlast) { + mlast = list_get_first(&genreq->inhead, + struct lpfc_dmabufext, + dma.list); + } + if (mlast) { + bpl = genreq->cmdbpl; + memcpy((uint8_t *) mlast->dma.virt, buf, count); + genreq->offset += count; + tmpptr = (uint32_t *)mlast->dma.virt; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1258 cmd %x cmdsz %d rspsz %d " + "copied %d addrL:%x addrH:%x\n", + *tmpptr, + *(tmpptr+1), + *(tmpptr+2), + (int)count, + bpl->addrLow,bpl->addrHigh); + } else { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1230 Could not find buffer for FCoE" + " cmd:off %d indmp %p %d\n", (int)off, + genreq->indmp,(int)count); + } + } + + sysfs_menlo->state = SMENLO_WRITING; + spin_unlock_irq(&phba->hbalock); + return count; + } else { + ssize_t adj_off = off - sizeof(struct lpfc_sysfs_menlo_hdr); + int found = 0; + if (sysfs_menlo->state != SMENLO_WRITING || + genreq->offset != adj_off) { + spin_unlock_irq(&phba->hbalock); + sysfs_menlo_idle(phba, sysfs_menlo); + return -EAGAIN; + } + mlast = NULL; + temp_off = sizeof(struct lpfc_sysfs_menlo_hdr); + if (genreq->indmp) { + list_for_each_entry(mlast, + &genreq->inhead, dma.list) { + if (temp_off == off) + break; + else + temp_off += BUF_SZ_4K; + mlastcnt++; + } + } + addr_low = le32_to_cpu( putPaddrLow(mlast->dma.phys) ); + addr_high = le32_to_cpu( putPaddrHigh(mlast->dma.phys) ); + bpl = genreq->cmdbpl; + bpl += mlastcnt; + if (bpl->addrLow != addr_low || bpl->addrHigh != addr_high) { + mlast = NULL; + list_for_each_entry(mlast, + &genreq->inhead, dma.list) { + + addr_low = le32_to_cpu( + putPaddrLow(mlast->dma.phys) ); + addr_high = le32_to_cpu( + putPaddrHigh(mlast->dma.phys) ); + if (bpl->addrLow == addr_low + && bpl->addrHigh == addr_high) { + found = 1; + break; + } + if ( mlastcnt < 3 ) + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1234 menlo_write: off:%d " + " mlastcnt:%d addl:%x addl:%x " + " addrh:%x addrh:%x mlast:%p\n", + (int)genreq->offset, + mlastcnt, + bpl->addrLow, + addr_low, + bpl->addrHigh, + addr_high,mlast); + } + } else + found = 1; + + if (!found) { + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1235 Could not find buffer for FCoE" + " cmd: off:%d poff:%d cnt:%d" + " mlastcnt:%d addl:%x addh:%x mdsz:%d \n", + (int)genreq->offset, + (int)off, + (int)count, + mlastcnt, + bpl->addrLow, + bpl->addrHigh, + (int)sysfs_menlo->cmdhdr.cmdsize); + mlast = NULL; + } + + } + + if (mlast) { + if (sysfs_menlo->cmdhdr.cmd == MENLO_CMD_FW_DOWNLOAD ) { + bpl = genreq->cmdbpl; + bpl += mlastcnt; + tmpptr = (uint32_t *)mlast->dma.virt; + if ( genreq->offset < hdr_offset ) { + memcpy((uint8_t *) mlast->dma.virt, + buf+hdr_offset, + count-hdr_offset); + bpl->tus.f.bdeSize = (ushort)count-hdr_offset; + mlast->size = (ushort)count-hdr_offset; + } else { + memcpy((uint8_t *) mlast->dma.virt, buf, count); + bpl->tus.f.bdeSize = (ushort)count; + mlast->size = (ushort)count; + } + bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; + bpl->tus.w = le32_to_cpu(bpl->tus.w); + + } else + memcpy((uint8_t *) mlast->dma.virt, buf, count); + + if (sysfs_menlo->cmdhdr.cmd == MENLO_CMD_FW_DOWNLOAD + && genreq->offset < hdr_offset) { + if (sysfs_menlo->cr.indmp + && sysfs_menlo->cr.indmp->dma.virt) { + mlast = sysfs_menlo->cr.indmp; + memcpy((uint8_t *) mlast->dma.virt, + buf, hdr_offset); + tmpptr = (uint32_t *)mlast->dma.virt; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1237 cmd %x cmd1 %x cmd2 %x " + "copied %d\n", + *tmpptr, + *(tmpptr+1), + *(tmpptr+2), + hdr_offset); + } + } + genreq->offset += count; + } else { + spin_unlock_irq(&phba->hbalock); + sysfs_menlo_idle(phba,sysfs_menlo); + return -ERANGE; + } + + spin_unlock_irq(&phba->hbalock); + return count; + +} + + +static ssize_t +sysfs_menlo_write(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + struct lpfc_hba *phba = vport->phba; + + return lpfc_menlo_write(phba, buf, off, count); +} + + +static ssize_t +sysfs_menlo_issue_iocb_wait(struct lpfc_hba *phba, + struct lpfc_menlo_genreq64 *req, + struct lpfc_sysfs_menlo *sysfs_menlo) +{ + struct lpfc_sli *psli = NULL; + struct lpfc_sli_ring *pring = NULL; + int rc = 0; + IOCB_t *rsp = NULL; + struct lpfc_iocbq *cmdiocbq = NULL; + + psli = &phba->sli; + pring = &psli->ring[LPFC_ELS_RING]; + rsp = &req->rspiocbq->iocb; + cmdiocbq = req->cmdiocbq; + + rc = lpfc_sli_issue_iocb_wait(phba, pring, req->cmdiocbq, req->rspiocbq, + req->timeout); + + if (rc == IOCB_TIMEDOUT) { + + cmdiocbq->context2 = NULL; + cmdiocbq->context3 = sysfs_menlo; + cmdiocbq->iocb_cmpl = lpfc_menlo_iocb_timeout_cmpl; + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "1227 FCoE IOCB TMO: handler set for %p\n", + cmdiocbq->context3); + return -EACCES; + } + + if (rc != IOCB_SUCCESS) { + rc = -EFAULT; + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1216 FCoE IOCB failed: off %d rc=%d \n", + (int)req->offset, rc); + goto sysfs_menlo_issue_iocb_wait_exit; + } + + if (rsp->ulpStatus) { + if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { + switch (rsp->un.ulpWord[4] & 0xff) { + case IOERR_SEQUENCE_TIMEOUT: + rc = -ETIMEDOUT; + break; + case IOERR_INVALID_RPI: + rc = -EFAULT; + break; + default: + rc = -EFAULT; + break; + } + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1217 mlo_issueIocb:2 off %d rc=%d " + "ulpWord[4] 0x%x\n", + (int)req->offset, rc, rsp->un.ulpWord[4]); + } + } +sysfs_menlo_issue_iocb_wait_exit: + return rc; +} + + +static ssize_t +sysfs_menlo_issue_iocb(struct lpfc_hba *phba, struct lpfc_menlo_genreq64 *req, + struct lpfc_sysfs_menlo *sysfs_menlo) +{ + struct lpfc_sli *psli = NULL; + struct lpfc_sli_ring *pring = NULL; + int rc = 0; + IOCB_t *rsp = NULL; + struct lpfc_iocbq *cmdiocbq = NULL; + + psli = &phba->sli; + pring = &psli->ring[LPFC_ELS_RING]; + rsp = &req->rspiocbq->iocb; + cmdiocbq = req->cmdiocbq; + cmdiocbq->context2 = sysfs_menlo; + cmdiocbq->iocb_cmpl = lpfc_menlo_iocb_cmpl; + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "1257 lpfc_menlo_issue_iocb: handler set for %p\n", + cmdiocbq->context3); + + rc = lpfc_sli_issue_iocb(phba, pring, req->cmdiocbq, 0); + + if (rc == IOCB_TIMEDOUT) { + + cmdiocbq->context2 = NULL; + cmdiocbq->context3 = sysfs_menlo; + cmdiocbq->iocb_cmpl = lpfc_menlo_iocb_timeout_cmpl; + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "1228 FCoE IOCB TMO: handler set for %p\n", + cmdiocbq->context3); + return -EACCES; + } + + if (rc != IOCB_SUCCESS) { + rc = -EFAULT; + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1238 FCoE IOCB failed: off %d rc=%d \n", + (int)req->offset, rc); + goto sysfs_menlo_issue_iocb_exit; + } + + if (rsp->ulpStatus) { + if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { + switch (rsp->un.ulpWord[4] & 0xff) { + case IOERR_SEQUENCE_TIMEOUT: + rc = -ETIMEDOUT; + break; + case IOERR_INVALID_RPI: + rc = -EFAULT; + break; + default: + rc = -EFAULT; + break; + } + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1239 mlo_issueIocb:2 off %d rc=%d " + "ulpWord[4] 0x%x\n", + (int)req->offset, rc, rsp->un.ulpWord[4]); + } + } +sysfs_menlo_issue_iocb_exit: + return rc; +} + +static ssize_t +lpfc_menlo_read(struct lpfc_hba *phba, char *buf, loff_t off, size_t count, + int wait) +{ + struct lpfc_sli *psli = NULL; + struct lpfc_sli_ring *pring = NULL; + int rc = 0; + struct lpfc_sysfs_menlo *sysfs_menlo; + struct lpfc_dmabufext *mlast = NULL; + loff_t temp_off = 0; + struct lpfc_menlo_genreq64 *genreq = NULL; + IOCB_t *cmd = NULL, *rsp = NULL; + uint32_t * uptr = NULL; + + + psli = &phba->sli; + pring = &psli->ring[LPFC_ELS_RING]; + + if (off > SYSFS_MENLO_ATTR_SIZE) + return -ERANGE; + + if ((count + off) > SYSFS_MENLO_ATTR_SIZE) + count = SYSFS_MENLO_ATTR_SIZE - off; + + if (off % 4 || count % 4 || (unsigned long)buf % 4) + return -EINVAL; + + if (off && count == 0) + return 0; + + sysfs_menlo = lpfc_get_sysfs_menlo(phba, 0); + + if (!sysfs_menlo) + return -EPERM; + + if (!(psli->sli_flag & LPFC_SLI2_ACTIVE)) { + sysfs_menlo_idle(phba, sysfs_menlo); + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1214 Can not issue FCoE cmd," + " SLI not active: off %d rc= -EACCESS\n", + (int)off); + return -EACCES; + } + + + if ((phba->link_state < LPFC_LINK_UP) + && !(psli->sli_flag & LPFC_MENLO_MAINT) + && wait) { + rc = -EPERM; + lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, + "1215 Can not issue FCoE cmd:" + " not ready or not in maint mode" + " off %d rc=%d \n", + (int)off, rc); + spin_lock_irq(&phba->hbalock); + goto lpfc_menlo_read_err_exit; + } + + if (off == 0 && sysfs_menlo->state == SMENLO_WRITING) { + if (sysfs_menlo->cmdhdr.cmd == MENLO_CMD_FW_DOWNLOAD) { + spin_lock_irq(&phba->hbalock); + genreq = &sysfs_menlo->cr; + spin_unlock_irq(&phba->hbalock); + } + if ( wait ) + rc = sysfs_menlo_issue_iocb_wait(phba, + &sysfs_menlo->cr, + sysfs_menlo); + else { + rc = sysfs_menlo_issue_iocb(phba, + &sysfs_menlo->cr, + sysfs_menlo); + return rc; + } + + spin_lock_irq(&phba->hbalock); + if (rc < 0) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "1224 FCoE iocb failed: off %d rc=%d \n", + (int)off, rc); + if (rc != -EACCES) + goto lpfc_menlo_read_err_exit; + else { + spin_unlock_irq(&phba->hbalock); + return rc; + } + } + + if (sysfs_menlo->cmdhdr.cmd == MENLO_CMD_FW_DOWNLOAD) { + cmd = &sysfs_menlo->cx.cmdiocbq->iocb; + rsp = &sysfs_menlo->cr.rspiocbq->iocb; + mlast = list_get_first(&sysfs_menlo->cr.outhead, + struct lpfc_dmabufext, + dma.list); + if ( *((uint32_t *) mlast->dma.virt) != 0 ) { + memcpy(buf,(uint8_t *) mlast->dma.virt, count); + goto lpfc_menlo_read_err_exit; + } + mlast = NULL; + + cmd->ulpCommand = CMD_GEN_REQUEST64_CX; + cmd->ulpContext = rsp->ulpContext; + cmd->ulpPU = 1; /* RelOffset */ + cmd->un.ulpWord[4] = 0; /* offset 0 */ + + spin_unlock_irq(&phba->hbalock); + rc = sysfs_menlo_issue_iocb_wait(phba, &sysfs_menlo->cx, + sysfs_menlo); + spin_lock_irq(&phba->hbalock); + if (rc < 0) { + uptr = (uint32_t *) rsp; + + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1225 menlo_read: off %d rc=%d " + "rspxri %d cmdxri %d \n", + (int)off, rc, rsp->ulpContext, + cmd->ulpContext); + uptr = (uint32_t *) + &sysfs_menlo->cr.cmdiocbq->iocb; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1236 cr:%.08x %.08x %.08x %.08x " + "%.08x %.08x %.08x %.08x %.08x\n", + *uptr, *(uptr+1), *(uptr+2), + *(uptr+3), *(uptr+4), *(uptr+5), + *(uptr+6), *(uptr+7), *(uptr+8)); + uptr = (uint32_t *)rsp; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1232 cr-rsp:%.08x %.08x %.08x %.08x " + "%.08x %.08x %.08x %.08x %.08x\n", + *uptr, *(uptr+1), *(uptr+2), + *(uptr+3), *(uptr+4), *(uptr+5), + *(uptr+6), *(uptr+7), *(uptr+8)); + uptr = (uint32_t *)cmd; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1233 cx:%.08x %.08x %.08x %.08x " + "%.08x %.08x %.08x %.08x %.08x\n", + *uptr, *(uptr+1), *(uptr+2), + *(uptr+3), *(uptr+4), *(uptr+5), + *(uptr+6), *(uptr+7), *(uptr+8)); + if (rc != -EACCES) + goto lpfc_menlo_read_err_exit; + else { + spin_unlock_irq(&phba->hbalock); + return rc; + } + } + } + sysfs_menlo->state = SMENLO_READING; + sysfs_menlo->cr.offset = 0; + + } else + spin_lock_irq(&phba->hbalock); + + if (sysfs_menlo->cmdhdr.cmd == MENLO_CMD_FW_DOWNLOAD) + genreq = &sysfs_menlo->cx; + else + genreq = &sysfs_menlo->cr; + + /* Copy back response data */ + if (sysfs_menlo->cmdhdr.rspsize > count) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1218 MloMgnt Rqst err Data: x%x %d %d %d %d\n", + genreq->outdmp->flag, + sysfs_menlo->cmdhdr.rspsize, + (int)count, (int)off, (int)genreq->offset); + } + + if (phba->sli.sli_flag & LPFC_BLOCK_MGMT_IO) { + rc = -EAGAIN; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1219 menlo_read:4 off %d rc=%d \n", + (int)off, rc); + goto lpfc_menlo_read_err_exit; + } + else if ( sysfs_menlo->state != SMENLO_READING) { + rc = -EAGAIN; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1220 menlo_read:5 off %d reg off %d rc=%d state %x\n", + (int)off,(int)genreq->offset, sysfs_menlo->state, rc); + goto lpfc_menlo_read_err_exit; + } + temp_off = 0; + mlast = NULL; + list_for_each_entry(mlast, &genreq->outhead, dma.list) { + if (temp_off == off) + break; + else + temp_off += BUF_SZ_4K; + } + if (mlast) + memcpy(buf,(uint8_t *) mlast->dma.virt, count); + else { + rc = -ERANGE; + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1221 menlo_read:6 off %d rc=%d \n", + (int)off, rc); + goto lpfc_menlo_read_err_exit; + } + genreq->offset += count; + + + if (genreq->offset >= sysfs_menlo->cmdhdr.rspsize) { + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, + "1222 menlo_read: done off %d rc=%d" + " cnt %d rsp_code %x\n", + (int)off, rc, (int)count,*((uint32_t *)buf)); + rc = count; + goto lpfc_menlo_read_err_exit; + } + + if (count >= sysfs_menlo->cmdhdr.rspsize) + rc = sysfs_menlo->cmdhdr.rspsize; + else /* Can there be a > 4k response */ + rc = count; + if (genreq->offset < sysfs_menlo->cmdhdr.rspsize) { + spin_unlock_irq(&phba->hbalock); + return rc; + } + +lpfc_menlo_read_err_exit: + spin_unlock_irq(&phba->hbalock); + sysfs_menlo_idle(phba,sysfs_menlo); + return rc; +} + + +static ssize_t +sysfs_menlo_read(struct kobject *kobj, struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + struct lpfc_hba *phba = vport->phba; + + return lpfc_menlo_read(phba, buf, off, count, 1); +} +int need_non_blocking = 0; +void lpfc_check_menlo_cfg(struct lpfc_hba *phba) +{ + uint32_t cmd_size; + uint32_t rsp_size; + menlo_get_cmd_t *cmd = NULL; + menlo_init_rsp_t *rsp = NULL; + int rc = 0; + + lpfc_printf_log (phba, KERN_INFO, LOG_LINK_EVENT, + "1253 Checking FCoE chip firmware.\n"); + if ( need_non_blocking ) /* Need non blocking issue_iocb */ + return; + + cmd_size = sizeof (menlo_get_cmd_t); + cmd = kmalloc(cmd_size, GFP_KERNEL); + if (!cmd ) { + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1240 Unable to allocate command buffer memory.\n"); + return; + } + + rsp_size = sizeof (menlo_init_rsp_t); + rsp = kmalloc(rsp_size, GFP_KERNEL); + if (!rsp ) { + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1244 Unable to allocate response buffer memory.\n"); + kfree(rsp); + return; + } + + memset(cmd,0, cmd_size); + memset(rsp,0, rsp_size); + + cmd->code = MENLO_CMD_GET_INIT; + cmd->context = cmd_size; + cmd->length = rsp_size; + rc = lpfc_menlo_write (phba, (char *) cmd, 0, cmd_size); + if ( rc != cmd_size ) { + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1250 Menlo command error. code=%d.\n", rc); + + kfree (cmd); + kfree (rsp); + return; + } + cmd->code = MENLO_CMD_GET_INIT; + cmd->context = 0; + cmd->length = rsp_size; + BE_swap32_buffer ((uint8_t *) cmd, cmd_size); + rc = lpfc_menlo_write (phba, (char *) cmd, cmd_size, cmd_size); + if ( rc != cmd_size ) { + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1251 Menlo command error. code=%d.\n", rc); + + kfree (cmd); + kfree (rsp); + return; + } + rc = lpfc_menlo_read (phba, (char *) rsp, 0, rsp_size,0); + if ( rc && rc != rsp_size ) { + lpfc_printf_log (phba, KERN_ERR, LOG_LINK_EVENT, + "1252 Menlo command error. code=%d.\n", rc); + + } + kfree (cmd); + kfree (rsp); + return; +} + +struct bin_attribute sysfs_menlo_attr = { + .attr = { + .name = "menlo", + .mode = S_IRUSR | S_IWUSR, + .owner = THIS_MODULE, + }, + .size = SYSFS_MENLO_ATTR_SIZE, + .read = sysfs_menlo_read, + .write = sysfs_menlo_write, +}; --- a/drivers/scsi/lpfc/lpfc_scsi.c +++ b/drivers/scsi/lpfc/lpfc_scsi.c @@ -42,7 +42,6 @@ #define LPFC_RESET_WAIT 2 #define LPFC_ABORT_WAIT 2 - /** * lpfc_update_stats: Update statistical data for the command completion. * @phba: Pointer to HBA object. @@ -336,6 +335,22 @@ lpfc_scsi_dev_block(struct lpfc_hba *phb lpfc_destroy_vport_work_array(phba, vports); } +void +lpfc_scsi_dev_rescan(struct lpfc_hba *phba) +{ + struct lpfc_vport **vports; + struct Scsi_Host *shost; + int i; + + vports = lpfc_create_vport_work_array(phba); + if (vports != NULL) + for (i = 0; i <= phba->max_vpi && vports[i] != NULL; i++) { + shost = lpfc_shost_from_vport(vports[i]); + scsi_scan_host(shost); + } + lpfc_destroy_vport_work_array(phba, vports); +} + /* * This routine allocates a scsi buffer, which contains all the necessary * information needed to initiate a SCSI I/O. The non-DMAable buffer region @@ -1841,22 +1856,3 @@ struct scsi_host_template lpfc_template .max_sectors = 0xFFFF, }; -struct scsi_host_template lpfc_vport_template = { - .module = THIS_MODULE, - .name = LPFC_DRIVER_NAME, - .info = lpfc_info, - .queuecommand = lpfc_queuecommand, - .eh_abort_handler = lpfc_abort_handler, - .eh_device_reset_handler= lpfc_device_reset_handler, - .eh_bus_reset_handler = lpfc_bus_reset_handler, - .slave_alloc = lpfc_slave_alloc, - .slave_configure = lpfc_slave_configure, - .slave_destroy = lpfc_slave_destroy, - .scan_finished = lpfc_scan_finished, - .this_id = -1, - .sg_tablesize = LPFC_DEFAULT_SG_SEG_CNT, - .cmd_per_lun = LPFC_CMD_PER_LUN, - .use_clustering = ENABLE_CLUSTERING, - .shost_attrs = lpfc_vport_attrs, - .max_sectors = 0xFFFF, -}; --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_security.c @@ -0,0 +1,339 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2006-2008 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.emulex.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ + +#include +#include +#include + +#include +#include + +#include "lpfc_hw.h" +#include "lpfc_sli.h" +#include "lpfc_nl.h" +#include "lpfc_disc.h" +#include "lpfc.h" +#include "lpfc_crtn.h" +#include "lpfc_logmsg.h" +#include "lpfc_security.h" +#include "lpfc_auth_access.h" +#include "lpfc_vport.h" + +uint8_t lpfc_security_service_state = SECURITY_OFFLINE; + +void +lpfc_security_service_online(struct Scsi_Host *shost) +{ + struct lpfc_vport *vport = (struct lpfc_vport *)shost->hostdata; + + lpfc_security_service_state = SECURITY_ONLINE; + if (vport->cfg_enable_auth && + vport->auth.auth_mode == FC_AUTHMODE_UNKNOWN && + vport->phba->link_state == LPFC_HBA_ERROR) + lpfc_selective_reset(vport->phba); +} + +void +lpfc_security_service_offline(struct Scsi_Host *shost) +{ + lpfc_security_service_state = SECURITY_OFFLINE; +} + +void +lpfc_security_config(struct Scsi_Host *shost, int status, void *rsp) +{ + struct fc_auth_rsp *auth_rsp = (struct fc_auth_rsp *)rsp; + struct lpfc_vport *vport = (struct lpfc_vport *)shost->hostdata; + struct lpfc_nodelist *ndlp; + uint32_t old_interval, new_interval; + unsigned long new_jiffies, temp_jiffies; + uint8_t last_auth_mode; + + if (status) + return; + ndlp = lpfc_findnode_did(vport, Fabric_DID); + if (!ndlp || !NLP_CHK_NODE_ACT(ndlp)) + return; + + vport->auth.bidirectional = + auth_rsp->u.dhchap_security_config.bidirectional; + memcpy(&vport->auth.hash_priority[0], + &auth_rsp->u.dhchap_security_config.hash_priority[0], + sizeof(vport->auth.hash_priority)); + vport->auth.hash_len = auth_rsp->u.dhchap_security_config.hash_len; + memcpy(&vport->auth.dh_group_priority[0], + &auth_rsp->u.dhchap_security_config. + dh_group_priority[0], + sizeof(vport->auth.dh_group_priority)); + vport->auth.dh_group_len = + auth_rsp->u.dhchap_security_config.dh_group_len; + old_interval = vport->auth.reauth_interval; + vport->auth.reauth_interval = + auth_rsp->u.dhchap_security_config.reauth_interval; + new_interval = vport->auth.reauth_interval; + /* + * If interval changed we need to adjust the running timer + * If enabled then start timer now. + * If disabled then stop the timer. + * If changed to chorter then elapsed time, then set to fire now + * If changed to longer than elapsed time, extend the timer. + */ + if (old_interval != new_interval && + vport->auth.auth_state == LPFC_AUTH_SUCCESS) { + new_jiffies = msecs_to_jiffies(new_interval * 60000); + del_timer_sync(&ndlp->nlp_reauth_tmr); + if (old_interval == 0) + temp_jiffies = jiffies + new_jiffies; + if (new_interval == 0) + temp_jiffies = 0; + else if (new_jiffies < (jiffies - vport->auth.last_auth)) + temp_jiffies = jiffies + msecs_to_jiffies(1); + else + temp_jiffies = jiffies + (new_jiffies - + (jiffies - vport->auth.last_auth)); + if (temp_jiffies) + mod_timer(&ndlp->nlp_reauth_tmr, temp_jiffies); + } + last_auth_mode = vport->auth.auth_mode; + vport->auth.auth_mode = + auth_rsp->u.dhchap_security_config.auth_mode; + lpfc_printf_vlog(vport, KERN_INFO, LOG_SECURITY, + "1025 Received security config local_wwpn:" + "%llX remote_wwpn:%llX \nmode:0x%x " + "hash(%d):%x:%x:%x:%x bidir:0x%x " + "dh_group(%d):%x:%x:%x:%x:%x:%x:%x:%x " + "reauth_interval:0x%x\n", + (unsigned long long)auth_rsp->local_wwpn, + (unsigned long long)auth_rsp->remote_wwpn, + auth_rsp->u.dhchap_security_config.auth_mode, + auth_rsp->u.dhchap_security_config.hash_len, + auth_rsp->u.dhchap_security_config.hash_priority[0], + auth_rsp->u.dhchap_security_config.hash_priority[1], + auth_rsp->u.dhchap_security_config.hash_priority[2], + auth_rsp->u.dhchap_security_config.hash_priority[3], + auth_rsp->u.dhchap_security_config.bidirectional, + auth_rsp->u.dhchap_security_config.dh_group_len, + auth_rsp->u.dhchap_security_config.dh_group_priority[0], + auth_rsp->u.dhchap_security_config.dh_group_priority[1], + auth_rsp->u.dhchap_security_config.dh_group_priority[2], + auth_rsp->u.dhchap_security_config.dh_group_priority[3], + auth_rsp->u.dhchap_security_config.dh_group_priority[4], + auth_rsp->u.dhchap_security_config.dh_group_priority[5], + auth_rsp->u.dhchap_security_config.dh_group_priority[6], + auth_rsp->u.dhchap_security_config.dh_group_priority[7], + auth_rsp->u.dhchap_security_config.reauth_interval); + kfree(auth_rsp); + if (vport->auth.auth_mode == FC_AUTHMODE_ACTIVE) + vport->auth.security_active = 1; + else if (vport->auth.auth_mode == FC_AUTHMODE_PASSIVE) { + if (ndlp->nlp_flag & NLP_SC_REQ) + vport->auth.security_active = 1; + else { + lpfc_printf_vlog(vport, KERN_INFO, LOG_SECURITY, + "1038 Authentication not " + "required by the fabric. " + "Disabled.\n"); + vport->auth.security_active = 0; + } + } else { + vport->auth.security_active = 0; + /* + * If switch require authentication and authentication + * is disabled for this HBA/Fabric port, fail the + * discovery. + */ + if (ndlp->nlp_flag & NLP_SC_REQ) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1050 Authentication mode is " + "disabled, but is required by " + "the fabric.\n"); + lpfc_vport_set_state(vport, FC_VPORT_FAILED); + /* Cancel discovery timer */ + lpfc_can_disctmo(vport); + } + } + if (last_auth_mode == FC_AUTHMODE_UNKNOWN) { + if (vport->auth.security_active) + lpfc_start_authentication(vport, ndlp); + else + lpfc_start_discovery(vport); + } +} + +int +lpfc_get_security_enabled(struct Scsi_Host *shost) +{ + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + + return(vport->cfg_enable_auth); +} + +int +lpfc_security_wait(struct lpfc_hba *phba) +{ + int i = 0; + if (lpfc_security_service_state == SECURITY_ONLINE) + return 0; + lpfc_printf_log(phba, KERN_WARNING, LOG_SECURITY, + "1058 Waiting for authentication service...\n"); + while (lpfc_security_service_state == SECURITY_OFFLINE) { + i++; + if (i > SECURITY_WAIT_TMO * 2) + return -ETIMEDOUT; + /* Delay for half of a second */ + msleep(500); + } + lpfc_printf_log(phba, KERN_WARNING, LOG_SECURITY, + "1059 Authentication service online.\n"); + return 0; +} + +int +lpfc_security_config_wait(struct lpfc_vport *vport) +{ + int i = 0; + + while (vport->auth.auth_mode == FC_AUTHMODE_UNKNOWN) { + i++; + if (i > 120) { + return -ETIMEDOUT; + } + /* Delay for half of a second */ + msleep(500); + } + return 0; +} + +void +lpfc_reauth_node(unsigned long ptr) +{ + struct lpfc_nodelist *ndlp = (struct lpfc_nodelist *) ptr; + struct lpfc_vport *vport = ndlp->vport; + struct lpfc_hba *phba = vport->phba; + unsigned long flags; + struct lpfc_work_evt *evtp = &ndlp->els_reauth_evt; + + ndlp = (struct lpfc_nodelist *) ptr; + phba = ndlp->vport->phba; + + spin_lock_irqsave(&phba->hbalock, flags); + if (!list_empty(&evtp->evt_listp)) { + spin_unlock_irqrestore(&phba->hbalock, flags); + return; + } + + /* We need to hold the node resource by incrementing the reference + * count until this queued work is done + */ + evtp->evt_arg1 = lpfc_nlp_get(ndlp); + if (evtp->evt_arg1) { + evtp->evt = LPFC_EVT_REAUTH; + list_add_tail(&evtp->evt_listp, &phba->work_list); + lpfc_worker_wake_up(phba); + } + spin_unlock_irqrestore(&phba->hbalock, flags); + return; +} + +void +lpfc_reauthentication_handler(struct lpfc_nodelist *ndlp) +{ + struct lpfc_vport *vport = ndlp->vport; + if (vport->auth.auth_msg_state != LPFC_DHCHAP_SUCCESS) + return; + + if (lpfc_start_node_authentication(ndlp)) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1029 Reauthentication Failure\n"); + if (vport->auth.auth_state == LPFC_AUTH_SUCCESS) + lpfc_port_auth_failed(ndlp); + } +} + +/* + * This function will kick start authentication for a node. + * This is used for re-authentication of a node or a user + * initiated node authentication. + */ +int +lpfc_start_node_authentication(struct lpfc_nodelist *ndlp) +{ + struct lpfc_vport *vport; + int ret; + + vport = ndlp->vport; + /* If there is authentication timer cancel the timer */ + del_timer_sync(&ndlp->nlp_reauth_tmr); + ret = lpfc_get_auth_config(ndlp, &ndlp->nlp_portname); + if (ret) + return ret; + ret = lpfc_security_config_wait(vport); + if (ret) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1032 Start Authentication: get config " + "timed out.\n"); + return ret; + } + return 0; +} + +int +lpfc_get_auth_config(struct lpfc_nodelist *ndlp, struct lpfc_name *rwwn) +{ + struct lpfc_vport *vport; + struct fc_auth_req auth_req; + struct fc_auth_rsp *auth_rsp; + struct Scsi_Host *shost; + int ret; + + vport = ndlp->vport; + shost = lpfc_shost_from_vport(vport); + + auth_req.local_wwpn = wwn_to_u64(vport->fc_portname.u.wwn); + if (ndlp->nlp_type & NLP_FABRIC) + auth_req.remote_wwpn = AUTH_FABRIC_WWN; + else + auth_req.remote_wwpn = wwn_to_u64(rwwn->u.wwn); + if (lpfc_security_service_state == SECURITY_OFFLINE) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1053 Start Authentication: " + "Security service offline.\n"); + return -EINVAL; + } + auth_rsp = kmalloc(sizeof(struct fc_auth_rsp), GFP_KERNEL); + if (!auth_rsp) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1028 Start Authentication: No buffers\n"); + return -ENOMEM; + } + vport->auth.auth_mode = FC_AUTHMODE_UNKNOWN; + ret = lpfc_fc_security_get_config(shost, &auth_req, + sizeof(struct fc_auth_req), + auth_rsp, + sizeof(struct fc_auth_rsp)); + if (ret) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_SECURITY, + "1031 Start Authentication: Get config " + "failed.\n"); + kfree(auth_rsp); + return ret; + } + return 0; +} --- /dev/null +++ b/drivers/scsi/lpfc/lpfc_security.h @@ -0,0 +1,24 @@ +/******************************************************************* + * This file is part of the Emulex Linux Device Driver for * + * Fibre Channel Host Bus Adapters. * + * Copyright (C) 2006-2007 Emulex. All rights reserved. * + * EMULEX and SLI are trademarks of Emulex. * + * www.emulex.com * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of version 2 of the GNU General * + * Public License as published by the Free Software Foundation. * + * This program is distributed in the hope that it will be useful. * + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE * + * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD * + * TO BE LEGALLY INVALID. See the GNU General Public License for * + * more details, a copy of which can be found in the file COPYING * + * included with this package. * + *******************************************************************/ + +#define SECURITY_OFFLINE 0x0 +#define SECURITY_ONLINE 0x1 + +#define SECURITY_WAIT_TMO 30 /* seconds to wait for the auth service */ --- a/drivers/scsi/lpfc/lpfc_sli.c +++ b/drivers/scsi/lpfc/lpfc_sli.c @@ -796,7 +796,7 @@ lpfc_sli_hbq_to_firmware(struct lpfc_hba hbqe->bde.addrHigh = le32_to_cpu(putPaddrHigh(physaddr)); hbqe->bde.addrLow = le32_to_cpu(putPaddrLow(physaddr)); hbqe->bde.tus.f.bdeSize = hbq_buf->size; - hbqe->bde.tus.f.bdeFlags = 0; + hbqe->bde.tus.f.bdeFlags = BUFF_TYPE_BDE_64; hbqe->bde.tus.w = le32_to_cpu(hbqe->bde.tus.w); hbqe->buffer_tag = le32_to_cpu(hbq_buf->tag); /* Sync SLIM */ @@ -1051,6 +1051,9 @@ lpfc_sli_chk_mbx_command(uint8_t mbxComm case MBX_REG_VPI: case MBX_UNREG_VPI: case MBX_HEARTBEAT: + case MBX_READ_EVENT_LOG_STATUS: + case MBX_READ_EVENT_LOG: + case MBX_WRITE_EVENT_LOG: case MBX_PORT_CAPABILITIES: case MBX_PORT_IOV_CONTROL: ret = mbxCommand; @@ -3546,9 +3549,35 @@ lpfc_sli_issue_mbox(struct lpfc_hba *phb mb->mbxOwner = OWN_CHIP; if (psli->sli_flag & LPFC_SLI2_ACTIVE) { - /* First copy command data to host SLIM area */ + /* Populate mbox extension offset word. */ + if (pmbox->in_ext_byte_len || pmbox->out_ext_byte_len) { + *(((uint32_t *)mb) + pmbox->mbox_offset_word) + = (uint8_t *)phba->mbox_ext + - (uint8_t *)phba->mbox; + } + + /* Copy the mailbox extension data */ + if (pmbox->in_ext_byte_len && pmbox->context2) { + lpfc_sli_pcimem_bcopy(pmbox->context2, + (uint8_t*)phba->mbox_ext, + pmbox->in_ext_byte_len); + } + /* Copy command data to host SLIM area */ lpfc_sli_pcimem_bcopy(mb, phba->mbox, MAILBOX_CMD_SIZE); + } else { + /* Populate mbox extension offset word. */ + if (pmbox->in_ext_byte_len || pmbox->out_ext_byte_len) + *(((uint32_t *)mb) + pmbox->mbox_offset_word) + = MAILBOX_HBA_EXT_OFFSET; + + /* Copy the mailbox extension data */ + if (pmbox->in_ext_byte_len && pmbox->context2) { + lpfc_memcpy_to_slim(phba->MBslimaddr + + MAILBOX_HBA_EXT_OFFSET, + pmbox->context2, pmbox->in_ext_byte_len); + + } if (mb->mbxCommand == MBX_CONFIG_PORT) { /* copy command data into host mbox for cmpl */ lpfc_sli_pcimem_bcopy(mb, phba->mbox, MAILBOX_CMD_SIZE); @@ -3658,15 +3687,22 @@ lpfc_sli_issue_mbox(struct lpfc_hba *phb if (psli->sli_flag & LPFC_SLI2_ACTIVE) { /* copy results back to user */ lpfc_sli_pcimem_bcopy(phba->mbox, mb, MAILBOX_CMD_SIZE); + /* Copy the mailbox extension data */ + if (pmbox->out_ext_byte_len && pmbox->context2) { + lpfc_sli_pcimem_bcopy(phba->mbox_ext, + pmbox->context2, + pmbox->out_ext_byte_len); + } } else { /* First copy command data */ lpfc_memcpy_from_slim(mb, phba->MBslimaddr, MAILBOX_CMD_SIZE); - if ((mb->mbxCommand == MBX_DUMP_MEMORY) && - pmbox->context2) { - lpfc_memcpy_from_slim((void *)pmbox->context2, - phba->MBslimaddr + DMP_RSP_OFFSET, - mb->un.varDmp.word_cnt); + /* Copy the mailbox extension data */ + if (pmbox->out_ext_byte_len && pmbox->context2) { + lpfc_memcpy_from_slim(pmbox->context2, + phba->MBslimaddr + + MAILBOX_HBA_EXT_OFFSET, + pmbox->out_ext_byte_len); } } @@ -5395,6 +5431,15 @@ lpfc_sp_intr_handler(int irq, void *dev_ if (pmb->mbox_cmpl) { lpfc_sli_pcimem_bcopy(mbox, pmbox, MAILBOX_CMD_SIZE); + /* Copy the mailbox extension data */ + if (pmb->out_ext_byte_len && + pmb->context2) { + lpfc_sli_pcimem_bcopy( + phba->mbox_ext, + pmb->context2, + pmb->out_ext_byte_len); + } + } if (pmb->mbox_flag & LPFC_MBX_IMED_UNREG) { pmb->mbox_flag &= ~LPFC_MBX_IMED_UNREG; --- a/drivers/scsi/lpfc/lpfc_sli.h +++ b/drivers/scsi/lpfc/lpfc_sli.h @@ -88,6 +88,9 @@ typedef struct lpfcMboxq { void (*mbox_cmpl) (struct lpfc_hba *, struct lpfcMboxq *); uint8_t mbox_flag; + uint16_t in_ext_byte_len; + uint16_t out_ext_byte_len; + uint8_t mbox_offset_word; } LPFC_MBOXQ_t; --- a/drivers/scsi/lpfc/lpfc_version.h +++ b/drivers/scsi/lpfc/lpfc_version.h @@ -18,7 +18,7 @@ * included with this package. * *******************************************************************/ -#define LPFC_DRIVER_VERSION "8.2.8" +#define LPFC_DRIVER_VERSION "8.2.8.1" #define LPFC_DRIVER_NAME "lpfc" #define LPFC_SP_DRIVER_HANDLER_NAME "lpfc:sp" --- a/drivers/scsi/lpfc/lpfc_vport.c +++ b/drivers/scsi/lpfc/lpfc_vport.c @@ -42,6 +42,7 @@ #include "lpfc_crtn.h" #include "lpfc_version.h" #include "lpfc_vport.h" +#include "lpfc_auth_access.h" inline void lpfc_vport_set_state(struct lpfc_vport *vport, enum fc_vport_state new_state) @@ -394,6 +395,21 @@ lpfc_vport_create(struct fc_vport *fc_vp goto error_out; } + shost = lpfc_shost_from_vport(vport); + + if ((lpfc_get_security_enabled)(shost)) { + spin_lock_irq(&fc_security_user_lock); + + list_add_tail(&vport->sc_users, &fc_security_user_list); + + spin_unlock_irq(&fc_security_user_lock); + + if (fc_service_state == FC_SC_SERVICESTATE_ONLINE) { + lpfc_fc_queue_security_work(vport, + &vport->sc_online_work); + } + } + *(struct lpfc_vport **)fc_vport->dd_data = vport; vport->fc_vport = fc_vport; --- a/drivers/scsi/lpfc/Makefile +++ b/drivers/scsi/lpfc/Makefile @@ -1,7 +1,7 @@ #/******************************************************************* # * This file is part of the Emulex Linux Device Driver for * # * Fibre Channel Host Bus Adapters. * -# * Copyright (C) 2004-2006 Emulex. All rights reserved. * +# * Copyright (C) 2004-2008 Emulex. All rights reserved. * # * EMULEX and SLI are trademarks of Emulex. * # * www.emulex.com * # * * @@ -28,4 +28,5 @@ obj-$(CONFIG_SCSI_LPFC) := lpfc.o lpfc-objs := lpfc_mem.o lpfc_sli.o lpfc_ct.o lpfc_els.o lpfc_hbadisc.o \ lpfc_init.o lpfc_mbox.o lpfc_nportdisc.o lpfc_scsi.o lpfc_attr.o \ - lpfc_vport.o lpfc_debugfs.o + lpfc_vport.o lpfc_debugfs.o lpfc_security.o lpfc_auth_access.o \ + lpfc_auth.o lpfc_ioctl.o lpfc_menlo.o