linux/drivers/s390/cio/ccwgroup.c
<<
>>
Prefs
   1/*
   2 *  bus driver for ccwgroup
   3 *
   4 *  Copyright IBM Corp. 2002, 2009
   5 *
   6 *  Author(s): Arnd Bergmann (arndb@de.ibm.com)
   7 *             Cornelia Huck (cornelia.huck@de.ibm.com)
   8 */
   9#include <linux/module.h>
  10#include <linux/errno.h>
  11#include <linux/slab.h>
  12#include <linux/list.h>
  13#include <linux/device.h>
  14#include <linux/init.h>
  15#include <linux/ctype.h>
  16#include <linux/dcache.h>
  17
  18#include <asm/ccwdev.h>
  19#include <asm/ccwgroup.h>
  20
  21#define CCW_BUS_ID_SIZE         20
  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 */
  32static int
  33ccwgroup_bus_match (struct device * dev, struct device_driver * drv)
  34{
  35        struct ccwgroup_device *gdev;
  36        struct ccwgroup_driver *gdrv;
  37
  38        gdev = to_ccwgroupdev(dev);
  39        gdrv = to_ccwgroupdrv(drv);
  40
  41        if (gdev->creator_id == gdrv->driver_id)
  42                return 1;
  43
  44        return 0;
  45}
  46static int
  47ccwgroup_uevent (struct device *dev, struct kobj_uevent_env *env)
  48{
  49        /* TODO */
  50        return 0;
  51}
  52
  53static struct bus_type ccwgroup_bus_type;
  54
  55static void
  56__ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
  57{
  58        int i;
  59        char str[8];
  60
  61        for (i = 0; i < gdev->count; i++) {
  62                sprintf(str, "cdev%d", i);
  63                sysfs_remove_link(&gdev->dev.kobj, str);
  64                sysfs_remove_link(&gdev->cdev[i]->dev.kobj, "group_device");
  65        }
  66        
  67}
  68
  69/*
  70 * Provide an 'ungroup' attribute so the user can remove group devices no
  71 * longer needed or accidentially created. Saves memory :)
  72 */
  73static void ccwgroup_ungroup_callback(struct device *dev)
  74{
  75        struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
  76
  77        mutex_lock(&gdev->reg_mutex);
  78        if (device_is_registered(&gdev->dev)) {
  79                __ccwgroup_remove_symlinks(gdev);
  80                device_unregister(dev);
  81        }
  82        mutex_unlock(&gdev->reg_mutex);
  83}
  84
  85static ssize_t
  86ccwgroup_ungroup_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
  87{
  88        struct ccwgroup_device *gdev;
  89        int rc;
  90
  91        gdev = to_ccwgroupdev(dev);
  92
  93        /* Prevent concurrent online/offline processing and ungrouping. */
  94        if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
  95                return -EAGAIN;
  96        if (gdev->state != CCWGROUP_OFFLINE) {
  97                rc = -EINVAL;
  98                goto out;
  99        }
 100        /* Note that we cannot unregister the device from one of its
 101         * attribute methods, so we have to use this roundabout approach.
 102         */
 103        rc = device_schedule_callback(dev, ccwgroup_ungroup_callback);
 104out:
 105        if (rc) {
 106                if (rc != -EAGAIN)
 107                        /* Release onoff "lock" when ungrouping failed. */
 108                        atomic_set(&gdev->onoff, 0);
 109                return rc;
 110        }
 111        return count;
 112}
 113
 114static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store);
 115
 116static void
 117ccwgroup_release (struct device *dev)
 118{
 119        struct ccwgroup_device *gdev;
 120        int i;
 121
 122        gdev = to_ccwgroupdev(dev);
 123
 124        for (i = 0; i < gdev->count; i++) {
 125                if (gdev->cdev[i]) {
 126                        if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
 127                                dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
 128                        put_device(&gdev->cdev[i]->dev);
 129                }
 130        }
 131        kfree(gdev);
 132}
 133
 134static int
 135__ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
 136{
 137        char str[8];
 138        int i, rc;
 139
 140        for (i = 0; i < gdev->count; i++) {
 141                rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj, &gdev->dev.kobj,
 142                                       "group_device");
 143                if (rc) {
 144                        for (--i; i >= 0; i--)
 145                                sysfs_remove_link(&gdev->cdev[i]->dev.kobj,
 146                                                  "group_device");
 147                        return rc;
 148                }
 149        }
 150        for (i = 0; i < gdev->count; i++) {
 151                sprintf(str, "cdev%d", i);
 152                rc = sysfs_create_link(&gdev->dev.kobj, &gdev->cdev[i]->dev.kobj,
 153                                       str);
 154                if (rc) {
 155                        for (--i; i >= 0; i--) {
 156                                sprintf(str, "cdev%d", i);
 157                                sysfs_remove_link(&gdev->dev.kobj, str);
 158                        }
 159                        for (i = 0; i < gdev->count; i++)
 160                                sysfs_remove_link(&gdev->cdev[i]->dev.kobj,
 161                                                  "group_device");
 162                        return rc;
 163                }
 164        }
 165        return 0;
 166}
 167
 168static int __get_next_bus_id(const char **buf, char *bus_id)
 169{
 170        int rc, len;
 171        char *start, *end;
 172
 173        start = (char *)*buf;
 174        end = strchr(start, ',');
 175        if (!end) {
 176                /* Last entry. Strip trailing newline, if applicable. */
 177                end = strchr(start, '\n');
 178                if (end)
 179                        *end = '\0';
 180                len = strlen(start) + 1;
 181        } else {
 182                len = end - start + 1;
 183                end++;
 184        }
 185        if (len < CCW_BUS_ID_SIZE) {
 186                strlcpy(bus_id, start, len);
 187                rc = 0;
 188        } else
 189                rc = -EINVAL;
 190        *buf = end;
 191        return rc;
 192}
 193
 194static int __is_valid_bus_id(char bus_id[CCW_BUS_ID_SIZE])
 195{
 196        int cssid, ssid, devno;
 197
 198        /* Must be of form %x.%x.%04x */
 199        if (sscanf(bus_id, "%x.%1x.%04x", &cssid, &ssid, &devno) != 3)
 200                return 0;
 201        return 1;
 202}
 203
 204/**
 205 * ccwgroup_create_from_string() - create and register a ccw group device
 206 * @root: parent device for the new device
 207 * @creator_id: identifier of creating driver
 208 * @cdrv: ccw driver of slave devices
 209 * @num_devices: number of slave devices
 210 * @buf: buffer containing comma separated bus ids of slave devices
 211 *
 212 * Create and register a new ccw group device as a child of @root. Slave
 213 * devices are obtained from the list of bus ids given in @buf and must all
 214 * belong to @cdrv.
 215 * Returns:
 216 *  %0 on success and an error code on failure.
 217 * Context:
 218 *  non-atomic
 219 */
 220int ccwgroup_create_from_string(struct device *root, unsigned int creator_id,
 221                                struct ccw_driver *cdrv, int num_devices,
 222                                const char *buf)
 223{
 224        struct ccwgroup_device *gdev;
 225        int rc, i;
 226        char tmp_bus_id[CCW_BUS_ID_SIZE];
 227        const char *curr_buf;
 228
 229        gdev = kzalloc(sizeof(*gdev) + num_devices * sizeof(gdev->cdev[0]),
 230                       GFP_KERNEL);
 231        if (!gdev)
 232                return -ENOMEM;
 233
 234        atomic_set(&gdev->onoff, 0);
 235        mutex_init(&gdev->reg_mutex);
 236        mutex_lock(&gdev->reg_mutex);
 237        gdev->creator_id = creator_id;
 238        gdev->count = num_devices;
 239        gdev->dev.bus = &ccwgroup_bus_type;
 240        gdev->dev.parent = root;
 241        gdev->dev.release = ccwgroup_release;
 242        device_initialize(&gdev->dev);
 243
 244        curr_buf = buf;
 245        for (i = 0; i < num_devices && curr_buf; i++) {
 246                rc = __get_next_bus_id(&curr_buf, tmp_bus_id);
 247                if (rc != 0)
 248                        goto error;
 249                if (!__is_valid_bus_id(tmp_bus_id)) {
 250                        rc = -EINVAL;
 251                        goto error;
 252                }
 253                gdev->cdev[i] = get_ccwdev_by_busid(cdrv, tmp_bus_id);
 254                /*
 255                 * All devices have to be of the same type in
 256                 * order to be grouped.
 257                 */
 258                if (!gdev->cdev[i]
 259                    || gdev->cdev[i]->id.driver_info !=
 260                    gdev->cdev[0]->id.driver_info) {
 261                        rc = -EINVAL;
 262                        goto error;
 263                }
 264                /* Don't allow a device to belong to more than one group. */
 265                if (dev_get_drvdata(&gdev->cdev[i]->dev)) {
 266                        rc = -EINVAL;
 267                        goto error;
 268                }
 269                dev_set_drvdata(&gdev->cdev[i]->dev, gdev);
 270        }
 271        /* Check for sufficient number of bus ids. */
 272        if (i < num_devices && !curr_buf) {
 273                rc = -EINVAL;
 274                goto error;
 275        }
 276        /* Check for trailing stuff. */
 277        if (i == num_devices && strlen(curr_buf) > 0) {
 278                rc = -EINVAL;
 279                goto error;
 280        }
 281
 282        dev_set_name(&gdev->dev, "%s", dev_name(&gdev->cdev[0]->dev));
 283
 284        rc = device_add(&gdev->dev);
 285        if (rc)
 286                goto error;
 287        get_device(&gdev->dev);
 288        rc = device_create_file(&gdev->dev, &dev_attr_ungroup);
 289
 290        if (rc) {
 291                device_unregister(&gdev->dev);
 292                goto error;
 293        }
 294
 295        rc = __ccwgroup_create_symlinks(gdev);
 296        if (!rc) {
 297                mutex_unlock(&gdev->reg_mutex);
 298                put_device(&gdev->dev);
 299                return 0;
 300        }
 301        device_remove_file(&gdev->dev, &dev_attr_ungroup);
 302        device_unregister(&gdev->dev);
 303error:
 304        for (i = 0; i < num_devices; i++)
 305                if (gdev->cdev[i]) {
 306                        if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
 307                                dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
 308                        put_device(&gdev->cdev[i]->dev);
 309                        gdev->cdev[i] = NULL;
 310                }
 311        mutex_unlock(&gdev->reg_mutex);
 312        put_device(&gdev->dev);
 313        return rc;
 314}
 315EXPORT_SYMBOL(ccwgroup_create_from_string);
 316
 317static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action,
 318                             void *data);
 319
 320static struct notifier_block ccwgroup_nb = {
 321        .notifier_call = ccwgroup_notifier
 322};
 323
 324static int __init init_ccwgroup(void)
 325{
 326        int ret;
 327
 328        ret = bus_register(&ccwgroup_bus_type);
 329        if (ret)
 330                return ret;
 331
 332        ret = bus_register_notifier(&ccwgroup_bus_type, &ccwgroup_nb);
 333        if (ret)
 334                bus_unregister(&ccwgroup_bus_type);
 335
 336        return ret;
 337}
 338
 339static void __exit cleanup_ccwgroup(void)
 340{
 341        bus_unregister_notifier(&ccwgroup_bus_type, &ccwgroup_nb);
 342        bus_unregister(&ccwgroup_bus_type);
 343}
 344
 345module_init(init_ccwgroup);
 346module_exit(cleanup_ccwgroup);
 347
 348/************************** driver stuff ******************************/
 349
 350static int
 351ccwgroup_set_online(struct ccwgroup_device *gdev)
 352{
 353        struct ccwgroup_driver *gdrv;
 354        int ret;
 355
 356        if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
 357                return -EAGAIN;
 358        if (gdev->state == CCWGROUP_ONLINE) {
 359                ret = 0;
 360                goto out;
 361        }
 362        if (!gdev->dev.driver) {
 363                ret = -EINVAL;
 364                goto out;
 365        }
 366        gdrv = to_ccwgroupdrv (gdev->dev.driver);
 367        if ((ret = gdrv->set_online ? gdrv->set_online(gdev) : 0))
 368                goto out;
 369
 370        gdev->state = CCWGROUP_ONLINE;
 371 out:
 372        atomic_set(&gdev->onoff, 0);
 373        return ret;
 374}
 375
 376static int
 377ccwgroup_set_offline(struct ccwgroup_device *gdev)
 378{
 379        struct ccwgroup_driver *gdrv;
 380        int ret;
 381
 382        if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
 383                return -EAGAIN;
 384        if (gdev->state == CCWGROUP_OFFLINE) {
 385                ret = 0;
 386                goto out;
 387        }
 388        if (!gdev->dev.driver) {
 389                ret = -EINVAL;
 390                goto out;
 391        }
 392        gdrv = to_ccwgroupdrv (gdev->dev.driver);
 393        if ((ret = gdrv->set_offline ? gdrv->set_offline(gdev) : 0))
 394                goto out;
 395
 396        gdev->state = CCWGROUP_OFFLINE;
 397 out:
 398        atomic_set(&gdev->onoff, 0);
 399        return ret;
 400}
 401
 402static ssize_t
 403ccwgroup_online_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
 404{
 405        struct ccwgroup_device *gdev;
 406        struct ccwgroup_driver *gdrv;
 407        unsigned long value;
 408        int ret;
 409
 410        if (!dev->driver)
 411                return -ENODEV;
 412
 413        gdev = to_ccwgroupdev(dev);
 414        gdrv = to_ccwgroupdrv(dev->driver);
 415
 416        if (!try_module_get(gdrv->owner))
 417                return -EINVAL;
 418
 419        ret = strict_strtoul(buf, 0, &value);
 420        if (ret)
 421                goto out;
 422
 423        if (value == 1)
 424                ret = ccwgroup_set_online(gdev);
 425        else if (value == 0)
 426                ret = ccwgroup_set_offline(gdev);
 427        else
 428                ret = -EINVAL;
 429out:
 430        module_put(gdrv->owner);
 431        return (ret == 0) ? count : ret;
 432}
 433
 434static ssize_t
 435ccwgroup_online_show (struct device *dev, struct device_attribute *attr, char *buf)
 436{
 437        int online;
 438
 439        online = (to_ccwgroupdev(dev)->state == CCWGROUP_ONLINE);
 440
 441        return sprintf(buf, online ? "1\n" : "0\n");
 442}
 443
 444static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store);
 445
 446static int
 447ccwgroup_probe (struct device *dev)
 448{
 449        struct ccwgroup_device *gdev;
 450        struct ccwgroup_driver *gdrv;
 451
 452        int ret;
 453
 454        gdev = to_ccwgroupdev(dev);
 455        gdrv = to_ccwgroupdrv(dev->driver);
 456
 457        if ((ret = device_create_file(dev, &dev_attr_online)))
 458                return ret;
 459
 460        ret = gdrv->probe ? gdrv->probe(gdev) : -ENODEV;
 461        if (ret)
 462                device_remove_file(dev, &dev_attr_online);
 463
 464        return ret;
 465}
 466
 467static int
 468ccwgroup_remove (struct device *dev)
 469{
 470        struct ccwgroup_device *gdev;
 471        struct ccwgroup_driver *gdrv;
 472
 473        device_remove_file(dev, &dev_attr_online);
 474        device_remove_file(dev, &dev_attr_ungroup);
 475
 476        if (!dev->driver)
 477                return 0;
 478
 479        gdev = to_ccwgroupdev(dev);
 480        gdrv = to_ccwgroupdrv(dev->driver);
 481
 482        if (gdrv->remove)
 483                gdrv->remove(gdev);
 484
 485        return 0;
 486}
 487
 488static void ccwgroup_shutdown(struct device *dev)
 489{
 490        struct ccwgroup_device *gdev;
 491        struct ccwgroup_driver *gdrv;
 492
 493        if (!dev->driver)
 494                return;
 495
 496        gdev = to_ccwgroupdev(dev);
 497        gdrv = to_ccwgroupdrv(dev->driver);
 498
 499        if (gdrv->shutdown)
 500                gdrv->shutdown(gdev);
 501}
 502
 503static int ccwgroup_pm_prepare(struct device *dev)
 504{
 505        struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
 506        struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
 507
 508        /* Fail while device is being set online/offline. */
 509        if (atomic_read(&gdev->onoff))
 510                return -EAGAIN;
 511
 512        if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
 513                return 0;
 514
 515        return gdrv->prepare ? gdrv->prepare(gdev) : 0;
 516}
 517
 518static void ccwgroup_pm_complete(struct device *dev)
 519{
 520        struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
 521        struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);
 522
 523        if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
 524                return;
 525
 526        if (gdrv->complete)
 527                gdrv->complete(gdev);
 528}
 529
 530static int ccwgroup_pm_freeze(struct device *dev)
 531{
 532        struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
 533        struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
 534
 535        if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
 536                return 0;
 537
 538        return gdrv->freeze ? gdrv->freeze(gdev) : 0;
 539}
 540
 541static int ccwgroup_pm_thaw(struct device *dev)
 542{
 543        struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
 544        struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
 545
 546        if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
 547                return 0;
 548
 549        return gdrv->thaw ? gdrv->thaw(gdev) : 0;
 550}
 551
 552static int ccwgroup_pm_restore(struct device *dev)
 553{
 554        struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
 555        struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
 556
 557        if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
 558                return 0;
 559
 560        return gdrv->restore ? gdrv->restore(gdev) : 0;
 561}
 562
 563static struct dev_pm_ops ccwgroup_pm_ops = {
 564        .prepare = ccwgroup_pm_prepare,
 565        .complete = ccwgroup_pm_complete,
 566        .freeze = ccwgroup_pm_freeze,
 567        .thaw = ccwgroup_pm_thaw,
 568        .restore = ccwgroup_pm_restore,
 569};
 570
 571static struct bus_type ccwgroup_bus_type = {
 572        .name   = "ccwgroup",
 573        .match  = ccwgroup_bus_match,
 574        .uevent = ccwgroup_uevent,
 575        .probe  = ccwgroup_probe,
 576        .remove = ccwgroup_remove,
 577        .shutdown = ccwgroup_shutdown,
 578        .pm = &ccwgroup_pm_ops,
 579};
 580
 581
 582static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action,
 583                             void *data)
 584{
 585        struct device *dev = data;
 586
 587        if (action == BUS_NOTIFY_UNBIND_DRIVER)
 588                device_schedule_callback(dev, ccwgroup_ungroup_callback);
 589
 590        return NOTIFY_OK;
 591}
 592
 593
 594/**
 595 * ccwgroup_driver_register() - register a ccw group driver
 596 * @cdriver: driver to be registered
 597 *
 598 * This function is mainly a wrapper around driver_register().
 599 */
 600int ccwgroup_driver_register(struct ccwgroup_driver *cdriver)
 601{
 602        /* register our new driver with the core */
 603        cdriver->driver.bus = &ccwgroup_bus_type;
 604        cdriver->driver.name = cdriver->name;
 605        cdriver->driver.owner = cdriver->owner;
 606
 607        return driver_register(&cdriver->driver);
 608}
 609
 610static int
 611__ccwgroup_match_all(struct device *dev, void *data)
 612{
 613        return 1;
 614}
 615
 616/**
 617 * ccwgroup_driver_unregister() - deregister a ccw group driver
 618 * @cdriver: driver to be deregistered
 619 *
 620 * This function is mainly a wrapper around driver_unregister().
 621 */
 622void ccwgroup_driver_unregister(struct ccwgroup_driver *cdriver)
 623{
 624        struct device *dev;
 625
 626        /* We don't want ccwgroup devices to live longer than their driver. */
 627        get_driver(&cdriver->driver);
 628        while ((dev = driver_find_device(&cdriver->driver, NULL, NULL,
 629                                         __ccwgroup_match_all))) {
 630                struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
 631
 632                mutex_lock(&gdev->reg_mutex);
 633                __ccwgroup_remove_symlinks(gdev);
 634                device_unregister(dev);
 635                mutex_unlock(&gdev->reg_mutex);
 636                put_device(dev);
 637        }
 638        put_driver(&cdriver->driver);
 639        driver_unregister(&cdriver->driver);
 640}
 641
 642/**
 643 * ccwgroup_probe_ccwdev() - probe function for slave devices
 644 * @cdev: ccw device to be probed
 645 *
 646 * This is a dummy probe function for ccw devices that are slave devices in
 647 * a ccw group device.
 648 * Returns:
 649 *  always %0
 650 */
 651int ccwgroup_probe_ccwdev(struct ccw_device *cdev)
 652{
 653        return 0;
 654}
 655
 656static struct ccwgroup_device *
 657__ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
 658{
 659        struct ccwgroup_device *gdev;
 660
 661        gdev = dev_get_drvdata(&cdev->dev);
 662        if (gdev) {
 663                if (get_device(&gdev->dev)) {
 664                        mutex_lock(&gdev->reg_mutex);
 665                        if (device_is_registered(&gdev->dev))
 666                                return gdev;
 667                        mutex_unlock(&gdev->reg_mutex);
 668                        put_device(&gdev->dev);
 669                }
 670                return NULL;
 671        }
 672        return NULL;
 673}
 674
 675/**
 676 * ccwgroup_remove_ccwdev() - remove function for slave devices
 677 * @cdev: ccw device to be removed
 678 *
 679 * This is a remove function for ccw devices that are slave devices in a ccw
 680 * group device. It sets the ccw device offline and also deregisters the
 681 * embedding ccw group device.
 682 */
 683void ccwgroup_remove_ccwdev(struct ccw_device *cdev)
 684{
 685        struct ccwgroup_device *gdev;
 686
 687        /* Ignore offlining errors, device is gone anyway. */
 688        ccw_device_set_offline(cdev);
 689        /* If one of its devices is gone, the whole group is done for. */
 690        gdev = __ccwgroup_get_gdev_by_cdev(cdev);
 691        if (gdev) {
 692                __ccwgroup_remove_symlinks(gdev);
 693                device_unregister(&gdev->dev);
 694                mutex_unlock(&gdev->reg_mutex);
 695                put_device(&gdev->dev);
 696        }
 697}
 698
 699MODULE_LICENSE("GPL");
 700EXPORT_SYMBOL(ccwgroup_driver_register);
 701EXPORT_SYMBOL(ccwgroup_driver_unregister);
 702EXPORT_SYMBOL(ccwgroup_probe_ccwdev);
 703EXPORT_SYMBOL(ccwgroup_remove_ccwdev);
 704