[S390] qdio: convert global statistics to per-device stats

Revamp the qdio performance statistics and move them from procfs to
debugfs using the seq_file interface. Since the statistics are not
intended for the general user the removal of /proc/qdio_perf should
not surprise anyone.

The per device statistics are disabled by default, writing 1 to
/<debugfs mountpoint>/qdio/<device bus ID>/statistics enables the
statistics for the given device.

Signed-off-by: Jan Glauber <jang@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
diff --git a/drivers/s390/cio/qdio_debug.c b/drivers/s390/cio/qdio_debug.c
index 7676997..f49761f 100644
--- a/drivers/s390/cio/qdio_debug.c
+++ b/drivers/s390/cio/qdio_debug.c
@@ -55,13 +55,11 @@
 	if (!q)
 		return 0;
 
-	seq_printf(m, "device state indicator: %d\n", *(u32 *)q->irq_ptr->dsci);
-	seq_printf(m, "nr_used: %d\n", atomic_read(&q->nr_buf_used));
-	seq_printf(m, "ftc: %d\n", q->first_to_check);
-	seq_printf(m, "last_move: %d\n", q->last_move);
-	seq_printf(m, "polling: %d\n", q->u.in.polling);
-	seq_printf(m, "ack start: %d\n", q->u.in.ack_start);
-	seq_printf(m, "ack count: %d\n", q->u.in.ack_count);
+	seq_printf(m, "DSCI: %d   nr_used: %d\n",
+		   *(u32 *)q->irq_ptr->dsci, atomic_read(&q->nr_buf_used));
+	seq_printf(m, "ftc: %d  last_move: %d\n", q->first_to_check, q->last_move);
+	seq_printf(m, "polling: %d  ack start: %d  ack count: %d\n",
+		   q->u.in.polling, q->u.in.ack_start, q->u.in.ack_count);
 	seq_printf(m, "slsb buffer states:\n");
 	seq_printf(m, "|0      |8      |16     |24     |32     |40     |48     |56  63|\n");
 
@@ -110,7 +108,6 @@
 
 	if (!q)
 		return 0;
-
 	if (q->is_input_q)
 		xchg(q->irq_ptr->dsci, 1);
 	local_bh_disable();
@@ -134,6 +131,98 @@
 	.release = single_release,
 };
 
+static char *qperf_names[] = {
+	"Assumed adapter interrupts",
+	"QDIO interrupts",
+	"Requested PCIs",
+	"Inbound tasklet runs",
+	"Inbound tasklet resched",
+	"Inbound tasklet resched2",
+	"Outbound tasklet runs",
+	"SIGA read",
+	"SIGA write",
+	"SIGA sync",
+	"Inbound calls",
+	"Inbound handler",
+	"Inbound stop_polling",
+	"Inbound queue full",
+	"Outbound calls",
+	"Outbound handler",
+	"Outbound fast_requeue",
+	"Outbound target_full",
+	"QEBSM eqbs",
+	"QEBSM eqbs partial",
+	"QEBSM sqbs",
+	"QEBSM sqbs partial"
+};
+
+static int qperf_show(struct seq_file *m, void *v)
+{
+	struct qdio_irq *irq_ptr = m->private;
+	unsigned int *stat;
+	int i;
+
+	if (!irq_ptr)
+		return 0;
+	if (!irq_ptr->perf_stat_enabled) {
+		seq_printf(m, "disabled\n");
+		return 0;
+	}
+	stat = (unsigned int *)&irq_ptr->perf_stat;
+
+	for (i = 0; i < ARRAY_SIZE(qperf_names); i++)
+		seq_printf(m, "%26s:\t%u\n",
+			   qperf_names[i], *(stat + i));
+	return 0;
+}
+
+static ssize_t qperf_seq_write(struct file *file, const char __user *ubuf,
+			       size_t count, loff_t *off)
+{
+	struct seq_file *seq = file->private_data;
+	struct qdio_irq *irq_ptr = seq->private;
+	unsigned long val;
+	char buf[8];
+	int ret;
+
+	if (!irq_ptr)
+		return 0;
+	if (count >= sizeof(buf))
+		return -EINVAL;
+	if (copy_from_user(&buf, ubuf, count))
+		return -EFAULT;
+	buf[count] = 0;
+
+	ret = strict_strtoul(buf, 10, &val);
+	if (ret < 0)
+		return ret;
+
+	switch (val) {
+	case 0:
+		irq_ptr->perf_stat_enabled = 0;
+		memset(&irq_ptr->perf_stat, 0, sizeof(irq_ptr->perf_stat));
+		break;
+	case 1:
+		irq_ptr->perf_stat_enabled = 1;
+		break;
+	}
+	return count;
+}
+
+static int qperf_seq_open(struct inode *inode, struct file *filp)
+{
+	return single_open(filp, qperf_show,
+			   filp->f_path.dentry->d_inode->i_private);
+}
+
+static struct file_operations debugfs_perf_fops = {
+	.owner	 = THIS_MODULE,
+	.open	 = qperf_seq_open,
+	.read	 = seq_read,
+	.write	 = qperf_seq_write,
+	.llseek  = seq_lseek,
+	.release = single_release,
+};
 static void setup_debugfs_entry(struct qdio_q *q, struct ccw_device *cdev)
 {
 	char name[QDIO_DEBUGFS_NAME_LEN];
@@ -156,6 +245,14 @@
 						  debugfs_root);
 	if (IS_ERR(irq_ptr->debugfs_dev))
 		irq_ptr->debugfs_dev = NULL;
+
+	irq_ptr->debugfs_perf = debugfs_create_file("statistics",
+				S_IFREG | S_IRUGO | S_IWUSR,
+				irq_ptr->debugfs_dev, irq_ptr,
+				&debugfs_perf_fops);
+	if (IS_ERR(irq_ptr->debugfs_perf))
+		irq_ptr->debugfs_perf = NULL;
+
 	for_each_input_queue(irq_ptr, q, i)
 		setup_debugfs_entry(q, cdev);
 	for_each_output_queue(irq_ptr, q, i)
@@ -171,6 +268,7 @@
 		debugfs_remove(q->debugfs_q);
 	for_each_output_queue(irq_ptr, q, i)
 		debugfs_remove(q->debugfs_q);
+	debugfs_remove(irq_ptr->debugfs_perf);
 	debugfs_remove(irq_ptr->debugfs_dev);
 }