/* * Persistent memory accessor * * Copyright (C) 2018 Google, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. */ #include #include #include #include #include #include #include #include #define DEVICE_NAME "access_ramoops" #define MAX_OPEN 4 struct access_ramoops_info { char *name; phys_addr_t phys; void *addr; size_t size; struct mutex lock; int is_mapped; int is_open; const char *label; struct miscdevice miscdev; }; static ssize_t label_show(struct device *dev, struct device_attribute *attr, char *buf) { struct miscdevice *miscdev = dev_get_drvdata(dev); struct access_ramoops_info *info = container_of(miscdev, struct access_ramoops_info, miscdev); return snprintf(buf, PAGE_SIZE, "%s\n", info->label); } static ssize_t size_show(struct device *dev, struct device_attribute *attr, char *buf) { struct miscdevice *miscdev = dev_get_drvdata(dev); struct access_ramoops_info *info = container_of(miscdev, struct access_ramoops_info, miscdev); return snprintf(buf, PAGE_SIZE, "%zd\n", info->size); } static DEVICE_ATTR_RO(label); static DEVICE_ATTR_RO(size); static struct attribute *access_ramoops_attrs[] = { &dev_attr_label.attr, &dev_attr_size.attr, NULL, }; ATTRIBUTE_GROUPS(access_ramoops); static ssize_t access_ramoops_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct access_ramoops_info *info = filp->private_data; return simple_read_from_buffer(buf, count, ppos, info->addr, info->size); } static ssize_t access_ramoops_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { struct access_ramoops_info *info = filp->private_data; return simple_write_to_buffer(info->addr, info->size, ppos, buf, count); } static int access_ramoops_open(struct inode *inode, struct file *filp) { int res = 0; struct access_ramoops_info *info = container_of(filp->private_data, struct access_ramoops_info, miscdev); filp->private_data = info; mutex_lock(&info->lock); if (info->is_open == MAX_OPEN) { res = -EBUSY; goto out; } if (!info->is_open) { if (!request_mem_region(info->phys, info->size, "access_ramoops")) { dev_err(info->miscdev.this_device, "request mem region (%zx@%pa) failed\n", info->size, &info->phys); res = -ENOMEM; goto out; } info->addr = ioremap(info->phys, info->size); if (IS_ERR(info->addr)) { dev_err(info->miscdev.this_device, "unable to map region (%zx@%pa)\n", info->size, &info->phys); info->addr = NULL; res = -ENOMEM; goto out; } info->is_mapped = 1; } info->is_open++; out: mutex_unlock(&info->lock); return res; } static int access_ramoops_release(struct inode *inode, struct file *filp) { struct access_ramoops_info *info = filp->private_data; mutex_lock(&info->lock); info->is_open--; if (!info->is_open) { info->is_mapped = 0; iounmap(info->addr); info->addr = NULL; release_mem_region(info->phys, info->size); } filp->private_data = NULL; mutex_unlock(&info->lock); return 0; } static struct file_operations access_ramoops_fops = { .owner = THIS_MODULE, .open = access_ramoops_open, .read = access_ramoops_read, .write = access_ramoops_write, .release = access_ramoops_release, }; static int __init access_ramoops_probe(struct platform_device *pdev) { int ret = 0; struct access_ramoops_info *info; struct device_node *of_node = pdev->dev.of_node; struct device_node *mem_region; struct resource res; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; platform_set_drvdata(pdev, info); mem_region = of_parse_phandle(of_node, "memory-region", 0); if (!mem_region) { dev_err(&pdev->dev, "no memory-region phandle\n"); return -ENODEV; } ret = of_property_read_string(of_node, "label", &info->label); if (ret) { dev_err(&pdev->dev, "failed to get region label: %d\n", ret); return -EINVAL; } info->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "access-%s", info->label); if (!info->name) { dev_err(&pdev->dev, "failed to alloc name\n"); return -ENOMEM; } ret = of_address_to_resource(mem_region, 0, &res); of_node_put(mem_region); if (ret) { dev_err(&pdev->dev, "failed to get memory-region resource: %d\n", ret); return -ENOMEM; } info->phys = res.start; info->size = resource_size(&res); mutex_init(&info->lock); info->miscdev.minor = MISC_DYNAMIC_MINOR; info->miscdev.name = info->name; info->miscdev.fops = &access_ramoops_fops; info->miscdev.groups = access_ramoops_groups; info->miscdev.parent = &pdev->dev; ret = misc_register(&info->miscdev); if (ret) { dev_err(info->miscdev.this_device, "failed to register misc device %d\n", ret); return ret; } dev_info(info->miscdev.this_device, "registered '%s' %d:%d, (%zx@%pa)\n", info->label, MISC_MAJOR, info->miscdev.minor, info->size, &info->phys); return ret; } static int __exit access_ramoops_remove(struct platform_device *pdev) { struct access_ramoops_info *info = platform_get_drvdata(pdev); dev_info(info->miscdev.this_device, "removing"); misc_deregister(&info->miscdev); if (!info->is_mapped) return 0; info->is_mapped = 0; iounmap(info->addr); release_mem_region(info->phys, info->size); return 0; } static const struct of_device_id dt_match[] = { { .compatible = "access_ramoops" }, {} }; static struct platform_driver access_ramoops_driver = { .driver = { .name = "access_ramoops", .of_match_table = dt_match, }, .remove = __exit_p(access_ramoops_remove), }; module_platform_driver_probe(access_ramoops_driver, access_ramoops_probe); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Patrick Tjin "); MODULE_DESCRIPTION("Persistent memory access driver");