ccwgroup.c 15.1 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3
/*
 *  bus driver for ccwgroup
 *
4
 *  Copyright IBM Corp. 2002, 2012
5 6 7
 *
 *  Author(s): Arnd Bergmann (arndb@de.ibm.com)
 *	       Cornelia Huck (cornelia.huck@de.ibm.com)
L
Linus Torvalds 已提交
8 9 10 11 12 13 14 15 16 17
 */
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/ctype.h>
#include <linux/dcache.h>

18
#include <asm/cio.h>
L
Linus Torvalds 已提交
19 20 21
#include <asm/ccwdev.h>
#include <asm/ccwgroup.h>

22 23 24
#include "device.h"

#define CCW_BUS_ID_SIZE		10
25

L
Linus Torvalds 已提交
26 27 28 29 30 31 32
/* In Linux 2.4, we had a channel device layer called "chandev"
 * that did all sorts of obscure stuff for networking devices.
 * This is another driver that serves as a replacement for just
 * one of its functions, namely the translation of single subchannels
 * to devices that use multiple subchannels.
 */

33
static struct bus_type ccwgroup_bus_type;
L
Linus Torvalds 已提交
34

S
Sebastian Ott 已提交
35
static void __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
L
Linus Torvalds 已提交
36 37 38 39 40 41 42 43 44 45 46
{
	int i;
	char str[8];

	for (i = 0; i < gdev->count; i++) {
		sprintf(str, "cdev%d", i);
		sysfs_remove_link(&gdev->dev.kobj, str);
		sysfs_remove_link(&gdev->cdev[i]->dev.kobj, "group_device");
	}
}

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
/*
 * Remove references from ccw devices to ccw group device and from
 * ccw group device to ccw devices.
 */
static void __ccwgroup_remove_cdev_refs(struct ccwgroup_device *gdev)
{
	struct ccw_device *cdev;
	int i;

	for (i = 0; i < gdev->count; i++) {
		cdev = gdev->cdev[i];
		if (!cdev)
			continue;
		spin_lock_irq(cdev->ccwlock);
		dev_set_drvdata(&cdev->dev, NULL);
		spin_unlock_irq(cdev->ccwlock);
		gdev->cdev[i] = NULL;
		put_device(&cdev->dev);
	}
}

S
Sebastian Ott 已提交
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
static int ccwgroup_set_online(struct ccwgroup_device *gdev)
{
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
	int ret = 0;

	if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
		return -EAGAIN;
	if (gdev->state == CCWGROUP_ONLINE)
		goto out;
	if (gdrv->set_online)
		ret = gdrv->set_online(gdev);
	if (ret)
		goto out;

	gdev->state = CCWGROUP_ONLINE;
out:
	atomic_set(&gdev->onoff, 0);
	return ret;
}

static int ccwgroup_set_offline(struct ccwgroup_device *gdev)
{
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);
	int ret = 0;

	if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
		return -EAGAIN;
	if (gdev->state == CCWGROUP_OFFLINE)
		goto out;
	if (gdrv->set_offline)
		ret = gdrv->set_offline(gdev);
	if (ret)
		goto out;

	gdev->state = CCWGROUP_OFFLINE;
out:
	atomic_set(&gdev->onoff, 0);
	return ret;
}

108 109
static ssize_t ccwgroup_online_store(struct device *dev,
				     struct device_attribute *attr,
S
Sebastian Ott 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
				     const char *buf, size_t count)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);
	unsigned long value;
	int ret;

	if (!dev->driver)
		return -EINVAL;
	if (!try_module_get(gdrv->driver.owner))
		return -EINVAL;

	ret = strict_strtoul(buf, 0, &value);
	if (ret)
		goto out;

	if (value == 1)
		ret = ccwgroup_set_online(gdev);
	else if (value == 0)
		ret = ccwgroup_set_offline(gdev);
	else
		ret = -EINVAL;
