[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:
James Smart
2008-09-07 11:52:10 -04:00
committed by James Bottomley
parent 977b5a0af6
commit ea2151b4e1
18 changed files with 1207 additions and 15 deletions

View File

@@ -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);
}