ccwgroup.c 14.3 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7
/*
 *  drivers/s390/cio/ccwgroup.c
 *  bus driver for ccwgroup
 *
 *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
 *                       IBM Corporation
 *    Author(s): Arnd Bergmann (arndb@de.ibm.com)
8
 *               Cornelia Huck (cornelia.huck@de.ibm.com)
L
Linus Torvalds 已提交
9 10 11 12 13 14 15 16 17 18 19 20 21
 */
#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>

#include <asm/ccwdev.h>
#include <asm/ccwgroup.h>

22 23
#define CCW_BUS_ID_SIZE		20

L
Linus Torvalds 已提交
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
/* 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.
 */

/* a device matches a driver if all its slave devices match the same
 * entry of the driver */
static int
ccwgroup_bus_match (struct device * dev, struct device_driver * drv)
{
	struct ccwgroup_device *gdev;
	struct ccwgroup_driver *gdrv;

39 40
	gdev = to_ccwgroupdev(dev);
	gdrv = to_ccwgroupdrv(drv);
L
Linus Torvalds 已提交
41 42 43 44 45 46 47

	if (gdev->creator_id == gdrv->driver_id)
		return 1;

	return 0;
}
static int
48
ccwgroup_uevent (struct device *dev, struct kobj_uevent_env *env)
L
Linus Torvalds 已提交
49 50 51 52 53
{
	/* TODO */
	return 0;
}

54
static struct bus_type ccwgroup_bus_type;
L
Linus Torvalds 已提交
55

56
static void
L
Linus Torvalds 已提交
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
__ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
{
	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");
	}
	
}

/*
 * Provide an 'ungroup' attribute so the user can remove group devices no
 * longer needed or accidentially created. Saves memory :)
 */
74 75 76 77
static void ccwgroup_ungroup_callback(struct device *dev)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);

78
	mutex_lock(&gdev->reg_mutex);
79 80 81 82
	if (device_is_registered(&gdev->dev)) {
		__ccwgroup_remove_symlinks(gdev);
		device_unregister(dev);
	}
83
	mutex_unlock(&gdev->reg_mutex);
84 85
}

L
Linus Torvalds 已提交
86
static ssize_t
87
ccwgroup_ungroup_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
L
Linus Torvalds 已提交
88 89
{
	struct ccwgroup_device *gdev;
90
	int rc;
L
Linus Torvalds 已提交
91 92 93

	gdev = to_ccwgroupdev(dev);

94 95 96 97 98 99 100
	/* 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;
	}
101 102 103 104
	/* 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);
105 106
out:
	if (rc) {
107 108 109
		if (rc != -EAGAIN)
			/* Release onoff "lock" when ungrouping failed. */
			atomic_set(&gdev->onoff, 0);
110 111
		return rc;
	}
L
Linus Torvalds 已提交
112 113 114 115 116 117 118 119 120 121 122 123 124 125
	return count;
}

static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store);

static void
ccwgroup_release (struct device *dev)
{
	struct ccwgroup_device *gdev;
	int i;

	gdev = to_ccwgroupdev(dev);

	for (i = 0; i < gdev->count; i++) {
126
		if (gdev->cdev[i]) {
127 128
			if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
				dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
129 130
			put_device(&gdev->cdev[i]->dev);
		}
L
Linus Torvalds 已提交
131 132 133 134
	}
	kfree(gdev);
}

135
static int
L
Linus Torvalds 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
__ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
{
	char str[8];
	int i, rc;

	for (i = 0; i < gdev->count; i++) {
		rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj, &gdev->dev.kobj,
				       "group_device");
		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);
		rc = sysfs_create_link(&gdev->dev.kobj, &gdev->cdev[i]->dev.kobj,
				       str);
		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;
}

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
static int __get_next_bus_id(const char **buf, char *bus_id)
{
	int rc, len;
	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++;
	}
186
	if (len < CCW_BUS_ID_SIZE) {
187 188 189 190 191 192 193 194
		strlcpy(bus_id, start, len);
		rc = 0;
	} else
		rc = -EINVAL;
	*buf = end;
	return rc;
}

195
static int __is_valid_bus_id(char bus_id[CCW_BUS_ID_SIZE])
196 197 198 199 200 201 202 203 204
{
	int cssid, ssid, devno;

	/* Must be of form %x.%x.%04x */
	if (sscanf(bus_id, "%x.%1x.%04x", &cssid, &ssid, &devno) != 3)
		return 0;
	return 1;
}

