ccwgroup.c 13.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 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
 */
#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>

/* 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;

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

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

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

52
static struct bus_type ccwgroup_bus_type;
L
Linus Torvalds 已提交
53

54
static void
L
Linus Torvalds 已提交
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
__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 :)
 */
72 73 74 75
static void ccwgroup_ungroup_callback(struct device *dev)
{
	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);

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

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

	gdev = to_ccwgroupdev(dev);

	if (gdev->state != CCWGROUP_OFFLINE)
		return -EINVAL;

95 96 97 98 99 100
	/* 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);
	if (rc)
		count = rc;
L
Linus Torvalds 已提交
101 102 103 104 105 106 107 108 109 110 111 112 113 114
	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++) {
115
		if (gdev->cdev[i]) {
116 117
			if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
				dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
118 119
			put_device(&gdev->cdev[i]->dev);
		}
L
Linus Torvalds 已提交
120 121 122 123
	}
	kfree(gdev);
}

124
static int
L
Linus Torvalds 已提交
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
__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;
}

158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
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++;
	}
	if (len < BUS_ID_SIZE) {
		strlcpy(bus_id, start, len);
		rc = 0;
	} else
		rc = -EINVAL;
	*buf = end;
	return rc;
}

static int __is_valid_bus_id(char bus_id[BUS_ID_SIZE])
{
	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 已提交
194
/**
195
 * ccwgroup_create_from_string() - create and register a ccw group device
C
Cornelia Huck 已提交
196 197 198
 * @root: parent device for the new device
 * @creator_id: identifier of creating driver
 * @cdrv: ccw driver of slave devices
199 200
 * @num_devices: number of slave devices
 * @buf: buffer containing comma separated bus ids of slave devices
C
Cornelia Huck 已提交
201 202
 *
 * Create and register a new ccw group device as a child of @root. Slave
203
 * devices are obtained from the list of bus ids given in @buf and must all
C
Cornelia Huck 已提交
204 205 206 207 208
 * belong to @cdrv.
 * Returns:
 *  %0 on success and an error code on failure.
 * Context:
 *  non-atomic
L
Linus Torvalds 已提交
209
 */
210 211 212
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 已提交
213 214
{
	struct ccwgroup_device *gdev;
215 216 217
	int rc, i;
	char tmp_bus_id[BUS_ID_SIZE];
	const char *curr_buf;
L
Linus Torvalds 已提交
218

219 220
	gdev = kzalloc(sizeof(*gdev) + num_devices * sizeof(gdev->cdev[0]),
		       GFP_KERNEL);
L
Linus Torvalds 已提交
221 222 223 224
	if (!gdev)
		return -ENOMEM;

	atomic_set(&gdev->onoff, 0);
225 226
	mutex_init(&gdev->reg_mutex);
	mutex_lock(&gdev->reg_mutex);
227 228 229 230 231 232 233
	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);

234 235 236 237 238 239 240 241 242 243 244 245 246 247
	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 已提交
248 249 250 251
		if (!gdev->cdev[i]
		    || gdev->cdev[i]->id.driver_info !=
		    gdev->cdev[0]->id.driver_info) {
			rc = -EINVAL;
252
			goto error;
L
Linus Torvalds 已提交
253 254
		}
		/* Don't allow a device to belong to more than one group. */
255
		if (dev_get_drvdata(&gdev->cdev[i]->dev)) {
L
Linus Torvalds 已提交
256
			rc = -EINVAL;
257
			goto error;
L
Linus Torvalds 已提交
258
		}
259
		dev_set_drvdata(&gdev->cdev[i]->dev, gdev);
260
	}
