[S390] s390_hypfs: Add new attributes

In order to access the data of the hypfs diagnose calls from user
space also in binary form, this patch adds two new attributes in
debugfs:
 * z/VM: s390_hypfs/d2fc_bin
 * LPAR: s390_hypfs/d204_bin

Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
diff --git a/arch/s390/hypfs/hypfs.h b/arch/s390/hypfs/hypfs.h
index aea5720..fa487d4 100644
--- a/arch/s390/hypfs/hypfs.h
+++ b/arch/s390/hypfs/hypfs.h
@@ -11,6 +11,7 @@
 
 #include <linux/fs.h>
 #include <linux/types.h>
+#include <linux/debugfs.h>
 
 #define REG_FILE_MODE    0440
 #define UPDATE_FILE_MODE 0220
@@ -34,6 +35,9 @@
 
 /* VM Hypervisor */
 extern int hypfs_vm_init(void);
+extern void hypfs_vm_exit(void);
 extern int hypfs_vm_create_files(struct super_block *sb, struct dentry *root);
 
+/* Directory for debugfs files */
+extern struct dentry *hypfs_dbfs_dir;
 #endif /* _HYPFS_H_ */
diff --git a/arch/s390/hypfs/hypfs_diag.c b/arch/s390/hypfs/hypfs_diag.c
index 5b1acdb..1211bb1 100644
--- a/arch/s390/hypfs/hypfs_diag.c
+++ b/arch/s390/hypfs/hypfs_diag.c
@@ -15,6 +15,7 @@
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/vmalloc.h>
+#include <linux/mm.h>
 #include <asm/ebcdic.h>
 #include "hypfs.h"
 
@@ -22,6 +23,8 @@
 #define CPU_NAME_LEN 16		/* type name len of cpus in diag224 name table */
 #define TMP_SIZE 64		/* size of temporary buffers */
 
+#define DBFS_D204_HDR_VERSION	0
+
 /* diag 204 subcodes */
 enum diag204_sc {
 	SUBC_STIB4 = 4,
@@ -47,6 +50,8 @@
 static void *diag204_buf_vmalloc;	/* vmalloc pointer for diag204 data */
 static int diag204_buf_pages;		/* number of pages for diag204 data */
 
+static struct dentry *dbfs_d204_file;
+
 /*
  * DIAG 204 data structures and member access functions.
  *
@@ -364,18 +369,21 @@
 	} else {
 		free_pages((unsigned long) diag204_buf, 0);
 	}
-	diag204_buf_pages = 0;
 	diag204_buf = NULL;
 }
 
+static void *page_align_ptr(void *ptr)
+{
+	return (void *) PAGE_ALIGN((unsigned long) ptr);
+}
+
 static void *diag204_alloc_vbuf(int pages)
 {
 	/* The buffer has to be page aligned! */
 	diag204_buf_vmalloc = vmalloc(PAGE_SIZE * (pages + 1));
 	if (!diag204_buf_vmalloc)
 		return ERR_PTR(-ENOMEM);
-	diag204_buf = (void*)((unsigned long)diag204_buf_vmalloc
-				& ~0xfffUL) + 0x1000;
+	diag204_buf = page_align_ptr(diag204_buf_vmalloc);
 	diag204_buf_pages = pages;
 	return diag204_buf;
 }
@@ -468,17 +476,26 @@
 	return rc;
 }
 
+static int diag204_do_store(void *buf, int pages)
+{
+	int rc;
+
+	rc = diag204((unsigned long) diag204_store_sc |
+		     (unsigned long) diag204_info_type, pages, buf);
+	return rc < 0 ? -ENOSYS : 0;
+}
+
 static void *diag204_store(void)
 {
 	void *buf;
-	int pages;
+	int pages, rc;
 
 	buf = diag204_get_buffer(diag204_info_type, &pages);
 	if (IS_ERR(buf))
 		goto out;
-	if (diag204((unsigned long)diag204_store_sc |
-		    (unsigned long)diag204_info_type, pages, buf) < 0)
-		return ERR_PTR(-ENOSYS);
+	rc = diag204_do_store(buf, pages);
+	if (rc)
+		return ERR_PTR(rc);
 out:
 	return buf;
 }
