| |
| |
| |
| |
| |
| |
| |
| |
| #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; |
| |
| #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, |
| }; |
| |
| |
| |
| |
| |
| 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); |
| |
| |
| |
| |
| |
| void scm_driver_unregister(struct scm_driver *scmdrv) |
| { |
| <------>driver_unregister(&scmdrv->drv); |
| } |
| EXPORT_SYMBOL_GPL(scm_driver_unregister); |
| |
| void scm_irq_handler(struct aob *aob, blk_status_t 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; |
| } |
| |
| |
| |
| |
| 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, SCM_CHANGE); |
| out: |
| <------>device_unlock(&scmdev->dev); |
| <------>if (changed) |
| <------><------>kobject_uevent(&scmdev->dev.kobj, KOBJ_CHANGE); |
| } |
| |
| static int check_address(struct device *dev, const void *data) |
| { |
| <------>struct scm_device *scmdev = to_scm_dev(dev); |
| <------>const 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); |
| <------><------><------> |
| <------><------><------>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) { |
| <------><------><------> |
| <------><------><------>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 scm_dev_avail(struct device *dev, void *unused) |
| { |
| <------>struct scm_driver *scmdrv = to_scm_drv(dev->driver); |
| <------>struct scm_device *scmdev = to_scm_dev(dev); |
| |
| <------>if (dev->driver && scmdrv->notify) |
| <------><------>scmdrv->notify(scmdev, SCM_AVAIL); |
| |
| <------>return 0; |
| } |
| |
| int scm_process_availability_information(void) |
| { |
| <------>return bus_for_each_dev(&scm_bus_type, NULL, NULL, scm_dev_avail); |
| } |
| |
| 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); |
| |