out:
	module_put(gdrv->driver.owner);
	return (ret == 0) ? count : ret;
}

137 138
static ssize_t ccwgroup_online_show(struct device *dev,
				    struct device_attribute *attr,
S
Sebastian Ott 已提交
139 140 141 142 143 144 145 146 147 148
				    char *buf)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
	int online;

	online = (gdev->state == CCWGROUP_ONLINE) ? 1 : 0;

	return scnprintf(buf, PAGE_SIZE, "%d\n", online);
}

L
Linus Torvalds 已提交
149 150 151 152
/*
 * Provide an 'ungroup' attribute so the user can remove group devices no
 * longer needed or accidentially created. Saves memory :)
 */
153 154 155 156
static void ccwgroup_ungroup_callback(struct device *dev)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);

157
	mutex_lock(&gdev->reg_mutex);
158 159 160
	if (device_is_registered(&gdev->dev)) {
		__ccwgroup_remove_symlinks(gdev);
		device_unregister(dev);
161
		__ccwgroup_remove_cdev_refs(gdev);
162
	}
163
	mutex_unlock(&gdev->reg_mutex);
164 165
}

S
Sebastian Ott 已提交
166 167 168
static ssize_t ccwgroup_ungroup_store(struct device *dev,
				      struct device_attribute *attr,
				      const char *buf, size_t count)
L
Linus Torvalds 已提交
169
{
S
Sebastian Ott 已提交
170
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
171
	int rc;
L
Linus Torvalds 已提交
172

173 174 175 176 177 178 179
	/* Prevent concurrent online/offline processing and ungrouping. */
	if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
		return -EAGAIN;
	if (gdev->state != CCWGROUP_OFFLINE) {
		rc = -EINVAL;
		goto out;
	}
180 181 182 183
	/* Note that we cannot unregister the device from one of its
	 * attribute methods, so we have to use this roundabout approach.
	 */
	rc = device_schedule_callback(dev, ccwgroup_ungroup_callback);
184 185
out:
	if (rc) {
186 187 188
		if (rc != -EAGAIN)
			/* Release onoff "lock" when ungrouping failed. */
			atomic_set(&gdev->onoff, 0);
189 190
		return rc;
	}
L
Linus Torvalds 已提交
191 192 193
	return count;
}
static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store);
194 195 196 197 198 199 200 201 202 203 204 205 206 207
static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store);

static struct attribute *ccwgroup_attrs[] = {
	&dev_attr_online.attr,
	&dev_attr_ungroup.attr,
	NULL,
};
static struct attribute_group ccwgroup_attr_group = {
	.attrs = ccwgroup_attrs,
};
static const struct attribute_group *ccwgroup_attr_groups[] = {
	&ccwgroup_attr_group,
	NULL,
};
L
Linus Torvalds 已提交
208

S
Sebastian Ott 已提交
209
static void ccwgroup_release(struct device *dev)
L
Linus Torvalds 已提交
210
{
211
	kfree(to_ccwgroupdev(dev));
L
Linus Torvalds 已提交
212 213
}

S
Sebastian Ott 已提交
214
static int __ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
L
Linus Torvalds 已提交
215 216 217 218 219
{
	char str[8];
	int i, rc;

	for (i = 0; i < gdev->count; i++) {
S
Sebastian Ott 已提交
220 221
		rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj,
				       &gdev->dev.kobj, "group_device");
L
Linus Torvalds 已提交
222 223 224 225 226 227 228 229 230
		if (rc) {
			for (--i; i >= 0; i--)
				sysfs_remove_link(&gdev->cdev[i]->dev.kobj,
						  "group_device");
			return rc;
		}
	}
	for (i = 0; i < gdev->count; i++) {
		sprintf(str, "cdev%d", i);
S
Sebastian Ott 已提交
231 232
		rc = sysfs_create_link(&gdev->dev.kobj,
				       &gdev->cdev[i]->dev.kobj, str);
L
Linus Torvalds 已提交
233 234 235 236 237 238 239 240 241 242 243 244 245 246
		if (rc) {
			for (--i; i >= 0; i--) {
				sprintf(str, "cdev%d", i);
				sysfs_remove_link(&gdev->dev.kobj, str);
			}
			for (i = 0; i < gdev->count; i++)
				sysfs_remove_link(&gdev->cdev[i]->dev.kobj,
						  "group_device");
			return rc;
		}
	}
	return 0;
}

