[SCSI] lpfc 8.2.8 v2 : Add statistical reporting control and additional fc vendor events
Added support for new sysfs attributes: lpfc_stat_data_ctrl and lpfc_max_scsicmpl_time. The attributes control statistical reporting of io load. Added support for new fc vendor events for error reporting. Signed-off-by: James Smart <james.smart@emulex.com> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
This commit is contained in:

committed by
James Bottomley

parent
977b5a0af6
commit
ea2151b4e1
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "lpfc_hw.h"
|
||||
#include "lpfc_sli.h"
|
||||
#include "lpfc_nl.h"
|
||||
#include "lpfc_disc.h"
|
||||
#include "lpfc_scsi.h"
|
||||
#include "lpfc.h"
|
||||
@@ -2183,6 +2184,335 @@ lpfc_param_store(topology)
|
||||
static DEVICE_ATTR(lpfc_topology, S_IRUGO | S_IWUSR,
|
||||
lpfc_topology_show, lpfc_topology_store);
|
||||
|
||||
|
||||
/**
|
||||
* lpfc_stat_data_ctrl_store: write call back for lpfc_stat_data_ctrl
|
||||
* sysfs file.
|
||||
* @dev: Pointer to class device.
|
||||
* @buf: Data buffer.
|
||||
* @count: Size of the data buffer.
|
||||
*
|
||||
* This function get called when an user write to the lpfc_stat_data_ctrl
|
||||
* sysfs file. This function parse the command written to the sysfs file
|
||||
* and take appropriate action. These commands are used for controlling
|
||||
* driver statistical data collection.
|
||||
* Following are the command this function handles.
|
||||
*
|
||||
* setbucket <bucket_type> <base> <step>
|
||||
* = Set the latency buckets.
|
||||
* destroybucket = destroy all the buckets.
|
||||
* start = start data collection
|
||||
* stop = stop data collection
|
||||
* reset = reset the collected data
|
||||
**/
|
||||
static ssize_t
|
||||
lpfc_stat_data_ctrl_store(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;
|
||||
#define LPFC_MAX_DATA_CTRL_LEN 1024
|
||||
static char bucket_data[LPFC_MAX_DATA_CTRL_LEN];
|
||||
unsigned long i;
|
||||
char *str_ptr, *token;
|
||||
struct lpfc_vport **vports;
|
||||
struct Scsi_Host *v_shost;
|
||||
char *bucket_type_str, *base_str, *step_str;
|
||||
unsigned long base, step, bucket_type;
|
||||
|
||||
if (!strncmp(buf, "setbucket", strlen("setbucket"))) {
|
||||
if (strlen(buf) > LPFC_MAX_DATA_CTRL_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
strcpy(bucket_data, buf);
|
||||
str_ptr = &bucket_data[0];
|
||||
/* Ignore this token - this is command token */
|
||||
token = strsep(&str_ptr, "\t ");
|
||||
if (!token)
|
||||
return -EINVAL;
|
||||
|
||||
bucket_type_str = strsep(&str_ptr, "\t ");
|
||||
if (!bucket_type_str)
|
||||
return -EINVAL;
|
||||
|
||||
if (!strncmp(bucket_type_str, "linear", strlen("linear")))
|
||||
bucket_type = LPFC_LINEAR_BUCKET;
|
||||
else if (!strncmp(bucket_type_str, "power2", strlen("power2")))
|
||||
bucket_type = LPFC_POWER2_BUCKET;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
base_str = strsep(&str_ptr, "\t ");
|
||||
if (!base_str)
|
||||
return -EINVAL;
|
||||
base = simple_strtoul(base_str, NULL, 0);
|
||||
|
||||
step_str = strsep(&str_ptr, "\t ");
|
||||
if (!step_str)
|
||||
return -EINVAL;
|
||||
step = simple_strtoul(step_str, NULL, 0);
|
||||
if (!step)
|
||||
return -EINVAL;
|
||||
|
||||
/* Block the data collection for every vport */
|
||||
vports = lpfc_create_vport_work_array(phba);
|
||||
if (vports == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i <= phba->max_vpi && vports[i] != NULL; i++) {
|
||||
v_shost = lpfc_shost_from_vport(vports[i]);
|
||||
spin_lock_irq(v_shost->host_lock);
|
||||
/* Block and reset data collection */
|
||||
vports[i]->stat_data_blocked = 1;
|
||||
if (vports[i]->stat_data_enabled)
|
||||
lpfc_vport_reset_stat_data(vports[i]);
|
||||
spin_unlock_irq(v_shost->host_lock);
|
||||
}
|
||||
|
||||
/* Set the bucket attributes */
|
||||
phba->bucket_type = bucket_type;
|
||||
phba->bucket_base = base;
|
||||
phba->bucket_step = step;
|
||||
|
||||
for (i = 0; i <= phba->max_vpi && vports[i] != NULL; i++) {
|
||||
v_shost = lpfc_shost_from_vport(vports[i]);
|
||||
|
||||
/* Unblock data collection */
|
||||
spin_lock_irq(v_shost->host_lock);
|
||||
vports[i]->stat_data_blocked = 0;
|
||||
spin_unlock_irq(v_shost->host_lock);
|
||||
}
|
||||
lpfc_destroy_vport_work_array(phba, vports);
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
if (!strncmp(buf, "destroybucket", strlen("destroybucket"))) {
|
||||
vports = lpfc_create_vport_work_array(phba);
|
||||
if (vports == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i <= phba->max_vpi && vports[i] != NULL; i++) {
|
||||
v_shost = lpfc_shost_from_vport(vports[i]);
|
||||
spin_lock_irq(shost->host_lock);
|
||||
vports[i]->stat_data_blocked = 1;
|
||||
lpfc_free_bucket(vport);
|
||||
vport->stat_data_enabled = 0;
|
||||
vports[i]->stat_data_blocked = 0;
|
||||
spin_unlock_irq(shost->host_lock);
|
||||
}
|
||||
lpfc_destroy_vport_work_array(phba, vports);
|
||||
phba->bucket_type = LPFC_NO_BUCKET;
|
||||
phba->bucket_base = 0;
|
||||
phba->bucket_step = 0;
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
if (!strncmp(buf, "start", strlen("start"))) {
|
||||
/* If no buckets configured return error */
|
||||
if (phba->bucket_type == LPFC_NO_BUCKET)
|
||||
return -EINVAL;
|
||||
spin_lock_irq(shost->host_lock);
|
||||
if (vport->stat_data_enabled) {
|
||||
spin_unlock_irq(shost->host_lock);
|
||||
return strlen(buf);
|
||||
}
|
||||
lpfc_alloc_bucket(vport);
|
||||
vport->stat_data_enabled = 1;
|
||||
spin_unlock_irq(shost->host_lock);
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
if (!strncmp(buf, "stop", strlen("stop"))) {
|
||||
spin_lock_irq(shost->host_lock);
|
||||
if (vport->stat_data_enabled == 0) {
|
||||
spin_unlock_irq(shost->host_lock);
|
||||
return strlen(buf);
|
||||
}
|
||||
lpfc_free_bucket(vport);
|
||||
vport->stat_data_enabled = 0;
|
||||
spin_unlock_irq(shost->host_lock);
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
if (!strncmp(buf, "reset", strlen("reset"))) {
|
||||
if ((phba->bucket_type == LPFC_NO_BUCKET)
|
||||
|| !vport->stat_data_enabled)
|
||||
return strlen(buf);
|
||||
spin_lock_irq(shost->host_lock);
|
||||
vport->stat_data_blocked = 1;
|
||||
lpfc_vport_reset_stat_data(vport);
|
||||
vport->stat_data_blocked = 0;
|
||||
spin_unlock_irq(shost->host_lock);
|
||||
return strlen(buf);
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* lpfc_stat_data_ctrl_show: Read callback function for
|
||||
* lpfc_stat_data_ctrl sysfs file.
|
||||
* @dev: Pointer to class device object.
|
||||
* @buf: Data buffer.
|
||||
*
|
||||
* This function is the read call back function for
|
||||
* lpfc_stat_data_ctrl sysfs file. This function report the
|
||||
* current statistical data collection state.
|
||||
**/
|
||||
static ssize_t
|
||||
lpfc_stat_data_ctrl_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 lpfc_hba *phba = vport->phba;
|
||||
int index = 0;
|
||||
int i;
|
||||
char *bucket_type;
|
||||
unsigned long bucket_value;
|
||||
|
||||
switch (phba->bucket_type) {
|
||||
case LPFC_LINEAR_BUCKET:
|
||||
bucket_type = "linear";
|
||||
break;
|
||||
case LPFC_POWER2_BUCKET:
|
||||
bucket_type = "power2";
|
||||
break;
|
||||
default:
|
||||
bucket_type = "No Bucket";
|
||||
break;
|
||||
}
|
||||
|
||||
sprintf(&buf[index], "Statistical Data enabled :%d, "
|
||||
"blocked :%d, Bucket type :%s, Bucket base :%d,"
|
||||
" Bucket step :%d\nLatency Ranges :",
|
||||
vport->stat_data_enabled, vport->stat_data_blocked,
|
||||
bucket_type, phba->bucket_base, phba->bucket_step);
|
||||
index = strlen(buf);
|
||||
if (phba->bucket_type != LPFC_NO_BUCKET) {
|
||||
for (i = 0; i < LPFC_MAX_BUCKET_COUNT; i++) {
|
||||
if (phba->bucket_type == LPFC_LINEAR_BUCKET)
|
||||
bucket_value = phba->bucket_base +
|
||||
phba->bucket_step * i;
|
||||
else
|
||||
bucket_value = phba->bucket_base +
|
||||
(1 << i) * phba->bucket_step;
|
||||
|
||||
if (index + 10 > PAGE_SIZE)
|
||||
break;
|
||||
sprintf(&buf[index], "%08ld ", bucket_value);
|
||||
index = strlen(buf);
|
||||
}
|
||||
}
|
||||
sprintf(&buf[index], "\n");
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sysfs attribute to control the statistical data collection.
|
||||
*/
|
||||
static DEVICE_ATTR(lpfc_stat_data_ctrl, S_IRUGO | S_IWUSR,
|
||||
lpfc_stat_data_ctrl_show, lpfc_stat_data_ctrl_store);
|
||||
|
||||
/*
|
||||
* lpfc_drvr_stat_data: sysfs attr to get driver statistical data.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Each Bucket takes 11 characters and 1 new line + 17 bytes WWN
|
||||
* for each target.
|
||||
*/
|
||||
#define STAT_DATA_SIZE_PER_TARGET(NUM_BUCKETS) ((NUM_BUCKETS) * 11 + 18)
|
||||
#define MAX_STAT_DATA_SIZE_PER_TARGET \
|
||||
STAT_DATA_SIZE_PER_TARGET(LPFC_MAX_BUCKET_COUNT)
|
||||
|
||||
|
||||
/**
|
||||
* sysfs_drvr_stat_data_read: Read callback function for lpfc_drvr_stat_data
|
||||
* sysfs attribute.
|
||||
* @kobj: Pointer to the kernel object
|
||||
* @bin_attr: Attribute object
|
||||
* @buff: Buffer pointer
|
||||
* @off: File offset
|
||||
* @count: Buffer size
|
||||
*
|
||||
* This function is the read call back function for lpfc_drvr_stat_data
|
||||
* sysfs file. This function export the statistical data to user
|
||||
* applications.
|
||||
**/
|
||||
static ssize_t
|
||||
sysfs_drvr_stat_data_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;
|
||||
int i = 0, index = 0;
|
||||
unsigned long nport_index;
|
||||
struct lpfc_nodelist *ndlp = NULL;
|
||||
nport_index = (unsigned long)off /
|
||||
MAX_STAT_DATA_SIZE_PER_TARGET;
|
||||
|
||||
if (!vport->stat_data_enabled || vport->stat_data_blocked
|
||||
|| (phba->bucket_type == LPFC_NO_BUCKET))
|
||||
return 0;
|
||||
|
||||
spin_lock_irq(shost->host_lock);
|
||||
list_for_each_entry(ndlp, &vport->fc_nodes, nlp_listp) {
|
||||
if (!NLP_CHK_NODE_ACT(ndlp) || !ndlp->lat_data)
|
||||
continue;
|
||||
|
||||
if (nport_index > 0) {
|
||||
nport_index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((index + MAX_STAT_DATA_SIZE_PER_TARGET)
|
||||
> count)
|
||||
break;
|
||||
|
||||
if (!ndlp->lat_data)
|
||||
continue;
|
||||
|
||||
/* Print the WWN */
|
||||
sprintf(&buf[index], "%02x%02x%02x%02x%02x%02x%02x%02x:",
|
||||
ndlp->nlp_portname.u.wwn[0],
|
||||
ndlp->nlp_portname.u.wwn[1],
|
||||
ndlp->nlp_portname.u.wwn[2],
|
||||
ndlp->nlp_portname.u.wwn[3],
|
||||
ndlp->nlp_portname.u.wwn[4],
|
||||
ndlp->nlp_portname.u.wwn[5],
|
||||
ndlp->nlp_portname.u.wwn[6],
|
||||
ndlp->nlp_portname.u.wwn[7]);
|
||||
|
||||
index = strlen(buf);
|
||||
|
||||
for (i = 0; i < LPFC_MAX_BUCKET_COUNT; i++) {
|
||||
sprintf(&buf[index], "%010u,",
|
||||
ndlp->lat_data[i].cmd_count);
|
||||
index = strlen(buf);
|
||||
}
|
||||
sprintf(&buf[index], "\n");
|
||||
index = strlen(buf);
|
||||
}
|
||||
spin_unlock_irq(shost->host_lock);
|
||||
return index;
|
||||
}
|
||||
|
||||
static struct bin_attribute sysfs_drvr_stat_data_attr = {
|
||||
.attr = {
|
||||
.name = "lpfc_drvr_stat_data",
|
||||
.mode = S_IRUSR,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.size = LPFC_MAX_TARGET * MAX_STAT_DATA_SIZE_PER_TARGET,
|
||||
.read = sysfs_drvr_stat_data_read,
|
||||
.write = NULL,
|
||||
};
|
||||
|
||||
/*
|
||||
# lpfc_link_speed: Link speed selection for initializing the Fibre Channel
|
||||
# connection.
|
||||
@@ -2502,6 +2832,7 @@ struct device_attribute *lpfc_hba_attrs[] = {
|
||||
&dev_attr_lpfc_enable_hba_heartbeat,
|
||||
&dev_attr_lpfc_sg_seg_cnt,
|
||||
&dev_attr_lpfc_max_scsicmpl_time,
|
||||
&dev_attr_lpfc_stat_data_ctrl,
|
||||
NULL,
|
||||
};
|
||||
|
||||
@@ -2524,6 +2855,8 @@ struct device_attribute *lpfc_vport_attrs[] = {
|
||||
&dev_attr_nport_evt_cnt,
|
||||
&dev_attr_npiv_info,
|
||||
&dev_attr_lpfc_enable_da_id,
|
||||
&dev_attr_lpfc_max_scsicmpl_time,
|
||||
&dev_attr_lpfc_stat_data_ctrl,
|
||||
NULL,
|
||||
};
|
||||
|
||||
@@ -2958,7 +3291,14 @@ lpfc_alloc_sysfs_attr(struct lpfc_vport *vport)
|
||||
if (error)
|
||||
goto out_remove_ctlreg_attr;
|
||||
|
||||
error = sysfs_create_bin_file(&shost->shost_dev.kobj,
|
||||
&sysfs_drvr_stat_data_attr);
|
||||
if (error)
|
||||
goto out_remove_mbox_attr;
|
||||
|
||||
return 0;
|
||||
out_remove_mbox_attr:
|
||||
sysfs_remove_bin_file(&shost->shost_dev.kobj, &sysfs_mbox_attr);
|
||||
out_remove_ctlreg_attr:
|
||||
sysfs_remove_bin_file(&shost->shost_dev.kobj, &sysfs_ctlreg_attr);
|
||||
out:
|
||||
@@ -2973,7 +3313,8 @@ void
|
||||
lpfc_free_sysfs_attr(struct lpfc_vport *vport)
|
||||
{
|
||||
struct Scsi_Host *shost = lpfc_shost_from_vport(vport);
|
||||
|
||||
sysfs_remove_bin_file(&shost->shost_dev.kobj,
|
||||
&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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user