C
Cornelia Huck 已提交
205
/**
206
 * ccwgroup_create_from_string() - create and register a ccw group device
C
Cornelia Huck 已提交
207 208 209
 * @root: parent device for the new device
 * @creator_id: identifier of creating driver
 * @cdrv: ccw driver of slave devices
210 211
 * @num_devices: number of slave devices
 * @buf: buffer containing comma separated bus ids of slave devices
C
Cornelia Huck 已提交
212 213
 *
 * Create and register a new ccw group device as a child of @root. Slave
214
 * devices are obtained from the list of bus ids given in @buf and must all
C
Cornelia Huck 已提交
215 216 217 218 219
 * belong to @cdrv.
 * Returns:
 *  %0 on success and an error code on failure.
 * Context:
 *  non-atomic
L
Linus Torvalds 已提交
220
 */
221 222 223
int ccwgroup_create_from_string(struct device *root, unsigned int creator_id,
				struct ccw_driver *cdrv, int num_devices,
				const char *buf)
L
Linus Torvalds 已提交
224 225
{
	struct ccwgroup_device *gdev;
226
	int rc, i;
227
	char tmp_bus_id[CCW_BUS_ID_SIZE];
228
	const char *curr_buf;
L
Linus Torvalds 已提交
229

230 231
	gdev = kzalloc(sizeof(*gdev) + num_devices * sizeof(gdev->cdev[0]),
		       GFP_KERNEL);
L
Linus Torvalds 已提交
232 233 234 235
	if (!gdev)
		return -ENOMEM;

	atomic_set(&gdev->onoff, 0);
236 237
	mutex_init(&gdev->reg_mutex);
	mutex_lock(&gdev->reg_mutex);
238 239 240 241 242 243 244
	gdev->creator_id = creator_id;
	gdev->count = num_devices;
	gdev->dev.bus = &ccwgroup_bus_type;
	gdev->dev.parent = root;
	gdev->dev.release = ccwgroup_release;
	device_initialize(&gdev->dev);

245 246 247 248 249 250 251 252 253 254 255 256 257 258
	curr_buf = buf;
	for (i = 0; i < num_devices && curr_buf; i++) {
		rc = __get_next_bus_id(&curr_buf, tmp_bus_id);
		if (rc != 0)
			goto error;
		if (!__is_valid_bus_id(tmp_bus_id)) {
			rc = -EINVAL;
			goto error;
		}
		gdev->cdev[i] = get_ccwdev_by_busid(cdrv, tmp_bus_id);
		/*
		 * All devices have to be of the same type in
		 * order to be grouped.
		 */
L
Linus Torvalds 已提交
259 260 261 262
		if (!gdev->cdev[i]
		    || gdev->cdev[i]->id.driver_info !=
		    gdev->cdev[0]->id.driver_info) {
			rc = -EINVAL;
263
			goto error;
L
Linus Torvalds 已提交
264 265
		}
		/* Don't allow a device to belong to more than one group. */
266
		if (dev_get_drvdata(&gdev->cdev[i]->dev)) {
L
Linus Torvalds 已提交
267
			rc = -EINVAL;
268
			goto error;
L
Linus Torvalds 已提交
269
		}
270
		dev_set_drvdata(&gdev->cdev[i]->dev, gdev);
271
	}
272 273 274 275 276 277 278 279 280 281
	/* Check for sufficient number of bus ids. */
	if (i < num_devices && !curr_buf) {
		rc = -EINVAL;
		goto error;
	}
	/* Check for trailing stuff. */
	if (i == num_devices && strlen(curr_buf) > 0) {
		rc = -EINVAL;
		goto error;
	}
L
Linus Torvalds 已提交
282

283
	dev_set_name(&gdev->dev, "%s", dev_name(&gdev->cdev[0]->dev));
L
Linus Torvalds 已提交
284

285
	rc = device_add(&gdev->dev);
L
Linus Torvalds 已提交
286
	if (rc)
287
		goto error;
L
Linus Torvalds 已提交
288 289 290 291 292 293 294 295 296 297
	get_device(&gdev->dev);
	rc = device_create_file(&gdev->dev, &dev_attr_ungroup);

	if (rc) {
		device_unregister(&gdev->dev);
		goto error;
	}

	rc = __ccwgroup_create_symlinks(gdev);
	if (!rc) {
298
		mutex_unlock(&gdev->reg_mutex);
L
Linus Torvalds 已提交
299 300 301 302 303 304
		put_device(&gdev->dev);
		return 0;
	}
	device_remove_file(&gdev->dev, &dev_attr_ungroup);
	device_unregister(&gdev->dev);
error:
305
	for (i = 0; i < num_devices; i++)
L
Linus Torvalds 已提交
306
		if (gdev->cdev[i]) {
307 308
			if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
				dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
309
			put_device(&gdev->cdev[i]->dev);
310
			gdev->cdev[i] = NULL;
L
Linus Torvalds 已提交
311
		}
312 313
	mutex_unlock(&gdev->reg_mutex);
	put_device(&gdev->dev);
L
Linus Torvalds 已提交
314 315
	return rc;
}
316
EXPORT_SYMBOL(ccwgroup_create_from_string);
L
Linus Torvalds 已提交
317

