brintos

brintos / linux-shallow public Read only

0
0
Text · 13.1 KiB · 66b1bdc Raw
555 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 *  bus driver for ccwgroup4 *5 *  Copyright IBM Corp. 2002, 20126 *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/cio.h>20#include <asm/ccwdev.h>21#include <asm/ccwgroup.h>22 23#include "device.h"24 25#define CCW_BUS_ID_SIZE		1026 27/* In Linux 2.4, we had a channel device layer called "chandev"28 * that did all sorts of obscure stuff for networking devices.29 * This is another driver that serves as a replacement for just30 * one of its functions, namely the translation of single subchannels31 * to devices that use multiple subchannels.32 */33 34static const struct bus_type ccwgroup_bus_type;35 36static void __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)37{38	int i;39	char str[16];40 41	for (i = 0; i < gdev->count; i++) {42		sprintf(str, "cdev%d", i);43		sysfs_remove_link(&gdev->dev.kobj, str);44		sysfs_remove_link(&gdev->cdev[i]->dev.kobj, "group_device");45	}46}47 48/**49 * ccwgroup_set_online() - enable a ccwgroup device50 * @gdev: target ccwgroup device51 *52 * This function attempts to put the ccwgroup device into the online state.53 * Returns:54 *  %0 on success and a negative error value on failure.55 */56int ccwgroup_set_online(struct ccwgroup_device *gdev)57{58	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);59	int ret = -EINVAL;60 61	if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)62		return -EAGAIN;63	if (gdev->state == CCWGROUP_ONLINE)64		goto out;65	if (gdrv->set_online)66		ret = gdrv->set_online(gdev);67	if (ret)68		goto out;69 70	gdev->state = CCWGROUP_ONLINE;71out:72	atomic_set(&gdev->onoff, 0);73	return ret;74}75EXPORT_SYMBOL(ccwgroup_set_online);76 77/**78 * ccwgroup_set_offline() - disable a ccwgroup device79 * @gdev: target ccwgroup device80 * @call_gdrv: Call the registered gdrv set_offline function81 *82 * This function attempts to put the ccwgroup device into the offline state.83 * Returns:84 *  %0 on success and a negative error value on failure.85 */86int ccwgroup_set_offline(struct ccwgroup_device *gdev, bool call_gdrv)87{88	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(gdev->dev.driver);89	int ret = -EINVAL;90 91	if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)92		return -EAGAIN;93	if (gdev->state == CCWGROUP_OFFLINE)94		goto out;95	if (!call_gdrv) {96		ret = 0;97		goto offline;98	}99	if (gdrv->set_offline)100		ret = gdrv->set_offline(gdev);101	if (ret)102		goto out;103 104offline:105	gdev->state = CCWGROUP_OFFLINE;106out:107	atomic_set(&gdev->onoff, 0);108	return ret;109}110EXPORT_SYMBOL(ccwgroup_set_offline);111 112static ssize_t ccwgroup_online_store(struct device *dev,113				     struct device_attribute *attr,114				     const char *buf, size_t count)115{116	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);117	unsigned long value;118	int ret;119 120	device_lock(dev);121	if (!dev->driver) {122		ret = -EINVAL;123		goto out;124	}125 126	ret = kstrtoul(buf, 0, &value);127	if (ret)128		goto out;129 130	if (value == 1)131		ret = ccwgroup_set_online(gdev);132	else if (value == 0)133		ret = ccwgroup_set_offline(gdev, true);134	else135		ret = -EINVAL;136out:137	device_unlock(dev);138	return (ret == 0) ? count : ret;139}140 141static ssize_t ccwgroup_online_show(struct device *dev,142				    struct device_attribute *attr,143				    char *buf)144{145	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);146	int online;147 148	online = (gdev->state == CCWGROUP_ONLINE) ? 1 : 0;149 150	return scnprintf(buf, PAGE_SIZE, "%d\n", online);151}152 153/*154 * Provide an 'ungroup' attribute so the user can remove group devices no155 * longer needed or accidentally created. Saves memory :)156 */157static void ccwgroup_ungroup(struct ccwgroup_device *gdev)158{159	mutex_lock(&gdev->reg_mutex);160	if (device_is_registered(&gdev->dev)) {161		__ccwgroup_remove_symlinks(gdev);162		device_unregister(&gdev->dev);163	}164	mutex_unlock(&gdev->reg_mutex);165}166 167static ssize_t ccwgroup_ungroup_store(struct device *dev,168				      struct device_attribute *attr,169				      const char *buf, size_t count)170{171	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);172	int rc = 0;173 174	/* Prevent concurrent online/offline processing and ungrouping. */175	if (atomic_cmpxchg(&gdev->onoff, 0, 1) != 0)176		return -EAGAIN;177	if (gdev->state != CCWGROUP_OFFLINE) {178		rc = -EINVAL;179		goto out;180	}181 182	if (device_remove_file_self(dev, attr))183		ccwgroup_ungroup(gdev);184	else185		rc = -ENODEV;186out:187	if (rc) {188		/* Release onoff "lock" when ungrouping failed. */189		atomic_set(&gdev->onoff, 0);190		return rc;191	}192	return count;193}194static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store);195static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store);196 197static struct attribute *ccwgroup_dev_attrs[] = {198	&dev_attr_online.attr,199	&dev_attr_ungroup.attr,200	NULL,201};202ATTRIBUTE_GROUPS(ccwgroup_dev);203 204static void ccwgroup_ungroup_workfn(struct work_struct *work)205{206	struct ccwgroup_device *gdev =207		container_of(work, struct ccwgroup_device, ungroup_work);208 209	ccwgroup_ungroup(gdev);210	put_device(&gdev->dev);211}212 213static void ccwgroup_release(struct device *dev)214{215	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);216	unsigned int i;217 218	for (i = 0; i < gdev->count; i++) {219		struct ccw_device *cdev = gdev->cdev[i];220		unsigned long flags;221 222		if (cdev) {223			spin_lock_irqsave(cdev->ccwlock, flags);224			if (dev_get_drvdata(&cdev->dev) == gdev)225				dev_set_drvdata(&cdev->dev, NULL);226			spin_unlock_irqrestore(cdev->ccwlock, flags);227			put_device(&cdev->dev);228		}229	}230 231	kfree(gdev);232}233 234static int __ccwgroup_create_symlinks(struct ccwgroup_device *gdev)235{236	char str[16];237	int i, rc;238 239	for (i = 0; i < gdev->count; i++) {240		rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj,241				       &gdev->dev.kobj, "group_device");242		if (rc) {243			while (i--)244				sysfs_remove_link(&gdev->cdev[i]->dev.kobj,245						  "group_device");246			return rc;247		}248	}249	for (i = 0; i < gdev->count; i++) {250		sprintf(str, "cdev%d", i);251		rc = sysfs_create_link(&gdev->dev.kobj,252				       &gdev->cdev[i]->dev.kobj, str);253		if (rc) {254			while (i--) {255				sprintf(str, "cdev%d", i);256				sysfs_remove_link(&gdev->dev.kobj, str);257			}258			for (i = 0; i < gdev->count; i++)259				sysfs_remove_link(&gdev->cdev[i]->dev.kobj,260						  "group_device");261			return rc;262		}263	}264	return 0;265}266 267static int __get_next_id(const char **buf, struct ccw_dev_id *id)268{269	unsigned int cssid, ssid, devno;270	int ret = 0, len;271	char *start, *end;272 273	start = (char *)*buf;274	end = strchr(start, ',');275	if (!end) {276		/* Last entry. Strip trailing newline, if applicable. */277		end = strchr(start, '\n');278		if (end)279			*end = '\0';280		len = strlen(start) + 1;281	} else {282		len = end - start + 1;283		end++;284	}285	if (len <= CCW_BUS_ID_SIZE) {286		if (sscanf(start, "%2x.%1x.%04x", &cssid, &ssid, &devno) != 3)287			ret = -EINVAL;288	} else289		ret = -EINVAL;290 291	if (!ret) {292		id->ssid = ssid;293		id->devno = devno;294	}295	*buf = end;296	return ret;297}298 299/**300 * ccwgroup_create_dev() - create and register a ccw group device301 * @parent: parent device for the new device302 * @gdrv: driver for the new group device303 * @num_devices: number of slave devices304 * @buf: buffer containing comma separated bus ids of slave devices305 *306 * Create and register a new ccw group device as a child of @parent. Slave307 * devices are obtained from the list of bus ids given in @buf.308 * Returns:309 *  %0 on success and an error code on failure.310 * Context:311 *  non-atomic312 */313int ccwgroup_create_dev(struct device *parent, struct ccwgroup_driver *gdrv,314			int num_devices, const char *buf)315{316	struct ccwgroup_device *gdev;317	struct ccw_dev_id dev_id;318	int rc, i;319 320	if (num_devices < 1)321		return -EINVAL;322 323	gdev = kzalloc(struct_size(gdev, cdev, num_devices), GFP_KERNEL);324	if (!gdev)325		return -ENOMEM;326 327	atomic_set(&gdev->onoff, 0);328	mutex_init(&gdev->reg_mutex);329	mutex_lock(&gdev->reg_mutex);330	INIT_WORK(&gdev->ungroup_work, ccwgroup_ungroup_workfn);331	gdev->count = num_devices;332	gdev->dev.bus = &ccwgroup_bus_type;333	gdev->dev.parent = parent;334	gdev->dev.release = ccwgroup_release;335	device_initialize(&gdev->dev);336 337	for (i = 0; i < num_devices && buf; i++) {338		rc = __get_next_id(&buf, &dev_id);339		if (rc != 0)340			goto error;341		gdev->cdev[i] = get_ccwdev_by_dev_id(&dev_id);342		/*343		 * All devices have to be of the same type in344		 * order to be grouped.345		 */346		if (!gdev->cdev[i] || !gdev->cdev[i]->drv ||347		    gdev->cdev[i]->drv != gdev->cdev[0]->drv ||348		    gdev->cdev[i]->id.driver_info !=349		    gdev->cdev[0]->id.driver_info) {350			rc = -EINVAL;351			goto error;352		}353		/* Don't allow a device to belong to more than one group. */354		spin_lock_irq(gdev->cdev[i]->ccwlock);355		if (dev_get_drvdata(&gdev->cdev[i]->dev)) {356			spin_unlock_irq(gdev->cdev[i]->ccwlock);357			rc = -EINVAL;358			goto error;359		}360		dev_set_drvdata(&gdev->cdev[i]->dev, gdev);361		spin_unlock_irq(gdev->cdev[i]->ccwlock);362	}363	/* Check for sufficient number of bus ids. */364	if (i < num_devices) {365		rc = -EINVAL;366		goto error;367	}368	/* Check for trailing stuff. */369	if (i == num_devices && buf && strlen(buf) > 0) {370		rc = -EINVAL;371		goto error;372	}373	/* Check if the devices are bound to the required ccw driver. */374	if (gdrv && gdrv->ccw_driver &&375	    gdev->cdev[0]->drv != gdrv->ccw_driver) {376		rc = -EINVAL;377		goto error;378	}379 380	dev_set_name(&gdev->dev, "%s", dev_name(&gdev->cdev[0]->dev));381 382	if (gdrv) {383		gdev->dev.driver = &gdrv->driver;384		rc = gdrv->setup ? gdrv->setup(gdev) : 0;385		if (rc)386			goto error;387	}388	rc = device_add(&gdev->dev);389	if (rc)390		goto error;391	rc = __ccwgroup_create_symlinks(gdev);392	if (rc) {393		device_del(&gdev->dev);394		goto error;395	}396	mutex_unlock(&gdev->reg_mutex);397	return 0;398error:399	mutex_unlock(&gdev->reg_mutex);400	put_device(&gdev->dev);401	return rc;402}403EXPORT_SYMBOL(ccwgroup_create_dev);404 405static int ccwgroup_notifier(struct notifier_block *nb, unsigned long action,406			     void *data)407{408	struct ccwgroup_device *gdev = to_ccwgroupdev(data);409 410	if (action == BUS_NOTIFY_UNBOUND_DRIVER) {411		get_device(&gdev->dev);412		schedule_work(&gdev->ungroup_work);413	}414 415	return NOTIFY_OK;416}417 418static struct notifier_block ccwgroup_nb = {419	.notifier_call = ccwgroup_notifier420};421 422static int __init init_ccwgroup(void)423{424	int ret;425 426	ret = bus_register(&ccwgroup_bus_type);427	if (ret)428		return ret;429 430	ret = bus_register_notifier(&ccwgroup_bus_type, &ccwgroup_nb);431	if (ret)432		bus_unregister(&ccwgroup_bus_type);433 434	return ret;435}436 437static void __exit cleanup_ccwgroup(void)438{439	bus_unregister_notifier(&ccwgroup_bus_type, &ccwgroup_nb);440	bus_unregister(&ccwgroup_bus_type);441}442 443module_init(init_ccwgroup);444module_exit(cleanup_ccwgroup);445 446/************************** driver stuff ******************************/447 448static void ccwgroup_remove(struct device *dev)449{450	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);451	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);452 453	if (gdrv->remove)454		gdrv->remove(gdev);455}456 457static void ccwgroup_shutdown(struct device *dev)458{459	struct ccwgroup_device *gdev = to_ccwgroupdev(dev);460	struct ccwgroup_driver *gdrv = to_ccwgroupdrv(dev->driver);461 462	if (!dev->driver)463		return;464	if (gdrv->shutdown)465		gdrv->shutdown(gdev);466}467 468static const struct bus_type ccwgroup_bus_type = {469	.name   = "ccwgroup",470	.dev_groups = ccwgroup_dev_groups,471	.remove = ccwgroup_remove,472	.shutdown = ccwgroup_shutdown,473};474 475bool dev_is_ccwgroup(struct device *dev)476{477	return dev->bus == &ccwgroup_bus_type;478}479EXPORT_SYMBOL(dev_is_ccwgroup);480 481/**482 * ccwgroup_driver_register() - register a ccw group driver483 * @cdriver: driver to be registered484 *485 * This function is mainly a wrapper around driver_register().486 */487int ccwgroup_driver_register(struct ccwgroup_driver *cdriver)488{489	/* register our new driver with the core */490	cdriver->driver.bus = &ccwgroup_bus_type;491 492	return driver_register(&cdriver->driver);493}494EXPORT_SYMBOL(ccwgroup_driver_register);495 496/**497 * ccwgroup_driver_unregister() - deregister a ccw group driver498 * @cdriver: driver to be deregistered499 *500 * This function is mainly a wrapper around driver_unregister().501 */502void ccwgroup_driver_unregister(struct ccwgroup_driver *cdriver)503{504	driver_unregister(&cdriver->driver);505}506EXPORT_SYMBOL(ccwgroup_driver_unregister);507 508/**509 * ccwgroup_probe_ccwdev() - probe function for slave devices510 * @cdev: ccw device to be probed511 *512 * This is a dummy probe function for ccw devices that are slave devices in513 * a ccw group device.514 * Returns:515 *  always %0516 */517int ccwgroup_probe_ccwdev(struct ccw_device *cdev)518{519	return 0;520}521EXPORT_SYMBOL(ccwgroup_probe_ccwdev);522 523/**524 * ccwgroup_remove_ccwdev() - remove function for slave devices525 * @cdev: ccw device to be removed526 *527 * This is a remove function for ccw devices that are slave devices in a ccw528 * group device. It sets the ccw device offline and also deregisters the529 * embedding ccw group device.530 */531void ccwgroup_remove_ccwdev(struct ccw_device *cdev)532{533	struct ccwgroup_device *gdev;534 535	/* Ignore offlining errors, device is gone anyway. */536	ccw_device_set_offline(cdev);537	/* If one of its devices is gone, the whole group is done for. */538	spin_lock_irq(cdev->ccwlock);539	gdev = dev_get_drvdata(&cdev->dev);540	if (!gdev) {541		spin_unlock_irq(cdev->ccwlock);542		return;543	}544	/* Get ccwgroup device reference for local processing. */545	get_device(&gdev->dev);546	spin_unlock_irq(cdev->ccwlock);547	/* Unregister group device. */548	ccwgroup_ungroup(gdev);549	/* Release ccwgroup device reference for local processing. */550	put_device(&gdev->dev);551}552EXPORT_SYMBOL(ccwgroup_remove_ccwdev);553MODULE_DESCRIPTION("ccwgroup bus driver");554MODULE_LICENSE("GPL");555