![Nicholas Bellinger](/assets/img/avatar_default.png)
LIO target is a full featured in-kernel target framework with the following feature set: High-performance, non-blocking, multithreaded architecture with SIMD support. Advanced SCSI feature set: * Persistent Reservations (PRs) * Asymmetric Logical Unit Assignment (ALUA) * Protocol and intra-nexus multiplexing, load-balancing and failover (MC/S) * Full Error Recovery (ERL=0,1,2) * Active/active task migration and session continuation (ERL=2) * Thin LUN provisioning (UNMAP and WRITE_SAMExx) Multiprotocol target plugins Storage media independence: * Virtualization of all storage media; transparent mapping of IO to LUNs * No hard limits on number of LUNs per Target; maximum LUN size ~750 TB * Backstores: SATA, SAS, SCSI, BluRay, DVD, FLASH, USB, ramdisk, etc. Standards compliance: * Full compliance with IETF (RFC 3720) * Full implementation of SPC-4 PRs and ALUA Significant code cleanups done by Christoph Hellwig. [jejb: fix up for new block bdev exclusive interface. Minor fixes from Randy Dunlap and Dan Carpenter.] Signed-off-by: Nicholas A. Bellinger <nab@linux-iscsi.org> Signed-off-by: James Bottomley <James.Bottomley@suse.de>
1079 lines
27 KiB
C
1079 lines
27 KiB
C
/*******************************************************************************
|
|
* Filename: target_core_mib.c
|
|
*
|
|
* Copyright (c) 2006-2007 SBE, Inc. All Rights Reserved.
|
|
* Copyright (c) 2007-2010 Rising Tide Systems
|
|
* Copyright (c) 2008-2010 Linux-iSCSI.org
|
|
*
|
|
* Nicholas A. Bellinger <nab@linux-iscsi.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
******************************************************************************/
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/string.h>
|
|
#include <linux/version.h>
|
|
#include <generated/utsrelease.h>
|
|
#include <linux/utsname.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/blkdev.h>
|
|
#include <scsi/scsi.h>
|
|
#include <scsi/scsi_device.h>
|
|
#include <scsi/scsi_host.h>
|
|
|
|
#include <target/target_core_base.h>
|
|
#include <target/target_core_transport.h>
|
|
#include <target/target_core_fabric_ops.h>
|
|
#include <target/target_core_configfs.h>
|
|
|
|
#include "target_core_hba.h"
|
|
#include "target_core_mib.h"
|
|
|
|
/* SCSI mib table index */
|
|
static struct scsi_index_table scsi_index_table;
|
|
|
|
#ifndef INITIAL_JIFFIES
|
|
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))
|
|
#endif
|
|
|
|
/* SCSI Instance Table */
|
|
#define SCSI_INST_SW_INDEX 1
|
|
#define SCSI_TRANSPORT_INDEX 1
|
|
|
|
#define NONE "None"
|
|
#define ISPRINT(a) ((a >= ' ') && (a <= '~'))
|
|
|
|
static inline int list_is_first(const struct list_head *list,
|
|
const struct list_head *head)
|
|
{
|
|
return list->prev == head;
|
|
}
|
|
|
|
static void *locate_hba_start(
|
|
struct seq_file *seq,
|
|
loff_t *pos)
|
|
{
|
|
spin_lock(&se_global->g_device_lock);
|
|
return seq_list_start(&se_global->g_se_dev_list, *pos);
|
|
}
|
|
|
|
static void *locate_hba_next(
|
|
struct seq_file *seq,
|
|
void *v,
|
|
loff_t *pos)
|
|
{
|
|
return seq_list_next(v, &se_global->g_se_dev_list, pos);
|
|
}
|
|
|
|
static void locate_hba_stop(struct seq_file *seq, void *v)
|
|
{
|
|
spin_unlock(&se_global->g_device_lock);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* SCSI MIB Tables
|
|
****************************************************************************/
|
|
|
|
/*
|
|
* SCSI Instance Table
|
|
*/
|
|
static void *scsi_inst_seq_start(
|
|
struct seq_file *seq,
|
|
loff_t *pos)
|
|
{
|
|
spin_lock(&se_global->hba_lock);
|
|
return seq_list_start(&se_global->g_hba_list, *pos);
|
|
}
|
|
|
|
static void *scsi_inst_seq_next(
|
|
struct seq_file *seq,
|
|
void *v,
|
|
loff_t *pos)
|
|
{
|
|
return seq_list_next(v, &se_global->g_hba_list, pos);
|
|
}
|
|
|
|
static void scsi_inst_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
spin_unlock(&se_global->hba_lock);
|
|
}
|
|
|
|
static int scsi_inst_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct se_hba *hba = list_entry(v, struct se_hba, hba_list);
|
|
|
|
if (list_is_first(&hba->hba_list, &se_global->g_hba_list))
|
|
seq_puts(seq, "inst sw_indx\n");
|
|
|
|
seq_printf(seq, "%u %u\n", hba->hba_index, SCSI_INST_SW_INDEX);
|
|
seq_printf(seq, "plugin: %s version: %s\n",
|
|
hba->transport->name, TARGET_CORE_VERSION);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations scsi_inst_seq_ops = {
|
|
.start = scsi_inst_seq_start,
|
|
.next = scsi_inst_seq_next,
|
|
.stop = scsi_inst_seq_stop,
|
|
.show = scsi_inst_seq_show
|
|
};
|
|
|
|
static int scsi_inst_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &scsi_inst_seq_ops);
|
|
}
|
|
|
|
static const struct file_operations scsi_inst_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = scsi_inst_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/*
|
|
* SCSI Device Table
|
|
*/
|
|
static void *scsi_dev_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
return locate_hba_start(seq, pos);
|
|
}
|
|
|
|
static void *scsi_dev_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
return locate_hba_next(seq, v, pos);
|
|
}
|
|
|
|
static void scsi_dev_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
locate_hba_stop(seq, v);
|
|
}
|
|
|
|
static int scsi_dev_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct se_hba *hba;
|
|
struct se_subsystem_dev *se_dev = list_entry(v, struct se_subsystem_dev,
|
|
g_se_dev_list);
|
|
struct se_device *dev = se_dev->se_dev_ptr;
|
|
char str[28];
|
|
int k;
|
|
|
|
if (list_is_first(&se_dev->g_se_dev_list, &se_global->g_se_dev_list))
|
|
seq_puts(seq, "inst indx role ports\n");
|
|
|
|
if (!(dev))
|
|
return 0;
|
|
|
|
hba = dev->se_hba;
|
|
if (!(hba)) {
|
|
/* Log error ? */
|
|
return 0;
|
|
}
|
|
|
|
seq_printf(seq, "%u %u %s %u\n", hba->hba_index,
|
|
dev->dev_index, "Target", dev->dev_port_count);
|
|
|
|
memcpy(&str[0], (void *)DEV_T10_WWN(dev), 28);
|
|
|
|
/* vendor */
|
|
for (k = 0; k < 8; k++)
|
|
str[k] = ISPRINT(DEV_T10_WWN(dev)->vendor[k]) ?
|
|
DEV_T10_WWN(dev)->vendor[k] : 0x20;
|
|
str[k] = 0x20;
|
|
|
|
/* model */
|
|
for (k = 0; k < 16; k++)
|
|
str[k+9] = ISPRINT(DEV_T10_WWN(dev)->model[k]) ?
|
|
DEV_T10_WWN(dev)->model[k] : 0x20;
|
|
str[k + 9] = 0;
|
|
|
|
seq_printf(seq, "dev_alias: %s\n", str);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations scsi_dev_seq_ops = {
|
|
.start = scsi_dev_seq_start,
|
|
.next = scsi_dev_seq_next,
|
|
.stop = scsi_dev_seq_stop,
|
|
.show = scsi_dev_seq_show
|
|
};
|
|
|
|
static int scsi_dev_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &scsi_dev_seq_ops);
|
|
}
|
|
|
|
static const struct file_operations scsi_dev_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = scsi_dev_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/*
|
|
* SCSI Port Table
|
|
*/
|
|
static void *scsi_port_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
return locate_hba_start(seq, pos);
|
|
}
|
|
|
|
static void *scsi_port_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
return locate_hba_next(seq, v, pos);
|
|
}
|
|
|
|
static void scsi_port_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
locate_hba_stop(seq, v);
|
|
}
|
|
|
|
static int scsi_port_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct se_hba *hba;
|
|
struct se_subsystem_dev *se_dev = list_entry(v, struct se_subsystem_dev,
|
|
g_se_dev_list);
|
|
struct se_device *dev = se_dev->se_dev_ptr;
|
|
struct se_port *sep, *sep_tmp;
|
|
|
|
if (list_is_first(&se_dev->g_se_dev_list, &se_global->g_se_dev_list))
|
|
seq_puts(seq, "inst device indx role busy_count\n");
|
|
|
|
if (!(dev))
|
|
return 0;
|
|
|
|
hba = dev->se_hba;
|
|
if (!(hba)) {
|
|
/* Log error ? */
|
|
return 0;
|
|
}
|
|
|
|
/* FIXME: scsiPortBusyStatuses count */
|
|
spin_lock(&dev->se_port_lock);
|
|
list_for_each_entry_safe(sep, sep_tmp, &dev->dev_sep_list, sep_list) {
|
|
seq_printf(seq, "%u %u %u %s%u %u\n", hba->hba_index,
|
|
dev->dev_index, sep->sep_index, "Device",
|
|
dev->dev_index, 0);
|
|
}
|
|
spin_unlock(&dev->se_port_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations scsi_port_seq_ops = {
|
|
.start = scsi_port_seq_start,
|
|
.next = scsi_port_seq_next,
|
|
.stop = scsi_port_seq_stop,
|
|
.show = scsi_port_seq_show
|
|
};
|
|
|
|
static int scsi_port_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &scsi_port_seq_ops);
|
|
}
|
|
|
|
static const struct file_operations scsi_port_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = scsi_port_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/*
|
|
* SCSI Transport Table
|
|
*/
|
|
static void *scsi_transport_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
return locate_hba_start(seq, pos);
|
|
}
|
|
|
|
static void *scsi_transport_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
return locate_hba_next(seq, v, pos);
|
|
}
|
|
|
|
static void scsi_transport_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
locate_hba_stop(seq, v);
|
|
}
|
|
|
|
static int scsi_transport_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct se_hba *hba;
|
|
struct se_subsystem_dev *se_dev = list_entry(v, struct se_subsystem_dev,
|
|
g_se_dev_list);
|
|
struct se_device *dev = se_dev->se_dev_ptr;
|
|
struct se_port *se, *se_tmp;
|
|
struct se_portal_group *tpg;
|
|
struct t10_wwn *wwn;
|
|
char buf[64];
|
|
|
|
if (list_is_first(&se_dev->g_se_dev_list, &se_global->g_se_dev_list))
|
|
seq_puts(seq, "inst device indx dev_name\n");
|
|
|
|
if (!(dev))
|
|
return 0;
|
|
|
|
hba = dev->se_hba;
|
|
if (!(hba)) {
|
|
/* Log error ? */
|
|
return 0;
|
|
}
|
|
|
|
wwn = DEV_T10_WWN(dev);
|
|
|
|
spin_lock(&dev->se_port_lock);
|
|
list_for_each_entry_safe(se, se_tmp, &dev->dev_sep_list, sep_list) {
|
|
tpg = se->sep_tpg;
|
|
sprintf(buf, "scsiTransport%s",
|
|
TPG_TFO(tpg)->get_fabric_name());
|
|
|
|
seq_printf(seq, "%u %s %u %s+%s\n",
|
|
hba->hba_index, /* scsiTransportIndex */
|
|
buf, /* scsiTransportType */
|
|
(TPG_TFO(tpg)->tpg_get_inst_index != NULL) ?
|
|
TPG_TFO(tpg)->tpg_get_inst_index(tpg) :
|
|
0,
|
|
TPG_TFO(tpg)->tpg_get_wwn(tpg),
|
|
(strlen(wwn->unit_serial)) ?
|
|
/* scsiTransportDevName */
|
|
wwn->unit_serial : wwn->vendor);
|
|
}
|
|
spin_unlock(&dev->se_port_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations scsi_transport_seq_ops = {
|
|
.start = scsi_transport_seq_start,
|
|
.next = scsi_transport_seq_next,
|
|
.stop = scsi_transport_seq_stop,
|
|
.show = scsi_transport_seq_show
|
|
};
|
|
|
|
static int scsi_transport_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &scsi_transport_seq_ops);
|
|
}
|
|
|
|
static const struct file_operations scsi_transport_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = scsi_transport_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/*
|
|
* SCSI Target Device Table
|
|
*/
|
|
static void *scsi_tgt_dev_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
return locate_hba_start(seq, pos);
|
|
}
|
|
|
|
static void *scsi_tgt_dev_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
return locate_hba_next(seq, v, pos);
|
|
}
|
|
|
|
static void scsi_tgt_dev_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
locate_hba_stop(seq, v);
|
|
}
|
|
|
|
|
|
#define LU_COUNT 1 /* for now */
|
|
static int scsi_tgt_dev_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct se_hba *hba;
|
|
struct se_subsystem_dev *se_dev = list_entry(v, struct se_subsystem_dev,
|
|
g_se_dev_list);
|
|
struct se_device *dev = se_dev->se_dev_ptr;
|
|
int non_accessible_lus = 0;
|
|
char status[16];
|
|
|
|
if (list_is_first(&se_dev->g_se_dev_list, &se_global->g_se_dev_list))
|
|
seq_puts(seq, "inst indx num_LUs status non_access_LUs"
|
|
" resets\n");
|
|
|
|
if (!(dev))
|
|
return 0;
|
|
|
|
hba = dev->se_hba;
|
|
if (!(hba)) {
|
|
/* Log error ? */
|
|
return 0;
|
|
}
|
|
|
|
switch (dev->dev_status) {
|
|
case TRANSPORT_DEVICE_ACTIVATED:
|
|
strcpy(status, "activated");
|
|
break;
|
|
case TRANSPORT_DEVICE_DEACTIVATED:
|
|
strcpy(status, "deactivated");
|
|
non_accessible_lus = 1;
|
|
break;
|
|
case TRANSPORT_DEVICE_SHUTDOWN:
|
|
strcpy(status, "shutdown");
|
|
non_accessible_lus = 1;
|
|
break;
|
|
case TRANSPORT_DEVICE_OFFLINE_ACTIVATED:
|
|
case TRANSPORT_DEVICE_OFFLINE_DEACTIVATED:
|
|
strcpy(status, "offline");
|
|
non_accessible_lus = 1;
|
|
break;
|
|
default:
|
|
sprintf(status, "unknown(%d)", dev->dev_status);
|
|
non_accessible_lus = 1;
|
|
}
|
|
|
|
seq_printf(seq, "%u %u %u %s %u %u\n",
|
|
hba->hba_index, dev->dev_index, LU_COUNT,
|
|
status, non_accessible_lus, dev->num_resets);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations scsi_tgt_dev_seq_ops = {
|
|
.start = scsi_tgt_dev_seq_start,
|
|
.next = scsi_tgt_dev_seq_next,
|
|
.stop = scsi_tgt_dev_seq_stop,
|
|
.show = scsi_tgt_dev_seq_show
|
|
};
|
|
|
|
static int scsi_tgt_dev_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &scsi_tgt_dev_seq_ops);
|
|
}
|
|
|
|
static const struct file_operations scsi_tgt_dev_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = scsi_tgt_dev_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/*
|
|
* SCSI Target Port Table
|
|
*/
|
|
static void *scsi_tgt_port_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
return locate_hba_start(seq, pos);
|
|
}
|
|
|
|
static void *scsi_tgt_port_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
return locate_hba_next(seq, v, pos);
|
|
}
|
|
|
|
static void scsi_tgt_port_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
locate_hba_stop(seq, v);
|
|
}
|
|
|
|
static int scsi_tgt_port_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct se_hba *hba;
|
|
struct se_subsystem_dev *se_dev = list_entry(v, struct se_subsystem_dev,
|
|
g_se_dev_list);
|
|
struct se_device *dev = se_dev->se_dev_ptr;
|
|
struct se_port *sep, *sep_tmp;
|
|
struct se_portal_group *tpg;
|
|
u32 rx_mbytes, tx_mbytes;
|
|
unsigned long long num_cmds;
|
|
char buf[64];
|
|
|
|
if (list_is_first(&se_dev->g_se_dev_list, &se_global->g_se_dev_list))
|
|
seq_puts(seq, "inst device indx name port_index in_cmds"
|
|
" write_mbytes read_mbytes hs_in_cmds\n");
|
|
|
|
if (!(dev))
|
|
return 0;
|
|
|
|
hba = dev->se_hba;
|
|
if (!(hba)) {
|
|
/* Log error ? */
|
|
return 0;
|
|
}
|
|
|
|
spin_lock(&dev->se_port_lock);
|
|
list_for_each_entry_safe(sep, sep_tmp, &dev->dev_sep_list, sep_list) {
|
|
tpg = sep->sep_tpg;
|
|
sprintf(buf, "%sPort#",
|
|
TPG_TFO(tpg)->get_fabric_name());
|
|
|
|
seq_printf(seq, "%u %u %u %s%d %s%s%d ",
|
|
hba->hba_index,
|
|
dev->dev_index,
|
|
sep->sep_index,
|
|
buf, sep->sep_index,
|
|
TPG_TFO(tpg)->tpg_get_wwn(tpg), "+t+",
|
|
TPG_TFO(tpg)->tpg_get_tag(tpg));
|
|
|
|
spin_lock(&sep->sep_lun->lun_sep_lock);
|
|
num_cmds = sep->sep_stats.cmd_pdus;
|
|
rx_mbytes = (sep->sep_stats.rx_data_octets >> 20);
|
|
tx_mbytes = (sep->sep_stats.tx_data_octets >> 20);
|
|
spin_unlock(&sep->sep_lun->lun_sep_lock);
|
|
|
|
seq_printf(seq, "%llu %u %u %u\n", num_cmds,
|
|
rx_mbytes, tx_mbytes, 0);
|
|
}
|
|
spin_unlock(&dev->se_port_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations scsi_tgt_port_seq_ops = {
|
|
.start = scsi_tgt_port_seq_start,
|
|
.next = scsi_tgt_port_seq_next,
|
|
.stop = scsi_tgt_port_seq_stop,
|
|
.show = scsi_tgt_port_seq_show
|
|
};
|
|
|
|
static int scsi_tgt_port_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &scsi_tgt_port_seq_ops);
|
|
}
|
|
|
|
static const struct file_operations scsi_tgt_port_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = scsi_tgt_port_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/*
|
|
* SCSI Authorized Initiator Table:
|
|
* It contains the SCSI Initiators authorized to be attached to one of the
|
|
* local Target ports.
|
|
* Iterates through all active TPGs and extracts the info from the ACLs
|
|
*/
|
|
static void *scsi_auth_intr_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
spin_lock_bh(&se_global->se_tpg_lock);
|
|
return seq_list_start(&se_global->g_se_tpg_list, *pos);
|
|
}
|
|
|
|
static void *scsi_auth_intr_seq_next(struct seq_file *seq, void *v,
|
|
loff_t *pos)
|
|
{
|
|
return seq_list_next(v, &se_global->g_se_tpg_list, pos);
|
|
}
|
|
|
|
static void scsi_auth_intr_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
spin_unlock_bh(&se_global->se_tpg_lock);
|
|
}
|
|
|
|
static int scsi_auth_intr_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct se_portal_group *se_tpg = list_entry(v, struct se_portal_group,
|
|
se_tpg_list);
|
|
struct se_dev_entry *deve;
|
|
struct se_lun *lun;
|
|
struct se_node_acl *se_nacl;
|
|
int j;
|
|
|
|
if (list_is_first(&se_tpg->se_tpg_list,
|
|
&se_global->g_se_tpg_list))
|
|
seq_puts(seq, "inst dev port indx dev_or_port intr_name "
|
|
"map_indx att_count num_cmds read_mbytes "
|
|
"write_mbytes hs_num_cmds creation_time row_status\n");
|
|
|
|
if (!(se_tpg))
|
|
return 0;
|
|
|
|
spin_lock(&se_tpg->acl_node_lock);
|
|
list_for_each_entry(se_nacl, &se_tpg->acl_node_list, acl_list) {
|
|
|
|
atomic_inc(&se_nacl->mib_ref_count);
|
|
smp_mb__after_atomic_inc();
|
|
spin_unlock(&se_tpg->acl_node_lock);
|
|
|
|
spin_lock_irq(&se_nacl->device_list_lock);
|
|
for (j = 0; j < TRANSPORT_MAX_LUNS_PER_TPG; j++) {
|
|
deve = &se_nacl->device_list[j];
|
|
if (!(deve->lun_flags &
|
|
TRANSPORT_LUNFLAGS_INITIATOR_ACCESS) ||
|
|
(!deve->se_lun))
|
|
continue;
|
|
lun = deve->se_lun;
|
|
if (!lun->lun_se_dev)
|
|
continue;
|
|
|
|
seq_printf(seq, "%u %u %u %u %u %s %u %u %u %u %u %u"
|
|
" %u %s\n",
|
|
/* scsiInstIndex */
|
|
(TPG_TFO(se_tpg)->tpg_get_inst_index != NULL) ?
|
|
TPG_TFO(se_tpg)->tpg_get_inst_index(se_tpg) :
|
|
0,
|
|
/* scsiDeviceIndex */
|
|
lun->lun_se_dev->dev_index,
|
|
/* scsiAuthIntrTgtPortIndex */
|
|
TPG_TFO(se_tpg)->tpg_get_tag(se_tpg),
|
|
/* scsiAuthIntrIndex */
|
|
se_nacl->acl_index,
|
|
/* scsiAuthIntrDevOrPort */
|
|
1,
|
|
/* scsiAuthIntrName */
|
|
se_nacl->initiatorname[0] ?
|
|
se_nacl->initiatorname : NONE,
|
|
/* FIXME: scsiAuthIntrLunMapIndex */
|
|
0,
|
|
/* scsiAuthIntrAttachedTimes */
|
|
deve->attach_count,
|
|
/* scsiAuthIntrOutCommands */
|
|
deve->total_cmds,
|
|
/* scsiAuthIntrReadMegaBytes */
|
|
(u32)(deve->read_bytes >> 20),
|
|
/* scsiAuthIntrWrittenMegaBytes */
|
|
(u32)(deve->write_bytes >> 20),
|
|
/* FIXME: scsiAuthIntrHSOutCommands */
|
|
0,
|
|
/* scsiAuthIntrLastCreation */
|
|
(u32)(((u32)deve->creation_time -
|
|
INITIAL_JIFFIES) * 100 / HZ),
|
|
/* FIXME: scsiAuthIntrRowStatus */
|
|
"Ready");
|
|
}
|
|
spin_unlock_irq(&se_nacl->device_list_lock);
|
|
|
|
spin_lock(&se_tpg->acl_node_lock);
|
|
atomic_dec(&se_nacl->mib_ref_count);
|
|
smp_mb__after_atomic_dec();
|
|
}
|
|
spin_unlock(&se_tpg->acl_node_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations scsi_auth_intr_seq_ops = {
|
|
.start = scsi_auth_intr_seq_start,
|
|
.next = scsi_auth_intr_seq_next,
|
|
.stop = scsi_auth_intr_seq_stop,
|
|
.show = scsi_auth_intr_seq_show
|
|
};
|
|
|
|
static int scsi_auth_intr_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &scsi_auth_intr_seq_ops);
|
|
}
|
|
|
|
static const struct file_operations scsi_auth_intr_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = scsi_auth_intr_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/*
|
|
* SCSI Attached Initiator Port Table:
|
|
* It lists the SCSI Initiators attached to one of the local Target ports.
|
|
* Iterates through all active TPGs and use active sessions from each TPG
|
|
* to list the info fo this table.
|
|
*/
|
|
static void *scsi_att_intr_port_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
spin_lock_bh(&se_global->se_tpg_lock);
|
|
return seq_list_start(&se_global->g_se_tpg_list, *pos);
|
|
}
|
|
|
|
static void *scsi_att_intr_port_seq_next(struct seq_file *seq, void *v,
|
|
loff_t *pos)
|
|
{
|
|
return seq_list_next(v, &se_global->g_se_tpg_list, pos);
|
|
}
|
|
|
|
static void scsi_att_intr_port_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
spin_unlock_bh(&se_global->se_tpg_lock);
|
|
}
|
|
|
|
static int scsi_att_intr_port_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct se_portal_group *se_tpg = list_entry(v, struct se_portal_group,
|
|
se_tpg_list);
|
|
struct se_dev_entry *deve;
|
|
struct se_lun *lun;
|
|
struct se_node_acl *se_nacl;
|
|
struct se_session *se_sess;
|
|
unsigned char buf[64];
|
|
int j;
|
|
|
|
if (list_is_first(&se_tpg->se_tpg_list,
|
|
&se_global->g_se_tpg_list))
|
|
seq_puts(seq, "inst dev port indx port_auth_indx port_name"
|
|
" port_ident\n");
|
|
|
|
if (!(se_tpg))
|
|
return 0;
|
|
|
|
spin_lock(&se_tpg->session_lock);
|
|
list_for_each_entry(se_sess, &se_tpg->tpg_sess_list, sess_list) {
|
|
if ((TPG_TFO(se_tpg)->sess_logged_in(se_sess)) ||
|
|
(!se_sess->se_node_acl) ||
|
|
(!se_sess->se_node_acl->device_list))
|
|
continue;
|
|
|
|
atomic_inc(&se_sess->mib_ref_count);
|
|
smp_mb__after_atomic_inc();
|
|
se_nacl = se_sess->se_node_acl;
|
|
atomic_inc(&se_nacl->mib_ref_count);
|
|
smp_mb__after_atomic_inc();
|
|
spin_unlock(&se_tpg->session_lock);
|
|
|
|
spin_lock_irq(&se_nacl->device_list_lock);
|
|
for (j = 0; j < TRANSPORT_MAX_LUNS_PER_TPG; j++) {
|
|
deve = &se_nacl->device_list[j];
|
|
if (!(deve->lun_flags &
|
|
TRANSPORT_LUNFLAGS_INITIATOR_ACCESS) ||
|
|
(!deve->se_lun))
|
|
continue;
|
|
|
|
lun = deve->se_lun;
|
|
if (!lun->lun_se_dev)
|
|
continue;
|
|
|
|
memset(buf, 0, 64);
|
|
if (TPG_TFO(se_tpg)->sess_get_initiator_sid != NULL)
|
|
TPG_TFO(se_tpg)->sess_get_initiator_sid(
|
|
se_sess, (unsigned char *)&buf[0], 64);
|
|
|
|
seq_printf(seq, "%u %u %u %u %u %s+i+%s\n",
|
|
/* scsiInstIndex */
|
|
(TPG_TFO(se_tpg)->tpg_get_inst_index != NULL) ?
|
|
TPG_TFO(se_tpg)->tpg_get_inst_index(se_tpg) :
|
|
0,
|
|
/* scsiDeviceIndex */
|
|
lun->lun_se_dev->dev_index,
|
|
/* scsiPortIndex */
|
|
TPG_TFO(se_tpg)->tpg_get_tag(se_tpg),
|
|
/* scsiAttIntrPortIndex */
|
|
(TPG_TFO(se_tpg)->sess_get_index != NULL) ?
|
|
TPG_TFO(se_tpg)->sess_get_index(se_sess) :
|
|
0,
|
|
/* scsiAttIntrPortAuthIntrIdx */
|
|
se_nacl->acl_index,
|
|
/* scsiAttIntrPortName */
|
|
se_nacl->initiatorname[0] ?
|
|
se_nacl->initiatorname : NONE,
|
|
/* scsiAttIntrPortIdentifier */
|
|
buf);
|
|
}
|
|
spin_unlock_irq(&se_nacl->device_list_lock);
|
|
|
|
spin_lock(&se_tpg->session_lock);
|
|
atomic_dec(&se_nacl->mib_ref_count);
|
|
smp_mb__after_atomic_dec();
|
|
atomic_dec(&se_sess->mib_ref_count);
|
|
smp_mb__after_atomic_dec();
|
|
}
|
|
spin_unlock(&se_tpg->session_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations scsi_att_intr_port_seq_ops = {
|
|
.start = scsi_att_intr_port_seq_start,
|
|
.next = scsi_att_intr_port_seq_next,
|
|
.stop = scsi_att_intr_port_seq_stop,
|
|
.show = scsi_att_intr_port_seq_show
|
|
};
|
|
|
|
static int scsi_att_intr_port_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &scsi_att_intr_port_seq_ops);
|
|
}
|
|
|
|
static const struct file_operations scsi_att_intr_port_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = scsi_att_intr_port_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/*
|
|
* SCSI Logical Unit Table
|
|
*/
|
|
static void *scsi_lu_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
return locate_hba_start(seq, pos);
|
|
}
|
|
|
|
static void *scsi_lu_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
return locate_hba_next(seq, v, pos);
|
|
}
|
|
|
|
static void scsi_lu_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
locate_hba_stop(seq, v);
|
|
}
|
|
|
|
#define SCSI_LU_INDEX 1
|
|
static int scsi_lu_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
struct se_hba *hba;
|
|
struct se_subsystem_dev *se_dev = list_entry(v, struct se_subsystem_dev,
|
|
g_se_dev_list);
|
|
struct se_device *dev = se_dev->se_dev_ptr;
|
|
int j;
|
|
char str[28];
|
|
|
|
if (list_is_first(&se_dev->g_se_dev_list, &se_global->g_se_dev_list))
|
|
seq_puts(seq, "inst dev indx LUN lu_name vend prod rev"
|
|
" dev_type status state-bit num_cmds read_mbytes"
|
|
" write_mbytes resets full_stat hs_num_cmds creation_time\n");
|
|
|
|
if (!(dev))
|
|
return 0;
|
|
|
|
hba = dev->se_hba;
|
|
if (!(hba)) {
|
|
/* Log error ? */
|
|
return 0;
|
|
}
|
|
|
|
/* Fix LU state, if we can read it from the device */
|
|
seq_printf(seq, "%u %u %u %llu %s", hba->hba_index,
|
|
dev->dev_index, SCSI_LU_INDEX,
|
|
(unsigned long long)0, /* FIXME: scsiLuDefaultLun */
|
|
(strlen(DEV_T10_WWN(dev)->unit_serial)) ?
|
|
/* scsiLuWwnName */
|
|
(char *)&DEV_T10_WWN(dev)->unit_serial[0] :
|
|
"None");
|
|
|
|
memcpy(&str[0], (void *)DEV_T10_WWN(dev), 28);
|
|
/* scsiLuVendorId */
|
|
for (j = 0; j < 8; j++)
|
|
str[j] = ISPRINT(DEV_T10_WWN(dev)->vendor[j]) ?
|
|
DEV_T10_WWN(dev)->vendor[j] : 0x20;
|
|
str[8] = 0;
|
|
seq_printf(seq, " %s", str);
|
|
|
|
/* scsiLuProductId */
|
|
for (j = 0; j < 16; j++)
|
|
str[j] = ISPRINT(DEV_T10_WWN(dev)->model[j]) ?
|
|
DEV_T10_WWN(dev)->model[j] : 0x20;
|
|
str[16] = 0;
|
|
seq_printf(seq, " %s", str);
|
|
|
|
/* scsiLuRevisionId */
|
|
for (j = 0; j < 4; j++)
|
|
str[j] = ISPRINT(DEV_T10_WWN(dev)->revision[j]) ?
|
|
DEV_T10_WWN(dev)->revision[j] : 0x20;
|
|
str[4] = 0;
|
|
seq_printf(seq, " %s", str);
|
|
|
|
seq_printf(seq, " %u %s %s %llu %u %u %u %u %u %u\n",
|
|
/* scsiLuPeripheralType */
|
|
TRANSPORT(dev)->get_device_type(dev),
|
|
(dev->dev_status == TRANSPORT_DEVICE_ACTIVATED) ?
|
|
"available" : "notavailable", /* scsiLuStatus */
|
|
"exposed", /* scsiLuState */
|
|
(unsigned long long)dev->num_cmds,
|
|
/* scsiLuReadMegaBytes */
|
|
(u32)(dev->read_bytes >> 20),
|
|
/* scsiLuWrittenMegaBytes */
|
|
(u32)(dev->write_bytes >> 20),
|
|
dev->num_resets, /* scsiLuInResets */
|
|
0, /* scsiLuOutTaskSetFullStatus */
|
|
0, /* scsiLuHSInCommands */
|
|
(u32)(((u32)dev->creation_time - INITIAL_JIFFIES) *
|
|
100 / HZ));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations scsi_lu_seq_ops = {
|
|
.start = scsi_lu_seq_start,
|
|
.next = scsi_lu_seq_next,
|
|
.stop = scsi_lu_seq_stop,
|
|
.show = scsi_lu_seq_show
|
|
};
|
|
|
|
static int scsi_lu_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &scsi_lu_seq_ops);
|
|
}
|
|
|
|
static const struct file_operations scsi_lu_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = scsi_lu_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/****************************************************************************/
|
|
|
|
/*
|
|
* Remove proc fs entries
|
|
*/
|
|
void remove_scsi_target_mib(void)
|
|
{
|
|
remove_proc_entry("scsi_target/mib/scsi_inst", NULL);
|
|
remove_proc_entry("scsi_target/mib/scsi_dev", NULL);
|
|
remove_proc_entry("scsi_target/mib/scsi_port", NULL);
|
|
remove_proc_entry("scsi_target/mib/scsi_transport", NULL);
|
|
remove_proc_entry("scsi_target/mib/scsi_tgt_dev", NULL);
|
|
remove_proc_entry("scsi_target/mib/scsi_tgt_port", NULL);
|
|
remove_proc_entry("scsi_target/mib/scsi_auth_intr", NULL);
|
|
remove_proc_entry("scsi_target/mib/scsi_att_intr_port", NULL);
|
|
remove_proc_entry("scsi_target/mib/scsi_lu", NULL);
|
|
remove_proc_entry("scsi_target/mib", NULL);
|
|
}
|
|
|
|
/*
|
|
* Create proc fs entries for the mib tables
|
|
*/
|
|
int init_scsi_target_mib(void)
|
|
{
|
|
struct proc_dir_entry *dir_entry;
|
|
struct proc_dir_entry *scsi_inst_entry;
|
|
struct proc_dir_entry *scsi_dev_entry;
|
|
struct proc_dir_entry *scsi_port_entry;
|
|
struct proc_dir_entry *scsi_transport_entry;
|
|
struct proc_dir_entry *scsi_tgt_dev_entry;
|
|
struct proc_dir_entry *scsi_tgt_port_entry;
|
|
struct proc_dir_entry *scsi_auth_intr_entry;
|
|
struct proc_dir_entry *scsi_att_intr_port_entry;
|
|
struct proc_dir_entry *scsi_lu_entry;
|
|
|
|
dir_entry = proc_mkdir("scsi_target/mib", NULL);
|
|
if (!(dir_entry)) {
|
|
printk(KERN_ERR "proc_mkdir() failed.\n");
|
|
return -1;
|
|
}
|
|
|
|
scsi_inst_entry =
|
|
create_proc_entry("scsi_target/mib/scsi_inst", 0, NULL);
|
|
if (scsi_inst_entry)
|
|
scsi_inst_entry->proc_fops = &scsi_inst_seq_fops;
|
|
else
|
|
goto error;
|
|
|
|
scsi_dev_entry =
|
|
create_proc_entry("scsi_target/mib/scsi_dev", 0, NULL);
|
|
if (scsi_dev_entry)
|
|
scsi_dev_entry->proc_fops = &scsi_dev_seq_fops;
|
|
else
|
|
goto error;
|
|
|
|
scsi_port_entry =
|
|
create_proc_entry("scsi_target/mib/scsi_port", 0, NULL);
|
|
if (scsi_port_entry)
|
|
scsi_port_entry->proc_fops = &scsi_port_seq_fops;
|
|
else
|
|
goto error;
|
|
|
|
scsi_transport_entry =
|
|
create_proc_entry("scsi_target/mib/scsi_transport", 0, NULL);
|
|
if (scsi_transport_entry)
|
|
scsi_transport_entry->proc_fops = &scsi_transport_seq_fops;
|
|
else
|
|
goto error;
|
|
|
|
scsi_tgt_dev_entry =
|
|
create_proc_entry("scsi_target/mib/scsi_tgt_dev", 0, NULL);
|
|
if (scsi_tgt_dev_entry)
|
|
scsi_tgt_dev_entry->proc_fops = &scsi_tgt_dev_seq_fops;
|
|
else
|
|
goto error;
|
|
|
|
scsi_tgt_port_entry =
|
|
create_proc_entry("scsi_target/mib/scsi_tgt_port", 0, NULL);
|
|
if (scsi_tgt_port_entry)
|
|
scsi_tgt_port_entry->proc_fops = &scsi_tgt_port_seq_fops;
|
|
else
|
|
goto error;
|
|
|
|
scsi_auth_intr_entry =
|
|
create_proc_entry("scsi_target/mib/scsi_auth_intr", 0, NULL);
|
|
if (scsi_auth_intr_entry)
|
|
scsi_auth_intr_entry->proc_fops = &scsi_auth_intr_seq_fops;
|
|
else
|
|
goto error;
|
|
|
|
scsi_att_intr_port_entry =
|
|
create_proc_entry("scsi_target/mib/scsi_att_intr_port", 0, NULL);
|
|
if (scsi_att_intr_port_entry)
|
|
scsi_att_intr_port_entry->proc_fops =
|
|
&scsi_att_intr_port_seq_fops;
|
|
else
|
|
goto error;
|
|
|
|
scsi_lu_entry = create_proc_entry("scsi_target/mib/scsi_lu", 0, NULL);
|
|
if (scsi_lu_entry)
|
|
scsi_lu_entry->proc_fops = &scsi_lu_seq_fops;
|
|
else
|
|
goto error;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
printk(KERN_ERR "create_proc_entry() failed.\n");
|
|
remove_scsi_target_mib();
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Initialize the index table for allocating unique row indexes to various mib
|
|
* tables
|
|
*/
|
|
void init_scsi_index_table(void)
|
|
{
|
|
memset(&scsi_index_table, 0, sizeof(struct scsi_index_table));
|
|
spin_lock_init(&scsi_index_table.lock);
|
|
}
|
|
|
|
/*
|
|
* Allocate a new row index for the entry type specified
|
|
*/
|
|
u32 scsi_get_new_index(scsi_index_t type)
|
|
{
|
|
u32 new_index;
|
|
|
|
if ((type < 0) || (type >= SCSI_INDEX_TYPE_MAX)) {
|
|
printk(KERN_ERR "Invalid index type %d\n", type);
|
|
return -1;
|
|
}
|
|
|
|
spin_lock(&scsi_index_table.lock);
|
|
new_index = ++scsi_index_table.scsi_mib_index[type];
|
|
if (new_index == 0)
|
|
new_index = ++scsi_index_table.scsi_mib_index[type];
|
|
spin_unlock(&scsi_index_table.lock);
|
|
|
|
return new_index;
|
|
}
|
|
EXPORT_SYMBOL(scsi_get_new_index);
|