diff options
author | Maxime Ripard <maxime.ripard@bootlin.com> | 2019-01-11 18:32:10 +0300 |
---|---|---|
committer | Maxime Ripard <maxime.ripard@bootlin.com> | 2019-01-11 18:32:10 +0300 |
commit | 23d19ba06b9c5614d6457f5fed349ec8f6d4dac9 (patch) | |
tree | 39f0b657e5b1b5b958780cae4ae6360f69548d50 /drivers/base | |
parent | 7d0250ed8e69fb6a66caecf60b8753a21224cc1a (diff) | |
parent | e3d093070eb0b5e3df668d3eb04100ea79343c65 (diff) | |
download | linux-23d19ba06b9c5614d6457f5fed349ec8f6d4dac9.tar.xz |
Merge drm/drm-next into drm-misc-next
drm-next has been forwarded to 5.0-rc1, and we need it to apply the damage
helper for dirtyfb series from Noralf Trønnes.
Signed-off-by: Maxime Ripard <maxime.ripard@bootlin.com>
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/Makefile | 2 | ||||
-rw-r--r-- | drivers/base/bus.c | 19 | ||||
-rw-r--r-- | drivers/base/component.c | 12 | ||||
-rw-r--r-- | drivers/base/core.c | 66 | ||||
-rw-r--r-- | drivers/base/dd.c | 19 | ||||
-rw-r--r-- | drivers/base/devres.c | 10 | ||||
-rw-r--r-- | drivers/base/devtmpfs.c | 1 | ||||
-rw-r--r-- | drivers/base/memory.c | 89 | ||||
-rw-r--r-- | drivers/base/platform-msi.c | 6 | ||||
-rw-r--r-- | drivers/base/platform.c | 42 | ||||
-rw-r--r-- | drivers/base/power/domain.c | 343 | ||||
-rw-r--r-- | drivers/base/power/runtime.c | 63 | ||||
-rw-r--r-- | drivers/base/power/trace.c | 4 | ||||
-rw-r--r-- | drivers/base/property.c | 513 | ||||
-rw-r--r-- | drivers/base/regmap/regcache-rbtree.c | 12 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-debugfs.c | 12 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-irq.c | 142 | ||||
-rw-r--r-- | drivers/base/swnode.c | 676 |
18 files changed, 1164 insertions, 867 deletions
diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 704f44295810..157452080f3d 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -6,7 +6,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ cpu.o firmware.o init.o map.o devres.o \ attribute_container.o transport_class.o \ topology.o container.o property.o cacheinfo.o \ - devcon.o + devcon.o swnode.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-y += power/ obj-$(CONFIG_ISA_BUS_API) += isa.o diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 8bfd27ec73d6..e06a57936cc9 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -31,6 +31,9 @@ static struct kset *system_kset; #define to_drv_attr(_attr) container_of(_attr, struct driver_attribute, attr) +#define DRIVER_ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) \ + struct driver_attribute driver_attr_##_name = \ + __ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) static int __must_check bus_rescan_devices_helper(struct device *dev, void *data); @@ -195,7 +198,7 @@ static ssize_t unbind_store(struct device_driver *drv, const char *buf, bus_put(bus); return err; } -static DRIVER_ATTR_WO(unbind); +static DRIVER_ATTR_IGNORE_LOCKDEP(unbind, S_IWUSR, NULL, unbind_store); /* * Manually attach a device to a driver. @@ -231,7 +234,7 @@ static ssize_t bind_store(struct device_driver *drv, const char *buf, bus_put(bus); return err; } -static DRIVER_ATTR_WO(bind); +static DRIVER_ATTR_IGNORE_LOCKDEP(bind, S_IWUSR, NULL, bind_store); static ssize_t show_drivers_autoprobe(struct bus_type *bus, char *buf) { @@ -611,8 +614,10 @@ static void remove_probe_files(struct bus_type *bus) static ssize_t uevent_store(struct device_driver *drv, const char *buf, size_t count) { - kobject_synth_uevent(&drv->p->kobj, buf, count); - return count; + int rc; + + rc = kobject_synth_uevent(&drv->p->kobj, buf, count); + return rc ? rc : count; } static DRIVER_ATTR_WO(uevent); @@ -828,8 +833,10 @@ static void klist_devices_put(struct klist_node *n) static ssize_t bus_uevent_store(struct bus_type *bus, const char *buf, size_t count) { - kobject_synth_uevent(&bus->p->subsys.kobj, buf, count); - return count; + int rc; + + rc = kobject_synth_uevent(&bus->p->subsys.kobj, buf, count); + return rc ? rc : count; } static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store); diff --git a/drivers/base/component.c b/drivers/base/component.c index e8d676fad0c9..ddcea8739c12 100644 --- a/drivers/base/component.c +++ b/drivers/base/component.c @@ -85,17 +85,7 @@ static int component_devices_show(struct seq_file *s, void *data) return 0; } -static int component_devices_open(struct inode *inode, struct file *file) -{ - return single_open(file, component_devices_show, inode->i_private); -} - -static const struct file_operations component_devices_fops = { - .open = component_devices_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(component_devices); static int __init component_debug_init(void) { diff --git a/drivers/base/core.c b/drivers/base/core.c index 04bbcd779e11..0073b09bb99f 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -8,6 +8,7 @@ * Copyright (c) 2006 Novell, Inc. */ +#include <linux/acpi.h> #include <linux/device.h> #include <linux/err.h> #include <linux/fwnode.h> @@ -728,6 +729,26 @@ static inline int device_is_not_partition(struct device *dev) } #endif +static int +device_platform_notify(struct device *dev, enum kobject_action action) +{ + int ret; + + ret = acpi_platform_notify(dev, action); + if (ret) + return ret; + + ret = software_node_notify(dev, action); + if (ret) + return ret; + + if (platform_notify && action == KOBJ_ADD) + platform_notify(dev); + else if (platform_notify_remove && action == KOBJ_REMOVE) + platform_notify_remove(dev); + return 0; +} + /** * dev_driver_string - Return a device's driver name, if at all possible * @dev: struct device to get the name of @@ -794,10 +815,12 @@ ssize_t device_store_ulong(struct device *dev, const char *buf, size_t size) { struct dev_ext_attribute *ea = to_ext_attr(attr); - char *end; - unsigned long new = simple_strtoul(buf, &end, 0); - if (end == buf) - return -EINVAL; + int ret; + unsigned long new; + + ret = kstrtoul(buf, 0, &new); + if (ret) + return ret; *(unsigned long *)(ea->var) = new; /* Always return full write size even if we didn't consume all */ return size; @@ -818,9 +841,14 @@ ssize_t device_store_int(struct device *dev, const char *buf, size_t size) { struct dev_ext_attribute *ea = to_ext_attr(attr); - char *end; - long new = simple_strtol(buf, &end, 0); - if (end == buf || new > INT_MAX || new < INT_MIN) + int ret; + long new; + + ret = kstrtol(buf, 0, &new); + if (ret) + return ret; + + if (new > INT_MAX || new < INT_MIN) return -EINVAL; *(int *)(ea->var) = new; /* Always return full write size even if we didn't consume all */ @@ -890,8 +918,7 @@ static void device_release(struct kobject *kobj) else if (dev->class && dev->class->dev_release) dev->class->dev_release(dev); else - WARN(1, KERN_ERR "Device '%s' does not have a release() " - "function, it is broken and must be fixed.\n", + WARN(1, KERN_ERR "Device '%s' does not have a release() function, it is broken and must be fixed. See Documentation/kobject.txt.\n", dev_name(dev)); kfree(p); } @@ -1067,8 +1094,14 @@ out: static ssize_t uevent_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - if (kobject_synth_uevent(&dev->kobj, buf, count)) + int rc; + + rc = kobject_synth_uevent(&dev->kobj, buf, count); + + if (rc) { dev_err(dev, "uevent: failed to send synthetic uevent\n"); + return rc; + } return count; } @@ -1883,8 +1916,9 @@ int device_add(struct device *dev) } /* notify platform of device entry */ - if (platform_notify) - platform_notify(dev); + error = device_platform_notify(dev, KOBJ_ADD); + if (error) + goto platform_error; error = device_create_file(dev, &dev_attr_uevent); if (error) @@ -1960,6 +1994,8 @@ done: SymlinkError: device_remove_file(dev, &dev_attr_uevent); attrError: + device_platform_notify(dev, KOBJ_REMOVE); +platform_error: kobject_uevent(&dev->kobj, KOBJ_REMOVE); glue_dir = get_glue_dir(dev); kobject_del(&dev->kobj); @@ -2077,14 +2113,10 @@ void device_del(struct device *dev) bus_remove_device(dev); device_pm_remove(dev); driver_deferred_probe_del(dev); + device_platform_notify(dev, KOBJ_REMOVE); device_remove_properties(dev); device_links_purge(dev); - /* Notify the platform of the removal, in case they - * need to do anything... - */ - if (platform_notify_remove) - platform_notify_remove(dev); if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_REMOVED_DEVICE, dev); diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 169412ee4ae8..8ac10af17c00 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -179,7 +179,7 @@ static void driver_deferred_probe_trigger(void) } /** - * device_block_probing() - Block/defere device's probes + * device_block_probing() - Block/defer device's probes * * It will disable probing of devices and defer their probes instead. */ @@ -223,7 +223,10 @@ DEFINE_SHOW_ATTRIBUTE(deferred_devs); static int deferred_probe_timeout = -1; static int __init deferred_probe_timeout_setup(char *str) { - deferred_probe_timeout = simple_strtol(str, NULL, 10); + int timeout; + + if (!kstrtoint(str, 10, &timeout)) + deferred_probe_timeout = timeout; return 1; } __setup("deferred_probe_timeout=", deferred_probe_timeout_setup); @@ -453,7 +456,7 @@ static int really_probe(struct device *dev, struct device_driver *drv) if (defer_all_probes) { /* * Value of defer_all_probes can be set only by - * device_defer_all_probes_enable() which, in turn, will call + * device_block_probing() which, in turn, will call * wait_for_device_probe() right after that to avoid any races. */ dev_dbg(dev, "Driver %s force probe deferral\n", drv->name); @@ -928,16 +931,13 @@ static void __device_release_driver(struct device *dev, struct device *parent) drv = dev->driver; if (drv) { - if (driver_allows_async_probing(drv)) - async_synchronize_full(); - while (device_links_busy(dev)) { device_unlock(dev); - if (parent) + if (parent && dev->bus->need_parent_lock) device_unlock(parent); device_links_unbind_consumers(dev); - if (parent) + if (parent && dev->bus->need_parent_lock) device_lock(parent); device_lock(dev); @@ -1036,6 +1036,9 @@ void driver_detach(struct device_driver *drv) struct device_private *dev_prv; struct device *dev; + if (driver_allows_async_probing(drv)) + async_synchronize_full(); + for (;;) { spin_lock(&drv->p->klist_devices.k_lock); if (list_empty(&drv->p->klist_devices.k_list)) { diff --git a/drivers/base/devres.c b/drivers/base/devres.c index 4aaf00d2098b..e038e2b3b7ea 100644 --- a/drivers/base/devres.c +++ b/drivers/base/devres.c @@ -26,8 +26,14 @@ struct devres_node { struct devres { struct devres_node node; - /* -- 3 pointers */ - unsigned long long data[]; /* guarantee ull alignment */ + /* + * Some archs want to perform DMA into kmalloc caches + * and need a guaranteed alignment larger than + * the alignment of a 64-bit integer. + * Thus we use ARCH_KMALLOC_MINALIGN here and get exactly the same + * buffer alignment as if it was allocated by plain kmalloc(). + */ + u8 __aligned(ARCH_KMALLOC_MINALIGN) data[]; }; struct devres_group { diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index b93fc862d365..0dbc43068eeb 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -25,6 +25,7 @@ #include <linux/sched.h> #include <linux/slab.h> #include <linux/kthread.h> +#include <uapi/linux/mount.h> #include "base.h" static struct task_struct *thread; diff --git a/drivers/base/memory.c b/drivers/base/memory.c index 0e5985682642..048cbf7d5233 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -109,8 +109,8 @@ static unsigned long get_memory_block_size(void) * uses. */ -static ssize_t show_mem_start_phys_index(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t phys_index_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct memory_block *mem = to_memory_block(dev); unsigned long phys_index; @@ -122,8 +122,8 @@ static ssize_t show_mem_start_phys_index(struct device *dev, /* * Show whether the section of memory is likely to be hot-removable */ -static ssize_t show_mem_removable(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t removable_show(struct device *dev, struct device_attribute *attr, + char *buf) { unsigned long i, pfn; int ret = 1; @@ -146,8 +146,8 @@ out: /* * online, offline, going offline, etc. */ -static ssize_t show_mem_state(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct memory_block *mem = to_memory_block(dev); ssize_t len = 0; @@ -207,15 +207,15 @@ static bool pages_correctly_probed(unsigned long start_pfn) return false; if (!present_section_nr(section_nr)) { - pr_warn("section %ld pfn[%lx, %lx) not present", + pr_warn("section %ld pfn[%lx, %lx) not present\n", section_nr, pfn, pfn + PAGES_PER_SECTION); return false; } else if (!valid_section_nr(section_nr)) { - pr_warn("section %ld pfn[%lx, %lx) no valid memmap", + pr_warn("section %ld pfn[%lx, %lx) no valid memmap\n", section_nr, pfn, pfn + PAGES_PER_SECTION); return false; } else if (online_section_nr(section_nr)) { - pr_warn("section %ld pfn[%lx, %lx) is already online", + pr_warn("section %ld pfn[%lx, %lx) is already online\n", section_nr, pfn, pfn + PAGES_PER_SECTION); return false; } @@ -286,7 +286,7 @@ static int memory_subsys_online(struct device *dev) return 0; /* - * If we are called from store_mem_state(), online_type will be + * If we are called from state_store(), online_type will be * set >= 0 Otherwise we were called from the device online * attribute and need to set the online_type. */ @@ -315,9 +315,8 @@ static int memory_subsys_offline(struct device *dev) return memory_block_change_state(mem, MEM_OFFLINE, MEM_ONLINE); } -static ssize_t -store_mem_state(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) +static ssize_t state_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { struct memory_block *mem = to_memory_block(dev); int ret, online_type; @@ -374,7 +373,7 @@ err: * s.t. if I offline all of these sections I can then * remove the physical device? */ -static ssize_t show_phys_device(struct device *dev, +static ssize_t phys_device_show(struct device *dev, struct device_attribute *attr, char *buf) { struct memory_block *mem = to_memory_block(dev); @@ -395,7 +394,7 @@ static void print_allowed_zone(char *buf, int nid, unsigned long start_pfn, } } -static ssize_t show_valid_zones(struct device *dev, +static ssize_t valid_zones_show(struct device *dev, struct device_attribute *attr, char *buf) { struct memory_block *mem = to_memory_block(dev); @@ -435,33 +434,31 @@ out: return strlen(buf); } -static DEVICE_ATTR(valid_zones, 0444, show_valid_zones, NULL); +static DEVICE_ATTR_RO(valid_zones); #endif -static DEVICE_ATTR(phys_index, 0444, show_mem_start_phys_index, NULL); -static DEVICE_ATTR(state, 0644, show_mem_state, store_mem_state); -static DEVICE_ATTR(phys_device, 0444, show_phys_device, NULL); -static DEVICE_ATTR(removable, 0444, show_mem_removable, NULL); +static DEVICE_ATTR_RO(phys_index); +static DEVICE_ATTR_RW(state); +static DEVICE_ATTR_RO(phys_device); +static DEVICE_ATTR_RO(removable); /* * Block size attribute stuff */ -static ssize_t -print_block_size(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t block_size_bytes_show(struct device *dev, + struct device_attribute *attr, char *buf) { return sprintf(buf, "%lx\n", get_memory_block_size()); } -static DEVICE_ATTR(block_size_bytes, 0444, print_block_size, NULL); +static DEVICE_ATTR_RO(block_size_bytes); /* * Memory auto online policy. */ -static ssize_t -show_auto_online_blocks(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t auto_online_blocks_show(struct device *dev, + struct device_attribute *attr, char *buf) { if (memhp_auto_online) return sprintf(buf, "online\n"); @@ -469,9 +466,9 @@ show_auto_online_blocks(struct device *dev, struct device_attribute *attr, return sprintf(buf, "offline\n"); } -static ssize_t -store_auto_online_blocks(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t auto_online_blocks_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { if (sysfs_streq(buf, "online")) memhp_auto_online = true; @@ -483,8 +480,7 @@ store_auto_online_blocks(struct device *dev, struct device_attribute *attr, return count; } -static DEVICE_ATTR(auto_online_blocks, 0644, show_auto_online_blocks, - store_auto_online_blocks); +static DEVICE_ATTR_RW(auto_online_blocks); /* * Some architectures will have custom drivers to do this, and @@ -493,9 +489,8 @@ static DEVICE_ATTR(auto_online_blocks, 0644, show_auto_online_blocks, * and will require this interface. */ #ifdef CONFIG_ARCH_MEMORY_PROBE -static ssize_t -memory_probe_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t probe_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { u64 phys_addr; int nid, ret; @@ -525,7 +520,7 @@ out: return ret; } -static DEVICE_ATTR(probe, S_IWUSR, NULL, memory_probe_store); +static DEVICE_ATTR_WO(probe); #endif #ifdef CONFIG_MEMORY_FAILURE @@ -534,10 +529,9 @@ static DEVICE_ATTR(probe, S_IWUSR, NULL, memory_probe_store); */ /* Soft offline a page */ -static ssize_t -store_soft_offline_page(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t soft_offline_page_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { int ret; u64 pfn; @@ -553,10 +547,9 @@ store_soft_offline_page(struct device *dev, } /* Forcibly offline a page, including killing processes. */ -static ssize_t -store_hard_offline_page(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t hard_offline_page_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { int ret; u64 pfn; @@ -569,8 +562,8 @@ store_hard_offline_page(struct device *dev, return ret ? ret : count; } -static DEVICE_ATTR(soft_offline_page, S_IWUSR, NULL, store_soft_offline_page); -static DEVICE_ATTR(hard_offline_page, S_IWUSR, NULL, store_hard_offline_page); +static DEVICE_ATTR_WO(soft_offline_page); +static DEVICE_ATTR_WO(hard_offline_page); #endif /* @@ -688,7 +681,7 @@ static int add_memory_block(int base_section_nr) int i, ret, section_count = 0, section_nr; for (i = base_section_nr; - (i < base_section_nr + sections_per_block) && i < NR_MEM_SECTIONS; + i < base_section_nr + sections_per_block; i++) { if (!present_section_nr(i)) continue; @@ -739,7 +732,7 @@ unregister_memory(struct memory_block *memory) { BUG_ON(memory->dev.bus != &memory_subsys); - /* drop the ref. we got in remove_memory_block() */ + /* drop the ref. we got in remove_memory_section() */ put_device(&memory->dev); device_unregister(&memory->dev); } diff --git a/drivers/base/platform-msi.c b/drivers/base/platform-msi.c index f39a920496fb..8da314b81eab 100644 --- a/drivers/base/platform-msi.c +++ b/drivers/base/platform-msi.c @@ -368,14 +368,16 @@ void platform_msi_domain_free(struct irq_domain *domain, unsigned int virq, unsigned int nvec) { struct platform_msi_priv_data *data = domain->host_data; - struct msi_desc *desc; - for_each_msi_entry(desc, data->dev) { + struct msi_desc *desc, *tmp; + for_each_msi_entry_safe(desc, tmp, data->dev) { if (WARN_ON(!desc->irq || desc->nvec_used != 1)) return; if (!(desc->irq >= virq && desc->irq < (virq + nvec))) continue; irq_domain_free_irqs_common(domain, desc->irq, 1); + list_del(&desc->list); + free_msi_entry(desc); } } diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 41b91af95afb..1c958eb33ef4 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -26,6 +26,7 @@ #include <linux/clk/clk-conf.h> #include <linux/limits.h> #include <linux/property.h> +#include <linux/kmemleak.h> #include "base.h" #include "power/power.h" @@ -234,7 +235,7 @@ struct platform_object { */ void platform_device_put(struct platform_device *pdev) { - if (pdev) + if (!IS_ERR_OR_NULL(pdev)) put_device(&pdev->dev); } EXPORT_SYMBOL_GPL(platform_device_put); @@ -447,8 +448,7 @@ void platform_device_del(struct platform_device *pdev) { int i; - if (pdev) { - device_remove_properties(&pdev->dev); + if (!IS_ERR_OR_NULL(pdev)) { device_del(&pdev->dev); if (pdev->id_auto) { @@ -525,6 +525,8 @@ struct platform_device *platform_device_register_full( if (!pdev->dev.dma_mask) goto err; + kmemleak_ignore(pdev->dev.dma_mask); + *pdev->dev.dma_mask = pdevinfo->dma_mask; pdev->dev.coherent_dma_mask = pdevinfo->dma_mask; } @@ -1138,8 +1140,7 @@ int platform_dma_configure(struct device *dev) ret = of_dma_configure(dev, dev->of_node, true); } else if (has_acpi_companion(dev)) { attr = acpi_get_dma_attr(to_acpi_device_node(dev->fwnode)); - if (attr != DEV_DMA_NOT_SUPPORTED) - ret = acpi_dma_configure(dev, attr); + ret = acpi_dma_configure(dev, attr); } return ret; @@ -1179,37 +1180,6 @@ int __init platform_bus_init(void) return error; } -#ifndef ARCH_HAS_DMA_GET_REQUIRED_MASK -static u64 dma_default_get_required_mask(struct device *dev) -{ - u32 low_totalram = ((max_pfn - 1) << PAGE_SHIFT); - u32 high_totalram = ((max_pfn - 1) >> (32 - PAGE_SHIFT)); - u64 mask; - - if (!high_totalram) { - /* convert to mask just covering totalram */ - low_totalram = (1 << (fls(low_totalram) - 1)); - low_totalram += low_totalram - 1; - mask = low_totalram; - } else { - high_totalram = (1 << (fls(high_totalram) - 1)); - high_totalram += high_totalram - 1; - mask = (((u64)high_totalram) << 32) + 0xffffffff; - } - return mask; -} - -u64 dma_get_required_mask(struct device *dev) -{ - const struct dma_map_ops *ops = get_dma_ops(dev); - - if (ops->get_required_mask) - return ops->get_required_mask(dev); - return dma_default_get_required_mask(dev); -} -EXPORT_SYMBOL_GPL(dma_get_required_mask); -#endif - static __initdata LIST_HEAD(early_platform_driver_list); static __initdata LIST_HEAD(early_platform_device_list); diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 7f38a92b444a..500de1dee967 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -239,6 +239,127 @@ static void genpd_update_accounting(struct generic_pm_domain *genpd) static inline void genpd_update_accounting(struct generic_pm_domain *genpd) {} #endif +static int _genpd_reeval_performance_state(struct generic_pm_domain *genpd, + unsigned int state) +{ + struct generic_pm_domain_data *pd_data; + struct pm_domain_data *pdd; + struct gpd_link *link; + + /* New requested state is same as Max requested state */ + if (state == genpd->performance_state) + return state; + + /* New requested state is higher than Max requested state */ + if (state > genpd->performance_state) + return state; + + /* Traverse all devices within the domain */ + list_for_each_entry(pdd, &genpd->dev_list, list_node) { + pd_data = to_gpd_data(pdd); + + if (pd_data->performance_state > state) + state = pd_data->performance_state; + } + + /* + * Traverse all sub-domains within the domain. This can be + * done without any additional locking as the link->performance_state + * field is protected by the master genpd->lock, which is already taken. + * + * Also note that link->performance_state (subdomain's performance state + * requirement to master domain) is different from + * link->slave->performance_state (current performance state requirement + * of the devices/sub-domains of the subdomain) and so can have a + * different value. + * + * Note that we also take vote from powered-off sub-domains into account + * as the same is done for devices right now. + */ + list_for_each_entry(link, &genpd->master_links, master_node) { + if (link->performance_state > state) + state = link->performance_state; + } + + return state; +} + +static int _genpd_set_performance_state(struct generic_pm_domain *genpd, + unsigned int state, int depth) +{ + struct generic_pm_domain *master; + struct gpd_link *link; + int master_state, ret; + + if (state == genpd->performance_state) + return 0; + + /* Propagate to masters of genpd */ + list_for_each_entry(link, &genpd->slave_links, slave_node) { + master = link->master; + + if (!master->set_performance_state) + continue; + + /* Find master's performance state */ + ret = dev_pm_opp_xlate_performance_state(genpd->opp_table, + master->opp_table, + state); + if (unlikely(ret < 0)) + goto err; + + master_state = ret; + + genpd_lock_nested(master, depth + 1); + + link->prev_performance_state = link->performance_state; + link->performance_state = master_state; + master_state = _genpd_reeval_performance_state(master, + master_state); + ret = _genpd_set_performance_state(master, master_state, depth + 1); + if (ret) + link->performance_state = link->prev_performance_state; + + genpd_unlock(master); + + if (ret) + goto err; + } + + ret = genpd->set_performance_state(genpd, state); + if (ret) + goto err; + + genpd->performance_state = state; + return 0; + +err: + /* Encountered an error, lets rollback */ + list_for_each_entry_continue_reverse(link, &genpd->slave_links, + slave_node) { + master = link->master; + + if (!master->set_performance_state) + continue; + + genpd_lock_nested(master, depth + 1); + + master_state = link->prev_performance_state; + link->performance_state = master_state; + + master_state = _genpd_reeval_performance_state(master, + master_state); + if (_genpd_set_performance_state(master, master_state, depth + 1)) { + pr_err("%s: Failed to roll back to %d performance state\n", + master->name, master_state); + } + + genpd_unlock(master); + } + + return ret; +} + /** * dev_pm_genpd_set_performance_state- Set performance state of device's power * domain. @@ -257,10 +378,9 @@ static inline void genpd_update_accounting(struct generic_pm_domain *genpd) {} int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state) { struct generic_pm_domain *genpd; - struct generic_pm_domain_data *gpd_data, *pd_data; - struct pm_domain_data *pdd; + struct generic_pm_domain_data *gpd_data; unsigned int prev; - int ret = 0; + int ret; genpd = dev_to_genpd(dev); if (IS_ERR(genpd)) @@ -281,47 +401,11 @@ int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state) prev = gpd_data->performance_state; gpd_data->performance_state = state; - /* New requested state is same as Max requested state */ - if (state == genpd->performance_state) - goto unlock; - - /* New requested state is higher than Max requested state */ - if (state > genpd->performance_state) - goto update_state; - - /* Traverse all devices within the domain */ - list_for_each_entry(pdd, &genpd->dev_list, list_node) { - pd_data = to_gpd_data(pdd); - - if (pd_data->performance_state > state) - state = pd_data->performance_state; - } - - if (state == genpd->performance_state) - goto unlock; - - /* - * We aren't propagating performance state changes of a subdomain to its - * masters as we don't have hardware that needs it. Over that, the - * performance states of subdomain and its masters may not have - * one-to-one mapping and would require additional information. We can - * get back to this once we have hardware that needs it. For that - * reason, we don't have to consider performance state of the subdomains - * of genpd here. - */ - -update_state: - if (genpd_status_on(genpd)) { - ret = genpd->set_performance_state(genpd, state); - if (ret) { - gpd_data->performance_state = prev; - goto unlock; - } - } - - genpd->performance_state = state; + state = _genpd_reeval_performance_state(genpd, state); + ret = _genpd_set_performance_state(genpd, state, 0); + if (ret) + gpd_data->performance_state = prev; -unlock: genpd_unlock(genpd); return ret; @@ -347,15 +431,6 @@ static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed) return ret; elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); - - if (unlikely(genpd->set_performance_state)) { - ret = genpd->set_performance_state(genpd, genpd->performance_state); - if (ret) { - pr_warn("%s: Failed to set performance state %d (%d)\n", - genpd->name, genpd->performance_state, ret); - } - } - if (elapsed_ns <= genpd->states[state_idx].power_on_latency_ns) return ret; @@ -1907,12 +1982,21 @@ int of_genpd_add_provider_simple(struct device_node *np, ret); goto unlock; } + + /* + * Save table for faster processing while setting performance + * state. + */ + genpd->opp_table = dev_pm_opp_get_opp_table(&genpd->dev); + WARN_ON(!genpd->opp_table); } ret = genpd_add_provider(np, genpd_xlate_simple, genpd); if (ret) { - if (genpd->set_performance_state) + if (genpd->set_performance_state) { + dev_pm_opp_put_opp_table(genpd->opp_table); dev_pm_opp_of_remove_table(&genpd->dev); + } goto unlock; } @@ -1965,6 +2049,13 @@ int of_genpd_add_provider_onecell(struct device_node *np, i, ret); goto error; } + + /* + * Save table for faster processing while setting + * performance state. + */ + genpd->opp_table = dev_pm_opp_get_opp_table_indexed(&genpd->dev, i); + WARN_ON(!genpd->opp_table); } genpd->provider = &np->fwnode; @@ -1989,8 +2080,10 @@ error: genpd->provider = NULL; genpd->has_provider = false; - if (genpd->set_performance_state) + if (genpd->set_performance_state) { + dev_pm_opp_put_opp_table(genpd->opp_table); dev_pm_opp_of_remove_table(&genpd->dev); + } } mutex_unlock(&gpd_list_lock); @@ -2024,6 +2117,7 @@ void of_genpd_del_provider(struct device_node *np) if (!gpd->set_performance_state) continue; + dev_pm_opp_put_opp_table(gpd->opp_table); dev_pm_opp_of_remove_table(&gpd->dev); } } @@ -2338,7 +2432,7 @@ EXPORT_SYMBOL_GPL(genpd_dev_pm_attach); struct device *genpd_dev_pm_attach_by_id(struct device *dev, unsigned int index) { - struct device *genpd_dev; + struct device *virt_dev; int num_domains; int ret; @@ -2352,31 +2446,31 @@ struct device *genpd_dev_pm_attach_by_id(struct device *dev, return NULL; /* Allocate and register device on the genpd bus. */ - genpd_dev = kzalloc(sizeof(*genpd_dev), GFP_KERNEL); - if (!genpd_dev) + virt_dev = kzalloc(sizeof(*virt_dev), GFP_KERNEL); + if (!virt_dev) return ERR_PTR(-ENOMEM); - dev_set_name(genpd_dev, "genpd:%u:%s", index, dev_name(dev)); - genpd_dev->bus = &genpd_bus_type; - genpd_dev->release = genpd_release_dev; + dev_set_name(virt_dev, "genpd:%u:%s", index, dev_name(dev)); + virt_dev->bus = &genpd_bus_type; + virt_dev->release = genpd_release_dev; - ret = device_register(genpd_dev); + ret = device_register(virt_dev); if (ret) { - kfree(genpd_dev); + kfree(virt_dev); return ERR_PTR(ret); } /* Try to attach the device to the PM domain at the specified index. */ - ret = __genpd_dev_pm_attach(genpd_dev, dev->of_node, index, false); + ret = __genpd_dev_pm_attach(virt_dev, dev->of_node, index, false); if (ret < 1) { - device_unregister(genpd_dev); + device_unregister(virt_dev); return ret ? ERR_PTR(ret) : NULL; } - pm_runtime_enable(genpd_dev); - genpd_queue_power_off_work(dev_to_genpd(genpd_dev)); + pm_runtime_enable(virt_dev); + genpd_queue_power_off_work(dev_to_genpd(virt_dev)); - return genpd_dev; + return virt_dev; } EXPORT_SYMBOL_GPL(genpd_dev_pm_attach_by_id); @@ -2521,52 +2615,36 @@ int of_genpd_parse_idle_states(struct device_node *dn, EXPORT_SYMBOL_GPL(of_genpd_parse_idle_states); /** - * of_genpd_opp_to_performance_state- Gets performance state of device's - * power domain corresponding to a DT node's "required-opps" property. + * pm_genpd_opp_to_performance_state - Gets performance state of the genpd from its OPP node. * - * @dev: Device for which the performance-state needs to be found. - * @np: DT node where the "required-opps" property is present. This can be - * the device node itself (if it doesn't have an OPP table) or a node - * within the OPP table of a device (if device has an OPP table). + * @genpd_dev: Genpd's device for which the performance-state needs to be found. + * @opp: struct dev_pm_opp of the OPP for which we need to find performance + * state. * - * Returns performance state corresponding to the "required-opps" property of - * a DT node. This calls platform specific genpd->opp_to_performance_state() - * callback to translate power domain OPP to performance state. + * Returns performance state encoded in the OPP of the genpd. This calls + * platform specific genpd->opp_to_performance_state() callback to translate + * power domain OPP to performance state. * * Returns performance state on success and 0 on failure. */ -unsigned int of_genpd_opp_to_performance_state(struct device *dev, - struct device_node *np) +unsigned int pm_genpd_opp_to_performance_state(struct device *genpd_dev, + struct dev_pm_opp *opp) { - struct generic_pm_domain *genpd; - struct dev_pm_opp *opp; - int state = 0; + struct generic_pm_domain *genpd = NULL; + int state; - genpd = dev_to_genpd(dev); - if (IS_ERR(genpd)) - return 0; + genpd = container_of(genpd_dev, struct generic_pm_domain, dev); - if (unlikely(!genpd->set_performance_state)) + if (unlikely(!genpd->opp_to_performance_state)) return 0; genpd_lock(genpd); - - opp = of_dev_pm_opp_find_required_opp(&genpd->dev, np); - if (IS_ERR(opp)) { - dev_err(dev, "Failed to find required OPP: %ld\n", - PTR_ERR(opp)); - goto unlock; - } - state = genpd->opp_to_performance_state(genpd, opp); - dev_pm_opp_put(opp); - -unlock: genpd_unlock(genpd); return state; } -EXPORT_SYMBOL_GPL(of_genpd_opp_to_performance_state); +EXPORT_SYMBOL_GPL(pm_genpd_opp_to_performance_state); static int __init genpd_bus_init(void) { @@ -2671,7 +2749,7 @@ exit: return 0; } -static int genpd_summary_show(struct seq_file *s, void *data) +static int summary_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd; int ret = 0; @@ -2694,7 +2772,7 @@ static int genpd_summary_show(struct seq_file *s, void *data) return ret; } -static int genpd_status_show(struct seq_file *s, void *data) +static int status_show(struct seq_file *s, void *data) { static const char * const status_lookup[] = { [GPD_STATE_ACTIVE] = "on", @@ -2721,7 +2799,7 @@ exit: return ret; } -static int genpd_sub_domains_show(struct seq_file *s, void *data) +static int sub_domains_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; struct gpd_link *link; @@ -2738,7 +2816,7 @@ static int genpd_sub_domains_show(struct seq_file *s, void *data) return ret; } -static int genpd_idle_states_show(struct seq_file *s, void *data) +static int idle_states_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; unsigned int i; @@ -2767,7 +2845,7 @@ static int genpd_idle_states_show(struct seq_file *s, void *data) return ret; } -static int genpd_active_time_show(struct seq_file *s, void *data) +static int active_time_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; ktime_t delta = 0; @@ -2787,7 +2865,7 @@ static int genpd_active_time_show(struct seq_file *s, void *data) return ret; } -static int genpd_total_idle_time_show(struct seq_file *s, void *data) +static int total_idle_time_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; ktime_t delta = 0, total = 0; @@ -2815,7 +2893,7 @@ static int genpd_total_idle_time_show(struct seq_file *s, void *data) } -static int genpd_devices_show(struct seq_file *s, void *data) +static int devices_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; struct pm_domain_data *pm_data; @@ -2841,7 +2919,7 @@ static int genpd_devices_show(struct seq_file *s, void *data) return ret; } -static int genpd_perf_state_show(struct seq_file *s, void *data) +static int perf_state_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; @@ -2854,37 +2932,14 @@ static int genpd_perf_state_show(struct seq_file *s, void *data) return 0; } -#define define_genpd_open_function(name) \ -static int genpd_##name##_open(struct inode *inode, struct file *file) \ -{ \ - return single_open(file, genpd_##name##_show, inode->i_private); \ -} - -define_genpd_open_function(summary); -define_genpd_open_function(status); -define_genpd_open_function(sub_domains); -define_genpd_open_function(idle_states); -define_genpd_open_function(active_time); -define_genpd_open_function(total_idle_time); -define_genpd_open_function(devices); -define_genpd_open_function(perf_state); - -#define define_genpd_debugfs_fops(name) \ -static const struct file_operations genpd_##name##_fops = { \ - .open = genpd_##name##_open, \ - .read = seq_read, \ - .llseek = seq_lseek, \ - .release = single_release, \ -} - -define_genpd_debugfs_fops(summary); -define_genpd_debugfs_fops(status); -define_genpd_debugfs_fops(sub_domains); -define_genpd_debugfs_fops(idle_states); -define_genpd_debugfs_fops(active_time); -define_genpd_debugfs_fops(total_idle_time); -define_genpd_debugfs_fops(devices); -define_genpd_debugfs_fops(perf_state); +DEFINE_SHOW_ATTRIBUTE(summary); +DEFINE_SHOW_ATTRIBUTE(status); +DEFINE_SHOW_ATTRIBUTE(sub_domains); +DEFINE_SHOW_ATTRIBUTE(idle_states); +DEFINE_SHOW_ATTRIBUTE(active_time); +DEFINE_SHOW_ATTRIBUTE(total_idle_time); +DEFINE_SHOW_ATTRIBUTE(devices); +DEFINE_SHOW_ATTRIBUTE(perf_state); static int __init genpd_debug_init(void) { @@ -2897,7 +2952,7 @@ static int __init genpd_debug_init(void) return -ENOMEM; d = debugfs_create_file("pm_genpd_summary", S_IRUGO, - genpd_debugfs_dir, NULL, &genpd_summary_fops); + genpd_debugfs_dir, NULL, &summary_fops); if (!d) return -ENOMEM; @@ -2907,20 +2962,20 @@ static int __init genpd_debug_init(void) return -ENOMEM; debugfs_create_file("current_state", 0444, - d, genpd, &genpd_status_fops); + d, genpd, &status_fops); debugfs_create_file("sub_domains", 0444, - d, genpd, &genpd_sub_domains_fops); + d, genpd, &sub_domains_fops); debugfs_create_file("idle_states", 0444, - d, genpd, &genpd_idle_states_fops); + d, genpd, &idle_states_fops); debugfs_create_file("active_time", 0444, - d, genpd, &genpd_active_time_fops); + d, genpd, &active_time_fops); debugfs_create_file("total_idle_time", 0444, - d, genpd, &genpd_total_idle_time_fops); + d, genpd, &total_idle_time_fops); debugfs_create_file("devices", 0444, - d, genpd, &genpd_devices_fops); + d, genpd, &devices_fops); if (genpd->set_performance_state) debugfs_create_file("perf_state", 0444, - d, genpd, &genpd_perf_state_fops); + d, genpd, &perf_state_fops); } return 0; diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index beb85c31f3fa..70624695b6d5 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -8,6 +8,8 @@ */ #include <linux/sched/mm.h> +#include <linux/ktime.h> +#include <linux/hrtimer.h> #include <linux/export.h> #include <linux/pm_runtime.h> #include <linux/pm_wakeirq.h> @@ -93,7 +95,7 @@ static void __update_runtime_status(struct device *dev, enum rpm_status status) static void pm_runtime_deactivate_timer(struct device *dev) { if (dev->power.timer_expires > 0) { - del_timer(&dev->power.suspend_timer); + hrtimer_cancel(&dev->power.suspend_timer); dev->power.timer_expires = 0; } } @@ -124,12 +126,11 @@ static void pm_runtime_cancel_pending(struct device *dev) * This function may be called either with or without dev->power.lock held. * Either way it can be racy, since power.last_busy may be updated at any time. */ -unsigned long pm_runtime_autosuspend_expiration(struct device *dev) +u64 pm_runtime_autosuspend_expiration(struct device *dev) { int autosuspend_delay; - long elapsed; - unsigned long last_busy; - unsigned long expires = 0; + u64 last_busy, expires = 0; + u64 now = ktime_to_ns(ktime_get()); if (!dev->power.use_autosuspend) goto out; @@ -139,19 +140,9 @@ unsigned long pm_runtime_autosuspend_expiration(struct device *dev) goto out; last_busy = READ_ONCE(dev->power.last_busy); - elapsed = jiffies - last_busy; - if (elapsed < 0) - goto out; /* jiffies has wrapped around. */ - /* - * If the autosuspend_delay is >= 1 second, align the timer by rounding - * up to the nearest second. - */ - expires = last_busy + msecs_to_jiffies(autosuspend_delay); - if (autosuspend_delay >= 1000) - expires = round_jiffies(expires); - expires += !expires; - if (elapsed >= expires - last_busy) + expires = last_busy + autosuspend_delay * NSEC_PER_MSEC; + if (expires <= now) expires = 0; /* Already expired. */ out: @@ -515,7 +506,7 @@ static int rpm_suspend(struct device *dev, int rpmflags) /* If the autosuspend_delay time hasn't expired yet, reschedule. */ if ((rpmflags & RPM_AUTO) && dev->power.runtime_status != RPM_SUSPENDING) { - unsigned long expires = pm_runtime_autosuspend_expiration(dev); + u64 expires = pm_runtime_autosuspend_expiration(dev); if (expires != 0) { /* Pending requests need to be canceled. */ @@ -528,10 +519,20 @@ static int rpm_suspend(struct device *dev, int rpmflags) * expire; pm_suspend_timer_fn() will take care of the * rest. */ - if (!(dev->power.timer_expires && time_before_eq( - dev->power.timer_expires, expires))) { + if (!(dev->power.timer_expires && + dev->power.timer_expires <= expires)) { + /* + * We add a slack of 25% to gather wakeups + * without sacrificing the granularity. + */ + u64 slack = READ_ONCE(dev->power.autosuspend_delay) * + (NSEC_PER_MSEC >> 2); + dev->power.timer_expires = expires; - mod_timer(&dev->power.suspend_timer, expires); + hrtimer_start_range_ns(&dev->power.suspend_timer, + ns_to_ktime(expires), + slack, + HRTIMER_MODE_ABS); } dev->power.timer_autosuspends = 1; goto out; @@ -895,23 +896,25 @@ static void pm_runtime_work(struct work_struct *work) * * Check if the time is right and queue a suspend request. */ -static void pm_suspend_timer_fn(struct timer_list *t) +static enum hrtimer_restart pm_suspend_timer_fn(struct hrtimer *timer) { - struct device *dev = from_timer(dev, t, power.suspend_timer); + struct device *dev = container_of(timer, struct device, power.suspend_timer); unsigned long flags; - unsigned long expires; + u64 expires; spin_lock_irqsave(&dev->power.lock, flags); expires = dev->power.timer_expires; /* If 'expire' is after 'jiffies' we've been called too early. */ - if (expires > 0 && !time_after(expires, jiffies)) { + if (expires > 0 && expires < ktime_to_ns(ktime_get())) { dev->power.timer_expires = 0; rpm_suspend(dev, dev->power.timer_autosuspends ? (RPM_ASYNC | RPM_AUTO) : RPM_ASYNC); } spin_unlock_irqrestore(&dev->power.lock, flags); + + return HRTIMER_NORESTART; } /** @@ -922,6 +925,7 @@ static void pm_suspend_timer_fn(struct timer_list *t) int pm_schedule_suspend(struct device *dev, unsigned int delay) { unsigned long flags; + ktime_t expires; int retval; spin_lock_irqsave(&dev->power.lock, flags); @@ -938,10 +942,10 @@ int pm_schedule_suspend(struct device *dev, unsigned int delay) /* Other scheduled or pending requests need to be canceled. */ pm_runtime_cancel_pending(dev); - dev->power.timer_expires = jiffies + msecs_to_jiffies(delay); - dev->power.timer_expires += !dev->power.timer_expires; + expires = ktime_add(ktime_get(), ms_to_ktime(delay)); + dev->power.timer_expires = ktime_to_ns(expires); dev->power.timer_autosuspends = 0; - mod_timer(&dev->power.suspend_timer, dev->power.timer_expires); + hrtimer_start(&dev->power.suspend_timer, expires, HRTIMER_MODE_ABS); out: spin_unlock_irqrestore(&dev->power.lock, flags); @@ -1491,7 +1495,8 @@ void pm_runtime_init(struct device *dev) INIT_WORK(&dev->power.work, pm_runtime_work); dev->power.timer_expires = 0; - timer_setup(&dev->power.suspend_timer, pm_suspend_timer_fn, 0); + hrtimer_init(&dev->power.suspend_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + dev->power.suspend_timer.function = pm_suspend_timer_fn; init_waitqueue_head(&dev->power.wait_queue); } diff --git a/drivers/base/power/trace.c b/drivers/base/power/trace.c index 1cda505d6a85..b11f47a1e819 100644 --- a/drivers/base/power/trace.c +++ b/drivers/base/power/trace.c @@ -118,9 +118,7 @@ static unsigned int read_magic_time(void) unsigned int val; mc146818_get_time(&time); - pr_info("RTC time: %2d:%02d:%02d, date: %02d/%02d/%02d\n", - time.tm_hour, time.tm_min, time.tm_sec, - time.tm_mon + 1, time.tm_mday, time.tm_year % 100); + pr_info("RTC time: %ptRt, date: %ptRd\n", &time, &time); val = time.tm_year; /* 100 years */ if (val > 100) val -= 100; diff --git a/drivers/base/property.c b/drivers/base/property.c index 240ab5230ff6..8b91ab380d14 100644 --- a/drivers/base/property.c +++ b/drivers/base/property.c @@ -18,236 +18,6 @@ #include <linux/etherdevice.h> #include <linux/phy.h> -struct property_set { - struct device *dev; - struct fwnode_handle fwnode; - const struct property_entry *properties; -}; - -static const struct fwnode_operations pset_fwnode_ops; - -static inline bool is_pset_node(const struct fwnode_handle *fwnode) -{ - return !IS_ERR_OR_NULL(fwnode) && fwnode->ops == &pset_fwnode_ops; -} - -#define to_pset_node(__fwnode) \ - ({ \ - typeof(__fwnode) __to_pset_node_fwnode = __fwnode; \ - \ - is_pset_node(__to_pset_node_fwnode) ? \ - container_of(__to_pset_node_fwnode, \ - struct property_set, fwnode) : \ - NULL; \ - }) - -static const struct property_entry * -pset_prop_get(const struct property_set *pset, const char *name) -{ - const struct property_entry *prop; - - if (!pset || !pset->properties) - return NULL; - - for (prop = pset->properties; prop->name; prop++) - if (!strcmp(name, prop->name)) - return prop; - - return NULL; -} - -static const void *property_get_pointer(const struct property_entry *prop) -{ - switch (prop->type) { - case DEV_PROP_U8: - if (prop->is_array) - return prop->pointer.u8_data; - return &prop->value.u8_data; - case DEV_PROP_U16: - if (prop->is_array) - return prop->pointer.u16_data; - return &prop->value.u16_data; - case DEV_PROP_U32: - if (prop->is_array) - return prop->pointer.u32_data; - return &prop->value.u32_data; - case DEV_PROP_U64: - if (prop->is_array) - return prop->pointer.u64_data; - return &prop->value.u64_data; - case DEV_PROP_STRING: - if (prop->is_array) - return prop->pointer.str; - return &prop->value.str; - default: - return NULL; - } -} - -static void property_set_pointer(struct property_entry *prop, const void *pointer) -{ - switch (prop->type) { - case DEV_PROP_U8: - if (prop->is_array) - prop->pointer.u8_data = pointer; - else - prop->value.u8_data = *((u8 *)pointer); - break; - case DEV_PROP_U16: - if (prop->is_array) - prop->pointer.u16_data = pointer; - else - prop->value.u16_data = *((u16 *)pointer); - break; - case DEV_PROP_U32: - if (prop->is_array) - prop->pointer.u32_data = pointer; - else - prop->value.u32_data = *((u32 *)pointer); - break; - case DEV_PROP_U64: - if (prop->is_array) - prop->pointer.u64_data = pointer; - else - prop->value.u64_data = *((u64 *)pointer); - break; - case DEV_PROP_STRING: - if (prop->is_array) - prop->pointer.str = pointer; - else - prop->value.str = pointer; - break; - default: - break; - } -} - -static const void *pset_prop_find(const struct property_set *pset, - const char *propname, size_t length) -{ - const struct property_entry *prop; - const void *pointer; - - prop = pset_prop_get(pset, propname); - if (!prop) - return ERR_PTR(-EINVAL); - pointer = property_get_pointer(prop); - if (!pointer) - return ERR_PTR(-ENODATA); - if (length > prop->length) - return ERR_PTR(-EOVERFLOW); - return pointer; -} - -static int pset_prop_read_u8_array(const struct property_set *pset, - const char *propname, - u8 *values, size_t nval) -{ - const void *pointer; - size_t length = nval * sizeof(*values); - - pointer = pset_prop_find(pset, propname, length); - if (IS_ERR(pointer)) - return PTR_ERR(pointer); - - memcpy(values, pointer, length); - return 0; -} - -static int pset_prop_read_u16_array(const struct property_set *pset, - const char *propname, - u16 *values, size_t nval) -{ - const void *pointer; - size_t length = nval * sizeof(*values); - - pointer = pset_prop_find(pset, propname, length); - if (IS_ERR(pointer)) - return PTR_ERR(pointer); - - memcpy(values, pointer, length); - return 0; -} - -static int pset_prop_read_u32_array(const struct property_set *pset, - const char *propname, - u32 *values, size_t nval) -{ - const void *pointer; - size_t length = nval * sizeof(*values); - - pointer = pset_prop_find(pset, propname, length); - if (IS_ERR(pointer)) - return PTR_ERR(pointer); - - memcpy(values, pointer, length); - return 0; -} - -static int pset_prop_read_u64_array(const struct property_set *pset, - const char *propname, - u64 *values, size_t nval) -{ - const void *pointer; - size_t length = nval * sizeof(*values); - - pointer = pset_prop_find(pset, propname, length); - if (IS_ERR(pointer)) - return PTR_ERR(pointer); - - memcpy(values, pointer, length); - return 0; -} - -static int pset_prop_count_elems_of_size(const struct property_set *pset, - const char *propname, size_t length) -{ - const struct property_entry *prop; - - prop = pset_prop_get(pset, propname); - if (!prop) - return -EINVAL; - - return prop->length / length; -} - -static int pset_prop_read_string_array(const struct property_set *pset, - const char *propname, - const char **strings, size_t nval) -{ - const struct property_entry *prop; - const void *pointer; - size_t array_len, length; - - /* Find out the array length. */ - prop = pset_prop_get(pset, propname); - if (!prop) - return -EINVAL; - - if (!prop->is_array) - /* The array length for a non-array string property is 1. */ - array_len = 1; - else - /* Find the length of an array. */ - array_len = pset_prop_count_elems_of_size(pset, propname, - sizeof(const char *)); - - /* Return how many there are if strings is NULL. */ - if (!strings) - return array_len; - - array_len = min(nval, array_len); - length = array_len * sizeof(*strings); - - pointer = pset_prop_find(pset, propname, length); - if (IS_ERR(pointer)) - return PTR_ERR(pointer); - - memcpy(strings, pointer, length); - - return array_len; -} - struct fwnode_handle *dev_fwnode(struct device *dev) { return IS_ENABLED(CONFIG_OF) && dev->of_node ? @@ -255,51 +25,6 @@ struct fwnode_handle *dev_fwnode(struct device *dev) } EXPORT_SYMBOL_GPL(dev_fwnode); -static bool pset_fwnode_property_present(const struct fwnode_handle *fwnode, - const char *propname) -{ - return !!pset_prop_get(to_pset_node(fwnode), propname); -} - -static int pset_fwnode_read_int_array(const struct fwnode_handle *fwnode, - const char *propname, - unsigned int elem_size, void *val, - size_t nval) -{ - const struct property_set *node = to_pset_node(fwnode); - - if (!val) - return pset_prop_count_elems_of_size(node, propname, elem_size); - - switch (elem_size) { - case sizeof(u8): - return pset_prop_read_u8_array(node, propname, val, nval); - case sizeof(u16): - return pset_prop_read_u16_array(node, propname, val, nval); - case sizeof(u32): - return pset_prop_read_u32_array(node, propname, val, nval); - case sizeof(u64): - return pset_prop_read_u64_array(node, propname, val, nval); - } - - return -ENXIO; -} - -static int -pset_fwnode_property_read_string_array(const struct fwnode_handle *fwnode, - const char *propname, - const char **val, size_t nval) -{ - return pset_prop_read_string_array(to_pset_node(fwnode), propname, - val, nval); -} - -static const struct fwnode_operations pset_fwnode_ops = { - .property_present = pset_fwnode_property_present, - .property_read_int_array = pset_fwnode_read_int_array, - .property_read_string_array = pset_fwnode_property_read_string_array, -}; - /** * device_property_present - check if a property of a device is present * @dev: Device whose property is being checked @@ -759,223 +484,25 @@ int fwnode_property_get_reference_args(const struct fwnode_handle *fwnode, } EXPORT_SYMBOL_GPL(fwnode_property_get_reference_args); -static void property_entry_free_data(const struct property_entry *p) -{ - const void *pointer = property_get_pointer(p); - size_t i, nval; - - if (p->is_array) { - if (p->type == DEV_PROP_STRING && p->pointer.str) { - nval = p->length / sizeof(const char *); - for (i = 0; i < nval; i++) - kfree(p->pointer.str[i]); - } - kfree(pointer); - } else if (p->type == DEV_PROP_STRING) { - kfree(p->value.str); - } - kfree(p->name); -} - -static int property_copy_string_array(struct property_entry *dst, - const struct property_entry *src) -{ - const char **d; - size_t nval = src->length / sizeof(*d); - int i; - - d = kcalloc(nval, sizeof(*d), GFP_KERNEL); - if (!d) - return -ENOMEM; - - for (i = 0; i < nval; i++) { - d[i] = kstrdup(src->pointer.str[i], GFP_KERNEL); - if (!d[i] && src->pointer.str[i]) { - while (--i >= 0) - kfree(d[i]); - kfree(d); - return -ENOMEM; - } - } - - dst->pointer.str = d; - return 0; -} - -static int property_entry_copy_data(struct property_entry *dst, - const struct property_entry *src) -{ - const void *pointer = property_get_pointer(src); - const void *new; - int error; - - if (src->is_array) { - if (!src->length) - return -ENODATA; - - if (src->type == DEV_PROP_STRING) { - error = property_copy_string_array(dst, src); - if (error) - return error; - new = dst->pointer.str; - } else { - new = kmemdup(pointer, src->length, GFP_KERNEL); - if (!new) - return -ENOMEM; - } - } else if (src->type == DEV_PROP_STRING) { - new = kstrdup(src->value.str, GFP_KERNEL); - if (!new && src->value.str) - return -ENOMEM; - } else { - new = pointer; - } - - dst->length = src->length; - dst->is_array = src->is_array; - dst->type = src->type; - - property_set_pointer(dst, new); - - dst->name = kstrdup(src->name, GFP_KERNEL); - if (!dst->name) - goto out_free_data; - - return 0; - -out_free_data: - property_entry_free_data(dst); - return -ENOMEM; -} - -/** - * property_entries_dup - duplicate array of properties - * @properties: array of properties to copy - * - * This function creates a deep copy of the given NULL-terminated array - * of property entries. - */ -struct property_entry * -property_entries_dup(const struct property_entry *properties) -{ - struct property_entry *p; - int i, n = 0; - - while (properties[n].name) - n++; - - p = kcalloc(n + 1, sizeof(*p), GFP_KERNEL); - if (!p) - return ERR_PTR(-ENOMEM); - - for (i = 0; i < n; i++) { - int ret = property_entry_copy_data(&p[i], &properties[i]); - if (ret) { - while (--i >= 0) - property_entry_free_data(&p[i]); - kfree(p); - return ERR_PTR(ret); - } - } - - return p; -} -EXPORT_SYMBOL_GPL(property_entries_dup); - -/** - * property_entries_free - free previously allocated array of properties - * @properties: array of properties to destroy - * - * This function frees given NULL-terminated array of property entries, - * along with their data. - */ -void property_entries_free(const struct property_entry *properties) -{ - const struct property_entry *p; - - for (p = properties; p->name; p++) - property_entry_free_data(p); - - kfree(properties); -} -EXPORT_SYMBOL_GPL(property_entries_free); - -/** - * pset_free_set - releases memory allocated for copied property set - * @pset: Property set to release - * - * Function takes previously copied property set and releases all the - * memory allocated to it. - */ -static void pset_free_set(struct property_set *pset) -{ - if (!pset) - return; - - property_entries_free(pset->properties); - kfree(pset); -} - -/** - * pset_copy_set - copies property set - * @pset: Property set to copy - * - * This function takes a deep copy of the given property set and returns - * pointer to the copy. Call device_free_property_set() to free resources - * allocated in this function. - * - * Return: Pointer to the new property set or error pointer. - */ -static struct property_set *pset_copy_set(const struct property_set *pset) -{ - struct property_entry *properties; - struct property_set *p; - - p = kzalloc(sizeof(*p), GFP_KERNEL); - if (!p) - return ERR_PTR(-ENOMEM); - - properties = property_entries_dup(pset->properties); - if (IS_ERR(properties)) { - kfree(p); - return ERR_CAST(properties); - } - - p->properties = properties; - return p; -} - /** * device_remove_properties - Remove properties from a device object. * @dev: Device whose properties to remove. * * The function removes properties previously associated to the device - * secondary firmware node with device_add_properties(). Memory allocated - * to the properties will also be released. + * firmware node with device_add_properties(). Memory allocated to the + * properties will also be released. */ void device_remove_properties(struct device *dev) { - struct fwnode_handle *fwnode; - struct property_set *pset; + struct fwnode_handle *fwnode = dev_fwnode(dev); - fwnode = dev_fwnode(dev); if (!fwnode) return; - /* - * Pick either primary or secondary node depending which one holds - * the pset. If there is no real firmware node (ACPI/DT) primary - * will hold the pset. - */ - pset = to_pset_node(fwnode); - if (pset) { - set_primary_fwnode(dev, NULL); - } else { - pset = to_pset_node(fwnode->secondary); - if (pset && dev == pset->dev) - set_secondary_fwnode(dev, NULL); + + if (is_software_node(fwnode->secondary)) { + fwnode_remove_software_node(fwnode->secondary); + set_secondary_fwnode(dev, NULL); } - if (pset && dev == pset->dev) - pset_free_set(pset); } EXPORT_SYMBOL_GPL(device_remove_properties); @@ -985,26 +512,22 @@ EXPORT_SYMBOL_GPL(device_remove_properties); * @properties: Collection of properties to add. * * Associate a collection of device properties represented by @properties with - * @dev as its secondary firmware node. The function takes a copy of - * @properties. + * @dev. The function takes a copy of @properties. + * + * WARNING: The callers should not use this function if it is known that there + * is no real firmware node associated with @dev! In that case the callers + * should create a software node and assign it to @dev directly. */ int device_add_properties(struct device *dev, const struct property_entry *properties) { - struct property_set *p, pset; - - if (!properties) - return -EINVAL; - - pset.properties = properties; + struct fwnode_handle *fwnode; - p = pset_copy_set(&pset); - if (IS_ERR(p)) - return PTR_ERR(p); + fwnode = fwnode_create_software_node(properties, NULL); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); - p->fwnode.ops = &pset_fwnode_ops; - set_secondary_fwnode(dev, &p->fwnode); - p->dev = dev; + set_secondary_fwnode(dev, fwnode); return 0; } EXPORT_SYMBOL_GPL(device_add_properties); @@ -1341,7 +864,7 @@ int fwnode_irq_get(struct fwnode_handle *fwnode, unsigned int index) EXPORT_SYMBOL(fwnode_irq_get); /** - * device_graph_get_next_endpoint - Get next endpoint firmware node + * fwnode_graph_get_next_endpoint - Get next endpoint firmware node * @fwnode: Pointer to the parent firmware node * @prev: Previous endpoint node or %NULL to get the first * diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index b1e9aae9a5d0..2e8f0144f9ab 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -177,17 +177,7 @@ static int rbtree_show(struct seq_file *s, void *ignored) return 0; } -static int rbtree_open(struct inode *inode, struct file *file) -{ - return single_open(file, rbtree_show, inode->i_private); -} - -static const struct file_operations rbtree_fops = { - .open = rbtree_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(rbtree); static void rbtree_debugfs_init(struct regmap *map) { diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 87b562e49a43..19eb454f26c3 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -435,17 +435,7 @@ static int regmap_access_show(struct seq_file *s, void *ignored) return 0; } -static int access_open(struct inode *inode, struct file *file) -{ - return single_open(file, regmap_access_show, inode->i_private); -} - -static const struct file_operations regmap_access_fops = { - .open = access_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(regmap_access); static ssize_t regmap_cache_only_write_file(struct file *file, const char __user *user_buf, diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 429ca8ed7e51..1bd1145ad8b5 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -44,6 +44,8 @@ struct regmap_irq_chip_data { unsigned int irq_reg_stride; unsigned int type_reg_stride; + + bool clear_status:1; }; static inline const @@ -77,6 +79,7 @@ static void regmap_irq_sync_unlock(struct irq_data *data) int i, ret; u32 reg; u32 unmask_offset; + u32 val; if (d->chip->runtime_pm) { ret = pm_runtime_get_sync(map->dev); @@ -85,6 +88,20 @@ static void regmap_irq_sync_unlock(struct irq_data *data) ret); } + if (d->clear_status) { + for (i = 0; i < d->chip->num_regs; i++) { + reg = d->chip->status_base + + (i * map->reg_stride * d->irq_reg_stride); + + ret = regmap_read(map, reg, &val); + if (ret) + dev_err(d->map->dev, + "Failed to clear the interrupt status bits\n"); + } + + d->clear_status = false; + } + /* * If there's been a change in the mask write it back to the * hardware. We rely on the use of the regmap core cache to @@ -157,20 +174,23 @@ static void regmap_irq_sync_unlock(struct irq_data *data) } } - for (i = 0; i < d->chip->num_type_reg; i++) { - if (!d->type_buf_def[i]) - continue; - reg = d->chip->type_base + - (i * map->reg_stride * d->type_reg_stride); - if (d->chip->type_invert) - ret = regmap_irq_update_bits(d, reg, - d->type_buf_def[i], ~d->type_buf[i]); - else - ret = regmap_irq_update_bits(d, reg, - d->type_buf_def[i], d->type_buf[i]); - if (ret != 0) - dev_err(d->map->dev, "Failed to sync type in %x\n", - reg); + /* Don't update the type bits if we're using mask bits for irq type. */ + if (!d->chip->type_in_mask) { + for (i = 0; i < d->chip->num_type_reg; i++) { + if (!d->type_buf_def[i]) + continue; + reg = d->chip->type_base + + (i * map->reg_stride * d->type_reg_stride); + if (d->chip->type_invert) + ret = regmap_irq_update_bits(d, reg, + d->type_buf_def[i], ~d->type_buf[i]); + else + ret = regmap_irq_update_bits(d, reg, + d->type_buf_def[i], d->type_buf[i]); + if (ret != 0) + dev_err(d->map->dev, "Failed to sync type in %x\n", + reg); + } } if (d->chip->runtime_pm) @@ -194,8 +214,30 @@ static void regmap_irq_enable(struct irq_data *data) struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data); struct regmap *map = d->map; const struct regmap_irq *irq_data = irq_to_regmap_irq(d, data->hwirq); + unsigned int mask, type; + + type = irq_data->type.type_falling_val | irq_data->type.type_rising_val; - d->mask_buf[irq_data->reg_offset / map->reg_stride] &= ~irq_data->mask; + /* + * The type_in_mask flag means that the underlying hardware uses + * separate mask bits for rising and falling edge interrupts, but + * we want to make them into a single virtual interrupt with + * configurable edge. + * + * If the interrupt we're enabling defines the falling or rising + * masks then instead of using the regular mask bits for this + * interrupt, use the value previously written to the type buffer + * at the corresponding offset in regmap_irq_set_type(). + */ + if (d->chip->type_in_mask && type) + mask = d->type_buf[irq_data->reg_offset / map->reg_stride]; + else + mask = irq_data->mask; + + if (d->chip->clear_on_unmask) + d->clear_status = true; + + d->mask_buf[irq_data->reg_offset / map->reg_stride] &= ~mask; } static void regmap_irq_disable(struct irq_data *data) @@ -212,27 +254,42 @@ static int regmap_irq_set_type(struct irq_data *data, unsigned int type) struct regmap_irq_chip_data *d = irq_data_get_irq_chip_data(data); struct regmap *map = d->map; const struct regmap_irq *irq_data = irq_to_regmap_irq(d, data->hwirq); - int reg = irq_data->type_reg_offset / map->reg_stride; + int reg; + const struct regmap_irq_type *t = &irq_data->type; - if (!(irq_data->type_rising_mask | irq_data->type_falling_mask)) - return 0; + if ((t->types_supported & type) != type) + return -ENOTSUPP; - d->type_buf[reg] &= ~(irq_data->type_falling_mask | - irq_data->type_rising_mask); + reg = t->type_reg_offset / map->reg_stride; + + if (t->type_reg_mask) + d->type_buf[reg] &= ~t->type_reg_mask; + else + d->type_buf[reg] &= ~(t->type_falling_val | + t->type_rising_val | + t->type_level_low_val | + t->type_level_high_val); switch (type) { case IRQ_TYPE_EDGE_FALLING: - d->type_buf[reg] |= irq_data->type_falling_mask; + d->type_buf[reg] |= t->type_falling_val; break; case IRQ_TYPE_EDGE_RISING: - d->type_buf[reg] |= irq_data->type_rising_mask; + d->type_buf[reg] |= t->type_rising_val; break; case IRQ_TYPE_EDGE_BOTH: - d->type_buf[reg] |= (irq_data->type_falling_mask | - irq_data->type_rising_mask); + d->type_buf[reg] |= (t->type_falling_val | + t->type_rising_val); break; + case IRQ_TYPE_LEVEL_HIGH: + d->type_buf[reg] |= t->type_level_high_val; + break; + + case IRQ_TYPE_LEVEL_LOW: + d->type_buf[reg] |= t->type_level_low_val; + break; default: return -EINVAL; } @@ -430,12 +487,16 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, struct regmap_irq_chip_data *d; int i; int ret = -ENOMEM; + int num_type_reg; u32 reg; u32 unmask_offset; if (chip->num_regs <= 0) return -EINVAL; + if (chip->clear_on_unmask && (chip->ack_base || chip->use_ack)) + return -EINVAL; + for (i = 0; i < chip->num_irqs; i++) { if (chip->irqs[i].reg_offset % map->reg_stride) return -EINVAL; @@ -479,13 +540,14 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, goto err_alloc; } - if (chip->num_type_reg) { - d->type_buf_def = kcalloc(chip->num_type_reg, - sizeof(unsigned int), GFP_KERNEL); + num_type_reg = chip->type_in_mask ? chip->num_regs : chip->num_type_reg; + if (num_type_reg) { + d->type_buf_def = kcalloc(num_type_reg, + sizeof(unsigned int), GFP_KERNEL); if (!d->type_buf_def) goto err_alloc; - d->type_buf = kcalloc(chip->num_type_reg, sizeof(unsigned int), + d->type_buf = kcalloc(num_type_reg, sizeof(unsigned int), GFP_KERNEL); if (!d->type_buf) goto err_alloc; @@ -600,27 +662,21 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, } } - if (chip->num_type_reg) { - for (i = 0; i < chip->num_irqs; i++) { - reg = chip->irqs[i].type_reg_offset / map->reg_stride; - d->type_buf_def[reg] |= chip->irqs[i].type_rising_mask | - chip->irqs[i].type_falling_mask; - } + if (chip->num_type_reg && !chip->type_in_mask) { for (i = 0; i < chip->num_type_reg; ++i) { if (!d->type_buf_def[i]) continue; reg = chip->type_base + (i * map->reg_stride * d->type_reg_stride); - if (chip->type_invert) - ret = regmap_irq_update_bits(d, reg, - d->type_buf_def[i], 0xFF); - else - ret = regmap_irq_update_bits(d, reg, - d->type_buf_def[i], 0x0); - if (ret != 0) { - dev_err(map->dev, - "Failed to set type in 0x%x: %x\n", + + ret = regmap_read(map, reg, &d->type_buf_def[i]); + + if (d->chip->type_invert) + d->type_buf_def[i] = ~d->type_buf_def[i]; + + if (ret) { + dev_err(map->dev, "Failed to get type defaults at 0x%x: %d\n", reg, ret); goto err_alloc; } diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c new file mode 100644 index 000000000000..89ad8dee6ad5 --- /dev/null +++ b/drivers/base/swnode.c @@ -0,0 +1,676 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Software nodes for the firmware node framework. + * + * Copyright (C) 2018, Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/property.h> +#include <linux/slab.h> + +struct software_node { + int id; + struct kobject kobj; + struct fwnode_handle fwnode; + + /* hierarchy */ + struct ida child_ids; + struct list_head entry; + struct list_head children; + struct software_node *parent; + + /* properties */ + const struct property_entry *properties; +}; + +static DEFINE_IDA(swnode_root_ids); +static struct kset *swnode_kset; + +#define kobj_to_swnode(_kobj_) container_of(_kobj_, struct software_node, kobj) + +static const struct fwnode_operations software_node_ops; + +bool is_software_node(const struct fwnode_handle *fwnode) +{ + return !IS_ERR_OR_NULL(fwnode) && fwnode->ops == &software_node_ops; +} + +#define to_software_node(__fwnode) \ + ({ \ + typeof(__fwnode) __to_software_node_fwnode = __fwnode; \ + \ + is_software_node(__to_software_node_fwnode) ? \ + container_of(__to_software_node_fwnode, \ + struct software_node, fwnode) : \ + NULL; \ + }) + +/* -------------------------------------------------------------------------- */ +/* property_entry processing */ + +static const struct property_entry * +property_entry_get(const struct property_entry *prop, const char *name) +{ + if (!prop) + return NULL; + + for (; prop->name; prop++) + if (!strcmp(name, prop->name)) + return prop; + + return NULL; +} + +static void +property_set_pointer(struct property_entry *prop, const void *pointer) +{ + switch (prop->type) { + case DEV_PROP_U8: + if (prop->is_array) + prop->pointer.u8_data = pointer; + else + prop->value.u8_data = *((u8 *)pointer); + break; + case DEV_PROP_U16: + if (prop->is_array) + prop->pointer.u16_data = pointer; + else + prop->value.u16_data = *((u16 *)pointer); + break; + case DEV_PROP_U32: + if (prop->is_array) + prop->pointer.u32_data = pointer; + else + prop->value.u32_data = *((u32 *)pointer); + break; + case DEV_PROP_U64: + if (prop->is_array) + prop->pointer.u64_data = pointer; + else + prop->value.u64_data = *((u64 *)pointer); + break; + case DEV_PROP_STRING: + if (prop->is_array) + prop->pointer.str = pointer; + else + prop->value.str = pointer; + break; + default: + break; + } +} + +static const void *property_get_pointer(const struct property_entry *prop) +{ + switch (prop->type) { + case DEV_PROP_U8: + if (prop->is_array) + return prop->pointer.u8_data; + return &prop->value.u8_data; + case DEV_PROP_U16: + if (prop->is_array) + return prop->pointer.u16_data; + return &prop->value.u16_data; + case DEV_PROP_U32: + if (prop->is_array) + return prop->pointer.u32_data; + return &prop->value.u32_data; + case DEV_PROP_U64: + if (prop->is_array) + return prop->pointer.u64_data; + return &prop->value.u64_data; + case DEV_PROP_STRING: + if (prop->is_array) + return prop->pointer.str; + return &prop->value.str; + default: + return NULL; + } +} + +static const void *property_entry_find(const struct property_entry *props, + const char *propname, size_t length) +{ + const struct property_entry *prop; + const void *pointer; + + prop = property_entry_get(props, propname); + if (!prop) + return ERR_PTR(-EINVAL); + pointer = property_get_pointer(prop); + if (!pointer) + return ERR_PTR(-ENODATA); + if (length > prop->length) + return ERR_PTR(-EOVERFLOW); + return pointer; +} + +static int property_entry_read_u8_array(const struct property_entry *props, + const char *propname, + u8 *values, size_t nval) +{ + const void *pointer; + size_t length = nval * sizeof(*values); + + pointer = property_entry_find(props, propname, length); + if (IS_ERR(pointer)) + return PTR_ERR(pointer); + + memcpy(values, pointer, length); + return 0; +} + +static int property_entry_read_u16_array(const struct property_entry *props, + const char *propname, + u16 *values, size_t nval) +{ + const void *pointer; + size_t length = nval * sizeof(*values); + + pointer = property_entry_find(props, propname, length); + if (IS_ERR(pointer)) + return PTR_ERR(pointer); + + memcpy(values, pointer, length); + return 0; +} + +static int property_entry_read_u32_array(const struct property_entry *props, + const char *propname, + u32 *values, size_t nval) +{ + const void *pointer; + size_t length = nval * sizeof(*values); + + pointer = property_entry_find(props, propname, length); + if (IS_ERR(pointer)) + return PTR_ERR(pointer); + + memcpy(values, pointer, length); + return 0; +} + +static int property_entry_read_u64_array(const struct property_entry *props, + const char *propname, + u64 *values, size_t nval) +{ + const void *pointer; + size_t length = nval * sizeof(*values); + + pointer = property_entry_find(props, propname, length); + if (IS_ERR(pointer)) + return PTR_ERR(pointer); + + memcpy(values, pointer, length); + return 0; +} + +static int +property_entry_count_elems_of_size(const struct property_entry *props, + const char *propname, size_t length) +{ + const struct property_entry *prop; + + prop = property_entry_get(props, propname); + if (!prop) + return -EINVAL; + + return prop->length / length; +} + +static int property_entry_read_int_array(const struct property_entry *props, + const char *name, + unsigned int elem_size, void *val, + size_t nval) +{ + if (!val) + return property_entry_count_elems_of_size(props, name, + elem_size); + switch (elem_size) { + case sizeof(u8): + return property_entry_read_u8_array(props, name, val, nval); + case sizeof(u16): + return property_entry_read_u16_array(props, name, val, nval); + case sizeof(u32): + return property_entry_read_u32_array(props, name, val, nval); + case sizeof(u64): + return property_entry_read_u64_array(props, name, val, nval); + } + + return -ENXIO; +} + +static int property_entry_read_string_array(const struct property_entry *props, + const char *propname, + const char **strings, size_t nval) +{ + const struct property_entry *prop; + const void *pointer; + size_t array_len, length; + + /* Find out the array length. */ + prop = property_entry_get(props, propname); + if (!prop) + return -EINVAL; + + if (prop->is_array) + /* Find the length of an array. */ + array_len = property_entry_count_elems_of_size(props, propname, + sizeof(const char *)); + else + /* The array length for a non-array string property is 1. */ + array_len = 1; + + /* Return how many there are if strings is NULL. */ + if (!strings) + return array_len; + + array_len = min(nval, array_len); + length = array_len * sizeof(*strings); + + pointer = property_entry_find(props, propname, length); + if (IS_ERR(pointer)) + return PTR_ERR(pointer); + + memcpy(strings, pointer, length); + + return array_len; +} + +static void property_entry_free_data(const struct property_entry *p) +{ + const void *pointer = property_get_pointer(p); + size_t i, nval; + + if (p->is_array) { + if (p->type == DEV_PROP_STRING && p->pointer.str) { + nval = p->length / sizeof(const char *); + for (i = 0; i < nval; i++) + kfree(p->pointer.str[i]); + } + kfree(pointer); + } else if (p->type == DEV_PROP_STRING) { + kfree(p->value.str); + } + kfree(p->name); +} + +static int property_copy_string_array(struct property_entry *dst, + const struct property_entry *src) +{ + const char **d; + size_t nval = src->length / sizeof(*d); + int i; + + d = kcalloc(nval, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + for (i = 0; i < nval; i++) { + d[i] = kstrdup(src->pointer.str[i], GFP_KERNEL); + if (!d[i] && src->pointer.str[i]) { + while (--i >= 0) + kfree(d[i]); + kfree(d); + return -ENOMEM; + } + } + + dst->pointer.str = d; + return 0; +} + +static int property_entry_copy_data(struct property_entry *dst, + const struct property_entry *src) +{ + const void *pointer = property_get_pointer(src); + const void *new; + int error; + + if (src->is_array) { + if (!src->length) + return -ENODATA; + + if (src->type == DEV_PROP_STRING) { + error = property_copy_string_array(dst, src); + if (error) + return error; + new = dst->pointer.str; + } else { + new = kmemdup(pointer, src->length, GFP_KERNEL); + if (!new) + return -ENOMEM; + } + } else if (src->type == DEV_PROP_STRING) { + new = kstrdup(src->value.str, GFP_KERNEL); + if (!new && src->value.str) + return -ENOMEM; + } else { + new = pointer; + } + + dst->length = src->length; + dst->is_array = src->is_array; + dst->type = src->type; + + property_set_pointer(dst, new); + + dst->name = kstrdup(src->name, GFP_KERNEL); + if (!dst->name) + goto out_free_data; + + return 0; + +out_free_data: + property_entry_free_data(dst); + return -ENOMEM; +} + +/** + * property_entries_dup - duplicate array of properties + * @properties: array of properties to copy + * + * This function creates a deep copy of the given NULL-terminated array + * of property entries. + */ +struct property_entry * +property_entries_dup(const struct property_entry *properties) +{ + struct property_entry *p; + int i, n = 0; + int ret; + + while (properties[n].name) + n++; + + p = kcalloc(n + 1, sizeof(*p), GFP_KERNEL); + if (!p) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < n; i++) { + ret = property_entry_copy_data(&p[i], &properties[i]); + if (ret) { + while (--i >= 0) + property_entry_free_data(&p[i]); + kfree(p); + return ERR_PTR(ret); + } + } + + return p; +} +EXPORT_SYMBOL_GPL(property_entries_dup); + +/** + * property_entries_free - free previously allocated array of properties + * @properties: array of properties to destroy + * + * This function frees given NULL-terminated array of property entries, + * along with their data. + */ +void property_entries_free(const struct property_entry *properties) +{ + const struct property_entry *p; + + if (!properties) + return; + + for (p = properties; p->name; p++) + property_entry_free_data(p); + + kfree(properties); +} +EXPORT_SYMBOL_GPL(property_entries_free); + +/* -------------------------------------------------------------------------- */ +/* fwnode operations */ + +static struct fwnode_handle *software_node_get(struct fwnode_handle *fwnode) +{ + struct software_node *swnode = to_software_node(fwnode); + + kobject_get(&swnode->kobj); + + return &swnode->fwnode; +} + +static void software_node_put(struct fwnode_handle *fwnode) +{ + struct software_node *swnode = to_software_node(fwnode); + + kobject_put(&swnode->kobj); +} + +static bool software_node_property_present(const struct fwnode_handle *fwnode, + const char *propname) +{ + return !!property_entry_get(to_software_node(fwnode)->properties, + propname); +} + +static int software_node_read_int_array(const struct fwnode_handle *fwnode, + const char *propname, + unsigned int elem_size, void *val, + size_t nval) +{ + struct software_node *swnode = to_software_node(fwnode); + + return property_entry_read_int_array(swnode->properties, propname, + elem_size, val, nval); +} + +static int software_node_read_string_array(const struct fwnode_handle *fwnode, + const char *propname, + const char **val, size_t nval) +{ + struct software_node *swnode = to_software_node(fwnode); + + return property_entry_read_string_array(swnode->properties, propname, + val, nval); +} + +struct fwnode_handle * +software_node_get_parent(const struct fwnode_handle *fwnode) +{ + struct software_node *swnode = to_software_node(fwnode); + + return swnode ? (swnode->parent ? &swnode->parent->fwnode : NULL) : + NULL; +} + +struct fwnode_handle * +software_node_get_next_child(const struct fwnode_handle *fwnode, + struct fwnode_handle *child) +{ + struct software_node *p = to_software_node(fwnode); + struct software_node *c = to_software_node(child); + + if (!p || list_empty(&p->children) || + (c && list_is_last(&c->entry, &p->children))) + return NULL; + + if (c) + c = list_next_entry(c, entry); + else + c = list_first_entry(&p->children, struct software_node, entry); + return &c->fwnode; +} + + +static const struct fwnode_operations software_node_ops = { + .get = software_node_get, + .put = software_node_put, + .property_present = software_node_property_present, + .property_read_int_array = software_node_read_int_array, + .property_read_string_array = software_node_read_string_array, + .get_parent = software_node_get_parent, + .get_next_child_node = software_node_get_next_child, +}; + +/* -------------------------------------------------------------------------- */ + +static int +software_node_register_properties(struct software_node *swnode, + const struct property_entry *properties) +{ + struct property_entry *props; + + props = property_entries_dup(properties); + if (IS_ERR(props)) + return PTR_ERR(props); + + swnode->properties = props; + + return 0; +} + +static void software_node_release(struct kobject *kobj) +{ + struct software_node *swnode = kobj_to_swnode(kobj); + + if (swnode->parent) { + ida_simple_remove(&swnode->parent->child_ids, swnode->id); + list_del(&swnode->entry); + } else { + ida_simple_remove(&swnode_root_ids, swnode->id); + } + + ida_destroy(&swnode->child_ids); + property_entries_free(swnode->properties); + kfree(swnode); +} + +static struct kobj_type software_node_type = { + .release = software_node_release, + .sysfs_ops = &kobj_sysfs_ops, +}; + +struct fwnode_handle * +fwnode_create_software_node(const struct property_entry *properties, + const struct fwnode_handle *parent) +{ + struct software_node *p = NULL; + struct software_node *swnode; + int ret; + + if (parent) { + if (IS_ERR(parent)) + return ERR_CAST(parent); + if (!is_software_node(parent)) + return ERR_PTR(-EINVAL); + p = to_software_node(parent); + } + + swnode = kzalloc(sizeof(*swnode), GFP_KERNEL); + if (!swnode) + return ERR_PTR(-ENOMEM); + + ret = ida_simple_get(p ? &p->child_ids : &swnode_root_ids, 0, 0, + GFP_KERNEL); + if (ret < 0) { + kfree(swnode); + return ERR_PTR(ret); + } + + swnode->id = ret; + swnode->kobj.kset = swnode_kset; + swnode->fwnode.ops = &software_node_ops; + + ida_init(&swnode->child_ids); + INIT_LIST_HEAD(&swnode->entry); + INIT_LIST_HEAD(&swnode->children); + swnode->parent = p; + + if (p) + list_add_tail(&swnode->entry, &p->children); + + ret = kobject_init_and_add(&swnode->kobj, &software_node_type, + p ? &p->kobj : NULL, "node%d", swnode->id); + if (ret) { + kobject_put(&swnode->kobj); + return ERR_PTR(ret); + } + + ret = software_node_register_properties(swnode, properties); + if (ret) { + kobject_put(&swnode->kobj); + return ERR_PTR(ret); + } + + kobject_uevent(&swnode->kobj, KOBJ_ADD); + return &swnode->fwnode; +} +EXPORT_SYMBOL_GPL(fwnode_create_software_node); + +void fwnode_remove_software_node(struct fwnode_handle *fwnode) +{ + struct software_node *swnode = to_software_node(fwnode); + + if (!swnode) + return; + + kobject_put(&swnode->kobj); +} +EXPORT_SYMBOL_GPL(fwnode_remove_software_node); + +int software_node_notify(struct device *dev, unsigned long action) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct software_node *swnode; + int ret; + + if (!fwnode) + return 0; + + if (!is_software_node(fwnode)) + fwnode = fwnode->secondary; + if (!is_software_node(fwnode)) + return 0; + + swnode = to_software_node(fwnode); + + switch (action) { + case KOBJ_ADD: + ret = sysfs_create_link(&dev->kobj, &swnode->kobj, + "software_node"); + if (ret) + break; + + ret = sysfs_create_link(&swnode->kobj, &dev->kobj, + dev_name(dev)); + if (ret) { + sysfs_remove_link(&dev->kobj, "software_node"); + break; + } + kobject_get(&swnode->kobj); + break; + case KOBJ_REMOVE: + sysfs_remove_link(&swnode->kobj, dev_name(dev)); + sysfs_remove_link(&dev->kobj, "software_node"); + kobject_put(&swnode->kobj); + break; + default: + break; + } + + return 0; +} + +static int __init software_node_init(void) +{ + swnode_kset = kset_create_and_add("software_nodes", NULL, kernel_kobj); + if (!swnode_kset) + return -ENOMEM; + return 0; +} +postcore_initcall(software_node_init); + +static void __exit software_node_exit(void) +{ + ida_destroy(&swnode_root_ids); + kset_unregister(swnode_kset); +} +__exitcall(software_node_exit); |