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; | |
1d1c8f78 SO |
18 | |
19 | #define to_scm_dev(n) container_of(n, struct scm_device, dev) | |
20 | #define to_scm_drv(d) container_of(d, struct scm_driver, drv) | |
21 | ||
22 | static int scmdev_probe(struct device *dev) | |
23 | { | |
24 | struct scm_device *scmdev = to_scm_dev(dev); | |
25 | struct scm_driver *scmdrv = to_scm_drv(dev->driver); | |
26 | ||
27 | return scmdrv->probe ? scmdrv->probe(scmdev) : -ENODEV; | |
28 | } | |
29 | ||
30 | static int scmdev_remove(struct device *dev) | |
31 | { | |
32 | struct scm_device *scmdev = to_scm_dev(dev); | |
33 | struct scm_driver *scmdrv = to_scm_drv(dev->driver); | |
34 | ||
35 | return scmdrv->remove ? scmdrv->remove(scmdev) : -ENODEV; | |
36 | } | |
37 | ||
38 | static int scmdev_uevent(struct device *dev, struct kobj_uevent_env *env) | |
39 | { | |
40 | return add_uevent_var(env, "MODALIAS=scm:scmdev"); | |
41 | } | |
42 | ||
43 | static struct bus_type scm_bus_type = { | |
44 | .name = "scm", | |
45 | .probe = scmdev_probe, | |
46 | .remove = scmdev_remove, | |
47 | .uevent = scmdev_uevent, | |
48 | }; | |
49 | ||
50 | /** | |
51 | * scm_driver_register() - register a scm driver | |
52 | * @scmdrv: driver to be registered | |
53 | */ | |
54 | int scm_driver_register(struct scm_driver *scmdrv) | |
55 | { | |
56 | struct device_driver *drv = &scmdrv->drv; | |
57 | ||
58 | drv->bus = &scm_bus_type; | |
59 | ||
60 | return driver_register(drv); | |
61 | } | |
62 | EXPORT_SYMBOL_GPL(scm_driver_register); | |
63 | ||
64 | /** | |
65 | * scm_driver_unregister() - deregister a scm driver | |
66 | * @scmdrv: driver to be deregistered | |
67 | */ | |
68 | void scm_driver_unregister(struct scm_driver *scmdrv) | |
69 | { | |
70 | driver_unregister(&scmdrv->drv); | |
71 | } | |
72 | EXPORT_SYMBOL_GPL(scm_driver_unregister); | |
73 | ||
1d1c8f78 SO |
74 | void scm_irq_handler(struct aob *aob, int error) |
75 | { | |
76 | struct aob_rq_header *aobrq = (void *) aob->request.data; | |
77 | struct scm_device *scmdev = aobrq->scmdev; | |
78 | struct scm_driver *scmdrv = to_scm_drv(scmdev->dev.driver); | |
79 | ||
80 | scmdrv->handler(scmdev, aobrq->data, error); | |
81 | } | |
82 | EXPORT_SYMBOL_GPL(scm_irq_handler); | |
83 | ||
84 | #define scm_attr(name) \ | |
85 | static ssize_t show_##name(struct device *dev, \ | |
86 | struct device_attribute *attr, char *buf) \ | |
87 | { \ | |
88 | struct scm_device *scmdev = to_scm_dev(dev); \ | |
89 | int ret; \ | |
90 | \ | |
c3e6d407 | 91 | device_lock(dev); \ |
1d1c8f78 | 92 | ret = sprintf(buf, "%u\n", scmdev->attrs.name); \ |
c3e6d407 | 93 | device_unlock(dev); \ |
1d1c8f78 SO |
94 | \ |
95 | return ret; \ | |
96 | } \ | |
97 | static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL); | |
98 | ||
99 | scm_attr(persistence); | |
100 | scm_attr(oper_state); | |
101 | scm_attr(data_state); | |
102 | scm_attr(rank); | |
103 | scm_attr(release); | |
104 | scm_attr(res_id); | |
105 | ||
106 | static struct attribute *scmdev_attrs[] = { | |
107 | &dev_attr_persistence.attr, | |
108 | &dev_attr_oper_state.attr, | |
109 | &dev_attr_data_state.attr, | |
110 | &dev_attr_rank.attr, | |
111 | &dev_attr_release.attr, | |
112 | &dev_attr_res_id.attr, | |
113 | NULL, | |
114 | }; | |
115 | ||
116 | static struct attribute_group scmdev_attr_group = { | |
117 | .attrs = scmdev_attrs, | |
118 | }; | |
119 | ||
120 | static const struct attribute_group *scmdev_attr_groups[] = { | |
121 | &scmdev_attr_group, | |
122 | NULL, | |
123 | }; | |
124 | ||
125 | static void scmdev_release(struct device *dev) | |
126 | { | |
127 | struct scm_device *scmdev = to_scm_dev(dev); | |
128 | ||
129 | kfree(scmdev); | |
130 | } | |
131 | ||
132 | static void scmdev_setup(struct scm_device *scmdev, struct sale *sale, | |
133 | unsigned int size, unsigned int max_blk_count) | |
134 | { | |
135 | dev_set_name(&scmdev->dev, "%016llx", (unsigned long long) sale->sa); | |
136 | scmdev->nr_max_block = max_blk_count; | |
137 | scmdev->address = sale->sa; | |
138 | scmdev->size = 1UL << size; | |
139 | scmdev->attrs.rank = sale->rank; | |
140 | scmdev->attrs.persistence = sale->p; | |
141 | scmdev->attrs.oper_state = sale->op_state; | |
142 | scmdev->attrs.data_state = sale->data_state; | |
143 | scmdev->attrs.rank = sale->rank; | |
144 | scmdev->attrs.release = sale->r; | |
145 | scmdev->attrs.res_id = sale->rid; | |
146 | scmdev->dev.parent = scm_root; | |
147 | scmdev->dev.bus = &scm_bus_type; | |
148 | scmdev->dev.release = scmdev_release; | |
149 | scmdev->dev.groups = scmdev_attr_groups; | |
1d1c8f78 SO |
150 | } |
151 | ||
40ff4cc0 SO |
152 | /* |
153 | * Check for state-changes, notify the driver and userspace. | |
154 | */ | |
155 | static void scmdev_update(struct scm_device *scmdev, struct sale *sale) | |
156 | { | |
157 | struct scm_driver *scmdrv; | |
158 | bool changed; | |
159 | ||
160 | device_lock(&scmdev->dev); | |
161 | changed = scmdev->attrs.rank != sale->rank || | |
162 | scmdev->attrs.oper_state != sale->op_state; | |
163 | scmdev->attrs.rank = sale->rank; | |
164 | scmdev->attrs.oper_state = sale->op_state; | |
165 | if (!scmdev->dev.driver) | |
166 | goto out; | |
167 | scmdrv = to_scm_drv(scmdev->dev.driver); | |
168 | if (changed && scmdrv->notify) | |
93481c90 | 169 | scmdrv->notify(scmdev, SCM_CHANGE); |
40ff4cc0 SO |
170 | out: |
171 | device_unlock(&scmdev->dev); | |
172 | if (changed) | |
173 | kobject_uevent(&scmdev->dev.kobj, KOBJ_CHANGE); | |
174 | } | |
175 | ||
176 | static int check_address(struct device *dev, void *data) | |
177 | { | |
178 | struct scm_device *scmdev = to_scm_dev(dev); | |
179 | struct sale *sale = data; | |
180 | ||
181 | return scmdev->address == sale->sa; | |
182 | } | |
183 | ||
184 | static struct scm_device *scmdev_find(struct sale *sale) | |
185 | { | |
186 | struct device *dev; | |
187 | ||
188 | dev = bus_find_device(&scm_bus_type, NULL, sale, check_address); | |
189 | ||
190 | return dev ? to_scm_dev(dev) : NULL; | |
191 | } | |
192 | ||
1d1c8f78 SO |
193 | static int scm_add(struct chsc_scm_info *scm_info, size_t num) |
194 | { | |
195 | struct sale *sale, *scmal = scm_info->scmal; | |
196 | struct scm_device *scmdev; | |
197 | int ret; | |
198 | ||
199 | for (sale = scmal; sale < scmal + num; sale++) { | |
40ff4cc0 SO |
200 | scmdev = scmdev_find(sale); |
201 | if (scmdev) { | |
202 | scmdev_update(scmdev, sale); | |
203 | /* Release reference from scm_find(). */ | |
204 | put_device(&scmdev->dev); | |
205 | continue; | |
206 | } | |
1d1c8f78 SO |
207 | scmdev = kzalloc(sizeof(*scmdev), GFP_KERNEL); |
208 | if (!scmdev) | |
209 | return -ENODEV; | |
210 | scmdev_setup(scmdev, sale, scm_info->is, scm_info->mbc); | |
211 | ret = device_register(&scmdev->dev); | |
212 | if (ret) { | |
213 | /* Release reference from device_initialize(). */ | |
214 | put_device(&scmdev->dev); | |
215 | return ret; | |
216 | } | |
217 | } | |
218 | ||
219 | return 0; | |
220 | } | |
221 | ||
40ff4cc0 | 222 | int scm_update_information(void) |
1d1c8f78 SO |
223 | { |
224 | struct chsc_scm_info *scm_info; | |
225 | u64 token = 0; | |
226 | size_t num; | |
227 | int ret; | |
228 | ||
229 | scm_info = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); | |
230 | if (!scm_info) | |
231 | return -ENOMEM; | |
232 | ||
233 | do { | |
234 | ret = chsc_scm_info(scm_info, token); | |
235 | if (ret) | |
236 | break; | |
237 | ||
238 | num = (scm_info->response.length - | |
239 | (offsetof(struct chsc_scm_info, scmal) - | |
240 | offsetof(struct chsc_scm_info, response)) | |
241 | ) / sizeof(struct sale); | |
242 | ||
243 | ret = scm_add(scm_info, num); | |
244 | if (ret) | |
245 | break; | |
246 | ||
247 | token = scm_info->restok; | |
248 | } while (token); | |
249 | ||
250 | free_page((unsigned long)scm_info); | |
251 | ||
252 | return ret; | |
253 | } | |
254 | ||
aebfa669 SO |
255 | static int scm_dev_avail(struct device *dev, void *unused) |
256 | { | |
257 | struct scm_driver *scmdrv = to_scm_drv(dev->driver); | |
258 | struct scm_device *scmdev = to_scm_dev(dev); | |
259 | ||
260 | if (dev->driver && scmdrv->notify) | |
261 | scmdrv->notify(scmdev, SCM_AVAIL); | |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
266 | int scm_process_availability_information(void) | |
267 | { | |
268 | return bus_for_each_dev(&scm_bus_type, NULL, NULL, scm_dev_avail); | |
269 | } | |
270 | ||
1d1c8f78 SO |
271 | static int __init scm_init(void) |
272 | { | |
273 | int ret; | |
274 | ||
275 | ret = bus_register(&scm_bus_type); | |
276 | if (ret) | |
277 | return ret; | |
278 | ||
279 | scm_root = root_device_register("scm"); | |
280 | if (IS_ERR(scm_root)) { | |
281 | bus_unregister(&scm_bus_type); | |
282 | return PTR_ERR(scm_root); | |
283 | } | |
284 | ||
285 | scm_update_information(); | |
286 | return 0; | |
287 | } | |
288 | subsys_initcall_sync(scm_init); |