diff options
-rw-r--r-- | arch/s390/Kconfig | 7 | ||||
-rw-r--r-- | arch/s390/include/asm/eadm.h | 51 | ||||
-rw-r--r-- | drivers/s390/cio/Makefile | 1 | ||||
-rw-r--r-- | drivers/s390/cio/scm.c | 271 |
4 files changed, 330 insertions, 0 deletions
diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 443fac9f164..3781db22f42 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -433,6 +433,13 @@ config CHSC_SCH If unsure, say N. +config SCM_BUS + def_bool y + depends on 64BIT + prompt "SCM bus driver" + help + Bus driver for Storage Class Memory. + endmenu menu "Dump support" diff --git a/arch/s390/include/asm/eadm.h b/arch/s390/include/asm/eadm.h index 4a65803baa0..3922f525717 100644 --- a/arch/s390/include/asm/eadm.h +++ b/arch/s390/include/asm/eadm.h @@ -2,6 +2,8 @@ #define _ASM_S390_EADM_H #include <linux/types.h> +#include <linux/device.h> +#include <linux/spinlock.h> struct arqb { u64 data; @@ -71,4 +73,53 @@ struct aob { struct msb msb[AOB_NR_MSB]; } __packed __aligned(PAGE_SIZE); +struct aob_rq_header { + struct scm_device *scmdev; + char data[0]; +}; + +struct scm_device { + u64 address; + u64 size; + unsigned int nr_max_block; + struct device dev; + spinlock_t lock; + struct { + unsigned int persistence:4; + unsigned int oper_state:4; + unsigned int data_state:4; + unsigned int rank:4; + unsigned int release:1; + unsigned int res_id:8; + } __packed attrs; +}; + +#define OP_STATE_GOOD 1 +#define OP_STATE_TEMP_ERR 2 +#define OP_STATE_PERM_ERR 3 + +struct scm_driver { + struct device_driver drv; + int (*probe) (struct scm_device *scmdev); + int (*remove) (struct scm_device *scmdev); + void (*handler) (struct scm_device *scmdev, void *data, int error); +}; + +int scm_driver_register(struct scm_driver *scmdrv); +void scm_driver_unregister(struct scm_driver *scmdrv); + +int scm_start_aob(struct aob *aob); +void scm_irq_handler(struct aob *aob, int error); + +struct eadm_ops { + int (*eadm_start) (struct aob *aob); + struct module *owner; +}; + +int scm_get_ref(void); +void scm_put_ref(void); + +void register_eadm_ops(struct eadm_ops *ops); +void unregister_eadm_ops(struct eadm_ops *ops); + #endif /* _ASM_S390_EADM_H */ diff --git a/drivers/s390/cio/Makefile b/drivers/s390/cio/Makefile index e1b700a1964..ac8f210e9a8 100644 --- a/drivers/s390/cio/Makefile +++ b/drivers/s390/cio/Makefile @@ -8,6 +8,7 @@ ccw_device-objs += device.o device_fsm.o device_ops.o ccw_device-objs += device_id.o device_pgid.o device_status.o obj-y += ccw_device.o cmf.o obj-$(CONFIG_CHSC_SCH) += chsc_sch.o +obj-$(CONFIG_SCM_BUS) += scm.o obj-$(CONFIG_CCWGROUP) += ccwgroup.o qdio-objs := qdio_main.o qdio_thinint.o qdio_debug.o qdio_setup.o diff --git a/drivers/s390/cio/scm.c b/drivers/s390/cio/scm.c new file mode 100644 index 00000000000..874b64a4bf2 --- /dev/null +++ b/drivers/s390/cio/scm.c @@ -0,0 +1,271 @@ +/* + * Recognize and maintain s390 storage class memory. + * + * Copyright IBM Corp. 2012 + * Author(s): Sebastian Ott <sebott@linux.vnet.ibm.com> + */ + +#include <linux/spinlock.h> +#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; \ + \ + spin_lock(&scmdev->lock); \ + ret = sprintf(buf, "%u\n", scmdev->attrs.name); \ + spin_unlock(&scmdev->lock); \ + \ + 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; + spin_lock_init(&scmdev->lock); +} + +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 = 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; +} + +static 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); |