261 262 263 264 265 266 267 268 269 270
	/* 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 已提交
271 272 273 274

	snprintf (gdev->dev.bus_id, BUS_ID_SIZE, "%s",
			gdev->cdev[0]->dev.bus_id);

275
	rc = device_add(&gdev->dev);
L
Linus Torvalds 已提交
276
	if (rc)
277
		goto error;
L
Linus Torvalds 已提交
278 279 280 281 282 283 284 285 286 287
	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) {
288
		mutex_unlock(&gdev->reg_mutex);
L
Linus Torvalds 已提交
289 290 291 292 293 294
		put_device(&gdev->dev);
		return 0;
	}
	device_remove_file(&gdev->dev, &dev_attr_ungroup);
	device_unregister(&gdev->dev);
error:
295
	for (i = 0; i < num_devices; i++)
L
Linus Torvalds 已提交
296
		if (gdev->cdev[i]) {
297 298
			if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
				dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
299
			put_device(&gdev->cdev[i]->dev);
300
			gdev->cdev[i] = NULL;
L
Linus Torvalds 已提交
301
		}
302 303
	mutex_unlock(&gdev->reg_mutex);
	put_device(&gdev->dev);
L
Linus Torvalds 已提交
304 305
	return rc;
}
306
EXPORT_SYMBOL(ccwgroup_create_from_string);
L
Linus Torvalds 已提交
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330

static int __init
init_ccwgroup (void)
{
	return bus_register (&ccwgroup_bus_type);
}

static void __exit
cleanup_ccwgroup (void)
{
	bus_unregister (&ccwgroup_bus_type);
}

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;

331
	if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)
L
Linus Torvalds 已提交
332 333 334 335 336 337 338 339 340 341
		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);
342
	if ((ret = gdrv->set_online ? gdrv->set_online(gdev) : 0))
L
Linus Torvalds 已提交
343 344 345 346 347 348 349 350 351 352 353 354 355 356
		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;

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_OFFLINE) {
		ret = 0;
		goto out;
	}
	if (!gdev->dev.driver) {
		ret = -EINVAL;
		goto out;
	}
	gdrv = to_ccwgroupdrv (gdev->dev.driver);
368
	if ((ret = gdrv->set_offline ? gdrv->set_offline(gdev) : 0))
L
Linus Torvalds 已提交
369 370 371 372 373 374 375 376 377
		goto out;

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

static ssize_t
378
ccwgroup_online_store (struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
L
Linus Torvalds 已提交
379 380 381
{
	struct ccwgroup_device *gdev;
	struct ccwgroup_driver *gdrv;
382
	unsigned long value;
L
Linus Torvalds 已提交
383 384 385 386 387 388 389 390 391 392
	int ret;

	gdev = to_ccwgroupdev(dev);
	if (!dev->driver)
		return count;

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

393 394 395
	ret = strict_strtoul(buf, 0, &value);
	if (ret)
		goto out;
L
Linus Torvalds 已提交
396 397 398 399 400 401 402
	ret = count;
	if (value == 1)
		ccwgroup_set_online(gdev);
	else if (value == 0)
		ccwgroup_set_offline(gdev);
	else
		ret = -EINVAL;
403
out:
L
Linus Torvalds 已提交
404 405 406 407 408
	module_put(gdrv->owner);
	return ret;
}

static ssize_t
409
ccwgroup_online_show (struct device *dev, struct device_attribute *attr, char *buf)
L
Linus Torvalds 已提交
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
{
	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;

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

	device_remove_file(dev, &dev_attr_online);

	if (gdrv && gdrv->remove)
		gdrv->remove(gdev);
	return 0;
}

457 458 459 460 461 462 463 464 465 466 467
static void ccwgroup_shutdown(struct device *dev)
{
	struct ccwgroup_device *gdev;
	struct ccwgroup_driver *gdrv;

	gdev = to_ccwgroupdev(dev);
	gdrv = to_ccwgroupdrv(dev->driver);
	if (gdrv && gdrv->shutdown)
		gdrv->shutdown(gdev);
}

468 469 470 471 472 473
static struct bus_type ccwgroup_bus_type = {
	.name   = "ccwgroup",
	.match  = ccwgroup_bus_match,
	.uevent = ccwgroup_uevent,
	.probe  = ccwgroup_probe,
	.remove = ccwgroup_remove,
474
	.shutdown = ccwgroup_shutdown,
475 476
};

C
Cornelia Huck 已提交
477 478 479 480 481 482 483
/**
 * 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 已提交
484 485
{
	/* register our new driver with the core */
486 487
	cdriver->driver.bus = &ccwgroup_bus_type;
	cdriver->driver.name = cdriver->name;
488
	cdriver->driver.owner = cdriver->owner;
L
Linus Torvalds 已提交
489 490 491 492

	return driver_register(&cdriver->driver);
}

C
Cornelia Huck 已提交
493
static int
494
__ccwgroup_match_all(struct device *dev, void *data)
L
Linus Torvalds 已提交
495
{
496
	return 1;
L
Linus Torvalds 已提交
497 498
}

C
Cornelia Huck 已提交
499 500 501 502 503 504 505
/**
 * 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 已提交
506
{
507 508
	struct device *dev;

L
Linus Torvalds 已提交
509 510
	/* We don't want ccwgroup devices to live longer than their driver. */
	get_driver(&cdriver->driver);
511 512
	while ((dev = driver_find_device(&cdriver->driver, NULL, NULL,
					 __ccwgroup_match_all))) {
513 514 515 516
		struct ccwgroup_device *gdev = to_ccwgroupdev(dev);

		mutex_lock(&gdev->reg_mutex);
		__ccwgroup_remove_symlinks(gdev);
517
		device_unregister(dev);
518
		mutex_unlock(&gdev->reg_mutex);
519 520
		put_device(dev);
	}
L
Linus Torvalds 已提交
521 522 523 524
	put_driver(&cdriver->driver);
	driver_unregister(&cdriver->driver);
}

C
Cornelia Huck 已提交
525 526 527 528 529 530 531 532 533 534
/**
 * 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 已提交
535 536 537 538
{
	return 0;
}

539
static struct ccwgroup_device *
L
Linus Torvalds 已提交
540 541 542 543
__ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
{
	struct ccwgroup_device *gdev;

544 545
	gdev = dev_get_drvdata(&cdev->dev);
	if (gdev) {
L
Linus Torvalds 已提交
546
		if (get_device(&gdev->dev)) {
547
			mutex_lock(&gdev->reg_mutex);
548
			if (device_is_registered(&gdev->dev))
L
Linus Torvalds 已提交
549
				return gdev;
550
			mutex_unlock(&gdev->reg_mutex);
L
Linus Torvalds 已提交
551 552 553 554 555 556 557
			put_device(&gdev->dev);
		}
		return NULL;
	}
	return NULL;
}

C
Cornelia Huck 已提交
558 559 560 561 562 563 564 565 566
/**
 * 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 已提交
567 568 569 570 571 572 573 574 575 576
{
	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);
577
		mutex_unlock(&gdev->reg_mutex);
L
Linus Torvalds 已提交
578 579 580 581 582 583 584 585 586
		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);