|
@@ -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 @@ struct seq_operations cpuinfo_op = {
|
|
|
.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);
|