318 319 320 321 322 323 324 325
static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action,
			     void *data);

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

static int __init init_ccwgroup(void)
L
Linus Torvalds 已提交
326
{
327 328 329 330 331 332 333 334 335 336 337
	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 已提交
338 339
}

340
static void __exit cleanup_ccwgroup(void)
L
Linus Torvalds 已提交
341
{
342 343
	bus_unregister_notifier(&ccwgroup_bus_type, &ccwgroup_nb);
	bus_unregister(&ccwgroup_bus_type);
L
Linus Torvalds 已提交
344 345 346 347 348 349 350 351 352 353 354 355 356
}

module_init(init_ccwgroup);
module_exit(cleanup_ccwgroup);

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

static int
ccwgroup_set_online(struct ccwgroup_device *gdev)
{
	struct ccwgroup_driver *gdrv;
	int ret;

357
	if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
L
Linus Torvalds 已提交
358 359 360 361 362 363 364 365 366 367
		return -EAGAIN;
	if (gdev->state == CCWGROUP_ONLINE) {
		ret = 0;
		goto out;
	}
	if (!gdev->dev.driver) {
		ret = -EINVAL;
		goto out;
	}
	gdrv = to_ccwgroupdrv (gdev->dev.driver);
368
	if ((ret = gdrv->set_online ? gdrv->set_online(gdev) : 0))
L
Linus Torvalds 已提交
369 370 371 372 373 374 375 376 377 378 379 380 381 382
		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;
	int ret;

383
	if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
L
Linus Torvalds 已提交
384 385 386 387 388 389 390 391 392 393
		return -EAGAIN;
	if (gdev->state == CCWGROUP_OFFLINE) {
		ret = 0;
		goto out;
	}
	if (!gdev->dev.driver) {
		ret = -EINVAL;
		goto out;
	}
	gdrv = to_ccwgroupdrv (gdev->dev.driver);
394
	if ((ret = gdrv->set_offline ? gdrv->set_offline(gdev) : 0))
L
Linus Torvalds 已提交
395 396 397 398 399 400 401 402 403
		goto out;

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

static ssize_t
404
ccwgroup_online_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
L
Linus Torvalds 已提交
405 406 407
{
	struct ccwgroup_device *gdev;
	struct ccwgroup_driver *gdrv;
408
	unsigned long value;
L
Linus Torvalds 已提交
409 410 411
	int ret;

	if (!dev->driver)
412 413 414 415
		return -ENODEV;

	gdev = to_ccwgroupdev(dev);
	gdrv = to_ccwgroupdrv(dev->driver);
L
Linus Torvalds 已提交
416 417 418 419

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

420 421 422
	ret = strict_strtoul(buf, 0, &value);
	if (ret)
		goto out;
423

L
Linus Torvalds 已提交
424
	if (value == 1)
425
		ret = ccwgroup_set_online(gdev);
L
Linus Torvalds 已提交
426
	else if (value == 0)
427
		ret = ccwgroup_set_offline(gdev);
L
Linus Torvalds 已提交
428 429
	else
		ret = -EINVAL;
430
out:
L
Linus Torvalds 已提交
431
	module_put(gdrv->owner);
432
	return (ret == 0) ? count : ret;
L
Linus Torvalds 已提交
433 434 435
}

static ssize_t
436
ccwgroup_online_show (struct device *dev, struct device_attribute *attr, char *buf)
L
Linus Torvalds 已提交
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
{
	int online;

	online = (to_ccwgroupdev(dev)->state == CCWGROUP_ONLINE);

	return sprintf(buf, online ? "1\n" : "0\n");
}

static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store);

static int
ccwgroup_probe (struct device *dev)
{
	struct ccwgroup_device *gdev;
	struct ccwgroup_driver *gdrv;

	int ret;

	gdev = to_ccwgroupdev(dev);
	gdrv = to_ccwgroupdrv(dev->driver);

	if ((ret = device_create_file(dev, &dev_attr_online)))
		return ret;

	ret = gdrv->probe ? gdrv->probe(gdev) : -ENODEV;
	if (ret)
		device_remove_file(dev, &dev_attr_online);

	return ret;
}

