[PATCH] s390: export ipl device parameters

Sysfs interface to export ipl device parameters.  Dependent on the ipl type
the interface will look like this:

- ccw ipl:

/sys/firmware/ipl/device
		 /ipl_type

- fcp ipl:

/sys/firmware/ipl/binary_parameter
		 /bootprog
		 /br_lba
		 /device
		 /ipl_type
		 /lun
		 /scp_data
		 /wwpn

- otherwise (unknown that is):

/sys/firmware/ipl/ipl_type

Acked-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/arch/s390/kernel/setup.c b/arch/s390/kernel/setup.c
index 5204778..31e7b19 100644
--- a/arch/s390/kernel/setup.c
+++ b/arch/s390/kernel/setup.c
@@ -36,6 +36,7 @@
 #include <linux/console.h>
 #include <linux/seq_file.h>
 #include <linux/kernel_stat.h>
+#include <linux/device.h>
 
 #include <asm/uaccess.h>
 #include <asm/system.h>
@@ -685,3 +686,188 @@
 	.show	= show_cpuinfo,
 };
 
+#define DEFINE_IPL_ATTR(_name, _format, _value)			\
+static ssize_t ipl_##_name##_show(struct subsystem *subsys,	\
+		char *page)					\
+{								\
+	return sprintf(page, _format, _value);			\
+}								\
+static struct subsys_attribute ipl_##_name##_attr =		\
+	__ATTR(_name, S_IRUGO, ipl_##_name##_show, NULL);
+
+DEFINE_IPL_ATTR(wwpn, "0x%016llx\n", (unsigned long long)
+		IPL_PARMBLOCK_START->fcp.wwpn);
+DEFINE_IPL_ATTR(lun, "0x%016llx\n", (unsigned long long)
+		IPL_PARMBLOCK_START->fcp.lun);
+DEFINE_IPL_ATTR(bootprog, "%lld\n", (unsigned long long)
+		IPL_PARMBLOCK_START->fcp.bootprog);
+DEFINE_IPL_ATTR(br_lba, "%lld\n", (unsigned long long)
+		IPL_PARMBLOCK_START->fcp.br_lba);
+
+enum ipl_type_type {
+	ipl_type_unknown,
+	ipl_type_ccw,
+	ipl_type_fcp,
+};
+
+static enum ipl_type_type
+get_ipl_type(void)
+{
+	struct ipl_parameter_block *ipl = IPL_PARMBLOCK_START;
+
+	if (!IPL_DEVNO_VALID)
+		return ipl_type_unknown;
+	if (!IPL_PARMBLOCK_VALID)
+		return ipl_type_ccw;
+	if (ipl->hdr.header.version > IPL_MAX_SUPPORTED_VERSION)
+		return ipl_type_unknown;
+	if (ipl->fcp.pbt != IPL_TYPE_FCP)
+		return ipl_type_unknown;
+	return ipl_type_fcp;
+}
+
+static ssize_t
+ipl_type_show(struct subsystem *subsys, char *page)
+{
+	switch (get_ipl_type()) {
+	case ipl_type_ccw:
+		return sprintf(page, "ccw\n");
+	case ipl_type_fcp:
+		return sprintf(page, "fcp\n");
+	default:
+		return sprintf(page, "unknown\n");
+	}
+}
+
+static struct subsys_attribute ipl_type_attr = __ATTR_RO(ipl_type);
+
+static ssize_t
+ipl_device_show(struct subsystem *subsys, char *page)
+{
+	struct ipl_parameter_block *ipl = IPL_PARMBLOCK_START;
+
+	switch (get_ipl_type()) {
+	case ipl_type_ccw:
+		return sprintf(page, "0.0.%04x\n", ipl_devno);
+	case ipl_type_fcp:
+		return sprintf(page, "0.0.%04x\n", ipl->fcp.devno);
+	default:
+		return 0;
+	}
+}
+
+static struct subsys_attribute ipl_device_attr =
+	__ATTR(device, S_IRUGO, ipl_device_show, NULL);
+
+static struct attribute *ipl_fcp_attrs[] = {
+	&ipl_type_attr.attr,
+	&ipl_device_attr.attr,
+	&ipl_wwpn_attr.attr,
+	&ipl_lun_attr.attr,
+	&ipl_bootprog_attr.attr,
+	&ipl_br_lba_attr.attr,
+	NULL,
+};
+
+static struct attribute_group ipl_fcp_attr_group = {
+	.attrs = ipl_fcp_attrs,
+};
+
+static struct attribute *ipl_ccw_attrs[] = {
+	&ipl_type_attr.attr,
+	&ipl_device_attr.attr,
+	NULL,
+};
+
+static struct attribute_group ipl_ccw_attr_group = {
+	.attrs = ipl_ccw_attrs,
+};
+
+static struct attribute *ipl_unknown_attrs[] = {
+	&ipl_type_attr.attr,
+	NULL,
+};
+
+static struct attribute_group ipl_unknown_attr_group = {
+	.attrs = ipl_unknown_attrs,
+};
+
+static ssize_t
+ipl_parameter_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
+{
+	unsigned int size = IPL_PARMBLOCK_SIZE;
+
+	if (off > size)
+		return 0;
+	if (off + count > size)
+		count = size - off;
+
+	memcpy(buf, (void *) IPL_PARMBLOCK_START + off, count);
+	return count;
+}
+
+static struct bin_attribute ipl_parameter_attr = {
+	.attr = {
+		.name = "binary_parameter",
+		.mode = S_IRUGO,
+		.owner = THIS_MODULE,
+	},
+	.size = PAGE_SIZE,
+	.read = &ipl_parameter_read,
+};
+
+static ssize_t
+ipl_scp_data_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
+{
+	unsigned int size =  IPL_PARMBLOCK_START->fcp.scp_data_len;
+	void *scp_data = &IPL_PARMBLOCK_START->fcp.scp_data;
+
+	if (off > size)
+		return 0;
+	if (off + count > size)
+		count = size - off;
+
+	memcpy(buf, scp_data + off, count);
+	return count;
+}
+
+static struct bin_attribute ipl_scp_data_attr = {
+	.attr = {
+		.name = "scp_data",
+		.mode = S_IRUGO,
+		.owner = THIS_MODULE,
+	},
+	.size = PAGE_SIZE,
+	.read = &ipl_scp_data_read,
+};
+
+static decl_subsys(ipl, NULL, NULL);
+
+static int __init
+ipl_device_sysfs_register(void) {
+	int rc;
+
+	rc = firmware_register(&ipl_subsys);
+	if (rc)
+		return rc;
+
+	switch (get_ipl_type()) {
+	case ipl_type_ccw:
+		sysfs_create_group(&ipl_subsys.kset.kobj, &ipl_ccw_attr_group);
+		break;
+	case ipl_type_fcp:
+		sysfs_create_group(&ipl_subsys.kset.kobj, &ipl_fcp_attr_group);
+		sysfs_create_bin_file(&ipl_subsys.kset.kobj,
+				      &ipl_parameter_attr);
+		sysfs_create_bin_file(&ipl_subsys.kset.kobj,
+				      &ipl_scp_data_attr);
+		break;
+	default:
+		sysfs_create_group(&ipl_subsys.kset.kobj,
+				   &ipl_unknown_attr_group);
+		break;
+	}
+	return 0;
+}
+
+__initcall(ipl_device_sysfs_register);