diff options
Diffstat (limited to 'drivers/s390/char/sclp_ocf.c')
-rw-r--r-- | drivers/s390/char/sclp_ocf.c | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/drivers/s390/char/sclp_ocf.c b/drivers/s390/char/sclp_ocf.c new file mode 100644 index 000000000000..ab294d5a534e --- /dev/null +++ b/drivers/s390/char/sclp_ocf.c | |||
@@ -0,0 +1,145 @@ | |||
1 | /* | ||
2 | * drivers/s390/char/sclp_ocf.c | ||
3 | * SCLP OCF communication parameters sysfs interface | ||
4 | * | ||
5 | * Copyright IBM Corp. 2011 | ||
6 | * Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com> | ||
7 | */ | ||
8 | |||
9 | #define KMSG_COMPONENT "sclp_ocf" | ||
10 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | ||
11 | |||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/stat.h> | ||
15 | #include <linux/device.h> | ||
16 | #include <linux/string.h> | ||
17 | #include <linux/ctype.h> | ||
18 | #include <linux/kmod.h> | ||
19 | #include <linux/timer.h> | ||
20 | #include <linux/err.h> | ||
21 | #include <asm/ebcdic.h> | ||
22 | #include <asm/sclp.h> | ||
23 | |||
24 | #include "sclp.h" | ||
25 | |||
26 | #define OCF_LENGTH_HMC_NETWORK 8UL | ||
27 | #define OCF_LENGTH_CPC_NAME 8UL | ||
28 | |||
29 | static char hmc_network[OCF_LENGTH_HMC_NETWORK + 1]; | ||
30 | static char cpc_name[OCF_LENGTH_CPC_NAME + 1]; | ||
31 | |||
32 | static DEFINE_SPINLOCK(sclp_ocf_lock); | ||
33 | static struct work_struct sclp_ocf_change_work; | ||
34 | |||
35 | static struct kset *ocf_kset; | ||
36 | |||
37 | static void sclp_ocf_change_notify(struct work_struct *work) | ||
38 | { | ||
39 | kobject_uevent(&ocf_kset->kobj, KOBJ_CHANGE); | ||
40 | } | ||
41 | |||
42 | /* Handler for OCF event. Look for the CPC image name. */ | ||
43 | static void sclp_ocf_handler(struct evbuf_header *evbuf) | ||
44 | { | ||
45 | struct gds_vector *v; | ||
46 | struct gds_subvector *sv, *netid, *cpc; | ||
47 | size_t size; | ||
48 | |||
49 | /* Find the 0x9f00 block. */ | ||
50 | v = sclp_find_gds_vector(evbuf + 1, (void *) evbuf + evbuf->length, | ||
51 | 0x9f00); | ||
52 | if (!v) | ||
53 | return; | ||
54 | /* Find the 0x9f22 block inside the 0x9f00 block. */ | ||
55 | v = sclp_find_gds_vector(v + 1, (void *) v + v->length, 0x9f22); | ||
56 | if (!v) | ||
57 | return; | ||
58 | /* Find the 0x81 block inside the 0x9f22 block. */ | ||
59 | sv = sclp_find_gds_subvector(v + 1, (void *) v + v->length, 0x81); | ||
60 | if (!sv) | ||
61 | return; | ||
62 | /* Find the 0x01 block inside the 0x81 block. */ | ||
63 | netid = sclp_find_gds_subvector(sv + 1, (void *) sv + sv->length, 1); | ||
64 | /* Find the 0x02 block inside the 0x81 block. */ | ||
65 | cpc = sclp_find_gds_subvector(sv + 1, (void *) sv + sv->length, 2); | ||
66 | /* Copy network name and cpc name. */ | ||
67 | spin_lock(&sclp_ocf_lock); | ||
68 | if (netid) { | ||
69 | size = min(OCF_LENGTH_HMC_NETWORK, (size_t) netid->length); | ||
70 | memcpy(hmc_network, netid + 1, size); | ||
71 | EBCASC(hmc_network, size); | ||
72 | hmc_network[size] = 0; | ||
73 | } | ||
74 | if (cpc) { | ||
75 | size = min(OCF_LENGTH_CPC_NAME, (size_t) cpc->length); | ||
76 | memcpy(cpc_name, cpc + 1, size); | ||
77 | EBCASC(cpc_name, size); | ||
78 | cpc_name[size] = 0; | ||
79 | } | ||
80 | spin_unlock(&sclp_ocf_lock); | ||
81 | schedule_work(&sclp_ocf_change_work); | ||
82 | } | ||
83 | |||
84 | static struct sclp_register sclp_ocf_event = { | ||
85 | .receive_mask = EVTYP_OCF_MASK, | ||
86 | .receiver_fn = sclp_ocf_handler, | ||
87 | }; | ||
88 | |||
89 | static ssize_t cpc_name_show(struct kobject *kobj, | ||
90 | struct kobj_attribute *attr, char *page) | ||
91 | { | ||
92 | int rc; | ||
93 | |||
94 | spin_lock_irq(&sclp_ocf_lock); | ||
95 | rc = snprintf(page, PAGE_SIZE, "%s\n", cpc_name); | ||
96 | spin_unlock_irq(&sclp_ocf_lock); | ||
97 | return rc; | ||
98 | } | ||
99 | |||
100 | static struct kobj_attribute cpc_name_attr = | ||
101 | __ATTR(cpc_name, 0444, cpc_name_show, NULL); | ||
102 | |||
103 | static ssize_t hmc_network_show(struct kobject *kobj, | ||
104 | struct kobj_attribute *attr, char *page) | ||
105 | { | ||
106 | int rc; | ||
107 | |||
108 | spin_lock_irq(&sclp_ocf_lock); | ||
109 | rc = snprintf(page, PAGE_SIZE, "%s\n", hmc_network); | ||
110 | spin_unlock_irq(&sclp_ocf_lock); | ||
111 | return rc; | ||
112 | } | ||
113 | |||
114 | static struct kobj_attribute hmc_network_attr = | ||
115 | __ATTR(hmc_network, 0444, hmc_network_show, NULL); | ||
116 | |||
117 | static struct attribute *ocf_attrs[] = { | ||
118 | &cpc_name_attr.attr, | ||
119 | &hmc_network_attr.attr, | ||
120 | NULL, | ||
121 | }; | ||
122 | |||
123 | static struct attribute_group ocf_attr_group = { | ||
124 | .attrs = ocf_attrs, | ||
125 | }; | ||
126 | |||
127 | static int __init ocf_init(void) | ||
128 | { | ||
129 | int rc; | ||
130 | |||
131 | INIT_WORK(&sclp_ocf_change_work, sclp_ocf_change_notify); | ||
132 | ocf_kset = kset_create_and_add("ocf", NULL, firmware_kobj); | ||
133 | if (!ocf_kset) | ||
134 | return -ENOMEM; | ||
135 | |||
136 | rc = sysfs_create_group(&ocf_kset->kobj, &ocf_attr_group); | ||
137 | if (rc) { | ||
138 | kset_unregister(ocf_kset); | ||
139 | return rc; | ||
140 | } | ||
141 | |||
142 | return sclp_register(&sclp_ocf_event); | ||
143 | } | ||
144 | |||
145 | device_initcall(ocf_init); | ||