247
static int __get_next_id(const char **buf, struct ccw_dev_id *id)
248
{
249 250
	unsigned int cssid, ssid, devno;
	int ret = 0, len;
251 252 253 254 255 256 257 258 259 260 261 262 263 264
	char *start, *end;

	start = (char *)*buf;
	end = strchr(start, ',');
	if (!end) {
		/* Last entry. Strip trailing newline, if applicable. */
		end = strchr(start, '\n');
		if (end)
			*end = '\0';
		len = strlen(start) + 1;
	} else {
		len = end - start + 1;
		end++;
	}
265 266 267
	if (len <= CCW_BUS_ID_SIZE) {
		if (sscanf(start, "%2x.%1x.%04x", &cssid, &ssid, &devno) != 3)
			ret = -EINVAL;
268
	} else
269
		ret = -EINVAL;
270

271 272 273 274 275 276
	if (!ret) {
		id->ssid = ssid;
		id->devno = devno;
	}
	*buf = end;
	return ret;
277 278
}

C
Cornelia Huck 已提交
279
/**
280 281 282
 * ccwgroup_create_dev() - create and register a ccw group device
 * @parent: parent device for the new device
 * @gdrv: driver for the new group device
283 284
 * @num_devices: number of slave devices
 * @buf: buffer containing comma separated bus ids of slave devices
C
Cornelia Huck 已提交
285
 *
286
 * Create and register a new ccw group device as a child of @parent. Slave
287
 * devices are obtained from the list of bus ids given in @buf.
C
Cornelia Huck 已提交
288 289 290 291
 * Returns:
 *  %0 on success and an error code on failure.
 * Context:
 *  non-atomic
L
Linus Torvalds 已提交
292
 */
293 294
int ccwgroup_create_dev(struct device *parent, struct ccwgroup_driver *gdrv,
			int num_devices, const char *buf)
