875fbdfe9b
- Add functionality to run in polled mode only. Includes run time attribute to enable mode. - Enable runtime writable hba settings for coallescing and delay parameters Customers have requested a mode in the driver to run strictly polled. This is generally to support an environment where the server is extremely loaded and is looking to reclaim some cpu cycles from adapter interrupt handling. This patch adds a new "poll" attribute, and the following behavior: if value is 0 (default): The driver uses the normal method for i/o completion. It uses the firmware feature of interrupt coalesing. The firmware allows a minimum number of i/o completions before an interrupt, or a maximum time delay between interrupts. By default, the driver sets these to no delay (disabled) or 1 i/o - meaning coalescing is disabled. Attributes were provided to change the coalescing values, but it was a module-load time only and global across all adapters. This patch allows them to be writable on a per-adapter basis. if value is 1 : Interrupts are left enabled, expecting that the user has tuned the interrupt coalescing values. When this setting is enabled, the driver will attempt to service completed i/o whenever new i/o is submitted to the adapter. If the coalescing values are large, and the i/o generation rate steady, an interrupt will be avoided by servicing completed i/o prior to the coalescing thresholds kicking in. However, if the i/o completion load is high enough or i/o generation slow, the coalescion values will ensure that completed i/o is serviced in a timely fashion. if value is 3 : Turns off FCP i/o interrupts altogether. The coalescing values now have no effect. A new attribute "poll_tmo" (default 10ms) exists to set the polling interval for i/o completion. When this setting is enabled, the driver will attempt to service completed i/o and restart the interval timer whenever new i/o is submitted. This behavior allows for servicing of completed i/o sooner than the interval timer, but ensures that if no i/o is being issued, then the interval timer will kick in to service the outstanding i/o. Signed-off-by: James Smart <James.Smart@emulex.com> Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
1415 lines
40 KiB
C
1415 lines
40 KiB
C
/*******************************************************************
|
|
* This file is part of the Emulex Linux Device Driver for *
|
|
* Fibre Channel Host Bus Adapters. *
|
|
* Copyright (C) 2004-2005 Emulex. All rights reserved. *
|
|
* EMULEX and SLI are trademarks of Emulex. *
|
|
* www.emulex.com *
|
|
* Portions Copyright (C) 2004-2005 Christoph Hellwig *
|
|
* *
|
|
* 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 <linux/ctype.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <scsi/scsi.h>
|
|
#include <scsi/scsi_device.h>
|
|
#include <scsi/scsi_host.h>
|
|
#include <scsi/scsi_tcq.h>
|
|
#include <scsi/scsi_transport_fc.h>
|
|
|
|
#include "lpfc_hw.h"
|
|
#include "lpfc_sli.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"
|
|
|
|
|
|
static void
|
|
lpfc_jedec_to_ascii(int incr, char hdw[])
|
|
{
|
|
int i, j;
|
|
for (i = 0; i < 8; i++) {
|
|
j = (incr & 0xf);
|
|
if (j <= 9)
|
|
hdw[7 - i] = 0x30 + j;
|
|
else
|
|
hdw[7 - i] = 0x61 + j - 10;
|
|
incr = (incr >> 4);
|
|
}
|
|
hdw[8] = 0;
|
|
return;
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_drvr_version_show(struct class_device *cdev, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, LPFC_MODULE_DESC "\n");
|
|
}
|
|
|
|
static ssize_t
|
|
management_version_show(struct class_device *cdev, char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, DFC_API_VERSION "\n");
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_info_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",lpfc_info(host));
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_serialnum_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",phba->SerialNumber);
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_modeldesc_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",phba->ModelDesc);
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_modelname_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",phba->ModelName);
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_programtype_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",phba->ProgramType);
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_portnum_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",phba->Port);
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_fwrev_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
char fwrev[32];
|
|
lpfc_decode_firmware_rev(phba, fwrev, 1);
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",fwrev);
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_hdw_show(struct class_device *cdev, char *buf)
|
|
{
|
|
char hdw[9];
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
lpfc_vpd_t *vp = &phba->vpd;
|
|
lpfc_jedec_to_ascii(vp->rev.biuRev, hdw);
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", hdw);
|
|
}
|
|
static ssize_t
|
|
lpfc_option_rom_version_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", phba->OptionROMVersion);
|
|
}
|
|
static ssize_t
|
|
lpfc_state_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
int len = 0;
|
|
switch (phba->hba_state) {
|
|
case LPFC_INIT_START:
|
|
case LPFC_INIT_MBX_CMDS:
|
|
case LPFC_LINK_DOWN:
|
|
len += snprintf(buf + len, PAGE_SIZE-len, "Link Down\n");
|
|
break;
|
|
case LPFC_LINK_UP:
|
|
case LPFC_LOCAL_CFG_LINK:
|
|
len += snprintf(buf + len, PAGE_SIZE-len, "Link Up\n");
|
|
break;
|
|
case LPFC_FLOGI:
|
|
case LPFC_FABRIC_CFG_LINK:
|
|
case LPFC_NS_REG:
|
|
case LPFC_NS_QRY:
|
|
case LPFC_BUILD_DISC_LIST:
|
|
case LPFC_DISC_AUTH:
|
|
case LPFC_CLEAR_LA:
|
|
len += snprintf(buf + len, PAGE_SIZE-len,
|
|
"Link Up - Discovery\n");
|
|
break;
|
|
case LPFC_HBA_READY:
|
|
len += snprintf(buf + len, PAGE_SIZE-len,
|
|
"Link Up - Ready:\n");
|
|
if (phba->fc_topology == TOPOLOGY_LOOP) {
|
|
if (phba->fc_flag & FC_PUBLIC_LOOP)
|
|
len += snprintf(buf + len, PAGE_SIZE-len,
|
|
" Public Loop\n");
|
|
else
|
|
len += snprintf(buf + len, PAGE_SIZE-len,
|
|
" Private Loop\n");
|
|
} else {
|
|
if (phba->fc_flag & FC_FABRIC)
|
|
len += snprintf(buf + len, PAGE_SIZE-len,
|
|
" Fabric\n");
|
|
else
|
|
len += snprintf(buf + len, PAGE_SIZE-len,
|
|
" Point-2-Point\n");
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_num_discovered_ports_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", phba->fc_map_cnt +
|
|
phba->fc_unmap_cnt);
|
|
}
|
|
|
|
|
|
static int
|
|
lpfc_issue_lip(struct Scsi_Host *host)
|
|
{
|
|
struct lpfc_hba *phba = (struct lpfc_hba *) host->hostdata[0];
|
|
LPFC_MBOXQ_t *pmboxq;
|
|
int mbxstatus = MBXERR_ERROR;
|
|
|
|
if ((phba->fc_flag & FC_OFFLINE_MODE) ||
|
|
(phba->hba_state != LPFC_HBA_READY))
|
|
return -EPERM;
|
|
|
|
pmboxq = mempool_alloc(phba->mbox_mem_pool,GFP_KERNEL);
|
|
|
|
if (!pmboxq)
|
|
return -ENOMEM;
|
|
|
|
memset((void *)pmboxq, 0, sizeof (LPFC_MBOXQ_t));
|
|
lpfc_init_link(phba, pmboxq, phba->cfg_topology, phba->cfg_link_speed);
|
|
mbxstatus = lpfc_sli_issue_mbox_wait(phba, pmboxq, phba->fc_ratov * 2);
|
|
|
|
if (mbxstatus == MBX_TIMEOUT)
|
|
pmboxq->mbox_cmpl = lpfc_sli_def_mbox_cmpl;
|
|
else
|
|
mempool_free(pmboxq, phba->mbox_mem_pool);
|
|
|
|
if (mbxstatus == MBXERR_ERROR)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_nport_evt_cnt_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", phba->nport_event_cnt);
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_board_online_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
|
|
if (phba->fc_flag & FC_OFFLINE_MODE)
|
|
return snprintf(buf, PAGE_SIZE, "0\n");
|
|
else
|
|
return snprintf(buf, PAGE_SIZE, "1\n");
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_board_online_store(struct class_device *cdev, const char *buf,
|
|
size_t count)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
struct completion online_compl;
|
|
int val=0, status=0;
|
|
|
|
if (sscanf(buf, "%d", &val) != 1)
|
|
return -EINVAL;
|
|
|
|
init_completion(&online_compl);
|
|
|
|
if (val)
|
|
lpfc_workq_post_event(phba, &status, &online_compl,
|
|
LPFC_EVT_ONLINE);
|
|
else
|
|
lpfc_workq_post_event(phba, &status, &online_compl,
|
|
LPFC_EVT_OFFLINE);
|
|
wait_for_completion(&online_compl);
|
|
if (!status)
|
|
return strlen(buf);
|
|
else
|
|
return -EIO;
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_poll_show(struct class_device *cdev, char *buf)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%#x\n", phba->cfg_poll);
|
|
}
|
|
|
|
static ssize_t
|
|
lpfc_poll_store(struct class_device *cdev, const char *buf,
|
|
size_t count)
|
|
{
|
|
struct Scsi_Host *host = class_to_shost(cdev);
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
uint32_t creg_val;
|
|
uint32_t old_val;
|
|
int val=0;
|
|
|
|
if (!isdigit(buf[0]))
|
|
return -EINVAL;
|
|
|
|
if (sscanf(buf, "%i", &val) != 1)
|
|
return -EINVAL;
|
|
|
|
if ((val & 0x3) != val)
|
|
return -EINVAL;
|
|
|
|
spin_lock_irq(phba->host->host_lock);
|
|
|
|
old_val = phba->cfg_poll;
|
|
|
|
if (val & ENABLE_FCP_RING_POLLING) {
|
|
if ((val & DISABLE_FCP_RING_INT) &&
|
|
!(old_val & DISABLE_FCP_RING_INT)) {
|
|
creg_val = readl(phba->HCregaddr);
|
|
creg_val &= ~(HC_R0INT_ENA << LPFC_FCP_RING);
|
|
writel(creg_val, phba->HCregaddr);
|
|
readl(phba->HCregaddr); /* flush */
|
|
|
|
lpfc_poll_start_timer(phba);
|
|
}
|
|
} else if (val != 0x0) {
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!(val & DISABLE_FCP_RING_INT) &&
|
|
(old_val & DISABLE_FCP_RING_INT))
|
|
{
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
del_timer(&phba->fcp_poll_timer);
|
|
spin_lock_irq(phba->host->host_lock);
|
|
creg_val = readl(phba->HCregaddr);
|
|
creg_val |= (HC_R0INT_ENA << LPFC_FCP_RING);
|
|
writel(creg_val, phba->HCregaddr);
|
|
readl(phba->HCregaddr); /* flush */
|
|
}
|
|
|
|
phba->cfg_poll = val;
|
|
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
|
|
return strlen(buf);
|
|
}
|
|
|
|
#define lpfc_param_show(attr) \
|
|
static ssize_t \
|
|
lpfc_##attr##_show(struct class_device *cdev, char *buf) \
|
|
{ \
|
|
struct Scsi_Host *host = class_to_shost(cdev);\
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];\
|
|
int val = 0;\
|
|
val = phba->cfg_##attr;\
|
|
return snprintf(buf, PAGE_SIZE, "%d\n",\
|
|
phba->cfg_##attr);\
|
|
}
|
|
|
|
#define lpfc_param_hex_show(attr) \
|
|
static ssize_t \
|
|
lpfc_##attr##_show(struct class_device *cdev, char *buf) \
|
|
{ \
|
|
struct Scsi_Host *host = class_to_shost(cdev);\
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];\
|
|
int val = 0;\
|
|
val = phba->cfg_##attr;\
|
|
return snprintf(buf, PAGE_SIZE, "%#x\n",\
|
|
phba->cfg_##attr);\
|
|
}
|
|
|
|
#define lpfc_param_init(attr, default, minval, maxval) \
|
|
static int \
|
|
lpfc_##attr##_init(struct lpfc_hba *phba, int val) \
|
|
{ \
|
|
if (val >= minval && val <= maxval) {\
|
|
phba->cfg_##attr = val;\
|
|
return 0;\
|
|
}\
|
|
lpfc_printf_log(phba, KERN_ERR, LOG_INIT, \
|
|
"%d:0449 lpfc_"#attr" attribute cannot be set to %d, "\
|
|
"allowed range is ["#minval", "#maxval"]\n", \
|
|
phba->brd_no, val); \
|
|
phba->cfg_##attr = default;\
|
|
return -EINVAL;\
|
|
}
|
|
|
|
#define lpfc_param_set(attr, default, minval, maxval) \
|
|
static int \
|
|
lpfc_##attr##_set(struct lpfc_hba *phba, int val) \
|
|
{ \
|
|
if (val >= minval && val <= maxval) {\
|
|
phba->cfg_##attr = val;\
|
|
return 0;\
|
|
}\
|
|
lpfc_printf_log(phba, KERN_ERR, LOG_INIT, \
|
|
"%d:0450 lpfc_"#attr" attribute cannot be set to %d, "\
|
|
"allowed range is ["#minval", "#maxval"]\n", \
|
|
phba->brd_no, val); \
|
|
return -EINVAL;\
|
|
}
|
|
|
|
#define lpfc_param_store(attr) \
|
|
static ssize_t \
|
|
lpfc_##attr##_store(struct class_device *cdev, const char *buf, size_t count) \
|
|
{ \
|
|
struct Scsi_Host *host = class_to_shost(cdev);\
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];\
|
|
int val=0;\
|
|
if (!isdigit(buf[0]))\
|
|
return -EINVAL;\
|
|
if (sscanf(buf, "%i", &val) != 1)\
|
|
return -EINVAL;\
|
|
if (lpfc_##attr##_set(phba, val) == 0) \
|
|
return strlen(buf);\
|
|
else \
|
|
return -EINVAL;\
|
|
}
|
|
|
|
#define LPFC_ATTR(name, defval, minval, maxval, desc) \
|
|
static int lpfc_##name = defval;\
|
|
module_param(lpfc_##name, int, 0);\
|
|
MODULE_PARM_DESC(lpfc_##name, desc);\
|
|
lpfc_param_init(name, defval, minval, maxval)
|
|
|
|
#define LPFC_ATTR_R(name, defval, minval, maxval, desc) \
|
|
static int lpfc_##name = defval;\
|
|
module_param(lpfc_##name, int, 0);\
|
|
MODULE_PARM_DESC(lpfc_##name, desc);\
|
|
lpfc_param_show(name)\
|
|
lpfc_param_init(name, defval, minval, maxval)\
|
|
static CLASS_DEVICE_ATTR(lpfc_##name, S_IRUGO , lpfc_##name##_show, NULL)
|
|
|
|
#define LPFC_ATTR_RW(name, defval, minval, maxval, desc) \
|
|
static int lpfc_##name = defval;\
|
|
module_param(lpfc_##name, int, 0);\
|
|
MODULE_PARM_DESC(lpfc_##name, desc);\
|
|
lpfc_param_show(name)\
|
|
lpfc_param_init(name, defval, minval, maxval)\
|
|
lpfc_param_set(name, defval, minval, maxval)\
|
|
lpfc_param_store(name)\
|
|
static CLASS_DEVICE_ATTR(lpfc_##name, S_IRUGO | S_IWUSR,\
|
|
lpfc_##name##_show, lpfc_##name##_store)
|
|
|
|
#define LPFC_ATTR_HEX_R(name, defval, minval, maxval, desc) \
|
|
static int lpfc_##name = defval;\
|
|
module_param(lpfc_##name, int, 0);\
|
|
MODULE_PARM_DESC(lpfc_##name, desc);\
|
|
lpfc_param_hex_show(name)\
|
|
lpfc_param_init(name, defval, minval, maxval)\
|
|
static CLASS_DEVICE_ATTR(lpfc_##name, S_IRUGO , lpfc_##name##_show, NULL)
|
|
|
|
#define LPFC_ATTR_HEX_RW(name, defval, minval, maxval, desc) \
|
|
static int lpfc_##name = defval;\
|
|
module_param(lpfc_##name, int, 0);\
|
|
MODULE_PARM_DESC(lpfc_##name, desc);\
|
|
lpfc_param_hex_show(name)\
|
|
lpfc_param_init(name, defval, minval, maxval)\
|
|
lpfc_param_set(name, defval, minval, maxval)\
|
|
lpfc_param_store(name)\
|
|
static CLASS_DEVICE_ATTR(lpfc_##name, S_IRUGO | S_IWUSR,\
|
|
lpfc_##name##_show, lpfc_##name##_store)
|
|
|
|
static CLASS_DEVICE_ATTR(info, S_IRUGO, lpfc_info_show, NULL);
|
|
static CLASS_DEVICE_ATTR(serialnum, S_IRUGO, lpfc_serialnum_show, NULL);
|
|
static CLASS_DEVICE_ATTR(modeldesc, S_IRUGO, lpfc_modeldesc_show, NULL);
|
|
static CLASS_DEVICE_ATTR(modelname, S_IRUGO, lpfc_modelname_show, NULL);
|
|
static CLASS_DEVICE_ATTR(programtype, S_IRUGO, lpfc_programtype_show, NULL);
|
|
static CLASS_DEVICE_ATTR(portnum, S_IRUGO, lpfc_portnum_show, NULL);
|
|
static CLASS_DEVICE_ATTR(fwrev, S_IRUGO, lpfc_fwrev_show, NULL);
|
|
static CLASS_DEVICE_ATTR(hdw, S_IRUGO, lpfc_hdw_show, NULL);
|
|
static CLASS_DEVICE_ATTR(state, S_IRUGO, lpfc_state_show, NULL);
|
|
static CLASS_DEVICE_ATTR(option_rom_version, S_IRUGO,
|
|
lpfc_option_rom_version_show, NULL);
|
|
static CLASS_DEVICE_ATTR(num_discovered_ports, S_IRUGO,
|
|
lpfc_num_discovered_ports_show, NULL);
|
|
static CLASS_DEVICE_ATTR(nport_evt_cnt, S_IRUGO, lpfc_nport_evt_cnt_show, NULL);
|
|
static CLASS_DEVICE_ATTR(lpfc_drvr_version, S_IRUGO, lpfc_drvr_version_show,
|
|
NULL);
|
|
static CLASS_DEVICE_ATTR(management_version, S_IRUGO, management_version_show,
|
|
NULL);
|
|
static CLASS_DEVICE_ATTR(board_online, S_IRUGO | S_IWUSR,
|
|
lpfc_board_online_show, lpfc_board_online_store);
|
|
|
|
static int lpfc_poll = 0;
|
|
module_param(lpfc_poll, int, 0);
|
|
MODULE_PARM_DESC(lpfc_poll, "FCP ring polling mode control:"
|
|
" 0 - none,"
|
|
" 1 - poll with interrupts enabled"
|
|
" 3 - poll and disable FCP ring interrupts");
|
|
|
|
static CLASS_DEVICE_ATTR(lpfc_poll, S_IRUGO | S_IWUSR,
|
|
lpfc_poll_show, lpfc_poll_store);
|
|
|
|
/*
|
|
# lpfc_log_verbose: Only turn this flag on if you are willing to risk being
|
|
# deluged with LOTS of information.
|
|
# You can set a bit mask to record specific types of verbose messages:
|
|
#
|
|
# LOG_ELS 0x1 ELS events
|
|
# LOG_DISCOVERY 0x2 Link discovery events
|
|
# LOG_MBOX 0x4 Mailbox events
|
|
# LOG_INIT 0x8 Initialization events
|
|
# LOG_LINK_EVENT 0x10 Link events
|
|
# LOG_IP 0x20 IP traffic history
|
|
# LOG_FCP 0x40 FCP traffic history
|
|
# LOG_NODE 0x80 Node table events
|
|
# LOG_MISC 0x400 Miscellaneous events
|
|
# LOG_SLI 0x800 SLI events
|
|
# LOG_CHK_COND 0x1000 FCP Check condition flag
|
|
# LOG_LIBDFC 0x2000 LIBDFC events
|
|
# LOG_ALL_MSG 0xffff LOG all messages
|
|
*/
|
|
LPFC_ATTR_HEX_RW(log_verbose, 0x0, 0x0, 0xffff, "Verbose logging bit-mask");
|
|
|
|
/*
|
|
# lun_queue_depth: This parameter is used to limit the number of outstanding
|
|
# commands per FCP LUN. Value range is [1,128]. Default value is 30.
|
|
*/
|
|
LPFC_ATTR_R(lun_queue_depth, 30, 1, 128,
|
|
"Max number of FCP commands we can queue to a specific LUN");
|
|
|
|
/*
|
|
# Some disk devices have a "select ID" or "select Target" capability.
|
|
# From a protocol standpoint "select ID" usually means select the
|
|
# Fibre channel "ALPA". In the FC-AL Profile there is an "informative
|
|
# annex" which contains a table that maps a "select ID" (a number
|
|
# between 0 and 7F) to an ALPA. By default, for compatibility with
|
|
# older drivers, the lpfc driver scans this table from low ALPA to high
|
|
# ALPA.
|
|
#
|
|
# Turning on the scan-down variable (on = 1, off = 0) will
|
|
# cause the lpfc driver to use an inverted table, effectively
|
|
# scanning ALPAs from high to low. Value range is [0,1]. Default value is 1.
|
|
#
|
|
# (Note: This "select ID" functionality is a LOOP ONLY characteristic
|
|
# and will not work across a fabric. Also this parameter will take
|
|
# effect only in the case when ALPA map is not available.)
|
|
*/
|
|
LPFC_ATTR_R(scan_down, 1, 0, 1,
|
|
"Start scanning for devices from highest ALPA to lowest");
|
|
|
|
/*
|
|
# lpfc_nodev_tmo: If set, it will hold all I/O errors on devices that disappear
|
|
# until the timer expires. Value range is [0,255]. Default value is 30.
|
|
# NOTE: this MUST be less then the SCSI Layer command timeout - 1.
|
|
*/
|
|
LPFC_ATTR_RW(nodev_tmo, 30, 0, 255,
|
|
"Seconds driver will hold I/O waiting for a device to come back");
|
|
|
|
/*
|
|
# lpfc_topology: link topology for init link
|
|
# 0x0 = attempt loop mode then point-to-point
|
|
# 0x02 = attempt point-to-point mode only
|
|
# 0x04 = attempt loop mode only
|
|
# 0x06 = attempt point-to-point mode then loop
|
|
# Set point-to-point mode if you want to run as an N_Port.
|
|
# Set loop mode if you want to run as an NL_Port. Value range is [0,0x6].
|
|
# Default value is 0.
|
|
*/
|
|
LPFC_ATTR_R(topology, 0, 0, 6, "Select Fibre Channel topology");
|
|
|
|
/*
|
|
# lpfc_link_speed: Link speed selection for initializing the Fibre Channel
|
|
# connection.
|
|
# 0 = auto select (default)
|
|
# 1 = 1 Gigabaud
|
|
# 2 = 2 Gigabaud
|
|
# 4 = 4 Gigabaud
|
|
# Value range is [0,4]. Default value is 0.
|
|
*/
|
|
LPFC_ATTR_R(link_speed, 0, 0, 4, "Select link speed");
|
|
|
|
/*
|
|
# lpfc_fcp_class: Determines FC class to use for the FCP protocol.
|
|
# Value range is [2,3]. Default value is 3.
|
|
*/
|
|
LPFC_ATTR_R(fcp_class, 3, 2, 3,
|
|
"Select Fibre Channel class of service for FCP sequences");
|
|
|
|
/*
|
|
# lpfc_use_adisc: Use ADISC for FCP rediscovery instead of PLOGI. Value range
|
|
# is [0,1]. Default value is 0.
|
|
*/
|
|
LPFC_ATTR_RW(use_adisc, 0, 0, 1,
|
|
"Use ADISC on rediscovery to authenticate FCP devices");
|
|
|
|
/*
|
|
# lpfc_ack0: Use ACK0, instead of ACK1 for class 2 acknowledgement. Value
|
|
# range is [0,1]. Default value is 0.
|
|
*/
|
|
LPFC_ATTR_R(ack0, 0, 0, 1, "Enable ACK0 support");
|
|
|
|
/*
|
|
# lpfc_cr_delay & lpfc_cr_count: Default values for I/O colaesing
|
|
# cr_delay (msec) or cr_count outstanding commands. cr_delay can take
|
|
# value [0,63]. cr_count can take value [0,255]. Default value of cr_delay
|
|
# is 0. Default value of cr_count is 1. The cr_count feature is disabled if
|
|
# cr_delay is set to 0.
|
|
*/
|
|
LPFC_ATTR_RW(cr_delay, 0, 0, 63, "A count of milliseconds after which an"
|
|
"interrupt response is generated");
|
|
|
|
LPFC_ATTR_RW(cr_count, 1, 1, 255, "A count of I/O completions after which an"
|
|
"interrupt response is generated");
|
|
|
|
/*
|
|
# lpfc_fdmi_on: controls FDMI support.
|
|
# 0 = no FDMI support
|
|
# 1 = support FDMI without attribute of hostname
|
|
# 2 = support FDMI with attribute of hostname
|
|
# Value range [0,2]. Default value is 0.
|
|
*/
|
|
LPFC_ATTR_RW(fdmi_on, 0, 0, 2, "Enable FDMI support");
|
|
|
|
/*
|
|
# Specifies the maximum number of ELS cmds we can have outstanding (for
|
|
# discovery). Value range is [1,64]. Default value = 32.
|
|
*/
|
|
LPFC_ATTR(discovery_threads, 32, 1, 64, "Maximum number of ELS commands"
|
|
"during discovery");
|
|
|
|
/*
|
|
# lpfc_max_luns: maximum number of LUNs per target driver will support
|
|
# Value range is [1,32768]. Default value is 256.
|
|
# NOTE: The SCSI layer will scan each target for this many luns
|
|
*/
|
|
LPFC_ATTR_R(max_luns, 256, 1, 32768,
|
|
"Maximum number of LUNs per target driver will support");
|
|
|
|
/*
|
|
# lpfc_poll_tmo: .Milliseconds driver will wait between polling FCP ring.
|
|
# Value range is [1,255], default value is 10.
|
|
*/
|
|
LPFC_ATTR_RW(poll_tmo, 10, 1, 255,
|
|
"Milliseconds driver will wait between polling FCP ring");
|
|
|
|
struct class_device_attribute *lpfc_host_attrs[] = {
|
|
&class_device_attr_info,
|
|
&class_device_attr_serialnum,
|
|
&class_device_attr_modeldesc,
|
|
&class_device_attr_modelname,
|
|
&class_device_attr_programtype,
|
|
&class_device_attr_portnum,
|
|
&class_device_attr_fwrev,
|
|
&class_device_attr_hdw,
|
|
&class_device_attr_option_rom_version,
|
|
&class_device_attr_state,
|
|
&class_device_attr_num_discovered_ports,
|
|
&class_device_attr_lpfc_drvr_version,
|
|
&class_device_attr_lpfc_log_verbose,
|
|
&class_device_attr_lpfc_lun_queue_depth,
|
|
&class_device_attr_lpfc_nodev_tmo,
|
|
&class_device_attr_lpfc_fcp_class,
|
|
&class_device_attr_lpfc_use_adisc,
|
|
&class_device_attr_lpfc_ack0,
|
|
&class_device_attr_lpfc_topology,
|
|
&class_device_attr_lpfc_scan_down,
|
|
&class_device_attr_lpfc_link_speed,
|
|
&class_device_attr_lpfc_cr_delay,
|
|
&class_device_attr_lpfc_cr_count,
|
|
&class_device_attr_lpfc_fdmi_on,
|
|
&class_device_attr_lpfc_max_luns,
|
|
&class_device_attr_nport_evt_cnt,
|
|
&class_device_attr_management_version,
|
|
&class_device_attr_board_online,
|
|
&class_device_attr_lpfc_poll,
|
|
&class_device_attr_lpfc_poll_tmo,
|
|
NULL,
|
|
};
|
|
|
|
static ssize_t
|
|
sysfs_ctlreg_write(struct kobject *kobj, char *buf, loff_t off, size_t count)
|
|
{
|
|
size_t buf_off;
|
|
struct Scsi_Host *host = class_to_shost(container_of(kobj,
|
|
struct class_device, kobj));
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
|
|
if ((off + count) > FF_REG_AREA_SIZE)
|
|
return -ERANGE;
|
|
|
|
if (count == 0) return 0;
|
|
|
|
if (off % 4 || count % 4 || (unsigned long)buf % 4)
|
|
return -EINVAL;
|
|
|
|
spin_lock_irq(phba->host->host_lock);
|
|
|
|
if (!(phba->fc_flag & FC_OFFLINE_MODE)) {
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
return -EPERM;
|
|
}
|
|
|
|
for (buf_off = 0; buf_off < count; buf_off += sizeof(uint32_t))
|
|
writel(*((uint32_t *)(buf + buf_off)),
|
|
phba->ctrl_regs_memmap_p + off + buf_off);
|
|
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
sysfs_ctlreg_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
|
|
{
|
|
size_t buf_off;
|
|
uint32_t * tmp_ptr;
|
|
struct Scsi_Host *host = class_to_shost(container_of(kobj,
|
|
struct class_device, kobj));
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
|
|
if (off > FF_REG_AREA_SIZE)
|
|
return -ERANGE;
|
|
|
|
if ((off + count) > FF_REG_AREA_SIZE)
|
|
count = FF_REG_AREA_SIZE - off;
|
|
|
|
if (count == 0) return 0;
|
|
|
|
if (off % 4 || count % 4 || (unsigned long)buf % 4)
|
|
return -EINVAL;
|
|
|
|
spin_lock_irq(phba->host->host_lock);
|
|
|
|
for (buf_off = 0; buf_off < count; buf_off += sizeof(uint32_t)) {
|
|
tmp_ptr = (uint32_t *)(buf + buf_off);
|
|
*tmp_ptr = readl(phba->ctrl_regs_memmap_p + off + buf_off);
|
|
}
|
|
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct bin_attribute sysfs_ctlreg_attr = {
|
|
.attr = {
|
|
.name = "ctlreg",
|
|
.mode = S_IRUSR | S_IWUSR,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.size = 256,
|
|
.read = sysfs_ctlreg_read,
|
|
.write = sysfs_ctlreg_write,
|
|
};
|
|
|
|
|
|
static void
|
|
sysfs_mbox_idle (struct lpfc_hba * phba)
|
|
{
|
|
phba->sysfs_mbox.state = SMBOX_IDLE;
|
|
phba->sysfs_mbox.offset = 0;
|
|
|
|
if (phba->sysfs_mbox.mbox) {
|
|
mempool_free(phba->sysfs_mbox.mbox,
|
|
phba->mbox_mem_pool);
|
|
phba->sysfs_mbox.mbox = NULL;
|
|
}
|
|
}
|
|
|
|
static ssize_t
|
|
sysfs_mbox_write(struct kobject *kobj, char *buf, loff_t off, size_t count)
|
|
{
|
|
struct Scsi_Host * host =
|
|
class_to_shost(container_of(kobj, struct class_device, kobj));
|
|
struct lpfc_hba * phba = (struct lpfc_hba*)host->hostdata[0];
|
|
struct lpfcMboxq * mbox = NULL;
|
|
|
|
if ((count + off) > MAILBOX_CMD_SIZE)
|
|
return -ERANGE;
|
|
|
|
if (off % 4 || count % 4 || (unsigned long)buf % 4)
|
|
return -EINVAL;
|
|
|
|
if (count == 0)
|
|
return 0;
|
|
|
|
if (off == 0) {
|
|
mbox = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL);
|
|
if (!mbox)
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
spin_lock_irq(host->host_lock);
|
|
|
|
if (off == 0) {
|
|
if (phba->sysfs_mbox.mbox)
|
|
mempool_free(mbox, phba->mbox_mem_pool);
|
|
else
|
|
phba->sysfs_mbox.mbox = mbox;
|
|
phba->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);
|
|
spin_unlock_irq(host->host_lock);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
memcpy((uint8_t *) & phba->sysfs_mbox.mbox->mb + off,
|
|
buf, count);
|
|
|
|
phba->sysfs_mbox.offset = off + count;
|
|
|
|
spin_unlock_irq(host->host_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
sysfs_mbox_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
|
|
{
|
|
struct Scsi_Host *host =
|
|
class_to_shost(container_of(kobj, struct class_device,
|
|
kobj));
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)host->hostdata[0];
|
|
int rc;
|
|
|
|
if (off > sizeof(MAILBOX_t))
|
|
return -ERANGE;
|
|
|
|
if ((count + off) > sizeof(MAILBOX_t))
|
|
count = sizeof(MAILBOX_t) - off;
|
|
|
|
if (off % 4 || count % 4 || (unsigned long)buf % 4)
|
|
return -EINVAL;
|
|
|
|
if (off && count == 0)
|
|
return 0;
|
|
|
|
spin_lock_irq(phba->host->host_lock);
|
|
|
|
if (off == 0 &&
|
|
phba->sysfs_mbox.state == SMBOX_WRITING &&
|
|
phba->sysfs_mbox.offset >= 2 * sizeof(uint32_t)) {
|
|
|
|
switch (phba->sysfs_mbox.mbox->mb.mbxCommand) {
|
|
/* Offline only */
|
|
case MBX_WRITE_NV:
|
|
case MBX_INIT_LINK:
|
|
case MBX_DOWN_LINK:
|
|
case MBX_CONFIG_LINK:
|
|
case MBX_CONFIG_RING:
|
|
case MBX_RESET_RING:
|
|
case MBX_UNREG_LOGIN:
|
|
case MBX_CLEAR_LA:
|
|
case MBX_DUMP_CONTEXT:
|
|
case MBX_RUN_DIAGS:
|
|
case MBX_RESTART:
|
|
case MBX_FLASH_WR_ULA:
|
|
case MBX_SET_MASK:
|
|
case MBX_SET_SLIM:
|
|
case MBX_SET_DEBUG:
|
|
if (!(phba->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);
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
return -EPERM;
|
|
}
|
|
case MBX_LOAD_SM:
|
|
case MBX_READ_NV:
|
|
case MBX_READ_CONFIG:
|
|
case MBX_READ_RCONFIG:
|
|
case MBX_READ_STATUS:
|
|
case MBX_READ_XRI:
|
|
case MBX_READ_REV:
|
|
case MBX_READ_LNK_STAT:
|
|
case MBX_DUMP_MEMORY:
|
|
case MBX_DOWN_LOAD:
|
|
case MBX_UPDATE_CFG:
|
|
case MBX_LOAD_AREA:
|
|
case MBX_LOAD_EXP_ROM:
|
|
break;
|
|
case MBX_READ_SPARM64:
|
|
case MBX_READ_LA:
|
|
case MBX_READ_LA64:
|
|
case MBX_REG_LOGIN:
|
|
case MBX_REG_LOGIN64:
|
|
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);
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
return -EPERM;
|
|
default:
|
|
printk(KERN_WARNING "mbox_read: Unknown Command 0x%x\n",
|
|
phba->sysfs_mbox.mbox->mb.mbxCommand);
|
|
sysfs_mbox_idle(phba);
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
return -EPERM;
|
|
}
|
|
|
|
if ((phba->fc_flag & FC_OFFLINE_MODE) ||
|
|
(!(phba->sli.sli_flag & LPFC_SLI2_ACTIVE))){
|
|
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
rc = lpfc_sli_issue_mbox (phba,
|
|
phba->sysfs_mbox.mbox,
|
|
MBX_POLL);
|
|
spin_lock_irq(phba->host->host_lock);
|
|
|
|
} else {
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
rc = lpfc_sli_issue_mbox_wait (phba,
|
|
phba->sysfs_mbox.mbox,
|
|
phba->fc_ratov * 2);
|
|
spin_lock_irq(phba->host->host_lock);
|
|
}
|
|
|
|
if (rc != MBX_SUCCESS) {
|
|
sysfs_mbox_idle(phba);
|
|
spin_unlock_irq(host->host_lock);
|
|
return -ENODEV;
|
|
}
|
|
phba->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);
|
|
spin_unlock_irq(host->host_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(buf, (uint8_t *) & phba->sysfs_mbox.mbox->mb + off, count);
|
|
|
|
phba->sysfs_mbox.offset = off + count;
|
|
|
|
if (phba->sysfs_mbox.offset == sizeof(MAILBOX_t))
|
|
sysfs_mbox_idle(phba);
|
|
|
|
spin_unlock_irq(phba->host->host_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static struct bin_attribute sysfs_mbox_attr = {
|
|
.attr = {
|
|
.name = "mbox",
|
|
.mode = S_IRUSR | S_IWUSR,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.size = sizeof(MAILBOX_t),
|
|
.read = sysfs_mbox_read,
|
|
.write = sysfs_mbox_write,
|
|
};
|
|
|
|
int
|
|
lpfc_alloc_sysfs_attr(struct lpfc_hba *phba)
|
|
{
|
|
struct Scsi_Host *host = phba->host;
|
|
int error;
|
|
|
|
error = sysfs_create_bin_file(&host->shost_classdev.kobj,
|
|
&sysfs_ctlreg_attr);
|
|
if (error)
|
|
goto out;
|
|
|
|
error = sysfs_create_bin_file(&host->shost_classdev.kobj,
|
|
&sysfs_mbox_attr);
|
|
if (error)
|
|
goto out_remove_ctlreg_attr;
|
|
|
|
return 0;
|
|
out_remove_ctlreg_attr:
|
|
sysfs_remove_bin_file(&host->shost_classdev.kobj, &sysfs_ctlreg_attr);
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
void
|
|
lpfc_free_sysfs_attr(struct lpfc_hba *phba)
|
|
{
|
|
struct Scsi_Host *host = phba->host;
|
|
|
|
sysfs_remove_bin_file(&host->shost_classdev.kobj, &sysfs_mbox_attr);
|
|
sysfs_remove_bin_file(&host->shost_classdev.kobj, &sysfs_ctlreg_attr);
|
|
}
|
|
|
|
|
|
/*
|
|
* Dynamic FC Host Attributes Support
|
|
*/
|
|
|
|
static void
|
|
lpfc_get_host_port_id(struct Scsi_Host *shost)
|
|
{
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)shost->hostdata[0];
|
|
/* note: fc_myDID already in cpu endianness */
|
|
fc_host_port_id(shost) = phba->fc_myDID;
|
|
}
|
|
|
|
static void
|
|
lpfc_get_host_port_type(struct Scsi_Host *shost)
|
|
{
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)shost->hostdata[0];
|
|
|
|
spin_lock_irq(shost->host_lock);
|
|
|
|
if (phba->hba_state == LPFC_HBA_READY) {
|
|
if (phba->fc_topology == TOPOLOGY_LOOP) {
|
|
if (phba->fc_flag & FC_PUBLIC_LOOP)
|
|
fc_host_port_type(shost) = FC_PORTTYPE_NLPORT;
|
|
else
|
|
fc_host_port_type(shost) = FC_PORTTYPE_LPORT;
|
|
} else {
|
|
if (phba->fc_flag & FC_FABRIC)
|
|
fc_host_port_type(shost) = FC_PORTTYPE_NPORT;
|
|
else
|
|
fc_host_port_type(shost) = FC_PORTTYPE_PTP;
|
|
}
|
|
} else
|
|
fc_host_port_type(shost) = FC_PORTTYPE_UNKNOWN;
|
|
|
|
spin_unlock_irq(shost->host_lock);
|
|
}
|
|
|
|
static void
|
|
lpfc_get_host_port_state(struct Scsi_Host *shost)
|
|
{
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)shost->hostdata[0];
|
|
|
|
spin_lock_irq(shost->host_lock);
|
|
|
|
if (phba->fc_flag & FC_OFFLINE_MODE)
|
|
fc_host_port_state(shost) = FC_PORTSTATE_OFFLINE;
|
|
else {
|
|
switch (phba->hba_state) {
|
|
case LPFC_INIT_START:
|
|
case LPFC_INIT_MBX_CMDS:
|
|
case LPFC_LINK_DOWN:
|
|
fc_host_port_state(shost) = FC_PORTSTATE_LINKDOWN;
|
|
break;
|
|
case LPFC_LINK_UP:
|
|
case LPFC_LOCAL_CFG_LINK:
|
|
case LPFC_FLOGI:
|
|
case LPFC_FABRIC_CFG_LINK:
|
|
case LPFC_NS_REG:
|
|
case LPFC_NS_QRY:
|
|
case LPFC_BUILD_DISC_LIST:
|
|
case LPFC_DISC_AUTH:
|
|
case LPFC_CLEAR_LA:
|
|
case LPFC_HBA_READY:
|
|
/* Links up, beyond this port_type reports state */
|
|
fc_host_port_state(shost) = FC_PORTSTATE_ONLINE;
|
|
break;
|
|
case LPFC_HBA_ERROR:
|
|
fc_host_port_state(shost) = FC_PORTSTATE_ERROR;
|
|
break;
|
|
default:
|
|
fc_host_port_state(shost) = FC_PORTSTATE_UNKNOWN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
spin_unlock_irq(shost->host_lock);
|
|
}
|
|
|
|
static void
|
|
lpfc_get_host_speed(struct Scsi_Host *shost)
|
|
{
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)shost->hostdata[0];
|
|
|
|
spin_lock_irq(shost->host_lock);
|
|
|
|
if (phba->hba_state == LPFC_HBA_READY) {
|
|
switch(phba->fc_linkspeed) {
|
|
case LA_1GHZ_LINK:
|
|
fc_host_speed(shost) = FC_PORTSPEED_1GBIT;
|
|
break;
|
|
case LA_2GHZ_LINK:
|
|
fc_host_speed(shost) = FC_PORTSPEED_2GBIT;
|
|
break;
|
|
case LA_4GHZ_LINK:
|
|
fc_host_speed(shost) = FC_PORTSPEED_4GBIT;
|
|
break;
|
|
default:
|
|
fc_host_speed(shost) = FC_PORTSPEED_UNKNOWN;
|
|
break;
|
|
}
|
|
}
|
|
|
|
spin_unlock_irq(shost->host_lock);
|
|
}
|
|
|
|
static void
|
|
lpfc_get_host_fabric_name (struct Scsi_Host *shost)
|
|
{
|
|
struct lpfc_hba *phba = (struct lpfc_hba*)shost->hostdata[0];
|
|
u64 node_name;
|
|
|
|
spin_lock_irq(shost->host_lock);
|
|
|
|
if ((phba->fc_flag & FC_FABRIC) ||
|
|
((phba->fc_topology == TOPOLOGY_LOOP) &&
|
|
(phba->fc_flag & FC_PUBLIC_LOOP)))
|
|
node_name = wwn_to_u64(phba->fc_fabparam.nodeName.u.wwn);
|
|
else
|
|
/* fabric is local port if there is no F/FL_Port */
|
|
node_name = wwn_to_u64(phba->fc_nodename.u.wwn);
|
|
|
|
spin_unlock_irq(shost->host_lock);
|
|
|
|
fc_host_fabric_name(shost) = node_name;
|
|
}
|
|
|
|
|
|
static struct fc_host_statistics *
|
|
lpfc_get_stats(struct Scsi_Host *shost)
|
|
{
|
|
struct lpfc_hba *phba = (struct lpfc_hba *)shost->hostdata[0];
|
|
struct lpfc_sli *psli = &phba->sli;
|
|
struct fc_host_statistics *hs = &phba->link_stats;
|
|
LPFC_MBOXQ_t *pmboxq;
|
|
MAILBOX_t *pmb;
|
|
int rc = 0;
|
|
|
|
pmboxq = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL);
|
|
if (!pmboxq)
|
|
return NULL;
|
|
memset(pmboxq, 0, sizeof (LPFC_MBOXQ_t));
|
|
|
|
pmb = &pmboxq->mb;
|
|
pmb->mbxCommand = MBX_READ_STATUS;
|
|
pmb->mbxOwner = OWN_HOST;
|
|
pmboxq->context1 = NULL;
|
|
|
|
if ((phba->fc_flag & FC_OFFLINE_MODE) ||
|
|
(!(psli->sli_flag & LPFC_SLI2_ACTIVE)))
|
|
rc = lpfc_sli_issue_mbox(phba, pmboxq, MBX_POLL);
|
|
else
|
|
rc = lpfc_sli_issue_mbox_wait(phba, pmboxq, phba->fc_ratov * 2);
|
|
|
|
if (rc != MBX_SUCCESS) {
|
|
if (rc == MBX_TIMEOUT)
|
|
pmboxq->mbox_cmpl = lpfc_sli_def_mbox_cmpl;
|
|
else
|
|
mempool_free(pmboxq, phba->mbox_mem_pool);
|
|
return NULL;
|
|
}
|
|
|
|
memset(hs, 0, sizeof (struct fc_host_statistics));
|
|
|
|
hs->tx_frames = pmb->un.varRdStatus.xmitFrameCnt;
|
|
hs->tx_words = (pmb->un.varRdStatus.xmitByteCnt * 256);
|
|
hs->rx_frames = pmb->un.varRdStatus.rcvFrameCnt;
|
|
hs->rx_words = (pmb->un.varRdStatus.rcvByteCnt * 256);
|
|
|
|
memset(pmboxq, 0, sizeof (LPFC_MBOXQ_t));
|
|
pmb->mbxCommand = MBX_READ_LNK_STAT;
|
|
pmb->mbxOwner = OWN_HOST;
|
|
pmboxq->context1 = NULL;
|
|
|
|
if ((phba->fc_flag & FC_OFFLINE_MODE) ||
|
|
(!(psli->sli_flag & LPFC_SLI2_ACTIVE)))
|
|
rc = lpfc_sli_issue_mbox(phba, pmboxq, MBX_POLL);
|
|
else
|
|
rc = lpfc_sli_issue_mbox_wait(phba, pmboxq, phba->fc_ratov * 2);
|
|
|
|
if (rc != MBX_SUCCESS) {
|
|
if (rc == MBX_TIMEOUT)
|
|
pmboxq->mbox_cmpl = lpfc_sli_def_mbox_cmpl;
|
|
else
|
|
mempool_free( pmboxq, phba->mbox_mem_pool);
|
|
return NULL;
|
|
}
|
|
|
|
hs->link_failure_count = pmb->un.varRdLnk.linkFailureCnt;
|
|
hs->loss_of_sync_count = pmb->un.varRdLnk.lossSyncCnt;
|
|
hs->loss_of_signal_count = pmb->un.varRdLnk.lossSignalCnt;
|
|
hs->prim_seq_protocol_err_count = pmb->un.varRdLnk.primSeqErrCnt;
|
|
hs->invalid_tx_word_count = pmb->un.varRdLnk.invalidXmitWord;
|
|
hs->invalid_crc_count = pmb->un.varRdLnk.crcCnt;
|
|
hs->error_frames = pmb->un.varRdLnk.crcCnt;
|
|
|
|
if (phba->fc_topology == TOPOLOGY_LOOP) {
|
|
hs->lip_count = (phba->fc_eventTag >> 1);
|
|
hs->nos_count = -1;
|
|
} else {
|
|
hs->lip_count = -1;
|
|
hs->nos_count = (phba->fc_eventTag >> 1);
|
|
}
|
|
|
|
hs->dumped_frames = -1;
|
|
|
|
/* FIX ME */
|
|
/*hs->SecondsSinceLastReset = (jiffies - lpfc_loadtime) / HZ;*/
|
|
|
|
return hs;
|
|
}
|
|
|
|
|
|
/*
|
|
* The LPFC driver treats linkdown handling as target loss events so there
|
|
* are no sysfs handlers for link_down_tmo.
|
|
*/
|
|
static void
|
|
lpfc_get_starget_port_id(struct scsi_target *starget)
|
|
{
|
|
struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
|
|
struct lpfc_hba *phba = (struct lpfc_hba *) shost->hostdata[0];
|
|
uint32_t did = -1;
|
|
struct lpfc_nodelist *ndlp = NULL;
|
|
|
|
spin_lock_irq(shost->host_lock);
|
|
/* Search the mapped list for this target ID */
|
|
list_for_each_entry(ndlp, &phba->fc_nlpmap_list, nlp_listp) {
|
|
if (starget->id == ndlp->nlp_sid) {
|
|
did = ndlp->nlp_DID;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irq(shost->host_lock);
|
|
|
|
fc_starget_port_id(starget) = did;
|
|
}
|
|
|
|
static void
|
|
lpfc_get_starget_node_name(struct scsi_target *starget)
|
|
{
|
|
struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
|
|
struct lpfc_hba *phba = (struct lpfc_hba *) shost->hostdata[0];
|
|
u64 node_name = 0;
|
|
struct lpfc_nodelist *ndlp = NULL;
|
|
|
|
spin_lock_irq(shost->host_lock);
|
|
/* Search the mapped list for this target ID */
|
|
list_for_each_entry(ndlp, &phba->fc_nlpmap_list, nlp_listp) {
|
|
if (starget->id == ndlp->nlp_sid) {
|
|
node_name = wwn_to_u64(ndlp->nlp_nodename.u.wwn);
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irq(shost->host_lock);
|
|
|
|
fc_starget_node_name(starget) = node_name;
|
|
}
|
|
|
|
static void
|
|
lpfc_get_starget_port_name(struct scsi_target *starget)
|
|
{
|
|
struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
|
|
struct lpfc_hba *phba = (struct lpfc_hba *) shost->hostdata[0];
|
|
u64 port_name = 0;
|
|
struct lpfc_nodelist *ndlp = NULL;
|
|
|
|
spin_lock_irq(shost->host_lock);
|
|
/* Search the mapped list for this target ID */
|
|
list_for_each_entry(ndlp, &phba->fc_nlpmap_list, nlp_listp) {
|
|
if (starget->id == ndlp->nlp_sid) {
|
|
port_name = wwn_to_u64(ndlp->nlp_portname.u.wwn);
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irq(shost->host_lock);
|
|
|
|
fc_starget_port_name(starget) = port_name;
|
|
}
|
|
|
|
static void
|
|
lpfc_get_rport_loss_tmo(struct fc_rport *rport)
|
|
{
|
|
/*
|
|
* Return the driver's global value for device loss timeout plus
|
|
* five seconds to allow the driver's nodev timer to run.
|
|
*/
|
|
rport->dev_loss_tmo = lpfc_nodev_tmo + 5;
|
|
}
|
|
|
|
static void
|
|
lpfc_set_rport_loss_tmo(struct fc_rport *rport, uint32_t timeout)
|
|
{
|
|
/*
|
|
* The driver doesn't have a per-target timeout setting. Set
|
|
* this value globally. lpfc_nodev_tmo should be greater then 0.
|
|
*/
|
|
if (timeout)
|
|
lpfc_nodev_tmo = timeout;
|
|
else
|
|
lpfc_nodev_tmo = 1;
|
|
rport->dev_loss_tmo = lpfc_nodev_tmo + 5;
|
|
}
|
|
|
|
|
|
#define lpfc_rport_show_function(field, format_string, sz, cast) \
|
|
static ssize_t \
|
|
lpfc_show_rport_##field (struct class_device *cdev, char *buf) \
|
|
{ \
|
|
struct fc_rport *rport = transport_class_to_rport(cdev); \
|
|
struct lpfc_rport_data *rdata = rport->hostdata; \
|
|
return snprintf(buf, sz, format_string, \
|
|
(rdata->target) ? cast rdata->target->field : 0); \
|
|
}
|
|
|
|
#define lpfc_rport_rd_attr(field, format_string, sz) \
|
|
lpfc_rport_show_function(field, format_string, sz, ) \
|
|
static FC_RPORT_ATTR(field, S_IRUGO, lpfc_show_rport_##field, NULL)
|
|
|
|
|
|
struct fc_function_template lpfc_transport_functions = {
|
|
/* fixed attributes the driver supports */
|
|
.show_host_node_name = 1,
|
|
.show_host_port_name = 1,
|
|
.show_host_supported_classes = 1,
|
|
.show_host_supported_fc4s = 1,
|
|
.show_host_symbolic_name = 1,
|
|
.show_host_supported_speeds = 1,
|
|
.show_host_maxframe_size = 1,
|
|
|
|
/* dynamic attributes the driver supports */
|
|
.get_host_port_id = lpfc_get_host_port_id,
|
|
.show_host_port_id = 1,
|
|
|
|
.get_host_port_type = lpfc_get_host_port_type,
|
|
.show_host_port_type = 1,
|
|
|
|
.get_host_port_state = lpfc_get_host_port_state,
|
|
.show_host_port_state = 1,
|
|
|
|
/* active_fc4s is shown but doesn't change (thus no get function) */
|
|
.show_host_active_fc4s = 1,
|
|
|
|
.get_host_speed = lpfc_get_host_speed,
|
|
.show_host_speed = 1,
|
|
|
|
.get_host_fabric_name = lpfc_get_host_fabric_name,
|
|
.show_host_fabric_name = 1,
|
|
|
|
/*
|
|
* The LPFC driver treats linkdown handling as target loss events
|
|
* so there are no sysfs handlers for link_down_tmo.
|
|
*/
|
|
|
|
.get_fc_host_stats = lpfc_get_stats,
|
|
|
|
/* the LPFC driver doesn't support resetting stats yet */
|
|
|
|
.dd_fcrport_size = sizeof(struct lpfc_rport_data),
|
|
.show_rport_maxframe_size = 1,
|
|
.show_rport_supported_classes = 1,
|
|
|
|
.get_rport_dev_loss_tmo = lpfc_get_rport_loss_tmo,
|
|
.set_rport_dev_loss_tmo = lpfc_set_rport_loss_tmo,
|
|
.show_rport_dev_loss_tmo = 1,
|
|
|
|
.get_starget_port_id = lpfc_get_starget_port_id,
|
|
.show_starget_port_id = 1,
|
|
|
|
.get_starget_node_name = lpfc_get_starget_node_name,
|
|
.show_starget_node_name = 1,
|
|
|
|
.get_starget_port_name = lpfc_get_starget_port_name,
|
|
.show_starget_port_name = 1,
|
|
|
|
.issue_fc_host_lip = lpfc_issue_lip,
|
|
};
|
|
|
|
void
|
|
lpfc_get_cfgparam(struct lpfc_hba *phba)
|
|
{
|
|
lpfc_log_verbose_init(phba, lpfc_log_verbose);
|
|
lpfc_cr_delay_init(phba, lpfc_cr_delay);
|
|
lpfc_cr_count_init(phba, lpfc_cr_count);
|
|
lpfc_lun_queue_depth_init(phba, lpfc_lun_queue_depth);
|
|
lpfc_fcp_class_init(phba, lpfc_fcp_class);
|
|
lpfc_use_adisc_init(phba, lpfc_use_adisc);
|
|
lpfc_ack0_init(phba, lpfc_ack0);
|
|
lpfc_topology_init(phba, lpfc_topology);
|
|
lpfc_scan_down_init(phba, lpfc_scan_down);
|
|
lpfc_nodev_tmo_init(phba, lpfc_nodev_tmo);
|
|
lpfc_link_speed_init(phba, lpfc_link_speed);
|
|
lpfc_fdmi_on_init(phba, lpfc_fdmi_on);
|
|
lpfc_discovery_threads_init(phba, lpfc_discovery_threads);
|
|
lpfc_max_luns_init(phba, lpfc_max_luns);
|
|
lpfc_poll_tmo_init(phba, lpfc_poll_tmo);
|
|
|
|
phba->cfg_poll = lpfc_poll;
|
|
|
|
/*
|
|
* The total number of segments is the configuration value plus 2
|
|
* since the IOCB need a command and response bde.
|
|
*/
|
|
phba->cfg_sg_seg_cnt = LPFC_SG_SEG_CNT + 2;
|
|
|
|
/*
|
|
* Since the sg_tablesize is module parameter, the sg_dma_buf_size
|
|
* used to create the sg_dma_buf_pool must be dynamically calculated
|
|
*/
|
|
phba->cfg_sg_dma_buf_size = sizeof(struct fcp_cmnd) +
|
|
sizeof(struct fcp_rsp) +
|
|
(phba->cfg_sg_seg_cnt * sizeof(struct ulp_bde64));
|
|
|
|
switch (phba->pcidev->device) {
|
|
case PCI_DEVICE_ID_LP101:
|
|
case PCI_DEVICE_ID_BSMB:
|
|
case PCI_DEVICE_ID_ZSMB:
|
|
phba->cfg_hba_queue_depth = LPFC_LP101_HBA_Q_DEPTH;
|
|
break;
|
|
case PCI_DEVICE_ID_RFLY:
|
|
case PCI_DEVICE_ID_PFLY:
|
|
case PCI_DEVICE_ID_BMID:
|
|
case PCI_DEVICE_ID_ZMID:
|
|
case PCI_DEVICE_ID_TFLY:
|
|
phba->cfg_hba_queue_depth = LPFC_LC_HBA_Q_DEPTH;
|
|
break;
|
|
default:
|
|
phba->cfg_hba_queue_depth = LPFC_DFT_HBA_Q_DEPTH;
|
|
}
|
|
return;
|
|
}
|