@@ -526,6 +543,92 @@
 	return 0;
 }
 
+struct dbfs_d204_hdr {
+	u64	len;		/* Length of d204 buffer without header */
+	u16	version;	/* Version of header */
+	u8	sc;		/* Used subcode */
+	char	reserved[53];
+} __attribute__ ((packed));
+
+struct dbfs_d204 {
+	struct dbfs_d204_hdr	hdr;	/* 64 byte header */
+	char			buf[];	/* d204 buffer */
+} __attribute__ ((packed));
+
+struct dbfs_d204_private {
+	struct dbfs_d204	*d204;	/* Aligned d204 data with header */
+	void			*base;	/* Base pointer (needed for vfree) */
+};
+
+static int dbfs_d204_open(struct inode *inode, struct file *file)
+{
+	struct dbfs_d204_private *data;
+	struct dbfs_d204 *d204;
+	int rc, buf_size;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	buf_size = PAGE_SIZE * (diag204_buf_pages + 1) + sizeof(d204->hdr);
+	data->base = vmalloc(buf_size);
+	if (!data->base) {
+		rc = -ENOMEM;
+		goto fail_kfree_data;
+	}
+	memset(data->base, 0, buf_size);
+	d204 = page_align_ptr(data->base + sizeof(d204->hdr))
+		- sizeof(d204->hdr);
+	rc = diag204_do_store(&d204->buf, diag204_buf_pages);
+	if (rc)
+		goto fail_vfree_base;
+	d204->hdr.version = DBFS_D204_HDR_VERSION;
+	d204->hdr.len = PAGE_SIZE * diag204_buf_pages;
+	d204->hdr.sc = diag204_store_sc;
+	data->d204 = d204;
+	file->private_data = data;
+	return nonseekable_open(inode, file);
+
+fail_vfree_base:
+	vfree(data->base);
+fail_kfree_data:
+	kfree(data);
+	return rc;
+}
+
+static int dbfs_d204_release(struct inode *inode, struct file *file)
+{
+	struct dbfs_d204_private *data = file->private_data;
+
+	vfree(data->base);
+	kfree(data);
+	return 0;
+}
+
+static ssize_t dbfs_d204_read(struct file *file, char __user *buf,
+			      size_t size, loff_t *ppos)
+{
+	struct dbfs_d204_private *data = file->private_data;
+
+	return simple_read_from_buffer(buf, size, ppos, data->d204,
+				       data->d204->hdr.len +
+				       sizeof(data->d204->hdr));
+}
+
+static const struct file_operations dbfs_d204_ops = {
+	.open		= dbfs_d204_open,
+	.read		= dbfs_d204_read,
+	.release	= dbfs_d204_release,
+};
+
+static int hypfs_dbfs_init(void)
+{
+	dbfs_d204_file = debugfs_create_file("diag_204", 0400, hypfs_dbfs_dir,
+					     NULL, &dbfs_d204_ops);
+	if (IS_ERR(dbfs_d204_file))
+		return PTR_ERR(dbfs_d204_file);
+	return 0;
+}
+
 __init int hypfs_diag_init(void)
 {
 	int rc;
@@ -540,11 +643,17 @@
 		pr_err("The hardware system does not provide all "
 		       "functions required by hypfs\n");
 	}
+	if (diag204_info_type == INFO_EXT) {
+		rc = hypfs_dbfs_init();
+		if (rc)
+			diag204_free_buffer();
+	}
 	return rc;
 }
 
 void hypfs_diag_exit(void)
 {
+	debugfs_remove(dbfs_d204_file);
 	diag224_delete_name_table();
 	diag204_free_buffer();
 }
diff --git a/arch/s390/hypfs/hypfs_vm.c b/arch/s390/hypfs/hypfs_vm.c
index f0b0d31..ee5ab1a 100644
--- a/arch/s390/hypfs/hypfs_vm.c
+++ b/arch/s390/hypfs/hypfs_vm.c
@@ -10,14 +10,18 @@
 #include <linux/string.h>
 #include <linux/vmalloc.h>
 #include <asm/ebcdic.h>