L
Linus Torvalds 已提交
295 296
{
	struct ccwgroup_device *gdev;
297
	struct ccw_dev_id dev_id;
298
	int rc, i;
L
Linus Torvalds 已提交
299

300 301
	gdev = kzalloc(sizeof(*gdev) + num_devices * sizeof(gdev->cdev[0]),
		       GFP_KERNEL);
L
Linus Torvalds 已提交
302 303 304 305
	if (!gdev)
		return -ENOMEM;

	atomic_set(&gdev->onoff, 0);
306 307
	mutex_init(&gdev->reg_mutex);
	mutex_lock(&gdev->reg_mutex);
308 309
	gdev->count = num_devices;
	gdev->dev.bus = &ccwgroup_bus_type;
310
	gdev->dev.parent = parent;
311 312 313
	gdev->dev.release = ccwgroup_release;
	device_initialize(&gdev->dev);

314 315
	for (i = 0; i < num_devices && buf; i++) {
		rc = __get_next_id(&buf, &dev_id);
316 317
		if (rc != 0)
			goto error;
318
		gdev->cdev[i] = get_ccwdev_by_dev_id(&dev_id);
319 320 321 322
		/*
		 * All devices have to be of the same type in
		 * order to be grouped.
		 */
323 324 325
		if (!gdev->cdev[i] || !gdev->cdev[i]->drv ||
		    gdev->cdev[i]->drv != gdev->cdev[0]->drv ||
		    gdev->cdev[i]->id.driver_info !=
L
Linus Torvalds 已提交
326 327
		    gdev->cdev[0]->id.driver_info) {
			rc = -EINVAL;
328
			goto error;
L
Linus Torvalds 已提交
329 330
		}
		/* Don't allow a device to belong to more than one group. */
331
		spin_lock_irq(gdev->cdev[i]->ccwlock);
332
		if (dev_get_drvdata(&gdev->cdev[i]->dev)) {
333
			spin_unlock_irq(gdev->cdev[i]->ccwlock);
L
Linus Torvalds 已提交
334
			rc = -EINVAL;
335
			goto error;
L
Linus Torvalds 已提交
336
		}
337
		dev_set_drvdata(&gdev->cdev[i]->dev, gdev);
338
		spin_unlock_irq(gdev->cdev[i]->ccwlock);
339
	}
340
	/* Check for sufficient number of bus ids. */
341
	if (i < num_devices) {
342 343 344 345
		rc = -EINVAL;
		goto error;
	}
	/* Check for trailing stuff. */
346
	if (i == num_devices && strlen(buf) > 0) {
347 348 349
		rc = -EINVAL;
		goto error;
	}
L
Linus Torvalds 已提交
350

351
	dev_set_name(&gdev->dev, "%s", dev_name(&gdev->cdev[0]->dev));
352
	gdev->dev.groups = ccwgroup_attr_groups;
353 354 355 356 357 358 359

	if (gdrv) {
		gdev->dev.driver = &gdrv->driver;
		rc = gdrv->setup ? gdrv->setup(gdev) : 0;
		if (rc)
			goto error;
	}
360
	rc = device_add(&gdev->dev);
L
Linus Torvalds 已提交
361
	if (rc)
362
		goto error;
L
Linus Torvalds 已提交
363
	rc = __ccwgroup_create_symlinks(gdev);
S
Sebastian Ott 已提交
364 365 366
	if (rc) {
		device_del(&gdev->dev);
		goto error;
L
Linus Torvalds 已提交
367
	}
S
Sebastian Ott 已提交
368 369
	mutex_unlock(&gdev->reg_mutex);
	return 0;
L
Linus Torvalds 已提交
370
error:
371
	for (i = 0; i < num_devices; i++)
L
Linus Torvalds 已提交
372
		if (gdev->cdev[i]) {
373
			spin_lock_irq(gdev->cdev[i]->ccwlock);
374 375
			if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
				dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
376
			spin_unlock_irq(gdev->cdev[i]->ccwlock);
377
			put_device(&gdev->cdev[i]->dev);
378
			gdev->cdev[i] = NULL;
L
Linus Torvalds 已提交
379
		}
380 381
	mutex_unlock(&gdev->reg_mutex);
	put_device(&gdev->dev);
L
Linus Torvalds 已提交
382 383
	return rc;
}
384 385
EXPORT_SYMBOL(ccwgroup_create_dev);

386
static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action,
S
Sebastian Ott 已提交
387 388 389 390 391 392 393 394 395
			     void *data)
{
	struct device *dev = data;

	if (action == BUS_NOTIFY_UNBIND_DRIVER)
		device_schedule_callback(dev, ccwgroup_ungroup_callback);

	return NOTIFY_OK;
}
396 397 398 399 400 401

static struct notifier_block ccwgroup_nb = {
	.notifier_call = ccwgroup_notifier
};

static int __init init_ccwgroup(void)
L
Linus Torvalds 已提交
402
{
403 404 405 406 407 408 409 410 411 412 413
	int ret;

	ret = bus_register(&ccwgroup_bus_type);
	if (ret)
		return ret;

	ret = bus_register_notifier(&ccwgroup_bus_type, &ccwgroup_nb);
	if (ret)
		bus_unregister(&ccwgroup_bus_type);

	return ret;
L
Linus Torvalds 已提交
414 415
}

