patch-2_6_7-vs1_9_1_12
[linux-2.6.git] / drivers / s390 / cio / ccwgroup.c
1 /*
2  *  drivers/s390/cio/ccwgroup.c
3  *  bus driver for ccwgroup
4  *   $Revision: 1.28 $
5  *
6  *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
7  *                       IBM Corporation
8  *    Author(s): Arnd Bergmann (arndb@de.ibm.com)
9  *               Cornelia Huck (cohuck@de.ibm.com)
10  */
11 #include <linux/module.h>
12 #include <linux/errno.h>
13 #include <linux/slab.h>
14 #include <linux/list.h>
15 #include <linux/device.h>
16 #include <linux/init.h>
17 #include <linux/ctype.h>
18 #include <linux/dcache.h>
19
20 #include <asm/semaphore.h>
21 #include <asm/ccwdev.h>
22 #include <asm/ccwgroup.h>
23
24 /* In Linux 2.4, we had a channel device layer called "chandev"
25  * that did all sorts of obscure stuff for networking devices.
26  * This is another driver that serves as a replacement for just
27  * one of its functions, namely the translation of single subchannels
28  * to devices that use multiple subchannels.
29  */
30
31 /* a device matches a driver if all its slave devices match the same
32  * entry of the driver */
33 static int
34 ccwgroup_bus_match (struct device * dev, struct device_driver * drv)
35 {
36         struct ccwgroup_device *gdev;
37         struct ccwgroup_driver *gdrv;
38
39         gdev = container_of(dev, struct ccwgroup_device, dev);
40         gdrv = container_of(drv, struct ccwgroup_driver, driver);
41
42         if (gdev->creator_id == gdrv->driver_id)
43                 return 1;
44
45         return 0;
46 }
47 static int
48 ccwgroup_hotplug (struct device *dev, char **envp, int num_envp, char *buffer,
49                   int buffer_size)
50 {
51         /* TODO */
52         return 0;
53 }
54
55 static struct bus_type ccwgroup_bus_type = {
56         .name    = "ccwgroup",
57         .match   = ccwgroup_bus_match,
58         .hotplug = ccwgroup_hotplug,
59 };
60
61 static inline void
62 __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
63 {
64         int i;
65         char str[8];
66
67         for (i = 0; i < gdev->count; i++) {
68                 sprintf(str, "cdev%d", i);
69                 sysfs_remove_link(&gdev->dev.kobj, str);
70                 sysfs_remove_link(&gdev->cdev[i]->dev.kobj, "group_device");
71         }
72         
73 }
74
75 /*
76  * Provide an 'ungroup' attribute so the user can remove group devices no
77  * longer needed or accidentially created. Saves memory :)
78  */
79 static ssize_t
80 ccwgroup_ungroup_store(struct device *dev, const char *buf, size_t count)
81 {
82         struct ccwgroup_device *gdev;
83
84         gdev = to_ccwgroupdev(dev);
85
86         if (gdev->state != CCWGROUP_OFFLINE)
87                 return -EINVAL;
88
89         __ccwgroup_remove_symlinks(gdev);
90         device_unregister(dev);
91
92         return count;
93 }
94
95 static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store);
96
97 static void
98 ccwgroup_release (struct device *dev)
99 {
100         struct ccwgroup_device *gdev;
101         int i;
102
103         gdev = to_ccwgroupdev(dev);
104
105         for (i = 0; i < gdev->count; i++) {
106                 gdev->cdev[i]->dev.driver_data = NULL;
107                 put_device(&gdev->cdev[i]->dev);
108         }
109         kfree(gdev);
110 }
111
112 static inline int
113 __ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
114 {
115         char str[8];
116         int i, rc;
117
118         for (i = 0; i < gdev->count; i++) {
119                 rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj, &gdev->dev.kobj,
120                                        "group_device");
121                 if (rc) {
122                         for (--i; i >= 0; i--)
123                                 sysfs_remove_link(&gdev->cdev[i]->dev.kobj,
124                                                   "group_device");
125                         return rc;
126                 }
127         }
128         for (i = 0; i < gdev->count; i++) {
129                 sprintf(str, "cdev%d", i);
130                 rc = sysfs_create_link(&gdev->dev.kobj, &gdev->cdev[i]->dev.kobj,
131                                        str);
132                 if (rc) {
133                         for (--i; i >= 0; i--) {
134                                 sprintf(str, "cdev%d", i);
135                                 sysfs_remove_link(&gdev->dev.kobj, str);
136                         }
137                         for (i = 0; i < gdev->count; i++)
138                                 sysfs_remove_link(&gdev->cdev[i]->dev.kobj,
139                                                   "group_device");
140                         return rc;
141                 }
142         }
143         return 0;
144 }
145
146 /*
147  * try to add a new ccwgroup device for one driver
148  * argc and argv[] are a list of bus_id's of devices
149  * belonging to the driver.
150  */
151 int
152 ccwgroup_create(struct device *root,
153                 unsigned int creator_id,
154                 struct ccw_driver *cdrv,
155                 int argc, char *argv[])
156 {
157         struct ccwgroup_device *gdev;
158         int i;
159         int rc;
160         int del_drvdata;
161
162         if (argc > 256) /* disallow dumb users */
163                 return -EINVAL;
164
165         gdev = kmalloc(sizeof(*gdev) + argc*sizeof(gdev->cdev[0]), GFP_KERNEL);
166         if (!gdev)
167                 return -ENOMEM;
168
169         memset(gdev, 0, sizeof(*gdev) + argc*sizeof(gdev->cdev[0]));
170         atomic_set(&gdev->onoff, 0);
171
172         del_drvdata = 0;
173         for (i = 0; i < argc; i++) {
174                 gdev->cdev[i] = get_ccwdev_by_busid(cdrv, argv[i]);
175
176                 /* all devices have to be of the same type in
177                  * order to be grouped */
178                 if (!gdev->cdev[i]
179                     || gdev->cdev[i]->id.driver_info !=
180                     gdev->cdev[0]->id.driver_info) {
181                         rc = -EINVAL;
182                         goto free_dev;
183                 }
184                 /* Don't allow a device to belong to more than one group. */
185                 if (gdev->cdev[i]->dev.driver_data) {
186                         rc = -EINVAL;
187                         goto free_dev;
188                 }
189         }
190         for (i = 0; i < argc; i++)
191                 gdev->cdev[i]->dev.driver_data = gdev;
192         del_drvdata = 1;
193
194         *gdev = (struct ccwgroup_device) {
195                 .creator_id = creator_id,
196                 .count = argc,
197                 .dev = {
198                         .bus = &ccwgroup_bus_type,
199                         .parent = root,
200                         .release = ccwgroup_release,
201                 },
202         };
203
204         snprintf (gdev->dev.bus_id, BUS_ID_SIZE, "%s",
205                         gdev->cdev[0]->dev.bus_id);
206
207         rc = device_register(&gdev->dev);
208         
209         if (rc)
210                 goto free_dev;
211         get_device(&gdev->dev);
212         rc = device_create_file(&gdev->dev, &dev_attr_ungroup);
213
214         if (rc) {
215                 device_unregister(&gdev->dev);
216                 goto error;
217         }
218
219         rc = __ccwgroup_create_symlinks(gdev);
220         if (!rc) {
221                 put_device(&gdev->dev);
222                 return 0;
223         }
224         device_remove_file(&gdev->dev, &dev_attr_ungroup);
225         device_unregister(&gdev->dev);
226 error:
227         for (i = 0; i < argc; i++)
228                 if (gdev->cdev[i]) {
229                         put_device(&gdev->cdev[i]->dev);
230                         gdev->cdev[i]->dev.driver_data = NULL;
231                 }
232         put_device(&gdev->dev);
233         return rc;
234 free_dev:
235         for (i = 0; i < argc; i++)
236                 if (gdev->cdev[i]) {
237                         put_device(&gdev->cdev[i]->dev);
238                         if (del_drvdata)
239                                 gdev->cdev[i]->dev.driver_data = NULL;
240                 }
241         kfree(gdev);
242         return rc;
243 }
244
245 static int __init
246 init_ccwgroup (void)
247 {
248         return bus_register (&ccwgroup_bus_type);
249 }
250
251 static void __exit
252 cleanup_ccwgroup (void)
253 {
254         bus_unregister (&ccwgroup_bus_type);
255 }
256
257 module_init(init_ccwgroup);
258 module_exit(cleanup_ccwgroup);
259
260 /************************** driver stuff ******************************/
261
262 static int
263 ccwgroup_set_online(struct ccwgroup_device *gdev)
264 {
265         struct ccwgroup_driver *gdrv;
266         int ret;
267
268         if (atomic_compare_and_swap(0, 1, &gdev->onoff))
269                 return -EAGAIN;
270         if (gdev->state == CCWGROUP_ONLINE) {
271                 ret = 0;
272                 goto out;
273         }
274         if (!gdev->dev.driver) {
275                 ret = -EINVAL;
276                 goto out;
277         }
278         gdrv = to_ccwgroupdrv (gdev->dev.driver);
279         if ((ret = gdrv->set_online(gdev)))
280                 goto out;
281
282         gdev->state = CCWGROUP_ONLINE;
283  out:
284         atomic_set(&gdev->onoff, 0);
285         return ret;
286 }
287
288 static int
289 ccwgroup_set_offline(struct ccwgroup_device *gdev)
290 {
291         struct ccwgroup_driver *gdrv;
292         int ret;
293
294         if (atomic_compare_and_swap(0, 1, &gdev->onoff))
295                 return -EAGAIN;
296         if (gdev->state == CCWGROUP_OFFLINE) {
297                 ret = 0;
298                 goto out;
299         }
300         if (!gdev->dev.driver) {
301                 ret = -EINVAL;
302                 goto out;
303         }
304         gdrv = to_ccwgroupdrv (gdev->dev.driver);
305         if ((ret = gdrv->set_offline(gdev)))
306                 goto out;
307
308         gdev->state = CCWGROUP_OFFLINE;
309  out:
310         atomic_set(&gdev->onoff, 0);
311         return ret;
312 }
313
314 static ssize_t
315 ccwgroup_online_store (struct device *dev, const char *buf, size_t count)
316 {
317         struct ccwgroup_device *gdev;
318         struct ccwgroup_driver *gdrv;
319         unsigned int value;
320         int ret;
321
322         gdev = to_ccwgroupdev(dev);
323         if (!dev->driver)
324                 return count;
325
326         gdrv = to_ccwgroupdrv (gdev->dev.driver);
327         if (!try_module_get(gdrv->owner))
328                 return -EINVAL;
329
330         value = simple_strtoul(buf, 0, 0);
331         ret = count;
332         if (value == 1)
333                 ccwgroup_set_online(gdev);
334         else if (value == 0)
335                 ccwgroup_set_offline(gdev);
336         else
337                 ret = -EINVAL;
338         module_put(gdrv->owner);
339         return ret;
340 }
341
342 static ssize_t
343 ccwgroup_online_show (struct device *dev, char *buf)
344 {
345         int online;
346
347         online = (to_ccwgroupdev(dev)->state == CCWGROUP_ONLINE);
348
349         return sprintf(buf, online ? "1\n" : "0\n");
350 }
351
352 static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store);
353
354 static int
355 ccwgroup_probe (struct device *dev)
356 {
357         struct ccwgroup_device *gdev;
358         struct ccwgroup_driver *gdrv;
359
360         int ret;
361
362         gdev = to_ccwgroupdev(dev);
363         gdrv = to_ccwgroupdrv(dev->driver);
364
365         if ((ret = device_create_file(dev, &dev_attr_online)))
366                 return ret;
367
368         pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id);
369         ret = gdrv->probe ? gdrv->probe(gdev) : -ENODEV;
370         if (ret)
371                 device_remove_file(dev, &dev_attr_online);
372
373         return ret;
374 }
375
376 static int
377 ccwgroup_remove (struct device *dev)
378 {
379         struct ccwgroup_device *gdev;
380         struct ccwgroup_driver *gdrv;
381
382         gdev = to_ccwgroupdev(dev);
383         gdrv = to_ccwgroupdrv(dev->driver);
384
385         pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id);
386
387         device_remove_file(dev, &dev_attr_online);
388
389         if (gdrv && gdrv->remove)
390                 gdrv->remove(gdev);
391         return 0;
392 }
393
394 int
395 ccwgroup_driver_register (struct ccwgroup_driver *cdriver)
396 {
397         /* register our new driver with the core */
398         cdriver->driver = (struct device_driver) {
399                 .bus = &ccwgroup_bus_type,
400                 .name = cdriver->name,
401                 .probe = ccwgroup_probe,
402                 .remove = ccwgroup_remove,
403         };
404
405         return driver_register(&cdriver->driver);
406 }
407
408 static inline struct device *
409 __get_next_ccwgroup_device(struct device_driver *drv)
410 {
411         struct device *dev, *d;
412
413         down_read(&drv->bus->subsys.rwsem);
414         dev = NULL;
415         list_for_each_entry(d, &drv->devices, driver_list) {
416                 dev = get_device(d);
417                 if (dev)
418                         break;
419         }
420         up_read(&drv->bus->subsys.rwsem);
421         return dev;
422 }
423
424 void
425 ccwgroup_driver_unregister (struct ccwgroup_driver *cdriver)
426 {
427         struct device *dev;
428
429         /* We don't want ccwgroup devices to live longer than their driver. */
430         get_driver(&cdriver->driver);
431         while ((dev = __get_next_ccwgroup_device(&cdriver->driver))) {
432                 __ccwgroup_remove_symlinks(to_ccwgroupdev(dev));
433                 device_unregister(dev);
434                 put_device(dev);
435         };
436         put_driver(&cdriver->driver);
437         driver_unregister(&cdriver->driver);
438 }
439
440 int
441 ccwgroup_probe_ccwdev(struct ccw_device *cdev)
442 {
443         return 0;
444 }
445
446 static inline struct ccwgroup_device *
447 __ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
448 {
449         struct ccwgroup_device *gdev;
450
451         if (cdev->dev.driver_data) {
452                 gdev = (struct ccwgroup_device *)cdev->dev.driver_data;
453                 if (get_device(&gdev->dev)) {
454                         if (!list_empty(&gdev->dev.node))
455                                 return gdev;
456                         put_device(&gdev->dev);
457                 }
458                 return NULL;
459         }
460         return NULL;
461 }
462
463 void
464 ccwgroup_remove_ccwdev(struct ccw_device *cdev)
465 {
466         struct ccwgroup_device *gdev;
467
468         /* Ignore offlining errors, device is gone anyway. */
469         ccw_device_set_offline(cdev);
470         /* If one of its devices is gone, the whole group is done for. */
471         gdev = __ccwgroup_get_gdev_by_cdev(cdev);
472         if (gdev) {
473                 __ccwgroup_remove_symlinks(gdev);
474                 device_unregister(&gdev->dev);
475                 put_device(&gdev->dev);
476         }
477 }
478
479 MODULE_LICENSE("GPL");
480 EXPORT_SYMBOL(ccwgroup_driver_register);
481 EXPORT_SYMBOL(ccwgroup_driver_unregister);
482 EXPORT_SYMBOL(ccwgroup_create);
483 EXPORT_SYMBOL(ccwgroup_probe_ccwdev);
484 EXPORT_SYMBOL(ccwgroup_remove_ccwdev);