static int
ccwgroup_remove (struct device *dev)
{
	struct ccwgroup_device *gdev;
	struct ccwgroup_driver *gdrv;

474
	device_remove_file(dev, &dev_attr_online);
475
	device_remove_file(dev, &dev_attr_ungroup);
476 477 478 479

	if (!dev->driver)
		return 0;

L
Linus Torvalds 已提交
480 481 482
	gdev = to_ccwgroupdev(dev);
	gdrv = to_ccwgroupdrv(dev->driver);

483
	if (gdrv->remove)
L
Linus Torvalds 已提交
484
		gdrv->remove(gdev);
485

L
Linus Torvalds 已提交
486 487 488
	return 0;
}

489 490 491 492 493
static void ccwgroup_shutdown(struct device *dev)
{
	struct ccwgroup_device *gdev;
	struct ccwgroup_driver *gdrv;

494 495 496
	if (!dev->driver)
		return;

497 498
	gdev = to_ccwgroupdev(dev);
	gdrv = to_ccwgroupdrv(dev->driver);
499 500

	if (gdrv->shutdown)
501 502 503
		gdrv->shutdown(gdev);
}

504 505 506 507 508 509
static struct bus_type ccwgroup_bus_type = {
	.name   = "ccwgroup",
	.match  = ccwgroup_bus_match,
	.uevent = ccwgroup_uevent,
	.probe  = ccwgroup_probe,
	.remove = ccwgroup_remove,
510
	.shutdown = ccwgroup_shutdown,
511 512
};

513 514 515 516 517 518 519 520 521 522 523 524 525

static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action,
			     void *data)
{
	struct device *dev = data;

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

	return NOTIFY_OK;
}


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 536
	cdriver->driver.bus = &ccwgroup_bus_type;
	cdriver->driver.name = cdriver->name;
537
	cdriver->driver.owner = cdriver->owner;
L
Linus Torvalds 已提交
538 539 540 541

	return driver_register(&cdriver->driver);
}

C
Cornelia Huck 已提交
542
static int
543
__ccwgroup_match_all(struct device *dev, void *data)
L
Linus Torvalds 已提交
544
{
545
	return 1;
L
Linus Torvalds 已提交
546 547
}

C
Cornelia Huck 已提交
548 549 550 551 552 553 554
/**
 * 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 已提交
555
{
556 557
	struct device *dev;

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

		mutex_lock(&gdev->reg_mutex);
		__ccwgroup_remove_symlinks(gdev);
566
		device_unregister(dev);
567
		mutex_unlock(&gdev->reg_mutex);
568 569
		put_device(dev);
	}
L
Linus Torvalds 已提交
570 571 572 573
	put_driver(&cdriver->driver);
	driver_unregister(&cdriver->driver);
}

C
Cornelia Huck 已提交
574 575 576 577 578 579 580 581 582 583
/**
 * 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 已提交
584 585 586 587
{
	return 0;
}

588
static struct ccwgroup_device *
L
Linus Torvalds 已提交
589 590 591 592
__ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
{
	struct ccwgroup_device *gdev;

593 594
	gdev = dev_get_drvdata(&cdev->dev);
	if (gdev) {
L
Linus Torvalds 已提交
595
		if (get_device(&gdev->dev)) {
596
			mutex_lock(&gdev->reg_mutex);
597
			if (device_is_registered(&gdev->dev))
L
Linus Torvalds 已提交
598
				return gdev;
599
			mutex_unlock(&gdev->reg_mutex);
L
Linus Torvalds 已提交
600 601 602 603 604 605 606
			put_device(&gdev->dev);
		}
		return NULL;
	}
	return NULL;
}

C
Cornelia Huck 已提交
607 608 609 610 611 612 613 614 615
/**
 * 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 已提交
616 617 618 619 620 621 622 623 624 625
{
	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. */
	gdev = __ccwgroup_get_gdev_by_cdev(cdev);
	if (gdev) {
		__ccwgroup_remove_symlinks(gdev);
		device_unregister(&gdev->dev);
626
		mutex_unlock(&gdev->reg_mutex);
L
Linus Torvalds 已提交
627 628 629 630 631 632 633 634 635
		put_device(&gdev->dev);
	}
}

MODULE_LICENSE("GPL");
EXPORT_SYMBOL(ccwgroup_driver_register);
EXPORT_SYMBOL(ccwgroup_driver_unregister);
EXPORT_SYMBOL(ccwgroup_probe_ccwdev);
EXPORT_SYMBOL(ccwgroup_remove_ccwdev);