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