diff options
Diffstat (limited to 'drivers/s390/cio/scm.c')
| -rw-r--r-- | drivers/s390/cio/scm.c | 317 | 
1 files changed, 317 insertions, 0 deletions
diff --git a/drivers/s390/cio/scm.c b/drivers/s390/cio/scm.c new file mode 100644 index 00000000000..bcf20f3aa51 --- /dev/null +++ b/drivers/s390/cio/scm.c @@ -0,0 +1,317 @@ +/* + * Recognize and maintain s390 storage class memory. + * + * Copyright IBM Corp. 2012 + * Author(s): Sebastian Ott <sebott@linux.vnet.ibm.com> + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/err.h> +#include <asm/eadm.h> +#include "chsc.h" + +static struct device *scm_root; +static struct eadm_ops *eadm_ops; +static DEFINE_MUTEX(eadm_ops_mutex); + +#define to_scm_dev(n) container_of(n, struct scm_device, dev) +#define	to_scm_drv(d) container_of(d, struct scm_driver, drv) + +static int scmdev_probe(struct device *dev) +{ +	struct scm_device *scmdev = to_scm_dev(dev); +	struct scm_driver *scmdrv = to_scm_drv(dev->driver); + +	return scmdrv->probe ? scmdrv->probe(scmdev) : -ENODEV; +} + +static int scmdev_remove(struct device *dev) +{ +	struct scm_device *scmdev = to_scm_dev(dev); +	struct scm_driver *scmdrv = to_scm_drv(dev->driver); + +	return scmdrv->remove ? scmdrv->remove(scmdev) : -ENODEV; +} + +static int scmdev_uevent(struct device *dev, struct kobj_uevent_env *env) +{ +	return add_uevent_var(env, "MODALIAS=scm:scmdev"); +} + +static struct bus_type scm_bus_type = { +	.name  = "scm", +	.probe = scmdev_probe, +	.remove = scmdev_remove, +	.uevent = scmdev_uevent, +}; + +/** + * scm_driver_register() - register a scm driver + * @scmdrv: driver to be registered + */ +int scm_driver_register(struct scm_driver *scmdrv) +{ +	struct device_driver *drv = &scmdrv->drv; + +	drv->bus = &scm_bus_type; + +	return driver_register(drv); +} +EXPORT_SYMBOL_GPL(scm_driver_register); + +/** + * scm_driver_unregister() - deregister a scm driver + * @scmdrv: driver to be deregistered + */ +void scm_driver_unregister(struct scm_driver *scmdrv) +{ +	driver_unregister(&scmdrv->drv); +} +EXPORT_SYMBOL_GPL(scm_driver_unregister); + +int scm_get_ref(void) +{ +	int ret = 0; + +	mutex_lock(&eadm_ops_mutex); +	if (!eadm_ops || !try_module_get(eadm_ops->owner)) +		ret = -ENOENT; +	mutex_unlock(&eadm_ops_mutex); + +	return ret; +} +EXPORT_SYMBOL_GPL(scm_get_ref); + +void scm_put_ref(void) +{ +	mutex_lock(&eadm_ops_mutex); +	module_put(eadm_ops->owner); +	mutex_unlock(&eadm_ops_mutex); +} +EXPORT_SYMBOL_GPL(scm_put_ref); + +void register_eadm_ops(struct eadm_ops *ops) +{ +	mutex_lock(&eadm_ops_mutex); +	eadm_ops = ops; +	mutex_unlock(&eadm_ops_mutex); +} +EXPORT_SYMBOL_GPL(register_eadm_ops); + +void unregister_eadm_ops(struct eadm_ops *ops) +{ +	mutex_lock(&eadm_ops_mutex); +	eadm_ops = NULL; +	mutex_unlock(&eadm_ops_mutex); +} +EXPORT_SYMBOL_GPL(unregister_eadm_ops); + +int scm_start_aob(struct aob *aob) +{ +	return eadm_ops->eadm_start(aob); +} +EXPORT_SYMBOL_GPL(scm_start_aob); + +void scm_irq_handler(struct aob *aob, int error) +{ +	struct aob_rq_header *aobrq = (void *) aob->request.data; +	struct scm_device *scmdev = aobrq->scmdev; +	struct scm_driver *scmdrv = to_scm_drv(scmdev->dev.driver); + +	scmdrv->handler(scmdev, aobrq->data, error); +} +EXPORT_SYMBOL_GPL(scm_irq_handler); + +#define scm_attr(name)							\ +static ssize_t show_##name(struct device *dev,				\ +	       struct device_attribute *attr, char *buf)		\ +{									\ +	struct scm_device *scmdev = to_scm_dev(dev);			\ +	int ret;							\ +									\ +	device_lock(dev);						\ +	ret = sprintf(buf, "%u\n", scmdev->attrs.name);			\ +	device_unlock(dev);						\ +									\ +	return ret;							\ +}									\ +static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL); + +scm_attr(persistence); +scm_attr(oper_state); +scm_attr(data_state); +scm_attr(rank); +scm_attr(release); +scm_attr(res_id); + +static struct attribute *scmdev_attrs[] = { +	&dev_attr_persistence.attr, +	&dev_attr_oper_state.attr, +	&dev_attr_data_state.attr, +	&dev_attr_rank.attr, +	&dev_attr_release.attr, +	&dev_attr_res_id.attr, +	NULL, +}; + +static struct attribute_group scmdev_attr_group = { +	.attrs = scmdev_attrs, +}; + +static const struct attribute_group *scmdev_attr_groups[] = { +	&scmdev_attr_group, +	NULL, +}; + +static void scmdev_release(struct device *dev) +{ +	struct scm_device *scmdev = to_scm_dev(dev); + +	kfree(scmdev); +} + +static void scmdev_setup(struct scm_device *scmdev, struct sale *sale, +			 unsigned int size, unsigned int max_blk_count) +{ +	dev_set_name(&scmdev->dev, "%016llx", (unsigned long long) sale->sa); +	scmdev->nr_max_block = max_blk_count; +	scmdev->address = sale->sa; +	scmdev->size = 1UL << size; +	scmdev->attrs.rank = sale->rank; +	scmdev->attrs.persistence = sale->p; +	scmdev->attrs.oper_state = sale->op_state; +	scmdev->attrs.data_state = sale->data_state; +	scmdev->attrs.rank = sale->rank; +	scmdev->attrs.release = sale->r; +	scmdev->attrs.res_id = sale->rid; +	scmdev->dev.parent = scm_root; +	scmdev->dev.bus = &scm_bus_type; +	scmdev->dev.release = scmdev_release; +	scmdev->dev.groups = scmdev_attr_groups; +} + +/* + * Check for state-changes, notify the driver and userspace. + */ +static void scmdev_update(struct scm_device *scmdev, struct sale *sale) +{ +	struct scm_driver *scmdrv; +	bool changed; + +	device_lock(&scmdev->dev); +	changed = scmdev->attrs.rank != sale->rank || +		  scmdev->attrs.oper_state != sale->op_state; +	scmdev->attrs.rank = sale->rank; +	scmdev->attrs.oper_state = sale->op_state; +	if (!scmdev->dev.driver) +		goto out; +	scmdrv = to_scm_drv(scmdev->dev.driver); +	if (changed && scmdrv->notify) +		scmdrv->notify(scmdev); +out: +	device_unlock(&scmdev->dev); +	if (changed) +		kobject_uevent(&scmdev->dev.kobj, KOBJ_CHANGE); +} + +static int check_address(struct device *dev, void *data) +{ +	struct scm_device *scmdev = to_scm_dev(dev); +	struct sale *sale = data; + +	return scmdev->address == sale->sa; +} + +static struct scm_device *scmdev_find(struct sale *sale) +{ +	struct device *dev; + +	dev = bus_find_device(&scm_bus_type, NULL, sale, check_address); + +	return dev ? to_scm_dev(dev) : NULL; +} + +static int scm_add(struct chsc_scm_info *scm_info, size_t num) +{ +	struct sale *sale, *scmal = scm_info->scmal; +	struct scm_device *scmdev; +	int ret; + +	for (sale = scmal; sale < scmal + num; sale++) { +		scmdev = scmdev_find(sale); +		if (scmdev) { +			scmdev_update(scmdev, sale); +			/* Release reference from scm_find(). */ +			put_device(&scmdev->dev); +			continue; +		} +		scmdev = kzalloc(sizeof(*scmdev), GFP_KERNEL); +		if (!scmdev) +			return -ENODEV; +		scmdev_setup(scmdev, sale, scm_info->is, scm_info->mbc); +		ret = device_register(&scmdev->dev); +		if (ret) { +			/* Release reference from device_initialize(). */ +			put_device(&scmdev->dev); +			return ret; +		} +	} + +	return 0; +} + +int scm_update_information(void) +{ +	struct chsc_scm_info *scm_info; +	u64 token = 0; +	size_t num; +	int ret; + +	scm_info = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); +	if (!scm_info) +		return -ENOMEM; + +	do { +		ret = chsc_scm_info(scm_info, token); +		if (ret) +			break; + +		num = (scm_info->response.length - +		       (offsetof(struct chsc_scm_info, scmal) - +			offsetof(struct chsc_scm_info, response)) +		      ) / sizeof(struct sale); + +		ret = scm_add(scm_info, num); +		if (ret) +			break; + +		token = scm_info->restok; +	} while (token); + +	free_page((unsigned long)scm_info); + +	return ret; +} + +static int __init scm_init(void) +{ +	int ret; + +	ret = bus_register(&scm_bus_type); +	if (ret) +		return ret; + +	scm_root = root_device_register("scm"); +	if (IS_ERR(scm_root)) { +		bus_unregister(&scm_bus_type); +		return PTR_ERR(scm_root); +	} + +	scm_update_information(); +	return 0; +} +subsys_initcall_sync(scm_init);  |