162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * drivers/uio/uio.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright(C) 2005, Benedikt Spranger <b.spranger@linutronix.de> 662306a36Sopenharmony_ci * Copyright(C) 2005, Thomas Gleixner <tglx@linutronix.de> 762306a36Sopenharmony_ci * Copyright(C) 2006, Hans J. Koch <hjk@hansjkoch.de> 862306a36Sopenharmony_ci * Copyright(C) 2006, Greg Kroah-Hartman <greg@kroah.com> 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * Userspace IO 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * Base Functions 1362306a36Sopenharmony_ci */ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/init.h> 1762306a36Sopenharmony_ci#include <linux/poll.h> 1862306a36Sopenharmony_ci#include <linux/device.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci#include <linux/mm.h> 2162306a36Sopenharmony_ci#include <linux/idr.h> 2262306a36Sopenharmony_ci#include <linux/sched/signal.h> 2362306a36Sopenharmony_ci#include <linux/string.h> 2462306a36Sopenharmony_ci#include <linux/kobject.h> 2562306a36Sopenharmony_ci#include <linux/cdev.h> 2662306a36Sopenharmony_ci#include <linux/uio_driver.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define UIO_MAX_DEVICES (1U << MINORBITS) 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic int uio_major; 3162306a36Sopenharmony_cistatic struct cdev *uio_cdev; 3262306a36Sopenharmony_cistatic DEFINE_IDR(uio_idr); 3362306a36Sopenharmony_cistatic const struct file_operations uio_fops; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* Protect idr accesses */ 3662306a36Sopenharmony_cistatic DEFINE_MUTEX(minor_lock); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/* 3962306a36Sopenharmony_ci * attributes 4062306a36Sopenharmony_ci */ 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistruct uio_map { 4362306a36Sopenharmony_ci struct kobject kobj; 4462306a36Sopenharmony_ci struct uio_mem *mem; 4562306a36Sopenharmony_ci}; 4662306a36Sopenharmony_ci#define to_map(map) container_of(map, struct uio_map, kobj) 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic ssize_t map_name_show(struct uio_mem *mem, char *buf) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci if (unlikely(!mem->name)) 5162306a36Sopenharmony_ci mem->name = ""; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci return sprintf(buf, "%s\n", mem->name); 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic ssize_t map_addr_show(struct uio_mem *mem, char *buf) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci return sprintf(buf, "%pa\n", &mem->addr); 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic ssize_t map_size_show(struct uio_mem *mem, char *buf) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci return sprintf(buf, "%pa\n", &mem->size); 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic ssize_t map_offset_show(struct uio_mem *mem, char *buf) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci return sprintf(buf, "0x%llx\n", (unsigned long long)mem->offs); 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistruct map_sysfs_entry { 7262306a36Sopenharmony_ci struct attribute attr; 7362306a36Sopenharmony_ci ssize_t (*show)(struct uio_mem *, char *); 7462306a36Sopenharmony_ci ssize_t (*store)(struct uio_mem *, const char *, size_t); 7562306a36Sopenharmony_ci}; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic struct map_sysfs_entry name_attribute = 7862306a36Sopenharmony_ci __ATTR(name, S_IRUGO, map_name_show, NULL); 7962306a36Sopenharmony_cistatic struct map_sysfs_entry addr_attribute = 8062306a36Sopenharmony_ci __ATTR(addr, S_IRUGO, map_addr_show, NULL); 8162306a36Sopenharmony_cistatic struct map_sysfs_entry size_attribute = 8262306a36Sopenharmony_ci __ATTR(size, S_IRUGO, map_size_show, NULL); 8362306a36Sopenharmony_cistatic struct map_sysfs_entry offset_attribute = 8462306a36Sopenharmony_ci __ATTR(offset, S_IRUGO, map_offset_show, NULL); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic struct attribute *map_attrs[] = { 8762306a36Sopenharmony_ci &name_attribute.attr, 8862306a36Sopenharmony_ci &addr_attribute.attr, 8962306a36Sopenharmony_ci &size_attribute.attr, 9062306a36Sopenharmony_ci &offset_attribute.attr, 9162306a36Sopenharmony_ci NULL, /* need to NULL terminate the list of attributes */ 9262306a36Sopenharmony_ci}; 9362306a36Sopenharmony_ciATTRIBUTE_GROUPS(map); 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic void map_release(struct kobject *kobj) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci struct uio_map *map = to_map(kobj); 9862306a36Sopenharmony_ci kfree(map); 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic ssize_t map_type_show(struct kobject *kobj, struct attribute *attr, 10262306a36Sopenharmony_ci char *buf) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct uio_map *map = to_map(kobj); 10562306a36Sopenharmony_ci struct uio_mem *mem = map->mem; 10662306a36Sopenharmony_ci struct map_sysfs_entry *entry; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci entry = container_of(attr, struct map_sysfs_entry, attr); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (!entry->show) 11162306a36Sopenharmony_ci return -EIO; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci return entry->show(mem, buf); 11462306a36Sopenharmony_ci} 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_cistatic const struct sysfs_ops map_sysfs_ops = { 11762306a36Sopenharmony_ci .show = map_type_show, 11862306a36Sopenharmony_ci}; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic struct kobj_type map_attr_type = { 12162306a36Sopenharmony_ci .release = map_release, 12262306a36Sopenharmony_ci .sysfs_ops = &map_sysfs_ops, 12362306a36Sopenharmony_ci .default_groups = map_groups, 12462306a36Sopenharmony_ci}; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistruct uio_portio { 12762306a36Sopenharmony_ci struct kobject kobj; 12862306a36Sopenharmony_ci struct uio_port *port; 12962306a36Sopenharmony_ci}; 13062306a36Sopenharmony_ci#define to_portio(portio) container_of(portio, struct uio_portio, kobj) 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic ssize_t portio_name_show(struct uio_port *port, char *buf) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci if (unlikely(!port->name)) 13562306a36Sopenharmony_ci port->name = ""; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return sprintf(buf, "%s\n", port->name); 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic ssize_t portio_start_show(struct uio_port *port, char *buf) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci return sprintf(buf, "0x%lx\n", port->start); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic ssize_t portio_size_show(struct uio_port *port, char *buf) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci return sprintf(buf, "0x%lx\n", port->size); 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic ssize_t portio_porttype_show(struct uio_port *port, char *buf) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci const char *porttypes[] = {"none", "x86", "gpio", "other"}; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci if ((port->porttype < 0) || (port->porttype > UIO_PORT_OTHER)) 15562306a36Sopenharmony_ci return -EINVAL; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci return sprintf(buf, "port_%s\n", porttypes[port->porttype]); 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistruct portio_sysfs_entry { 16162306a36Sopenharmony_ci struct attribute attr; 16262306a36Sopenharmony_ci ssize_t (*show)(struct uio_port *, char *); 16362306a36Sopenharmony_ci ssize_t (*store)(struct uio_port *, const char *, size_t); 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic struct portio_sysfs_entry portio_name_attribute = 16762306a36Sopenharmony_ci __ATTR(name, S_IRUGO, portio_name_show, NULL); 16862306a36Sopenharmony_cistatic struct portio_sysfs_entry portio_start_attribute = 16962306a36Sopenharmony_ci __ATTR(start, S_IRUGO, portio_start_show, NULL); 17062306a36Sopenharmony_cistatic struct portio_sysfs_entry portio_size_attribute = 17162306a36Sopenharmony_ci __ATTR(size, S_IRUGO, portio_size_show, NULL); 17262306a36Sopenharmony_cistatic struct portio_sysfs_entry portio_porttype_attribute = 17362306a36Sopenharmony_ci __ATTR(porttype, S_IRUGO, portio_porttype_show, NULL); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic struct attribute *portio_attrs[] = { 17662306a36Sopenharmony_ci &portio_name_attribute.attr, 17762306a36Sopenharmony_ci &portio_start_attribute.attr, 17862306a36Sopenharmony_ci &portio_size_attribute.attr, 17962306a36Sopenharmony_ci &portio_porttype_attribute.attr, 18062306a36Sopenharmony_ci NULL, 18162306a36Sopenharmony_ci}; 18262306a36Sopenharmony_ciATTRIBUTE_GROUPS(portio); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic void portio_release(struct kobject *kobj) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct uio_portio *portio = to_portio(kobj); 18762306a36Sopenharmony_ci kfree(portio); 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_cistatic ssize_t portio_type_show(struct kobject *kobj, struct attribute *attr, 19162306a36Sopenharmony_ci char *buf) 19262306a36Sopenharmony_ci{ 19362306a36Sopenharmony_ci struct uio_portio *portio = to_portio(kobj); 19462306a36Sopenharmony_ci struct uio_port *port = portio->port; 19562306a36Sopenharmony_ci struct portio_sysfs_entry *entry; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci entry = container_of(attr, struct portio_sysfs_entry, attr); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci if (!entry->show) 20062306a36Sopenharmony_ci return -EIO; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci return entry->show(port, buf); 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic const struct sysfs_ops portio_sysfs_ops = { 20662306a36Sopenharmony_ci .show = portio_type_show, 20762306a36Sopenharmony_ci}; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic struct kobj_type portio_attr_type = { 21062306a36Sopenharmony_ci .release = portio_release, 21162306a36Sopenharmony_ci .sysfs_ops = &portio_sysfs_ops, 21262306a36Sopenharmony_ci .default_groups = portio_groups, 21362306a36Sopenharmony_ci}; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic ssize_t name_show(struct device *dev, 21662306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci struct uio_device *idev = dev_get_drvdata(dev); 21962306a36Sopenharmony_ci int ret; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 22262306a36Sopenharmony_ci if (!idev->info) { 22362306a36Sopenharmony_ci ret = -EINVAL; 22462306a36Sopenharmony_ci dev_err(dev, "the device has been unregistered\n"); 22562306a36Sopenharmony_ci goto out; 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci ret = sprintf(buf, "%s\n", idev->info->name); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ciout: 23162306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 23262306a36Sopenharmony_ci return ret; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_cistatic DEVICE_ATTR_RO(name); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic ssize_t version_show(struct device *dev, 23762306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci struct uio_device *idev = dev_get_drvdata(dev); 24062306a36Sopenharmony_ci int ret; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 24362306a36Sopenharmony_ci if (!idev->info) { 24462306a36Sopenharmony_ci ret = -EINVAL; 24562306a36Sopenharmony_ci dev_err(dev, "the device has been unregistered\n"); 24662306a36Sopenharmony_ci goto out; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci ret = sprintf(buf, "%s\n", idev->info->version); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ciout: 25262306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 25362306a36Sopenharmony_ci return ret; 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_cistatic DEVICE_ATTR_RO(version); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_cistatic ssize_t event_show(struct device *dev, 25862306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 25962306a36Sopenharmony_ci{ 26062306a36Sopenharmony_ci struct uio_device *idev = dev_get_drvdata(dev); 26162306a36Sopenharmony_ci return sprintf(buf, "%u\n", (unsigned int)atomic_read(&idev->event)); 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_cistatic DEVICE_ATTR_RO(event); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic struct attribute *uio_attrs[] = { 26662306a36Sopenharmony_ci &dev_attr_name.attr, 26762306a36Sopenharmony_ci &dev_attr_version.attr, 26862306a36Sopenharmony_ci &dev_attr_event.attr, 26962306a36Sopenharmony_ci NULL, 27062306a36Sopenharmony_ci}; 27162306a36Sopenharmony_ciATTRIBUTE_GROUPS(uio); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci/* UIO class infrastructure */ 27462306a36Sopenharmony_cistatic struct class uio_class = { 27562306a36Sopenharmony_ci .name = "uio", 27662306a36Sopenharmony_ci .dev_groups = uio_groups, 27762306a36Sopenharmony_ci}; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic bool uio_class_registered; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci/* 28262306a36Sopenharmony_ci * device functions 28362306a36Sopenharmony_ci */ 28462306a36Sopenharmony_cistatic int uio_dev_add_attributes(struct uio_device *idev) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci int ret; 28762306a36Sopenharmony_ci int mi, pi; 28862306a36Sopenharmony_ci int map_found = 0; 28962306a36Sopenharmony_ci int portio_found = 0; 29062306a36Sopenharmony_ci struct uio_mem *mem; 29162306a36Sopenharmony_ci struct uio_map *map; 29262306a36Sopenharmony_ci struct uio_port *port; 29362306a36Sopenharmony_ci struct uio_portio *portio; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci for (mi = 0; mi < MAX_UIO_MAPS; mi++) { 29662306a36Sopenharmony_ci mem = &idev->info->mem[mi]; 29762306a36Sopenharmony_ci if (mem->size == 0) 29862306a36Sopenharmony_ci break; 29962306a36Sopenharmony_ci if (!map_found) { 30062306a36Sopenharmony_ci map_found = 1; 30162306a36Sopenharmony_ci idev->map_dir = kobject_create_and_add("maps", 30262306a36Sopenharmony_ci &idev->dev.kobj); 30362306a36Sopenharmony_ci if (!idev->map_dir) { 30462306a36Sopenharmony_ci ret = -ENOMEM; 30562306a36Sopenharmony_ci goto err_map; 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci } 30862306a36Sopenharmony_ci map = kzalloc(sizeof(*map), GFP_KERNEL); 30962306a36Sopenharmony_ci if (!map) { 31062306a36Sopenharmony_ci ret = -ENOMEM; 31162306a36Sopenharmony_ci goto err_map; 31262306a36Sopenharmony_ci } 31362306a36Sopenharmony_ci kobject_init(&map->kobj, &map_attr_type); 31462306a36Sopenharmony_ci map->mem = mem; 31562306a36Sopenharmony_ci mem->map = map; 31662306a36Sopenharmony_ci ret = kobject_add(&map->kobj, idev->map_dir, "map%d", mi); 31762306a36Sopenharmony_ci if (ret) 31862306a36Sopenharmony_ci goto err_map_kobj; 31962306a36Sopenharmony_ci ret = kobject_uevent(&map->kobj, KOBJ_ADD); 32062306a36Sopenharmony_ci if (ret) 32162306a36Sopenharmony_ci goto err_map_kobj; 32262306a36Sopenharmony_ci } 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci for (pi = 0; pi < MAX_UIO_PORT_REGIONS; pi++) { 32562306a36Sopenharmony_ci port = &idev->info->port[pi]; 32662306a36Sopenharmony_ci if (port->size == 0) 32762306a36Sopenharmony_ci break; 32862306a36Sopenharmony_ci if (!portio_found) { 32962306a36Sopenharmony_ci portio_found = 1; 33062306a36Sopenharmony_ci idev->portio_dir = kobject_create_and_add("portio", 33162306a36Sopenharmony_ci &idev->dev.kobj); 33262306a36Sopenharmony_ci if (!idev->portio_dir) { 33362306a36Sopenharmony_ci ret = -ENOMEM; 33462306a36Sopenharmony_ci goto err_portio; 33562306a36Sopenharmony_ci } 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci portio = kzalloc(sizeof(*portio), GFP_KERNEL); 33862306a36Sopenharmony_ci if (!portio) { 33962306a36Sopenharmony_ci ret = -ENOMEM; 34062306a36Sopenharmony_ci goto err_portio; 34162306a36Sopenharmony_ci } 34262306a36Sopenharmony_ci kobject_init(&portio->kobj, &portio_attr_type); 34362306a36Sopenharmony_ci portio->port = port; 34462306a36Sopenharmony_ci port->portio = portio; 34562306a36Sopenharmony_ci ret = kobject_add(&portio->kobj, idev->portio_dir, 34662306a36Sopenharmony_ci "port%d", pi); 34762306a36Sopenharmony_ci if (ret) 34862306a36Sopenharmony_ci goto err_portio_kobj; 34962306a36Sopenharmony_ci ret = kobject_uevent(&portio->kobj, KOBJ_ADD); 35062306a36Sopenharmony_ci if (ret) 35162306a36Sopenharmony_ci goto err_portio_kobj; 35262306a36Sopenharmony_ci } 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci return 0; 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_cierr_portio: 35762306a36Sopenharmony_ci pi--; 35862306a36Sopenharmony_cierr_portio_kobj: 35962306a36Sopenharmony_ci for (; pi >= 0; pi--) { 36062306a36Sopenharmony_ci port = &idev->info->port[pi]; 36162306a36Sopenharmony_ci portio = port->portio; 36262306a36Sopenharmony_ci kobject_put(&portio->kobj); 36362306a36Sopenharmony_ci } 36462306a36Sopenharmony_ci kobject_put(idev->portio_dir); 36562306a36Sopenharmony_cierr_map: 36662306a36Sopenharmony_ci mi--; 36762306a36Sopenharmony_cierr_map_kobj: 36862306a36Sopenharmony_ci for (; mi >= 0; mi--) { 36962306a36Sopenharmony_ci mem = &idev->info->mem[mi]; 37062306a36Sopenharmony_ci map = mem->map; 37162306a36Sopenharmony_ci kobject_put(&map->kobj); 37262306a36Sopenharmony_ci } 37362306a36Sopenharmony_ci kobject_put(idev->map_dir); 37462306a36Sopenharmony_ci dev_err(&idev->dev, "error creating sysfs files (%d)\n", ret); 37562306a36Sopenharmony_ci return ret; 37662306a36Sopenharmony_ci} 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_cistatic void uio_dev_del_attributes(struct uio_device *idev) 37962306a36Sopenharmony_ci{ 38062306a36Sopenharmony_ci int i; 38162306a36Sopenharmony_ci struct uio_mem *mem; 38262306a36Sopenharmony_ci struct uio_port *port; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci for (i = 0; i < MAX_UIO_MAPS; i++) { 38562306a36Sopenharmony_ci mem = &idev->info->mem[i]; 38662306a36Sopenharmony_ci if (mem->size == 0) 38762306a36Sopenharmony_ci break; 38862306a36Sopenharmony_ci kobject_put(&mem->map->kobj); 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci kobject_put(idev->map_dir); 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci for (i = 0; i < MAX_UIO_PORT_REGIONS; i++) { 39362306a36Sopenharmony_ci port = &idev->info->port[i]; 39462306a36Sopenharmony_ci if (port->size == 0) 39562306a36Sopenharmony_ci break; 39662306a36Sopenharmony_ci kobject_put(&port->portio->kobj); 39762306a36Sopenharmony_ci } 39862306a36Sopenharmony_ci kobject_put(idev->portio_dir); 39962306a36Sopenharmony_ci} 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_cistatic int uio_get_minor(struct uio_device *idev) 40262306a36Sopenharmony_ci{ 40362306a36Sopenharmony_ci int retval; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci mutex_lock(&minor_lock); 40662306a36Sopenharmony_ci retval = idr_alloc(&uio_idr, idev, 0, UIO_MAX_DEVICES, GFP_KERNEL); 40762306a36Sopenharmony_ci if (retval >= 0) { 40862306a36Sopenharmony_ci idev->minor = retval; 40962306a36Sopenharmony_ci retval = 0; 41062306a36Sopenharmony_ci } else if (retval == -ENOSPC) { 41162306a36Sopenharmony_ci dev_err(&idev->dev, "too many uio devices\n"); 41262306a36Sopenharmony_ci retval = -EINVAL; 41362306a36Sopenharmony_ci } 41462306a36Sopenharmony_ci mutex_unlock(&minor_lock); 41562306a36Sopenharmony_ci return retval; 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_cistatic void uio_free_minor(unsigned long minor) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci mutex_lock(&minor_lock); 42162306a36Sopenharmony_ci idr_remove(&uio_idr, minor); 42262306a36Sopenharmony_ci mutex_unlock(&minor_lock); 42362306a36Sopenharmony_ci} 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci/** 42662306a36Sopenharmony_ci * uio_event_notify - trigger an interrupt event 42762306a36Sopenharmony_ci * @info: UIO device capabilities 42862306a36Sopenharmony_ci */ 42962306a36Sopenharmony_civoid uio_event_notify(struct uio_info *info) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci struct uio_device *idev = info->uio_dev; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci atomic_inc(&idev->event); 43462306a36Sopenharmony_ci wake_up_interruptible(&idev->wait); 43562306a36Sopenharmony_ci kill_fasync(&idev->async_queue, SIGIO, POLL_IN); 43662306a36Sopenharmony_ci} 43762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(uio_event_notify); 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci/** 44062306a36Sopenharmony_ci * uio_interrupt - hardware interrupt handler 44162306a36Sopenharmony_ci * @irq: IRQ number, can be UIO_IRQ_CYCLIC for cyclic timer 44262306a36Sopenharmony_ci * @dev_id: Pointer to the devices uio_device structure 44362306a36Sopenharmony_ci */ 44462306a36Sopenharmony_cistatic irqreturn_t uio_interrupt(int irq, void *dev_id) 44562306a36Sopenharmony_ci{ 44662306a36Sopenharmony_ci struct uio_device *idev = (struct uio_device *)dev_id; 44762306a36Sopenharmony_ci irqreturn_t ret; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci ret = idev->info->handler(irq, idev->info); 45062306a36Sopenharmony_ci if (ret == IRQ_HANDLED) 45162306a36Sopenharmony_ci uio_event_notify(idev->info); 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci return ret; 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cistruct uio_listener { 45762306a36Sopenharmony_ci struct uio_device *dev; 45862306a36Sopenharmony_ci s32 event_count; 45962306a36Sopenharmony_ci}; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_cistatic int uio_open(struct inode *inode, struct file *filep) 46262306a36Sopenharmony_ci{ 46362306a36Sopenharmony_ci struct uio_device *idev; 46462306a36Sopenharmony_ci struct uio_listener *listener; 46562306a36Sopenharmony_ci int ret = 0; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci mutex_lock(&minor_lock); 46862306a36Sopenharmony_ci idev = idr_find(&uio_idr, iminor(inode)); 46962306a36Sopenharmony_ci if (!idev) { 47062306a36Sopenharmony_ci ret = -ENODEV; 47162306a36Sopenharmony_ci mutex_unlock(&minor_lock); 47262306a36Sopenharmony_ci goto out; 47362306a36Sopenharmony_ci } 47462306a36Sopenharmony_ci get_device(&idev->dev); 47562306a36Sopenharmony_ci mutex_unlock(&minor_lock); 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci if (!try_module_get(idev->owner)) { 47862306a36Sopenharmony_ci ret = -ENODEV; 47962306a36Sopenharmony_ci goto err_module_get; 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci listener = kmalloc(sizeof(*listener), GFP_KERNEL); 48362306a36Sopenharmony_ci if (!listener) { 48462306a36Sopenharmony_ci ret = -ENOMEM; 48562306a36Sopenharmony_ci goto err_alloc_listener; 48662306a36Sopenharmony_ci } 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci listener->dev = idev; 48962306a36Sopenharmony_ci listener->event_count = atomic_read(&idev->event); 49062306a36Sopenharmony_ci filep->private_data = listener; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 49362306a36Sopenharmony_ci if (!idev->info) { 49462306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 49562306a36Sopenharmony_ci ret = -EINVAL; 49662306a36Sopenharmony_ci goto err_infoopen; 49762306a36Sopenharmony_ci } 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci if (idev->info->open) 50062306a36Sopenharmony_ci ret = idev->info->open(idev->info, inode); 50162306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 50262306a36Sopenharmony_ci if (ret) 50362306a36Sopenharmony_ci goto err_infoopen; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci return 0; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_cierr_infoopen: 50862306a36Sopenharmony_ci kfree(listener); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_cierr_alloc_listener: 51162306a36Sopenharmony_ci module_put(idev->owner); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_cierr_module_get: 51462306a36Sopenharmony_ci put_device(&idev->dev); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ciout: 51762306a36Sopenharmony_ci return ret; 51862306a36Sopenharmony_ci} 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_cistatic int uio_fasync(int fd, struct file *filep, int on) 52162306a36Sopenharmony_ci{ 52262306a36Sopenharmony_ci struct uio_listener *listener = filep->private_data; 52362306a36Sopenharmony_ci struct uio_device *idev = listener->dev; 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci return fasync_helper(fd, filep, on, &idev->async_queue); 52662306a36Sopenharmony_ci} 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_cistatic int uio_release(struct inode *inode, struct file *filep) 52962306a36Sopenharmony_ci{ 53062306a36Sopenharmony_ci int ret = 0; 53162306a36Sopenharmony_ci struct uio_listener *listener = filep->private_data; 53262306a36Sopenharmony_ci struct uio_device *idev = listener->dev; 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 53562306a36Sopenharmony_ci if (idev->info && idev->info->release) 53662306a36Sopenharmony_ci ret = idev->info->release(idev->info, inode); 53762306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci module_put(idev->owner); 54062306a36Sopenharmony_ci kfree(listener); 54162306a36Sopenharmony_ci put_device(&idev->dev); 54262306a36Sopenharmony_ci return ret; 54362306a36Sopenharmony_ci} 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_cistatic __poll_t uio_poll(struct file *filep, poll_table *wait) 54662306a36Sopenharmony_ci{ 54762306a36Sopenharmony_ci struct uio_listener *listener = filep->private_data; 54862306a36Sopenharmony_ci struct uio_device *idev = listener->dev; 54962306a36Sopenharmony_ci __poll_t ret = 0; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 55262306a36Sopenharmony_ci if (!idev->info || !idev->info->irq) 55362306a36Sopenharmony_ci ret = -EIO; 55462306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci if (ret) 55762306a36Sopenharmony_ci return ret; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci poll_wait(filep, &idev->wait, wait); 56062306a36Sopenharmony_ci if (listener->event_count != atomic_read(&idev->event)) 56162306a36Sopenharmony_ci return EPOLLIN | EPOLLRDNORM; 56262306a36Sopenharmony_ci return 0; 56362306a36Sopenharmony_ci} 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_cistatic ssize_t uio_read(struct file *filep, char __user *buf, 56662306a36Sopenharmony_ci size_t count, loff_t *ppos) 56762306a36Sopenharmony_ci{ 56862306a36Sopenharmony_ci struct uio_listener *listener = filep->private_data; 56962306a36Sopenharmony_ci struct uio_device *idev = listener->dev; 57062306a36Sopenharmony_ci DECLARE_WAITQUEUE(wait, current); 57162306a36Sopenharmony_ci ssize_t retval = 0; 57262306a36Sopenharmony_ci s32 event_count; 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci if (count != sizeof(s32)) 57562306a36Sopenharmony_ci return -EINVAL; 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci add_wait_queue(&idev->wait, &wait); 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci do { 58062306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 58162306a36Sopenharmony_ci if (!idev->info || !idev->info->irq) { 58262306a36Sopenharmony_ci retval = -EIO; 58362306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 58462306a36Sopenharmony_ci break; 58562306a36Sopenharmony_ci } 58662306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci set_current_state(TASK_INTERRUPTIBLE); 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci event_count = atomic_read(&idev->event); 59162306a36Sopenharmony_ci if (event_count != listener->event_count) { 59262306a36Sopenharmony_ci __set_current_state(TASK_RUNNING); 59362306a36Sopenharmony_ci if (copy_to_user(buf, &event_count, count)) 59462306a36Sopenharmony_ci retval = -EFAULT; 59562306a36Sopenharmony_ci else { 59662306a36Sopenharmony_ci listener->event_count = event_count; 59762306a36Sopenharmony_ci retval = count; 59862306a36Sopenharmony_ci } 59962306a36Sopenharmony_ci break; 60062306a36Sopenharmony_ci } 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci if (filep->f_flags & O_NONBLOCK) { 60362306a36Sopenharmony_ci retval = -EAGAIN; 60462306a36Sopenharmony_ci break; 60562306a36Sopenharmony_ci } 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci if (signal_pending(current)) { 60862306a36Sopenharmony_ci retval = -ERESTARTSYS; 60962306a36Sopenharmony_ci break; 61062306a36Sopenharmony_ci } 61162306a36Sopenharmony_ci schedule(); 61262306a36Sopenharmony_ci } while (1); 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci __set_current_state(TASK_RUNNING); 61562306a36Sopenharmony_ci remove_wait_queue(&idev->wait, &wait); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci return retval; 61862306a36Sopenharmony_ci} 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_cistatic ssize_t uio_write(struct file *filep, const char __user *buf, 62162306a36Sopenharmony_ci size_t count, loff_t *ppos) 62262306a36Sopenharmony_ci{ 62362306a36Sopenharmony_ci struct uio_listener *listener = filep->private_data; 62462306a36Sopenharmony_ci struct uio_device *idev = listener->dev; 62562306a36Sopenharmony_ci ssize_t retval; 62662306a36Sopenharmony_ci s32 irq_on; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci if (count != sizeof(s32)) 62962306a36Sopenharmony_ci return -EINVAL; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci if (copy_from_user(&irq_on, buf, count)) 63262306a36Sopenharmony_ci return -EFAULT; 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 63562306a36Sopenharmony_ci if (!idev->info) { 63662306a36Sopenharmony_ci retval = -EINVAL; 63762306a36Sopenharmony_ci goto out; 63862306a36Sopenharmony_ci } 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci if (!idev->info->irq) { 64162306a36Sopenharmony_ci retval = -EIO; 64262306a36Sopenharmony_ci goto out; 64362306a36Sopenharmony_ci } 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci if (!idev->info->irqcontrol) { 64662306a36Sopenharmony_ci retval = -ENOSYS; 64762306a36Sopenharmony_ci goto out; 64862306a36Sopenharmony_ci } 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci retval = idev->info->irqcontrol(idev->info, irq_on); 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ciout: 65362306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 65462306a36Sopenharmony_ci return retval ? retval : sizeof(s32); 65562306a36Sopenharmony_ci} 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_cistatic int uio_find_mem_index(struct vm_area_struct *vma) 65862306a36Sopenharmony_ci{ 65962306a36Sopenharmony_ci struct uio_device *idev = vma->vm_private_data; 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci if (vma->vm_pgoff < MAX_UIO_MAPS) { 66262306a36Sopenharmony_ci if (idev->info->mem[vma->vm_pgoff].size == 0) 66362306a36Sopenharmony_ci return -1; 66462306a36Sopenharmony_ci return (int)vma->vm_pgoff; 66562306a36Sopenharmony_ci } 66662306a36Sopenharmony_ci return -1; 66762306a36Sopenharmony_ci} 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_cistatic vm_fault_t uio_vma_fault(struct vm_fault *vmf) 67062306a36Sopenharmony_ci{ 67162306a36Sopenharmony_ci struct uio_device *idev = vmf->vma->vm_private_data; 67262306a36Sopenharmony_ci struct page *page; 67362306a36Sopenharmony_ci unsigned long offset; 67462306a36Sopenharmony_ci void *addr; 67562306a36Sopenharmony_ci vm_fault_t ret = 0; 67662306a36Sopenharmony_ci int mi; 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 67962306a36Sopenharmony_ci if (!idev->info) { 68062306a36Sopenharmony_ci ret = VM_FAULT_SIGBUS; 68162306a36Sopenharmony_ci goto out; 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci mi = uio_find_mem_index(vmf->vma); 68562306a36Sopenharmony_ci if (mi < 0) { 68662306a36Sopenharmony_ci ret = VM_FAULT_SIGBUS; 68762306a36Sopenharmony_ci goto out; 68862306a36Sopenharmony_ci } 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci /* 69162306a36Sopenharmony_ci * We need to subtract mi because userspace uses offset = N*PAGE_SIZE 69262306a36Sopenharmony_ci * to use mem[N]. 69362306a36Sopenharmony_ci */ 69462306a36Sopenharmony_ci offset = (vmf->pgoff - mi) << PAGE_SHIFT; 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci addr = (void *)(unsigned long)idev->info->mem[mi].addr + offset; 69762306a36Sopenharmony_ci if (idev->info->mem[mi].memtype == UIO_MEM_LOGICAL) 69862306a36Sopenharmony_ci page = virt_to_page(addr); 69962306a36Sopenharmony_ci else 70062306a36Sopenharmony_ci page = vmalloc_to_page(addr); 70162306a36Sopenharmony_ci get_page(page); 70262306a36Sopenharmony_ci vmf->page = page; 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ciout: 70562306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci return ret; 70862306a36Sopenharmony_ci} 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_cistatic const struct vm_operations_struct uio_logical_vm_ops = { 71162306a36Sopenharmony_ci .fault = uio_vma_fault, 71262306a36Sopenharmony_ci}; 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_cistatic int uio_mmap_logical(struct vm_area_struct *vma) 71562306a36Sopenharmony_ci{ 71662306a36Sopenharmony_ci vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP); 71762306a36Sopenharmony_ci vma->vm_ops = &uio_logical_vm_ops; 71862306a36Sopenharmony_ci return 0; 71962306a36Sopenharmony_ci} 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_cistatic const struct vm_operations_struct uio_physical_vm_ops = { 72262306a36Sopenharmony_ci#ifdef CONFIG_HAVE_IOREMAP_PROT 72362306a36Sopenharmony_ci .access = generic_access_phys, 72462306a36Sopenharmony_ci#endif 72562306a36Sopenharmony_ci}; 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_cistatic int uio_mmap_physical(struct vm_area_struct *vma) 72862306a36Sopenharmony_ci{ 72962306a36Sopenharmony_ci struct uio_device *idev = vma->vm_private_data; 73062306a36Sopenharmony_ci int mi = uio_find_mem_index(vma); 73162306a36Sopenharmony_ci struct uio_mem *mem; 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ci if (mi < 0) 73462306a36Sopenharmony_ci return -EINVAL; 73562306a36Sopenharmony_ci mem = idev->info->mem + mi; 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci if (mem->addr & ~PAGE_MASK) 73862306a36Sopenharmony_ci return -ENODEV; 73962306a36Sopenharmony_ci if (vma->vm_end - vma->vm_start > mem->size) 74062306a36Sopenharmony_ci return -EINVAL; 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci vma->vm_ops = &uio_physical_vm_ops; 74362306a36Sopenharmony_ci if (idev->info->mem[mi].memtype == UIO_MEM_PHYS) 74462306a36Sopenharmony_ci vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci /* 74762306a36Sopenharmony_ci * We cannot use the vm_iomap_memory() helper here, 74862306a36Sopenharmony_ci * because vma->vm_pgoff is the map index we looked 74962306a36Sopenharmony_ci * up above in uio_find_mem_index(), rather than an 75062306a36Sopenharmony_ci * actual page offset into the mmap. 75162306a36Sopenharmony_ci * 75262306a36Sopenharmony_ci * So we just do the physical mmap without a page 75362306a36Sopenharmony_ci * offset. 75462306a36Sopenharmony_ci */ 75562306a36Sopenharmony_ci return remap_pfn_range(vma, 75662306a36Sopenharmony_ci vma->vm_start, 75762306a36Sopenharmony_ci mem->addr >> PAGE_SHIFT, 75862306a36Sopenharmony_ci vma->vm_end - vma->vm_start, 75962306a36Sopenharmony_ci vma->vm_page_prot); 76062306a36Sopenharmony_ci} 76162306a36Sopenharmony_ci 76262306a36Sopenharmony_cistatic int uio_mmap(struct file *filep, struct vm_area_struct *vma) 76362306a36Sopenharmony_ci{ 76462306a36Sopenharmony_ci struct uio_listener *listener = filep->private_data; 76562306a36Sopenharmony_ci struct uio_device *idev = listener->dev; 76662306a36Sopenharmony_ci int mi; 76762306a36Sopenharmony_ci unsigned long requested_pages, actual_pages; 76862306a36Sopenharmony_ci int ret = 0; 76962306a36Sopenharmony_ci 77062306a36Sopenharmony_ci if (vma->vm_end < vma->vm_start) 77162306a36Sopenharmony_ci return -EINVAL; 77262306a36Sopenharmony_ci 77362306a36Sopenharmony_ci vma->vm_private_data = idev; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 77662306a36Sopenharmony_ci if (!idev->info) { 77762306a36Sopenharmony_ci ret = -EINVAL; 77862306a36Sopenharmony_ci goto out; 77962306a36Sopenharmony_ci } 78062306a36Sopenharmony_ci 78162306a36Sopenharmony_ci mi = uio_find_mem_index(vma); 78262306a36Sopenharmony_ci if (mi < 0) { 78362306a36Sopenharmony_ci ret = -EINVAL; 78462306a36Sopenharmony_ci goto out; 78562306a36Sopenharmony_ci } 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci requested_pages = vma_pages(vma); 78862306a36Sopenharmony_ci actual_pages = ((idev->info->mem[mi].addr & ~PAGE_MASK) 78962306a36Sopenharmony_ci + idev->info->mem[mi].size + PAGE_SIZE -1) >> PAGE_SHIFT; 79062306a36Sopenharmony_ci if (requested_pages > actual_pages) { 79162306a36Sopenharmony_ci ret = -EINVAL; 79262306a36Sopenharmony_ci goto out; 79362306a36Sopenharmony_ci } 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_ci if (idev->info->mmap) { 79662306a36Sopenharmony_ci ret = idev->info->mmap(idev->info, vma); 79762306a36Sopenharmony_ci goto out; 79862306a36Sopenharmony_ci } 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci switch (idev->info->mem[mi].memtype) { 80162306a36Sopenharmony_ci case UIO_MEM_IOVA: 80262306a36Sopenharmony_ci case UIO_MEM_PHYS: 80362306a36Sopenharmony_ci ret = uio_mmap_physical(vma); 80462306a36Sopenharmony_ci break; 80562306a36Sopenharmony_ci case UIO_MEM_LOGICAL: 80662306a36Sopenharmony_ci case UIO_MEM_VIRTUAL: 80762306a36Sopenharmony_ci ret = uio_mmap_logical(vma); 80862306a36Sopenharmony_ci break; 80962306a36Sopenharmony_ci default: 81062306a36Sopenharmony_ci ret = -EINVAL; 81162306a36Sopenharmony_ci } 81262306a36Sopenharmony_ci 81362306a36Sopenharmony_ci out: 81462306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 81562306a36Sopenharmony_ci return ret; 81662306a36Sopenharmony_ci} 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_cistatic const struct file_operations uio_fops = { 81962306a36Sopenharmony_ci .owner = THIS_MODULE, 82062306a36Sopenharmony_ci .open = uio_open, 82162306a36Sopenharmony_ci .release = uio_release, 82262306a36Sopenharmony_ci .read = uio_read, 82362306a36Sopenharmony_ci .write = uio_write, 82462306a36Sopenharmony_ci .mmap = uio_mmap, 82562306a36Sopenharmony_ci .poll = uio_poll, 82662306a36Sopenharmony_ci .fasync = uio_fasync, 82762306a36Sopenharmony_ci .llseek = noop_llseek, 82862306a36Sopenharmony_ci}; 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_cistatic int uio_major_init(void) 83162306a36Sopenharmony_ci{ 83262306a36Sopenharmony_ci static const char name[] = "uio"; 83362306a36Sopenharmony_ci struct cdev *cdev = NULL; 83462306a36Sopenharmony_ci dev_t uio_dev = 0; 83562306a36Sopenharmony_ci int result; 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_ci result = alloc_chrdev_region(&uio_dev, 0, UIO_MAX_DEVICES, name); 83862306a36Sopenharmony_ci if (result) 83962306a36Sopenharmony_ci goto out; 84062306a36Sopenharmony_ci 84162306a36Sopenharmony_ci result = -ENOMEM; 84262306a36Sopenharmony_ci cdev = cdev_alloc(); 84362306a36Sopenharmony_ci if (!cdev) 84462306a36Sopenharmony_ci goto out_unregister; 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci cdev->owner = THIS_MODULE; 84762306a36Sopenharmony_ci cdev->ops = &uio_fops; 84862306a36Sopenharmony_ci kobject_set_name(&cdev->kobj, "%s", name); 84962306a36Sopenharmony_ci 85062306a36Sopenharmony_ci result = cdev_add(cdev, uio_dev, UIO_MAX_DEVICES); 85162306a36Sopenharmony_ci if (result) 85262306a36Sopenharmony_ci goto out_put; 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci uio_major = MAJOR(uio_dev); 85562306a36Sopenharmony_ci uio_cdev = cdev; 85662306a36Sopenharmony_ci return 0; 85762306a36Sopenharmony_ciout_put: 85862306a36Sopenharmony_ci kobject_put(&cdev->kobj); 85962306a36Sopenharmony_ciout_unregister: 86062306a36Sopenharmony_ci unregister_chrdev_region(uio_dev, UIO_MAX_DEVICES); 86162306a36Sopenharmony_ciout: 86262306a36Sopenharmony_ci return result; 86362306a36Sopenharmony_ci} 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_cistatic void uio_major_cleanup(void) 86662306a36Sopenharmony_ci{ 86762306a36Sopenharmony_ci unregister_chrdev_region(MKDEV(uio_major, 0), UIO_MAX_DEVICES); 86862306a36Sopenharmony_ci cdev_del(uio_cdev); 86962306a36Sopenharmony_ci} 87062306a36Sopenharmony_ci 87162306a36Sopenharmony_cistatic int init_uio_class(void) 87262306a36Sopenharmony_ci{ 87362306a36Sopenharmony_ci int ret; 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci /* This is the first time in here, set everything up properly */ 87662306a36Sopenharmony_ci ret = uio_major_init(); 87762306a36Sopenharmony_ci if (ret) 87862306a36Sopenharmony_ci goto exit; 87962306a36Sopenharmony_ci 88062306a36Sopenharmony_ci ret = class_register(&uio_class); 88162306a36Sopenharmony_ci if (ret) { 88262306a36Sopenharmony_ci printk(KERN_ERR "class_register failed for uio\n"); 88362306a36Sopenharmony_ci goto err_class_register; 88462306a36Sopenharmony_ci } 88562306a36Sopenharmony_ci 88662306a36Sopenharmony_ci uio_class_registered = true; 88762306a36Sopenharmony_ci 88862306a36Sopenharmony_ci return 0; 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_cierr_class_register: 89162306a36Sopenharmony_ci uio_major_cleanup(); 89262306a36Sopenharmony_ciexit: 89362306a36Sopenharmony_ci return ret; 89462306a36Sopenharmony_ci} 89562306a36Sopenharmony_ci 89662306a36Sopenharmony_cistatic void release_uio_class(void) 89762306a36Sopenharmony_ci{ 89862306a36Sopenharmony_ci uio_class_registered = false; 89962306a36Sopenharmony_ci class_unregister(&uio_class); 90062306a36Sopenharmony_ci uio_major_cleanup(); 90162306a36Sopenharmony_ci} 90262306a36Sopenharmony_ci 90362306a36Sopenharmony_cistatic void uio_device_release(struct device *dev) 90462306a36Sopenharmony_ci{ 90562306a36Sopenharmony_ci struct uio_device *idev = dev_get_drvdata(dev); 90662306a36Sopenharmony_ci 90762306a36Sopenharmony_ci kfree(idev); 90862306a36Sopenharmony_ci} 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci/** 91162306a36Sopenharmony_ci * __uio_register_device - register a new userspace IO device 91262306a36Sopenharmony_ci * @owner: module that creates the new device 91362306a36Sopenharmony_ci * @parent: parent device 91462306a36Sopenharmony_ci * @info: UIO device capabilities 91562306a36Sopenharmony_ci * 91662306a36Sopenharmony_ci * returns zero on success or a negative error code. 91762306a36Sopenharmony_ci */ 91862306a36Sopenharmony_ciint __uio_register_device(struct module *owner, 91962306a36Sopenharmony_ci struct device *parent, 92062306a36Sopenharmony_ci struct uio_info *info) 92162306a36Sopenharmony_ci{ 92262306a36Sopenharmony_ci struct uio_device *idev; 92362306a36Sopenharmony_ci int ret = 0; 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci if (!uio_class_registered) 92662306a36Sopenharmony_ci return -EPROBE_DEFER; 92762306a36Sopenharmony_ci 92862306a36Sopenharmony_ci if (!parent || !info || !info->name || !info->version) 92962306a36Sopenharmony_ci return -EINVAL; 93062306a36Sopenharmony_ci 93162306a36Sopenharmony_ci info->uio_dev = NULL; 93262306a36Sopenharmony_ci 93362306a36Sopenharmony_ci idev = kzalloc(sizeof(*idev), GFP_KERNEL); 93462306a36Sopenharmony_ci if (!idev) { 93562306a36Sopenharmony_ci return -ENOMEM; 93662306a36Sopenharmony_ci } 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_ci idev->owner = owner; 93962306a36Sopenharmony_ci idev->info = info; 94062306a36Sopenharmony_ci mutex_init(&idev->info_lock); 94162306a36Sopenharmony_ci init_waitqueue_head(&idev->wait); 94262306a36Sopenharmony_ci atomic_set(&idev->event, 0); 94362306a36Sopenharmony_ci 94462306a36Sopenharmony_ci ret = uio_get_minor(idev); 94562306a36Sopenharmony_ci if (ret) { 94662306a36Sopenharmony_ci kfree(idev); 94762306a36Sopenharmony_ci return ret; 94862306a36Sopenharmony_ci } 94962306a36Sopenharmony_ci 95062306a36Sopenharmony_ci device_initialize(&idev->dev); 95162306a36Sopenharmony_ci idev->dev.devt = MKDEV(uio_major, idev->minor); 95262306a36Sopenharmony_ci idev->dev.class = &uio_class; 95362306a36Sopenharmony_ci idev->dev.parent = parent; 95462306a36Sopenharmony_ci idev->dev.release = uio_device_release; 95562306a36Sopenharmony_ci dev_set_drvdata(&idev->dev, idev); 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci ret = dev_set_name(&idev->dev, "uio%d", idev->minor); 95862306a36Sopenharmony_ci if (ret) 95962306a36Sopenharmony_ci goto err_device_create; 96062306a36Sopenharmony_ci 96162306a36Sopenharmony_ci ret = device_add(&idev->dev); 96262306a36Sopenharmony_ci if (ret) 96362306a36Sopenharmony_ci goto err_device_create; 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci ret = uio_dev_add_attributes(idev); 96662306a36Sopenharmony_ci if (ret) 96762306a36Sopenharmony_ci goto err_uio_dev_add_attributes; 96862306a36Sopenharmony_ci 96962306a36Sopenharmony_ci info->uio_dev = idev; 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_ci if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) { 97262306a36Sopenharmony_ci /* 97362306a36Sopenharmony_ci * Note that we deliberately don't use devm_request_irq 97462306a36Sopenharmony_ci * here. The parent module can unregister the UIO device 97562306a36Sopenharmony_ci * and call pci_disable_msi, which requires that this 97662306a36Sopenharmony_ci * irq has been freed. However, the device may have open 97762306a36Sopenharmony_ci * FDs at the time of unregister and therefore may not be 97862306a36Sopenharmony_ci * freed until they are released. 97962306a36Sopenharmony_ci */ 98062306a36Sopenharmony_ci ret = request_irq(info->irq, uio_interrupt, 98162306a36Sopenharmony_ci info->irq_flags, info->name, idev); 98262306a36Sopenharmony_ci if (ret) { 98362306a36Sopenharmony_ci info->uio_dev = NULL; 98462306a36Sopenharmony_ci goto err_request_irq; 98562306a36Sopenharmony_ci } 98662306a36Sopenharmony_ci } 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci return 0; 98962306a36Sopenharmony_ci 99062306a36Sopenharmony_cierr_request_irq: 99162306a36Sopenharmony_ci uio_dev_del_attributes(idev); 99262306a36Sopenharmony_cierr_uio_dev_add_attributes: 99362306a36Sopenharmony_ci device_del(&idev->dev); 99462306a36Sopenharmony_cierr_device_create: 99562306a36Sopenharmony_ci uio_free_minor(idev->minor); 99662306a36Sopenharmony_ci put_device(&idev->dev); 99762306a36Sopenharmony_ci return ret; 99862306a36Sopenharmony_ci} 99962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__uio_register_device); 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_cistatic void devm_uio_unregister_device(struct device *dev, void *res) 100262306a36Sopenharmony_ci{ 100362306a36Sopenharmony_ci uio_unregister_device(*(struct uio_info **)res); 100462306a36Sopenharmony_ci} 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_ci/** 100762306a36Sopenharmony_ci * __devm_uio_register_device - Resource managed uio_register_device() 100862306a36Sopenharmony_ci * @owner: module that creates the new device 100962306a36Sopenharmony_ci * @parent: parent device 101062306a36Sopenharmony_ci * @info: UIO device capabilities 101162306a36Sopenharmony_ci * 101262306a36Sopenharmony_ci * returns zero on success or a negative error code. 101362306a36Sopenharmony_ci */ 101462306a36Sopenharmony_ciint __devm_uio_register_device(struct module *owner, 101562306a36Sopenharmony_ci struct device *parent, 101662306a36Sopenharmony_ci struct uio_info *info) 101762306a36Sopenharmony_ci{ 101862306a36Sopenharmony_ci struct uio_info **ptr; 101962306a36Sopenharmony_ci int ret; 102062306a36Sopenharmony_ci 102162306a36Sopenharmony_ci ptr = devres_alloc(devm_uio_unregister_device, sizeof(*ptr), 102262306a36Sopenharmony_ci GFP_KERNEL); 102362306a36Sopenharmony_ci if (!ptr) 102462306a36Sopenharmony_ci return -ENOMEM; 102562306a36Sopenharmony_ci 102662306a36Sopenharmony_ci *ptr = info; 102762306a36Sopenharmony_ci ret = __uio_register_device(owner, parent, info); 102862306a36Sopenharmony_ci if (ret) { 102962306a36Sopenharmony_ci devres_free(ptr); 103062306a36Sopenharmony_ci return ret; 103162306a36Sopenharmony_ci } 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_ci devres_add(parent, ptr); 103462306a36Sopenharmony_ci 103562306a36Sopenharmony_ci return 0; 103662306a36Sopenharmony_ci} 103762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__devm_uio_register_device); 103862306a36Sopenharmony_ci 103962306a36Sopenharmony_ci/** 104062306a36Sopenharmony_ci * uio_unregister_device - unregister a industrial IO device 104162306a36Sopenharmony_ci * @info: UIO device capabilities 104262306a36Sopenharmony_ci * 104362306a36Sopenharmony_ci */ 104462306a36Sopenharmony_civoid uio_unregister_device(struct uio_info *info) 104562306a36Sopenharmony_ci{ 104662306a36Sopenharmony_ci struct uio_device *idev; 104762306a36Sopenharmony_ci unsigned long minor; 104862306a36Sopenharmony_ci 104962306a36Sopenharmony_ci if (!info || !info->uio_dev) 105062306a36Sopenharmony_ci return; 105162306a36Sopenharmony_ci 105262306a36Sopenharmony_ci idev = info->uio_dev; 105362306a36Sopenharmony_ci minor = idev->minor; 105462306a36Sopenharmony_ci 105562306a36Sopenharmony_ci mutex_lock(&idev->info_lock); 105662306a36Sopenharmony_ci uio_dev_del_attributes(idev); 105762306a36Sopenharmony_ci 105862306a36Sopenharmony_ci if (info->irq && info->irq != UIO_IRQ_CUSTOM) 105962306a36Sopenharmony_ci free_irq(info->irq, idev); 106062306a36Sopenharmony_ci 106162306a36Sopenharmony_ci idev->info = NULL; 106262306a36Sopenharmony_ci mutex_unlock(&idev->info_lock); 106362306a36Sopenharmony_ci 106462306a36Sopenharmony_ci wake_up_interruptible(&idev->wait); 106562306a36Sopenharmony_ci kill_fasync(&idev->async_queue, SIGIO, POLL_HUP); 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci uio_free_minor(minor); 106862306a36Sopenharmony_ci device_unregister(&idev->dev); 106962306a36Sopenharmony_ci 107062306a36Sopenharmony_ci return; 107162306a36Sopenharmony_ci} 107262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(uio_unregister_device); 107362306a36Sopenharmony_ci 107462306a36Sopenharmony_cistatic int __init uio_init(void) 107562306a36Sopenharmony_ci{ 107662306a36Sopenharmony_ci return init_uio_class(); 107762306a36Sopenharmony_ci} 107862306a36Sopenharmony_ci 107962306a36Sopenharmony_cistatic void __exit uio_exit(void) 108062306a36Sopenharmony_ci{ 108162306a36Sopenharmony_ci release_uio_class(); 108262306a36Sopenharmony_ci idr_destroy(&uio_idr); 108362306a36Sopenharmony_ci} 108462306a36Sopenharmony_ci 108562306a36Sopenharmony_cimodule_init(uio_init) 108662306a36Sopenharmony_cimodule_exit(uio_exit) 108762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 1088