diff options
Diffstat (limited to 'drivers/s390/cio/scm.c')
-rw-r--r-- | drivers/s390/cio/scm.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/drivers/s390/cio/scm.c b/drivers/s390/cio/scm.c new file mode 100644 index 000000000000..bcf20f3aa51b --- /dev/null +++ b/drivers/s390/cio/scm.c | |||
@@ -0,0 +1,317 @@ | |||
1 | /* | ||
2 | * Recognize and maintain s390 storage class memory. | ||
3 | * | ||
4 | * Copyright IBM Corp. 2012 | ||
5 | * Author(s): Sebastian Ott <sebott@linux.vnet.ibm.com> | ||
6 | */ | ||
7 | |||
8 | #include <linux/device.h> | ||
9 | #include <linux/module.h> | ||
10 | #include <linux/mutex.h> | ||
11 | #include <linux/slab.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/err.h> | ||
14 | #include <asm/eadm.h> | ||
15 | #include "chsc.h" | ||
16 | |||
17 | static struct device *scm_root; | ||
18 | static struct eadm_ops *eadm_ops; | ||
19 | static DEFINE_MUTEX(eadm_ops_mutex); | ||
20 | |||
21 | #define to_scm_dev(n) container_of(n, struct scm_device, dev) | ||
22 | #define to_scm_drv(d) container_of(d, struct scm_driver, drv) | ||
23 | |||
24 | static int scmdev_probe(struct device *dev) | ||
25 | { | ||
26 | struct scm_device *scmdev = to_scm_dev(dev); | ||
27 | struct scm_driver *scmdrv = to_scm_drv(dev->driver); | ||
28 | |||
29 | return scmdrv->probe ? scmdrv->probe(scmdev) : -ENODEV; | ||
30 | } | ||
31 | |||
32 | static int scmdev_remove(struct device *dev) | ||
33 | { | ||
34 | struct scm_device *scmdev = to_scm_dev(dev); | ||
35 | struct scm_driver *scmdrv = to_scm_drv(dev->driver); | ||
36 | |||
37 | return scmdrv->remove ? scmdrv->remove(scmdev) : -ENODEV; | ||
38 | } | ||
39 | |||
40 | static int scmdev_uevent(struct device *dev, struct kobj_uevent_env *env) | ||
41 | { | ||
42 | return add_uevent_var(env, "MODALIAS=scm:scmdev"); | ||
43 | } | ||
44 | |||
45 | static struct bus_type scm_bus_type = { | ||
46 | .name = "scm", | ||
47 | .probe = scmdev_probe, | ||
48 | .remove = scmdev_remove, | ||
49 | .uevent = scmdev_uevent, | ||
50 | }; | ||
51 | |||
52 | /** | ||
53 | * scm_driver_register() - register a scm driver | ||
54 | * @scmdrv: driver to be registered | ||
55 | */ | ||
56 | int scm_driver_register(struct scm_driver *scmdrv) | ||
57 | { | ||
58 | struct device_driver *drv = &scmdrv->drv; | ||
59 | |||
60 | drv->bus = &scm_bus_type; | ||
61 | |||
62 | return driver_register(drv); | ||
63 | } | ||
64 | EXPORT_SYMBOL_GPL(scm_driver_register); | ||
65 | |||
66 | /** | ||
67 | * scm_driver_unregister() - deregister a scm driver | ||
68 | * @scmdrv: driver to be deregistered | ||
69 | */ | ||
70 | void scm_driver_unregister(struct scm_driver *scmdrv) | ||
71 | { | ||
72 | driver_unregister(&scmdrv->drv); | ||
73 | } | ||
74 | EXPORT_SYMBOL_GPL(scm_driver_unregister); | ||
75 | |||
76 | int scm_get_ref(void) | ||
77 | { | ||
78 | int ret = 0; | ||
79 | |||
80 | mutex_lock(&eadm_ops_mutex); | ||
81 | if (!eadm_ops || !try_module_get(eadm_ops->owner)) | ||
82 | ret = -ENOENT; | ||
83 | mutex_unlock(&eadm_ops_mutex); | ||
84 | |||
85 | return ret; | ||
86 | } | ||
87 | EXPORT_SYMBOL_GPL(scm_get_ref); | ||
88 | |||
89 | void scm_put_ref(void) | ||
90 | { | ||
91 | mutex_lock(&eadm_ops_mutex); | ||
92 | module_put(eadm_ops->owner); | ||
93 | mutex_unlock(&eadm_ops_mutex); | ||
94 | } | ||
95 | EXPORT_SYMBOL_GPL(scm_put_ref); | ||
96 | |||
97 | void register_eadm_ops(struct eadm_ops *ops) | ||
98 | { | ||
99 | mutex_lock(&eadm_ops_mutex); | ||
100 | eadm_ops = ops; | ||
101 | mutex_unlock(&eadm_ops_mutex); | ||
102 | } | ||
103 | EXPORT_SYMBOL_GPL(register_eadm_ops); | ||
104 | |||
105 | void unregister_eadm_ops(struct eadm_ops *ops) | ||
106 | { | ||
107 | mutex_lock(&eadm_ops_mutex); | ||
108 | eadm_ops = NULL; | ||
109 | mutex_unlock(&eadm_ops_mutex); | ||
110 | } | ||
111 | EXPORT_SYMBOL_GPL(unregister_eadm_ops); | ||
112 | |||
113 | int scm_start_aob(struct aob *aob) | ||
114 | { | ||
115 | return eadm_ops->eadm_start(aob); | ||
116 | } | ||
117 | EXPORT_SYMBOL_GPL(scm_start_aob); | ||
118 | |||
119 | void scm_irq_handler(struct aob *aob, int error) | ||
120 | { | ||
121 | struct aob_rq_header *aobrq = (void *) aob->request.data; | ||
122 | struct scm_device *scmdev = aobrq->scmdev; | ||
123 | struct scm_driver *scmdrv = to_scm_drv(scmdev->dev.driver); | ||
124 | |||
125 | scmdrv->handler(scmdev, aobrq->data, error); | ||
126 | } | ||
127 | EXPORT_SYMBOL_GPL(scm_irq_handler); | ||
128 | |||
129 | #define scm_attr(name) \ | ||
130 | static ssize_t show_##name(struct device *dev, \ | ||
131 | struct device_attribute *attr, char *buf) \ | ||
132 | { \ | ||
133 | struct scm_device *scmdev = to_scm_dev(dev); \ | ||
134 | int ret; \ | ||
135 | \ | ||
136 | device_lock(dev); \ | ||
137 | ret = sprintf(buf, "%u\n", scmdev->attrs.name); \ | ||
138 | device_unlock(dev); \ | ||
139 | \ | ||
140 | return ret; \ | ||
141 | } \ | ||
142 | static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL); | ||
143 | |||
144 | scm_attr(persistence); | ||
145 | scm_attr(oper_state); | ||
146 | scm_attr(data_state); | ||
147 | scm_attr(rank); | ||
148 | scm_attr(release); | ||
149 | scm_attr(res_id); | ||
150 | |||
151 | static struct attribute *scmdev_attrs[] = { | ||
152 | &dev_attr_persistence.attr, | ||
153 | &dev_attr_oper_state.attr, | ||
154 | &dev_attr_data_state.attr, | ||
155 | &dev_attr_rank.attr, | ||
156 | &dev_attr_release.attr, | ||
157 | &dev_attr_res_id.attr, | ||
158 | NULL, | ||
159 | }; | ||
160 | |||
161 | static struct attribute_group scmdev_attr_group = { | ||
162 | .attrs = scmdev_attrs, | ||
163 | }; | ||
164 | |||
165 | static const struct attribute_group *scmdev_attr_groups[] = { | ||
166 | &scmdev_attr_group, | ||
167 | NULL, | ||
168 | }; | ||
169 | |||
170 | static void scmdev_release(struct device *dev) | ||
171 | { | ||
172 | struct scm_device *scmdev = to_scm_dev(dev); | ||
173 | |||
174 | kfree(scmdev); | ||
175 | } | ||
176 | |||
177 | static void scmdev_setup(struct scm_device *scmdev, struct sale *sale, | ||
178 | unsigned int size, unsigned int max_blk_count) | ||
179 | { | ||
180 | dev_set_name(&scmdev->dev, "%016llx", (unsigned long long) sale->sa); | ||
181 | scmdev->nr_max_block = max_blk_count; | ||
182 | scmdev->address = sale->sa; | ||
183 | scmdev->size = 1UL << size; | ||
184 | scmdev->attrs.rank = sale->rank; | ||
185 | scmdev->attrs.persistence = sale->p; | ||
186 | scmdev->attrs.oper_state = sale->op_state; | ||
187 | scmdev->attrs.data_state = sale->data_state; | ||
188 | scmdev->attrs.rank = sale->rank; | ||
189 | scmdev->attrs.release = sale->r; | ||
190 | scmdev->attrs.res_id = sale->rid; | ||
191 | scmdev->dev.parent = scm_root; | ||
192 | scmdev->dev.bus = &scm_bus_type; | ||
193 | scmdev->dev.release = scmdev_release; | ||
194 | scmdev->dev.groups = scmdev_attr_groups; | ||
195 | } | ||
196 | |||
197 | /* | ||
198 | * Check for state-changes, notify the driver and userspace. | ||
199 | */ | ||
200 | static void scmdev_update(struct scm_device *scmdev, struct sale *sale) | ||
201 | { | ||
202 | struct scm_driver *scmdrv; | ||
203 | bool changed; | ||
204 | |||
205 | device_lock(&scmdev->dev); | ||
206 | changed = scmdev->attrs.rank != sale->rank || | ||
207 | scmdev->attrs.oper_state != sale->op_state; | ||
208 | scmdev->attrs.rank = sale->rank; | ||
209 | scmdev->attrs.oper_state = sale->op_state; | ||
210 | if (!scmdev->dev.driver) | ||
211 | goto out; | ||
212 | scmdrv = to_scm_drv(scmdev->dev.driver); | ||
213 | if (changed && scmdrv->notify) | ||
214 | scmdrv->notify(scmdev); | ||
215 | out: | ||
216 | device_unlock(&scmdev->dev); | ||
217 | if (changed) | ||
218 | kobject_uevent(&scmdev->dev.kobj, KOBJ_CHANGE); | ||
219 | } | ||
220 | |||
221 | static int check_address(struct device *dev, void *data) | ||
222 | { | ||
223 | struct scm_device *scmdev = to_scm_dev(dev); | ||
224 | struct sale *sale = data; | ||
225 | |||
226 | return scmdev->address == sale->sa; | ||
227 | } | ||
228 | |||
229 | static struct scm_device *scmdev_find(struct sale *sale) | ||
230 | { | ||
231 | struct device *dev; | ||
232 | |||
233 | dev = bus_find_device(&scm_bus_type, NULL, sale, check_address); | ||
234 | |||
235 | return dev ? to_scm_dev(dev) : NULL; | ||
236 | } | ||
237 | |||
238 | static int scm_add(struct chsc_scm_info *scm_info, size_t num) | ||
239 | { | ||
240 | struct sale *sale, *scmal = scm_info->scmal; | ||
241 | struct scm_device *scmdev; | ||
242 | int ret; | ||
243 | |||
244 | for (sale = scmal; sale < scmal + num; sale++) { | ||
245 | scmdev = scmdev_find(sale); | ||
246 | if (scmdev) { | ||
247 | scmdev_update(scmdev, sale); | ||
248 | /* Release reference from scm_find(). */ | ||
249 | put_device(&scmdev->dev); | ||
250 | continue; | ||
251 | } | ||
252 | scmdev = kzalloc(sizeof(*scmdev), GFP_KERNEL); | ||
253 | if (!scmdev) | ||
254 | return -ENODEV; | ||
255 | scmdev_setup(scmdev, sale, scm_info->is, scm_info->mbc); | ||
256 | ret = device_register(&scmdev->dev); | ||
257 | if (ret) { | ||
258 | /* Release reference from device_initialize(). */ | ||
259 | put_device(&scmdev->dev); | ||
260 | return ret; | ||
261 | } | ||
262 | } | ||
263 | |||
264 | return 0; | ||
265 | } | ||
266 | |||
267 | int scm_update_information(void) | ||
268 | { | ||
269 | struct chsc_scm_info *scm_info; | ||
270 | u64 token = 0; | ||
271 | size_t num; | ||
272 | int ret; | ||
273 | |||
274 | scm_info = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); | ||
275 | if (!scm_info) | ||
276 | return -ENOMEM; | ||
277 | |||
278 | do { | ||
279 | ret = chsc_scm_info(scm_info, token); | ||
280 | if (ret) | ||
281 | break; | ||
282 | |||
283 | num = (scm_info->response.length - | ||
284 | (offsetof(struct chsc_scm_info, scmal) - | ||
285 | offsetof(struct chsc_scm_info, response)) | ||
286 | ) / sizeof(struct sale); | ||
287 | |||
288 | ret = scm_add(scm_info, num); | ||
289 | if (ret) | ||
290 | break; | ||
291 | |||
292 | token = scm_info->restok; | ||
293 | } while (token); | ||
294 | |||
295 | free_page((unsigned long)scm_info); | ||
296 | |||
297 | return ret; | ||
298 | } | ||
299 | |||
300 | static int __init scm_init(void) | ||
301 | { | ||
302 | int ret; | ||
303 | |||
304 | ret = bus_register(&scm_bus_type); | ||
305 | if (ret) | ||
306 | return ret; | ||
307 | |||
308 | scm_root = root_device_register("scm"); | ||
309 | if (IS_ERR(scm_root)) { | ||
310 | bus_unregister(&scm_bus_type); | ||
311 | return PTR_ERR(scm_root); | ||
312 | } | ||
313 | |||
314 | scm_update_information(); | ||
315 | return 0; | ||
316 | } | ||
317 | subsys_initcall_sync(scm_init); | ||