+#include <asm/timex.h>
 #include "hypfs.h"
 
 #define NAME_LEN 8
+#define DBFS_D2FC_HDR_VERSION 0
 
 static char local_guest[] = "        ";
 static char all_guests[] = "*       ";
 static char *guest_query;
 
+static struct dentry *dbfs_d2fc_file;
+
 struct diag2fc_data {
 	__u32 version;
 	__u32 flags;
@@ -76,23 +80,26 @@
 		return -residual_cnt;
 }
 
-static struct diag2fc_data *diag2fc_store(char *query, int *count)
+/*
+ * Allocate buffer for "query" and store diag 2fc at "offset"
+ */
+static void *diag2fc_store(char *query, unsigned int *count, int offset)
 {
+	void *data;
 	int size;
-	struct diag2fc_data *data;
 
 	do {
 		size = diag2fc(0, query, NULL);
 		if (size < 0)
 			return ERR_PTR(-EACCES);
-		data = vmalloc(size);
+		data = vmalloc(size + offset);
 		if (!data)
 			return ERR_PTR(-ENOMEM);
-		if (diag2fc(size, query, data) == 0)
+		if (diag2fc(size, query, data + offset) == 0)
 			break;
 		vfree(data);
 	} while (1);
-	*count = (size / sizeof(*data));
+	*count = (size / sizeof(struct diag2fc_data));
 
 	return data;
 }
@@ -168,9 +175,10 @@
 {
 	struct dentry *dir, *file;
 	struct diag2fc_data *data;
-	int rc, i, count = 0;
+	unsigned int count = 0;
+	int rc, i;
 
-	data = diag2fc_store(guest_query, &count);
+	data = diag2fc_store(guest_query, &count, 0);
 	if (IS_ERR(data))
 		return PTR_ERR(data);
 
@@ -218,8 +226,61 @@
 	return rc;
 }
 
+struct dbfs_d2fc_hdr {
+	u64	len;		/* Length of d2fc buffer without header */
+	u16	version;	/* Version of header */
+	char	tod_ext[16];	/* TOD clock for d2fc */
+	u64	count;		/* Number of VM guests in d2fc buffer */
+	char	reserved[30];
+} __attribute__ ((packed));
+
+struct dbfs_d2fc {
+	struct dbfs_d2fc_hdr	hdr;	/* 64 byte header */
+	char			buf[];	/* d2fc buffer */
+} __attribute__ ((packed));
+
+static int dbfs_d2fc_open(struct inode *inode, struct file *file)
+{
+	struct dbfs_d2fc *data;
+	unsigned int count;
+
+	data = diag2fc_store(guest_query, &count, sizeof(data->hdr));
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+	get_clock_ext(data->hdr.tod_ext);
+	data->hdr.len = count * sizeof(struct diag2fc_data);
+	data->hdr.version = DBFS_D2FC_HDR_VERSION;
+	data->hdr.count = count;
+	memset(&data->hdr.reserved, 0, sizeof(data->hdr.reserved));
+	file->private_data = data;
+	return nonseekable_open(inode, file);
+}
+
+static int dbfs_d2fc_release(struct inode *inode, struct file *file)
+{
+	diag2fc_free(file->private_data);
+	return 0;
+}
+
+static ssize_t dbfs_d2fc_read(struct file *file, char __user *buf,
+				    size_t size, loff_t *ppos)
+{
+	struct dbfs_d2fc *data = file->private_data;
+
+	return simple_read_from_buffer(buf, size, ppos, data, data->hdr.len +
+				       sizeof(struct dbfs_d2fc_hdr));
+}
+
+static const struct file_operations dbfs_d2fc_ops = {
+	.open		= dbfs_d2fc_open,
+	.read		= dbfs_d2fc_read,
+	.release	= dbfs_d2fc_release,
+};
+
 int hypfs_vm_init(void)
 {
+	if (!MACHINE_IS_VM)
+		return 0;
 	if (diag2fc(0, all_guests, NULL) > 0)
 		guest_query = all_guests;
 	else if (diag2fc(0, local_guest, NULL) > 0)
@@ -227,5 +288,17 @@
 	else
 		return -EACCES;
 
+	dbfs_d2fc_file = debugfs_create_file("diag_2fc", 0400, hypfs_dbfs_dir,
+					     NULL, &dbfs_d2fc_ops);
+	if (IS_ERR(dbfs_d2fc_file))
+		return PTR_ERR(dbfs_d2fc_file);
+
 	return 0;
 }
