cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

ccwgroup.c (13432B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 *  bus driver for ccwgroup
      4 *
      5 *  Copyright IBM Corp. 2002, 2012
      6 *
      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		10
     26
     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 just
     30 * one of its functions, namely the translation of single subchannels
     31 * to devices that use multiple subchannels.
     32 */
     33
     34static 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 device
     50 * @gdev: target ccwgroup device
     51 *
     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 device
     79 * @gdev: target ccwgroup device
     80 * @call_gdrv: Call the registered gdrv set_offline function
     81 *
     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	else
    135		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 no
    155 * longer needed or accidentially 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	else
    185		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			for (--i; i >= 0; 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			for (--i; i >= 0; 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	} else
    289		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 device
    301 * @parent: parent device for the new device
    302 * @gdrv: driver for the new group device
    303 * @num_devices: number of slave devices
    304 * @buf: buffer containing comma separated bus ids of slave devices
    305 *
    306 * Create and register a new ccw group device as a child of @parent. Slave
    307 * 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-atomic
    312 */
    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 in
    344		 * 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_notifier
    420};
    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 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 driver
    483 * @cdriver: driver to be registered
    484 *
    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 driver
    498 * @cdriver: driver to be deregistered
    499 *
    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 devices
    510 * @cdev: ccw device to be probed
    511 *
    512 * This is a dummy probe function for ccw devices that are slave devices in
    513 * a ccw group device.
    514 * Returns:
    515 *  always %0
    516 */
    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 devices
    525 * @cdev: ccw device to be removed
    526 *
    527 * This is a remove function for ccw devices that are slave devices in a ccw
    528 * group device. It sets the ccw device offline and also deregisters the
    529 * 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_LICENSE("GPL");