Commit | Line | Data |
---|---|---|
1d1c8f78 SO |
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 | ||
1d1c8f78 SO |
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 | \ | |
c3e6d407 | 136 | device_lock(dev); \ |
1d1c8f78 | 137 | ret = sprintf(buf, "%u\n", scmdev->attrs.name); \ |
c3e6d407 | 138 | device_unlock(dev); \ |
1d1c8f78 SO |
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; | |
1d1c8f78 SO |
195 | } |
196 | ||
40ff4cc0 SO |
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) | |
93481c90 | 214 | scmdrv->notify(scmdev, SCM_CHANGE); |
40ff4cc0 SO |
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 | ||
1d1c8f78 SO |
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++) { | |
40ff4cc0 SO |
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 | } | |
1d1c8f78 SO |
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 | ||
40ff4cc0 | 267 | int scm_update_information(void) |
1d1c8f78 SO |
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 | ||
aebfa669 SO |
300 | static int scm_dev_avail(struct device *dev, void *unused) |
301 | { | |
302 | struct scm_driver *scmdrv = to_scm_drv(dev->driver); | |
303 | struct scm_device *scmdev = to_scm_dev(dev); | |
304 | ||
305 | if (dev->driver && scmdrv->notify) | |
306 | scmdrv->notify(scmdev, SCM_AVAIL); | |
307 | ||
308 | return 0; | |
309 | } | |
310 | ||
311 | int scm_process_availability_information(void) | |
312 | { | |
313 | return bus_for_each_dev(&scm_bus_type, NULL, NULL, scm_dev_avail); | |
314 | } | |
315 | ||
1d1c8f78 SO |
316 | static int __init scm_init(void) |
317 | { | |
318 | int ret; | |
319 | ||
320 | ret = bus_register(&scm_bus_type); | |
321 | if (ret) | |
322 | return ret; | |
323 | ||
324 | scm_root = root_device_register("scm"); | |
325 | if (IS_ERR(scm_root)) { | |
326 | bus_unregister(&scm_bus_type); | |
327 | return PTR_ERR(scm_root); | |
328 | } | |
329 | ||
330 | scm_update_information(); | |
331 | return 0; | |
332 | } | |
333 | subsys_initcall_sync(scm_init); |