416
static void __exit cleanup_ccwgroup(void)
L
Linus Torvalds 已提交
417
{
418 419
	bus_unregister_notifier(&ccwgroup_bus_type, &ccwgroup_nb);
	bus_unregister(&ccwgroup_bus_type);
L
Linus Torvalds 已提交
420 421 422 423 424 425 426
}

module_init(init_ccwgroup);
module_exit(cleanup_ccwgroup);

/************************** driver stuff ******************************/

S
Sebastian Ott 已提交
427
static int ccwgroup_remove(struct device *dev)
L
Linus Torvalds 已提交
428
{
S
Sebastian Ott 已提交
429 430
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);
L
Linus Torvalds 已提交
431

432 433 434
	if (!dev->driver)
		return 0;
	if (gdrv->remove)
L
Linus Torvalds 已提交
435
		gdrv->remove(gdev);
436

L
Linus Torvalds 已提交
437 438 439
	return 0;
}

440 441
static void ccwgroup_shutdown(struct device *dev)
{
S
Sebastian Ott 已提交
442 443
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);
444

445 446 447
	if (!dev->driver)
		return;
	if (gdrv->shutdown)
448 449 450
		gdrv->shutdown(gdev);
}

451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
static int ccwgroup_pm_prepare(struct device *dev)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);

	/* Fail while device is being set online/offline. */
	if (atomic_read(&gdev->onoff))
		return -EAGAIN;

	if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
		return 0;

	return gdrv->prepare ? gdrv->prepare(gdev) : 0;
}

static void ccwgroup_pm_complete(struct device *dev)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);

	if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
		return;

	if (gdrv->complete)
		gdrv->complete(gdev);
}

static int ccwgroup_pm_freeze(struct device *dev)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);

	if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
		return 0;

	return gdrv->freeze ? gdrv->freeze(gdev) : 0;
}

static int ccwgroup_pm_thaw(struct device *dev)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);

	if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
		return 0;

	return gdrv->thaw ? gdrv->thaw(gdev) : 0;
}

static int ccwgroup_pm_restore(struct device *dev)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);
	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);

	if (!gdev->dev.driver || gdev->state != CCWGROUP_ONLINE)
		return 0;

	return gdrv->restore ? gdrv->restore(gdev) : 0;
}

511
static const struct dev_pm_ops ccwgroup_pm_ops = {
512 513 514 515 516 517 518
	.prepare = ccwgroup_pm_prepare,
	.complete = ccwgroup_pm_complete,
	.freeze = ccwgroup_pm_freeze,
	.thaw = ccwgroup_pm_thaw,
	.restore = ccwgroup_pm_restore,
};

519 520 521
static struct bus_type ccwgroup_bus_type = {
	.name   = "ccwgroup",
	.remove = ccwgroup_remove,
522
	.shutdown = ccwgroup_shutdown,
523
	.pm = &ccwgroup_pm_ops,
524 525
};

C
Cornelia Huck 已提交
526 527 528 529 530 531 532
/**
 * ccwgroup_driver_register() - register a ccw group driver
 * @cdriver: driver to be registered
 *
 * This function is mainly a wrapper around driver_register().
 */
int ccwgroup_driver_register(struct ccwgroup_driver *cdriver)
L
Linus Torvalds 已提交
533 534
{
	/* register our new driver with the core */
535
	cdriver->driver.bus = &ccwgroup_bus_type;
L
Linus Torvalds 已提交
536 537 538

	return driver_register(&cdriver->driver);
}
S
Sebastian Ott 已提交
539
EXPORT_SYMBOL(ccwgroup_driver_register);
L
Linus Torvalds 已提交
540

S
Sebastian Ott 已提交
541
static int __ccwgroup_match_all(struct device *dev, void *data)
L
Linus Torvalds 已提交
542
{
543
	return 1;
L
Linus Torvalds 已提交
544 545
}

