diff options
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/Kconfig | 4 | ||||
-rw-r--r-- | drivers/base/memory.c | 40 | ||||
-rw-r--r-- | drivers/base/node.c | 9 | ||||
-rw-r--r-- | drivers/base/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/base/power/qos-test.c | 117 | ||||
-rw-r--r-- | drivers/base/power/qos.c | 73 | ||||
-rw-r--r-- | drivers/base/power/wakeup.c | 54 |
7 files changed, 261 insertions, 37 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 28b92e3cc570..c3b3b5c0b0da 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -148,6 +148,10 @@ config DEBUG_TEST_DRIVER_REMOVE unusable. You should say N here unless you are explicitly looking to test this functionality. +config PM_QOS_KUNIT_TEST + bool "KUnit Test for PM QoS features" + depends on KUNIT + config HMEM_REPORTING bool default n diff --git a/drivers/base/memory.c b/drivers/base/memory.c index 84c4e1f72cbd..799b43191dea 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -19,15 +19,12 @@ #include <linux/memory.h> #include <linux/memory_hotplug.h> #include <linux/mm.h> -#include <linux/mutex.h> #include <linux/stat.h> #include <linux/slab.h> #include <linux/atomic.h> #include <linux/uaccess.h> -static DEFINE_MUTEX(mem_sysfs_mutex); - #define MEMORY_CLASS_NAME "memory" #define to_memory_block(dev) container_of(dev, struct memory_block, dev) @@ -538,12 +535,7 @@ static ssize_t soft_offline_page_store(struct device *dev, if (kstrtoull(buf, 0, &pfn) < 0) return -EINVAL; pfn >>= PAGE_SHIFT; - if (!pfn_valid(pfn)) - return -ENXIO; - /* Only online pages can be soft-offlined (esp., not ZONE_DEVICE). */ - if (!pfn_to_online_page(pfn)) - return -EIO; - ret = soft_offline_page(pfn_to_page(pfn), 0); + ret = soft_offline_page(pfn, 0); return ret == 0 ? count : ret; } @@ -705,6 +697,8 @@ static void unregister_memory(struct memory_block *memory) * Create memory block devices for the given memory area. Start and size * have to be aligned to memory block granularity. Memory block devices * will be initialized as offline. + * + * Called under device_hotplug_lock. */ int create_memory_block_devices(unsigned long start, unsigned long size) { @@ -718,7 +712,6 @@ int create_memory_block_devices(unsigned long start, unsigned long size) !IS_ALIGNED(size, memory_block_size_bytes()))) return -EINVAL; - mutex_lock(&mem_sysfs_mutex); for (block_id = start_block_id; block_id != end_block_id; block_id++) { ret = init_memory_block(&mem, block_id, MEM_OFFLINE); if (ret) @@ -730,11 +723,12 @@ int create_memory_block_devices(unsigned long start, unsigned long size) for (block_id = start_block_id; block_id != end_block_id; block_id++) { mem = find_memory_block_by_id(block_id); + if (WARN_ON_ONCE(!mem)) + continue; mem->section_count = 0; unregister_memory(mem); } } - mutex_unlock(&mem_sysfs_mutex); return ret; } @@ -742,6 +736,8 @@ int create_memory_block_devices(unsigned long start, unsigned long size) * Remove memory block devices for the given memory area. Start and size * have to be aligned to memory block granularity. Memory block devices * have to be offline. + * + * Called under device_hotplug_lock. */ void remove_memory_block_devices(unsigned long start, unsigned long size) { @@ -754,7 +750,6 @@ void remove_memory_block_devices(unsigned long start, unsigned long size) !IS_ALIGNED(size, memory_block_size_bytes()))) return; - mutex_lock(&mem_sysfs_mutex); for (block_id = start_block_id; block_id != end_block_id; block_id++) { mem = find_memory_block_by_id(block_id); if (WARN_ON_ONCE(!mem)) @@ -763,7 +758,6 @@ void remove_memory_block_devices(unsigned long start, unsigned long size) unregister_memory_block_under_nodes(mem); unregister_memory(mem); } - mutex_unlock(&mem_sysfs_mutex); } /* return true if the memory block is offlined, otherwise, return false */ @@ -797,12 +791,13 @@ static const struct attribute_group *memory_root_attr_groups[] = { }; /* - * Initialize the sysfs support for memory devices... + * Initialize the sysfs support for memory devices. At the time this function + * is called, we cannot have concurrent creation/deletion of memory block + * devices, the device_hotplug_lock is not needed. */ void __init memory_dev_init(void) { int ret; - int err; unsigned long block_sz, nr; /* Validate the configured memory block size */ @@ -813,24 +808,19 @@ void __init memory_dev_init(void) ret = subsys_system_register(&memory_subsys, memory_root_attr_groups); if (ret) - goto out; + panic("%s() failed to register subsystem: %d\n", __func__, ret); /* * Create entries for memory sections that were found * during boot and have been initialized */ - mutex_lock(&mem_sysfs_mutex); for (nr = 0; nr <= __highest_present_section_nr; nr += sections_per_block) { - err = add_memory_block(nr); - if (!ret) - ret = err; + ret = add_memory_block(nr); + if (ret) + panic("%s() failed to add memory block: %d\n", __func__, + ret); } - mutex_unlock(&mem_sysfs_mutex); - -out: - if (ret) - panic("%s() failed: %d\n", __func__, ret); } /** diff --git a/drivers/base/node.c b/drivers/base/node.c index 296546ffed6c..98a31bafc8a2 100644 --- a/drivers/base/node.c +++ b/drivers/base/node.c @@ -496,20 +496,17 @@ static ssize_t node_read_vmstat(struct device *dev, int n = 0; for (i = 0; i < NR_VM_ZONE_STAT_ITEMS; i++) - n += sprintf(buf+n, "%s %lu\n", vmstat_text[i], + n += sprintf(buf+n, "%s %lu\n", zone_stat_name(i), sum_zone_node_page_state(nid, i)); #ifdef CONFIG_NUMA for (i = 0; i < NR_VM_NUMA_STAT_ITEMS; i++) - n += sprintf(buf+n, "%s %lu\n", - vmstat_text[i + NR_VM_ZONE_STAT_ITEMS], + n += sprintf(buf+n, "%s %lu\n", numa_stat_name(i), sum_zone_numa_state(nid, i)); #endif for (i = 0; i < NR_VM_NODE_STAT_ITEMS; i++) - n += sprintf(buf+n, "%s %lu\n", - vmstat_text[i + NR_VM_ZONE_STAT_ITEMS + - NR_VM_NUMA_STAT_ITEMS], + n += sprintf(buf+n, "%s %lu\n", node_stat_name(i), node_page_state(pgdat, i)); return n; diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index ec5bb190b9d0..8fdd0073eeeb 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -4,5 +4,6 @@ obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o wakeup_stats.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o domain_governor.o obj-$(CONFIG_HAVE_CLK) += clock_ops.o +obj-$(CONFIG_PM_QOS_KUNIT_TEST) += qos-test.o ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG diff --git a/drivers/base/power/qos-test.c b/drivers/base/power/qos-test.c new file mode 100644 index 000000000000..3115db08d56b --- /dev/null +++ b/drivers/base/power/qos-test.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP + */ +#include <kunit/test.h> +#include <linux/pm_qos.h> + +/* Basic test for aggregating two "min" requests */ +static void freq_qos_test_min(struct kunit *test) +{ + struct freq_constraints qos; + struct freq_qos_request req1, req2; + int ret; + + freq_constraints_init(&qos); + memset(&req1, 0, sizeof(req1)); + memset(&req2, 0, sizeof(req2)); + + ret = freq_qos_add_request(&qos, &req1, FREQ_QOS_MIN, 1000); + KUNIT_EXPECT_EQ(test, ret, 1); + ret = freq_qos_add_request(&qos, &req2, FREQ_QOS_MIN, 2000); + KUNIT_EXPECT_EQ(test, ret, 1); + + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), 2000); + + ret = freq_qos_remove_request(&req2); + KUNIT_EXPECT_EQ(test, ret, 1); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), 1000); + + ret = freq_qos_remove_request(&req1); + KUNIT_EXPECT_EQ(test, ret, 1); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), + FREQ_QOS_MIN_DEFAULT_VALUE); +} + +/* Test that requests for MAX_DEFAULT_VALUE have no effect */ +static void freq_qos_test_maxdef(struct kunit *test) +{ + struct freq_constraints qos; + struct freq_qos_request req1, req2; + int ret; + + freq_constraints_init(&qos); + memset(&req1, 0, sizeof(req1)); + memset(&req2, 0, sizeof(req2)); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MAX), + FREQ_QOS_MAX_DEFAULT_VALUE); + + ret = freq_qos_add_request(&qos, &req1, FREQ_QOS_MAX, + FREQ_QOS_MAX_DEFAULT_VALUE); + KUNIT_EXPECT_EQ(test, ret, 0); + ret = freq_qos_add_request(&qos, &req2, FREQ_QOS_MAX, + FREQ_QOS_MAX_DEFAULT_VALUE); + KUNIT_EXPECT_EQ(test, ret, 0); + + /* Add max 1000 */ + ret = freq_qos_update_request(&req1, 1000); + KUNIT_EXPECT_EQ(test, ret, 1); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MAX), 1000); + + /* Add max 2000, no impact */ + ret = freq_qos_update_request(&req2, 2000); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MAX), 1000); + + /* Remove max 1000, new max 2000 */ + ret = freq_qos_remove_request(&req1); + KUNIT_EXPECT_EQ(test, ret, 1); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MAX), 2000); +} + +/* + * Test that a freq_qos_request can be added again after removal + * + * This issue was solved by commit 05ff1ba412fd ("PM: QoS: Invalidate frequency + * QoS requests after removal") + */ +static void freq_qos_test_readd(struct kunit *test) +{ + struct freq_constraints qos; + struct freq_qos_request req; + int ret; + + freq_constraints_init(&qos); + memset(&req, 0, sizeof(req)); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), + FREQ_QOS_MIN_DEFAULT_VALUE); + + /* Add */ + ret = freq_qos_add_request(&qos, &req, FREQ_QOS_MIN, 1000); + KUNIT_EXPECT_EQ(test, ret, 1); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), 1000); + + /* Remove */ + ret = freq_qos_remove_request(&req); + KUNIT_EXPECT_EQ(test, ret, 1); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), + FREQ_QOS_MIN_DEFAULT_VALUE); + + /* Add again */ + ret = freq_qos_add_request(&qos, &req, FREQ_QOS_MIN, 2000); + KUNIT_EXPECT_EQ(test, ret, 1); + KUNIT_EXPECT_EQ(test, freq_qos_read_value(&qos, FREQ_QOS_MIN), 2000); +} + +static struct kunit_case pm_qos_test_cases[] = { + KUNIT_CASE(freq_qos_test_min), + KUNIT_CASE(freq_qos_test_maxdef), + KUNIT_CASE(freq_qos_test_readd), + {}, +}; + +static struct kunit_suite pm_qos_test_module = { + .name = "qos-kunit-test", + .test_cases = pm_qos_test_cases, +}; +kunit_test_suite(pm_qos_test_module); diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c index 350dcafd751f..8e93167f1783 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -115,10 +115,20 @@ s32 dev_pm_qos_read_value(struct device *dev, enum dev_pm_qos_req_type type) spin_lock_irqsave(&dev->power.lock, flags); - if (type == DEV_PM_QOS_RESUME_LATENCY) { + switch (type) { + case DEV_PM_QOS_RESUME_LATENCY: ret = IS_ERR_OR_NULL(qos) ? PM_QOS_RESUME_LATENCY_NO_CONSTRAINT : pm_qos_read_value(&qos->resume_latency); - } else { + break; + case DEV_PM_QOS_MIN_FREQUENCY: + ret = IS_ERR_OR_NULL(qos) ? PM_QOS_MIN_FREQUENCY_DEFAULT_VALUE + : freq_qos_read_value(&qos->freq, FREQ_QOS_MIN); + break; + case DEV_PM_QOS_MAX_FREQUENCY: + ret = IS_ERR_OR_NULL(qos) ? PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE + : freq_qos_read_value(&qos->freq, FREQ_QOS_MAX); + break; + default: WARN_ON(1); ret = 0; } @@ -159,6 +169,10 @@ static int apply_constraint(struct dev_pm_qos_request *req, req->dev->power.set_latency_tolerance(req->dev, value); } break; + case DEV_PM_QOS_MIN_FREQUENCY: + case DEV_PM_QOS_MAX_FREQUENCY: + ret = freq_qos_apply(&req->data.freq, action, value); + break; case DEV_PM_QOS_FLAGS: ret = pm_qos_update_flags(&qos->flags, &req->data.flr, action, value); @@ -209,6 +223,8 @@ static int dev_pm_qos_constraints_allocate(struct device *dev) c->no_constraint_value = PM_QOS_LATENCY_TOLERANCE_NO_CONSTRAINT; c->type = PM_QOS_MIN; + freq_constraints_init(&qos->freq); + INIT_LIST_HEAD(&qos->flags.list); spin_lock_irq(&dev->power.lock); @@ -269,6 +285,20 @@ void dev_pm_qos_constraints_destroy(struct device *dev) memset(req, 0, sizeof(*req)); } + c = &qos->freq.min_freq; + plist_for_each_entry_safe(req, tmp, &c->list, data.freq.pnode) { + apply_constraint(req, PM_QOS_REMOVE_REQ, + PM_QOS_MIN_FREQUENCY_DEFAULT_VALUE); + memset(req, 0, sizeof(*req)); + } + + c = &qos->freq.max_freq; + plist_for_each_entry_safe(req, tmp, &c->list, data.freq.pnode) { + apply_constraint(req, PM_QOS_REMOVE_REQ, + PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE); + memset(req, 0, sizeof(*req)); + } + f = &qos->flags; list_for_each_entry_safe(req, tmp, &f->list, data.flr.node) { apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE); @@ -314,11 +344,22 @@ static int __dev_pm_qos_add_request(struct device *dev, ret = dev_pm_qos_constraints_allocate(dev); trace_dev_pm_qos_add_request(dev_name(dev), type, value); - if (!ret) { - req->dev = dev; - req->type = type; + if (ret) + return ret; + + req->dev = dev; + req->type = type; + if (req->type == DEV_PM_QOS_MIN_FREQUENCY) + ret = freq_qos_add_request(&dev->power.qos->freq, + &req->data.freq, + FREQ_QOS_MIN, value); + else if (req->type == DEV_PM_QOS_MAX_FREQUENCY) + ret = freq_qos_add_request(&dev->power.qos->freq, + &req->data.freq, + FREQ_QOS_MAX, value); + else ret = apply_constraint(req, PM_QOS_ADD_REQ, value); - } + return ret; } @@ -382,6 +423,10 @@ static int __dev_pm_qos_update_request(struct dev_pm_qos_request *req, case DEV_PM_QOS_LATENCY_TOLERANCE: curr_value = req->data.pnode.prio; break; + case DEV_PM_QOS_MIN_FREQUENCY: + case DEV_PM_QOS_MAX_FREQUENCY: + curr_value = req->data.freq.pnode.prio; + break; case DEV_PM_QOS_FLAGS: curr_value = req->data.flr.flags; break; @@ -507,6 +552,14 @@ int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier, ret = blocking_notifier_chain_register(dev->power.qos->resume_latency.notifiers, notifier); break; + case DEV_PM_QOS_MIN_FREQUENCY: + ret = freq_qos_add_notifier(&dev->power.qos->freq, + FREQ_QOS_MIN, notifier); + break; + case DEV_PM_QOS_MAX_FREQUENCY: + ret = freq_qos_add_notifier(&dev->power.qos->freq, + FREQ_QOS_MAX, notifier); + break; default: WARN_ON(1); ret = -EINVAL; @@ -546,6 +599,14 @@ int dev_pm_qos_remove_notifier(struct device *dev, ret = blocking_notifier_chain_unregister(dev->power.qos->resume_latency.notifiers, notifier); break; + case DEV_PM_QOS_MIN_FREQUENCY: + ret = freq_qos_remove_notifier(&dev->power.qos->freq, + FREQ_QOS_MIN, notifier); + break; + case DEV_PM_QOS_MAX_FREQUENCY: + ret = freq_qos_remove_notifier(&dev->power.qos->freq, + FREQ_QOS_MAX, notifier); + break; default: WARN_ON(1); ret = -EINVAL; diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 5817b51d2b15..70a9edb5f525 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -248,6 +248,60 @@ void wakeup_source_unregister(struct wakeup_source *ws) EXPORT_SYMBOL_GPL(wakeup_source_unregister); /** + * wakeup_sources_read_lock - Lock wakeup source list for read. + * + * Returns an index of srcu lock for struct wakeup_srcu. + * This index must be passed to the matching wakeup_sources_read_unlock(). + */ +int wakeup_sources_read_lock(void) +{ + return srcu_read_lock(&wakeup_srcu); +} +EXPORT_SYMBOL_GPL(wakeup_sources_read_lock); + +/** + * wakeup_sources_read_unlock - Unlock wakeup source list. + * @idx: return value from corresponding wakeup_sources_read_lock() + */ +void wakeup_sources_read_unlock(int idx) +{ + srcu_read_unlock(&wakeup_srcu, idx); +} +EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock); + +/** + * wakeup_sources_walk_start - Begin a walk on wakeup source list + * + * Returns first object of the list of wakeup sources. + * + * Note that to be safe, wakeup sources list needs to be locked by calling + * wakeup_source_read_lock() for this. + */ +struct wakeup_source *wakeup_sources_walk_start(void) +{ + struct list_head *ws_head = &wakeup_sources; + + return list_entry_rcu(ws_head->next, struct wakeup_source, entry); +} +EXPORT_SYMBOL_GPL(wakeup_sources_walk_start); + +/** + * wakeup_sources_walk_next - Get next wakeup source from the list + * @ws: Previous wakeup source object + * + * Note that to be safe, wakeup sources list needs to be locked by calling + * wakeup_source_read_lock() for this. + */ +struct wakeup_source *wakeup_sources_walk_next(struct wakeup_source *ws) +{ + struct list_head *ws_head = &wakeup_sources; + + return list_next_or_null_rcu(ws_head, &ws->entry, + struct wakeup_source, entry); +} +EXPORT_SYMBOL_GPL(wakeup_sources_walk_next); + +/** * device_wakeup_attach - Attach a wakeup source object to a device object. * @dev: Device to handle. * @ws: Wakeup source object to attach to @dev. |