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