+
+void hypfs_vm_exit(void)
+{
+	if (!MACHINE_IS_VM)
+		return;
+	debugfs_remove(dbfs_d2fc_file);
+}
diff --git a/arch/s390/hypfs/inode.c b/arch/s390/hypfs/inode.c
index 95c1aaa..6b120f0 100644
--- a/arch/s390/hypfs/inode.c
+++ b/arch/s390/hypfs/inode.c
@@ -46,6 +46,8 @@
 /* start of list of all dentries, which have to be deleted on update */
 static struct dentry *hypfs_last_dentry;
 
+struct dentry *hypfs_dbfs_dir;
+
 static void hypfs_update_update(struct super_block *sb)
 {
 	struct hypfs_sb_info *sb_info = sb->s_fs_info;
@@ -468,20 +470,22 @@
 {
 	int rc;
 
-	if (MACHINE_IS_VM) {
-		if (hypfs_vm_init())
-			/* no diag 2fc, just exit */
-			return -ENODATA;
-	} else {
-		if (hypfs_diag_init()) {
-			rc = -ENODATA;
-			goto fail_diag;
-		}
+	hypfs_dbfs_dir = debugfs_create_dir("s390_hypfs", NULL);
+	if (IS_ERR(hypfs_dbfs_dir))
+		return PTR_ERR(hypfs_dbfs_dir);
+
+	if (hypfs_diag_init()) {
+		rc = -ENODATA;
+		goto fail_debugfs_remove;
+	}
+	if (hypfs_vm_init()) {
+		rc = -ENODATA;
+		goto fail_hypfs_diag_exit;
 	}
 	s390_kobj = kobject_create_and_add("s390", hypervisor_kobj);
 	if (!s390_kobj) {
 		rc = -ENOMEM;
-		goto fail_sysfs;
+		goto fail_hypfs_vm_exit;
 	}
 	rc = register_filesystem(&hypfs_type);
 	if (rc)
@@ -490,18 +494,22 @@
 
 fail_filesystem:
 	kobject_put(s390_kobj);
-fail_sysfs:
-	if (!MACHINE_IS_VM)
-		hypfs_diag_exit();
-fail_diag:
+fail_hypfs_vm_exit:
+	hypfs_vm_exit();
+fail_hypfs_diag_exit:
+	hypfs_diag_exit();
+fail_debugfs_remove:
+	debugfs_remove(hypfs_dbfs_dir);
+
 	pr_err("Initialization of hypfs failed with rc=%i\n", rc);
 	return rc;
 }
 
 static void __exit hypfs_exit(void)
 {
-	if (!MACHINE_IS_VM)
-		hypfs_diag_exit();
+	hypfs_diag_exit();
+	hypfs_vm_exit();
+	debugfs_remove(hypfs_dbfs_dir);
 	unregister_filesystem(&hypfs_type);
 	kobject_put(s390_kobj);
 }
diff --git a/arch/s390/include/asm/timex.h b/arch/s390/include/asm/timex.h
index f174bda..09d345a 100644
--- a/arch/s390/include/asm/timex.h
+++ b/arch/s390/include/asm/timex.h
@@ -61,11 +61,15 @@
 	return clk;
 }
 
+static inline void get_clock_ext(char *clk)
+{
+	asm volatile("stcke %0" : "=Q" (*clk) : : "cc");
+}
+
 static inline unsigned long long get_clock_xt(void)
 {
 	unsigned char clk[16];
-
-	asm volatile("stcke %0" : "=Q" (clk) : : "cc");
+	get_clock_ext(clk);
 	return *((unsigned long long *)&clk[1]);
 }