1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
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]; /* in EBCDIC */ |
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(kobj: &ocf_kset->kobj, action: 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(start: evbuf + 1, end: (void *) evbuf + evbuf->length, |
51 | id: 0x9f00); |
52 | if (!v) |
53 | return; |
54 | /* Find the 0x9f22 block inside the 0x9f00 block. */ |
55 | v = sclp_find_gds_vector(start: v + 1, end: (void *) v + v->length, id: 0x9f22); |
56 | if (!v) |
57 | return; |
58 | /* Find the 0x81 block inside the 0x9f22 block. */ |
59 | sv = sclp_find_gds_subvector(start: v + 1, end: (void *) v + v->length, key: 0x81); |
60 | if (!sv) |
61 | return; |
62 | /* Find the 0x01 block inside the 0x81 block. */ |
63 | netid = sclp_find_gds_subvector(start: sv + 1, end: (void *) sv + sv->length, key: 1); |
64 | /* Find the 0x02 block inside the 0x81 block. */ |
65 | cpc = sclp_find_gds_subvector(start: sv + 1, end: (void *) sv + sv->length, key: 2); |
66 | /* Copy network name and cpc name. */ |
67 | spin_lock(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 | memset(cpc_name, 0, OCF_LENGTH_CPC_NAME); |
77 | memcpy(cpc_name, cpc + 1, size); |
78 | } |
79 | spin_unlock(lock: &sclp_ocf_lock); |
80 | schedule_work(work: &sclp_ocf_change_work); |
81 | } |
82 | |
83 | static struct sclp_register sclp_ocf_event = { |
84 | .receive_mask = EVTYP_OCF_MASK, |
85 | .receiver_fn = sclp_ocf_handler, |
86 | }; |
87 | |
88 | void sclp_ocf_cpc_name_copy(char *dst) |
89 | { |
90 | spin_lock_irq(lock: &sclp_ocf_lock); |
91 | memcpy(dst, cpc_name, OCF_LENGTH_CPC_NAME); |
92 | spin_unlock_irq(lock: &sclp_ocf_lock); |
93 | } |
94 | EXPORT_SYMBOL(sclp_ocf_cpc_name_copy); |
95 | |
96 | static ssize_t cpc_name_show(struct kobject *kobj, |
97 | struct kobj_attribute *attr, char *page) |
98 | { |
99 | char name[OCF_LENGTH_CPC_NAME + 1]; |
100 | |
101 | sclp_ocf_cpc_name_copy(name); |
102 | name[OCF_LENGTH_CPC_NAME] = 0; |
103 | EBCASC(name, OCF_LENGTH_CPC_NAME); |
104 | return snprintf(buf: page, PAGE_SIZE, fmt: "%s\n" , name); |
105 | } |
106 | |
107 | static struct kobj_attribute cpc_name_attr = |
108 | __ATTR(cpc_name, 0444, cpc_name_show, NULL); |
109 | |
110 | static ssize_t hmc_network_show(struct kobject *kobj, |
111 | struct kobj_attribute *attr, char *page) |
112 | { |
113 | int rc; |
114 | |
115 | spin_lock_irq(lock: &sclp_ocf_lock); |
116 | rc = snprintf(buf: page, PAGE_SIZE, fmt: "%s\n" , hmc_network); |
117 | spin_unlock_irq(lock: &sclp_ocf_lock); |
118 | return rc; |
119 | } |
120 | |
121 | static struct kobj_attribute hmc_network_attr = |
122 | __ATTR(hmc_network, 0444, hmc_network_show, NULL); |
123 | |
124 | static struct attribute *ocf_attrs[] = { |
125 | &cpc_name_attr.attr, |
126 | &hmc_network_attr.attr, |
127 | NULL, |
128 | }; |
129 | |
130 | static const struct attribute_group ocf_attr_group = { |
131 | .attrs = ocf_attrs, |
132 | }; |
133 | |
134 | static int __init ocf_init(void) |
135 | { |
136 | int rc; |
137 | |
138 | INIT_WORK(&sclp_ocf_change_work, sclp_ocf_change_notify); |
139 | ocf_kset = kset_create_and_add(name: "ocf" , NULL, parent_kobj: firmware_kobj); |
140 | if (!ocf_kset) |
141 | return -ENOMEM; |
142 | |
143 | rc = sysfs_create_group(kobj: &ocf_kset->kobj, grp: &ocf_attr_group); |
144 | if (rc) { |
145 | kset_unregister(kset: ocf_kset); |
146 | return rc; |
147 | } |
148 | |
149 | return sclp_register(reg: &sclp_ocf_event); |
150 | } |
151 | |
152 | device_initcall(ocf_init); |
153 | |