C
Cornelia Huck 已提交
546 547 548 549 550 551 552
/**
 * ccwgroup_driver_unregister() - deregister a ccw group driver
 * @cdriver: driver to be deregistered
 *
 * This function is mainly a wrapper around driver_unregister().
 */
void ccwgroup_driver_unregister(struct ccwgroup_driver *cdriver)
L
Linus Torvalds 已提交
553
{
554 555
	struct device *dev;

L
Linus Torvalds 已提交
556
	/* We don't want ccwgroup devices to live longer than their driver. */
557 558
	while ((dev = driver_find_device(&cdriver->driver, NULL, NULL,
					 __ccwgroup_match_all))) {
559 560 561 562
		struct ccwgroup_device *gdev = to_ccwgroupdev(dev);

		mutex_lock(&gdev->reg_mutex);
		__ccwgroup_remove_symlinks(gdev);
563
		device_unregister(dev);
564
		__ccwgroup_remove_cdev_refs(gdev);
565
		mutex_unlock(&gdev->reg_mutex);
566 567
		put_device(dev);
	}
L
Linus Torvalds 已提交
568 569
	driver_unregister(&cdriver->driver);
}
S
Sebastian Ott 已提交
570
EXPORT_SYMBOL(ccwgroup_driver_unregister);
L
Linus Torvalds 已提交
571

C
Cornelia Huck 已提交
572 573 574 575 576 577 578 579 580 581
/**
 * ccwgroup_probe_ccwdev() - probe function for slave devices
 * @cdev: ccw device to be probed
 *
 * This is a dummy probe function for ccw devices that are slave devices in
 * a ccw group device.
 * Returns:
 *  always %0
 */
int ccwgroup_probe_ccwdev(struct ccw_device *cdev)
L
Linus Torvalds 已提交
582 583 584
{
	return 0;
}
S
Sebastian Ott 已提交
585
EXPORT_SYMBOL(ccwgroup_probe_ccwdev);
L
Linus Torvalds 已提交
586

C
Cornelia Huck 已提交
587 588 589 590 591 592 593 594 595
/**
 * ccwgroup_remove_ccwdev() - remove function for slave devices
 * @cdev: ccw device to be removed
 *
 * This is a remove function for ccw devices that are slave devices in a ccw
 * group device. It sets the ccw device offline and also deregisters the
 * embedding ccw group device.
 */
void ccwgroup_remove_ccwdev(struct ccw_device *cdev)
L
Linus Torvalds 已提交
596 597 598 599 600 601
{
	struct ccwgroup_device *gdev;

	/* Ignore offlining errors, device is gone anyway. */
	ccw_device_set_offline(cdev);
	/* If one of its devices is gone, the whole group is done for. */
602 603 604 605 606 607 608 609 610 611 612 613
	spin_lock_irq(cdev->ccwlock);
	gdev = dev_get_drvdata(&cdev->dev);
	if (!gdev) {
		spin_unlock_irq(cdev->ccwlock);
		return;
	}
	/* Get ccwgroup device reference for local processing. */
	get_device(&gdev->dev);
	spin_unlock_irq(cdev->ccwlock);
	/* Unregister group device. */
	mutex_lock(&gdev->reg_mutex);
	if (device_is_registered(&gdev->dev)) {
L
Linus Torvalds 已提交
614 615
		__ccwgroup_remove_symlinks(gdev);
		device_unregister(&gdev->dev);
616
		__ccwgroup_remove_cdev_refs(gdev);
L
Linus Torvalds 已提交
617
	}
618 619 620
	mutex_unlock(&gdev->reg_mutex);
	/* Release ccwgroup device reference for local processing. */
	put_device(&gdev->dev);
L
Linus Torvalds 已提交
621 622
}
EXPORT_SYMBOL(ccwgroup_remove_ccwdev);
S
Sebastian Ott 已提交
623
MODULE_LICENSE("GPL");